完善下单逻辑及其ui展示,修复支付倒计时显示错误bug

This commit is contained in:
2026-05-25 15:35:41 +08:00
parent d25f80ccdd
commit cecb51a8e2
40 changed files with 13040 additions and 3217 deletions

View File

@@ -1,35 +1,35 @@
<template>
<template>
<view class="cart-search-page">
<view class="search-header" :style="{ paddingTop: statusBarHeight + 'px', paddingRight: searchHeaderRightPadding + 'px' }">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-icon">鈥?/text>
</view>
<view class="search-input-wrap">
<text class="search-icon">⌕</text>
<text class="search-icon">鈱?/text>
<input
class="search-input"
v-model="keyword"
placeholder="搜索购物车商品"
placeholder="鎼滅储璐墿杞﹀晢鍝?
confirm-type="search"
:focus="true"
@confirm="doSearch"
/>
<view v-if="keyword.length > 0" class="clear-keyword" @click="clearKeyword">
<text class="clear-keyword-text">×</text>
<text class="clear-keyword-text"></text>
</view>
</view>
<view class="search-btn" @click="doSearch">
<text class="search-btn-text">搜索</text>
<text class="search-btn-text">鎼滅储</text>
</view>
</view>
<scroll-view v-if="!hasSearched" class="search-content" :scroll-y="true" :show-scrollbar="false">
<view class="history-section">
<view class="section-header">
<text class="section-title">历史搜索</text>
<text class="clear-history" @click="clearSearchHistory">清空</text>
<text class="section-title">鍘嗗彶鎼滅储</text>
<text class="clear-history" @click="clearSearchHistory">娓呯┖</text>
</view>
<view v-if="searchHistory.length > 0" class="history-list">
@@ -44,13 +44,13 @@
</view>
<view v-else class="empty-history">
<text class="empty-history-text">暂无历史搜索</text>
<text class="empty-history-text">鏆傛棤鍘嗗彶鎼滅储</text>
</view>
</view>
<view class="discover-section">
<view class="section-header">
<text class="section-title">搜索发现</text>
<text class="section-title">鎼滅储鍙戠幇</text>
</view>
<view class="discover-grid">
@@ -66,10 +66,10 @@
</view>
</scroll-view>
<scroll-view v-else class="search-result-content" :scroll-y="true" :show-scrollbar="false">
<scroll-view v-else class="search-result-content" :scroll-y="true" :show-scrollbar="false" :lower-threshold="120" @scrolltolower="handleResultScrollToLower">
<view v-if="matchedCartItems.length > 0" class="cart-match-section">
<view class="result-section-title-wrap">
<text class="result-section-title">购物车内相关商品</text>
<text class="result-section-title">璐墿杞﹀唴鐩稿叧鍟嗗搧</text>
</view>
<view class="cart-result-list">
@@ -79,7 +79,7 @@
class="cart-result-card"
>
<view class="item-select" @click="toggleSelect(item.id)">
<text v-if="item.selected" class="selected-icon">✓</text>
<text v-if="item.selected" class="selected-icon">鉁?/text>
<text v-else class="unselected-icon"></text>
</view>
@@ -90,7 +90,7 @@
<text class="item-name" :lines="1">{{ item.name }}</text>
<text class="item-spec">{{ item.spec }}</text>
<view class="item-footer">
<text class="item-price">¥{{ item.price }}</text>
<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>
@@ -112,31 +112,17 @@
<view class="recommend-search-section">
<view class="recommend-title-wrap">
<view class="line"></view>
<text class="recommend-title">{{ matchedCartItems.length > 0 ? '为你搜索全站商品' : '为你搜索全部商品' }}</text>
<text class="recommend-title">{{ matchedCartItems.length > 0 ? '涓轰綘鎼滅储鍏ㄧ珯鍟嗗搧' : '涓轰綘鎼滅储鍏ㄩ儴鍟嗗搧' }}</text>
<view class="line"></view>
</view>
<view class="recommend-grid">
<view
v-for="product in recommendProducts"
:key="product.id"
class="recommend-card"
@click="goToProductDetail(product)"
>
<image class="recommend-image" :src="product.image" mode="aspectFill" />
<view class="recommend-info">
<text class="recommend-shop-tag">{{ product.shopName }}</text>
<text class="recommend-name" :lines="2">{{ product.name }}</text>
<text class="recommend-sales">{{ product.salesText }}</text>
<view class="recommend-price-row">
<text class="recommend-price">¥{{ product.price }}</text>
<view class="recommend-cart-btn" @click.stop="addRecommendToCart(product)">
<text class="recommend-cart-icon">🛒</text>
</view>
</view>
</view>
</view>
</view>
<GuessYouLike
title="猜你喜欢"
:pageSize="8"
:excludeProductIds="matchedProductIds"
:loadMoreKey="guessLoadMoreKey"
@productClick="goToRecommendProductDetail"
/>
<view class="bottom-safe-space"></view>
</view>
@@ -146,19 +132,19 @@
<view class="settlement-inner">
<view class="settlement-left" @click="toggleSelectAllInSearch">
<view class="select-circle" :class="{ 'select-circle-active': allSearchSelected }">
<text v-if="allSearchSelected" class="select-check">✓</text>
<text v-if="allSearchSelected" class="select-check">鉁?/text>
</view>
<text class="select-all-text">全选</text>
<text class="select-all-text">鍏ㄩ€?/text>
</view>
<view class="settlement-right">
<view class="total-info">
<text class="total-label">合计:</text>
<text class="total-price">¥{{ searchTotalPrice }}</text>
<text class="total-label">鍚堣:</text>
<text class="total-price">{{ searchTotalPrice }}</text>
</view>
<button class="checkout-btn" :class="{ 'checkout-btn-disabled': searchSelectedCount == 0 }" @click="goToCheckoutFromSearch">
去结算({{ searchSelectedCount }})
鍘荤粨绠?{{ searchSelectedCount }})
</button>
</view>
</view>
@@ -171,6 +157,7 @@ import { computed, ref } from 'vue'
import { onLoad } 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'
const CART_SEARCH_HISTORY_KEY = 'cart_search_history'
@@ -211,19 +198,19 @@ const keyword = ref<string>('')
const hasSearched = ref<boolean>(false)
const searchHistory = ref<Array<string>>([])
const searchDiscoverList = ref<Array<string>>([
'无人机',
'水杯',
'手机',
'天然泉水',
'按摩仪',
'摄像头',
'耳机',
'停车场设备',
'饮料',
'鏃犱汉鏈?,
'姘存澂',
'鎵嬫満',
'澶╃劧娉夋按',
'鎸夋懇浠?,
'鎽勫儚澶?,
'鑰虫満',
'鍋滆溅鍦鸿澶?,
'楗枡',
'iPhone'
])
const cartItems = ref<Array<LocalCartItem>>([])
const recommendProducts = ref<Array<ProductItem>>([])
const guessLoadMoreKey = ref<number>(0)
const isLoading = ref<boolean>(false)
const statusBarHeight = ref<number>(0)
const updatingItems = ref<Set<string>>(new Set())
@@ -233,7 +220,7 @@ const searchDiscoverWords = computed<Array<string>>(() => {
if (searchDiscoverList.value.length > 0) {
return searchDiscoverList.value
}
return ['无人机', '水杯', '手机', '天然泉水']
return ['鏃犱汉鏈?, '姘存澂', '鎵嬫満', '澶╃劧娉夋按']
})
const safeLower = (value: string): string => {
@@ -246,8 +233,8 @@ const matchedCartItems = computed<Array<LocalCartItem>>(() => {
return []
}
return cartItems.value.filter((item: LocalCartItem) => {
const title = safeLower(item.name)
return cartItems.value.filter((item: LocalCartItem) => {
const title = safeLower(item.name)
const name = safeLower(item.name)
const productName = safeLower(item.productName)
const skuName = safeLower(item.skuName)
@@ -256,17 +243,28 @@ const matchedCartItems = computed<Array<LocalCartItem>>(() => {
const merchantName = safeLower(item.merchantName)
const brandName = safeLower(item.brandName)
return title.indexOf(q) >= 0
|| name.indexOf(q) >= 0
|| productName.indexOf(q) >= 0
|| skuName.indexOf(q) >= 0
|| specName.indexOf(q) >= 0
|| shopName.indexOf(q) >= 0
|| merchantName.indexOf(q) >= 0
|| brandName.indexOf(q) >= 0
return title.indexOf(q) >= 0
|| name.indexOf(q) >= 0
|| productName.indexOf(q) >= 0
|| skuName.indexOf(q) >= 0
|| specName.indexOf(q) >= 0
|| shopName.indexOf(q) >= 0
|| merchantName.indexOf(q) >= 0
|| brandName.indexOf(q) >= 0
})
})
})
const matchedProductIds = computed<Array<string>>(() => {
const ids: Array<string> = []
for (let i = 0; i < matchedCartItems.value.length; i++) {
const item = matchedCartItems.value[i]
const productId = item.productId !== '' ? item.productId : item.id
if (productId !== '' && ids.indexOf(productId) < 0) {
ids.push(productId)
}
}
return ids
})
const searchSelectedItems = computed<Array<LocalCartItem>>(() => {
return matchedCartItems.value.filter((item: LocalCartItem) => item.selected == true)
})
@@ -291,9 +289,9 @@ const allSearchSelected = computed((): boolean => {
const noCartResultText = computed((): string => {
if (cartItems.value.length == 0) {
return '购物车为空,暂无相关商品'
return '璐墿杞︿负绌猴紝鏆傛棤鐩稿叧鍟嗗搧'
}
return '您的购物车里没有相关商品'
return '鎮ㄧ殑璐墿杞﹂噷娌℃湁鐩稿叧鍟嗗搧'
})
const searchHeaderRightPadding = computed((): number => {
@@ -326,7 +324,7 @@ const loadSearchHistory = () => {
const parsed = JSON.parse(cache) as Array<string>
searchHistory.value = parsed
} catch (e) {
console.error('解析搜索历史失败:', e)
console.error('瑙f瀽鎼滅储鍘嗗彶澶辫触:', e)
searchHistory.value = []
}
}
@@ -352,81 +350,6 @@ const clearSearchHistory = () => {
uni.removeStorageSync(CART_SEARCH_HISTORY_KEY)
}
const mockRecommendProducts = (q: string): Array<ProductItem> => {
const text = q.trim() == '' ? '热卖好物' : q
return [
{
id: 'mock-1',
name: text + ' 便携款',
price: 99,
image: '/static/images/default.png',
shopName: '平台精选',
salesText: '已售 200+',
merchantId: '',
skuId: ''
},
{
id: 'mock-2',
name: text + ' 升级版',
price: 159,
image: '/static/images/default.png',
shopName: '品牌旗舰',
salesText: '好评 98%',
merchantId: '',
skuId: ''
},
{
id: 'mock-3',
name: text + ' 热销套装',
price: 239,
image: '/static/images/default.png',
shopName: '今日推荐',
salesText: '月销 500+',
merchantId: '',
skuId: ''
},
{
id: 'mock-4',
name: text + ' 家用精选',
price: 79,
image: '/static/images/default.png',
shopName: '官方自营',
salesText: '已售 1200+',
merchantId: '',
skuId: ''
}
]
}
const loadRecommendProducts = async (q: string) => {
isLoading.value = true
try {
const result = await supabaseService.searchProducts(q, 1, 8, 'sales')
if (result.data.length > 0) {
recommendProducts.value = result.data.map((product: Product): ProductItem => {
const saleCount = product.sale_count ?? product.sales ?? 0
return {
id: product.id,
name: product.name,
price: product.base_price ?? product.market_price ?? 0,
image: product.main_image_url ?? product.image_url ?? '/static/images/default.png',
shopName: product.shop_name ?? '平台精选',
salesText: '已售 ' + saleCount + '+',
merchantId: product.merchant_id ?? '',
skuId: ''
}
})
} else {
recommendProducts.value = mockRecommendProducts(q)
}
} catch (e) {
console.error('加载推荐商品失败:', e)
recommendProducts.value = mockRecommendProducts(q)
} finally {
isLoading.value = false
}
}
const loadCartData = async () => {
isLoading.value = true
try {
@@ -438,7 +361,7 @@ const loadCartData = async () => {
memberDiscount = discountRaw as number
}
} catch (e) {
console.log('获取会员信息失败,使用默认折扣:', e)
console.log('鑾峰彇浼氬憳淇℃伅澶辫触锛屼娇鐢ㄩ粯璁ゆ姌鎵?', e)
}
const supabaseCartItems = await supabaseService.getCartItems()
@@ -449,18 +372,18 @@ const loadCartData = async () => {
memberPrice = Math.round(originalPrice * memberDiscount * 100) / 100
}
const productName = item.product_name ?? '未知商品'
const specName = item.product_specification ?? '标准规格'
const productName = item.product_name ?? '鏈煡鍟嗗搧'
const specName = item.product_specification ?? '鏍囧噯瑙勬牸'
return {
id: item.id,
shopId: item.shop_id ?? 'default_shop',
shopName: item.shop_name ?? '商城优选',
shopName: item.shop_name ?? '鍟嗗煄浼橀€?,
name: productName,
productName: productName,
skuName: specName,
specName: specName,
merchantName: item.shop_name ?? '商城优选',
merchantName: item.shop_name ?? '鍟嗗煄浼橀€?,
brandName: '',
price: originalPrice,
originalPrice: originalPrice,
@@ -475,7 +398,7 @@ const loadCartData = async () => {
}
})
} catch (e) {
console.error('加载购物车搜索数据失败:', e)
console.error('鍔犺浇璐墿杞︽悳绱㈡暟鎹け璐?', e)
cartItems.value = []
} finally {
isLoading.value = false
@@ -486,7 +409,7 @@ const doSearch = async () => {
const q = keyword.value.trim()
if (q == '') {
uni.showToast({
title: '请输入搜索关键词',
title: '璇疯緭鍏ユ悳绱㈠叧閿瘝',
icon: 'none'
})
return
@@ -494,7 +417,6 @@ const doSearch = async () => {
hasSearched.value = true
saveSearchHistory(q)
await loadRecommendProducts(q)
}
const useSearchWord = (word: string) => {
@@ -509,7 +431,7 @@ const goBack = () => {
const clearKeyword = () => {
keyword.value = ''
hasSearched.value = false
recommendProducts.value = []
guessLoadMoreKey.value = 0
}
const toggleSelect = async (itemId: string) => {
@@ -524,7 +446,7 @@ const toggleSelect = async (itemId: string) => {
if (!success) {
cartItems.value[index].selected = !newSelected
cartItems.value = [...cartItems.value]
uni.showToast({ title: '网络异常,请重试', icon: 'none' })
uni.showToast({ title: '缃戠粶寮傚父锛岃閲嶈瘯', icon: 'none' })
}
}
@@ -543,7 +465,7 @@ const increaseQuantity = async (itemId: string) => {
if (!success) {
cartItems.value[index].quantity = newQuantity - 1
cartItems.value = [...cartItems.value]
uni.showToast({ title: '更新失败', icon: 'none' })
uni.showToast({ title: '鏇存柊澶辫触', icon: 'none' })
}
}
@@ -553,7 +475,7 @@ const decreaseQuantity = async (itemId: string) => {
if (index == -1) return
if (cartItems.value[index].quantity <= 1) {
uni.showToast({ title: '最少保留1件可返回购物车删除', icon: 'none' })
uni.showToast({ title: '鏈€灏戜繚鐣?浠讹紝鍙繑鍥炶喘鐗╄溅鍒犻櫎', icon: 'none' })
return
}
@@ -567,7 +489,7 @@ const decreaseQuantity = async (itemId: string) => {
if (!success) {
cartItems.value[index].quantity = newQuantity + 1
cartItems.value = [...cartItems.value]
uni.showToast({ title: '更新失败', icon: 'none' })
uni.showToast({ title: '鏇存柊澶辫触', icon: 'none' })
}
}
@@ -587,14 +509,14 @@ const toggleSelectAllInSearch = async () => {
item.selected = !checked
})
cartItems.value = [...cartItems.value]
uni.showToast({ title: '操作失败', icon: 'none' })
uni.showToast({ title: '鎿嶄綔澶辫触', icon: 'none' })
}
}
const goToCheckoutFromSearch = () => {
if (searchSelectedCount.value == 0) {
uni.showToast({
title: '请选择商品',
title: '璇烽€夋嫨鍟嗗搧',
icon: 'none'
})
return
@@ -620,8 +542,8 @@ const goToCheckoutFromSearch = () => {
try {
uni.setStorageSync('checkout_items', JSON.stringify(selectedItems))
} catch (e) {
console.error('存储结算数据失败', e)
uni.showToast({ title: '系统异常,请重试', icon: 'none' })
console.error('瀛樺偍缁撶畻鏁版嵁澶辫触', e)
uni.showToast({ title: '绯荤粺寮傚父锛岃閲嶈瘯', icon: 'none' })
return
}
@@ -653,38 +575,17 @@ const goToProductDetail = (product: ProductItem) => {
uni.navigateTo({ url })
}
const addRecommendToCart = async (product: ProductItem) => {
const userId = supabaseService.getCurrentUserId()
if (userId == null || userId == '') {
goToLogin('/pages/main/cart-search/cart-search')
const goToRecommendProductDetail = (productId: string) => {
if (productId === '') {
return
}
uni.navigateTo({
url: '/pages/mall/consumer/product-detail?id=' + encodeURIComponent(productId) + '&productId=' + encodeURIComponent(productId)
})
}
uni.showLoading({ title: '添加中...' })
try {
const skus = await supabaseService.getProductSkus(product.id)
if (skus.length > 0) {
uni.hideLoading()
uni.showToast({ title: '请选择规格', icon: 'none' })
setTimeout(() => {
goToProductDetail(product)
}, 400)
return
}
const success = await supabaseService.addToCart(product.id, 1, product.skuId, product.merchantId)
uni.hideLoading()
if (success) {
uni.showToast({ title: '已添加到购物车', icon: 'success' })
loadCartData()
} else {
uni.showToast({ title: '添加失败', icon: 'none' })
}
} catch (e) {
console.error('推荐商品加入购物车失败:', e)
uni.hideLoading()
uni.showToast({ title: '添加失败', icon: 'none' })
}
const handleResultScrollToLower = () => {
guessLoadMoreKey.value = guessLoadMoreKey.value + 1
}
onLoad((options: UTSJSONObject) => {
@@ -698,7 +599,7 @@ onLoad((options: UTSJSONObject) => {
navBarRight.value = (systemInfo.screenWidth - menuButton.left) + 10
}
} catch (e) {
console.log('获取胶囊按钮信息失败:', e)
console.log('鑾峰彇鑳跺泭鎸夐挳淇℃伅澶辫触:', e)
navBarRight.value = 96
}
// #endif
@@ -1304,4 +1205,4 @@ onLoad((options: UTSJSONObject) => {
font-size: 14px;
}
}
</style>
</style>