多快好省,京東優選小程序快車等你上車~

timg.jpg

做者:Horace
聲明:本項目只做爲學習使用,歡迎你們一塊兒交流~
ps:新人小白一枚,若有錯誤,歡迎指出~

寫在前面

過年有大把的時光,爲什麼一直宅在家裏不出家門看着電腦,這到底是道德的淪喪仍是人性的泯滅...這一切都還得從一隻蝙蝠提及... html

咳咳,好了不皮了,言歸正傳。微信推出的小程序可謂是輕量又強大,因此最近我也開始了小程序的學習,學了挺多也看了不少文檔,但總以爲本身沒學到什麼,感受很迷茫。正所謂實踐出真知,因此我選擇了從高仿別人的小程序開始,選來選去最後選擇了京東優選這個小程序(絕對不是由於它的界面清爽!)。vue

開發工具

效果速覽

廢話很少說,咱先來搞一波圖片看看,點這裏查看更多圖片 git




項目結構

這個項目我使用的是普通的開發,把全部的數據都放在了json-server中模擬。
可能不少人會以爲很奇怪,但這是由於我發現easy mock的網站常常打不開請求失敗很是的不方便,因此我暫時沒有選擇mock數據,後期有時間我會把數據挪到easy mock上。github

|-jd_recommend  項目名
    |-api  模擬數據接口
        |-db.json 模擬的數據
    |-assets  資源文件
        |-icons   圖標資源
        |-images  圖片資源
    |-components  組件模塊
        |-navigationBar  自定義導航欄
        |-toast          自定義toast
        |-stepper        有贊vant步進器組件
        |-...            其餘小程序所需組件
    |-pages  項目頁面
        |-about          關於頁面
        |-account        個人訂單頁面
        |-afterMarket    售後類型頁面
        |-appointment    個人預定頁面
        |-buy            填寫訂單信息頁面
        |-commentDetail  評論詳情頁面
        |-discount       優惠券頁面
        |-explore        發現頁面
        |-feedback       反饋頁面
        |-fix            售後頁面
        |-goodsDetail    值得買優惠詳情頁面
        |-index          首頁
        |-jd             京東商品詳情頁面
        |-login          登陸頁面
        |-orderDetail    訂單詳情頁面
        |-seller         客服頁面
        |-service        退換/售後頁面
        |-shopCart       購物車頁面
        |-user           我的中心頁面
    |-style  公共樣式
        |-comment.wxss    評論區樣式
        |-goodsCard.wxss  商品卡片樣式
        |-nav.wxss        導航欄樣式
        |-orderCard.wxss  訂單卡片樣式
        |-popright.wxss   篩選框樣式
        |-popup.wxss      上拉菜單樣式
    |-utils  公共模塊
        |-util.js  promise封裝接口
    app.js         全局js
    app.json       全局json配置
    app.wxss       全局wxss

自定義組件

大部分人寫小程序確定要涉及修改navigationBar的title,微信小程序開發內置了這個組件,能夠直接在app.json中配置。可是,自帶的navigationBar的樣子是固定的,你確定見過長成下面這樣的navigationBar:數據庫


相比平時常見的navigationBar,它左上角多了一個返回主頁的按鈕,這對於有多級頁面的小程序來講是很是必要的,否則訪問的層級太深用戶不知道怎麼返回主頁。然而,小程序開發自帶並無這個樣子的,好在能夠自定義,接下來咱們就來自定義一個。json

navigationBar

首先,咱們構建一下頁面的結構:小程序

<!-- components/navigationBar/index.wxml -->
<view class='nav-wrap' style='height: {{height*2 + 20}}px;'>
    <!-- 導航欄 中間的標題 -->
    <view class='nav-title' style='line-height: {{height*2 + 44}}px;'>{{navbarData.title}}</view>
    <view style='display: flex; justify-content: space-around;flex-direction: column'>
        <!-- 導航欄  左上角的返回按鈕和home按鈕 -->
        <!-- 其中wx:if='{{navbarData.showCapsule}}' 是控制左上角按鈕的顯示隱藏,首頁不顯示 -->
        <view class='nav-capsule' style='height: {{height*2 + 44}}px;' wx:if='{{navbarData.showCapsule}}'>
            <!-- 左上角的返回按鈕,wx:if='{{!share}}'空制返回按鈕顯示 -->
            <view bindtap='_navback'>
                <image src='../../assets/icons/back.png' mode='aspectFill' class='back-pre'></image>
            </view>
            <view class='navbar-v-line' wx:if='{{!share}}'></view>
            <view bindtap='_backhome'>
                <image src='../../assets/icons/back_home.png' mode='aspectFill' class='back-home'></image>
            </view>
        </view>
    </view>
</view>

這就是一個很普通的頁面結構,值得注意的是,它的高度是根據獲取的設備的高度來肯定的。
接下來又到了切圖仔上線的時候了(誤):微信小程序

/* components/navigationBar/index.wxss */
/* 頂部要固定定位   標題要居中   自定義按鈕和標題要和右邊微信原生的膠囊上下對齊 */
.nav-wrap {
    position: fixed;
    width: 100%;
    top: 0;
    background: #fff;
    color: #000;
    z-index: 9999999;
    border-bottom: 1rpx solid #EFEFF4;
}
/* 標題要居中 */
.nav-title {
    position: absolute;
    text-align: center;
    max-width: 400rpx;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
    font-size: 36rpx;
    color: #2c2b2b;
    /* font-weight: 600; */
}
.nav-capsule {
    display: flex;
    align-items: center;
    margin-left: 30rpx;
    width: 140rpx;
    justify-content: space-between;
    height: 100%;
}
.navbar-v-line {
    width: 1px;
    height: 32rpx;
    background-color: #e5e5e5;
}
.back-pre, .back-home {
    width: 32rpx;
    height: 36rpx;
    margin-top: 4rpx;
    padding: 10rpx;
}
.nav-capsule .back-home {
    width: 36rpx;
    height: 40rpx;
    margin-top: 3rpx;
}
// components/navigationBar/index.js
const app = getApp()
Component({
  properties: {
    navbarData: {   //navbarData   由父頁面傳遞的數據,變量名字自命名
      type: Object,
      value: {},
      // observer: function (newVal, oldVal) { }
    }
  },
  data: {
    height: '',
    //默認值  默認顯示左上角
    navbarData: {
      showCapsule: 1
    }
  },
  attached: function () {
    // 定義導航欄的高度   方便對齊
    this.setData({
      height: app.globalData.height
    })
  },
  methods: {
    // 返回上一頁面
    _navback() {
      wx.navigateBack()
    },
    //返回到首頁
    _backhome() {
      wx.switchTab({
        url: '/pages/index/index',
      })
    }
  }
})

京東優選小程序這裏的兩個按鈕都是返回首頁,我在開發的時候以爲不對勁,因此我改過來了。
在這裏還去取了一下全局定義的變量,也就是獲取的設備頂部窗口的高度(不一樣設備窗口高度不同,根據這個來設置自定義導航欄的高度),在app.js中要定義一下:api

app.js
App({
  onLaunch: function () {
    ......
    wx.getSystemInfo({
      success: (res) => {
        this.globalData.height = res.statusBarHeight
      }
    })
  },
  globalData: {
    ...
    height: 0
  }
})

記得自定組件的時候必定要在json中寫成自定義組件promise

// components/navigationBar/index.json
{
  "component": true
}

接下來就是調用該組件了

<navigationBar navbar-data='{{navbarData}}'></navigationBar>

別忘了在要引用頁面的json中引入該組件

"usingComponents": {
    "navigationBar": "../../components/navigationBar/index"
 }

Toast

Toast一樣也是小程序開發已經作好給你用的了,雖然它能夠支持替換裏面的圖標,可是你會發現很雞肋的一點是,若是你想顯示兩行文字你就沒辦法作到了。我在開發過程當中也搜索過相關的實現方法,找到了大部分是說在要換行的文字後背加上rn就能實現了,可是我本身親測無效,因此實在忍不住也本身作了一個。

自定義Toast

<!-- components/toast/index.wxml -->
<!-- 距離頂部高度由業務須要動態肯定 -->
<view class='mask' hidden="{{hide}}" style='top: {{toastData.top}}'>
    <image class="image" src='../../assets/icons/{{toastData.icon}}.png' mode='aspectFit'></image>
    <view class="info">
        <view class='info1' wx:if="{{toastData.info1 != ''}}">{{toastData.info1}}</view>
        <view class="info2" wx:if="{{toastData.info2 != ''}}">{{toastData.info2}}</view>
    </view>
</view>
/* components/toast/index.wxss */
.mask {
    width: 440rpx;
    height: auto;
    border-radius: 20rpx;
    position: fixed;
    left: 155rpx;
    z-index: 1000;
    background: rgba(0, 0, 0, 0.6);
    text-align: center;
    padding-bottom: 30rpx;
}
.image {
    z-index: 1000;
    width: 80rpx;
    height: 80rpx;
    padding-top: 30rpx;
    padding-bottom: 20rpx;
}
.info1, .info2 {
    color: #ffffff;
    font-size: 32rpx;
}
.info {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
// components/toast/index.js
Component({
  properties: {              //定義組件屬性
    toastData: {           //用來顯示提示信息
      type: Object,         // 類型(必填),目前接受的類型包括:String, Number, Boolean, Object, Array, null(表示任意類型)
      value: {
        icon: 'success'
      }     // 屬性初始值(可選),若是未指定則會根據類型選擇一個
    },
  },
  data: {
    hide: true
  },
  methods: {
    showToast: function () {
      let that = this;
      that.setData({
        hide: false
      });
    },
    hideToast: function (e) {
      let that = this;
      setTimeout(function () {
        that.setData({
          hide: true
        });
      }, 2000);
    }
  }
})

這裏給組件定義了兩個方法,是用來顯示和隱藏Toast的。這裏要注意一下,調用給自定義組件定義方法要先在頁面上獲取該組件

<toast id="toast" toast-data="{{toastData}}"></toast>
Page({
   data: {
    toastData: { // toast須要的參數
      icon: "success",
      info1: "加入購物車成功",
      top: "50%"
    }
  },
  onReady() {
    this.toast = this.selectComponent("#toast");
  }
})

而後在須要觸發Toast的事件中寫上這兩句:

this.toast.showToast()
this.toast.hideToast()

功能實現

導航

所謂導航,也是很常見了,就是根據選擇欄目的不一樣,顯示不一樣的類別內容。例如:


功能要求:

  1. 點擊導航欄目,顯示對應的欄目數據。
  2. 若是欄目中沒有東西,要顯示對應的提示信息。

實現它的功能並不難,直接sroll-view往上懟。我的以爲,京東優選在這裏有一點不足的地方就是,若是點擊了偏右側的導航欄目的話,導航條不會跟着右移顯示後面的項目,可能它的開發者有不同的想法吧。

<view class="navigator">
  <scroll-view scroll-x="true" class="nav" scroll-left="{{navScrollLeft}}" scroll-with-animation="{{true}}">
    <block wx:for="{{navData}}" wx:for-index="id" wx:for-item="navItem" wx:key="id">
      <view class="nav-item {{currentTab == id?'active':''}}" data-name="{{navItem.name}}" data-current="{{id}}" bindtap="switchNav">
        {{navItem.name}}
      </view>
    </block>
  </scroll-view>
</view>

經過js能夠實現動態的填放數據,這裏設置的current就是當前選擇的欄目,能夠根據這個改變樣式等。

switchNav(e) {
        const cur = e.currentTarget.dataset.current; // Number
        let currData = []
        // console.log(cur.toString());
        if (cur === 0) {
            currData = this.data.goods
        } else {
            this.data.goods.forEach(val => {
                if (val.category === cur.toString()) {
                    currData.push(val)
                }
            })
        }
        this.setData({
            currentTab: cur,
            category: cur,
            currData
        });
}

若是是要實現點擊以後自動向點擊的方法滑出顯示更多的內容,能夠經過動態改變navScrollLeft的值去實現,這裏我就不細說了,不過我在實現的時候仍是花了一番功夫,實現的不是很好因此就沒有放在代碼裏,若是你之後想作出這種效果的導航欄建議去網上搜一搜demo看懂了以後借過來用一用,畢竟傳說程序猿最高的境界是複製粘貼,狗頭(誤)

上拉菜單和篩選框

這兩個比較類似,只是拉出的位置不同,這裏我就舉一個篩選框的例子,咱們先看看它長啥樣:


咱們先看看結構,這裏我省略了中間的一些內容:

<!-- 點擊篩選彈出的選擇菜單 -->
<view class="float {{isRuleTrue?'isRuleShow':'isRuleHide'}}">
    <view class="animation-element" animation="{{animation}}">
        ...中間本身放的具體內容...
        <!-- 底部的兩個按鈕 -->
        <view class='bottom'>
            <view class="animation-reset" bindtap="reset">重置</view>
            <view class="animation-button" bindtap="success">肯定</view>
        </view>
    </view>
</view>
/* 篩選彈框 */
/* 彈框的佈局 */
.isRuleShow {
    display: block;
}
.isRuleHide {
    display: none;
}
.float {
    height: 100%;
    width: 100%;
    position: fixed;
    z-index: 999;
    top: 0;
    left: 0;
    /* 彈出後背景的顏色 */
    background-color: rgba(0, 0, 0, 0.5);
    padding-left: 30rpx;
    padding-left: 30rpx;
    /* margin-top:80rpx; */
}
.animation-element {
    width: 600rpx;
    height: 100%;
    padding-left: 30rpx;
    padding-right: 30rpx;
    background-color: #ffffff;
    border: 1px solid #f3f0f0;
    position: absolute;
    right: -550rpx;
    box-sizing: border-box;
}
.bottom {
    width: 600rpx;
    height: 110rpx;
    font-size: 32rpx;
    padding-top: 55rpx;
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    display: flex;
}
.animation-reset {
    width: 50%;
    height: 100%;
    line-height: 50%;
    text-align: center;
    padding-top: 55rpx;
    border-top: 1px solid #EFEFF4;
}
.animation-button {
    width: 50%;
    height: 100%;
    line-height: 50%;
    color: #fff;
    text-align: center;
    background-color: #ED7358;
    padding-top: 55rpx;
}

重點是它的顯示和隱藏事件,須要用到animation,若是有不熟悉animation,能夠去參考一些資料,或者是官方文檔。一樣,我也去掉了我實現其餘業務的一些內容。

showSelect() { // 顯示選擇菜單
    this.setData({
      isRuleTrue: true
    })
    // 左偏移245 step表示一個動做的開始
    this.animation.translate(-245, 0).step()
    this.setData({ animation: this.animation.export() })
 },
 success: function () { // 關閉選擇菜單
    this.setData({
      isRuleTrue: false,
      selected: true
    })
    this.animation.translate(0, 0).step()
    this.setData({ animation: this.animation.export() })
 },

購物車邏輯


要實現這樣的效果並不困難,須要本身思路清晰,不能被繞進去了。實現加入購物車並不難,細節是購物車圖標右上角的數字要根據加入購物車的數量進行動態的改變,還要注意若是是同一件商品就不須要添加新的,只須要修改原來的數量。

在這裏我使用的是小程序的wx.setStorage()實現的:

<view class='bottom'>
    <view class="animation-reset" bindtap="addCart">加入購物車</view>
    <view class="animation-button" bindtap="buy">當即購買</view>
</view>
addCart() { // 加入購物車
    this.setData({
      toastData: { // toast須要的參數
        icon: "success",
        info1: "加入購物車成功",
        top: "50%"
      }
    })
    this.toast.showToast()
    this.toast.hideToast()
    this.hideModal()
    // 真正實現添加購物車的部分
    let cartData = wx.getStorageSync('cart') || [];
    let count = 0
    cartData.map(val => {
      if (val.title === this.data.currData[0].title && val.type === this.data.choose_value) {
        val.num += this.data.num
        count++ // 標記是否有找到相同的商品
      }
    })
    if (count === 0) { // 沒找到 添加新的商品信息進購物車
      let data = {
        id: this.data.currData[0]._id,
        title: this.data.currData[0].title,
        weight: "0.78kg",
        type: this.data.choose_value,
        num: this.data.num,
        price: this.data.currData[0].plain_price,
        img: this.data.currData[0].thumb,
        discount: 20,
        select: true // 是否選中,方便後續計算總價
      }
      cartData.push(data)
    }
    // 刷新購物車圖標上的數量
    let allNum = 0
    cartData.forEach(val => {
      allNum += val.num
    });
    this.setData({
      allNum
    })
    wx.setStorage({
      key: 'cart',
      data: cartData
    })
 },

這裏你能夠根據本身的開發來決定方式,若是你使用的是雲開發的話,能夠選擇把數據存進雲數據庫裏。

回到頂部


這也是一個老生常談的功能,當你滑到頁面比較後的位置的時候須要快速回頂。這裏要記住,用swiper實現。首先是在頁面上擼一個回到頂部的圖標出來:

<!-- 滑動一段距離後顯示返回頂部的按鈕 -->
<scroll-view class="bigWrap" 
    scroll-y="true" 
    scroll-top="{{scrollTop}}" 
    bindscroll="scroll" 
    style="position: absolute; left: 0; top:0; bottom: 0; right: -999rpx;">
<view class="goTop" bindtap="goTop" wx:if="{{&& floorstatus}}">
    <image class="icon_goTop" src="../../assets/icons/back_to_top.png"></image>
</view>
</scroll-view>

{{scrollTop}}用來表示滑動的時候距離頂部的位置。它的樣式也很簡單,使用固定定位把它定在屏幕上,這裏必定要注意頁面的層級,否則它可能會被其餘組件給遮擋掉!

/* 回到頂部  */
.goTop {
    position: fixed;
    bottom: 200rpx;
    right: 20rpx;
    width: 65rpx;
    height: 65rpx;
    border: 1px solid #DDDDDD;
    border-radius: 50%;
    background-color: #fff;
    text-align: center;
}
.icon_goTop {
    width: 40rpx;
    height: 40rpx;
    padding-top: 12rpx;
    padding-left: 2rpx;
}
goTop(e) { // 回到頂部
    this.setData({
      scrollTop: 0
    })
 }

你確定也注意到了,當滑到了必定距離的時候它才顯示出來,這就要靠swiper綁定的滾動事件了:

scroll(e) { // 滾動事件
    // 容器滾動時將此時的滾動距離賦值給 this.data.scrollTop
    let floorstatus = false
    if (e.detail.scrollTop > 300) {
      floorstatus = true
    }
    this.setData({
      floorstatus
    })
}

功能大體先說這麼一點,可能在大牛看起來都是些很容易不起眼的功能,可是對應我這個初學者來講仍是有點困難的,但願若是有大牛看了個人一些功能的實現以後我不會被罵死。

值得注意的一點

作太小程序開發或者是vue等開發的人必定聽過事件冒泡這個名詞:子元素的事件觸發了父元素的事件,例如點擊事件。我就是那個幸運鵝,我在開發的時候就遇到了這個狀況。
在購物車中點擊商品能夠跳轉商品詳情,可是我一開始把跳轉事件綁定在了每一個商品卡片上,這樣就致使了點擊修改商品數量的時候修改了數字可是也會直接跳轉商品詳情,好比下面這樣...

這就很不友好了,用戶體驗不好,關於事件冒泡,微信小程序的解決方法是把bindtap替換成catchtap,這樣能夠阻止子元素事件向上冒泡。

然而巧的是,我就是那個最幸運的鵝,步進器我用的是有贊Vant Weapp組件庫裏的,我搜索了不少資料都沒有找到有效的解決方案,差點就放棄使用組件庫了,好在最後發現京東優選小程序購物車綁定的跳轉事件是在商品的圖片和標題上。

這一點仍是比較重要的,因此你們在開發的時候必定要考慮事件的冒泡,這也是我把它放在最後來寫的緣由。

寫在後面

最後,我想說的是小程序開發真的不容易,開發一個好的小程序更是須要考慮性能和用戶體驗的方方面面。當我以爲本身第一個小程序差很少要完工的時候真的要跳起來唱joyful了(誤)。做爲一個程序猿真的不容易,難怪是個容易掉髮的羣體。但好在願意分享技術的人不少,在此次開發的過程當中我也查閱了不少的資料、社區和文檔。小程序的學習我也不會停下腳步,這個項目還有很是多作的很差的地方,我發出來也是但願你們和我進行交流分享,後期我也會繼續完善優化這個小程序項目。但願個人做品能夠對那些初學小程序的人有所幫助。

最後附上個人github項目地址:https://github.com/tearill/jd... 若是你以爲這個項目還不錯或者是對你有所幫助的話歡迎star,你點亮的每個star將都是我前進的動力!

相關文章
相關標籤/搜索