购物车
购物车功能分析
目的:了解购物车两种状态下的操作逻辑,方便后续的开发理解。

总结:
- 购物车的各种操作都会有两种状态的区分,但是不会在组件中去区分。
- 而是在封装在vuex中的actions中去区分,在组件上只需调用actions即可。
- 在actions中通过user信息去区分登录状态
- 未登录,通过mutations修改vuex中的数据即可,vuex已经实现持久化,会同步保持在本地。
- 已登录,通过api接口去服务端操作,响应成功后通过mutations修改vuex中的数据即可,它也会同步在本地。
- 不管何种操作何种状态返回一个promise,然后组件能够判断操作是否完毕是否成功,再而去做其他事情。
注意:
- 登录后,需要合并本地购物车到服务端。
- 退出后,清空vuex数据也会同步清空本地数据。
加入购物车-本地
目的:完成商品详情的添加购物车操作,支持未登录状态。
大致步骤:
- 约定本地存储的信息内容:要和加入购物车的接口参数保持一致
- 编写mutaions添加购物车逻辑
- 编写actions进行添加操作
- 在商品详情页实现添加逻辑
落地代码:
- vuex中的修改数据,获取数据
src/store/module/cart.js
// 本地:id skuId name picture price nowPrice count attrsText selected stock isEffective// 线上:比上面多 isCollect 有用 discount 无用 两项项信息mutations: {// 把商品添加到购物车insertCart (state, goods) {// goods参数表示商品详情信息(包含我们需要的相关参数)// 如果多次加入同一件商品,那么需要进行商品数量的累加操作// 根据skuId判断是否是同一件商品const index = state.list.findIndex(item => item.skuId === goods.skuId)if (index !== -1) {// 有这件商品,进行商品数量的累加操作goods.count = state.list[index].count + 1// 删除原来的商品state.list.splice(index, 1)}state.list.unshift(goods)}},actions: {// action方法触发后的默认返回值是Promise实例对象insertCart (context, goods) {// 判断当前用户是否处于登录状态// context.rootState可以获取所有的模块的状态const token = context.rootState.user.profiletokenif (token) {// 已经登录,调用接口加入购物车} else {// 尚未登录,添加商品到本地购物车context.commit('insertCart', goods)}}}
- 商品详情点击加入购物车
src/views/goods/index.vue
setup () {const goodsDetail = useGoods()// sku改变时候触发const skuInfo = (sku) => {if (sku.skuId) {goodsDetail.value.price = sku.pricegoodsDetail.value.oldPrice = sku.oldPricegoodsDetail.value.inventory = sku.inventory// 记录当前选中的sku信息+ currentSku.value = sku} else {+ currentSku.value = null}}// 选择的数量const num = ref(1)// 加入购物车逻辑+ // 获取当前组件的实例+ const instance = getCurrentInstance()+ // 加入购物车+ const insertCart = () => {+ // 判断是否选中了规格+ if (!currentSku.value) {+ // 进行消息提示+ return Massage({ type: 'warn', text: '请选择规格' })+ }+ // 判断是否还有库存+ // 判断是否还有库存+ if (num.value > goodsDetail.inventory) {+ // 库存不够,进行提示+ // instance.proxy表示组件的实例对象+ return instance.proxy.$message({ type: 'warn', text: '库存不够' })+ // return Massage({ type: 'warn', text: '库存不够' })+ }+ // 触发加入购物车的action+ // 加入购物车的信息 id skuId name picture price nowPrice count attrsText selected stock isEffective+ const goodsInfo = {+ // 当前商品的id+ id: goodsDetail.value.id,+ // 加入购物车的商品的skuId+ skuId: currentSku.value.id,+ // 商品名称+ name: goodsDetail.value.name,+ // 商品的第一张图片,在购物进行显示+ picture: goodsDetail.value.mainPictures[0],+ // 默认价格+ price: currentSku.value.price,+ // 当前价格+ nowPrice: currentSku.value.price,+ // 购买的数量+ count: num.value,+ // 规格参数+ attrsText: currentSku.value.attrsText,+ // 当前商品是否选中:默认设置为选中,用于支付+ selected: true,+ // 当前库存+ stock: currentSku.value.inventory,+ // 当前商品是否有效:默认值有效,这个商品依然在售+ isEffective: true+ }+ store.dispatch('cart/insertCart', goodsInfo)+ }
总结:
- 收集商品的数据,添加到购物车列表(vuex)
- 获取组件的实例对象 const instance = getCurrentInstance() ; instance.proxy
回顾
- QQ登录
- 配置QQ登录的基本环境:配置DNS;配置webpack
- 实现QQ登录图标按钮:点击后跳转到QQ授权登录的页面
- 准备回跳的页面:基本布局和路由配置
- 完善回跳页面的基本布局
- 完善用户的登录信息:尚未注册小兔鲜账号
- 绑定手机号:已经注册小兔鲜账号,但是尚未绑定手机号
- 扫码后直接登录:已经注册小兔鲜账号,并且已经绑定手机号
- 购物车
- 熟悉核心的流程:本地购物车;登录后远程购物车
- 添加本地购物车:
- 实现添加购物车的mutation方法
- 实现添加购物车的action方法
- 触发添加的动作
- 基于vue3提供的方法访问组件的实例对象
头部购物车-基础布局
目的:在网站头部购物车图片处,鼠标经过展示购物车列表。

大致步骤:
- 提取头部购物车组件,完成基础布局。
- 通过getters返回,有效商品总数,和有效商品列表。
- 渲染组件。
落的代码:
- 新建组件
src/views/layout/components/top-header-cart.vue
<template><div class="cart"><a class="curr" href="#"> <i class="iconfont icon-cart"></i><em>2</em> </a></div></template><script>export default {name: 'AppHeaderCart'}</script><style scoped lang="less">.cart {width: 50px;.curr {height: 32px;line-height: 32px;text-align: center;position: relative;display: block;.icon-cart {font-size: 22px;}em {font-style: normal;position: absolute;right: 0;top: 0;padding: 1px 6px;line-height: 1;background: @helpColor;color: #fff;font-size: 12px;border-radius: 10px;font-family: Arial;}}}</style>
- 把
src/views/layout/components/top-header.vue文件中的图标代码迁移到购物车组件中。
<div class="search"><i class="iconfont icon-search"></i><input type="text" placeholder="搜一搜"></div>+ <TopHeaderCart /></div></header></template><script>import AppHeaderNav from './app-header-nav'+import TopHeaderCart from './top-header-cart.vue'export default {name: 'AppHeader',+ components: { AppHeaderNav, TopHeaderCart }}</script>
- 基础布局和弹出效果
src/components/app-header-cart.vue
<template><div class="cart"><a class="curr" href="javascript:;"><i class="iconfont icon-cart"></i><em>2</em></a><div class="layer"><div class="list"><div class="item" v-for="i in 4" :key="i"><RouterLink to=""><img src="https://yanxuan-item.nosdn.127.net/ead73130f3dbdb3cabe1c7b0f4fd3d28.png" alt=""><div class="center"><p class="name ellipsis-2">和手足干裂说拜拜 ingrams手足皲裂修复霜</p><p class="attr ellipsis">颜色:修复绿瓶 容量:150ml</p></div><div class="right"><p class="price">¥45.00</p><p class="count">x2</p></div></RouterLink><i class="iconfont icon-close-new"></i></div></div><div class="foot"><div class="total"><p>共 3 件商品</p><p>¥135.00</p></div><XtxButton type="plain">去购物车结算</XtxButton></div></div></div></template><script>export default {name: 'TopHeaderCart'}</script><style scoped lang="less">.cart {width: 50px;position: relative;z-index: 600;.curr {height: 32px;line-height: 32px;text-align: center;position: relative;display: block;.icon-cart {font-size: 22px;}em {font-style: normal;position: absolute;right: 0;top: 0;padding: 1px 6px;line-height: 1;background: @helpColor;color: #fff;font-size: 12px;border-radius: 10px;font-family: Arial;}}&:hover {.layer {opacity: 1;transform: none}}.layer {opacity: 0;transition: all .4s .2s;transform: translateY(-200px) scale(1, 0);width: 400px;height: 400px;position: absolute;top: 50px;right: 0;box-shadow: 0 0 10px rgba(0,0,0,0.2);background: #fff;border-radius: 4px;padding-top: 10px;&::before {content: "";position: absolute;right: 14px;top: -10px;width: 20px;height: 20px;background: #fff;transform: scale(0.6,1) rotate(45deg);box-shadow: -3px -3px 5px rgba(0,0,0,0.1);}.foot {position: absolute;left: 0;bottom: 0;height: 70px;width: 100%;padding: 10px;display: flex;justify-content: space-between;background: #f8f8f8;align-items: center;.total {padding-left: 10px;color: #999;p {&:last-child {font-size: 18px;color: @priceColor;}}}}}.list {height: 310px;overflow: auto;padding: 0 10px;&::-webkit-scrollbar{width:10px;height:10px;}&::-webkit-scrollbar-track{background: #f8f8f8;border-radius: 2px;}&::-webkit-scrollbar-thumb{background: #eee;border-radius:10px;}&::-webkit-scrollbar-thumb:hover{background: #ccc;}.item {border-bottom: 1px solid #f5f5f5;padding: 10px 0;position: relative;i {position: absolute;bottom: 38px;right: 0;opacity: 0;color: #666;transition: all .5s;}&:hover {i {opacity: 1;cursor: pointer;}}a {display: flex;align-items: center;img {height: 80px;width: 80px;}.center {padding: 0 10px;width: 200px;.name {font-size: 16px;}.attr {color: #999;padding-top: 5px;}}.right {width: 100px;padding-right: 20px;text-align: center;.price {font-size: 16px;color: @priceColor;}.count {color: #999;margin-top: 5px;font-size: 16px;}}}}}}</style>
总结:封装购物车头部列表的组件
- 使用getters得到有效商品列表和期种件数
src/store/modules/cart.js
getters: {// 有效商品列表validList (state) {return state.list.filter(item => item.stock > 0 && item.isEffective)},// 有效商品件数validTotal (state, getters) {return getters.validList.reduce((p, c) => p + c.count, 0)},// 有效商品总金额validAmount (state, getters) {return getters.validList.reduce((p, c) => p + c.nowPrice * 100 * c.count, 0) / 100}}
- 渲染头部购物车信息
<template><div class="cart"><a class="curr" href="javascript:;"><i class="iconfont icon-cart"></i><em>{{$store.getters['cart/validTotal']}}</em></a><div class="layer"><div class="list"><div class="item" v-for="item in $store.getters['cart/validList']" :key="item.skuId"><RouterLink to=""><img :src="item.picture" alt=""><div class="center"><p class="name ellipsis-2">{{item.name}}</p><p class="attr ellipsis">{{item.attrsText}}</p></div><div class="right"><p class="price">¥{{item.nowPrice}}</p><p class="count">x{{item.count}}</p></div></RouterLink><i class="iconfont icon-close-new"></i></div></div><div class="foot"><div class="total"><p>共 {{$store.getters['cart/validTotal']}} 件商品</p><p>¥{{$store.getters['cart/validAmount']}}</p></div><XtxButton type="plain">去购物车结算</XtxButton></div></div></div></template>
总结:
- 通过getters动态计算有效的购物车商品的列表;总数;总价
头部购物车-列表状态修改-本地
目的:根据本地存储的商品获取最新的库存价格和有效状态。
顶部的购物车展示的商品有可能失效,所以需要调用接口查询相应规格的商品是否依然有效。
大致步骤:
- 定义获取最新信息的API
- 定义修改购物车商品信息的mutations
- 定义获取购物车列表信息的actions
- 在头部购物车组件初始化的时候更新列表信息
落的代码:
- 定义获取最新信息的API
src/api/cart.js
import request from '@/utils/request'// 获取新的商品信息(根据规格的skuId)export const getNewCartGoods = (skuId) => {return request({method: 'get',url: `/goods/stock/${skuId}`})}
- 定义修改购物车商品信息的mutations
src/store/module/cart.js
// 修改购物车商品updateCart (state, goods) {// goods中字段有可能不完整,goods有的信息才去修改。// 1. goods中必需又skuId,才能找到对应的商品信息const updateGoods = state.list.find(item => item.skuId === goods.skuId)for (const key in goods) {if (goods[key] !== null && goods[key] !== undefined && goods[key] !== '') {updateGoods[key] = goods[key]}}}
- 定义获取购物车列表信息的actions
src/store/module/cart.js
import { getNewCartGoods } from '@/api/cart.js'// 更新商品最新信息的actionfindCartList (context) {// 遍历每一件购物车的商品,分别查询每一件商品的最新信息const promiseArr = []context.state.list.forEach(item => {const ret = getNewCartGoods(item.skuId)promiseArr.push(ret)})// 并发触发多个异步任务,所有任务的结果获取后,可以通过then的回调的参数result得到所有的结果Promise.all(promiseArr).then(result => {// result表示所有商品的查询结果result.forEach((goods, i) => {context.commit('updateCart', {skuId: context.state.list[i].skuId,...goods.result})})}).catch(() => {// 获取购物车最新商品数据失败// console.log(err)Massage({ type: 'error', text: '获取购物车最新商品数据失败' })})},
- 再头部购物车组件初始化的时候更新列表信息
src/views/layout/components/top-header-cart.vue
setup () {const store = useStore()store.dispatch('cart/findCartList')}
总结:
- 准备更新购物车商品的action(批量更新)
- Promise.all触发多个任务用法
头部购物车-删除操作-本地
目的:完成头部购物车删除操作,支持未登录状态。
大致步骤:
- 编写mutaions删除购物车商品逻辑
- 编写actions进行删除操作
- 在头部购物车进行删除逻辑
落的代码:
- vuex的mutations和actions代码
src/store/module/cart.js
mutations: {// ... 省略// 删除顶部购物车单独的一件商品deleteCart (state, skuId) {const index = state.list.findIndex(item => item.skuId === skuId)if (index !== -1) {// 删除数组中一个元素state.list.splice(index, 1)}},},
actions: {// ... 省略// 删除顶部购物车的单件商品deleteCart (context, skuId) {const token = context.rootState.user.profiletokenif (token) {// 已经登录} else {// 未登录context.commit('deleteCart', skuId)}},}
- 头部组件实现删除逻辑
src/layout/views/components/top-header-cart.vue
+ 购物车无商品不显示弹出层,并且不是在购物车页面+<div class="layer" v-if="$store.getters['cart/validTotal']&&$route.path!=='/cart'">
+ 绑定点击事件传入skuId+<i @click="deleteCart(item.skuId)" class="iconfont icon-close-new"></i>
import { useStore } from 'vuex'import Massage from '@/components/library/Message.js'export default {name: 'TopHeaderCart',setup () {// 触发更新购物车商品信息的actionconst store = useStore()store.dispatch('cart/updateCart')// 删除购物车商品const deleteCart = (skuId) => {store.dispatch('cart/deleteCart', skuId).then(() => {// 删除成功Massage({ type: 'success', text: '删除购物车商品成功' })}).catch(() => {Massage({ type: 'error', text: '删除购物车商品失败' })})}return { deleteCart }}}
总结:
- 准备删除购物车商品的mutation
- 准备删除购物车的action
- 组件中触发action即可
购物车页面-基础布局
目的:完成购物车组件基础布局和路由配置与跳转链接。

大致步骤:
- 完成头部组件,购物车图标,购物车结算按钮,点击跳转购物车路由。商品点击跳转详情的操作。
- 配置购物车路由和组件,完成基础布局。
落的代码:
- 跳转功能
src/layout/views/components/top-header-cart.vue
<RouterLink to="/cart" class="curr"><i class="iconfont icon-cart"></i><em>{{$store.getters['cart/validTotal']}}</em></RouterLink>
<div class="item" v-for="item in $store.getters['cart/validList']" :key="item.skuId">+ <RouterLink :to="`/product/${item.id}`"><img :src="item.picture" alt="">
- 组件与路由
src/views/cart/index.vue
<template><div class="xtx-cart-page"><div class="container"><XtxBread><XtxBreadItem to="/">首页</XtxBreadItem><XtxBreadItem>购物车</XtxBreadItem></XtxBread><div class="cart"><table><thead><tr><th width="120"><XtxCheckbox>全选</XtxCheckbox></th><th width="400">商品信息</th><th width="220">单价</th><th width="180">数量</th><th width="180">小计</th><th width="140">操作</th></tr></thead><!-- 有效商品 --><tbody><tr v-for="i in 3" :key="i"><td><XtxCheckbox /></td><td><div class="goods"><RouterLink to="/"><img src="https://yanxuan-item.nosdn.127.net/13ab302f8f2c954d873f03be36f8fb03.png" alt=""></RouterLink><div><p class="name ellipsis">和手足干裂说拜拜 ingrams手足皲裂修复霜</p><!-- 选择规格组件 --></div></div></td><td class="tc"><p>¥200.00</p><p>比加入时降价 <span class="red">¥20.00</span></p></td><td class="tc"><XtxNumbox /></td><td class="tc"><p class="f16 red">¥200.00</p></td><td class="tc"><p><a href="javascript:;">移入收藏夹</a></p><p><a class="green" href="javascript:;">删除</a></p><p><a href="javascript:;">找相似</a></p></td></tr></tbody><!-- 无效商品 --><tbody><tr><td colspan="6"><h3 class="tit">失效商品</h3></td></tr><tr v-for="i in 3" :key="i"><td><XtxCheckbox style="color:#eee;" /></td><td><div class="goods"><RouterLink to="/"><img src="https://yanxuan-item.nosdn.127.net/13ab302f8f2c954d873f03be36f8fb03.png" alt=""></RouterLink><div><p class="name ellipsis">和手足干裂说拜拜 ingrams手足皲裂修复霜</p><p class="attr">颜色:粉色 尺寸:14cm 产地:中国</p></div></div></td><td class="tc"><p>¥200.00</p></td><td class="tc">1</td><td class="tc"><p>¥200.00</p></td><td class="tc"><p><a class="green" href="javascript:;">删除</a></p><p><a href="javascript:;">找相似</a></p></td></tr></tbody></table></div><!-- 操作栏 --><div class="action"><div class="batch"><XtxCheckbox>全选</XtxCheckbox><a href="javascript:;">删除商品</a><a href="javascript:;">移入收藏夹</a><a href="javascript:;">清空失效商品</a></div><div class="total">共 7 件商品,已选择 2 件,商品合计:<span class="red">¥400</span><XtxButton type="primary">下单结算</XtxButton></div></div><!-- 猜你喜欢 --><GoodRelevant /></div></div></template><script>import GoodRelevant from '@/views/goods/components/goods-relevant'export default {name: 'XtxCartPage',components: { GoodRelevant }}</script><style scoped lang="less">.tc {text-align: center;.xtx-numbox {margin: 0 auto;width: 120px;}}.red {color: @priceColor;}.green {color: @xtxColor}.f16 {font-size: 16px;}.goods {display: flex;align-items: center;img {width: 100px;height: 100px;}> div {width: 280px;font-size: 16px;padding-left: 10px;.attr {font-size: 14px;color: #999;}}}.action {display: flex;background: #fff;margin-top: 20px;height: 80px;align-items: center;font-size: 16px;justify-content: space-between;padding: 0 30px;.xtx-checkbox {color: #999;}.batch {a {margin-left: 20px;}}.red {font-size: 18px;margin-right: 20px;font-weight: bold;}}.tit {color: #666;font-size: 16px;font-weight: normal;line-height: 50px;}.xtx-cart-page {.cart {background: #fff;color: #666;table {border-spacing: 0;border-collapse: collapse;line-height: 24px;th,td{padding: 10px;border-bottom: 1px solid #f5f5f5;&:first-child {text-align: left;padding-left: 30px;color: #999;}}th {font-size: 16px;font-weight: normal;line-height: 50px;}}}}</style>
总结:准备购物车详情页面的路由配置和组件布局
购物车页面-列表展示-本地
目的:实现本地状态下的,购物车商品列表展示功能。
大致步骤:
- 准备失效商品列表数据。已选择商品列表数据。已选择商品件数数据。需要支付的金额数据。
- 渲染模版
落的代码:
- 准备数据
src/store/module/cart.js
// 选中商品的总价格selectedTotal (state, getters) {return getters.selectedList.reduce((total, item) => total + item.nowPrice * item.count, 0)},// 选中的商品的数量selectedCount (state, getters) {return getters.selectedList.reduce((count, item) => count + item.count, 0)},// 选中的商品列表(需要付款)selectedList (state) {return state.list.filter(item => item.selected)},// 无效的商品列表: 没有库存的;状态是失效的invalidList (state) {return state.list.filter(item => item.stock === 0 || !item.isEffective)},
- 渲染列表
<div class="cart"><table><thead><tr>+ <th width="120"><XtxCheckbox :modelValue="$store.getters['cart/isCheckAll']">全选</XtxCheckbox></th><th width="400">商品信息</th><th width="220">单价</th><th width="180">数量</th><th width="180">小计</th><th width="140">操作</th></tr></thead><!-- 有效商品 --><tbody>+ <tr v-for="item in $store.getters['cart/validList']" :key="item.skuId">+ <td><XtxCheckbox :modelValue="item.selected" /></td><td><div class="goods">+ <RouterLink :to="`/product/${item.id}`">+ <img :src="item.picture" alt=""></RouterLink><div>+ <p class="name ellipsis">{{item.name}}</p><!-- 选择规格组件 -->+ <p class="attr">{{item.attrsText}}</p></div></div></td><td class="tc">+ <p>¥{{item.nowPrice}}</p></td><td class="tc">+ <XtxNumbox :modelValue="item.count" /></td>+ <td class="tc"><p class="f16 red">¥{{item.nowPrice*100*item.count/100}}</p></td><td class="tc"><p><a href="javascript:;">移入收藏夹</a></p><p><a class="green" href="javascript:;">删除</a></p><p><a href="javascript:;">找相似</a></p></td></tr></tbody><!-- 无效商品 --><tbody v-if="$store.getters['cart/invalidList'].length>0"><tr><td colspan="6"><h3 class="tit">失效商品</h3></td></tr><tr v-for="item in $store.getters['cart/validList']" :key="item.skuId"><td><XtxCheckbox style="color:#eee;" /></td><td><div class="goods"><RouterLink :to="`/product/${item.id}`"><img :src="item.picture" alt=""></RouterLink><div><p class="name ellipsis">{{item.name}}</p><p class="attr">{{item.attrsText}}</p></div></div></td><td class="tc"><p>¥{{item.nowPrice}}</p></td><td class="tc">{{item.count}}</td><td class="tc"><p>¥{{item.nowPrice*100*item.count/100}}</p></td><td class="tc"><p><a class="green" href="javascript:;">删除</a></p><p><a href="javascript:;">找相似</a></p></td></tr></tbody></table></div><!-- 操作栏 --><div class="action"><div class="batch">+ <XtxCheckbox :modelValue="$store.getters['cart/isCheckAll']">全选</XtxCheckbox><a href="javascript:;">删除商品</a><a href="javascript:;">移入收藏夹</a><a href="javascript:;">清空失效商品</a></div><div class="total">+ 共 {{$store.getters['cart/validTotal']}} 件商品,已选择 {{$store.getters['cart/selectedTotal']}} 件,商品合计:+ <span class="red">¥{{$store.getters['cart/selectedAmount']}}</span><XtxButton type="primary">下单结算</XtxButton></div></div>
总结:购物车商品信息动态渲染
购物车页面-单选操作-本地
目的:实现本地状态下的,选中商品操作。
大致步骤:
- 使用购物车商品修改信息的mutations(已实现)
- 定义购物车商品选中状态的actions
- 在购物车页面绑定单选的复选框change事件
- 在事件中调用actions的修改函数
落的代码:
- 定义修改购物车商品选中状态的mutations
src/store/module/cart.js
// 修改购物车商品updateCart (state, goods) {// goods中字段有可能不完整,goods有的信息才去修改。// 1. goods中必需又skuId,才能找到对应的商品信息const updateGoods = state.list.find(item => item.skuId === goods.skuId)for (const key in goods) {// 布尔类型 false 值需要使用+ if (goods[key] !== null && goods[key] !== undefined && goods[key] !== '') {updateGoods[key] = goods[key]}}},
- 定义修改购物车商品的actions
src/store/module/cart.js
// 切换单个商品的选中状态toggleCartOne (context, goods) {const token = context.rootState.user.profiletokenif (token) {// 已经登录} else {// 未登录context.commit('updateCart', goods)}},
- 在购物车页面绑定单选的复选框change事件并处理选中
src/views/cart/index.vue
<td><XtxCheckbox @change="$event=>checkOne(item.skuId,$event)" :modelValue="item.selected" /></td>
import GoodRelevant from '@/views/goods/components/goods-relevant'import { useStore } from 'vuex'export default {name: 'XtxCartPage',components: { GoodRelevant },setup () {const store = useStore()// 单选const checkOne = (skuId, selected) => {// 根据SKUId的值修改单件商品的状态store.dispatch('cart/toggleCartOne', { skuId, selected })}return { checkOne }}}
总结:点击复选框,传递skuId和切换后的状态,提供给action,action触发mutation进行修改即可
购物车页面-全选操作-本地
目的:实现本地状态下的,全选商品操作。
大致步骤:
- 修改购物车所有有效商品选中状态的actions
- 在购物车页面修改调用actions的代码
- 在购物车页面绑定全选的复选框change事件
- 在事件中调用actions的修改函数
落的代码
- 修改购物车商品选中状态的actions让其支持全选
src/store/module/cart.js
// 做有效商品的全选&反选checkAllCart (ctx, selected) {return new Promise((resolve, reject) => {if (ctx.rootState.user.profile.token) {// 登录 TODO} else {// 本地// 1. 获取有效的商品列表,遍历的去调用修改mutations即可ctx.getters.validList.forEach(item => {ctx.commit('updateCart', { skuId: item.skuId, selected })})resolve()}})},
- 在购物车页面修改调用actions的代码
src/views/cart/index.vue
// 实现所有商品的选中控制const toggleAll = (selected) => {store.dispatch('cart/checkAllCart', selected)}return { toggleOne, toggleAll }
- 在购物车页面绑定全选的复选框change事件并处理选中
src/views/cart/index.vue
<!-- 两处都需要加 --><XtxCheckbox @change="toggleAll" :modelValue="$store.getters['cart/isAllCart']">全选</XtxCheckbox>
总结:
- 添加action控制全选,组件中触发action
- 复选框组件上监听点击操作,事件函数中处理选中,添加getters计算默认的全选按钮的状态
购物车页面-删除操作-本地
目的:实现本地状态下,购物车商品删除
大致步骤:
- 绑定
删除点击事件指定处理函数,调用删除actions - 处理无商品展示界面(没有商品时,给一个提示)
落的代码:
- 绑定
删除点击事件指定处理函数,调用删除actionssrc/views/cart/index.vue
<!-- 两处删除都绑定 --><p><a @click="deleteCart(item.skuId)" class="green" href="javascript:;">删除</a></p>
// 删除const deleteCart = (skuId) => {store.dispatch('cart/deleteCart', skuId)}return { checkOne, checkAll, deleteCart }
- 处理无商品展示界面
组件 src/views/cart/components/cart-none.vue
<template><div class="cart-none"><img src="@/assets/images/none.png" alt="" /><p>购物车内暂时没有商品</p><div class="btn"><XtxButton type="primary" @click="$router.push('/')">继续逛逛</XtxButton></div></div></template><script>export default {name: 'CartNone'}</script><style scoped lang="less">.cart-none {text-align: center;padding: 150px 0;background: #fff;img {width: 180px;}p {color: #999999;padding: 20px 0;}}</style>
使用 src/views/cart/index.vue
+import CartNone from './components/cart-none.vue'import { useStore } from 'vuex'export default {name: 'XtxCartPage',+ components: { GoodRelevant, CartNone },
<!-- 有效商品 --><tbody><tr v-if="$store.getters['cart/validList'].length===0"><td colspan="6"><CartNone /></td></tr>
总结:
- 实现删除单个商品的功能,同时添加没有商品的提示效果。
购物车页面-确认框组件
目的:通过vue实例调用$confirm函数弹出确认框。import导入函数使用也需要支持。

大致步骤:
- 实现组件基础结构和样式。
- 实现函数式调用组件方式和完成交互。
- 加上打开时动画效果。
- 给购物车删除加上确认框。
- 给vue挂载原型函数$confirm。
落地代码:
- 实现组件基础结构和样式。
组件 src/components/library/xtx-confirm.vue
<template><div class="xtx-confirm"><div class="wrapper"><div class="header"><h3>温馨提示</h3><a href="JavaScript:;" class="iconfont icon-close-new"></a></div><div class="body"><i class="iconfont icon-warning"></i><span>您确认从购物车删除该商品吗?</span></div><div class="footer"><XtxButton size="mini" type="gray">取消</XtxButton><XtxButton size="mini" type="primary">确认</XtxButton></div></div></div></template><script>export default {name: 'XtxConfirm'}</script><style scoped lang="less">.xtx-confirm {position: fixed;left: 0;top: 0;width: 100%;height: 100%;z-index: 8888;background: rgba(0,0,0,.5);.wrapper {width: 400px;background: #fff;border-radius: 4px;position: absolute;top: 50%;left: 50%;transform: translate(-50%,-50%);.header,.footer {height: 50px;line-height: 50px;padding: 0 20px;}.body {padding: 20px 40px;font-size: 16px;.icon-warning {color: @priceColor;margin-right: 3px;font-size: 16px;}}.footer {text-align: right;.xtx-button {margin-left: 20px;}}.header {position: relative;h3 {font-weight: normal;font-size: 18px;}a {position: absolute;right: 15px;top: 15px;font-size: 20px;width: 20px;height: 20px;line-height: 20px;text-align: center;color: #999;&:hover {color: #666;}}}}}</style>
为了看到布局在购物车页面用下
- 实现函数式调用组件方式和完成交互。

定义函数 src/components/library/Confirm.js
import { createVNode, render } from 'vue'import XtxConfirm from './xtx-confirm'// 准备divconst div = document.createElement('div')div.setAttribute('class', 'xtx-confirm-container')document.body.appendChild(div)// 该函数渲染XtxConfirm组件,标题和文本// 函数的返回值是promise对象export default ({ title, text }) => {return new Promise((resolve, reject) => {const submitCallback = () => {render(null, div)resolve()}const cancelCallback = () => {render(null, div)reject(new Error('点击取消'))}// 1. 渲染组件// 2. 点击确认按钮,触发resolve同时销毁组件// 3. 点击取消按钮,触发reject同时销毁组件const vnode = createVNode(XtxConfirm, { title, text, submitCallback, cancelCallback })render(vnode, div)})}
组件逻辑 src/components/library/xtx-confirm.vue
<template><div class="xtx-confirm" :class="{fade}"><div class="wrapper" :class="{fade}"><div class="header"><h3>{{title}}</h3><a @click="cancelCallback()" href="JavaScript:;" class="iconfont icon-close-new"></a></div><div class="body"><i class="iconfont icon-warning"></i><span>{{text}}</span></div><div class="footer"><XtxButton @click="cancelCallback()" size="mini" type="gray">取消</XtxButton><XtxButton @click="submitCallback()" size="mini" type="primary">确认</XtxButton></div></div></div></template><script>// 当前组件不是在APP下进行渲染,无法使用APP下的环境(全局组件,全局指令,原型属性函数)import XtxButton from '@/components/library/xtx-button'import { onMounted, ref } from 'vue'export default {name: 'XtxConfirm',components: { XtxButton },props: {title: {type: String,default: '温馨提示'},text: {type: String,default: ''},submitCallback: {type: Function},cancelCallback: {type: Function}},setup () {const fade = ref(false)onMounted(() => {// 当元素渲染完毕立即过渡的动画不会触发setTimeout(() => {fade.value = true}, 0)})return { fade }}}</script><style scoped lang="less">.xtx-confirm {position: fixed;left: 0;top: 0;width: 100%;height: 100%;z-index: 8888;background: rgba(0,0,0,0);&.fade {transition: all 0.4s;background: rgba(0,0,0,.5);}.wrapper {width: 400px;background: #fff;border-radius: 4px;position: absolute;top: 50%;left: 50%;transform: translate(-50%,-60%);opacity: 0;&.fade {transition: all 0.4s;transform: translate(-50%,-50%);opacity: 1;}.header,.footer {height: 50px;line-height: 50px;padding: 0 20px;}.body {padding: 20px 40px;font-size: 16px;.icon-warning {color: @priceColor;margin-right: 3px;font-size: 16px;}}.footer {text-align: right;.xtx-button {margin-left: 20px;}}.header {position: relative;h3 {font-weight: normal;font-size: 18px;}a {position: absolute;right: 15px;top: 15px;font-size: 20px;width: 20px;height: 20px;line-height: 20px;text-align: center;color: #999;&:hover {color: #666;}}}}}</style>
总结:
- 封装确认框组件结构
- 基于组件结构分支渲染函数Confirm,支持Promise的API
- 完善确认框组件的数据绑定
注意:全局组件中使用的全局特性(全局组件、全局指令等),需要单独导入,不可以直接使用
- 使用函数
src/views/cart/index.vue
// 删除const deleteCart = (skuId) => {// store.dispatch('cart/deleteCart', skuId)Confirm({ text: '您确定从购物车删除该商品吗?' }).then(() => {console.log('点击确认')}).catch(e => {console.log('点击取消')})}return { checkOne, checkAll, deleteCart }
- 给购物车删除加上确认框
src/views/cart/index.vue
const deleteCart = (item) => {Confirm(app, { text: ' 您确认从购物车删除该商品吗?' }).then(() => {// console.log('点击确认')store.dispatch('cart/deleteCart', item.skuId)}).catch(e => {// console.log('点击取消')})}
- 给vue挂载原型函数
实现:src/components/library/index.js
import Confirm from './Confirm'
// 如果你想挂载全局的属性,能够通过组件实例调用的属性 this.$messageapp.config.globalProperties.$message = Message+ app.config.globalProperties.$confirm = Confirm
测试:
mounted () {this.$confirm({ text: 'xxx' })},
总结:
- 导入方法Confirm并调用,通过then或者catch获取操作结果
- 通过this.$confirm方法调用方法
购物车页面-批量删除-本地
目的:实现本地批量删除选中商品功能。
大致的步骤:
- 定义一个批量删除商品的actions支持批量操作
- 遍历选中商品,调用单个删除调用mutations函数即可
- 绑定批量删除点击事件指定处理函数,调用actions进行删除。
落地代码:
- 批量操作商品的actions支持
src/store/module/cart.js
// 批量删除选中的商品batchDeleteCart (context) {const token = context.rootState.user.profiletokenif (token) {// 已经登录} else {// 未登录// 遍历所有有效的选中的商品列表,分别进行删除context.getters.selectedList.forEach(item => {context.commit('deleteCart', item.skuId)})}},
- 绑定批量删除点击事件指定处理函数,调用actions进行删除。
src/views/cart/index.vue
<a @click="batchDeleteCart()" href="javascript:;">删除商品</a>
// 批量删除const batchDeleteCart = () => {Confirm({ text: '您确定从购物车删除选中的商品吗?' }).then(() => {store.dispatch('cart/batchDeleteCart')}).catch(e => {})}return { checkOne, checkAll, deleteCart, batchDeleteCart }
总结:点击删除按钮,触发action,批量删除选中的商品
购物车页面-无效商品-本地
目的:实现本地清空无效商品功能。
大致思路:
去修改批量删除的actions让它适用于两个场景
- 批量删除选中的
- 批量删除失效的 isClear
落地代码:绑定清空无效商品点击事件指定处理函数,调用actions进行删除。
src/views/cart/index.vue
<a href="javascript:;" @click='batchDeleteCart(false)'>删除商品</a><a href="javascript:;" @click="batchDeleteCart(true)" >清空失效商品</a>
// 批量删除+ const batchDeleteCart = (isClear) => {+ Confirm({ text: `您确定从购物车删除${isClear ? '失效' : '选中'}的商品吗?` }).then(() => {store.dispatch('cart/batchDeleteCart', isClear)}).catch(e => {})}return { checkOne, checkAll, deleteCart, batchDeleteSelectedCart, batchDeleteInvalidCart }
- 批量删除商品的actions支持清空无效
src/store/module/cart.js
// 批量删除选中商品+ batchDeleteCart (ctx, isClear) {return new Promise((resolve, reject) => {if (ctx.rootState.user.profile.token) {// 登录 TODO} else {// 本地// 1. 获取选中商品列表,进行遍历调用deleteCart mutataions函数+ ctx.getters[isClear ? 'invalidList' : 'selectedList'].forEach(item => {ctx.commit('deleteCart', item.skuId)})resolve()}})},
总结:基于标志位重构删除的逻辑代码
购物车页面-修改数量-本地
目的:实现本地版本的修改商品数量。
大致的步骤:
- 绑定xtx-numbox组件的change事件指定处理函数
- 在函数种调用vuex的cart/updateCart函数修改数量
落的代码:
- 绑定xtx-numbox组件的change事件指定处理函数
<XtxNumbox @change='changeCount(item.skuId, $event)' :modelValue="item.count" :max='item.stock' />
- 在函数种调用vuex的cart/updateCart函数修改数量
// 修改数量const changeCount = (skuId, count) => {store.dispatch('cart/updateCart', { skuId, count })}return { checkOne, checkAll, deleteCart, batchDeleteCart, changeCount }
- xtx-numbox组件内部需要触发 change事件
// 通过change事件把计算的值传递回父组件emit('change', num)
总结:基于xtx-numbox组件获取变更的商品数量,然后vuex状态的值
购物车页面-修改规格-本地
目的:封装一个购物车SKU组件,来修改规格。

大致步骤:
- 定义一个组件完成基础结构
- 完成展开收起操作
- 展开的时候根据skuId得到商品信息(specs,skus)渲染商品规格。
- 选择完毕后,点击确认后,修改当前商品规格。
落的代码:
- 1.定义一个组件完成基础结构
定义组件 src/views/cart/components/cart-sku.vue
<template><div class="cart-sku"><div class="attrs"><span class="ellipsis">颜色:粉色 尺寸:14cm 产地:中国</span><i class="iconfont icon-angle-down"></i></div><div class="layer"><div class="loading"></div></div></div></template><script>export default {name: 'CartSku'}</script><style scoped lang="less">.cart-sku {height: 28px;border: 1px solid #f5f5f5;padding: 0 6px;position: relative;margin-top: 10px;display:inline-block;.attrs {line-height: 24px;display: flex;span {max-width: 230px;font-size: 14px;color: #999;}i {margin-left: 5px;font-size: 14px;}}.layer {position: absolute;left: -1px;top: 40px;z-index: 10;width: 400px;border: 1px solid @xtxColor;box-shadow: 2px 2px 4px lighten(@xtxColor,50%);background: #fff;border-radius: 4px;font-size: 14px;padding: 20px;&::before {content: "";width: 12px;height: 12px;border-left: 1px solid @xtxColor;border-top: 1px solid @xtxColor;position: absolute;left: 12px;top: -8px;background: #fff;transform: scale(.8,1) rotate(45deg);}.loading {height: 224px;background: url(../../../assets/images/loading.gif) no-repeat center;}}}</style>
使用组件 src/views/cart/index.vue
+import CartSku from './components/cart-sku'export default {name: 'XtxCartPage',+ components: { GoodRelevant, CartNone, CartSku },
<div><p class="name ellipsis">{{item.name}}</p><!-- 选择规格组件 -->+ <CartSku :attrsText='item.attrsText' /></div>
- 2.完成展开收起操作
src/views/cart/components/cart-sku.vue
<div class="cart-sku" ref="target"><div class="attrs" @click="toggle()"><span class="ellipsis">{{attrsText}}</span>
<script>import { ref } from 'vue'import { getSpecsAndSkus } from '@/api/product.js'import { onClickOutside } from '@vueuse/core'import GoodsSku from '@/views/goods/components/goods-sku.vue'export default {name: 'CartSku',components: { GoodsSku },props: {attrsText: {type: String,default: ''},skuId: {type: String,required: true}},setup (props) {// 根据SKUId获取的商品规格数据const goods = ref(null)const visible = ref(false)const target = ref(null)onClickOutside(target, () => {// 点击target指定的DOM之外的区域,触发回调函数:隐藏弹窗visible.value = falsegoods.value = null})// 控制显示和隐藏弹窗const toggle = () => {visible.value = !visible.valueif (!visible.value) {// 关闭弹窗,重置goods数据goods.value = null}getSpecsAndSkus(props.skuId).then(ret => {goods.value = ret.result})}return { visible, toggle, target, goods }}}</script>
- 3.展开的时候根据skuId得到商品信息(specs,skus)渲染商品规格。
接口API src/api/product.js
// 获取商品的specs和skusexport const getSpecsAndSkus = (skuId) => {return request({method: 'get',url: `/goods/sku/${skuId}`})}
使用组件传人skuId src/views/cart/index.vue
<div><p class="name ellipsis">{{item.name}}</p><!-- 选择规格组件 -->+ <CartSku attrs-text="item.attrsText" :skuId="item.skuId" /></div>
skuId: {type: String,default: ''}
- 请求数据渲染
src/views/cart/components/cart-sku.vue
<div class="layer" v-if="visible"><div v-if="!goods" class="loading"></div><GoodsSku v-if="goods" :skuId="skuId" :skus="goods.skus" :specs='goods.specs' /><XtxButton v-if="goods" type="primary" size="mini" style="margin-left:60px">确认</XtxButton></div>
+ const goods = ref(null)const show = () => {visible.value = true+ // 获取当前spec和sku数据+ getSpecsAndSkus(props.skuId).then(data => {+ goods.value = data.result+ })}const hide = () => {visible.value = false+ goods.value = null}
- 4.选择完毕后,点击确认后,修改当前商品规格: cart-sku.vue组件确认后传出sku信息
// 选择SKU时候触发const currSku = ref(null)const changeSku = (sku) => {currSku.value = sku}// 更新sku信息:点击确定之后的动作const submit = () => {// 判断当前选中的sku信息是否完整if (currentSku.value && currentSku.value.skuId && currentSku.value.skuId !== props.skuId) {// 直接触发action,更新sku即可store.dispatch('cart/updateCartSku', { skuId: props.skuId, sku: currentSku.value })// 关闭弹窗visible.value = false}}return { visible, toggle, target, goods, changeSku, submit }
<XtxButton v-if="goods" size="mini" type="primary" @click="submit()">确认</XtxButton>
- 再在actions种实现逻辑
src/store/modules/cart.js
// 根据skuId更新商品的规格参数updateCartSku (context, goods) {// 1、根据skuId查询之前的商品数据const oldGoods = context.state.list.find(item => item.skuId === goods.skuId)// 2、删除之前的商品context.commit('deleteCart', goods.skuId)// 3、准备要更新数据const { skuId, price: nowPrice, inventory: stock, specsText: attrsText } = goods.sku// 用新的数据覆盖旧的数据const newGoods = { ...oldGoods, skuId, nowPrice, stock, attrsText }// 4、加入准备要更新的商品数据context.commit('insertCart', newGoods)},
总结:
- 修改规格后,记录修改后的sku信息
- 点击确定后,触发action更新商品的规格数据
登录后-合并购物车
目的:登录后需要把把本地购物车合并,且清空本地购物车。

大致步骤:
- 编写合并购物车的API接口函数
- 编写设置购物车数据的mutations目的是清空购物车
- 编写合并购物车的actions函数,实现合并后清空本地
- 在登录完成后调用合并购物车函数
落地代码:
- 编写合并购物车的API接口函数
src/api/cart.js
/*** 合并本地购物车* @param {Array<object>} cartList - 本地购物车数组* @param {String} item.skuId - 商品SKUID* @param {Boolean} item.selected - 是否选中* @param {Integer} item.count - 数量*/export const mergeLocalCart = (cartList) => {return request({method: 'post',url: '/member/cart/merge',data: cartList})}
- 编写设置购物车数据的mutations目的是清空购物车
src/store/module/cart.js
// 设置购物车列表setCartList (state, list) {state.list = list}
- 编写合并购物车的actions函数,实现合并后清空本地
src/store/module/cart.js
// 购物车状态import { mergeCart } from '@/api/cart'
// 合并本地购物车async mergeLocalCart (ctx) {// 存储token后调用合并API接口函数进行购物合并const cartList = ctx.getters.validList.map(({ skuId, selected, count }) => {return { skuId, selected, count }})await mergeLocalCart(cartList)// 合并成功将本地购物车删除ctx.commit('setCartList', [])},
- 在登录完成(绑定成功,完善信息成功)后调用合并购物车函数
login/components/login-form.vue
// 合并购物车操作store.dispatch('cart/mergeLocalCart').then(() => {// 2. 提示Message({ type: 'success', text: '登录成功' })// 3. 跳转router.push('/')})
- 绑定手机号成功后
login/components/login-bind.vue
// 合并购物车操作store.dispatch('cart/mergeLocalCart').then(() => {// 2. 提示Message({ type: 'success', text: '绑定成功' })// 3. 跳转router.push('/')})
- 完善信息后
login/components/login-patch.vue
// 合并购物车操作store.dispatch('cart/mergeLocalCart').then(() => {// 2. 提示Message({ type: 'success', text: '完善信息成功' })// 3. 跳转router.push( '/')})
- 登录成功后
login/callback.vue
store.dispatch('cart/mergeLocalCart').then(() => {// 2. 跳转到来源页或者首页router.push('/')// 3. 成功提示Message({ type: 'success', text: 'QQ登录成功' })})
登录后-商品列表
目标:实现登陆后获取购物车商品列表。
大致步骤:
- 编写获取商品列表的API接口函数
- 在actions原有预留TODO位置获取列表
- 退出登录需要清空购物车
落地代码:
- 编写获取商品列表的API接口函数
src/api/cart.js
/*** 获取登录后的购物车列表* @returns Promise*/export const findCartList = () => {return request({method: 'get',url: '/member/cart'})}
- 在actions原有预留TODO位置获取列表
src/store/module/cart.js
// 获取购物车列表findCartList (ctx) {return new Promise((resolve, reject) => {if (ctx.rootState.user.profile.token) {+ // 登录 TODO+ findCartList().then(data => {+ ctx.commit('setCartList', data.result)+ resolve()+ })}
- 退出登录需要清空购物车
src/components/app-navbar.vue
// 退出登录// 1. 清空本地存储信息和vuex的用户信息// 2. 跳转登录const router = useRouter()const logout = () => {store.commit('user/setUser', {})// 清空购物车+ store.commit('cart/setCartList', [])router.push('/login')}
登录后-加入购物车
目标:实现登陆后加入购物车。
大致步骤:
- 编写加入购物车的API接口函数
- 在actions原有预留TODO位置加入购物车
落地代码:
- 编写加入购物车的API接口函数
src/api/cart.js
/*** 加入购物车* @param {String} skuId - 商品SKUID* @param {Integer} count - 商品数量* @returns Promise*/export const insertCart = ({ skuId, count }) => {return request({method: 'post',url: '/member/cart',data: { skuId, count }})}
- 在actions原有预留TODO位置加入购物车
src/store/module/cart.js
// 加入购物车insertCart (ctx, goods) {// ctx.state 当前模块状态 ctx.rootState 根状态对象return new Promise((resolve, reject) => {if (ctx.rootState.user.profile.token) {+ // 登录 TODO+ insertCart(goods).then(() => {+ return findCartList()+ }).then((data) => {+ ctx.commit('setCartList', data.result)+ resolve()+ })}
登录后-删除操作
目标:实现登陆后删除购物车商品操作(批量删除,清空无效)
大致步骤:
- 编写删除购物车商品的API接口函数
- 在actions原有预留TODO位置删除购物车商品
落地代码:
- 编写删除购物车商品的API接口函数
src/api/cart.js
/*** 删除商品(支持批量删除)* @param {Array<string>} ids - skuId集合* @returns Promise*/export const deleteCart = (ids) => {return request({method: 'delete',url: '/member/cart',data: {ids}})}
- 在actions原有预留TODO位置删除购物车商品
src/store/module/cart.js
// 删除购物车商品deleteCart (ctx, skuId) {return new Promise((resolve, reject) => {if (ctx.rootState.user.profile.token) {+ // 登录 TODO+ deleteCart([skuId]).then(() => {+ return findCartList()+ }).then((data) => {+ ctx.commit('setCartList', data.result)+ resolve()+ })}
登录后-批量删除
目标:完成批量删除选中商品,完成清空失效的商品
大概步骤:
- 完成cart.js模块中的批量删除actions的登录状态下逻辑
落的代码:
// 批量删除选中商品batchDeleteCart (ctx, isClear) {return new Promise((resolve, reject) => {if (ctx.rootState.user.profile.token) {+ // 登录 TODO+ // 得到需要删除的商品列表(失效,选中)的skuId集合+ const ids = ctx.getters[isClear ? 'invalidList' : 'selectedList'].map(item => item.skuId)+ deleteCart(ids).then(() => {+ return findCartList()+ }).then((data) => {+ ctx.commit('setCartList', data.result)+ resolve()+ })} else {
登录后-选中状态&修改数量
目的:实现登录后的选中操作。
大致步骤:
- 编写修改购物车商品的API接口函数
- 在actions原有预留TODO位置修改购物车商品
落地代码:
- 编写修改购物车商品的API接口函数
src/api/cart.js
/*** 修改购物车商品的状态和数量* @param {String} goods.skuId - 商品sku* @param {Boolean} goods.selected - 选中状态* @param {Integer} goods.count - 商品数量* @returns Promise*/export const updateCart = (goods) => {return request({method: 'put',url: '/member/cart/' + goods.skuId,data: goods})}
- 在actions原有预留TODO位置修改购物车商品
src/store/module/cart.js
// 修改购物车商品updateCart (ctx, goods) {// goods 中:必须有skuId,其他想修改的属性 selected countreturn new Promise((resolve, reject) => {if (ctx.rootState.user.profile.token) {+ // 登录 TODO+ updateCart(goods).then(() => {+ return findCartList()+ }).then((data) => {+ ctx.commit('setCartList', data.result)+ resolve()+ })} else {
登录后-全选反选
目标:完成有效商品的全选与反选功能
大概步骤:
- 准备全选与反选的API接口函数
- 去完善actions,全选与反选的中的 登录 TODO 的地方
落的代码:
src/api/cart.js
/*** 全选反选* @param {Boolean} selected - 选中状态* @param {Array<string>} ids - 有效商品skuId集合* @returns Promise*/export const checkAllCart = ({ selected, ids }) => {return request({method: 'put',url: '/member/cart/selected',data: { selected, ids }})}
src/store/modules/cart.js
// 做有效商品的全选&反选checkAllCart (ctx, selected) {return new Promise((resolve, reject) => {if (ctx.rootState.user.profile.token) {+ // 登录 TODO+ const ids = ctx.getters.validList.map(item => item.skuId)+ checkAllCart({ selected, ids }).then(() => {+ return findCartList()+ }).then((data) => {+ ctx.commit('setCartList', data.result)+ resolve()+ })} else {
登录后-修改规格
目的:实现登录后的修改规格操作。
大致步骤:
- 由于没有修改接口的接口。通过删除旧商品,插入新商品,完成修改规格。
- 去完善actions,修改规格的 登录 TODO 的地方
落地代码:
- 在actions原有预留TODO位置修改购物车商品规格
src/store/module/cart.js
// 修改sku规格函数updateCartSku (ctx, { oldSkuId, newSku }) {return new Promise((resolve, reject) => {if (ctx.rootState.user.profile.token) {+ // 登录 TODO+ // 1. 获取原先商品的数量+ // 2. 删除原先商品+ // 3. 获取修改的skuId 和 原先商品数量 做一个加入购物车操作+ // 4. 更新列表+ const oldGoods = ctx.state.list.find(item => item.skuId === oldSkuId)+ deleteCart([oldSkuId]).then(() => {+ return insertCart({ skuId: newSku.skuId, count: oldGoods.count })+ }).then(() => {+ return findCartList()+ }).then((data) => {+ ctx.commit('setCartList', data.result)+ resolve()+ })}
下单结算
目的:去结算,未登录给确认框提示。
大致需求:
- 绑定下单结算按钮指定处理函数
- 函数中:
- 判断是否选中有效商品。
- 判断是否登录,给确认框提示,点击确认
- 满足以上条件去填写订单(结算)页面。
- member/xxx 的域名需要登录,所以做路由拦截。
落的代码:
- 下单结束点击后逻辑
src/views/cart/index.vue
import Message from '@/components/library/Message'
// 跳转结算页面const router = useRouter()const goCheckout = () => {// 1. 判断是否选择有效商品// 2. 判断是否已经登录,未登录 弹窗提示// 3. 进行跳转 (需要做访问权限控制)if (store.getters['cart/selectedTotal'] === 0) return Message({ text: '至少选中一件商品才能结算' })if (!store.state.user.profile.token) {Confirm({ text: '下单结算需要登录,您是否去登录?' }).then(() => {// 点击确认router.push('/member/checkout')}).catch(e => {})} else {router.push('/member/checkout')}}return { checkOne, checkAll, deleteCart, batchDeleteCart, changeCount, updateCartSku, goCheckout }
<XtxButton type="primary" @click="goCheckout()">下单结算</XtxButton>
- 路由拦截
src/router/index.js
import store from '@/store'
// 前置导航守卫router.beforeEach((to, from, next) => {// 用户信息const { token } = store.state.user.profile// 跳转去member开头的地址却没有登录if (to.path.startsWith('/member') && !token) {return next({ path: '/login', query: { redirectUrl: to.fullPath } })}next()})
