Vue项目流程5,TheDetail和购物车页面的获取数据,Vuex三连环,动态展示等操作,利用swiper制作轮播图,制作放大镜,加入购物车的功能,购物车页面的各功能的实现

TheDetail页面

静态页面

1、pages文件夹内新建Detail文件夹,新建文件TheDetail.vue文件
2、配置路由,由于需要传params参数,所以需要占位,跳转路由时需要带参数good.id

<router-link :to="`/detail/${good.id}`"><img :src="good.defaultImg" /></router-link>

3、滚动行为

export default new VueRouter({
    //配置路由
    routes,
    scrollBehavior (){
        //滚动条在最上方
        return {y:0}
    }
})

获取数据

1、请求接口

export const reqGoodsInfo = (skuId) => requests({url:`/item/${skuId}`,method:'GET'})

2、获取详情信息
vuex需要新增模块detail,回到大仓库进行合并,在小仓库中发请求获取数据,在子组件派发action

import { reqGoodsInfo } from "@/api"

const state = {
    goodInfo:{}
}
const mutations = {
    GETGOODINFO(state,goodInfo){
        state.goodInfo = goodInfo
    }
}
const actions = {
    async getGoodInfo({commit},skuId){
        let result = await reqGoodsInfo(skuId)
        if(result.code == 200){
            commit('GETGOODINFO',result.data)
        }
    }
}
const getters = {}
export default{
    state,
    actions,
    mutations,
    getters
}
mounted(){
    //派发action获取数据
    this.$store.dispatch('getGoodInfo',this.$route.params.skuId)
}

3、数据展示
利用getters简化数据

const getters = {
    categoryView(state){
        //初始时goodInfo是一个空对象,它没有categoryView,所以此时会报错,所以此时至少要返回一个空对象
        return state.goodInfo.categoryView || {}
    },
    skuInfo(){
        return state.goodInfo.skuInfo || {}
    }
}

在组件映射

import { mapGetters } from 'vuex'
computed:{
    ...mapGetters(['categoryView','skuInfo'])
}

使用获取到的数据并且展示

动态展示

1、使用props将父组件的skuImageList传给子组件TheZoom。

//如果服务器的数据还未回来,此时skuInfo为undefined,会报错,所以至少返回一个空对象
skuImageList(){
  return this.skuInfo.skuImageList || []
}
  imgObj(){
    //也要保证子组件里的skuImageList[0]至少返回一个空对象
    return this.skuImageList[0] || {}
  }

不影响效果,但是会报错

2、利用props给子组件ImageList传数据skuImageList。
3、商品属性展示

  <dl v-for="(spuSaleAttr) in spuSaleAttrList" :key="spuSaleAttr.id">
    <dt class="title">{{spuSaleAttr.saleAttrName}}</dt>
    <dd changepirce="0" :class="{active:saleValue.isChecked == 1}" v-for="saleValue in spuSaleAttr.spuSaleAttrValueList" :key="saleValue.id">{{saleValue.saleAttrValueName}}</dd>
  </dl>

绑定点击事件:@click="changeActive(saleValue,spuSaleAttr.spuSaleAttrValueList)"
排他操作:

changeActive(saleValue,arr){
      //全部的属性值
      arr.forEach(item => {
        item.isChecked = '0'
      });
      saleValue.isChecked = '1'
    }

利用Swiper制作轮播图:

watch:{
  skuImageList(){
    this.$nextTick(()=>{
      new Swiper(this.$refs.cur, {
        // 如果需要前进后退按钮
        navigation: {
          nextEl: ".swiper-button-next",
          prevEl: ".swiper-button-prev",
        },
        //图片个数展示设置
        slidesPerView:3,
        //每一次切换图片个数,默认为1
        slidesPerGroup:1
      });
    })
  }
}

点击加框并且切换大图图片:

methods:{
  changeCurrentIndex(index){
    //修改响应式数据
    this.currentIndex = index
    //通知兄弟组件,当前索引值为几
    this.$bus.$emit('getIndex',this.currentIndex)
  }
}

利用全局事件总线兄弟组件获取数据并进行相关操作

data(){
  return {
    currentIndex:0
  }
},
computed:{
  imgObj(){
    //也要保证子组件里的skuImageList[0]至少返回一个空对象
    return this.skuImageList[this.currentIndex] || {}
  }
},
mounted(){
  //全局事件总线,获取兄弟组件传递过来的索引值
  this.$bus.$on('getIndex',(index)=>{
    this.currentIndex = index
  })
}

放大镜:

  <div class="spec-preview">
    <img :src="imgObj.imgUrl" />
    <div class="event" @mousemove="handler"></div>
    <div class="big">
      <img :src="imgObj.imgUrl" ref='big'/>
    </div>
    <!-- 遮罩层 -->
    <div class="mask" ref="mask"></div>
  </div>
handler(event){
        let mask = this.$refs.mask
        let big = this.$refs.big
        let left = event.offsetX - mask.offsetWidth/2
        let top = event.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'
      }

产品个数:

<div class="controls">
                <input autocomplete="off" class="itxt" v-model="skuNum" @change='changeSkuNum'/>
                <a href="javascript:" class="plus" @click="skuNum++">+</a>
                <a href="javascript:" class="mins" @click="skuNum>1?skuNum--:skuNum = 1">-</a>
              </div>
//修改产品个数
    changeSkuNum(event){
      let value = event.target.value * 1
      if(isNaN(value)||value<1){
        this.skuNum = 1
      }else{
        this.skuNum = parseInt(value)
      }
    }

加入购物车

1、发起添加购物车的请求并且携带相应参数

//将产品添加到购物车中(获取更新某一个产品的个数)地址:/api/cart/addToCart/{skuId}/{skuNum}  请求方式 POST   参数:需要
export const reqAddOrUpdateShopCart = (skuId,skuNum) => requests({url:`/cart/addToCart/${skuId}/${skuNum}`,method:'POST'})

2、书写actions

    //将产品添加到购物车
   async addOrUpdateShopCart({skuId,skuNum}){
    //服务器写入成功并没有返回其他数据,只是返回code=200,代表操作成功,不需要三连环存储数据
       let result = await reqAddOrUpdateShopCart(skuId,skuNum)
       console.log(result);
    }

3、给‘加入购物车’按钮添加事件

// 在跳转路由之前需要发一次请求
async addShopcar(){
  //派发actions并且带参
  //1、发请求,将产品加入到数据库(通知服务器)await this.$store.dispatch('addOrUpdateShopCart',{skuId:this.$route.params.skuId,skuNum:this.skuNum})
  /*
  当前派发了一个action,向服务器发了请求,但是此时返回的结果在仓库中,但此时在此处需要判断加入购物车是否成功
  以上发请求代码实则在调用仓库中的addOrUpdateShopCart,并且返回的是一个Promise
  */
  //2、成功 进行路由跳转并且传参
  //3、失败 给用户提示
  try {
    await this.$store.dispatch('addOrUpdateShopCart',{skuId:this.$route.params.skuId,skuNum:this.skuNum})
    //路由跳转
    this.$router.push({name:'addcartsuccess'})
  } catch (error) {
    alert(error.message)
  }
}

4、添加购物车成功的路由组件

{
        path:'/addcartsuccess',
        name:'addcartsuccess',
        component:AddCartSuccess,
        meta:{
            show:true
        }
    }

5、路由跳转时需将产品信息带给下一个路由组件,简单的信息如skuNum通过query形式传递过去,比较复杂的产品信息如skuInfo,通过会话存储。

//路由跳转
        sessionStorage.setItem('SKUINFO',JSON.stringify(this.skuInfo))
        this.$router.push({name:'addcartsuccess',query:{skuNum:this.skuNum}})
   computed:{
      skuInfo(){
        return JSON.parse(sessionStorage.getItem('SKUINFO'))
      }

浏览器存储功能:HTML5中新增的,本地存储和会话存储
本地存储:持久化–localStorage–5M
会话存储:非持久–sessionStorage–会消失
注意:存储功能一般存储的是字符串,要将对象转化成字符串进行存储,在使用时再将得到的字符串转换成对象

6、展示信息

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

7、查看详情,使用声名式导航并且携带产品的ID

 <router-link :to="`/detail/${skuInfo.id}`" class="sui-btn btn-xlarge">查看商品详情</router-link>

购物车页面

基本展示

1、添加静态页面
2、路由注册

{
    path:'/shopcart',
    name:'shopcart',
    component:ShopCart,
    meta:{
        show:true
    }
}

3、声名式导航

<router-link to="/shopcart">去购物车结算 > </router-link>

4、写接口,获取购物车数据

//获取购物车数据  地址:/api/cart/cartList  请求方式:GET  参数:无
export const reqCartList = ()=> requests({url:'/cart/cartList',method:'GET'})

5、Vuex三连环,获取购物车数据

此时并不能获取到购物车的数据,因为服务器不知道你是谁,此时需要用到uuid临时游客身份,即在加入购物车的瞬间高速浏览器你的uuid。

1)在detail的仓库中添加游客临时身份uuid_token

    //游客的临时身份
    uuid_token:getUUID()

2)书写getUUID

import {v4 as uuidv4} from 'uuid'
//生成一个随机的字符串,且每次执行不发生变化,游客身份持久存储
export const getUUID = ()=>{
    //先从本地存储获取uuid(看一下本地存储是否有)
    let uuid_token = localStorage.getItem('UUIDTOKEN')
    //没有
    if(!uuid_token){
        //生成临时身份
        uuid_token = uuidv4()
        //将身份本地存储
        localStorage.setItem('UUIDTOKEN',uuid_token)
    }
    return uuid_token
}

3)利用请求头将身份告诉服务器

if(store.state.detail.uuid_token){
    //请求头添加一个字段,已与后台协商好
    config.headers.userTempId = store.state.detail.uuid_token
}

4)三连环

const state = {
    cartList:[]
}
const mutations = {
    GETCARTLIST(state,cartList){
        state.cartList = cartList
    }
}
const actions = {
    //获取购物车列表数据
    async getCartList({commit}){
        const result = await reqCartList()
        if(result.code == 200){
            commit('GETCARTLIST',result.data)
        }
    }
}
const getters = {
    cartList(state){
        return state.cartList[0] || {}
    }
}

6、组件获取数据

computed:{
  ...mapGetters(['cartList']),
  cartInfoList(){
    return this.cartList.cartInfoList || []
  }
}

7、动态展示数据

产品数量修改

  <li class="cart-list-con5">
    <a href="javascript:void(0)" class="mins" @click="handler('minus', -1, cart)">-</a>
    <input
      autocomplete="off"
      type="text"
      minnum="1"
      class="itxt"
      :value="cart.skuNum"
      @change="handler('change', $event.target.value * 1, cart)"/>
    <a
      href="javascript:void(0)"
      class="plus"
      @click="handler('add', 1, cart)"
      >+</a></li>
    //修改产品个数
    //type:区分三个元素   disNum:变化量(input里是最终值) cart:哪一个产品(id)
    handler:throttle(async function(type,disNum,cart){
      switch (type) {
        case "add":
          disNum = 1;
          break;
        case "minus":
          //产品个数小于0时不变
          disNum = cart.skuNum > 1 ? -1 : 0;
          break;
        case "change":
          //如果是非法应该带0
          // if (isNaN(disNum) || disNum < 1) {
          //   disNum = 0;
          // } else {
          //   disNum = parseInt(disNum) - cart.skuNum;
          // }
          disNum =
            isNaN(disNum) || disNum < 1 ? 0 : parseInt(disNum) - cart.skuNum;
          break;
      }
      try {
        //代表修改成功
        await this.$store.dispatch("addOrUpdateShopCart", {
          skuId: cart.skuId,
          skuNum: disNum,
        });
        this.getData();
      } catch (error) {
        alert(error.mssage);
      }
    },3000),

接口仍是使用加入购物车的接口,返回修改产品的skuId和变化值,正数为加,负数为减。所以此时每改变一次数值都要向服务器发送一次请求。

用户操作可能会非常频繁,所以需要节流

删除购物车产品

1、使用接口,发送DELETE请求

//删除购物车产品  地址:/api/cart/deleteCart/{skuId}   请求方式:DELETE   参数:需要
export const reqDeleteCartById = (skuId)=>requests({url:`/cart/deleteCart/${skuId}`,method:'DELETE'})

2、书写actions

//删除购物车某一产品
async deleteCartListBySkuId({commit},skuId){
    let result =await reqDeleteCartById(skuId)
    if(result.code == 200){
        return 'ok'
    }else{
        return Promise.reject(new Error('faile'))
    }
}

3、设置点击事件

    //删除某一产品
    async deleteCartById(cart){
  try {
    await this.$store.dispatch('deleteCartListBySkuId',cart.skuId)
    this.getData()
  } catch (error) {
    alert(error.mssage)
  }
}

产品状态

1、写接口,发送get请求

//修改商品选中状态  地址:/api/cart/checkCart/{skuId}/{isChecked}   请求方式:GET   参数:需要
export const reqUpdateCheckedById = (skuId,isChecked)=>requests({url:`/cart/checkCart/${skuId}/${isChecked}`,method:'GET'})

2、actions

//修改购物车某一个产品的选中状态,没有返回数据
async updateCheckedById({commit},{skuId,isChecked}){
    let result = await reqUpdateCheckedById(skuId,isChecked)
    if(result.code == 200){
        return 'ok'
    }else{
        return Promise.reject(new Error('faile'))
    }
}

3、点击事件

//修改产品状态
async updateChecked(cart, event) {
  try {
    let isChecked = event.target.checked ? "1" : "0";
    await this.$store.dispatch("updateCheckedById", {
      skuId: cart.skuId,
      isChecked,
    });
    this.getData()
  } catch (error) {
    alert(error.message);
  }
},

删除选中的全部产品

1、actions

//删除全部勾选的产品
deleteAllCheckedCart({dispatch,getters}){
    //获取购物车全部产品是一个数组
    let PromiseAll = []
    getters.cartList.cartInfoList.forEach(item => {
        let promise = item.isChecked == 1 ? dispatch('deleteCartListBySkuId',item.skuId) :''
        //将返回的Promise返回到数组中
        PromiseAll.push(promise)
    });
    //只有都成功才成功
    return Promise.all(PromiseAll)
}

2、点击事件

//删除全部选中的产品,没办法传参
async deleteAllCheckedCart() {
  try {
    await this.$store.dispatch("deleteAllCheckedCart");
    this.getData();
  } catch (error) {
    alert(error.message);
  }
},

Promise.all([p1,p2,p3])
p1,p2,p3每一个都是Promise对象,如果有一个Promise失败就都失败,如果都成功则返回成功

全选框

1、actions

//修改全部产品的状态
updateAllCartIsChecked({dispatch,state},isChecked){
    let promiseAll = []
    state.cartList[0].cartInfoList.forEach(item=>{
        let promise = dispatch('updateCheckedById',{skuId:item.skuId,isChecked})
        promiseAll.push(promise)
    })
    //最终返回的结果
    return Promise.all(promiseAll)
}

2、点击事件

//修改全部产品的选择状态
async updateAllCartChecked(event) {
  try {
    let isChecked = event.target.checked ? "1" : "0";
    await this.$store.dispatch("updateAllCartIsChecked",isChecked);
    this.getData();
  } catch (error) {
    alert(error.message);
  }
},

3、显示

//计算属性判断全选框是否勾选
isAllCheck() {
  return this.cartInfoList.every((item) => item.isChecked == 1);
},
<input class="chooseAll" type="checkbox" :checked="isAllCheck&&cartInfoList.length"  @change="updateAllCartChecked"/>
<span>全选</span>

every:遍历数组中每一个元素,有一个为假就为false

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小婵婵不怕鬼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值