首页 > 其他分享 >购物车功能,获取地址

购物车功能,获取地址

时间:2023-01-31 11:24:58浏览次数:55  
标签:goods state cart 购物车 获取 地址 vuex id

购物车 - 动态设置tabbar徽标

官方文档

  • 就是在页面一打开时调用 uni.setTabBarBadge 方法进行设置
  • 所以来到 pages/cart页面,导入mapGetters,然后映射成计算属性,在页面打开时设置tabbar徽标
import { mapGetters } from 'vuex'
export default {
  computed:{
    ...mapGetters('cart',['total'])
  },
  // 页面加载
  onl oad() {
    uni.setTabBarBadge({
      index:2,
      // 要求给的是字符串
      text: this.total + ''
    })
  },
  }

购物车 - 将设置徽标代码设置为mixins

  • 因为其他三个tabbar页面也要能设置,那就意味着这段代码,其他的vue实例也要用,因此可以用 mixins进行混入
  • 来到项目根目录,新建 mixins文件夹,里面放一个 tabbar-badge.js代码如下
import {
  mapGetters
} from 'vuex'

export default {
  computed: {
    ...mapGetters('cart', ['total'])
  },
  // 页面显示
  onl oad() {
    uni.setTabBarBadge({
      index: 2,
      // 要求给的是字符串
      text: this.total + ''
    })
  },
}
  • 再依次来到 home,cate,cart,my页面,导入和混入
import { mapGetters } from 'vuex'
// 导入混入对象
import tabbarBadge from '../../mixins/tabbar-badge.js'
export default {
  mixins:[ tabbarBadge ],
  }

购物车 - 商品区域的布局图

image-20220707092601157

购物车 - 商品列表的标题区域

<view class="cart-body">
  <view class="title-box">
    <uni-icons class="shop-icon" type="shop" size="25"></uni-icons>
    <text>购物车</text>
  </view>
</view>
.title-box {
  padding: 10px;
  border-bottom: 1px solid #ddd;

  .shop-icon {
    margin-right: 10px;
  }

  text {
    vertical-align: middle;
  }
}

购物车 - 渲染商品列表

  • 经过观察发现,商品列表跟之前goods_list里的差不多,所以可以直接把那边的结构和样式都复制过来,只不过v-for换成从vuex里取到的购物车数据来进行v -for
<template>
  <view>
    <!-- 放主体内容的盒子 -->
    <view class="cart-body">
      <view class="title-box">
        <uni-icons class="shop-icon" type="shop" size="25"></uni-icons>
        <text>购物车</text>
      </view>
      <view class="goods-item" v-for="item in cartList" :key="item.goods_id" @click="toDetail(item)">
        <!-- 左边图片 -->
        <image class="pic"
          :src="item.goods_small_logo || 'https://img3.doubanio.com/f/movie/8dd0c794499fe925ae2ae89ee30cd225750457b4/pics/movie/celebrity-default-medium.png'">
        </image>
        <!-- 右边的盒子 -->
        <view class="info-box">
          <text class="title">{{ item.goods_name }}</text>
          <text class="price">¥ {{ item.goods_price.toFixed(2) }}</text>
        </view>
      </view>

    </view>
  </view>
</template>
  • 样式
.goods-item {
  display: flex;
  margin: 20rpx;

  .pic {
    width: 260rpx;
    height: 260rpx;
    margin-right: 10px;
  }

  .info-box {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: space-between;

    .title {
      // 单词换行模式,意思是一行不够放,单词打断另起一行
      word-break: break-all;
    }

    .price {
      color: #c00;
    }
  }
}
- 但是有些是中文混排的文字,记得要是英文里的单词折叠换行
  • js里记得要映射vuex里的购物车数据
import {
  mapState
} from 'vuex'

computed: {
    ...mapState('cart', ['cartList'])
  },

购物车 - 把每一项的点击事件完成

  • 购物车里点击商品任何位置要跳转到商品详情,其实就是把goods_list里的点击事件赋值过来即可
// 每个商品的点击事件
      toDetail(item) {
        uni.navigateTo({
          url: '/subpkg/goods_detail/goods_detail?goods_id=' + item.goods_id
        })
      },

购物车 - radio单选框添加至商品列表

  • 因为整体是分为一左一右,而左边现在不光要放image,还要放radio,所以用一个view包起来
<view class="pic-box">
          <label class="radio">
            <radio color="#c00"/>
          </label>
          <!-- 左边图片 -->
          <image class="pic"
            :src="item.goods_small_logo || 'https://img3.doubanio.com/f/movie/8dd0c794499fe925ae2ae89ee30cd225750457b4/pics/movie/celebrity-default-medium.png'">
          </image>
        </view>
  • 此时能看到单选框,但是他们没有对齐,所以给这个大盒子加弹性布局,让他们垂直居中
.pic-box {
      display: flex;
      align-items: center;
    ......
}

购物车 - radio数据绑定及完成切换选中功能

  • radio要通过checked设置选中状态,但是不能写死,所以给:checked然后绑定每一项的 goods_checked,以及,它点击要切换状态,所以意味着要加点击事件,但是如果加了点击事件,因为就有冒泡的存在,所以还会触发父盒子的点击事件,导致跳转了,所以我们要阻止冒泡
<radio color="#c00" @click.stop="setChecked(item)" :checked="item.goods_checked" />
  • 点击事件里需要改vuex里的数据,而外面不能直接改,所以回到store/modules/cart.js封装一个专门用来改选中状态的mutation
// 专门用来设置选中或不选中的方法
setGoodsChecked(state, goods_id) {
  // 找到对应的数据
  const item = state.cartList.find(v => v.goods_id === goods_id)
  item.goods_checked = !item.goods_checked
}
  • 然后回到 cart.vue的点击事件里调用此方法即可
setChecked(item) {

        this.$store.commit('cart/setGoodsChecked', item.goods_id)
      },

购物车 - 数字输入框number-box

<view class="price-box">
            <text class="price">¥ {{ item.goods_price.toFixed(2) }}</text>
            <uni-number-box @click.native.stop :value="item.goods_count"
              @change="changeValue(item, $event)" />
          </view>
  • 大盒子给弹性布局,然后一左一右
.price-box {
        display: flex;
        align-items: center;
        justify-content: space-between;
     ........
}
  • 解释代码:
    • value是用来设置它的数量,数量应该从数据里读取出来
    • @change是为了不管是点了+还是-,甚至是直接输入数量,都能触发的事件
    • @click.native.stop这里仅仅只是为了阻止冒泡到父盒子进行跳转
      • 为什么要加native?因为这是一个组件,组件没有这个click事件,要用native加原生上
    • $event就是这个子组件绑定事件默认传递过来的值

购物车 - 实现数量修改功能

  • 因为要修改到vuex,所以应该提供一个修改数量的方法,而修改数量要依赖id找到对应的数据,还要依赖数量进行修改,所以意味着要传两个参数,那么应该包成一个对象来传递
// 专门用来修改数量的方法
    // 因为既要传id,又要传数量,所以当一个对象传进来
    setGoodsCount(state, obj) {
      // 首先根据id找到要修改的商品
      const item = state.cartList.find(v => v.goods_id === obj.goods_id)
      item.goods_count = obj.goods_count
    }
  • 回到 购物车页面的上面写的change事件进行调用修改
// 数量改变触发的事件
      changeValue(item, num) {

        this.$store.commit('cart/setGoodsCount', {
          goods_id: item.goods_id,
          goods_count: num
        })
      },

购物车 - 修改后要存到本地

  • 此时发现,不管是打钩还是修改数量,一刷新数据就恢复了,原因是这两个方法改完后没有存到本地
  • 解决办法:每次改完都存到本地
// 专门用来设置选中或不选中的方法
setGoodsChecked(state, goods_id) {
  .......
  // 存到本地存储
  uni.setStorageSync('cart', state.cartList)
},

// 专门用来修改数量的方法
// 因为既要传id,又要传数量,所以当一个对象传进来
setGoodsCount(state, obj) {
  .......
  // 存到本地存储
  uni.setStorageSync('cart', state.cartList)
}

购物车 - 动态计算tabbar徽标

  • 问题: 当我在购物车里加了或减了数量,tabbar上的徽标不变
  • 原因:徽标只在页面打开时设置了一次,后面total总量变了,也没有重新设置徽标
  • 解决办法:
    • 在mixins里侦听total的改变,一旦改变就重新设置徽标
watch: {
    total() {
        uni.setTabBarBadge({
          index: 2,
          text: this.total + ''
        })
      
    }
}

购物车 - 如果总数量为0则移除tabbar徽标

  • 问题:如果数量为0,徽标上显示为0,而我们要如果为0就不显示徽标,所以要判断
watch: {
    total: {
      handler() {
        if (this.total > 0) {
          uni.setTabBarBadge({
            index: 2,
            text: this.total + ''
          })
        } else {
          // 移除徽标
          uni.removeTabBarBadge({
            index: 2
          })
        }
      },
      immediate: true
    }
  }

- 建议写成 `immediate`,这样的好处是,onLoad就不用写了,不然onLoad里也要把一样的代码复制一份

购物车 - 滑动操作swipe-action

  • 官方文档
  • 注意事项:
    • 写一个swipe-action里面就要写 swipe-action-item,有几个item就代表有几个可以左右滑动
    • swipe-action不能放到swiper里面
    • swipe-action-item有个属性叫 right-option用来设置右侧滑动出来的按钮,加了这个属性就有左滑
  • 因为我们需要每个商品都要有左滑,意味着,要把每个商品都包到swipe-action,并且每个商品要有对应的swipe-action-item,所以v-for应该写到swipe-action-item身上,改动后的html如下
<uni-swipe-action>
      <uni-swipe-action-item :right-options="options" v-for="item in cartList" :key="item.goods_id">
        <view class="goods-item" @click="toDetail(item)">
                      ...................
                  </view>
               
      </uni-swipe-action-item>
</uni-swipe-action>
  • 声明data里放按钮
data() {
    return {
      options: [{
        text: '删除',
        style: {
          backgroundColor: '#c00'
        }
      }],
    };
  },

购物车 - 完成删除功能

  • 删除要改vuex,要提供一个与之对应的mutations做删除
    • 删除的本质其实就是过滤出要删的元素以外的元素组成新数组,就相当于删除了
// 删除商品
  delGoodsById(state, id) {
    // 删除一个元素其实就相当于过滤出来除了它以外的元素
    // 过滤出id不等于我要删的id的其他元素组成新数组
    state.cartList = state.cartList.filter(v => v.goods_id != id)
          // 持久化数据
          uni.setStorageSync('cart', state.cartList)
  }
  • 回到 cart.vue页面,找到侧滑的组件,给它加click事件,因为这个组件内部优化不够好,key最好不要加到它身上,加到子元素
<block v-for="item in cartList">
  <uni-swipe-action-item @click="doDel(item.goods_id)" :right-options="options">
    <view :key="item.goods_id" class="goods-item" @click="toDetail(item)">
    .......
  • 点击事件里调用vuex的方法做删除即可
doDel(id) {
      // 点谁就删谁
      this.$store.commit('cart/delGoodsById', id)
    },

购物车 - 收货地址基本布局

  • 布局如下
<view>
    <view class="add-box">
      <view style="display:flex;justify-content: center;align-items: center;height: 180rpx;">
        <button size="mini" type="primary">请选择收货地址+</button>
      </view>
    </view>
    <!-- 图片 -->
    <view>
      <image style="width:100%;height:5px;" src="/static/[email protected]"></image>
    </view>
  </view>

购物车 - 小程序选择地址功能

"requiredPrivateInfos": [
   "chooseAddress"
 ],
  • uniapp开发:
    • uni.chooseAddress

    • 找到 manifest.json,打开后找到菜单里的最后一项:“源码视图”,再找到 mp-weixin,加上面的配置即可

      image-20221202161318427

  • 给地址里的按钮加了点击事件
<button @click="chooseAddr" size="mini" type="primary">请选择收货地址+</button>
methods: {
   async chooseAddr () {
       const res = await uni.chooseAddress()
       console.log(res)
   }
}
  • 如果点击了取消,或者没有正确选到地址,下标0有值,如果点击了确定(拿到了地址),返回值下标1有值,下标0为null

    image-20221202161701518

购物车 - 地址信息用vuex管理起来并缓存

  • 根据上面的步骤,已经可以拿到地址了,但是地址可能多个地方要用,所以用vuex存起来
  • 来到 store/modules新建user.js,这个模块以后专门存当前用户的个人信息
export default {
  namespaced: true,
  state() {
    return {
      address: {}
    }
  },
  getters: {
    // 专门用来拼接地址的
    addressStr(state) {
      return state.address.provinceName + state.address.cityName + state.address.countyName + state.address
        .detailInfo
    }
  },
  mutations: {
    setAddress(state, addr) {
      state.address = addr
    }
  }
}
  • 来到 store/index做导入和注册
import user from './modules/user.js'


modules: {
    cart,
    user
  }

购物车 - 地址渲染出来

image-20221202000106555

  • 首先回到 my-address,在按钮的点击事件里把拿到的地址存到vuex里
async chooseAddr() {
      // 要选择微信上的地址
      const res = await uni.chooseAddress()
      if (res[0]) {
        uni.showToast({
          title: '没选择任何地址',
          icon: 'error'
        })
      } else {
        // 有地址,拿到地址存起来
        this.$store.commit('user/setAddress', res[1])
      }
    }
  • 再导入getters里的内容,做渲染
import {
  mapGetters
} from 'vuex'
export default {
  name: "my-address",
  computed: {
    ...mapGetters('user', ['addressStr'])
  },
  • 渲染时要有选择的显示按钮或地址
<view v-if="!addressStr" class="btn-box">
    <button @click="chooseAddr" size="mini" type="primary">请选择收货地址+</button>
  </view>
  <!-- 放选择后的地址信息 -->
  <view v-else class="">
    <view class="addr">收货地址:{{ addressStr }}</view>
  </view>

购物车-完整显示地址

<view v-else style="padding:10px">
  <view style="display: flex;justify-content: space-between;">
    <view class="name">
      收货人:{{ address.userName }}
    </view>
    <view class="call">
      电话: {{ address.telNumber }}
    </view>
  </view>
  <view class="addr">收货地址:{{ addressStr }}</view>
</view>
  • 这里要访问address,这个数据在vuex里,所以做一个映射
import {
    mapGetters,
    mapState
  } from 'vuex'



  computed: {
      ...mapGetters('user', ['addressStr']),
      ...mapState('user', ['address'])
    },

购物车 - 结算区域组件准备

  • 来到 components右键新建组件,专门放结算区域,名字叫 my-settle

购物车 - 结算区域基本布局

image-20220707150905306

  • 结构如下
  <view class="settle-container">
    <label class="radio">
      <radio color="#c00000" value="" /><text>全选</text>
    </label>
    <view class="totalprice-box">
      合计:<text style="color:#c00000">¥ 665.00</text>
    </view>
    <view class="order-btn">结算(0)</view>
  </view>
  • 样式如下:
    • 大盒子要固定定位、里面内容弹性布局
.settle-container {
    height: 50px;
      background-color: #fff;
    position: fixed;
    border-top: 1rpx solid #dddddd;
    left: 0;
    bottom: 0;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-around;
    
    .order-btn {
      
      width: 200rpx;
      height: 80rpx;
      line-height: 80rpx;
      background-color: #c00000;
      color:#fff;
      text-align: center;
    }
  }
  • 来到 cart页面使用它,使用后发现,购物车里有一部分内容被盖住了,所以需要给购物车的大盒子加padding
<view style="padding-bottom:50px;">
    ...................
    <my-settle></my-settle>
</view>

购物车 - 渲染结算区域已勾选总数量

  • 总数量因为之前已经统计过,所以可以来到 my-settle直接用
import {
  mapGetters
} from 'vuex'
export default {
  name: "my-settle",
  computed: {
    ...mapGetters('cart', ['total'])
      }
  }
  • 然后渲染到购物车数量上
<view class="order-btn">结算({{ total }})</view>

购物车 - 渲染已勾选商品总价

  • 总价因为之前没有,所以还得回来到store/modules/cart里写一个getter用来计算总价
// 统计总价
totalPrice(state) {
  // 统计出总数量--要统计出选中的数量
  let price = 0
  state.cartList.forEach(v => {
    if (v.goods_checked) {
      price += v.goods_count * v.goods_price
    }
  })
  // 把统计的数量返回出去
  return price
},
  • 然后来到 my-settle进行使用
import {
  mapGetters
} from 'vuex'
export default {
  name: "my-settle",
  computed: {
    ...mapGetters('cart', ['total', 'totalPrice'])
  },
  }
<view class="totalprice-box">
  合计:<text style="color:#c00000">¥ {{ totalPrice }}</text>
</view>

购物车 - 动态渲染全选按钮的选中状态

  • 因为全选的选中状态依赖了购物车里的数据,而购物车里的数据在vuex里,所以来到vuex里写一个计算属性
checkAll(state) {
  // 统计每个商品的goods_checked是否为true,都为true才返回true,有一个是false就返回false,所以可以用every
  return state.cartList.every(v => v.goods_checked)
}
  • 然后来到 my-settle进行调用
import {
  mapGetters
} from 'vuex'
export default {
  name: "my-settle",
  computed: {
    ...mapGetters('cart', ['total', 'totalPrice', 'checkAll'])
  },
  }
  • 绑定到单选框
<radio :checked="checkAll" color="#c00000" value="" /><text>全选</text>

购物车 - 实现全选/全不选功能

  • 因为要全选/全不选,需要改动到vuex里的商品数据,所以需要提供一个mutations用来修改
setAllGoodsChekced(state, checked) {
  state.cartList.forEach(v => v.goods_checked = checked)
}
  • 这个功能需要给单选框加点击事件,点击时传递当前选中状态取反即可
<radio @click="clickAll" :checked="checkAll" color="#c00000" value="" /><text>全选</text>
clickAll() {
        // 全选原本是true就给false,原本是false就全部给true
        // 但是因为改的是vuex里的数据,所以又得封装方法
        this.$store.commit('cart/setAllGoodsChekced', !this.checkAll)
      }

购物车 - 渲染购物车为空时的结构

  • 无非就是再提供一个整体的view,判断有购物车数据就渲染之前购物车页面,没有就显示空空如也的view
<view v-if="cartList.length" style="padding-bottom:50px;">...........</view>
    
<view v-else class="empty-box">
    <image src="../../static/[email protected]"></image>
    <text>空空如也~</text>
  </view>
  • 样式如下
.empty-box {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  image {
    width: 80px;
    height: 80px;
  }
}

购物车 - 分支合并

  1. cart 分支进行本地提交:
git add .
git commit -m "完成了购物车的开发"
  1. 将本地的 cart 分支推送到码云:
git push -u origin cart
  1. 将本地 cart 分支中的代码合并到 master 分支:
git checkout master
git merge cart
git push
  1. 删除本地的 cart 分支:
git branch -d cart
<view class="settle-container">
  <label class="radio">
    <radio color="#c00000" value="" /><text>全选</text>
  </label>
  <view class="totalprice-box">
    合计:<text style="color:#c00000">¥ 665.00</text>
  </view>
  <view class="order-btn">结算(0)</view>
</view>

标签:goods,state,cart,购物车,获取,地址,vuex,id
From: https://www.cnblogs.com/strundent/p/17078373.html

相关文章