Vue2项目实战:尚品汇(三)商品详情界面、加入购物车成功界面、购物车界面

目录

(一)详情页detail模块搭建 

1.搭建静态页面

[1]静态页面搭建

[2]配置路由+路由模块化

2.获取详细商品信息

[1]配置发送axios请求和detail仓库

3.动态展示数据

[1]三级商品目录展示

[2]标题和价格的数据展示

[3]商品平台售卖属性的展示和选择

[4]实现购买商品数量的操作

4.放大镜和小轮播图组件的操作

[1] 小轮播图组件

[2]放大镜组件

[3]小轮播图和放大镜组件的数据传输

(二)加入购物车成功页面addCartSuccess搭建

1.发送商品id和数量的加入购物车请求

(1)配置加入购物车请求api和store

(2)商品成功加入购物车的路由组件

(3)完善加入购物车成功组件的功能

(三)购物车页面shopcart搭建

1.配置路由+静态组件

2.请求购物车数据

(1)配置接口+store仓库

(2)获取游客nanoid进行请求数据操作

3.动态展示购物车数据

4.实现购物车功能

(1)获取购物车商品数据

(2)单选并展示总价的功能

(3)更改商品数量的功能

(4)删除单个商品的功能

(5)单选商品修改选中状态的功能

(6)删除选中的商品的功能

(7)点击全选和反选的功能


(一)详情页detail模块搭建 

1.搭建静态页面

用户在搜索页点击商品图片就会跳转到商品详情页,商品信息根据商品id向服务器请求数据动态展示出 

[1]静态页面搭建

[2]配置路由+路由模块化

配置路由

routes:[
    {
        name: 'detail',
        path: '/detail/:skuId', //params参数
        component: Detail,
        meta: { showFooter: true }
    },
]

将routes配置项移到routes.js实现模块化管理

将routes数组写到routes.js中并默认暴露,index.js直接引用即可

在router/index中:
// 引入路由配置
import routes from "./routes";

const router = new VueRouter({
    routes,
    // 路由跳转后回到网页最上方
    scrollBehavior(to, from, savedPosition) {
        if (savedPosition) {
            return savedPosition
        } else {
            return { x: 0, y: 0 }
        }
    },
})
export default router

使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 滚动行为 | Vue Router 

使用声明式导航实现路由跳转

传递商品skuId 以params参数形式传递

在pages/search/index中:
<div class="p-img">
    <!-- 声明式导航 传递params参数 商品id -->
    <router-link :to="`/detail/${goods.id}`">
        <img :src="goods.defaultImg" />
    </router-link>
</div>

2.获取详细商品信息

[1]配置发送axios请求和detail仓库

在api/index中:
// 获取详情商品数据 get请求  携带参数skuId
export const goodsInfo = (skuId) => requests.get(`/item/${skuId}`)

在store/Detail/index中:
// 引入axios goodsInfo
import { goodsInfo } from "@/api"

// 用于异步操作 不进行数据操作
const actions = {
    // 发送请求获取categorylist数据
    async getGoodsInfo(context, skuId) {
        const result = await goodsInfo(skuId) //axios返回promise实例
        if (result.code == 200) { //成功接收
            console.log(result.data);
            context.commit('GETGOODSINFO', result)
        }
    },
}
// 进行数据操作
const mutations = {
    // 更新state中bannerList的值
    GETGOODSINFO(state, result) {
        state.goodsInfo = result.data
    },
}
// 存储共享数据
const state = {
    goodsInfo: {}
}

向服务器/api/item发送get请求,携带参数skuId,返回的数据是一个对象  

在pages/detail/index中:
mounted() {
    // 发送axios请求数据
    this.$store.dispatch('getGoodsInfo', this.skuId)
  },
computed: {
    skuId() {
      return this.$route.params.skuId
    }
  }

 

3.动态展示数据

将获取到的详细商品数据在组件上动态展示

[1]三级商品目录展示

在store/detail中:
const getters = {
    // 一二三级商品目录
    categoryView(state) {
        return state.goodsInfo.categoryView || {}
    },
}

在pages/detail/index中:
<!-- 导航路径区域 -->
<div class="conPoin">
<!-- 点击一二三级标签 跳转到目的路由 -->
    <span v-show="categoryView.category1Name" @click="gotoCategory(1)">{{ categoryView.category1Name }}</span>
    <span v-show="categoryView.category2Name" @click="gotoCategory(2)">{{ categoryView.category2Name }}</span>
    <span v-show="categoryView.category3Name" @click="gotoCategory(3)">{{ categoryView.category3Name }}</span>
</div>

computed:{
...mapGetters(['categoryView'])
}

 实现点击对应一二级目录跳转到对应的搜素页

methods: {
    // 点击一二三级标签跳转到对应路由
    gotoCategory(num) {
      let location = { name: 'search' }
      if (num == 1) {
        location.query = {
          categoryName: this.categoryView.category1Name,
          category1Id: this.categoryView.category1Id
        }
      } else if (num == 2) {
        location.query = {
          categoryName: this.categoryView.category1Name,
          category2Id: this.categoryView.category2Id
        }
      } else {
        location.query = {
          categoryName: this.categoryView.category1Name,
          category3Id: this.categoryView.category3Id
        }
      }
      this.$router.push(location)
    },
}

[2]标题和价格的数据展示

在store/detail中:
const getters = {
    // 商品信息
    skuInfo(state) {
        return state.goodsInfo.skuInfo || {}
    },
}

在pages/detail/index中:
<h3 class="InfoName">{{ skuInfo.skuName }}</h3>
<div class="price">
    <i>¥</i>
    <em>{{ skuInfo.price }}</em>
    <span>降价通知</span>
</div>

computed:{
...mapGetters(['skuInfo'])
}

[3]商品平台售卖属性的展示和选择

展示获取到的商品数据 

从服务器获取到的数据中每个售卖属性有固定的isChecked的值

在store/detail中:
const getters = {
    // 商品平台售卖信息
    spuSaleAttrList(state) {
        return state.goodsInfo.spuSaleAttrList || []
    }
}

在pages/detail/index中:
<div class="chooseArea">
    <!-- 获取商品平台售卖属性进行渲染 -->
    <dl v-if="spuSaleAttrList" v-for="spuSaleAttr in spuSaleAttrList"         
        :key="spuSaleAttr.baseSaleAttrId">
        <dt class="title">{{ spuSaleAttr.saleAttrName }}</dt>
        <dd changepirce="0" v-for="value in spuSaleAttr.spuSaleAttrValueList" 
            :key="value.id" :class="{ active: parseInt(value.isChecked) }">{{ value.saleAttrValueName }}</dd>
    </dl>
</div>

computed:{
...mapGetters(['spuSaleAttrList'])
}

实现点击对应标签显示对应标签的active

<dd changepirce="0" v-for="value in spuSaleAttr.spuSaleAttrValueList" :key="value.id"
    :class="{ active: parseInt(value.isChecked) }"
    @click="changeChecked(value, spuSaleAttr.spuSaleAttrValueList)">
    {{ value.saleAttrValueName }}</dd>

methods:{
 changeChecked(value, spuSaleAttrValueList) {
      // 先将所有选项都改为0
      spuSaleAttrValueList.forEach(item => {
        item.isChecked = 0
      })
      // 再将点击的目标选项改为1
      value.isChecked = 1
    },
}

[4]实现购买商品数量的操作

默认数量为1,点击加减进行数量的改变,可直接在input框内输入数量,对输入的内容要进行判断 

<div class="controls">
    <input autocomplete="off" class="itxt" v-model="skuNum" @change="changeNum">
    <a class="plus" @click="skuNum++">+</a>
    <a class="mins" @click="skuNum > 1 ? skuNum-- : skuNum = 1">-</a>
</div>

methods:{
changeNum(e) {
      //通过event事件对象获取用户输入内容[用户输入的内容一定是字符串类型的数据]
      let value = e.target.value * 1;
      //用户输入进来非法情况判断
      if (isNaN(value) || value < 1) { //非数字乘以1会变成NaN,可用于判断是否是数字
        this.skuNum = 1;
      } else {
        //正常情况
        this.skuNum = parseInt(value);
      }
    },
}

4.放大镜和小轮播图组件的操作

[1] 小轮播图组件

图片数据父传子

在pages/detail/index中:
 <!-- 小图列表 -->
<ImageList :skuImageList="skuImageList" />

computed: {
    // 给子组件的图片信息数据 若还没获取到就返还空数组
    skuImageList() {
      return this.skuInfo.skuImageList || []
    }
  },

利用swiper插件将图片数据展示出

在pages/detail/imageList中:
<div class="swiper-container">
    <swiper ref="swiper" :options="swiperOptions">
      <swiper-slide v-for="(img, index) in skuImageList" :key="index">
        <img :src="img.imgUrl" :class="{ active: imgIndex == index }">
      </swiper-slide>
      <!-- 左箭头 -->
      <div slot="button-prev" class="swiper-button-prev"></div>
      <!-- 右箭头 -->
      <div slot="button-next" class="swiper-button-next"></div>
    </swiper>
  </div>

// 局部引入swiper插件
import { Swiper, SwiperSlide } from 'vue-awesome-swiper'
import 'swiper/css/swiper.css'

export default {
  name: "ImageList",
  data() {
    return {
      // 默认第一张图片 active样式
      imgIndex: 0,
      // 轮播图配置
      swiperOptions: {
        slidesPerView: 6,
        // spaceBetween: 10,
        //导航前进后退按钮
        navigation: {
          nextEl: ".swiper-button-next",
          prevEl: ".swiper-button-prev",
        },
      }
    }
  },
  components: {
    Swiper, SwiperSlide
  },
  props: ['skuImageList'],

[2]放大镜组件

图片数据父传子

在pages/detail/index中:
<!--放大镜效果-->
<Zoom :skuImageList="skuImageList" />

先将第一张图片展示在放大镜上,后面再根据小轮播图组件的操作动态改变放大镜展示的图片

<div class="spec-preview">
    <img :src="image.imgUrl" />
    <!-- 绑定鼠标移动事件 -->
    <div class="event"></div>
    <div class="big">
      <img :src="image.imgUrl" ref="big" />
    </div>
    <div class="mask" ref="mask"></div>
</div>

computed: {
    image() {
      return this.skuImageList[0] || {}
    }
  },
props: ['skuImageList'],

[3]小轮播图和放大镜组件的数据传输

用户鼠标移入小轮播图组件中的图片时向放大镜组件发送移入的图片的数据,从而实现同步展示

在pages/detail/imageList中:
<img :src="img.imgUrl" @mouseover="changeImage(index, img)" 
    :class="{ active: imgIndex == index }">

methods: {
    changeImage(index) {
      // 当鼠标进入的不是当前展示的图片时,就向zoom发送新的图片索引
      if (this.imgIndex != index) {
        this.imgIndex = index
        // 全局事件总线 向兄弟组件发送图片索引
        this.$bus.$emit('getImage', index)
      }
    }
  },
在pages/detail/zoom中:
  <div class="spec-preview">
    <img :src="image.imgUrl" />
    <div class="event"></div>
    <div class="big">
      <img :src="image.imgUrl" ref="big" />
    </div>
    <div class="mask" ref="mask"></div>
  </div>

 data() {
    return {
      // 索引默认为0
      index: 0
    }
  },
  mounted() {
    // 接收imageList实时发来的图片索引
    this.$bus.$on('getImage', (index) => {
      this.index = index
    })
  },
  beforeDestroy() {
    this.$bus.$off('getImage')
  },
  computed: {
    image() {
      return this.skuImageList[this.index] || {}
    }
  },

实现用户鼠标移入放大镜实时展示放大区域 

<!-- 绑定鼠标移动事件 -->
<div class="event" @mousemove="move"></div>

methods: {
    move(e) {
      //获取蒙板
      let mask = this.$refs.mask;
      let big = this.$refs.big;
      //计算蒙板的left|top数值
      let left = e.offsetX - mask.offsetWidth / 2;
      let top = e.offsetY - mask.offsetHeight / 2;
      //约束蒙板的上下左右范围
      if (left < 0) left = 0;
      if (left > mask.offsetWidth) left = mask.offsetWidth;
      if (top < 0) top = 0;
      if (top > mask.offsetHeight) top = mask.offsetHeight;
      mask.style.left = left + "px";
      mask.style.top = top + "px";
      big.style.left = -2 * left + "px";
      big.style.top = -2 * top + "px";
    },
  },

(二)加入购物车成功页面addCartSuccess搭建

1.发送商品id和数量的加入购物车请求

向/api/cart/addToCart/{ skuId }/{ skuNum }端口发送请求(在路径中传递数据)

(1)配置加入购物车请求api和store

在api/index中:
// 将加入购物车的商品信息(或要修改的商品数量信息)传入服务器中 post请求,发送/{skuId}/{skuNum}
export const addUpdateShopcar = (skuId, skuNum) => requests({
    url: `/cart/addToCart/${skuId}/${skuNum}`,
    method: 'post',
})

在store/detaill中:
const actions = {
    // 发送请求添加或修改购物车商品信息 store读取的参数只能是单个
    // async函数返回的一定是一个promise对象
    async addUpdateShopcar(context, { skuId, skuNum }) {
        const result = await addUpdateShopcar(skuId, skuNum)
        // 加入购物车成功
        if (result.code == 200) {
            return 'ok'
        } else { //加入购物车失败
            return Promise.reject(new Error('加入购物车失败'))
        }
    }
}

在detail组件中调用$store的addUpdateShopcar本质是返回了一个promise函数,所以使用then接收是否加入购物车成功的结果即可 

在pages/detail/index中:
<!-- 点击加入购物车操作 -->
<a @click="addShopcar">加入购物车</a>

methods:{
    // 发送请求将产品加入到购物车中
    addShopcar() {
      // 发送请求
      this.$store.dispatch('addUpdateShopcar', {
        skuId: this.skuId, skuNum: this.skuNum
      }).then((resolve) => { // 返回成功
        // 进行路由跳转 跳转到购物车界面
        this.$router.push({
          path: '/addcartsuccess',
          query: { // 将该商品的信息和数量传递给添加购物车路由
            skuInfo: this.skuInfo,
            skuNum: this.skuNum
          }
        })
      }, (reject) => { // 返回失败
        alert(reject)
      })
    }
}

这里使用了query参数传递数据,但会跳转后地址栏看起来很杂乱,另一种方法是采用会话存储sessionStorage将数据保存在本地,跳转到成功加入购物车的路由组件时再获取数据也是可行的。 

(2)商品成功加入购物车的路由组件

配置路由

在router/routes中:
{
    name: 'addcartsuccess',
    path: '/addcartsuccess',
    component: AddCartSuccess,
    meta: { showFooter: true, showInput: false }
},

静态组件结构: 

<template>
  <div class="cart-complete-wrap">
    ...
  </div>
</template>

<script>
export default {
  name: 'AddCartSuccess',
  data() {
    return {
      // 接收商品信息和数量
      skuData: this.$route.query
    }
  },
}
</script>

<style lang="less" scoped>...</style>

 动态展示数据

<div class="left-pic">
    <img :src="skuData.skuInfo.skuDefaultImg">
</div>
<div class="right-info">
    <p class="title">{{ skuData.skuInfo.skuName }}</p>
    <p class="attr"> 数量:{{ skuData.skuNum }}</p>
</div>

(3)完善加入购物车成功组件的功能

查看商品详情

这里也可以使用push带query参数跳转到对应的详情页,用back应该算是偷懒了

<!-- 直接回退到商品详情路由 -->
<a class="sui-btn btn-xlarge" @click="$router.back()">查看商品详情</a>

去购物车结算

<!-- 跳转到购物车路由 -->
<router-link to="/shopcart">去购物车结算 > </router-link>

(三)购物车页面shopcart搭建

1.配置路由+静态组件

配置路由

在router/routes中:
{
    name: 'shopcart',
    path: '/shopcart',
    component: ShopCart,
    meta: { showFooter: true, showInput: false }
},

静态组件

2.请求购物车数据

向/api/cart/cartList端口请求数据 

注意:购物车数据不能直接请求到,必须发送自定义请求头userTempId才可以请求到对应的购物车数据

(1)配置接口+store仓库

在api/index中:
// 获取购物车数据 get请求
export const cartList = () => requests.get('/cart/cartList')

在store/shopcart中:
const actions = {
    async getCartList(context) {
        const result = await cartList()
        if (result.code == 200) {
            context.commit('GETCARTLIST', result)
        }
    }
}
// 进行数据操作
const mutations = {
    GETCARTLIST(state, result) {
        state.cartData = result[0] || []
    }
}
// 用于加工state内的数据 类似于computed
const getters = {
    cartList(state) {
        return state.cartData.cartInfoList || []
    }
}
// 存储共享数据
const state = {
    cartData: [],
}

(2)获取游客nanoid进行请求数据操作

目前还没有搞到用户登录注册的业务,所以先使用一个游客id进行练习,同时这个id要保持在网页中不变,所以使用nanoid插件随机一个id来使用

创建utils文件夹(用于存放一些工具函数,如正则表达式等)

在utils/nanoid_token中:
// 生成唯一的游客id
import { nanoid } from "nanoid"
export const nanoid_token = () => {
    // 在本地存储中寻找有没有游客id
    let nanoId = localStorage.getItem('nanoid_token')
    // 没有就新建一个
    if (!nanoId) {
        nanoId = nanoid()
        localStorage.setItem('nanoid_token', nanoId)
    }
    return nanoId
}

 shopCart组件获取nanoid(保存在store中) 

在store/shopcart中:
// 引入游客id nanoid_token
import { nanoid_token } from "@/utils/nanoid_token"

// 存储共享数据
const state = {
    nanoid_token: nanoid_token()
}

将nanoid添加到请求头中:

在api/ajax中:
// 引入游客id
import store from "@/store"
// 添加请求拦截器
requests.interceptors.request.use(
    (config) => {
        // 这里写发送请求后的操作

        // 如果有游客id就将这个id写在header里带给服务器 会返回对应的id的购物车信息
        if (store.state.ShopCart.nanoid_token) {
            config.headers.userTempId = store.state.ShopCart.nanoid_token
        }
        ...
    },
    (err) => {...}
)

3.动态展示购物车数据

把getters获取到的cartList数据展示到页面上(名字、图片、价格、总价、数量)

在pages/shopcart中:
<ul class="cart-list" v-for="cart in cartList" :key="cart.id">
    <li class="cart-list-con2">
        <img :src="cart.imgUrl">
        <div class="item-msg">{{ cart.skuName }}</div>
    </li>
    <li class="cart-list-con3">
        <span class="price">{{ cart.skuPrice }}</span>
    </li>
    <li class="cart-list-con4">
        <!-- 将输入框内的修改数量和商品实际数量的差传入函数中 -->
        <input autocomplete="off" type="text" :value="cart.skuNum">
        <a href="javascript:void(0)" class="plus" @click="changeSkuNum(1, 0, cart)">+</a>
    </li>
    <li class="cart-list-con5">
        <span class="sum">{{ cart.skuPrice * cart.skuNum }}</span>
    </li>
</ul>

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

4.实现购物车功能

(1)获取购物车商品数据

写成函数方便复用;初次挂载时就发请求加载数据 

methods: {
    // 获取购物车数据
    getData() {
      this.$store.dispatch('getCartList')
    },
},
mounted() {
    // 请求获取购物车数据
    this.getData()
  },

(2)单选并展示总价的功能

用计算属性实时判断cartList中商品的isChecked状态

<div class="chosed">已选择
    <span>{{ total.sumNum }}</span>件商品
</div>
<div class="sumprice">
    <em>总价(不含运费) :</em>
    <i class="summoney">{{ total.sumPrice }}</i>
</div>

computed:{
    // 计算选中商品的总价和数量
    total() {
      let sumPrice = 0
      let sumNum = 0
      this.cartList.forEach((item) => {
        if (item.isChecked) {
          sumPrice += item.skuPrice * item.skuNum
          sumNum += item.skuNum
        }
      })
      return { sumNum, sumPrice }
    },
}

(3)更改商品数量的功能

点击加减按钮进行增减,也可以直接输入数量(要判断输入是否合法)

配置请求更改商品数量的store

修改商品数量的api已经引入,直接引用就行 

在store/shopcart中:
// 引入添加或修改购物车数量
import { addUpdateShopcar } from "@/api"
const actions = {
    // 发送请求修改购物车商品信息 store读取的参数只能是单个;skuNum为原来数量的加减(-1 3等)
    // async函数返回的一定是一个promise对象
    async UpdateShopcar(context, { skuId, skuNum }) {
        const result = await addUpdateShopcar(skuId, skuNum)
        // 修改数量成功
        if (result.code == 200) {
            return 'ok'
        } else { //修改数量失败
            return Promise.reject(new Error('失败!'))
        }
    },
}

实现点击或输入修改数量

以加减形式修改向函数传入标志0;以输入框形式修改传入标志1 

<li class="cart-list-con4">
    <!-- 更改商品数量函数 -->
    <a href="javascript:void(0)" class="mins" @click="changeSkuNum(-1, 0, cart)">-</a>
    <!-- 将输入框内的修改数量和商品实际数量的差传入函数中 -->
    <input autocomplete="off" type="text" minnum="1" class="itxt" 
        :value="cart.skuNum"
        @change="changeSkuNum(($event.target.value - cart.skuNum) * 1, 1, cart)">
    <a href="javascript:void(0)" class="plus" @click="changeSkuNum(1, 0, cart)">+</a>
</li>

methods:{
    // num表示修改后数量和修改前数量的差值;tag=0表示按钮操作,tag=1表示输入操作;cart表示当前操作的商品的数据
    changeSkuNum(num, tag, cart) {
      // 如果tag=0就是按钮操作, 并判断操作后的商品数量是否大于0
      // 如果tag=1,判断输入框内容是否为数字,以及输入的数量是否大于0
      if (!tag && cart.skuNum + num > 0 || tag && !isNaN(num) && num > -cart.skuNum) {
        num = parseInt(num)
      } else { // 如果输入非法的内容会发送更改为0的请求
        num = 0
      }
      // 发送请求
      this.$store.dispatch('UpdateShopcar', {
        skuId: cart.skuId,
        skuNum: num
      }).then(res => {
        // 重新请求获取购物车数据
        this.getData()
      }, (err) => { alert(err); })
    },
}

(4)删除单个商品的功能

配置请求删除商品的api和store

在api/index中:
// 通过skuId删除购物车商品数据 delete请求
export const deleteCart = (skuId) => requests({
    url: `/cart/deleteCart/${skuId}`,
    method: 'delete'
})

在store/shopcart中:
const actions = {
    // 发送请求删除skuId商品的数据
    async deleteCartInfo(context, skuId) {
        const result = await deleteCart(skuId)
        if (result.code == 200) {
            return 'ok';
        } else {
            return Promise.reject();
        }
    },
}

点击删除按钮执行删除单个商品函数

在pages/shopcart中:
<!-- 删除商品信息函数 -->
<a class="sindelet" @click="deleteCartById(cart.skuId)">删除</a>

methods:{
    // 发送请求删除对应的商品数据
    deleteCartById(skuId) {
      this.$store.dispatch('deleteCartInfo', skuId).then(
        res => {
          // 重新请求获取购物车数据
          this.getData()
        }, (err) => { alert(err); })
    },
}

(5)单选商品修改选中状态的功能

点击商品的选择框实时更新服务器中商品的isChecked属性

配置请求修改商品isChecked属性的api和store

在api/index中:
// 修改商品的选中状态 get请求 传递参数/cart/checkCart/{skuID}/{isChecked}
export const changeCartChecked = (skuId, isChecked) => requests({
    url: `/cart/checkCart/${skuId}/${isChecked}`,
    method: 'get'
})

在store/shopcart中:
const actions = {
    // 发送请求修改商品的选中状态isChecked
    async changeChecked(context, { skuId, isChecked }) {
        let result = await changeCartChecked(skuId, isChecked)
        // 修改选中状态成功
        if (result.code == 200) {
            return 'ok'
        } else { // 修改选中状态失败
            console.log(result);
            return Promise.reject(new Error('失败!'))
        }
    },
}

点击选择框触发改变isChecked函数

在pages/shopcart中:
<input type="checkbox" name="chk_list" id="" value="" 
    :checked="cart.isChecked" 
    @change="changeChecked(cart)">

methods:{
    // 点击商品复选框修改isChecked状态
    changeChecked(cart) {
      // 发送修改请求
      this.$store.dispatch('changeChecked', {
        skuId: cart.skuId,
        // 如果目前复选框选中则目标提交修改状态为0,否则为1
        isChecked: cart.isChecked ? '0' : '1'
      }).then(
        (res) => { //成功就重新读取数据
          this.getData()
        }, (err) => { // 失败就输出错误
          alert(err)
        })
    },
}

(6)删除选中的商品的功能

删除选中商品需要实时传递商品的isChecked参数

思路:遍历store里的商品,isChecked参数为真则执行删除单个商品的actions

在pages/shopcart中:
<a @click="deleteAllChecked">删除选中的商品</a>

methods:{
    // 删除所有选中的商品
    deleteAllChecked() {
      this.$store.dispatch('deleteAllChecked').then(
        (res) => {
          this.getData()
        }, (err) => { alert(err) })
    },
}

在store/shopcart中:
const actions = {
    // 删除所有选中状态的商品  
    deleteAllChecked({ getters, dispatch }) {
        let promises = []
        getters.cartList.forEach(item => {
            if (item.isChecked == 1) {
                // 删除选中的元素
                let p = dispatch('deleteCartInfo', item.skuId) //每次返回一个promise对象
                promises.push(p) //将单个promise存放进数组中
            }
        })
        // Promise.all([p1,p2,...])只要有一个promise对象rejected那么整个promise数组都是rejected
        return Promise.all(promises)
    },
}

Promise.all()用法

(1)只有数组中所有promise的状态都变成fulfilled,promises的状态才会变成fulfilled,此时返回值组成一个数组,传递给promises的回调函数。

(2)只要数组之中有一个被rejected,promises的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给promises的回调函数。

(7)点击全选和反选的功能

此处也同理,只需要判断全选框的状态再传给store函数就可以进行isChecked的修改

再用计算属性实时判断全选状态

在pages/shopcart中:
<input class="chooseAll" type="checkbox" 
    :checked="isAllChecked" 
    @change="allChecked($event)">

computed:{
    // 判断是否全选
    isAllChecked() {
      // 数组方法every:每个子项都满足条件时返回真
      let checked = this.cartList.every(item => item.isChecked == true)
      return checked
    }
}
methods:{
    // 点击全选或反选
    allChecked(e) {
      let isChecked = e.target.checked ? '1' : '0'
      this.$store.dispatch('clickAllChecked', isChecked).then((res) => {
        this.getData()
      }, err => {
        alert(err.message)
      })
    },
}

在store/shopcart中:
const actions = {
    // 点击全选或反选,传入isChecked为真说明是正选,反之是反选;和删除所有选中商品的思路一致
    clickAllChecked({ getters, dispatch }, isChecked) {
        let promises = []
        getters.cartList.forEach(item => {
            if (item.isChecked != isChecked) {
                // 和全选复选框状态不一致则修改
                let p = dispatch('changeChecked', {
                    skuId: item.skuId,
                    isChecked
                })
                promises.push(p) // 将小promise对象存放到数组中
            }
        })
        return Promise.all(promises)
    },
}

  • 18
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
要在Vue界面中添加购物车界面,需要以下步骤: 1.创建一个Vue组件来表示购物车界面,可以使用Vue的template语法来定义组件的结构和样式。 2.将购物车数据保存在Vue实例中的data属性中。购物车数据可以包括商品的名称、价格、数量、图片等信息。 3.在组件中使用v-for指令来循环渲染购物车中的商品列表。每个商品列表项可以包含商品的名称、价格、数量和图片等信息。 4.使用Vue的计算属性来计算购物车商品的总价。计算属性可以根据购物车数据中的商品数量和价格来动态计算购物车商品的总价。 5.在组件中使用v-on指令来监听用户的操作,例如添加商品或删除商品。当用户点击“添加到购物车”按钮时,可以将商品的信息添加到购物车数据中;当用户点击删除按钮时,可以从购物车数据中删除商品信息。 6.在Vue实例中引入购物车组件,并将购物车数据作为组件的props属性传递给组件。这样,购物车组件就可以显示购物车中的商品信息,并能够响应用户的操作。 示例代码如下: ``` //购物车组件 <template> <div> <h2>购物车</h2> <ul> <li v-for="(item, index) in cartList" :key="index"> <div>{{ item.name }}</div> <div>{{ item.price }}</div> <div>{{ item.quantity }}</div> <div> <img :src="item.image" alt=""> </div> <div> <button @click="removeItem(index)">删除</button> </div> </li> </ul> <div>总价:{{ totalPrice }}</div> </div> </template> <script> export default { props: { cartList: { type: Array, required: true } }, computed: { totalPrice() { return this.cartList.reduce((total, item) => total + item.price * item.quantity, 0); } }, methods: { removeItem(index) { this.$emit('remove', index); } } } </script> //Vue实例 <template> <div> <h1>商品列表</h1> <ul> <li v-for="(item, index) in productList" :key="index"> <div>{{ item.name }}</div> <div>{{ item.price }}</div> <div> <img :src="item.image" alt=""> </div> <div> <button @click="addToCart(item)">添加到购物车</button> </div> </li> </ul> <cart :cart-list="cartList" @remove="removeFromCart"></cart> </div> </template> <script> import Cart from './Cart.vue'; export default { components: { Cart }, data() { return { productList: [ { name: '商品1', price: 100, quantity: 1, image: 'img1.jpg' }, { name: '商品2', price: 200, quantity: 1, image: 'img2.jpg' }, { name: '商品3', price: 300, quantity: 1, image: 'img3.jpg' } ], cartList: [] } }, methods: { addToCart(item) { const index = this.cartList.findIndex(cartItem => cartItem.name === item.name); if (index === -1) { this.cartList.push({...item, quantity: 1 }); } else { this.cartList[index].quantity++; } }, removeFromCart(index) { this.cartList.splice(index, 1); } } } </script> ``` 在上面的代码中,我们定义了一个购物车组件和一个Vue实例。购物车组件使用了v-for指令来循环渲染购物车中的商品列表,并使用计算属性来动态计算购物车商品的总价。Vue实例中包含了商品列表和购物车列表的数据,以及将商品列表和购物车列表传递给购物车组件的props属性。当用户点击“添加到购物车”按钮时,我们使用addToCart方法将商品信息添加到购物车数据中;当用户点击删除按钮时,我们使用removeFromCart方法从购物车数据中删除商品信息。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值