2592 lines
60 KiB
Plaintext
2592 lines
60 KiB
Plaintext
<!-- pages/main/cart.uvue -->
|
||
<template>
|
||
<view class="cart-page" :style="cartPageStyle">
|
||
<!-- 智能顶部导航栏 - 与消息页保持一致 -->
|
||
<view class="smart-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||
<view class="nav-container" :style="{ paddingRight: (navBarRight > 0 ? navBarRight : 16) + 'px' }">
|
||
<view class="nav-title-group">
|
||
<text class="nav-title">购物车</text>
|
||
<text class="nav-count">({{ cartItemCount }})</text>
|
||
</view>
|
||
<view class="nav-search-entry" @click="goToCartSearch">
|
||
<text class="nav-search-icon">⌕</text>
|
||
<text class="nav-search-text">搜索购物车商品</text>
|
||
</view>
|
||
</view>
|
||
<view class="cart-tabs-row">
|
||
<view class="cart-tabs-left">
|
||
<view
|
||
class="cart-tab"
|
||
:class="{ 'cart-tab-active': currentCartType == 'goods' }"
|
||
@click="switchCartType('goods')"
|
||
>
|
||
<text class="cart-tab-text">购物</text>
|
||
</view>
|
||
|
||
<view
|
||
class="cart-tab"
|
||
:class="{ 'cart-tab-active': currentCartType == 'service' }"
|
||
@click="switchCartType('service')"
|
||
>
|
||
<text class="cart-tab-text">服务</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="cart-manage-btn" @click="toggleManageMode">
|
||
<text class="cart-manage-text">{{ isManageMode ? '完成' : '管理' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="cart-filter-row">
|
||
<view class="cart-filter-left">
|
||
<view
|
||
class="filter-chip"
|
||
:class="{ 'filter-chip-active': activeQuickFilter == 'discount' }"
|
||
@click="toggleQuickFilter('discount')"
|
||
>
|
||
<text class="filter-chip-icon">↓</text>
|
||
<text class="filter-chip-text">降价</text>
|
||
</view>
|
||
|
||
<view
|
||
class="filter-chip"
|
||
:class="{ 'filter-chip-active': activeQuickFilter == 'frequent' }"
|
||
@click="toggleQuickFilter('frequent')"
|
||
>
|
||
<text class="filter-chip-icon">↺</text>
|
||
<text class="filter-chip-text">常购</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="filter-more-btn" @click="openFilterPanel">
|
||
<text class="filter-more-text">筛选</text>
|
||
<text class="filter-more-icon">⌯</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 导航栏占位符 - 需要包含statusBarHeight + 导航栏高度44px -->
|
||
<view class="navbar-placeholder" :style="{ height: navPlaceholderHeight + 'px' }"></view>
|
||
|
||
<!-- 购物车内容 -->
|
||
<scroll-view
|
||
:scroll-y="true"
|
||
class="cart-content"
|
||
:style="cartContentStyle"
|
||
:show-scrollbar="false"
|
||
:enhanced="true"
|
||
:bounces="true"
|
||
:lower-threshold="120"
|
||
@scroll="onRecommendScroll"
|
||
@scrolltolower="onRecommendScrollToLower"
|
||
>
|
||
<!-- 空购物车 -->
|
||
<view v-if="!loading && currentCartType == 'goods' && displayCartItems.length === 0" class="empty-cart">
|
||
<text class="empty-icon">🛒</text>
|
||
<text class="empty-title">{{ emptyTitle }}</text>
|
||
<text class="empty-desc">{{ emptyDesc }}</text>
|
||
<button v-if="showEmptyShoppingBtn" class="go-shopping-btn" @click="goShopping">去逛逛</button>
|
||
</view>
|
||
|
||
<view v-else-if="!loading && currentCartType == 'service' && displayServiceItems.length === 0" class="empty-cart">
|
||
<text class="empty-icon">🧰</text>
|
||
<text class="empty-title">{{ emptyTitle }}</text>
|
||
<text class="empty-desc">{{ emptyDesc }}</text>
|
||
</view>
|
||
|
||
<view v-else-if="currentCartType == 'service'" class="cart-list service-cart-list">
|
||
<view
|
||
v-for="item in displayServiceItems"
|
||
:key="item.id"
|
||
class="shop-group service-group"
|
||
>
|
||
<view class="service-header">
|
||
<text class="service-shop-name">{{ item.shopName }}</text>
|
||
<text class="service-status">{{ item.serviceStatus }}</text>
|
||
</view>
|
||
<view class="service-item">
|
||
<image class="service-image" :src="item.image" mode="aspectFill" />
|
||
<view class="service-info">
|
||
<text class="service-name" :lines="1">{{ item.name }}</text>
|
||
<text class="service-spec">{{ item.spec }}</text>
|
||
<text class="service-desc" :lines="2">{{ item.description }}</text>
|
||
<view class="service-footer">
|
||
<text class="service-price">¥{{ item.price }}</text>
|
||
<text class="service-action">查看详情</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 购物车商品列表 -->
|
||
<view v-else class="cart-list">
|
||
<view
|
||
v-for="group in displayCartGroups"
|
||
:key="group.shopId"
|
||
class="shop-group"
|
||
>
|
||
<!-- 店铺头部 -->
|
||
<view class="shop-header">
|
||
<view class="shop-select" @click="toggleShopSelect(group.shopId)">
|
||
<text v-if="isShopSelected(group.shopId)" class="selected-icon">✓</text>
|
||
<text v-else class="unselected-icon"></text>
|
||
</view>
|
||
<text class="shop-icon" @click="navigateToShop(group.shopId, group.merchantId)">🏪</text>
|
||
<text class="shop-name" :lines="1" @click="navigateToShop(group.shopId, group.merchantId)">{{ group.shopName }}</text>
|
||
<text class="shop-arrow" @click="navigateToShop(group.shopId, group.merchantId)">></text>
|
||
</view>
|
||
|
||
<!-- 店铺商品 -->
|
||
<view
|
||
v-for="item in group.items"
|
||
:key="item.id"
|
||
class="cart-item"
|
||
>
|
||
<view class="item-select" @click="toggleSelect(item.id)">
|
||
<text v-if="item.selected" class="selected-icon">✓</text>
|
||
<text v-else class="unselected-icon"></text>
|
||
</view>
|
||
|
||
<image
|
||
class="item-image"
|
||
:src="item.image"
|
||
mode="aspectFill"
|
||
@click="navigateToProduct(item)"
|
||
/>
|
||
|
||
<view class="item-info">
|
||
<view class="info-top">
|
||
<text class="item-name" :lines="1">{{ item.name }}</text>
|
||
<text class="item-spec">{{ item.spec }}</text>
|
||
</view>
|
||
|
||
<view class="item-footer">
|
||
<text class="item-price">¥{{ item.price }}</text>
|
||
<view class="quantity-control">
|
||
<text class="quantity-btn" @click="decreaseQuantity(item.id)">-</text>
|
||
<text class="quantity-value">{{ item.quantity }}</text>
|
||
<text class="quantity-btn" @click="increaseQuantity(item.id)">+</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<GuessYouLike
|
||
v-if="currentCartType == 'goods'"
|
||
title="猜你喜欢"
|
||
:pageSize="8"
|
||
:loadMoreKey="guessLoadMoreKey"
|
||
@productClick="handleGuessProductClick"
|
||
/>
|
||
<!-- 底部占位符:确保内容不被原生 TabBar 遮挡 -->
|
||
<view class="tabbar-safe-area"></view>
|
||
</scroll-view>
|
||
|
||
<view v-if="!isLoggedIn" class="guest-login-bar cart-guest-login-bar">
|
||
<view class="guest-login-left">
|
||
<image class="guest-login-avatar" src="/static/images/default.png" mode="aspectFit" />
|
||
<text class="guest-login-text">登录后可同步购物车中的商品</text>
|
||
</view>
|
||
<button class="guest-login-btn" @click.stop="goCartLogin">立即登录</button>
|
||
</view>
|
||
|
||
<view v-if="isLoggedIn && currentCartType == 'goods' && cartItems.length > 0" class="fixed-cart-settlement-bar">
|
||
<view class="settlement-inner">
|
||
<view class="settlement-left">
|
||
<view class="select-all" @click="toggleSelectAll">
|
||
<text v-if="allSelected" class="selected-icon">✓</text>
|
||
<text v-else class="unselected-icon"></text>
|
||
<text class="select-all-text">全选</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="settlement-right">
|
||
<view v-if="!isManageMode" class="total-info">
|
||
<text class="total-text">合计:</text>
|
||
<text class="total-price">¥{{ totalPrice }}</text>
|
||
</view>
|
||
|
||
<button
|
||
v-if="!isManageMode"
|
||
class="checkout-btn"
|
||
@click="goToCheckout"
|
||
>
|
||
去结算({{ selectedCount }})
|
||
</button>
|
||
|
||
<button
|
||
v-else
|
||
class="delete-btn"
|
||
@click="deleteSelectedItems"
|
||
>
|
||
删除({{ selectedCount }})
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部结算栏 - 已移除,移动到内容区域 -->
|
||
<!-- <view v-if="cartItems.length > 0" class="cart-footer">
|
||
<view class="footer-content">
|
||
<view class="footer-left">
|
||
<view class="select-all" @click="toggleSelectAll">
|
||
<text v-if="allSelected" class="selected-icon">✓</text>
|
||
<text v-else class="unselected-icon"></text>
|
||
<text class="select-all-text">全选</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="footer-right">
|
||
<view v-if="!isManageMode" class="total-info">
|
||
<text class="total-text">合计:</text>
|
||
<text class="total-price">¥{{ totalPrice }}</text>
|
||
</view>
|
||
<button v-if="!isManageMode" class="checkout-btn" @click="goToCheckout">
|
||
去结算({{ selectedCount }})
|
||
</button>
|
||
<button v-else class="delete-btn" @click="deleteSelectedItems">
|
||
删除({{ selectedCount }})
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view> -->
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import { onShow } from '@dcloudio/uni-app'
|
||
import { supabaseService, type CartItem as SupabaseCartItem, type Product } from '@/utils/supabaseService.uts'
|
||
import { goToLogin } from '@/utils/utils.uts'
|
||
import GuessYouLike from '@/components/mall/GuessYouLike/GuessYouLike.uvue'
|
||
|
||
type ModalSuccess = { confirm: boolean; cancel: boolean }
|
||
|
||
type LocalCartItem = {
|
||
id: string
|
||
shopId: string
|
||
shopName: string
|
||
name: string
|
||
price: number
|
||
originalPrice: number // 原价
|
||
memberPrice: number // 会员价
|
||
image: string
|
||
spec: string
|
||
quantity: number
|
||
selected: boolean
|
||
productId: string
|
||
skuId: string
|
||
merchantId: string
|
||
type?: string
|
||
hasDiscount?: boolean
|
||
discountAmount?: number
|
||
isFrequent?: boolean
|
||
buyCount?: number
|
||
isMock?: boolean
|
||
serviceStatus?: string
|
||
}
|
||
|
||
type ServiceCartItem = {
|
||
id: string
|
||
shopId: string
|
||
shopName: string
|
||
name: string
|
||
price: number
|
||
image: string
|
||
spec: string
|
||
serviceStatus: string
|
||
description: string
|
||
}
|
||
|
||
type CartGroup = {
|
||
shopId: string
|
||
shopName: string
|
||
merchantId: string
|
||
items: LocalCartItem[]
|
||
}
|
||
|
||
const compareStrings = (a: string, b: string): boolean => {
|
||
console.log('[compareStrings] a length:', a.length, 'b length:', b.length)
|
||
console.log('[compareStrings] a type:', typeof a, 'b type:', typeof b)
|
||
console.log('[compareStrings] a value:', JSON.stringify(a))
|
||
console.log('[compareStrings] b value:', JSON.stringify(b))
|
||
|
||
if (a.length !== b.length) return false
|
||
for (let i = 0; i < a.length; i++) {
|
||
const aCode = a.charCodeAt(i)
|
||
const bCode = b.charCodeAt(i)
|
||
if (aCode != null && bCode != null && aCode !== bCode) {
|
||
console.log('[compareStrings] mismatch at index:', i, 'a:', aCode, 'b:', bCode)
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
type RecommendProduct = {
|
||
id: string
|
||
shopId: string
|
||
shopName: string
|
||
name: string
|
||
price: number
|
||
image: string
|
||
skuId: string
|
||
merchant_id: string
|
||
}
|
||
|
||
// 响应式数据
|
||
const cartItems = ref<LocalCartItem[]>([])
|
||
const recommendProducts = ref<RecommendProduct[]>([])
|
||
const recommendPage = ref<number>(1)
|
||
const recommendPageSize = ref<number>(8)
|
||
const recommendHasMore = ref<boolean>(true)
|
||
const recommendInitialized = ref<boolean>(false)
|
||
const loading = ref<boolean>(false)
|
||
const isLoggedIn = ref<boolean>(false)
|
||
const statusBarHeight = ref(0)
|
||
const isManageMode = ref(false)
|
||
const updatingItems = ref<Set<string>>(new Set()) // Track items being updated to prevent race conditions
|
||
const currentCartType = ref<string>('goods')
|
||
const activeQuickFilter = ref<string>('')
|
||
const recommendLoading = ref(false)
|
||
const lastRecommendLoadTime = ref<number>(0)
|
||
const recommendPendingLoad = ref<boolean>(false)
|
||
const recommendBottomLocked = ref<boolean>(false)
|
||
const recommendViewportHeight = ref<number>(0)
|
||
const pageWindowHeight = ref<number>(0)
|
||
const isAndroidApp = ref<boolean>(false)
|
||
const guessLoadMoreKey = ref<number>(0)
|
||
const serviceMockItems = ref<ServiceCartItem[]>([
|
||
{
|
||
id: 'service-1',
|
||
shopId: 'service-shop-1',
|
||
shopName: '到店服务中心',
|
||
name: '空调深度清洗服务',
|
||
price: 199,
|
||
image: '/static/images/default.png',
|
||
spec: '上门服务|预约制',
|
||
serviceStatus: '待预约',
|
||
description: '含上门检测、深度清洗与基础保养'
|
||
},
|
||
{
|
||
id: 'service-2',
|
||
shopId: 'service-shop-2',
|
||
shopName: '家电保障服务',
|
||
name: '冰箱延保一年',
|
||
price: 89,
|
||
image: '/static/images/default.png',
|
||
spec: '电子保单|立即生效',
|
||
serviceStatus: '已加入',
|
||
description: '覆盖压缩机、电路板等核心部件'
|
||
}
|
||
])
|
||
|
||
// 小程序胶囊按钮信息类型
|
||
type CapsuleButtonInfo = {
|
||
left: number,
|
||
top: number,
|
||
right: number,
|
||
bottom: number,
|
||
width: number,
|
||
height: number
|
||
}
|
||
|
||
// 小程序胶囊按钮信息
|
||
const capsuleButtonInfo = ref<CapsuleButtonInfo | null>(null)
|
||
const navBarRight = ref(0) // 导航栏右侧预留空间
|
||
|
||
// 计算属性
|
||
const cartGroups = computed<CartGroup[]>(() => {
|
||
console.log('[cartGroups] 计算购物车分组, cartItems count:', cartItems.value.length)
|
||
const groups = new Map<string, CartGroup>()
|
||
|
||
cartItems.value.forEach((item: LocalCartItem) => {
|
||
console.log('[cartGroups] item:', item.id, 'shopId:', item.shopId, 'shopName:', item.shopName)
|
||
const shopKey = item.shopId
|
||
if (!groups.has(shopKey)) {
|
||
groups.set(shopKey, {
|
||
shopId: item.shopId,
|
||
shopName: item.shopName,
|
||
merchantId: item.merchantId,
|
||
items: []
|
||
})
|
||
}
|
||
|
||
const group = groups.get(shopKey)
|
||
if (group != null) {
|
||
group.items.push(item)
|
||
}
|
||
})
|
||
|
||
const groupArray: CartGroup[] = []
|
||
groups.forEach((value: CartGroup) => {
|
||
console.log('[cartGroups] group:', value.shopId, 'items count:', value.items.length)
|
||
groupArray.push(value)
|
||
})
|
||
return groupArray
|
||
})
|
||
|
||
const allSelected = computed(() => {
|
||
return cartItems.value.length > 0 && cartItems.value.every((item: LocalCartItem) => item.selected)
|
||
})
|
||
|
||
const cartItemCount = computed((): number => {
|
||
return cartItems.value.length
|
||
})
|
||
|
||
const displayCartItems = computed<LocalCartItem[]>(() => {
|
||
let list = cartItems.value
|
||
|
||
if (currentCartType.value == 'goods') {
|
||
list = list.filter((item: LocalCartItem) => {
|
||
const itemType = item.type
|
||
return itemType == null || itemType == '' || itemType == 'goods'
|
||
})
|
||
}
|
||
|
||
if (currentCartType.value == 'service') {
|
||
return []
|
||
}
|
||
|
||
if (activeQuickFilter.value == 'discount') {
|
||
list = list.filter((item: LocalCartItem) => {
|
||
const hasDiscount = item.hasDiscount == true
|
||
const discountAmount = item.discountAmount ?? 0
|
||
return hasDiscount || discountAmount > 0 || (item.memberPrice > 0 && item.memberPrice < item.price)
|
||
})
|
||
}
|
||
|
||
if (activeQuickFilter.value == 'frequent') {
|
||
list = list.filter((item: LocalCartItem) => {
|
||
const isFrequent = item.isFrequent == true
|
||
const buyCount = item.buyCount ?? 0
|
||
return isFrequent == true || (buyCount != null && buyCount > 1)
|
||
})
|
||
}
|
||
|
||
return list
|
||
})
|
||
|
||
const displayServiceItems = computed<ServiceCartItem[]>(() => {
|
||
let list = serviceMockItems.value
|
||
if (activeQuickFilter.value == 'discount') {
|
||
list = list.filter((item: ServiceCartItem) => item.price <= 199)
|
||
}
|
||
|
||
if (activeQuickFilter.value == 'frequent') {
|
||
list = list.filter((item: ServiceCartItem) => item.serviceStatus == '已加入')
|
||
}
|
||
|
||
return list
|
||
})
|
||
|
||
const displayCartGroups = computed<CartGroup[]>(() => {
|
||
const groups = new Map<string, CartGroup>()
|
||
|
||
displayCartItems.value.forEach((item: LocalCartItem) => {
|
||
const shopKey = item.shopId
|
||
if (!groups.has(shopKey)) {
|
||
groups.set(shopKey, {
|
||
shopId: item.shopId,
|
||
shopName: item.shopName,
|
||
merchantId: item.merchantId,
|
||
items: []
|
||
})
|
||
}
|
||
|
||
const group = groups.get(shopKey)
|
||
if (group != null) {
|
||
group.items.push(item)
|
||
}
|
||
})
|
||
|
||
const groupArray: CartGroup[] = []
|
||
groups.forEach((value: CartGroup) => {
|
||
groupArray.push(value)
|
||
})
|
||
return groupArray
|
||
})
|
||
|
||
const navPlaceholderHeight = computed((): number => {
|
||
return statusBarHeight.value + 44 + 52 + 46
|
||
})
|
||
|
||
const cartPageStyle = computed((): string => {
|
||
if (!isAndroidApp.value) {
|
||
return ''
|
||
}
|
||
if (pageWindowHeight.value <= 0) {
|
||
return ''
|
||
}
|
||
return 'height:' + pageWindowHeight.value + 'px;min-height:' + pageWindowHeight.value + 'px;'
|
||
})
|
||
|
||
const cartContentStyle = computed((): string => {
|
||
if (!isAndroidApp.value) {
|
||
return ''
|
||
}
|
||
if (pageWindowHeight.value <= 0) {
|
||
return ''
|
||
}
|
||
const contentHeight = Math.max(pageWindowHeight.value - navPlaceholderHeight.value, 240)
|
||
return 'height:' + contentHeight + 'px;min-height:' + contentHeight + 'px;'
|
||
})
|
||
|
||
const emptyTitle = computed((): string => {
|
||
if (currentCartType.value == 'service') {
|
||
return '暂无服务类购物车商品'
|
||
}
|
||
if (activeQuickFilter.value == 'discount') {
|
||
return '暂无降价商品'
|
||
}
|
||
if (activeQuickFilter.value == 'frequent') {
|
||
return '暂无常购商品'
|
||
}
|
||
return '购物车是空的'
|
||
})
|
||
|
||
const emptyDesc = computed((): string => {
|
||
if (!isLoggedIn.value && currentCartType.value == 'goods') {
|
||
return '登录后可同步购物车中的商品,换设备也能继续查看'
|
||
}
|
||
if (currentCartType.value == 'service') {
|
||
return '可切换筛选条件,或等待服务购物车正式接入'
|
||
}
|
||
if (activeQuickFilter.value == 'discount') {
|
||
return '当前没有符合降价条件的商品'
|
||
}
|
||
if (activeQuickFilter.value == 'frequent') {
|
||
return '当前没有符合常购条件的商品'
|
||
}
|
||
return '快去挑选喜欢的商品吧'
|
||
})
|
||
|
||
function refreshLoginState(): void {
|
||
const userId = supabaseService.getCurrentUserId()
|
||
isLoggedIn.value = userId != null && userId !== ''
|
||
}
|
||
|
||
function goCartLogin(): void {
|
||
goToLogin('/pages/main/cart')
|
||
}
|
||
|
||
const showEmptyShoppingBtn = computed((): boolean => {
|
||
return currentCartType.value == 'goods'
|
||
})
|
||
|
||
const selectedCount = computed(() => {
|
||
return cartItems.value.filter((item: LocalCartItem) => item.selected).reduce((sum: number, item: LocalCartItem) => sum + item.quantity, 0)
|
||
})
|
||
|
||
const totalPrice = computed(() => {
|
||
return cartItems.value
|
||
.filter((item: LocalCartItem) => item.selected)
|
||
.reduce((sum: number, item: LocalCartItem) => {
|
||
// 优先使用会员价,如果没有会员价则使用原价
|
||
const finalPrice = item.memberPrice > 0 && item.memberPrice < item.price ? item.memberPrice : item.price
|
||
return sum + finalPrice * item.quantity
|
||
}, 0)
|
||
.toFixed(2)
|
||
})
|
||
|
||
// 计算会员节省金额
|
||
const memberSavedAmount = computed(() => {
|
||
return cartItems.value
|
||
.filter((item: LocalCartItem) => item.selected && item.memberPrice > 0 && item.memberPrice < item.price)
|
||
.reduce((sum: number, item: LocalCartItem) => sum + (item.price - item.memberPrice) * item.quantity, 0)
|
||
.toFixed(2)
|
||
})
|
||
|
||
// 检查店铺是否全选
|
||
const isShopSelected = (shopId: string): boolean => {
|
||
const shopItems: LocalCartItem[] = []
|
||
for (let i = 0; i < cartItems.value.length; i++) {
|
||
if (compareStrings(cartItems.value[i].shopId, shopId)) {
|
||
shopItems.push(cartItems.value[i])
|
||
}
|
||
}
|
||
if (shopItems.length === 0) return false
|
||
for (let i = 0; i < shopItems.length; i++) {
|
||
if (!shopItems[i].selected) return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
const toggleManageMode = () => {
|
||
isManageMode.value = !isManageMode.value
|
||
}
|
||
|
||
const switchCartType = (type: string) => {
|
||
currentCartType.value = type
|
||
if (type == 'service') {
|
||
isManageMode.value = false
|
||
}
|
||
}
|
||
|
||
const toggleQuickFilter = (filter: string) => {
|
||
if (activeQuickFilter.value == filter) {
|
||
activeQuickFilter.value = ''
|
||
} else {
|
||
activeQuickFilter.value = filter
|
||
}
|
||
}
|
||
|
||
const goToCartSearch = () => {
|
||
uni.navigateTo({
|
||
url: '/pages/main/cart-search/cart-search'
|
||
})
|
||
}
|
||
|
||
const openFilterPanel = () => {
|
||
uni.showActionSheet({
|
||
itemList: ['全部商品', '只看降价', '只看常购', '只看有货', '只看失效商品'],
|
||
success: (res) => {
|
||
console.log('filter index:', res.tapIndex)
|
||
if (res.tapIndex === 0) {
|
||
activeQuickFilter.value = ''
|
||
return
|
||
}
|
||
if (res.tapIndex === 1) {
|
||
activeQuickFilter.value = 'discount'
|
||
return
|
||
}
|
||
if (res.tapIndex === 2) {
|
||
activeQuickFilter.value = 'frequent'
|
||
return
|
||
}
|
||
uni.showToast({
|
||
title: '当前筛选项待接入',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
// 初始化页面数据
|
||
const initPage = () => {
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
statusBarHeight.value = systemInfo.statusBarHeight ?? 0
|
||
pageWindowHeight.value = systemInfo.windowHeight ?? systemInfo.screenHeight ?? 0
|
||
recommendViewportHeight.value = Math.max(pageWindowHeight.value - navPlaceholderHeight.value - 120, 240)
|
||
// #ifdef APP-ANDROID
|
||
isAndroidApp.value = true
|
||
// #endif
|
||
|
||
// 获取小程序胶囊按钮信息
|
||
// #ifdef MP-WEIXIN
|
||
try {
|
||
const menuButton = uni.getMenuButtonBoundingClientRect()
|
||
if (menuButton != null) {
|
||
capsuleButtonInfo.value = {
|
||
left: menuButton.left,
|
||
top: menuButton.top,
|
||
right: menuButton.right,
|
||
bottom: menuButton.bottom,
|
||
width: menuButton.width,
|
||
height: menuButton.height
|
||
}
|
||
navBarRight.value = (systemInfo.screenWidth - menuButton.left) + 10
|
||
}
|
||
} catch (e) {
|
||
console.log('获取胶囊按钮信息失败', e)
|
||
navBarRight.value = 90
|
||
}
|
||
// #endif
|
||
}
|
||
|
||
// 生命周期
|
||
onMounted(() => {
|
||
initPage()
|
||
})
|
||
|
||
const mergeRecommendProducts = (oldList: RecommendProduct[], newList: RecommendProduct[]): RecommendProduct[] => {
|
||
const ids: string[] = []
|
||
const result: RecommendProduct[] = []
|
||
|
||
for (let i = 0; i < oldList.length; i++) {
|
||
ids.push(oldList[i].id)
|
||
result.push(oldList[i])
|
||
}
|
||
|
||
for (let i = 0; i < newList.length; i++) {
|
||
if (ids.indexOf(newList[i].id) < 0) {
|
||
ids.push(newList[i].id)
|
||
result.push(newList[i])
|
||
}
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
const fetchRecommendProducts = async (page: number, pageSize: number): Promise<RecommendProduct[]> => {
|
||
console.log('[cart推荐] fetchRecommendProducts 请求 page=', page, 'pageSize=', pageSize)
|
||
const hotResp = await supabaseService.searchProducts('', page, pageSize, 'sales')
|
||
console.log('[cart推荐] fetchRecommendProducts 完成 total=', hotResp.total, 'hasmore=', hotResp.hasmore, 'dataLength=', hotResp.data.length)
|
||
const rawList = hotResp.data
|
||
return rawList.map((p: Product): RecommendProduct => {
|
||
return {
|
||
id: p.id,
|
||
shopId: p.merchant_id ?? 'unknown',
|
||
shopName: p.shop_name ?? '商城推荐',
|
||
name: p.name,
|
||
price: p.base_price ?? p.market_price ?? 0,
|
||
image: p.main_image_url ?? p.image_url ?? '/static/images/default.png',
|
||
skuId: '',
|
||
merchant_id: p.merchant_id ?? ''
|
||
}
|
||
})
|
||
}
|
||
|
||
function toRecommendScrollJson(value: any | null): UTSJSONObject | null {
|
||
if (value == null) {
|
||
return null
|
||
}
|
||
if (value instanceof UTSJSONObject) {
|
||
return value as UTSJSONObject
|
||
}
|
||
const raw = JSON.stringify(value)
|
||
if (raw == '' || raw == 'null') {
|
||
return null
|
||
}
|
||
const parsed = JSON.parse(raw)
|
||
if (parsed == null) {
|
||
return null
|
||
}
|
||
return parsed as UTSJSONObject
|
||
}
|
||
|
||
function readRecommendScrollMetric(detail: UTSJSONObject | null, key: string): number {
|
||
if (detail == null) {
|
||
return 0
|
||
}
|
||
const value = detail.getNumber(key)
|
||
if (value != null) {
|
||
return value
|
||
}
|
||
return 0
|
||
}
|
||
|
||
async function loadRecommendProducts(reset: boolean): Promise<void> {
|
||
console.log('[cart推荐] loadRecommendProducts 入口 reset=', reset, 'page=', recommendPage.value, 'pageSize=', recommendPageSize.value, 'loading=', recommendLoading.value, 'hasMore=', recommendHasMore.value, 'oldLength=', recommendProducts.value.length)
|
||
if (recommendLoading.value) {
|
||
console.log('[cart推荐] 跳过:正在加载中')
|
||
if (!reset) {
|
||
recommendPendingLoad.value = true
|
||
}
|
||
return
|
||
}
|
||
if (!reset && !recommendHasMore.value) {
|
||
console.log('[cart推荐] 跳过:没有更多数据')
|
||
return
|
||
}
|
||
if (!reset) {
|
||
lastRecommendLoadTime.value = Date.now()
|
||
}
|
||
|
||
recommendLoading.value = true
|
||
|
||
if (reset) {
|
||
recommendPage.value = 1
|
||
recommendHasMore.value = true
|
||
}
|
||
|
||
try {
|
||
const page = recommendPage.value
|
||
const pageSize = recommendPageSize.value
|
||
const newList = await fetchRecommendProducts(page, pageSize)
|
||
console.log('[cart推荐] page=', page, '返回数量=', newList.length)
|
||
console.log('[cart推荐] 返回ID=', newList.map((item) => item.id).join(','))
|
||
const beforeLength = recommendProducts.value.length
|
||
let afterList: RecommendProduct[] = []
|
||
|
||
if (reset) {
|
||
afterList = newList
|
||
} else {
|
||
afterList = mergeRecommendProducts(recommendProducts.value, newList)
|
||
}
|
||
recommendProducts.value = afterList
|
||
console.log('[cart推荐] 追加前=', beforeLength, '追加后=', recommendProducts.value.length)
|
||
if (!reset && newList.length > 0 && afterList.length === beforeLength) {
|
||
console.warn('[cart推荐] 本次返回商品全部重复,请检查 searchProducts 分页是否生效 page=', page)
|
||
}
|
||
|
||
if (newList.length < pageSize) {
|
||
recommendHasMore.value = false
|
||
} else {
|
||
recommendPage.value = recommendPage.value + 1
|
||
}
|
||
|
||
if (!reset) {
|
||
recommendBottomLocked.value = false
|
||
}
|
||
|
||
recommendInitialized.value = true
|
||
} catch (error) {
|
||
console.error('加载推荐商品失败:', error)
|
||
recommendHasMore.value = false
|
||
} finally {
|
||
recommendLoading.value = false
|
||
if (!reset && recommendPendingLoad.value && recommendHasMore.value) {
|
||
console.log('[cart推荐] 消费待续加载请求')
|
||
recommendPendingLoad.value = false
|
||
loadRecommendProducts(false)
|
||
}
|
||
}
|
||
}
|
||
|
||
function onRecommendScrollToLower(): void {
|
||
if (currentCartType.value == 'goods') {
|
||
guessLoadMoreKey.value = guessLoadMoreKey.value + 1
|
||
}
|
||
}
|
||
|
||
function onRecommendScroll(event: any): void {
|
||
return
|
||
}
|
||
|
||
// 加载数据
|
||
const loadCartData = async () => {
|
||
loading.value = true
|
||
const userId = supabaseService.getCurrentUserId()
|
||
if (userId == null || userId === '') {
|
||
isLoggedIn.value = false
|
||
cartItems.value = []
|
||
loading.value = false
|
||
return
|
||
}
|
||
|
||
isLoggedIn.value = true
|
||
|
||
try {
|
||
// 获取会员折扣信息
|
||
let memberDiscount = 1.0
|
||
try {
|
||
const memberInfo = await supabaseService.getUserMemberInfo()
|
||
const discountRaw = memberInfo.get('discount')
|
||
if (discountRaw != null) {
|
||
memberDiscount = discountRaw as number
|
||
}
|
||
} catch (e) {
|
||
console.log('获取会员信息失败,使用默认折扣:', e)
|
||
}
|
||
|
||
// 从Supabase加载购物车数据
|
||
const supabaseCartItems = await supabaseService.getCartItems()
|
||
|
||
// 转换数据格式以匹配前端界面
|
||
const transformedItems = supabaseCartItems.map((item: SupabaseCartItem): LocalCartItem => {
|
||
// 调试日志:打印每条商品数据的关键字段
|
||
console.log(`CartItem raw: id=${item.id}, shop_id=${item.shop_id}, shop_name=${item.shop_name}, name=${item.product_name}, price=${item.product_price}`);
|
||
|
||
// 关键修复:确保shopId有值,如果后端返回null/undefined,使用'default_shop'作为分组键
|
||
const shopId = (item.shop_id != null && item.shop_id !== '') ? item.shop_id : 'default_shop'
|
||
const shopName = (item.shop_name != null && item.shop_name !== '') ? item.shop_name : '商城优选'
|
||
|
||
// 计算会员价
|
||
const originalPrice = item.product_price != null ? item.product_price : 0
|
||
let memberPrice = 0
|
||
if (memberDiscount > 0 && memberDiscount < 1 && originalPrice > 0) {
|
||
memberPrice = Math.round(originalPrice * memberDiscount * 100) / 100
|
||
}
|
||
|
||
return {
|
||
id: item.id,
|
||
shopId: shopId,
|
||
shopName: shopName,
|
||
name: item.product_name ?? '未知商品',
|
||
price: originalPrice,
|
||
originalPrice: originalPrice,
|
||
memberPrice: memberPrice,
|
||
image: item.product_image ?? '/static/images/default.png',
|
||
spec: item.product_specification ?? '标准规格',
|
||
quantity: item.quantity ?? 1,
|
||
selected: item.selected ?? false,
|
||
productId: item.product_id ?? '',
|
||
skuId: item.sku_id ?? '',
|
||
merchantId: item.merchant_id ?? ''
|
||
} as LocalCartItem
|
||
})
|
||
|
||
console.log('Transformed items count:', transformedItems.length);
|
||
cartItems.value = transformedItems
|
||
|
||
} catch (error) {
|
||
console.error('加载购物车数据失败:', error)
|
||
cartItems.value = []
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
onShow(() => {
|
||
refreshLoginState()
|
||
loadCartData()
|
||
})
|
||
|
||
const handleGuessProductClick = (productId: string) => {
|
||
navigateToProduct({ id: productId, productId: productId, price: 0 })
|
||
}
|
||
|
||
// 商品操作 - 更新选中状态到Supabase
|
||
const toggleSelect = async (itemId: string) => {
|
||
// 乐观更新
|
||
const index = cartItems.value.findIndex(item => item.id === itemId)
|
||
if (index !== -1) {
|
||
const newSelected = !cartItems.value[index].selected
|
||
cartItems.value[index].selected = newSelected
|
||
cartItems.value = [...cartItems.value] // 触发响应式更新
|
||
|
||
// 更新到Supabase
|
||
const success = await supabaseService.updateCartItemSelection(itemId, newSelected)
|
||
if (!success) {
|
||
console.error('更新选中状态失败')
|
||
// 恢复状态
|
||
cartItems.value[index].selected = !newSelected
|
||
cartItems.value = [...cartItems.value]
|
||
uni.showToast({ title: '网络异常,请重试', icon: 'none' })
|
||
}
|
||
}
|
||
}
|
||
|
||
const toggleShopSelect = async (shopId: string) => {
|
||
console.log('[toggleShopSelect] shopId:', shopId)
|
||
console.log('[toggleShopSelect] shopId length:', shopId.length)
|
||
console.log('[toggleShopSelect] cartItems.value.length:', cartItems.value.length)
|
||
|
||
// 用 for 循环替代 filter,避免安卓端 UTS filter 的问题
|
||
const shopItems: LocalCartItem[] = []
|
||
for (let i = 0; i < cartItems.value.length; i++) {
|
||
const item = cartItems.value[i]
|
||
const itemShopId = item.shopId
|
||
// 安卓端字符串比较问题:使用 localeCompare 或逐字符比较
|
||
const isMatch = compareStrings(itemShopId, shopId)
|
||
console.log('[toggleShopSelect] checking item:', item.id, 'item.shopId:', itemShopId, 'match:', isMatch)
|
||
if (isMatch) {
|
||
shopItems.push(item)
|
||
}
|
||
}
|
||
console.log('[toggleShopSelect] shopItems count:', shopItems.length)
|
||
|
||
if (shopItems.length === 0) return
|
||
|
||
// 用 for 循环替代 every
|
||
let allSelected = true
|
||
for (let i = 0; i < shopItems.length; i++) {
|
||
if (!shopItems[i].selected) {
|
||
allSelected = false
|
||
break
|
||
}
|
||
}
|
||
const newState = !allSelected
|
||
console.log('[toggleShopSelect] allSelected:', allSelected, 'newState:', newState)
|
||
|
||
const shopItemIds: string[] = []
|
||
for (let i = 0; i < shopItems.length; i++) {
|
||
shopItemIds.push(shopItems[i].id)
|
||
}
|
||
console.log('[toggleShopSelect] shopItemIds:', shopItemIds)
|
||
|
||
// 创建全新的数组来触发响应式更新
|
||
const newCartItems: LocalCartItem[] = []
|
||
for (let i = 0; i < cartItems.value.length; i++) {
|
||
const item = cartItems.value[i]
|
||
const isMatch = compareStrings(item.shopId, shopId)
|
||
if (isMatch) {
|
||
console.log('[toggleShopSelect] updating item:', item.id, 'to selected:', newState)
|
||
// 创建新的对象
|
||
const newItem: LocalCartItem = {
|
||
id: item.id,
|
||
shopId: item.shopId,
|
||
shopName: item.shopName,
|
||
name: item.name,
|
||
price: item.price,
|
||
originalPrice: item.originalPrice,
|
||
memberPrice: item.memberPrice,
|
||
image: item.image,
|
||
spec: item.spec,
|
||
quantity: item.quantity,
|
||
selected: newState,
|
||
productId: item.productId,
|
||
skuId: item.skuId,
|
||
merchantId: item.merchantId
|
||
}
|
||
newCartItems.push(newItem)
|
||
} else {
|
||
newCartItems.push(item)
|
||
}
|
||
}
|
||
// 替换整个数组
|
||
cartItems.value = newCartItems
|
||
|
||
// 批量更新到Supabase
|
||
const success = await supabaseService.batchUpdateCartItemSelection(shopItemIds, newState)
|
||
|
||
if (!success) {
|
||
console.error('批量更新店铺商品选中状态失败')
|
||
uni.showToast({
|
||
title: '操作失败',
|
||
icon: 'none'
|
||
})
|
||
// 重新加载数据以确保状态一致
|
||
loadCartData()
|
||
}
|
||
}
|
||
|
||
const toggleSelectAll = async () => {
|
||
// 目标状态:如果当前全选,则取消全选;否则全选
|
||
const newSelectedState = !allSelected.value
|
||
|
||
// 乐观更新
|
||
const oldItems = JSON.parse(JSON.stringify(cartItems.value)) as LocalCartItem[]
|
||
const selectedItems = cartItems.value.map((item): LocalCartItem => {
|
||
item.selected = newSelectedState
|
||
return item
|
||
})
|
||
cartItems.value = selectedItems
|
||
|
||
// 更新到Supabase
|
||
const itemIds = cartItems.value.map(item => item.id)
|
||
if (itemIds.length === 0) return
|
||
|
||
const success = await supabaseService.batchUpdateCartItemSelection(itemIds, newSelectedState)
|
||
|
||
if (!success) {
|
||
console.error('批量更新选中状态失败')
|
||
cartItems.value = oldItems
|
||
uni.showToast({
|
||
title: '操作失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
const increaseQuantity = async (itemId: string) => {
|
||
if (updatingItems.value.has(itemId)) return
|
||
|
||
const index = cartItems.value.findIndex(item => item.id === itemId)
|
||
if (index !== -1) {
|
||
updatingItems.value.add(itemId)
|
||
const newQuantity = cartItems.value[index].quantity + 1
|
||
cartItems.value[index].quantity = newQuantity
|
||
cartItems.value = [...cartItems.value]
|
||
|
||
// 更新到Supabase
|
||
const success = await supabaseService.updateCartItemQuantity(itemId, newQuantity)
|
||
updatingItems.value.delete(itemId)
|
||
|
||
if (!success) {
|
||
console.error('更新商品数量失败')
|
||
// 恢复状态
|
||
cartItems.value[index].quantity = newQuantity - 1
|
||
cartItems.value = [...cartItems.value]
|
||
uni.showToast({ title: '更新失败', icon: 'none' })
|
||
}
|
||
}
|
||
}
|
||
|
||
const decreaseQuantity = async (itemId: string) => {
|
||
if (updatingItems.value.has(itemId)) return
|
||
|
||
const index = cartItems.value.findIndex(item => item.id === itemId)
|
||
if (index !== -1) {
|
||
if (cartItems.value[index].quantity > 1) {
|
||
updatingItems.value.add(itemId)
|
||
const newQuantity = cartItems.value[index].quantity - 1
|
||
cartItems.value[index].quantity = newQuantity
|
||
cartItems.value = [...cartItems.value]
|
||
|
||
// 更新到Supabase
|
||
const success = await supabaseService.updateCartItemQuantity(itemId, newQuantity)
|
||
updatingItems.value.delete(itemId)
|
||
|
||
if (!success) {
|
||
console.error('更新商品数量失败')
|
||
// 恢复状态
|
||
cartItems.value[index].quantity = newQuantity + 1
|
||
cartItems.value = [...cartItems.value]
|
||
uni.showToast({ title: '更新失败', icon: 'none' })
|
||
}
|
||
} else {
|
||
// 数量为1时,询问是否删除
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定要从购物车移除该商品吗?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// 从Supabase删除
|
||
supabaseService.deleteCartItem(itemId).then((success) => {
|
||
if (success) {
|
||
cartItems.value.splice(index, 1)
|
||
cartItems.value = [...cartItems.value]
|
||
uni.showToast({
|
||
title: '已移除',
|
||
icon: 'none'
|
||
})
|
||
} else {
|
||
console.error('删除商品失败')
|
||
uni.showToast({
|
||
title: '删除失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 删除商品 - 增加保存逻辑
|
||
const deleteSelectedItems = async () => {
|
||
if (selectedCount.value === 0) {
|
||
uni.showToast({
|
||
title: '请选择要删除的商品',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: `确定要删除选中的 ${selectedCount.value} 件商品吗?`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// 获取选中的商品ID
|
||
const selectedItemIds = cartItems.value
|
||
.filter(item => item.selected)
|
||
.map(item => item.id)
|
||
|
||
// 批量删除到Supabase
|
||
supabaseService.batchDeleteCartItems(selectedItemIds).then((success) => {
|
||
if (success) {
|
||
// 从本地列表移除
|
||
cartItems.value = cartItems.value.filter(item => !item.selected)
|
||
|
||
// 如果购物车删空了,退出管理模式
|
||
if (cartItems.value.length === 0) {
|
||
isManageMode.value = false
|
||
}
|
||
uni.showToast({
|
||
title: '删除成功',
|
||
icon: 'success'
|
||
})
|
||
} else {
|
||
console.error('批量删除商品失败')
|
||
uni.showToast({
|
||
title: '删除失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const addToCart = async (product: RecommendProduct) => {
|
||
const userId = supabaseService.getCurrentUserId()
|
||
if (userId == null || userId === '') {
|
||
goToLogin('/pages/main/cart')
|
||
return
|
||
}
|
||
uni.showLoading({ title: '检查商品...' })
|
||
try {
|
||
const productId = product.id
|
||
const skuId = product.skuId
|
||
const merchantId = product.merchant_id
|
||
|
||
// 检查商品是否有SKU
|
||
const skus = await supabaseService.getProductSkus(productId)
|
||
uni.hideLoading()
|
||
|
||
if (skus.length > 0) {
|
||
// 有规格,提示并跳转到商品详情页选择规格
|
||
uni.showToast({
|
||
title: '请选择规格',
|
||
icon: 'none'
|
||
})
|
||
setTimeout(() => {
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/product-detail?id=' + productId
|
||
})
|
||
}, 500)
|
||
} else {
|
||
// 无规格,直接加入购物车
|
||
uni.showLoading({ title: '添加中...' })
|
||
const success = await supabaseService.addToCart(productId, 1, skuId, merchantId)
|
||
uni.hideLoading()
|
||
if (success) {
|
||
uni.showToast({
|
||
title: '已添加到购物车',
|
||
icon: 'success'
|
||
})
|
||
|
||
// 重新加载购物车数据
|
||
loadCartData()
|
||
} else {
|
||
console.error('添加商品到购物车失败')
|
||
uni.showToast({
|
||
title: '添加失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('添加商品到购物车异常:', error)
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '添加失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 导航函数
|
||
const navigateToShop = (shopId: string, merchantId: any) => {
|
||
// Prevent navigation for invalid shops
|
||
if (shopId == '' || shopId === 'default_shop' || shopId === 'unknown') return
|
||
|
||
let url = `/pages/mall/consumer/shop-detail?id=${shopId}`
|
||
if (merchantId != null) {
|
||
const mId = `${merchantId}`
|
||
if (mId !== '' && mId !== 'null' && mId !== 'undefined' && mId !== 'false') {
|
||
url += `&merchantId=${mId}`
|
||
}
|
||
}
|
||
uni.navigateTo({ url })
|
||
}
|
||
|
||
const goShopping = () => {
|
||
uni.switchTab({ url: '/pages/main/index' })
|
||
}
|
||
|
||
const navigateToProduct = (product: any) => {
|
||
console.log('navigateToProduct', product)
|
||
|
||
// 使用 JSON 转换确保可以作为 JSONObject 处理,兼容 LocalCartItem 类型和普通对象
|
||
const productJson = JSON.parse(JSON.stringify(product)) as UTSJSONObject
|
||
|
||
// 使用productId(如果存在)作为跳转的商品ID,否则使用id
|
||
let productId = productJson.getString('productId')
|
||
if (productId == null || productId == '') {
|
||
productId = productJson.getString('id')
|
||
}
|
||
|
||
if (productId == null || productId == '') {
|
||
console.error('无法获取商品ID', product)
|
||
return
|
||
}
|
||
|
||
// 传递完整的参数,确保商品详情页能正确加载
|
||
let paramsArr: string[] = []
|
||
paramsArr.push('id=' + encodeURIComponent(productId))
|
||
paramsArr.push('productId=' + encodeURIComponent(productId))
|
||
|
||
const price = productJson.getNumber('price') ?? 0
|
||
paramsArr.push('price=' + price)
|
||
|
||
let originalPrice = productJson.getNumber('original_price')
|
||
if (originalPrice == null) {
|
||
originalPrice = productJson.getNumber('originalPrice')
|
||
}
|
||
if (originalPrice == null) {
|
||
originalPrice = parseFloat((price * 1.2).toFixed(2))
|
||
}
|
||
paramsArr.push('originalPrice=' + originalPrice)
|
||
|
||
const name = productJson.getString('name') ?? ''
|
||
paramsArr.push('name=' + encodeURIComponent(name))
|
||
|
||
const image = productJson.getString('image') ?? '/static/images/default.png'
|
||
paramsArr.push('image=' + encodeURIComponent(image))
|
||
|
||
const url = `/pages/mall/consumer/product-detail?${paramsArr.join('&')}`
|
||
console.log('Navigate to:', url)
|
||
|
||
uni.navigateTo({
|
||
url: url
|
||
})
|
||
}
|
||
|
||
const goToCheckout = () => {
|
||
if (selectedCount.value === 0) {
|
||
uni.showToast({
|
||
title: '请选择商品',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
// 获取选中的商品 (直接过滤cartItems,不依赖cartGroups,确保扁平化传递)
|
||
const selectedItems = cartItems.value
|
||
.filter(item => item.selected)
|
||
.map(item => ({
|
||
id: item.id,
|
||
product_id: item.productId ?? item.id,
|
||
sku_id: item.skuId ?? item.id,
|
||
product_name: item.name,
|
||
shop_id: item.shopId, // 关键:保留shopId用于分组
|
||
shop_name: item.shopName, // 关键:保留shopName
|
||
merchant_id: item.merchantId,
|
||
product_image: item.image,
|
||
sku_specifications: item.spec,
|
||
price: item.price, // 确保是数字
|
||
quantity: item.quantity // 确保是数字
|
||
}))
|
||
|
||
// 关键修复:将结算数据写入 Storage,确保 checkout 页面能稳定获取
|
||
uni.setStorageSync('checkout_type', 'cart')
|
||
// 使用纯JSON序列化防止复杂对象引发的问题
|
||
try {
|
||
uni.setStorageSync('checkout_items', JSON.stringify(selectedItems))
|
||
} catch (e) {
|
||
console.error('存储结算数据失败', e)
|
||
uni.showToast({ title: '系统异常,请重试', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
// 跳转到结算页面并传递数据
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/checkout'
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.cart-page {
|
||
width: 100%;
|
||
height: 100vh;
|
||
min-height: 100vh;
|
||
background-color: #f5f5f5;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden; /* 防止整页滚动 */
|
||
}
|
||
|
||
/* 智能导航栏 */
|
||
.smart-navbar {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background-color: #ff5000;
|
||
z-index: 1000;
|
||
box-shadow: 0 2px 12px rgba(255, 80, 0, 0.15);
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: flex-start;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.nav-container {
|
||
padding: 0 16px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
width: 100%;
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
height: 44px; /* 统一高度 44px */
|
||
}
|
||
|
||
.nav-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: white;
|
||
}
|
||
|
||
.nav-title-group {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: baseline;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.nav-count {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: rgba(255, 255, 255, 0.92);
|
||
margin-left: 4px;
|
||
}
|
||
|
||
.nav-search-entry {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex: 1;
|
||
margin-left: 14px;
|
||
height: 34px;
|
||
padding: 0 12px;
|
||
border-radius: 17px;
|
||
background: rgba(255, 255, 255, 0.18);
|
||
min-width: 0;
|
||
}
|
||
|
||
.search-bar-row {
|
||
padding: 0 16px 12px 16px;
|
||
}
|
||
|
||
.nav-search-icon {
|
||
font-size: 15px;
|
||
color: rgba(255, 255, 255, 0.92);
|
||
margin-right: 6px;
|
||
}
|
||
|
||
.nav-search-text {
|
||
font-size: 13px;
|
||
color: rgba(255, 255, 255, 0.92);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.search-bar {
|
||
background-color: rgba(255, 255, 255, 0.96);
|
||
border-radius: 18px;
|
||
height: 40px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 0 12px;
|
||
}
|
||
|
||
.search-bar-icon {
|
||
font-size: 16px;
|
||
color: #999;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
height: 40px;
|
||
font-size: 14px;
|
||
color: #333;
|
||
background-color: transparent;
|
||
border: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
}
|
||
|
||
.search-clear {
|
||
font-size: 13px;
|
||
color: #666;
|
||
margin-left: 8px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* 导航栏占位符 */
|
||
.navbar-placeholder {
|
||
width: 100%;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.cart-tabs-row {
|
||
width: 100%;
|
||
height: 52px;
|
||
padding: 0 16px;
|
||
background-color: #ffffff;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.cart-tabs-left {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.cart-tab {
|
||
height: 52px;
|
||
margin-right: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
}
|
||
|
||
.cart-tab-text {
|
||
font-size: 18px;
|
||
color: #222222;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.cart-tab-active .cart-tab-text {
|
||
color: #e60012;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.cart-tab-active::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 50%;
|
||
bottom: 5px;
|
||
width: 28px;
|
||
height: 3px;
|
||
border-radius: 2px;
|
||
background-color: #e60012;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
.cart-manage-btn {
|
||
height: 36px;
|
||
padding: 0 4px 0 16px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-left: 1px solid #eeeeee;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.cart-manage-text {
|
||
font-size: 16px;
|
||
color: #222222;
|
||
}
|
||
|
||
.cart-filter-row {
|
||
width: 100%;
|
||
height: 46px;
|
||
padding: 0 16px;
|
||
background-color: #ffffff;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-sizing: border-box;
|
||
border-bottom: 1px solid #f1f1f1;
|
||
}
|
||
|
||
.cart-filter-left {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
min-width: 0;
|
||
}
|
||
|
||
.filter-chip {
|
||
height: 32px;
|
||
padding: 0 14px;
|
||
margin-right: 12px;
|
||
border-radius: 8px;
|
||
background-color: #f6f6f6;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.filter-chip-active {
|
||
background-color: #fff2e8;
|
||
}
|
||
|
||
.filter-chip-icon {
|
||
font-size: 14px;
|
||
color: #ff5000;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.filter-chip-text {
|
||
font-size: 15px;
|
||
color: #222222;
|
||
}
|
||
|
||
.filter-chip-active .filter-chip-text {
|
||
color: #ff5000;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.filter-more-btn {
|
||
height: 32px;
|
||
padding: 0 4px 0 12px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.filter-more-text {
|
||
font-size: 15px;
|
||
color: #222222;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.filter-more-icon {
|
||
font-size: 18px;
|
||
color: #222222;
|
||
}
|
||
|
||
/* 内容区 */
|
||
.cart-content {
|
||
flex: 1;
|
||
/* 必须设置 height: 0 或 overflow: hidden 可以在 flex 容器中正确收缩 */
|
||
height: 0px;
|
||
width: 100%;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.select-all {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.select-all-text {
|
||
font-size: 14px;
|
||
color: #333;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.total-info {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: baseline;
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.total-text {
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
|
||
.total-price {
|
||
font-size: 18px;
|
||
color: #ff5000;
|
||
font-weight: bold;
|
||
margin-left: 4px;
|
||
}
|
||
|
||
.checkout-btn {
|
||
background-color: #ff5000;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 20px;
|
||
padding: 8px 15px;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
min-width: 100px;
|
||
}
|
||
|
||
.delete-btn {
|
||
background-color: #fff;
|
||
color: #ff5000;
|
||
border: 1px solid #ff5000;
|
||
border-radius: 20px;
|
||
padding: 8px 15px;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
min-width: 100px;
|
||
}
|
||
|
||
/* 空购物车 */
|
||
.empty-cart {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 60px 20px;
|
||
text-align: center;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 80px;
|
||
color: #ddd;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.empty-title {
|
||
font-size: 18px;
|
||
color: #666;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.empty-desc {
|
||
font-size: 14px;
|
||
color: #999;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.go-shopping-btn {
|
||
background-color: #ff5000;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 25px;
|
||
padding: 10px 40px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.guest-login-bar {
|
||
position: fixed;
|
||
left: 12px;
|
||
right: 12px;
|
||
bottom: calc(var(--window-bottom) + 12px);
|
||
height: 48px;
|
||
padding: 0 8px 0 10px;
|
||
border-radius: 24px;
|
||
background-color: rgba(45, 38, 42, 0.72);
|
||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
|
||
backdrop-filter: blur(10px);
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
z-index: 998;
|
||
}
|
||
|
||
.cart-guest-login-bar {
|
||
background-color: rgba(55, 41, 43, 0.76);
|
||
}
|
||
|
||
.guest-login-left {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.guest-login-avatar {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 16px;
|
||
margin-right: 8px;
|
||
background-color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
|
||
.guest-login-text {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-size: 12px;
|
||
color: #ffffff;
|
||
lines: 1;
|
||
}
|
||
|
||
.guest-login-btn {
|
||
height: 32px;
|
||
line-height: 32px;
|
||
padding: 0 14px;
|
||
border-radius: 16px;
|
||
background-color: #ff4d31;
|
||
color: #ffffff;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.guest-login-btn::after {
|
||
border: none;
|
||
}
|
||
|
||
/* 购物车商品列表 */
|
||
.cart-list {
|
||
background-color: transparent; /* 背景透明,因为每个店铺有自己的卡片 */
|
||
margin: 10px;
|
||
border-radius: 0;
|
||
overflow: visible;
|
||
}
|
||
|
||
.shop-group {
|
||
background-color: white;
|
||
border-radius: 12px;
|
||
margin-bottom: 12px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.service-cart-list {
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.service-group {
|
||
padding: 14px;
|
||
}
|
||
|
||
.service-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.service-shop-name {
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
color: #222;
|
||
}
|
||
|
||
.service-status {
|
||
font-size: 12px;
|
||
color: #ff5000;
|
||
background-color: #fff2e8;
|
||
padding: 4px 8px;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.service-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.service-image {
|
||
width: 76px;
|
||
height: 76px;
|
||
border-radius: 10px;
|
||
margin-right: 12px;
|
||
flex-shrink: 0;
|
||
background-color: #f3f3f3;
|
||
}
|
||
|
||
.service-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
}
|
||
|
||
.service-name {
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
color: #222;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.service-spec {
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.service-desc {
|
||
font-size: 12px;
|
||
color: #999;
|
||
line-height: 18px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.service-footer {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.service-price {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #ff5000;
|
||
}
|
||
|
||
.service-action {
|
||
font-size: 13px;
|
||
color: #666;
|
||
}
|
||
|
||
.shop-header {
|
||
display: flex;
|
||
flex-direction: row; /* 强制横向排列 */
|
||
align-items: center;
|
||
justify-content: flex-start; /* 靠左对齐 */
|
||
padding: 12px;
|
||
border-bottom: 1px solid #f5f5f5;
|
||
}
|
||
|
||
.shop-select {
|
||
width: 24px;
|
||
height: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 8px;
|
||
flex-shrink: 0; /* 防止被压缩 */
|
||
}
|
||
|
||
.shop-icon {
|
||
font-size: 16px;
|
||
margin-right: 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.shop-name {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #333;
|
||
margin-right: 4px;
|
||
/* 自适应宽度,但不超过剩余空间 */
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.shop-arrow {
|
||
font-size: 12px;
|
||
color: #999;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.cart-item {
|
||
display: flex;
|
||
flex-direction: row; /* 显式横向排列 */
|
||
padding: 12px; /* 减小内边距 */
|
||
border-bottom: 1px solid #f5f5f5;
|
||
align-items: center;
|
||
height: 100px; /* 固定高度节省空间 */
|
||
}
|
||
|
||
.cart-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.item-select {
|
||
width: 30px; /* 减小选择框区域 */
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.selected-icon {
|
||
width: 18px;
|
||
height: 18px;
|
||
background-color: #ff5000;
|
||
color: white;
|
||
border-radius: 9px;
|
||
text-align: center;
|
||
line-height: 18px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.unselected-icon {
|
||
width: 18px;
|
||
height: 18px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 9px;
|
||
}
|
||
|
||
.item-image {
|
||
width: 70px; /* 减小图片尺寸 */
|
||
height: 70px;
|
||
border-radius: 6px;
|
||
margin-right: 10px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.item-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
height: 70px; /* 与图片高度一致 */
|
||
overflow: hidden;
|
||
}
|
||
|
||
.info-top {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.item-name {
|
||
font-size: 14px; /* 稍微减小字体 */
|
||
color: #333;
|
||
margin-bottom: 2px;
|
||
/* display: -webkit-box; REMOVED */
|
||
/* -webkit-line-clamp: 1; REMOVED */
|
||
/* -webkit-box-orient: vertical; REMOVED */
|
||
overflow: hidden;
|
||
font-weight: bold;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.item-spec {
|
||
font-size: 12px;
|
||
color: #999;
|
||
margin-bottom: auto; /* 自动占据中间空间 */
|
||
}
|
||
|
||
.item-footer {
|
||
display: flex;
|
||
flex-direction: row; /* 显式设置横向排列 */
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%; /* 确保占满宽度 */
|
||
}
|
||
|
||
.item-price {
|
||
font-size: 16px;
|
||
color: #ff5000;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.quantity-control {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
background-color: #f5f5f5;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
height: 28px;
|
||
}
|
||
|
||
.quantity-btn {
|
||
width: 28px;
|
||
height: 28px;
|
||
text-align: center;
|
||
line-height: 28px;
|
||
font-size: 16px;
|
||
color: #333;
|
||
background-color: #eee;
|
||
}
|
||
|
||
.quantity-value {
|
||
min-width: 36px;
|
||
text-align: center;
|
||
font-size: 14px;
|
||
line-height: 28px;
|
||
color: #333;
|
||
}
|
||
|
||
/* 推荐商品 */
|
||
.recommend-section {
|
||
margin: 20px 10px;
|
||
background-color: white;
|
||
border-radius: 10px;
|
||
padding: 15px;
|
||
}
|
||
|
||
.section-header {
|
||
margin-bottom: 15px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.refresh-btn {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 4px 8px;
|
||
}
|
||
|
||
.refresh-icon {
|
||
font-size: 14px;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.refresh-text {
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
|
||
.recommend-tip {
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.recommend-list {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
justify-content: space-between;
|
||
/* grid-template-columns: repeat(2, 1fr); REMOVED */
|
||
/* gap: 12px; REMOVED */
|
||
}
|
||
|
||
.recommend-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
width: 48%; /* 替换 grid 1fr auto fit */
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.recommend-image-wrapper {
|
||
width: 100%;
|
||
padding-bottom: 100%;
|
||
position: relative;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.recommend-image {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.recommend-name {
|
||
font-size: 13px;
|
||
color: #333;
|
||
margin-bottom: 5px;
|
||
line-height: 1.4;
|
||
height: 36px;
|
||
overflow: hidden;
|
||
/* display: -webkit-box; REMOVED */
|
||
/* -webkit-line-clamp: 2; REMOVED */
|
||
/* -webkit-box-orient: vertical; REMOVED */
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.recommend-bottom {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding-right: 8px;
|
||
}
|
||
|
||
.recommend-price {
|
||
font-size: 15px;
|
||
color: #ff5000;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.recommend-add-btn {
|
||
width: 24px;
|
||
height: 24px;
|
||
background-color: #ff5000;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.recommend-add-icon {
|
||
color: white;
|
||
font-size: 16px;
|
||
line-height: 1;
|
||
font-weight: bold;
|
||
}
|
||
|
||
|
||
.recommend-load-more {
|
||
width: 100%;
|
||
height: 48px;
|
||
padding-top: 4px;
|
||
padding-bottom: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-direction: row;
|
||
justify-content: center;
|
||
}
|
||
|
||
.recommend-load-text {
|
||
font-size: 13px;
|
||
color: #999999;
|
||
}
|
||
|
||
/* 响应式布局优化 */
|
||
@media screen and (max-width: 414px) {
|
||
.recommend-item {
|
||
width: 48%;
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 415px) and (max-width: 768px) {
|
||
.recommend-item {
|
||
width: 48%;
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 769px) and (max-width: 1024px) {
|
||
.recommend-item {
|
||
width: 32%;
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 1025px) and (max-width: 1399px) {
|
||
.recommend-item {
|
||
width: 23%;
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 1400px) {
|
||
.recommend-item {
|
||
width: 18%;
|
||
}
|
||
}
|
||
|
||
/* 保留原有的媒体查询用于其他样式 */
|
||
@media screen and (min-width: 768px) {
|
||
.cart-list,
|
||
.recommend-section {
|
||
margin: 20px auto;
|
||
width: 95%; /* max-width -> width */
|
||
}
|
||
|
||
.recommend-list {
|
||
/* grid-template-columns: repeat(4, 1fr); REMOVED */
|
||
/* gap: 16px; REMOVED */
|
||
/* Flex 布局参数调整在下方 update */
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 1024px) {
|
||
/* 桌面端整体布局调整 */
|
||
.cart-content {
|
||
padding: 20px 40px;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.cart-list,
|
||
.recommend-section {
|
||
margin: 20px auto;
|
||
width: 96%; /* max-width -> width: percentage is safer */
|
||
max-width: 1200px;
|
||
}
|
||
|
||
/* 店铺分组在桌面端显示为网格布局 */
|
||
.shop-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: transparent;
|
||
box-shadow: none;
|
||
border-radius: 0;
|
||
overflow: visible;
|
||
}
|
||
|
||
.shop-header {
|
||
background: white;
|
||
border-radius: 12px;
|
||
margin-bottom: 12px;
|
||
padding: 16px 80px 16px 24px; /* 同步增加右侧内边距 */
|
||
}
|
||
|
||
/* 购物车商品列表转为列表布局 */
|
||
.cart-item {
|
||
background: white;
|
||
border-radius: 0;
|
||
padding: 15px 80px 15px 30px; /* 进一步增加右侧内边距 */
|
||
height: 80px; /* 固定高度 */
|
||
border-bottom: 1px solid #eee;
|
||
box-shadow: none;
|
||
display: flex;
|
||
flex-direction: row; /* 显式设置横向排列 */
|
||
align-items: center; /* 垂直居中 */
|
||
width: 100%;
|
||
}
|
||
|
||
.cart-item:hover {
|
||
background-color: #f9f9f9;
|
||
transform: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.item-image {
|
||
width: 50px;
|
||
height: 50px;
|
||
margin-right: 20px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.item-info {
|
||
flex: 1;
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: row; /* 信息区域横向排列 */
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
overflow: visible;
|
||
}
|
||
|
||
.info-top {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: row; /* 名称和规格横向排列 */
|
||
align-items: center;
|
||
margin-right: 20px;
|
||
height: 100%;
|
||
}
|
||
|
||
.item-name {
|
||
font-size: 14px;
|
||
width: 250px; /* 固定名称宽度 */
|
||
margin-right: 20px;
|
||
/* 限制行数 */
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.item-spec {
|
||
width: 150px;
|
||
margin-bottom: 0;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.item-footer {
|
||
width: auto;
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: flex-end;
|
||
align-items: center;
|
||
/* gap: 40px; REMOVED */
|
||
height: 100%;
|
||
}
|
||
|
||
.item-price {
|
||
width: 100px;
|
||
text-align: right;
|
||
margin-bottom: 0;
|
||
margin-right: 40px; /* Replace gap */
|
||
}
|
||
|
||
.quantity-control {
|
||
margin-left: 0;
|
||
display: flex;
|
||
flex-direction: row;
|
||
}
|
||
|
||
/* 推荐商品优化 */
|
||
.recommend-list {
|
||
/* grid-template-columns: repeat(5, 1fr); REMOVED */
|
||
/* gap: 20px; REMOVED */
|
||
}
|
||
|
||
.recommend-image-wrapper {
|
||
padding-bottom: 100%;
|
||
}
|
||
|
||
/* 底部结算栏优化 */
|
||
.cart-footer {
|
||
padding: 0 40px;
|
||
width: 100%; /* max-width -> width */
|
||
margin: 0 auto;
|
||
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||
}
|
||
|
||
.footer-content {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 1400px) {
|
||
.cart-list,
|
||
.recommend-section {
|
||
width: 1400px;
|
||
}
|
||
|
||
/* 大屏下购物车商品显示3列 - 移除,保持单列列表 */
|
||
/* .cart-list .shop-group > view:not(.shop-header) {
|
||
grid-template-columns: repeat(3, 1fr);
|
||
} */
|
||
|
||
.recommend-list {
|
||
/* grid-template-columns: repeat(6, 1fr); REMOVED */
|
||
}
|
||
|
||
.footer-content {
|
||
width: 1400px;
|
||
}
|
||
}
|
||
|
||
.fixed-cart-settlement-bar {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: var(--window-bottom);
|
||
z-index: 999;
|
||
background-color: #ffffff;
|
||
border-top: 1px solid #f1f1f1;
|
||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.settlement-inner {
|
||
height: 64px;
|
||
padding: 0 14px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.settlement-left {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.settlement-right {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.total-info {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: baseline;
|
||
margin-right: 12px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.total-text {
|
||
font-size: 14px;
|
||
color: #333333;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.total-price {
|
||
font-size: 20px;
|
||
color: #ff5000;
|
||
font-weight: 700;
|
||
margin-left: 3px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.checkout-btn {
|
||
height: 44px;
|
||
min-width: 118px;
|
||
padding: 0 20px;
|
||
background-color: #ff5000;
|
||
color: #ffffff;
|
||
border-radius: 24px;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
border: none;
|
||
line-height: 44px;
|
||
margin: 0;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.delete-btn {
|
||
height: 44px;
|
||
min-width: 108px;
|
||
padding: 0 20px;
|
||
background-color: #ff3b30;
|
||
color: #ffffff;
|
||
border-radius: 24px;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
border: none;
|
||
line-height: 44px;
|
||
margin: 0;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
@media screen and (max-width: 375px) {
|
||
.nav-search-entry {
|
||
margin-left: 10px;
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.nav-search-text {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.search-bar-row {
|
||
padding: 0 10px 10px 10px;
|
||
}
|
||
|
||
.cart-tabs-row,
|
||
.cart-filter-row {
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.cart-tab {
|
||
margin-right: 20px;
|
||
}
|
||
|
||
.cart-tab-text,
|
||
.cart-manage-text,
|
||
.filter-chip-text,
|
||
.filter-more-text {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.filter-chip {
|
||
padding: 0 10px;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.settlement-inner {
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.total-price {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.checkout-btn,
|
||
.delete-btn {
|
||
min-width: 96px;
|
||
padding: 0 14px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.select-all-text {
|
||
font-size: 13px;
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 768px) {
|
||
.cart-tabs-row,
|
||
.cart-filter-row {
|
||
padding: 0 24px;
|
||
}
|
||
|
||
.fixed-cart-settlement-bar {
|
||
padding: 0 20px;
|
||
}
|
||
|
||
.settlement-inner {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 0 20px;
|
||
}
|
||
|
||
.total-price {
|
||
font-size: 22px;
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 1400px) {
|
||
.settlement-inner {
|
||
max-width: 1400px;
|
||
}
|
||
}
|
||
|
||
.tabbar-safe-area {
|
||
height: 166px;
|
||
width: 100%;
|
||
background-color: transparent;
|
||
}
|
||
</style>
|