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