wx小程序学习笔记(Shopping Cart) 二

一、学习目标
  • 实现Shopping Cart 增删改功能。


    image.png

二、封装购物车的商品列表展示组件

1、在项目components目录下创建my-goods.vue组件。

<template>
  <view>
    <!--商品-->
    <view class = "goods-item">
       <!--商品左侧box-->
       <view class = "goods-item-left">
          <!-- 存储在购物车中的商品,包含 goods_state 属性,表示商品的勾选状态 -->
          <!-- 在 radioChangeHandler 事件处理函数中,通过事件对象 e,得到商品的 goods_id 和 goods_state -->
          <!-- radio的默认颜色,在不同平台不一样。微信小程序是绿色的,抖音小程序为红色,其他平台是蓝色的。更改颜色使用color属性。
          如需调节radio大小,可通过css的scale方法调节,如缩小到70%style="transform:scale(0.7)"
          radio不是checkbox,点击一个已经选中的radio,不会将其取消选中 -->
          <radio :checked="goods.goods_state" color = "#c00000"  style="transform:scale(0.7)" v-if = "showRadio" ></radio>
          <image :src="goods.imgUrl" class = "goods-item-left-image"></image>
       </view>
       <!--商品右侧区域-->
       <view class = "goods-item-right">
         <!--商品标题-->
         <view class = "goods-name">{{goods.goodsDesc}}</view>
         <!--商品价格-->
         <view class = "goods-info-box">
          <view class = "goods-price">¥{{goods.price}}</view>
          <!--商品数字输入框-->
          <uni-number-box :min="1" v-if="showNum" :value="goods.goods_count" ></uni-number-box>
         </view>
       </view>
    </view>
  </view>
</template>

<script>
  export default {
    // 定义 props 属性,用来接收外界传递到当前组件的数据
    props:{
      // 商品的信息对象
      goods:{
        type:Object,
        default:{}
      },
      //是否展示radio
      showRadio:{
        type:Boolean,
        default: false
      },
      //是否展示数字框
      showNum:{
        type:Boolean,
        default: false
      }
    },
    data() {
      return {
        
      }
    },
    methods: {
      
    }
</script>

<style lang = "scss">
    .goods-item{
      display: flex;
      padding: 5px 10px;
      border-bottom: 1px solid #f0f0f0;
      background-color: #FFFFFF;
      
      .goods-item-left{
         display: flex;
         align-items: center;
         margin-right: 5px;
        
        .goods-item-left-image{
           width: 100px;
           height: 100px;
        }
      }
      .goods-item-right{
         display: flex;
         flex-direction: column;
         justify-content: space-between;
        .goods-name{
           font-size: 13px;
        }
        .goods-info-box{
          display: flex;
          justify-content: space-between;
          .goods-price{
            font-size: 16px;
             color:#c00000
          }
        }
      }
    }
</style>

三、渲染购物车商品列表的标题区域

1、 定义如下的 UI 结构。

<!-- 购物车商品列表的标题区域 -->
<view class="cart-title">
  <!-- 左侧的图标 -->
  <uni-icons type="shop" size="18"></uni-icons>
  <!-- 描述文本 -->
  <text class="cart-title-text">购物车</text>
</view>

2、美化样式

.cart-title {
  height: 40px;
  display: flex;
  align-items: center;
  font-size: 14px;
  padding-left: 5px;
  border-bottom: 1px solid #efefef;
  .cart-title-text {
    margin-left: 10px;
  }
}

3、 渲染效果


image.png

四、渲染商品列表区域的基本结构

1、通过 mapState 辅助函数,将 Store 中的 cart 数组映射到当前页面中使用。

import badgeMix from '@/mixins/tabbar-badge.js'
// 按需导入 mapState 这个辅助函数
import { mapState } from 'vuex'

export default {
  mixins: [badgeMix],
  computed: {
    // 将 m_cart 模块中的 cart 数组映射到当前页面中使用
    ...mapState('m_cart', ['cart']),
  },
  data() {
    return {}
  },
}

3、在 UI 结构中,通过 v-for 指令循环渲染自定义的 my-goods 组件,其中 :showRadio="true" 是展示商品的勾选框,:showNum="true"是展示商品数量的数字输入框。

<!-- 商品列表区域 -->
<block v-for="(goods, i) in cart" :key="i">
  <my-goods :goods = "goods" :showRadio="true"  :showNum="true" ></my-goods>
</block>

4、渲染效果


image.png

五、修改购物车中商品的勾选状态

1、为 my-goods 组件封装 radio-change 事件。
当点击 radio 组件,希望修改当前商品的勾选状态,此时可以为 my-goods 组件绑定自定义事件radio-change ,从而获取当前商品的 id 和 goods_state。

<my-goods :goods = "goods"
    :showRadio="true" 
    :showNum="true" 
    @radio-change="radioChangeHandler" >
</my-goods>

2、定义 radioChangeHandler 事件处理函数如下。

methods: {
  // 商品的勾选状态发生了变化
  radioChangeHandler(e) {
    console.log(e) // 参数e为当前被点击是否勾选的商品对象信息
  }
}

3、在 my-goods.vue 组件中,为 radio 组件绑定 @click 事件处理函数,并用this.$emit('radio-change')方法调用radio-change自定义事件处理函数。

methods: {
    // radio 组件的点击事件处理函数
   radioChangeHandler(){
        // 通过 this.$emit() 触发外界通过 @ 绑定的 radio-change 事件,
        // 同时把商品的 Id 和 勾选状态 作为参数传递给 radio-change 事件处理函数
        this.$emit('radio-change',{
          //商品ID
           id:this.goods.id,
           //商品状态
           goods_state: !this.goods.goods_state
        })
    },
}

4、在 store/cart.js 模块中,声明如下的 mutations 方法,用来修改对应商品的勾选状态。

/**
     * 根据商品ID修改购物车中的商品状态
     * @param {Object} state 当前storeage中已缓存的购物车中的商品信息
     * @param {Object} goods 需要更改状态的商品信息
     */
    updateGoodsState(state,goods){
       // 根据 商品id 查询购物车中对应商品的信息对象
       const findResult = state.cart.find(x=>x.id===goods.id)
       // 如果有对应的商品信息对象
       if(findResult){
         // 更新对应商品的勾选状态
         findResult.goods_state = goods.goods_state
         // 持久化存储到本地缓存
         this.commit('m_cart/saveToStorage')
       }
    },

2、在购物车cart.vue中,导入 mapMutations 这个辅助函数,从而将需要的 mutations 方法映射到当前页面中使用。

import badgeMix from '@/mixins/tabbar-badge.js'
import { mapState, mapMutations } from 'vuex'

export default {
  mixins: [badgeMix],
  computed: {
    ...mapState('m_cart', ['cart']),
  },
  data() {
    return {}
  },
  methods: {
    //将updateGoodsState方法映射到当前页面
    ...mapMutations('m_cart', ['updateGoodsState']),
    // 商品的勾选状态发生了变化
    radioChangeHandler(e) {
      this.updateGoodsState(e)
    },
  },
}

六、修改购物车中商品的数量

1、为 my-goods 组件封装num-change 事件。
当修改了 NumberBox 的值以后,希望将最新的商品数量更新到购物车中,此时可以为 my-goods 组件绑定 @num-change 事件,从而获取当前商品的id 和goods_count。

 <my-goods
     :goods = "goods" 
     :showRadio="true" 
     :showNum="true" 
     @radio-change="radioChangeHandler" 
     @num-change = "numChangeHandler">
</my-goods>

2、定义 numberChangeHandler 事件处理函数。

// 商品的数量发生了变化
numberChangeHandler(e) {
  console.log(e)
}

3、在 my-goods.vue 组件中,为 uni-number-box 组件绑定 @change 事件处理函数。并用this.$emit('num-change')方法调用radio-change自定义事件处理函数。

 //number组件的改变事件
 changeHandler(val){
     this.$emit('num-change',{
         // 商品的 Id
         id:this.goods.id,
         //商品的最新数量
         goods_count: +val //无法保证val是数字
    })
  }

4、在 store/cart.js 模块中,声明如下的 mutations 方法,用来修改对应商品的数量。

/**
     * 根据商品ID修改购物车中的商品数量
     * @param {Object} state 当前storeage中已缓存的购物车的商品信息
     * @param {Object} goods 需要更改数量的商品信息
     */
    updateGoodsCount(state,goods){
      // 根据 goods_id 查询购物车中对应商品的信息对象
       const findResult = state.cart.find(x=>x.id === goods.id)
      //如果有对应的商品
      if(findResult){
        //更新对应商品的数量
        findResult.goods_count = goods.goods_count
        //将更改的数据持久化到本地缓存
         this.commit('m_cart/saveToStorage')
      }
    },

5、在 购物车页面cart.vue 中,通过 mapMutations 这个辅助函数,将需要的 mutations 方法映射到当前页面中使用。

import badgeMix from '@/mixins/tabbar-badge.js'
import { mapState, mapMutations } from 'vuex'

export default {
  mixins: [badgeMix],
  computed: {
    ...mapState('m_cart', ['cart']),
  },
  data() {
    return {}
  },
  methods: {
    //将updateGoodsState方法映射到当前页面
    //将updateGoodsCount方法映射到当前页面
    ...mapMutations('m_cart', ['updateGoodsState', 'updateGoodsCount']),
    // 商品的勾选状态发生了变化
    radioChangeHandler(e) {
      this.updateGoodsState(e)
    },
    // 商品的数量发生了变化
    numberChangeHandler(e) {
      this.updateGoodsCount(e)
    },
  },
}

七、 实现滑动删除的功能

1、改造购物车cart.vue 页面的 UI 结构,将商品列表区域的结构修改如下(可以使用 uniSwipeAction 代码块快速生成基本的 UI 结构)。

<!-- 商品列表区域 -->
<!-- uni-swipe-action 是最外层包裹性质的容器 -->
<uni-swipe-action>
  <block v-for="(goods, i) in cart" :key="i">
    <!-- uni-swipe-action-item 可以为其子节点提供滑动操作的效果。需要通过 options 属性来指定操作按钮的配置信息 -->
    <uni-swipe-action-item :options="options" @click="swipeActionClickHandler(goods)">
      <my-goods :goods="goods" :show-radio="true" :show-num="true" @radio-change="radioChangeHandler" @num-change="numberChangeHandler"></my-goods>
    </uni-swipe-action-item>
  </block>
</uni-swipe-action>

2、在 data 节点中声明 options 数组,用来定义操作按钮的配置信息。

 data() {
      return {
        options: [{
              text: '删除', // 显示的文本内容
              style: {
                backgroundColor: '#C00000' ,// 按钮的背景颜色
                fontSize:'12px'   //显示的文本内容的字体大小
              }
            }]
      };
    },

3、渲染效果


image.png

4、在 methods 中声明 uni-swipe-action-item 组件的 @click 事件处理函数。

// 点击了滑动操作按钮
swipeActionClickHandler(goods) {
  console.log(goods)
}

3、在 store/cart.js 模块的 mutations 节点中声明如下的方法,从而根据商品的 Id 从购物车中移除对应的商品。

   /**
     * 根据商品ID删除商品
     * @param {Object} state 当前storeage中已缓存的购物车的数据
     * @param {Object} goods 需要删除的商品数据
     */
    removeGoods(state,goods){
      // 调用数组的 filter 方法进行过滤
      state.cart = state.cart.filter(x=> x.id !== goods.id)
      //将更改的数据持久化到本地缓存
      this.commit('m_cart/saveToStorage')
    },
  },

4、在 购物车页面cart.vue 中,使用 mapMutations 辅助函数,把需要的方法映射到当前页面中使用。

   methods:{
    //将updateGoodsState方法映射到当前页面
    //将updateGoodsCount方法映射到当前页面
    //将removeGoods方法映射到当前页面
     ...mapMutations ('m_cart', ['updateGoodsState','updateGoodsCount','removeGoods']),
      // 商品的勾选状态发生了变化
      radioChangeHandler(e){
        //修改购物车里的商品是否选中的状态
        this.updateGoodsState(e)
      },
      //商品的数量发生了变化
      numChangeHandler(e){
        //修改购物车里的商品的数量
        this.updateGoodsCount(e)
      },
      // 点击了滑动操作按钮
      swipeActionClickHandler(goods) {
        //删除购物车中的商品
        this.removeGoods(goods)
      }
    }

八、部分代码
1、my-goods.vue组件完整代码

<template>
  <view>
    <!--商品-->
    <view class = "goods-item">
       <!--商品左侧box-->
       <view class = "goods-item-left">
          <!-- 存储在购物车中的商品,包含 goods_state 属性,表示商品的勾选状态 -->
          <!-- 在 radioChangeHandler 事件处理函数中,通过事件对象 e,得到商品的 goods_id 和 goods_state -->
         <!-- radio的默认颜色,在不同平台不一样。微信小程序是绿色的,抖音小程序为红色,其他平台是蓝色的。更改颜色使用color属性。
          如需调节radio大小,可通过css的scale方法调节,如缩小到70%style="transform:scale(0.7)"
          radio不是checkbox,点击一个已经选中的radio,不会将其取消选中 -->
          <radio :checked="goods.goods_state" color = "#c00000"  style="transform:scale(0.7)" v-if = "showRadio" @click="radioChangeHandler"></radio>
          <image :src="goods.imgUrl" class = "goods-item-left-image"></image>
       </view>
       <!--商品右侧区域-->
       <view class = "goods-item-right">
         <!--商品标题-->
         <view class = "goods-name">{{goods.goodsDesc}}</view>
         <!--商品价格-->
         <view class = "goods-info-box">
          <view class = "goods-price">¥{{goods.price}}</view>
          <!--商品数字输入框-->
          <uni-number-box :min="1" v-if="showNum" :value="goods.goods_count" @change="changeHandler"></uni-number-box>
         </view>
       </view>
    </view>
  </view>
</template>

<script>
  export default {
    // 定义 props 属性,用来接收外界传递到当前组件的数据
    props:{
      // 商品的信息对象
      goods:{
        type:Object,
        default:{}
      },
      //是否展示radio
      showRadio:{
        type:Boolean,
        default: false
      },
      //是否展示数字框
      showNum:{
        type:Boolean,
        default: false
      }
    },
    data() {
      return {
        
      }
    },
    methods: {
      // radio 组件的点击事件处理函数
      radioChangeHandler(){
        // 通过 this.$emit() 触发外界通过 @ 绑定的 radio-change 事件,
        // 同时把商品的 Id 和 勾选状态 作为参数传递给 radio-change 事件处理函数
        this.$emit('radio-change',{
          //商品ID
           id:this.goods.id,
           //商品状态
           goods_state: !this.goods.goods_state
        })
      },
      //number组件的改变事件
      changeHandler(val){
        this.$emit('num-change',{
          id:this.goods.id,
          goods_count: +val //val-0  无法保证val是数字-0
        })
      }
    }
  }
</script>

<style lang = "scss">
    .goods-item{
      display: flex;
      padding: 5px 10px;
      border-bottom: 1px solid #f0f0f0;
      background-color: #FFFFFF;
      
      .goods-item-left{
         display: flex;
         align-items: center;
         margin-right: 5px;
        
        .goods-item-left-image{
           width: 100px;
           height: 100px;
        }
      }
      .goods-item-right{
         display: flex;
         flex-direction: column;
         justify-content: space-between;
        .goods-name{
           font-size: 13px;
        }
        .goods-info-box{
          display: flex;
          justify-content: space-between;
          .goods-price{
            font-size: 16px;
             color:#c00000
          }
        }
      }
    }
</style>

2、store/cart.js完整代码

// cart.js
export default {
  // 为当前模块开启命名空间
  namespaced: true,

  // 模块的 state 数据
  state: () => ({
    
    // 购物车的数组,用来存储购物车中每个商品的信息对象
    // 每个商品的信息对象,都包含如下 6 个属性:
    // { goods_id, goods_name, goods_price, goods_count, goods_small_logo, goods_state }
    cart: JSON.parse(uni.getStorageSync('cart')||'[]'),
  }),
  // 模块的 mutations 方法
  mutations: {
    addToCart(state, goods) {
          // 根据提交的商品的Id,查询购物车中是否存在这件商品
          // 如果不存在,则 findResult 为 undefined;否则,为查找到的商品信息对象
          const findResult = state.cart.find((x) => x.id === goods.id)
          if (!findResult) {
            // 如果购物车中没有这件商品,则直接 push
            state.cart.push(goods)
          } else {
            // 如果购物车中有这件商品,则只更新数量即可
            findResult.goods_count++
          }
           // 通过 commit 方法,调用 m_cart 命名空间下的 saveToStorage 方法
          this.commit('m_cart/saveToStorage')
    },
    /**
     * 根据商品ID修改购物车中的商品状态
     * @param {Object} state 当前storeage中已缓存的购物车的数据
     * @param {Object} goods 需要更改状态的商品数据
     */
    updateGoodsState(state,goods){
       // 根据 goods_id 查询购物车中对应商品的信息对象
       const findResult = state.cart.find(x=>x.id===goods.id)
       // 如果有对应的商品信息对象
       if(findResult){
         // 更新对应商品的勾选状态
         findResult.goods_state = goods.goods_state
         // 持久化存储到本地
         this.commit('m_cart/saveToStorage')
       }
    },
    /**
     * 根据商品ID修改购物车中的商品数量
     * @param {Object} state 当前storeage中已缓存的购物车的数据
     * @param {Object} goods 需要更改数量的商品数据
     */
    updateGoodsCount(state,goods){
      // 根据 goods_id 查询购物车中对应商品的信息对象
       const findResult = state.cart.find(x=>x.id === goods.id)
      //如果有对应的商品
      if(findResult){
        //更新对应商品的数量
        findResult.goods_count = goods.goods_count
        //将更改的数据持久化到本地缓存
         this.commit('m_cart/saveToStorage')
      }
    },
    /**
     * 根据商品ID删除商品
     * @param {Object} state 当前storeage中已缓存的购物车的数据
     * @param {Object} goods 需要删除的商品数据
     */
    removeGoods(state,goods){
      // 调用数组的 filter 方法进行过滤
      state.cart = state.cart.filter(x=> x.id !== goods.id)
      
      //将更改的数据持久化到本地缓存
      this.commit('m_cart/saveToStorage')
    },
    // 将购物车中的数据持久化存储到本地
    saveToStorage(state){
      uni.setStorageSync('cart',JSON.stringify(state.cart))
    }
  },
  // 模块的 getters 属性
  getters: {
    // 统计购物车中商品的总数量
    total(state){
       let c = 0
       // 循环统计商品的数量,累加到变量 c 中
       state.cart.forEach(goods=>c+=goods.goods_count)
       return c
     }
  },
}

3、购物车商品展示页面cart.vue完整代码

<template>
  <view>
      <!--购物车标题区域开始-->
       <view class = "cart-title">
         <uni-icons type="shop" size="18"></uni-icons>
         <text class = "cart-title-text">购物车</text>
       </view>
       <!--购物车标题区域结束-->
       <!--购物车列表区域开始-->
       <uni-swipe-action>
         <block v-for="(goods,i) in cart" :key = "i">
           <!--将购物车store中的数据传递给my-goods组件-->
           <!--此处需要向下兼容 option需要替换成right-option 详情请阅读uniapp官方文档中uni-swipe-action-item组件使用说明-->
           <uni-swipe-action-item 
           :right-options="options" 
           @click="swipeActionClickHandler(goods)">
              <my-goods :goods = "goods" :showRadio="true" :showNum="true" @radio-change="radioChangeHandler" @num-change = "numChangeHandler"></my-goods>
           </uni-swipe-action-item>
         </block>
       </uni-swipe-action>
       <!--购物车列表区域结束-->
  </view>
</template>

<script>
  //导入封装mixin
import badgeMixin from '@/mixins/tabbar_badge.js'
// 按需导入 mapState 这个辅助函数
import {mapState,mapMutations } from 'vuex'
  export default {
    //导入封装mixin 渲染购物车徽标
    mixins:[badgeMixin],
  
    computed:{
      ...mapState('m_cart',['cart'])
    },
    data() {
      return {
        options: [{
              text: '删除', // 显示的文本内容
              style: {
                backgroundColor: '#C00000' ,// 按钮的背景颜色
                fontSize:'12px'
              }
            }]
      };
    },
    methods:{
     ...mapMutations ('m_cart', ['updateGoodsState','updateGoodsCount','removeGoods']),
      // 商品的勾选状态发生了变化
      radioChangeHandler(e){
        //修改购物车里的商品是否选中的状态
        this.updateGoodsState(e)
      },
      //商品的数量发生了变化
      numChangeHandler(e){
        //修改购物车里的商品的数量
        this.updateGoodsCount(e)
      },
      // 点击了滑动操作按钮
      swipeActionClickHandler(goods) {
        console.log(goods)
        this.removeGoods(goods)
      }
    }
  }
</script>

<style lang="scss">
  
  .cart-title{
    height: 40px;
    border-bottom: 1px solid #efefef;
    display: flex;
    align-items: center;
    font-size: 14px;
    padding-left: 5px;
    background-color: #FFFFFF;
    .cart-title-text{
      margin-left: 10px;
    }
    
    .goods-item {
      // 让 goods-item 项占满整个屏幕的宽度
      width: 750rpx;
      // 设置盒模型为 border-box
      box-sizing: border-box;
      display: flex;
      padding: 10px 5px;
      border-bottom: 1px solid #f0f0f0;
    }
    
    .uni-swipe_button-text{
      font-size: 13px;
    }
    
  }

</style>
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容