【vue3.0】14.0 某东到家(十四)——购物车
目前页面如下:
新建组件src\views\shop\Cart.vue
:
<template>
<div class="cart">
<div class="check">
<div class="check__icon">
<img src="/i18n/9_16/img/basket.png" alt="" class="check__icon__img" />
<div class="check__icon__tag">1</div>
</div>
<div class="check__info">
总计:<span class="check__info__price">¥128</span>
</div>
<div class="check__btn">去结算</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'OrderConfirmation',
setup() {
const a = ref(0)
return { a }
}
}
</script>
<style lang="scss" scoped>
.cart {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 0.5rem;
}
.check {
}
</style>
进一步完善页面:
<template>
<div class="cart">
<div class="check">
<div class="check__icon">
<img src="/i18n/9_16/img/basket.png" alt="" class="check__icon__img" />
<div class="check__icon__tag">1</div>
</div>
<div class="check__info">
总计:<span class="check__info__price">¥128</span>
</div>
<div class="check__btn">去结算</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'OrderConfirmation',
setup() {
const a = ref(0)
return { a }
}
}
</script>
<style lang="scss" scoped>
.cart {
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
.check {
display: flex;
box-sizing: border-box; //往内塞入border
line-height: 0.49rem;
height: 0.49rem;
border-top: 0.01rem solid #f1f1f1;
&__icon {
width: 0.84rem;
position: relative;
&__img {
margin: 0.12rem auto;
display: block;
width: 0.28rem;
height: 0.28rem;
}
&__tag {
// 乘以2然后等比例缩小
position: absolute;
right: 0.2rem;
top: 0.04rem;
width: 0.2rem;
height: 0.2rem;
line-height: 0.2rem;
text-align: center;
background-color: #e93b3b;
border-radius: 50%;
font-size: 0.12rem;
color: #fff;
}
}
&__info {
flex: 1;
color: #333;
font-size: 0.12rem;
&__price {
line-height: 0.49rem;
color: #e93b3b;
font-size: 0.18rem;
}
}
&__btn {
width: 0.98rem;
background-color: #4fb0f9;
text-align: center;
color: #fff;
font-size: 0.14rem;
}
}
</style>
进一步优化style
......
<style lang="scss" scoped>
@import '@/style/viriables.scss';
.cart {
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
.check {
display: flex;
box-sizing: border-box; //往内塞入border
line-height: 0.49rem;
height: 0.49rem;
border-top: 0.01rem solid $content-bg-color;
&__icon {
width: 0.84rem;
position: relative;
&__img {
margin: 0.12rem auto;
display: block;
width: 0.28rem;
height: 0.28rem;
}
&__tag {
// 乘以2然后等比例缩小
position: absolute;
right: 0.2rem;
top: 0.04rem;
width: 0.2rem;
height: 0.2rem;
line-height: 0.2rem;
text-align: center;
background-color: $height-light-font-color;
border-radius: 50%;
font-size: 0.12rem;
color: $bg-color;
}
}
&__info {
flex: 1;
color: $content-font-color;
font-size: 0.12rem;
&__price {
line-height: 0.49rem;
color: $height-light-font-color;
font-size: 0.18rem;
}
}
&__btn {
width: 0.98rem;
background-color: #4fb0f9;
text-align: center;
color: $bg-color;
font-size: 0.14rem;
}
}
</style>
加入购物车的数据存储
首先需要根据商铺的不同存储不同的购物车信息。
这里我们需要运用vuex的store。
这里预设src\store\index.js
中将要存储的数据结构如下:
import { createStore } from 'vuex'
export default createStore({
state: {
cartList: {
// 第一层级:商铺的id
// 第二层内容是商品内容以及购物数量
// shopId: {
// productID: {
// _id: '1',
// name: '番茄250g/份',
// imgUrl: '/i18n/9_16/img/tomato.png',
// sales: 10,
// price: 33.6,
// oldPrice: 39.6,
// count: 0
// }
// }
}
},
mutations: {},
actions: {},
modules: {}
})
调整优化src\views\shop\Content.vue
关于加入购物车的逻辑
<template>
<div class="content">
......
<div class="product">
<div class="product__item" v-for="item in list" :key="item._id">
<img class="product__item__img" :src="item.imgUrl" />
......
<div class="product__number">
<span class="product__number__minus">-</span>
{{ cartList?.[shopId]?.[item._id]?.count || 0 }}
<span class="product__number__plus">+</span>
</div>
</div>
</div>
</div>
</template>
<script>
import { reactive, ref, toRefs, watchEffect } from 'vue'
import { useRoute } from 'vue-router' // 路由跳转方法
import { useStore } from 'vuex' // 路由跳转方法
import { get } from '@/utils/request.js'
const categories = [
{
name: '全部商品',
tab: 'all'
},
{
name: '秒杀',
tab: 'seckill'
},
{
name: '新鲜水果',
tab: 'fruit'
},
{
name: '休闲食品',
tab: 'snack'
}
]
// 和tab切换相关的逻辑
const useTabEffect = () => {
const currentTab = ref(categories[0].tab)
const handleTabClick = tab => {
console.log('click:' + tab)
currentTab.value = tab
}
return { currentTab, handleTabClick }
}
// 当前列表内容相关的函数
const useContentListEffect = (currentTab, shopId) => {
const content = reactive({ list: [] })
const getContentData = async () => {
const result = await get(`/api/shop/${shopId}/products`, {
tab: currentTab.value
})
console.log('result:' + result)
if (result?.code === 200 && result?.data?.length) {
content.list = result.data
}
}
// watchEffect:当首次页面加载时,或当其中监听的数据发生变化时执行
watchEffect(() => {
getContentData()
})
const { list } = toRefs(content)
return { list }
}
// 添加到购物车功能
const useCartEffect = () => {
const store = useStore()
const { cartList } = toRefs(store.state)
return { cartList }
}
export default {
name: 'Content',
props: {
id: String
},
setup() {
const route = useRoute() // 获取路由
const shopId = route.params.id
const { currentTab, handleTabClick } = useTabEffect()
const { list } = useContentListEffect(currentTab, shopId)
const { cartList } = useCartEffect()
return { list, categories, handleTabClick, currentTab, cartList, shopId }
}
}
</script>
<style lang="scss" scoped>
......
</style>
进一步优化:
src\store\index.js
import { createStore } from 'vuex'
export default createStore({
state: {
cartList: {
// 第一层级:商铺的id
// 第二层内容是商品内容以及购物数量
// shopId: {
// productID: {
// _id: '1',
// name: '番茄250g/份',
// imgUrl: '/i18n/9_16/img/tomato.png',
// sales: 10,
// price: 33.6,
// oldPrice: 39.6,
// count: 0
// }
// }
}
},
mutations: {
changeItemToCart(state, payload) {
const { shopId, productId, productInfo, num } = payload
console.log(shopId, productId, productInfo)
let shopInfo = state.cartList[shopId]
if (!shopInfo) {
shopInfo = {}
}
let product = shopInfo[productId]
if (!product) {
product = productInfo // 初始化
product.count = 0
}
product.count += num
if (product.count <= 0) {
shopInfo[productId].count = 0
}
shopInfo[productId] = product
// 赋值
state.cartList[shopId] = shopInfo
}
},
actions: {},
modules: {}
})
src\views\shop\Content.vue
代码优化如下,利用vuex实现购物车增减功能:
<template>
<div class="content">
<div class="category">
<div
:class="{
category__item: true,
'category__item--active': currentTab === item.tab
}"
v-for="item in categories"
:key="item.tab"
@click="handleTabClick(item.tab)"
>
{{ item.name }}
</div>
</div>
<div class="product">
<div class="product__item" v-for="item in list" :key="item._id">
<img class="product__item__img" :src="item.imgUrl" />
<div class="product__item__detail">
<h4 class="product__item__title">{{ item.name }}</h4>
<p class="product__item__sales">月售{{ item.sales }}件</p>
<p class="product__item__price">
<span class="product__item__yen"> ¥{{ item.price }} </span>
<span class="product__item__origin">
¥{{ item.oldPrice }}
</span>
</p>
</div>
<div class="product__number">
<span
class="product__number__minus"
@click="
() => {
changeItemToCart(shopId, item._id, item, -1)
}
"
>-</span
>
{{ cartList?.[shopId]?.[item._id]?.count || 0 }}
<span
class="product__number__plus"
@click="
() => {
changeItemToCart(shopId, item._id, item, 1)
}
"
>+</span
>
</div>
</div>
</div>
</div>
</template>
<script>
import { reactive, ref, toRefs, watchEffect } from 'vue'
import { useRoute } from 'vue-router' // 路由跳转方法
import { useStore } from 'vuex' // 路由跳转方法
import { get } from '@/utils/request.js'
const categories = [
{
name: '全部商品',
tab: 'all'
},
{
name: '秒杀',
tab: 'seckill'
},
{
name: '新鲜水果',
tab: 'fruit'
},
{
name: '休闲食品',
tab: 'snack'
}
]
// 和tab切换相关的逻辑
const useTabEffect = () => {
const currentTab = ref(categories[0].tab)
const handleTabClick = tab => {
console.log('click:' + tab)
currentTab.value = tab
}
return { currentTab, handleTabClick }
}
// 当前列表内容相关的函数
const useContentListEffect = (currentTab, shopId) => {
const content = reactive({ list: [] })
const getContentData = async () => {
const result = await get(`/api/shop/${shopId}/products`, {
tab: currentTab.value
})
console.log('result:' + result)
if (result?.code === 200 && result?.data?.length) {
content.list = result.data
}
}
// watchEffect:当首次页面加载时,或当其中监听的数据发生变化时执行
watchEffect(() => {
getContentData()
})
const { list } = toRefs(content)
return { list }
}
// 添加、减少到购物车功能
const useCartEffect = () => {
const store = useStore()
const { cartList } = toRefs(store.state)
debugger
const changeItemToCart = (shopId, productId, productInfo, num) => {
console.log(
'changeItemToCart:',
'shopId:' + shopId,
'productId:' + productId,
'productInfo:' + JSON.stringify(productInfo),
'num:' + num
)
store.commit('changeItemToCart', { shopId, productId, productInfo, num })
}
return { cartList, changeItemToCart }
}
export default {
name: 'Content',
props: {
id: String
},
setup() {
const route = useRoute() // 获取路由
const shopId = route.params.id
const { currentTab, handleTabClick } = useTabEffect()
const { list } = useContentListEffect(currentTab, shopId)
const { cartList, changeItemToCart } = useCartEffect()
return {
list,
categories,
handleTabClick,
currentTab,
cartList,
shopId,
changeItemToCart
}
}
}
</script>
<style lang="scss" scoped>
......
</style>
至此,src\views\shop\Content.vue
完整代码如下:
<template>
<div class="content">
<div class="category">
<div
:class="{
category__item: true,
'category__item--active': currentTab === item.tab
}"
v-for="item in categories"
:key="item.tab"
@click="handleTabClick(item.tab)"
>
{{ item.name }}
</div>
</div>
<div class="product">
<div class="product__item" v-for="item in list" :key="item._id">
<img class="product__item__img" :src="item.imgUrl" />
<div class="product__item__detail">
<h4 class="product__item__title">{{ item.name }}</h4>
<p class="product__item__sales">月售{{ item.sales }}件</p>
<p class="product__item__price">
<span class="product__item__yen"> ¥{{ item.price }} </span>
<span class="product__item__origin">
¥{{ item.oldPrice }}
</span>
</p>
</div>
<div class="product__number">
<span
class="product__number__minus"
@click="
() => {
changeItemToCart(shopId, item._id, item, -1)
}
"
>-</span
>
{{ cartList?.[shopId]?.[item._id]?.count || 0 }}
<span
class="product__number__plus"
@click="
() => {
changeItemToCart(shopId, item._id, item, 1)
}
"
>+</span
>
</div>
</div>
</div>
</div>
</template>
<script>
import { reactive, ref, toRefs, watchEffect } from 'vue'
import { useRoute } from 'vue-router' // 路由跳转方法
import { useStore } from 'vuex' // 路由跳转方法
import { get } from '@/utils/request.js'
const categories = [
{
name: '全部商品',
tab: 'all'
},
{
name: '秒杀',
tab: 'seckill'
},
{
name: '新鲜水果',
tab: 'fruit'
},
{
name: '休闲食品',
tab: 'snack'
}
]
// 和tab切换相关的逻辑
const useTabEffect = () => {
const currentTab = ref(categories[0].tab)
const handleTabClick = tab => {
console.log('click:' + tab)
currentTab.value = tab
}
return { currentTab, handleTabClick }
}
// 当前列表内容相关的函数
const useContentListEffect = (currentTab, shopId) => {
const content = reactive({ list: [] })
const getContentData = async () => {
const result = await get(`/api/shop/${shopId}/products`, {
tab: currentTab.value
})
console.log('result:' + result)
if (result?.code === 200 && result?.data?.length) {
content.list = result.data
}
}
// watchEffect:当首次页面加载时,或当其中监听的数据发生变化时执行
watchEffect(() => {
getContentData()
})
const { list } = toRefs(content)
return { list }
}
// 添加、减少到购物车功能
const useCartEffect = () => {
const store = useStore()
const { cartList } = toRefs(store.state)
debugger
const changeItemToCart = (shopId, productId, productInfo, num) => {
console.log(
'changeItemToCart:',
'shopId:' + shopId,
'productId:' + productId,
'productInfo:' + JSON.stringify(productInfo),
'num:' + num
)
store.commit('changeItemToCart', { shopId, productId, productInfo, num })
}
return { cartList, changeItemToCart }
}
export default {
name: 'Content',
props: {
id: String
},
setup() {
const route = useRoute() // 获取路由
const shopId = route.params.id
const { currentTab, handleTabClick } = useTabEffect()
const { list } = useContentListEffect(currentTab, shopId)
const { cartList, changeItemToCart } = useCartEffect()
return {
list,
categories,
handleTabClick,
currentTab,
cartList,
shopId,
changeItemToCart
}
}
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables.scss';
@import '@/style/mixins.scss';
.content {
display: flex;
position: absolute;
left: 0;
right: 0;
top: 1.6rem;
bottom: 0.5rem;
}
.category {
overflow-y: scroll;
width: 0.76rem;
background: $search-bg-color;
height: 100%;
&__item {
line-height: 0.4rem;
text-align: center;
font-size: 14px;
color: $content-font-color;
&--active {
background: $bg-color;
}
}
}
.product {
overflow-y: scroll;
flex: 1;
&__item {
position: relative;
display: flex;
padding: 0.12rem 0.16rem;
margin: 0 0.16rem;
border-bottom: 0.01rem solid $content-bg-color;
// 配合解决超出长度以省略号显示而不会出现换行
&__detail {
overflow: hidden;
}
&__img {
width: 0.68rem;
height: 0.68rem;
margin-right: 0.16rem;
}
&__title {
margin: 0;
line-height: 0.2rem;
font-size: 0.14rem;
color: $content-font-color;
// 超出长度以省略号显示而不会出现换行
@include ellipsis;
}
&__sales {
margin: 0.06rem 0;
line-height: 0.16rem;
font-size: 0.12rem;
color: $content-font-color;
}
&__price {
margin: 0;
line-height: 0.2rem;
font-size: 0.14rem;
color: $height-light-font-color;
}
&__yen {
font-size: 0.12rem;
}
&__origin {
margin-left: 0.06rem;
line-height: 0.2rem;
font-size: 0.12rem;
color: $light-font-color;
text-decoration: line-through; //中划线
}
// 购物车选购数量和加减号
.product__number {
position: absolute;
right: 0rem;
bottom: 0.12rem;
&__minus,
&__plus {
display: inline-block;
width: 0.2rem;
height: 0.2rem;
line-height: 0.16rem;
border-radius: 50%;
font-size: 0.2rem;
text-align: center;
}
// 边框白色
&__minus {
border: 0.01rem solid $medium-font-color;
color: $medium-font-color;
margin-right: 0.05rem;
}
//无边框,背景蓝色
&__plus {
color: $bg-color;
background: $btn-bg-color;
margin-left: 0.05rem;
}
}
}
}
</style>
完善购物车下栏的交互逻辑:
src\views\shop\Cart.vue
<template>
<div class="cart">
<div class="check">
<div class="check__icon">
<img src="/i18n/9_16/img/basket.png" alt="" class="check__icon__img" />
<div class="check__icon__tag">{{ total }}</div>
</div>
<div class="check__info">
总计:<span class="check__info__price">¥ {{ totalPrice }}</span>
</div>
<div class="check__btn">去结算</div>
</div>
</div>
</template>
<script>
import { computed } from 'vue'
import { useRoute } from 'vue-router' // 路由跳转方法
import { useStore } from 'vuex' // 路由跳转方法
const useCartEffect = () => {
const store = useStore()
const route = useRoute()
// 计算shopId下所有cartList的商品数量total、价钱之和totalPrice
const shopId = route.params.id // 店铺id
const cartList = store.state.cartList // 加入购物车的商品列表
const total = computed(() => {
const productList = cartList[shopId]
let count = 0
if (productList) {
for (const i in productList) {
const product = productList[i]
count += product.count
}
}
return count
})
const totalPrice = computed(() => {
const productList = cartList[shopId]
let count = 0
if (productList) {
for (const i in productList) {
const product = productList[i]
count += product.count * product.price
}
}
return count.toFixed(2)
})
return { total, totalPrice }
}
export default {
name: 'Cart',
setup() {
const { total, totalPrice } = useCartEffect()
return { total, totalPrice }
}
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables.scss';
.cart {
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
.check {
display: flex;
box-sizing: border-box; //往内塞入border
line-height: 0.49rem;
height: 0.49rem;
border-top: 0.01rem solid $content-bg-color;
&__icon {
width: 0.84rem;
position: relative;
&__img {
margin: 0.12rem auto;
display: block;
width: 0.28rem;
height: 0.28rem;
}
&__tag {
// 乘以2然后等比例缩小
position: absolute;
left: 0.46rem;
top: 0.04rem;
padding: 0 0.04rem;
min-width: 0.2rem;
height: 0.2rem;
line-height: 0.2rem;
text-align: center;
background-color: $height-light-font-color;
border-radius: 0.1rem;
font-size: 0.12rem;
color: $bg-color;
transform: scale(0.5);
transform-origin: left center;
}
}
&__info {
flex: 1;
color: $content-font-color;
font-size: 0.12rem;
&__price {
line-height: 0.49rem;
color: $height-light-font-color;
font-size: 0.18rem;
}
}
&__btn {
width: 0.98rem;
background-color: #4fb0f9;
text-align: center;
color: $bg-color;
font-size: 0.14rem;
}
}
</style>
最终效果如下:
发表评论 (审核通过后显示评论):