点击 加入购物车 的时候,先发请求,把数据存储到服务器,然后再进行路由跳转
// 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 @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
引入和配置路由
// 配置路由的地方
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'})
}
// 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>
开始渲染数据
在点击 加入购物车 的时候,告诉服务器你是谁
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"