JewelryMall/miniprogram/pages/cart/index.vue
2026-03-02 23:35:11 +08:00

290 lines
7.4 KiB
Vue

<template>
<view class="cart-page">
<!-- 自定义导航栏 -->
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar__content" :style="{ height: navBarHeight + 'px' }">
<text class="navbar__title">购物车</text>
</view>
</view>
<!-- 占位 -->
<view :style="{ height: (statusBarHeight + navBarHeight) + 'px' }"></view>
<!-- 购物车列表 -->
<view v-if="cartStore.items.length > 0" class="cart-list">
<view v-for="item in cartStore.items" :key="item.id" class="cart-item">
<!-- 复选框 -->
<view class="cart-item__check" @click="cartStore.toggleCheck(item.id)">
<image class="check-icon" :src="item.checked ? '/static/ic_check_s.png' : '/static/ic_check.png'" mode="aspectFit" />
</view>
<!-- 内容区 -->
<view class="cart-item__body">
<!-- 上部:图片 + 信息 -->
<view class="cart-item__top">
<image class="cart-item__img" :src="fullUrl(item.product?.thumb || item.product?.bannerImages?.[0] || '')" mode="aspectFill" />
<view class="cart-item__info">
<text class="cart-item__name">{{ item.product?.name || item.specData?.modelName }}</text>
<view class="cart-item__specs">
<text class="spec-tag">款号:{{ item.product?.styleNo }}</text>
<text class="spec-tag">条码号:{{ item.specData?.modelName }}</text>
<text class="spec-tag">镶口:{{ item.specData?.ringSize }}</text>
<text class="spec-tag">分手寸:{{ item.specData?.ringSize }}</text>
<text class="spec-tag">金重:{{ item.specData?.goldTotalWeight }}g</text>
<text class="spec-tag">{{ item.specData?.fineness }}</text>
</view>
</view>
</view>
<!-- 下部:价格 + 删除 -->
<view class="cart-item__bottom">
<text class="cart-item__price">¥{{ item.specData?.totalPrice || 0 }}元</text>
<text class="cart-item__delete" @click="cartStore.removeFromCart(item.id)">删除</text>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-else class="empty-cart">
<image class="empty-cart__icon" src="/static/ic_none.png" mode="aspectFit" />
<text class="empty-cart__text">购物车是空的</text>
</view>
<!-- 底部结算栏 -->
<view v-if="cartStore.items.length > 0" class="settle-bar">
<view class="settle-bar__left" @click="cartStore.toggleCheckAll()">
<image class="check-icon" :src="isAllChecked ? '/static/ic_check_s.png' : '/static/ic_check.png'" mode="aspectFit" />
<text class="settle-bar__all-text">全选</text>
</view>
<view class="settle-bar__right">
<view class="settle-bar__total">
<text class="settle-bar__total-label">合计:</text>
<text class="settle-bar__price">¥{{ cartStore.totalAmount.toFixed(2) }}</text>
</view>
<view
class="settle-bar__btn"
:class="{ 'settle-bar__btn--disabled': cartStore.checkedItems.length === 0 }"
@click="handleSettle"
>开始下单</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useCartStore } from '../../store/cart'
import { BASE_URL } from '../../utils/request'
import type { CartItem } from '../../types'
function fullUrl(path: string): string {
if (!path) return ''
if (path.startsWith('http')) return path
return BASE_URL + path
}
const cartStore = useCartStore()
const statusBarHeight = ref(0)
const navBarHeight = ref(44)
const isAllChecked = computed(() =>
cartStore.items.length > 0 && cartStore.items.every((item) => item.checked),
)
onMounted(() => {
const sysInfo = uni.getSystemInfoSync()
statusBarHeight.value = sysInfo.statusBarHeight || 0
const menuBtn = uni.getMenuButtonBoundingClientRect()
navBarHeight.value = menuBtn.height + (menuBtn.top - (sysInfo.statusBarHeight || 0)) * 2
cartStore.fetchCart()
})
function handleMinus(item: CartItem) {
if (item.quantity > 1) {
cartStore.updateQuantity(item.id, item.quantity - 1)
}
}
function handleSettle() {
if (cartStore.checkedItems.length === 0) {
uni.showToast({ title: '请先选择商品', icon: 'none' })
return
}
const token = uni.getStorageSync('token')
if (!token) {
uni.navigateTo({ url: '/pages/login/index' })
return
}
uni.navigateTo({ url: '/pages/order/submit' })
}
function goHome() {
uni.switchTab({ url: '/pages/index/index' })
}
</script>
<style scoped>
.cart-page {
min-height: 100vh;
background: #f5f5f5;
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
}
/* 导航栏 */
.navbar {
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
background: linear-gradient(to right, #FFCFDE, #FFA6C4);
}
.navbar__content {
display: flex; align-items: center; justify-content: center;
}
.navbar__title {
font-size: 34rpx; font-weight: 600; color: #333;
}
/* 购物车列表 */
.cart-list {
padding: 16rpx 20rpx;
}
.cart-item {
display: flex;
align-items: center;
background: #fff;
border-radius: 16rpx;
padding: 28rpx 20rpx;
margin-bottom: 16rpx;
}
.cart-item__check {
margin-right: 16rpx;
flex-shrink: 0;
}
.check-icon {
width: 44rpx;
height: 44rpx;
}
.cart-item__body {
flex: 1;
min-width: 0;
}
.cart-item__top {
display: flex;
}
.cart-item__img {
width: 200rpx;
height: 200rpx;
border-radius: 16rpx;
margin-right: 24rpx;
flex-shrink: 0;
}
.cart-item__info {
flex: 1;
min-width: 0;
}
.cart-item__name {
font-size: 32rpx;
color: #333;
font-weight: 700;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
margin-bottom: 14rpx;
}
.cart-item__specs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6rpx 20rpx;
}
.spec-tag {
font-size: 24rpx;
color: #666;
line-height: 1.7;
}
.cart-item__bottom {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 16rpx;
}
.cart-item__price {
font-size: 34rpx;
color: #e91e63;
font-weight: bold;
}
.cart-item__delete {
font-size: 24rpx;
color: #bbb;
padding: 8rpx 0;
}
/* 空状态 */
.empty-cart {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 280rpx;
}
.empty-cart__icon {
width: 360rpx;
height: 360rpx;
margin-bottom: 32rpx;
}
.empty-cart__text {
font-size: 30rpx;
color: #bbb;
}
/* 底部结算栏 */
.settle-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 24rpx;
box-shadow: 0 -2rpx 16rpx rgba(0, 0, 0, 0.06);
}
.settle-bar__left {
display: flex;
align-items: center;
}
.settle-bar__all-text {
font-size: 26rpx;
color: #333;
margin-left: 10rpx;
}
.settle-bar__right {
display: flex;
align-items: center;
}
.settle-bar__total {
margin-right: 20rpx;
display: flex;
align-items: baseline;
}
.settle-bar__total-label {
font-size: 26rpx;
color: #666;
}
.settle-bar__price {
color: #e91e63;
font-weight: bold;
font-size: 38rpx;
}
.settle-bar__btn {
background: linear-gradient(to right, #FFB6C8, #FF6D9B);
color: #fff;
font-size: 28rpx;
padding: 18rpx 48rpx;
border-radius: 44rpx;
font-weight: 500;
}
.settle-bar__btn--disabled {
background: linear-gradient(to right, #ddd, #ccc);
}
</style>