京东优选小程序快车等你上车~

2,523 阅读18分钟

作者:Horace
声明:本项目只作为学习使用,欢迎大家一起交流~
ps:新人小白一枚,如有错误,欢迎指出~

写在前面

过年有大把的时光,为何一直宅在家里不出家门看着电脑,这究竟是道德的沦丧还是人性的泯灭...这一切都还得从一只蝙蝠说起...

咳咳,好了不皮了,言归正传。微信推出的小程序可谓是轻量又强大,所以最近我也开始了小程序的学习,学了挺多也看了很多文档,但总觉得自己没学到什么,感觉很迷茫。正所谓实践出真知,所以我选择了从高仿别人的小程序开始,选来选去最后选择了京东优选这个小程序(绝对不是因为它的界面清爽!)。

开发工具

效果速览

废话不多说,咱先来搞一波图片看看,点这里查看更多图片






项目结构

这个项目我使用的是普通的开发,把所有的数据都放在了json-server中模拟。

可能很多人会觉得很奇怪,但这是因为我发现easy mock的网站经常打不开请求失败非常的不方便,所以我暂时没有选择mock数据,后期有时间我会把数据挪到easy mock上。

|-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,它左上角多了一个返回主页的按钮,这对于有多级页面的小程序来说是非常必要的,不然访问的层级太深用户不知道怎么返回主页。然而,小程序开发自带并没有这个样子的,好在可以自定义,接下来我们就来自定义一个。

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中要定义一下:

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

记得自定组件的时候一定要在json中写成自定义组件

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

接下来就是调用该组件了

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

别忘了在要引用页面的json中引入该组件(因为我只在商品详情页面使用了自定义导航栏,所以只在页面的json中配置了自定义导航栏)

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

值得注意的一点是,因为是自定义的navigationBar,所以page会延伸到整个屏幕,而navigationBar用的是fix布局脱离了文档流,下面添加的页面内容会跑上去和navigationBar重叠在一起,所以在它下面的组件中要记得加上 style='margin-top: {{height}}px'

Toast

Toast同样也是小程序开发已经做好给你用的了,虽然它可以支持替换里面的图标,但是你会发现很鸡肋的一点是,如果你想显示两行文字你就没办法做到了。我在开发过程中也搜索过相关的实现方法,找到了大部分是说在要换行的文字后背加上\r\n就能实现了,但是我自己亲测无效,所以实在忍不住也自己做了一个。

自定义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. 如果栏目中没有东西,要显示对应的提示信息。

实现它的功能并不难,直接scroll-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() })
 },

搜索

刚说完了筛选框,我们就来说说它旁边的那个搜索框吧。效果是下面这个样子的:


搜素框的样式并不难做,主要就是它的交互要好,比如清空文字时和失去焦点时的事件,不多说,直接上结构和样式。

<view class="search">
    <image class="icon" src="../../assets/icons/search.png"></image>
    <!-- 售后申请栏的输入框 -->
    <input type="text" wx:if="{{currentTab === 0}}" placeholder="商品名称/商品编号/订单编号/系列号" value="{{inputValue}}" bindinput="gainContent" bindfocus="showCancel" bindblur="hideCancel" />
    <input type="text" wx:else placeholder="商品名称/订单编号/服务单号" value="{{inputValue}}" bindinput="gainContent" bindfocus="showCancel" bindblur="hideCancel" />
    <!-- 清空文字的图标 -->
    <image class="delete" src="../../assets/icons/delete.png" wx:if="{{inputValue != ''}}" bindtap="hideCancel"></image>
    <view class="select" wx:if="{{!cancelShowed}}" bindtap="showSelect">
        <!-- 正常情况下的筛选图标 -->
        <image class="select_icon" wx:if="{{!selected}}" src="../../assets/icons/select.png"></image>
        <!-- 选中筛选后的筛选图标 -->
        <image class="select_icon" wx:if="{{selected}}" src="../../assets/icons/select_active.png"></image>
        <text class="select_text {{selected?'select_active':''}}">筛选</text>
    </view>
    <text class="cancel" wx:if="{{cancelShowed}}" bindtap="hideCancel">取消</text>
</view>
.search {
    /* height: 100%; */
    width: 100%;
    height: 120rpx;
    padding-left: 30rpx;
    padding-top: 20rpx;
    background-color: #fff;
    /* background-color: red; */
    position: relative;
    display: flex;
    flex: 1;
    box-sizing: border-box;
}
input {
    width: 593rpx;
    height: 80rpx;
    font-size: 25rpx;
    border-radius: 50rpx;
    background-color: #F5F5F5;
    box-sizing: border-box;
    padding-left: 60rpx;
    padding-right: 60rpx;
}
.icon {
    position: absolute;
    top: 45rpx;
    left: 45rpx;
    background-size: 100%;
    width: 30rpx;
    height: 30rpx;
}
.delete {
    width: 40rpx;
    height: 40rpx;
    position: absolute;
    left: 570rpx;
    top: 40rpx;
    /* image被文本框遮挡无法触发点击事件 */
    z-index: 990;
}
.cancel {
    font-size: 25rpx;
    color: #BCBCBC;
    position: absolute;
    top: 35%;
    right: 70rpx;
}

样式这里需要注意的一个坑是取消的那个图标放上去你会发现点了没反应,这是因为它被input框的图层遮盖住了所以没办法触发事件,所以要提高它的层级。

searchText(str) {
    let currData = []
    this.data.currData.forEach(val => {
      if (val.title.toLowerCase().indexOf(str.toLowerCase()) >= 0) {
        currData.push(val)
      }
    });
    this.setData({
      currData
    })
},
gainContent(e) { // 获取输入框内容
    const text = e.detail.value;
    console.log(typeof text)
    this.setData({
      inputValue: text
    })
    this.searchText(text)
    if (text === '') {
      this.fillData(this.data.currentTab)
    }
    console.log(typeof text, 'typeOf text');
},
deleteContent() { // 删除输入框内容
    this.setData({
      inputValue: ''
    })
    this.fillData(this.data.currentTab)
},
showCancel() { // 显示取消
    this.setData({
      cancelShowed: true
    })
  },
hideCancel() { // 隐藏取消
    this.setData({
      cancelShowed: false,
      inputValue: ''
    })
    this.fillData(this.data.currentTab)
},

实现搜索的时候注意要把输入的内容和检索的title统一转换为大写或者小写,这样就可以暴力的做到忽略大小写了,可以提供更好的用户体验。

购物车逻辑


要实现这样的效果并不困难,需要自己思路清晰,不能被绕进去了。实现加入购物车并不难,细节是购物车图标右上角的数字要根据加入购物车的数量进行动态的改变,还要注意如果是同一件商品就不需要添加新的,只需要修改原来的数量。

在这里我使用的是小程序的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
    })
}

功能大致先说这么一点,可能在大牛看起来都是些很容易不起眼的功能,但是对应我这个初学者来说还是有点困难的,希望如果有大牛看了我的一些功能的实现之后我不会被骂死。

值得注意的几点

页面跳转传值渲染问题

页面跳转传值是小程序开发中非常常见的一个操作,咱先来看看这个样子的传值:


这个传值也就是要把前一个页面点击的栏目的index传过去,然后在下一个页面取到后渲染相应的栏目。最容易想到的方法肯定是在跳转到url后面加上 ?index=传递的index,然后取出来渲染。然而,对于这种情况并没有什么作用,查了查资料,大抵是因为setData的异步性问题,所以这里我就采取了一个比较笨的办法,直接把index存放到Storage里面再去取。

showOptions(e) { // 跳转账户选项界面
    let index = e.currentTarget.dataset.index;
    console.log(index);
    if (this.data.loginFlag === 0) {
      wx.navigateTo({
        url: '../login/login?prev=account'
      })
    }else if (index === 3) {
      wx.navigateTo({
        url: '../service/service'
      })
    } else {
      index = index + 1;
      if(index > 3) {
        index = 0
      }
      // 使用storage携带index给下一个界面,否则无法在加载时渲染数据
      wx.setStorage({
        key: 'account:index',
        data: index
      })
      wx.navigateTo({
        url: '../account/account?index=' + index
      })
    }
 }

然后就是取出来:

onLoad(options) {
    let that = this
    let index = wx.getStorageSync('account:index');
    ... 渲染对应下标数据...
}

一时间我也想不到什么更优质的做法,所以我就暂时这么处理了,如果你在开发中遇到了同样的问题,欢迎一起交流~

事件冒泡问题

做过小程序开发或者是vue等开发的人一定听过事件冒泡这个名词:子元素的事件触发了父元素的事件,例如点击事件。我就是那个幸运鹅,我在开发的时候就遇到了这个情况。
在购物车中点击商品可以跳转商品详情,但是我一开始把跳转事件绑定在了每个商品卡片上,这样就导致了点击修改商品数量的时候修改了数字但是也会直接跳转商品详情,比如下面这样...

这就很不友好了,用户体验很差,关于事件冒泡,微信小程序的解决方法是把bindtap替换成catchtap,这样可以阻止子元素事件向上冒泡。

然而巧的是,我就是那个最幸运的鹅,步进器我用的是有赞Vant Weapp组件库里的,我搜索了很多资料都没有找到有效的解决方案,差点就放弃使用组件库了,好在最后发现京东优选小程序购物车绑定的跳转事件是在商品的图片和标题上。

这一点还是比较重要的,所以大家在开发的时候一定要考虑事件的冒泡,这也是我把它放在最后来写的原因。

写在后面

最后,我想说的是小程序开发真的不容易,开发一个好的小程序更是需要考虑性能和用户体验的方方面面。当我觉得自己第一个小程序差不多要完工的时候真的要跳起来唱joyful了(误)。作为一个程序猿真的不容易,难怪是个容易掉发的群体。但好在愿意分享技术的人很多,在这次开发的过程中我也查阅了很多的资料、社区和文档。小程序的学习我也不会停下脚步,这个项目还有非常多做的不好的地方,我发出来也是希望大家和我进行交流分享,后期我也会继续完善优化这个小程序项目。希望我的作品可以对那些初学小程序的人有所帮助。

最后附上我的github项目地址:github.com/tearill/jd_…
如果你觉得这个项目还不错或者是对你有所帮助的话欢迎star,你点亮的每一个star将都是我前进的动力!