1870 lines
46 KiB
Plaintext
1870 lines
46 KiB
Plaintext
<!-- pages/main/cart.uvue -->
|
||
<template>
|
||
<view class="cart-page">
|
||
<!-- 智能顶部导航栏 - 与消息页保持一致 -->
|
||
<view class="smart-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||
<view class="nav-container" :style="{ paddingRight: (navBarRight > 0 ? navBarRight : 16) + 'px' }">
|
||
<text class="nav-title">购物车</text>
|
||
<view class="nav-actions">
|
||
<view class="action-btn" @click="toggleManageMode">
|
||
<text class="action-icon">{{ isManageMode ? '✓' : '⚙️' }}</text>
|
||
<text class="action-text">{{ isManageMode ? '完成' : '管理' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 导航栏占位符 - 需要包含statusBarHeight + 导航栏高度44px -->
|
||
<view class="navbar-placeholder" :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
|
||
|
||
<!-- 购物车内容 -->
|
||
<scroll-view
|
||
:scroll-y="true"
|
||
class="cart-content"
|
||
:show-scrollbar="false"
|
||
:enhanced="true"
|
||
:bounces="true"
|
||
>
|
||
<!-- 空购物车 -->
|
||
<view v-if="!loading && cartItems.length === 0" class="empty-cart">
|
||
<text class="empty-icon">🛒</text>
|
||
<text class="empty-title">购物车是空的</text>
|
||
<text class="empty-desc">快去挑选喜欢的商品吧</text>
|
||
<button class="go-shopping-btn" @click="goShopping">去逛逛</button>
|
||
</view>
|
||
|
||
<!-- 购物车商品列表 -->
|
||
<view v-else class="cart-list">
|
||
<view
|
||
v-for="group in cartGroups"
|
||
: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>
|
||
|
||
<!-- 购物车操作栏 (移至推荐商品上方) -->
|
||
<view v-if="cartItems.length > 0" class="cart-action-bar">
|
||
<view class="action-bar-content">
|
||
<view class="action-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="action-right">
|
||
<view v-if="!isManageMode" class="total-info">
|
||
<text class="total-text">合计:</text>
|
||
<text class="total-price">¥{{ totalPrice }}</text>
|
||
<text v-if="parseFloat(memberSavedAmount) > 0" class="member-saved">会员已省¥{{ memberSavedAmount }}</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="recommendProducts.length > 0" class="recommend-section">
|
||
<view class="section-header">
|
||
<text class="section-title">猜你喜欢</text>
|
||
<view class="refresh-btn" @click="refreshRecommend">
|
||
<text class="refresh-icon">🔄</text>
|
||
<text class="refresh-text">换一批</text>
|
||
</view>
|
||
</view>
|
||
<view class="recommend-list">
|
||
<view
|
||
v-for="product in recommendProducts"
|
||
:key="product.id"
|
||
class="recommend-item"
|
||
@click="navigateToProduct(product)"
|
||
>
|
||
<view class="recommend-image-wrapper">
|
||
<image
|
||
class="recommend-image"
|
||
:src="product.image"
|
||
mode="aspectFill"
|
||
/>
|
||
</view>
|
||
<text class="recommend-name" :lines="2">{{ product.name }}</text>
|
||
<view class="recommend-bottom">
|
||
<text class="recommend-price">¥{{ product.price }}</text>
|
||
<view class="recommend-add-btn" @click.stop="addToCart(product)">
|
||
<text class="recommend-add-icon">+</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 底部占位符:确保内容不被原生 TabBar 遮挡 -->
|
||
<view class="tabbar-safe-area"></view>
|
||
</scroll-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'
|
||
|
||
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 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 loading = 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
|
||
|
||
// 小程序胶囊按钮信息类型
|
||
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 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 initPage = () => {
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
statusBarHeight.value = systemInfo.statusBarHeight ?? 0
|
||
|
||
// 获取小程序胶囊按钮信息
|
||
// #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 updateRecommendList = (recommends: Product[]) => {
|
||
recommendProducts.value = recommends.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-product.png',
|
||
skuId: '',
|
||
merchant_id: p.merchant_id ?? ''
|
||
}
|
||
})
|
||
}
|
||
|
||
const refreshRecommend = async () => {
|
||
try {
|
||
// 1. 模拟市面加载感,锁定按钮防止连续快速点击
|
||
if (loading.value) return
|
||
loading.value = true
|
||
|
||
uni.showLoading({
|
||
title: '正在挑选...',
|
||
mask: true
|
||
})
|
||
|
||
// 2. 模拟市面“随机性”逻辑:
|
||
// 淘宝京东不会按顺序翻页,而是跳跃选取页码,并打乱排序规则
|
||
const maxOffsetPages = 20 // 假设数据库中至少有 20 页热推商品
|
||
const sorts = ['sales', 'price_asc', 'rating']
|
||
|
||
// 随机页码 + 随机排序 = 每次点击都有新发现
|
||
const nextRandomPage = Math.floor(Math.random() * maxOffsetPages) + 1
|
||
const randomSort = sorts[Math.floor(Math.random() * sorts.length)]
|
||
|
||
console.log(`[refreshRecommend] 换一批: 随机页=${nextRandomPage}, 随机排=${randomSort}`)
|
||
|
||
const hotResp = await supabaseService.searchProducts('', nextRandomPage, 6, randomSort)
|
||
let recommends = hotResp.data
|
||
|
||
// 3. 兜底逻辑:如果随机到的页码没数据,回退到第 1 页
|
||
if (recommends.length === 0) {
|
||
const fallbackResp = await supabaseService.searchProducts('', 1, 6, 'sales')
|
||
recommends = fallbackResp.data
|
||
}
|
||
|
||
// 4. 前端打乱 (Shuffle):即使是同一页数据,乱序排布也会增加“新鲜感”
|
||
if (recommends.length > 0) {
|
||
recommends.sort(() => Math.random() - 0.5)
|
||
updateRecommendList(recommends)
|
||
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '已为你换一批好物',
|
||
icon: 'none',
|
||
duration: 1000
|
||
})
|
||
} else {
|
||
uni.hideLoading()
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
console.error('刷新推荐失败:', error)
|
||
uni.showToast({ title: '加载失败,请重试', icon: 'none' })
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 加载数据
|
||
const loadCartData = async () => {
|
||
loading.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-product.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
|
||
|
||
// 加载推荐商品(优先获取推荐位商品,如果没有则通过搜索获取热销商品)
|
||
let recommends = await supabaseService.getRecommendedProducts(6)
|
||
|
||
// 如果没有设置推荐商品,则获取热销商品作为补充
|
||
if (recommends.length === 0) {
|
||
const hotResp = await supabaseService.searchProducts('', 1, 6, 'sales')
|
||
recommends = hotResp.data
|
||
}
|
||
|
||
if (recommends.length > 0) {
|
||
recommendProducts.value = recommends.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-product.png',
|
||
skuId: '',
|
||
merchant_id: p.merchant_id ?? ''
|
||
}
|
||
})
|
||
} else {
|
||
recommendProducts.value = []
|
||
}
|
||
} catch (error) {
|
||
console.error('加载购物车数据失败:', error)
|
||
cartItems.value = []
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
onShow(() => {
|
||
loadCartData()
|
||
})
|
||
|
||
// 商品操作 - 更新选中状态到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) => {
|
||
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-product.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 {
|
||
flex: 1; /* 使用 Flex 撑满 */
|
||
height: 100%; /* 兼容性考虑,部分环境需要 */
|
||
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-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.action-btn {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
padding: 4px 12px;
|
||
border-radius: 20px;
|
||
/* cursor: pointer; REMOVED */
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.action-btn:hover {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.action-icon {
|
||
font-size: 14px;
|
||
margin-right: 4px;
|
||
color: white;
|
||
}
|
||
|
||
.action-text {
|
||
font-size: 12px;
|
||
color: white;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 导航栏占位符 */
|
||
.navbar-placeholder {
|
||
width: 100%;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* 内容区 */
|
||
.cart-content {
|
||
flex: 1;
|
||
/* 必须设置 height: 0 或 overflow: hidden 可以在 flex 容器中正确收缩 */
|
||
height: 0px;
|
||
width: 100%;
|
||
padding-bottom: 20px; /* 减小内边距,因为结算栏已在内容流中 */
|
||
}
|
||
|
||
/* 购物车操作栏 (移至推荐商品上方) */
|
||
.cart-action-bar {
|
||
background-color: white;
|
||
margin: 10px;
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||
z-index: 10;
|
||
}
|
||
|
||
.action-bar-content {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 12px 15px;
|
||
}
|
||
|
||
.action-left {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.action-right {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
/* 购物车商品列表 */
|
||
.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;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
/* 响应式布局优化 */
|
||
@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;
|
||
}
|
||
}
|
||
|
||
/* 购物车操作栏样式 - 自适应横向排列 */
|
||
.cart-action-bar {
|
||
background-color: white;
|
||
margin: 10px;
|
||
border-radius: 12px;
|
||
padding: 10px 15px; /* 减小内边距 */
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
}
|
||
|
||
.action-bar-content {
|
||
display: flex;
|
||
flex-direction: row; /* 强制横向 */
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
width: 100%;
|
||
/* gap: 20px; REMOVED from .action-bar-content usually in desktop */
|
||
}
|
||
|
||
.action-left {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.action-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: center;
|
||
margin-right: 10px;
|
||
flex-shrink: 1; /* 允许压缩 */
|
||
overflow: hidden;
|
||
}
|
||
|
||
.total-text {
|
||
font-size: 14px;
|
||
color: #333;
|
||
margin-right: 2px;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.total-price {
|
||
font-size: 16px;
|
||
color: #ff5000;
|
||
font-weight: bold;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* 结算按钮 */
|
||
.checkout-btn, .delete-btn {
|
||
background-color: #ff5000;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 25px;
|
||
padding: 6px 16px; /* 减小按钮内边距 */
|
||
font-size: 14px;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
margin: 0; /* 移除可能的margin */
|
||
}
|
||
|
||
.delete-btn {
|
||
background-color: #ff3b30;
|
||
padding: 6px 20px;
|
||
}
|
||
|
||
/* 全选区域 */
|
||
.select-all {
|
||
display: flex;
|
||
flex-direction: row; /* 强制横向 */
|
||
align-items: center;
|
||
}
|
||
|
||
.select-all-text {
|
||
margin-left: 8px;
|
||
font-size: 14px;
|
||
color: #333;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* 响应式调整 */
|
||
/* 手机端小屏幕优化 */
|
||
@media screen and (max-width: 375px) {
|
||
.cart-action-bar {
|
||
padding: 10px;
|
||
margin: 10px 5px; /* 减小外边距增加可用宽度 */
|
||
}
|
||
|
||
.total-text {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.total-price {
|
||
font-size: 15px;
|
||
}
|
||
|
||
.checkout-btn, .delete-btn {
|
||
padding: 6px 12px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.select-all-text {
|
||
font-size: 13px;
|
||
margin-left: 4px;
|
||
}
|
||
}
|
||
|
||
/* 平板端优化 */
|
||
@media screen and (min-width: 768px) {
|
||
.cart-action-bar {
|
||
margin: 20px auto;
|
||
width: 95%; /* max-width -> width */
|
||
padding: 20px;
|
||
}
|
||
|
||
.action-bar-content {
|
||
/* gap: 20px; REMOVED */
|
||
}
|
||
|
||
.total-price {
|
||
font-size: 20px;
|
||
}
|
||
|
||
.checkout-btn, .delete-btn {
|
||
padding: 10px 30px;
|
||
font-size: 16px;
|
||
margin-left: 20px; /* Replace gap */
|
||
}
|
||
}
|
||
|
||
/* 桌面端优化 */
|
||
@media screen and (min-width: 1024px) {
|
||
.cart-action-bar {
|
||
width: 1200px; /* max-width -> width */
|
||
padding: 20px 30px;
|
||
}
|
||
|
||
.action-bar-content {
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.total-info {
|
||
margin-right: 30px;
|
||
}
|
||
|
||
.total-text {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.total-price {
|
||
font-size: 22px;
|
||
}
|
||
|
||
.checkout-btn, .delete-btn {
|
||
padding: 12px 40px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.select-all-text {
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
/* 大屏幕优化 */
|
||
@media screen and (min-width: 1400px) {
|
||
.cart-action-bar {
|
||
width: 1400px; /* max-width -> width */
|
||
}
|
||
}
|
||
|
||
.tabbar-safe-area {
|
||
height: 100px;
|
||
width: 100%;
|
||
background-color: transparent;
|
||
}
|
||
</style>
|
||
|