文章目录
八、加入购物车
点击 加入购物车 的时候,先发请求,把数据存储到服务器,然后再进行路由跳转
- 当点击 加入购物车 的时候,会跳转到 加入购物车成功的页面,到时候需要配置路由,进行路由跳转
路由跳转之前发请求
api—请求接口
// src/api/index.js
// 将产品添加到购物车中(或者 更新某一个产品个数)
// /api/cart/addToCart/{ skuId }/{ skuNum } POST 带参数
export const reqAddOrUpdateShopCart = (skuId,skuNum)=>{
return requests({
url:`/cart/addToCart/${
skuId}/${
skuNum}`,method:'POST'})
}
将数据存储到服务器里
-
派发actions,发请求
// src / pages / detail /index.vue <div class="add"> <!-- 以前的路由跳转,从A路由跳转到B路由 --> <!-- 在这里路由跳转,进行路由跳转之前,需要发请求 把你购买的产品信息通过请求的形式通知服务器,服务器进行相应的存储 --> <a @click="addShopcar">加入购物车</a> </div>
// 加入购物车的回调函数 addShopcar() { // 1. 发请求--将产品加入到数据库(通知服务器) // 服务器存储成功----进行路由跳转 // 失败,给用户提示 this.$store.dispatch( "detail/addOrUpdateShopCart",{ skuId:this.$route.params.skuId,skuNum:this.skuNum}); },
加入购物车以后(发请求),前台将参数带给服务器
服务器写入数据成功,并没有返回其他的数据,只是返回code=200,代表这次成功
⚠ !!!因为服务器没有返回其余的数据,所以我们不需要vuex来存储数据
-
将参数带给服务器
const actions = { // 将产品添加到购物车中 async addOrUpdateShopCart({ commit},{ skuId,skuNum}){ // 加入购物车返回的结果 let result = await reqAddOrUpdateShopCart(skuId,skuNum); console.log('购物车',result); // 加入购物车以后(发请求),前台将参数带给服务器 // 服务器写入数据成功,并没有返回其他的数据,只是返回code=200,代表这次成功 // 因为服务器没有返回其余的数据,所以我们不需要vuex来存储数据 }, };
判断加入成功 或 失败
async
:只要有async返回的就是Promise,Promise返回的不是成功就是失败
-
发请求–将产品加入到数据库(通知服务器)
-
服务器存储成功----进行路由跳转
-
服务器存储失败------给用户提示
// src/pages/detail/index.vue
// 加入购物车的回调函数
async addShopcar() {
// 1. 发请求--将产品加入到数据库(通知服务器)
// 服务器存储成功----进行路由跳转
// 失败------给用户提示
try {
await this.$store.dispatch("detail/addOrUpdateShopCart", {
skuId: this.$route.params.skuId,skuNum: this.skuNum,});
// 成功了进行路由跳转
.....
} catch (error) {
alert(error.message);
}
},
this.$store.dispatch("detail/addOrUpdateShopCart", {skuId: this.$route.params.skuId,skuNum: this.skuNum,});
表示调用了addOrUpdateShopCart
这个函数,👇调用这个函数,这个函数有async,说明这个函数的返回值是Promise函数await返回的是promise成功的值,但是我们要有成功做什么以及失败做什么…如果await的promise失败了,就会抛出异常,我们需要通过try…catch捕获处理
所以在detail.vue中,给 加入购物车的回调函数加上 async
// src/store/detail/index.js
// 将产品添加到购物车中
async addOrUpdateShopCart({
commit }, {
skuId, skuNum }) {
// 加入购物车返回的结果
// 加入购物车以后(发请求),前台将参数带给服务器
// 服务器写入数据成功,并没有返回其他的数据,只是返回code=200,代表这次成功
// 因为服务器没有返回其余的数据,所以我们不需要vuex来存储数据
let result = await reqAddOrUpdateShopCart(skuId, skuNum);
// 当前的这个函数,如果执行,返回promise
// 👇代表服务器加入购物车成功
if(result.code==200){
return 'ok'; // 返回的只要是非空字符串就是成功
}else{
// 代表加入购物车失败
return Promise.reject(new Error('false'));
}
},
成功后进行路由跳转
注意:路由不是组件!!!??????❓
路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。使用组件的步骤:创建-注册-引用-使用
-
创建路由,写到专门放路由的文件夹下
-
编写路由配置项:src/router/index.js
// 配置路由的地方 import Vue from 'vue'; import VueRouter from 'vue-router'; // 使用插件 Vue.use(VueRouter); // 引入路由组件 import AddCartSuccess from '../pages/AddCartSuccess' // 先把VueRouter原型对象的push,先保存一份 const originalPush = VueRouter.prototype.push const originalReplace = VueRouter.prototype.replace // 重写push | replace // 参数:告诉原来的push方法,你往哪里跳转(传递哪些参数) VueRouter.prototype.push = function push(location) { return originalPush.call(this, location).catch(err => err) } VueRouter.prototype.replace = function replace(location) { return originalReplace.call(this, location).catch(err => err) } // 配置路由 export default new VueRouter({ // 配置路由 routes: [ ... { name: 'addcartsuccess', path: '/addcartsuccess', component: AddCartSuccess, meta: { showFooter: true } }, // 重定向:在项目跑起来的时候,访问/,立马让他定向到首页!!! { path: '*', redirect: '/home', } ], // 控制滚动条的滚动行为 scrollBehavior(to, from, savedPosition) { // return 期望滚动到哪个的位置 return { y: 0 }; // 始终滚动到顶部 } })
开始进行路由跳转,并进行路由传参
路由传参的话数据过多,在skuInfo里面,是个对象,以及还要带参数skuNum,地址栏会有点乱
所以这里我们只带skuNum参数传过去,其余复杂数据用会话存储—不持久化,会话结束数据消失
浏览器存储功能:HTML5中新增的,分为本地存储和会话存储
本地存储:持久化的,具有上限-----5M
会话存储:不是持久化的(浏览器关闭等),
// 加入购物车的回调函数
async addShopcar() {
// 1. 发请求--将产品加入到数据库(通知服务器)
// 服务器存储成功----进行路由跳转
// 失败------给用户提示
try {
await this.$store.dispatch("detail/addOrUpdateShopCart", {
skuId: this.$route.params.skuId,
skuNum: this.skuNum,
});
// 成功了进行路由跳转,并将产品的信息带给下一级的路由组件
// 会话存储 | 本地存储,一般存储的是字符串,所以将对象转换为字符串
sessionStorage.setItem('SKUINFO',JSON.stringify(this.skuInfo))
this.$router.push({
name: "addcartsuccess",
query:{
skuNum:this.skuNum},
});
} catch (error) {
alert(error.message);
}
},
本地存储 里面只能存储字符串格式 ,因此需要把对象转换为字符串
JSON.stringify()
获取本地存储数据,需要把里面的字符串转换为对象格式
JSON.parse()
我们才能使用里面的数据。
获取本地存储数据
开始渲染页面
<div class="left-good">
<div class="left-pic">
<img :src="skuInfo.skuDefaultImg" />
</div>
<div class="right-info">
<p class="title">{
{ skuInfo.skuName }}</p>
<p class="attr">
<span v-for="attrName in skuInfo.skuSaleAttrValueList" :key="attrName.id">{
{ attrName.saleAttrName }} {
{attrName.saleAttrValueName}} </span>
<span>数量:{
{$route.query.skuNum}}</span>
</p>
</div>
</div>
<div class="right-gocart">
购物车静态组件与修改
-
跳转到detail商品详情页,携带参数
<div class="right-gocart"> <router-link :to="`/detail/${skuInfo.id}`" class="sui-btn btn-xlarge">查看商品详情</router-link> <a href="javascript:">去购物车结算 > </a> </div>
-
跳转到购物车页面ShopCart
九、完成ShopCart购物车模块业务
-
引入和配置路由
// 配置路由的地方 import Vue from 'vue'; import VueRouter from 'vue-router'; // 使用插件 Vue.use(VueRouter); // 引入路由组件 import ShopCart from '../pages/ShopCart' // 先把VueRouter原型对象的push,先保存一份 const originalPush = VueRouter.prototype.push const originalReplace = VueRouter.prototype.replace // 重写push | replace // 参数:告诉原来的push方法,你往哪里跳转(传递哪些参数) VueRouter.prototype.push = function push(location) { return originalPush.call(this, location).catch(err => err) } VueRouter.prototype.replace = function replace(location) { return originalReplace.call(this, location).catch(err => err) } // 配置路由 export default new VueRouter({ // 配置路由 routes: [ { name: 'shopcart', path: '/shopcart', component: ShopCart, meta: { showFooter: true }, }, // 重定向:在项目跑起来的时候,访问/,立马让他定向到首页!!! { path: '*', redirect: '/home', } ], // 控制滚动条的滚动行为 scrollBehavior(to, from, savedPosition) { // return 期望滚动到哪个的位置 return { y: 0 }; // 始终滚动到顶部 } })
-
路由跳转
<div class="right-gocart"> <router-link :to="`/detail/${skuInfo.id}`" class="sui-btn btn-xlarge">查看商品详情</router-link> <router-link to="/shopcart">去购物车结算 > </router-link> </div>
向服务器发起请求,获取购物车数据
向服务器发请求
/api/cart/cartList GET 无参数
// src/api/index.js
// 获取购物车列表数据 /api/cart/cartList GET 无参数
export const reqCartList = ()=>{
return requests({
url:'/cart/cartList',method:'GET'})
}
操作vuex 将数据存储到仓库中
- 新建一个仓库,用来存储购物车的数据
// src/store/shopcart/index.js
import {
reqCartList } from '@/api/index'
const state = {
};
const mutations = {
};
const actions = {
};
const getters = {
};
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
- 在大仓库中引用小仓库
// src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
// 需要使用插件
Vue.use(Vuex);
// 引入小仓库
import home from './home'
import search from './search'
import detail from './detail'
import shopcart from './shopcart';
// 对外暴露Store类的一个实例
export default new Vuex.Store({
// 实现vuex仓库模块式开发存储数据
modules:{
home,
search,
detail,
shopcart
}
})
-
vuex三连环
const actions = { // 获取购物车列表的数据 async getCartList({ commit }) { let result = await reqCartList(); console.log('购物车列表',result); }, };
export default { name: 'ShopCart', mounted(){ // 获取服务器数据 this.getData(); }, methods:{ // 获取个人购物车数据 getData(){ this.$store.dispatch('shopcart/getCartList'); } } }
注意:发请求的时候,获取不到你购物车里的数据,因为这里不知道购物车获取谁的数据,需要给用户加一个身份!所以需要 👉 UUID临时游客身份
-
开始真正的vuex三连环!!
👇这个是shopcart仓库里面的数据,有点复杂,所以我们要简化
import { reqCartList } from '@/api/index' const state = { cartList:[], }; const mutations = { GETCARTLIST(state, cartList) { state.cartList = cartList; } }; const actions = { // 获取购物车列表的数据 async getCartList({ commit }) { let result = await reqCartList(); if (result.code == 200) { commit('GETCARTLIST', result.data); // result.data 是个数组 } }, }; const getters = { cartList(state){ // state.cartList[0] 如果没有返回,至少是个数组 return state.cartList[0]|| []; }, }; export default { namespaced: true, state, mutations, actions, getters }
现在仓库里面有了数据
组件获取数据展示数据
...mapGetters("shopcart", ["cartList"])
, 组件开始获取数据
遍历用every
<script>
import {
mapGetters } from "vuex";
export default {
name: "ShopCart",
mounted() {
// 获取服务器数据
this.getData();
},
methods: {
// 获取个人购物车数据
getData() {
this.$store.dispatch("shopcart/getCartList");
},
},
computed: {
...mapGetters("shopcart", ["cartList"]),// 并不是真正的购物车列表数据
// 真正的购物车列表数据
cartInfoList() {
// 至少是个空数组
return this.cartList.cartInfoList || [];
},
// 计算购买产品的总价
totalPrice() {
let sum = 0;
this.cartInfoList.forEach((item) => {
// item是购物车列表的每一行数据
sum += item.skuNum * item.cartPrice;
});
return sum;
},
// 判断底部的复选框是否勾选
isAllChecked(){
// 遍历每一个产品的isChecked,只要全部元素isChecked属性都为1,返回为真
return this.cartInfoList.every(item=>item.isChecked==1)
},
},
};
</script>
开始渲染数据
UUID临时游客身份
在点击 加入购物车 的时候,告诉服务器你是谁
utils :放一些常用的功能模块,比如正则,uuid
-
将游客身份用会话存储(sessionStorage)保存,放到detail仓库里
uuid是一个随机的字符串,且每次执行不能发生变化,且持久存储,
所以每次调用getUUID函数的时候,先从本地存储获取uuid,看会话存储是否有,
如果有的话就返回会话存储里面的uuid,
如果没有的话,就生成uuid
// src/utils/uuid_token.js
import {
v4 as uuidv4} from 'uuid'
// 要生成一个随机的字符串,且每次执行不能发生变化,游客身份持久存储
export const getUUID = ()=>{
// 先从会话存储获取uuid,看一下本地存储是否有
let uuid_token = sessionStorage.getItem('UUIDTOKEN');
// 如果没有,我就生成UUID
if(!uuid_token){
uuid_token=uuidv4();
// 会话存储 存储一次
sessionStorage.setItem('UUIDTOKEN',uuid_token);
}
// 切记要有返回值!
return uuid_token;
}
在detail仓库里面存储uuid
// src/store/detail/index.js
// 封装游客身份模块uuid---生成一个随机的字符串(不能再变了)
import {
getUUID} from '@/utils/uuid_token'
const state = {
goodsInfo: {
}, // 看api文档,result.data返回的是一个对象
// 游客临时身份
uuid_token:getUUID(),
};
现在游客身份在仓库里,我们要把数据带给服务器,可以利用请求头把数据带给服务器
找到请求拦截器,在请求拦截器捞到仓库里的数据
// src/api/request.js
// 在当前模块引入store仓库
import store from '@/store'
.....
// 请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情
requests.interceptors.request.use((config) => {
// config:配置对象,对象里面有一个属性很重要:headers请求头
if (store.state.detail.uuid_token) {
// 给请求头添加一个字段(userTempId):和后台老师商量好!
config.headers.userTempId = store.state.detail.uuid_token
}
// 进度条开始动
nprogress.start();
return config;
})
动态展示购物车
修改购物车产品的数量
点击 + 或 - 或 修改input框里的数字,input框里发生变化
问:这个时候向服务器发请求吗?
发请求,如果不发请求的话,服务器里的数据还是原来的,那么该页面的关于数量的数据和原来一样,
-----------所以我们每当修改的时候,就要发请求给服务器,然后再从服务器捞到数据,进行渲染页面
这个之前写过api(在将产品添加到购物车那里,detail详情页,也有仓库了)所以我们可以把数据存到detail仓库,就现在直接派发action就行了,然后再重新捞数据渲染
这里的skuNum是 现在状态的数字 与 起始状态的数字 的差值。比如:现在商品数量是5,我们要买12,这个skuNum是12-5 = 7
-
找到产品数量的结构位置
<li class="cart-list-con5"> <a href="javascript:void(0)" class="mins">-</a> <!-- 购买的个数 --> <input autocomplete="off" type="text" minnum="1" class="itxt" :value="cart.skuNum" /> <a href="javascript:void(0)" class="plus">+</a> </li>
这三个节点 要派发同一个action,也就是说这三个节点调用同一个回调函数
问:但是如何判断这三个节点?
通过传参。三个不同的参数,用形参接收,来区分这三个节点
-
要传三个参数
第一个参数type 是用来区分它们三个节点
第二个参数是disNum,他们的变化量,+号是1,-号是-1,input框暂定不是变化量,是修改后的值
第三个参数是cart 当前他们点击的产品的信息cart,然后得知他们的id,因为要发请求需要skuID
<li class="cart-list-con5"> <a href="javascript:void(0)" class="mins" @click="handler('mins',-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('plus',1,cart)">+</a> </li>
-
先写 + 号 和 - 号
加号:直接带给服务器变化的量
减号:判断产品的当前数量是否大于1,大于1才能减,传递给服务器-1
然后就是派发action发请求,发给服务器后再 捞数据 渲染页面
// 购物车里 修改某一个产品的个数 handler(type, disNum, cart) { //disNum 代表 现在状态的数字 与 起始状态的数字 的差值 // 目前disNum 形参: + 号变化量(1),- 号 变化量 (-1),input 最终的量(并不是变化量) // 向服务器发请求,修改数量 switch (type) { // 加号 case "plus": { // 带给服务器变化的量