feat: 商品提交列表

This commit is contained in:
2026-07-28 10:08:15 +08:00
parent 45489cd0c4
commit 78bca3b3d5
24 changed files with 1849 additions and 234 deletions
@@ -0,0 +1,324 @@
<template>
<section class="cart-panel" :class="{ 'cart-panel--page': pageMode }">
<van-notice-bar v-if="showNotice" text="满 199 元包邮,快去凑单吧~" left-icon="info-o" />
<section class="cart-list">
<van-empty v-if="!items.length" description="购物车还是空的" />
<template v-else>
<article
v-for="item in items"
:key="item.cartId"
class="cart-item"
>
<van-checkbox
:value="isItemChecked(item)"
class="item-check"
checked-color="#ff5b20"
@input="toggleItem(item, $event)"
/>
<van-image class="item-thumb" :src="item.image" fit="cover" />
<div class="item-info">
<h3>{{ item.name }}</h3>
<p class="item-desc">{{ item.desc }}</p>
<van-tag class="item-tag" plain type="danger">{{ item.tag }}</van-tag>
<div class="item-bottom">
<b>¥{{ item.price }}</b>
<van-stepper
v-model="item.count"
class="quantity-stepper"
integer
min="0"
button-size="30"
input-width="36"
@change="onCountChange(item, $event)"
/>
</div>
</div>
</article>
</template>
</section>
<van-submit-bar
:fixed="fixedSubmit"
:price="selectedTotalPrice"
:button-text="submitText"
@submit="$emit('checkout', selectedItems)"
>
<van-checkbox
:value="isAllChecked"
:indeterminate="isPartChecked"
checked-color="#ff5b20"
@input="toggleAll"
>全选</van-checkbox>
</van-submit-bar>
</section>
</template>
<script>
import { mapActions, mapState } from 'pinia'
import { useCartStore } from '@/store/cart'
export default {
name: 'JoyfulCartPanel',
props: {
fixedSubmit: {
type: Boolean,
default: false
},
pageMode: {
type: Boolean,
default: false
},
showNotice: {
type: Boolean,
default: true
}
},
data: () => ({
selectedIds: []
}),
computed: {
...mapState(useCartStore, ['items']),
cartIds() {
return this.items.map(item => item.cartId)
},
selectedItems() {
return this.items.filter(item => this.selectedIds.includes(item.cartId))
},
selectedTotalCount() {
return this.selectedItems.reduce((total, item) => total + Number(item.count || 0), 0)
},
selectedTotalPrice() {
return this.selectedItems.reduce((total, item) => total + Math.round(Number(item.price) * 100) * Number(item.count || 0), 0)
},
isAllChecked() {
return this.items.length > 0 && this.selectedIds.length === this.items.length
},
isPartChecked() {
return this.selectedIds.length > 0 && this.selectedIds.length < this.items.length
},
submitText() {
return `去结算(${this.selectedTotalCount})`
}
},
watch: {
cartIds(newIds, oldIds) {
const wasAllChecked = oldIds.length > 0 && oldIds.every(cartId => this.selectedIds.includes(cartId))
const validSelectedIds = this.selectedIds.filter(cartId => newIds.includes(cartId))
this.selectedIds = wasAllChecked ? newIds : validSelectedIds
}
},
created() {
this.loadCart()
this.selectAllItems()
},
methods: {
...mapActions(useCartStore, ['loadCart', 'persistCart', 'removeItems']),
selectAllItems() {
this.selectedIds = this.items.map(item => item.cartId)
},
isItemChecked(item) {
return this.selectedIds.includes(item.cartId)
},
toggleItem(item, checked) {
if (checked && !this.isItemChecked(item)) {
this.selectedIds = [...this.selectedIds, item.cartId]
return
}
if (!checked) {
this.selectedIds = this.selectedIds.filter(cartId => cartId !== item.cartId)
}
},
toggleAll(checked) {
this.selectedIds = checked ? this.items.map(item => item.cartId) : []
},
onCountChange(item, count) {
if (Number(count) < 1) {
this.removeItems([item.cartId])
return
}
this.persistCart()
}
}
}
</script>
<style scoped lang="less">
.cart-panel {
display: flex;
min-height: 0;
flex-direction: column;
background: #fff;
}
.cart-panel--page {
min-height: calc(100vh - 46px);
padding-bottom: 108px;
}
.cart-panel ::v-deep .van-notice-bar {
height: 34px;
flex: none;
color: #ff7a38;
background: #fff3ea;
font-size: 13px;
}
.cart-list {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 0 16px 12px;
background: #fff;
}
.cart-item {
display: flex;
min-height: 128px;
align-items: center;
padding: 16px 0;
background: #fff;
}
.cart-item + .cart-item {
border-top: 1px solid #f5f5f5;
}
.item-check {
flex: none;
margin-right: 12px;
}
.cart-panel ::v-deep .van-checkbox__icon--checked .van-icon {
border-color: #ff5b20;
background-color: #ff5b20;
}
.item-thumb {
width: 94px;
height: 94px;
flex: none;
overflow: hidden;
border-radius: 5px;
background: #f7f7f7;
}
.item-info {
display: flex;
min-width: 0;
min-height: 94px;
flex: 1;
flex-direction: column;
padding-left: 12px;
}
.item-info h3 {
display: -webkit-box;
overflow: hidden;
margin: 0 0 4px;
color: #222;
font-size: 15px;
font-weight: 700;
line-height: 20px;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.item-desc {
overflow: hidden;
margin: 0 0 6px;
color: #999;
font-size: 12px;
line-height: 16px;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-tag {
width: fit-content;
color: #ff5b20;
border-color: #ff9b73;
font-size: 11px;
line-height: 14px;
}
.item-bottom {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: auto;
}
.item-bottom b {
color: #ff5b20;
font-size: 16px;
font-weight: 700;
}
.quantity-stepper {
flex: none;
}
.cart-panel ::v-deep .quantity-stepper .van-stepper__minus,
.cart-panel ::v-deep .quantity-stepper .van-stepper__plus,
.cart-panel ::v-deep .quantity-stepper .van-stepper__input {
height: 30px;
margin: 0;
border: 1px solid #edf0f4;
background: #fff;
color: #222;
}
.cart-panel ::v-deep .quantity-stepper .van-stepper__minus {
width: 32px;
border-radius: 6px 0 0 6px;
color: #9aa0a6;
}
.cart-panel ::v-deep .quantity-stepper .van-stepper__input {
border-right: 0;
border-left: 0;
font-size: 14px;
font-weight: 700;
}
.cart-panel ::v-deep .quantity-stepper .van-stepper__plus {
width: 32px;
border-radius: 0 6px 6px 0;
}
.cart-panel ::v-deep .van-submit-bar {
position: relative;
height: 58px;
flex: none;
border-top: 1px solid #f0f0f0;
background: #fff;
}
.cart-panel--page ::v-deep .van-submit-bar {
position: fixed;
bottom: 50px;
}
.cart-panel ::v-deep .van-submit-bar__bar {
height: 58px;
padding: 0 18px;
}
.cart-panel ::v-deep .van-submit-bar__price {
color: #ff5b20;
font-size: 17px;
font-weight: 700;
}
.cart-panel ::v-deep .van-submit-bar__button {
width: 104px;
height: 40px;
border-radius: 22px;
background: #ff5b20;
border-color: #ff5b20;
font-weight: 700;
}
</style>