1307 lines
28 KiB
Plaintext
1307 lines
28 KiB
Plaintext
<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>
|
||
</view>
|
||
|
||
<view class="search-input-wrap">
|
||
<text class="search-icon">⌕</text>
|
||
<input
|
||
class="search-input"
|
||
v-model="keyword"
|
||
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>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="search-btn" @click="doSearch">
|
||
<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>
|
||
</view>
|
||
|
||
<view v-if="searchHistory.length > 0" class="history-list">
|
||
<view
|
||
v-for="item in searchHistory"
|
||
:key="item"
|
||
class="history-chip"
|
||
@click="useSearchWord(item)"
|
||
>
|
||
<text class="history-chip-text">{{ item }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-else class="empty-history">
|
||
<text class="empty-history-text">暂无历史搜索</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="discover-section">
|
||
<view class="section-header">
|
||
<text class="section-title">搜索发现</text>
|
||
</view>
|
||
|
||
<view class="discover-grid">
|
||
<view
|
||
v-for="item in searchDiscoverWords"
|
||
:key="item"
|
||
class="discover-item"
|
||
@click="useSearchWord(item)"
|
||
>
|
||
<text class="discover-text">{{ item }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<scroll-view v-else class="search-result-content" :scroll-y="true" :show-scrollbar="false">
|
||
<view v-if="matchedCartItems.length > 0" class="cart-match-section">
|
||
<view class="result-section-title-wrap">
|
||
<text class="result-section-title">购物车内相关商品</text>
|
||
</view>
|
||
|
||
<view class="cart-result-list">
|
||
<view
|
||
v-for="item in matchedCartItems"
|
||
:key="item.id"
|
||
class="cart-result-card"
|
||
>
|
||
<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="goToProductDetailFromCart(item)" />
|
||
|
||
<view class="result-item-info">
|
||
<text class="result-shop-name">{{ item.shopName }}</text>
|
||
<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>
|
||
<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-else class="no-cart-result">
|
||
<view class="warning-row">
|
||
<text class="warning-icon">!</text>
|
||
<text class="warning-text">{{ noCartResultText }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="recommend-search-section">
|
||
<view class="recommend-title-wrap">
|
||
<view class="line"></view>
|
||
<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>
|
||
|
||
<view class="bottom-safe-space"></view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view v-if="hasSearched" class="fixed-cart-settlement-bar">
|
||
<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>
|
||
</view>
|
||
<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>
|
||
</view>
|
||
|
||
<button class="checkout-btn" :class="{ 'checkout-btn-disabled': searchSelectedCount == 0 }" @click="goToCheckoutFromSearch">
|
||
去结算({{ searchSelectedCount }})
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
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'
|
||
|
||
const CART_SEARCH_HISTORY_KEY = 'cart_search_history'
|
||
|
||
type LocalCartItem = {
|
||
id: string
|
||
shopId: string
|
||
shopName: string
|
||
name: string
|
||
productName: string
|
||
skuName: string
|
||
specName: string
|
||
merchantName: string
|
||
brandName: string
|
||
price: number
|
||
originalPrice: number
|
||
memberPrice: number
|
||
image: string
|
||
spec: string
|
||
quantity: number
|
||
selected: boolean
|
||
productId: string
|
||
skuId: string
|
||
merchantId: string
|
||
}
|
||
|
||
type ProductItem = {
|
||
id: string
|
||
name: string
|
||
price: number
|
||
image: string
|
||
shopName: string
|
||
salesText: string
|
||
merchantId: string
|
||
skuId: string
|
||
}
|
||
|
||
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 isLoading = ref<boolean>(false)
|
||
const statusBarHeight = ref<number>(0)
|
||
const updatingItems = ref<Set<string>>(new Set())
|
||
const navBarRight = ref<number>(12)
|
||
|
||
const searchDiscoverWords = computed<Array<string>>(() => {
|
||
if (searchDiscoverList.value.length > 0) {
|
||
return searchDiscoverList.value
|
||
}
|
||
return ['无人机', '水杯', '手机', '天然泉水']
|
||
})
|
||
|
||
const safeLower = (value: string): string => {
|
||
return value.toLowerCase()
|
||
}
|
||
|
||
const matchedCartItems = computed<Array<LocalCartItem>>(() => {
|
||
const q = keyword.value.trim().toLowerCase()
|
||
if (q == '') {
|
||
return []
|
||
}
|
||
|
||
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)
|
||
const specName = safeLower(item.specName)
|
||
const shopName = safeLower(item.shopName)
|
||
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
|
||
})
|
||
})
|
||
|
||
const searchSelectedItems = computed<Array<LocalCartItem>>(() => {
|
||
return matchedCartItems.value.filter((item: LocalCartItem) => item.selected == true)
|
||
})
|
||
|
||
const searchSelectedCount = computed((): number => {
|
||
return searchSelectedItems.value.length
|
||
})
|
||
|
||
const searchTotalPrice = computed((): string => {
|
||
let total = 0
|
||
searchSelectedItems.value.forEach((item: LocalCartItem) => {
|
||
const finalPrice = item.memberPrice > 0 && item.memberPrice < item.price ? item.memberPrice : item.price
|
||
total += finalPrice * item.quantity
|
||
})
|
||
return total.toFixed(2)
|
||
})
|
||
|
||
const allSearchSelected = computed((): boolean => {
|
||
if (matchedCartItems.value.length == 0) return false
|
||
return matchedCartItems.value.every((item: LocalCartItem) => item.selected == true)
|
||
})
|
||
|
||
const noCartResultText = computed((): string => {
|
||
if (cartItems.value.length == 0) {
|
||
return '购物车为空,暂无相关商品'
|
||
}
|
||
return '您的购物车里没有相关商品'
|
||
})
|
||
|
||
const searchHeaderRightPadding = computed((): number => {
|
||
if (navBarRight.value > 12) {
|
||
return navBarRight.value
|
||
}
|
||
return 12
|
||
})
|
||
|
||
const getItemById = (itemId: string): LocalCartItem | null => {
|
||
const index = cartItems.value.findIndex((item: LocalCartItem) => item.id == itemId)
|
||
if (index == -1) return null
|
||
return cartItems.value[index]
|
||
}
|
||
|
||
const loadSearchHistory = () => {
|
||
const cache = uni.getStorageSync(CART_SEARCH_HISTORY_KEY)
|
||
if (cache == null || cache == '') {
|
||
searchHistory.value = []
|
||
return
|
||
}
|
||
|
||
if (Array.isArray(cache)) {
|
||
searchHistory.value = cache as Array<string>
|
||
return
|
||
}
|
||
|
||
if (typeof cache == 'string') {
|
||
try {
|
||
const parsed = JSON.parse(cache) as Array<string>
|
||
searchHistory.value = parsed
|
||
} catch (e) {
|
||
console.error('解析搜索历史失败:', e)
|
||
searchHistory.value = []
|
||
}
|
||
}
|
||
}
|
||
|
||
const saveSearchHistory = (word: string) => {
|
||
const text = word.trim()
|
||
if (text == '') return
|
||
|
||
let list = searchHistory.value.filter((item: string) => item != text)
|
||
list.unshift(text)
|
||
|
||
if (list.length > 10) {
|
||
list = list.slice(0, 10)
|
||
}
|
||
|
||
searchHistory.value = list
|
||
uni.setStorageSync(CART_SEARCH_HISTORY_KEY, JSON.stringify(list))
|
||
}
|
||
|
||
const clearSearchHistory = () => {
|
||
searchHistory.value = []
|
||
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 {
|
||
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)
|
||
}
|
||
|
||
const supabaseCartItems = await supabaseService.getCartItems()
|
||
cartItems.value = supabaseCartItems.map((item: SupabaseCartItem): LocalCartItem => {
|
||
const originalPrice = item.product_price ?? 0
|
||
let memberPrice = 0
|
||
if (memberDiscount > 0 && memberDiscount < 1 && originalPrice > 0) {
|
||
memberPrice = Math.round(originalPrice * memberDiscount * 100) / 100
|
||
}
|
||
|
||
const productName = item.product_name ?? '未知商品'
|
||
const specName = item.product_specification ?? '标准规格'
|
||
|
||
return {
|
||
id: item.id,
|
||
shopId: item.shop_id ?? 'default_shop',
|
||
shopName: item.shop_name ?? '商城优选',
|
||
name: productName,
|
||
productName: productName,
|
||
skuName: specName,
|
||
specName: specName,
|
||
merchantName: item.shop_name ?? '商城优选',
|
||
brandName: '',
|
||
price: originalPrice,
|
||
originalPrice: originalPrice,
|
||
memberPrice: memberPrice,
|
||
image: item.product_image ?? '/static/images/default.png',
|
||
spec: specName,
|
||
quantity: item.quantity ?? 1,
|
||
selected: item.selected ?? false,
|
||
productId: item.product_id ?? '',
|
||
skuId: item.sku_id ?? '',
|
||
merchantId: item.merchant_id ?? ''
|
||
}
|
||
})
|
||
} catch (e) {
|
||
console.error('加载购物车搜索数据失败:', e)
|
||
cartItems.value = []
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
}
|
||
|
||
const doSearch = async () => {
|
||
const q = keyword.value.trim()
|
||
if (q == '') {
|
||
uni.showToast({
|
||
title: '请输入搜索关键词',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
hasSearched.value = true
|
||
saveSearchHistory(q)
|
||
await loadRecommendProducts(q)
|
||
}
|
||
|
||
const useSearchWord = (word: string) => {
|
||
keyword.value = word
|
||
doSearch()
|
||
}
|
||
|
||
const goBack = () => {
|
||
uni.navigateBack()
|
||
}
|
||
|
||
const clearKeyword = () => {
|
||
keyword.value = ''
|
||
hasSearched.value = false
|
||
recommendProducts.value = []
|
||
}
|
||
|
||
const toggleSelect = async (itemId: string) => {
|
||
const index = cartItems.value.findIndex((item: LocalCartItem) => item.id == itemId)
|
||
if (index == -1) return
|
||
|
||
const newSelected = !cartItems.value[index].selected
|
||
cartItems.value[index].selected = newSelected
|
||
cartItems.value = [...cartItems.value]
|
||
|
||
const success = await supabaseService.updateCartItemSelection(itemId, newSelected)
|
||
if (!success) {
|
||
cartItems.value[index].selected = !newSelected
|
||
cartItems.value = [...cartItems.value]
|
||
uni.showToast({ title: '网络异常,请重试', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const increaseQuantity = async (itemId: string) => {
|
||
if (updatingItems.value.has(itemId)) return
|
||
const index = cartItems.value.findIndex((item: LocalCartItem) => item.id == itemId)
|
||
if (index == -1) return
|
||
|
||
updatingItems.value.add(itemId)
|
||
const newQuantity = cartItems.value[index].quantity + 1
|
||
cartItems.value[index].quantity = newQuantity
|
||
cartItems.value = [...cartItems.value]
|
||
const success = await supabaseService.updateCartItemQuantity(itemId, newQuantity)
|
||
updatingItems.value.delete(itemId)
|
||
|
||
if (!success) {
|
||
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: LocalCartItem) => item.id == itemId)
|
||
if (index == -1) return
|
||
|
||
if (cartItems.value[index].quantity <= 1) {
|
||
uni.showToast({ title: '最少保留1件,可返回购物车删除', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
updatingItems.value.add(itemId)
|
||
const newQuantity = cartItems.value[index].quantity - 1
|
||
cartItems.value[index].quantity = newQuantity
|
||
cartItems.value = [...cartItems.value]
|
||
const success = await supabaseService.updateCartItemQuantity(itemId, newQuantity)
|
||
updatingItems.value.delete(itemId)
|
||
|
||
if (!success) {
|
||
cartItems.value[index].quantity = newQuantity + 1
|
||
cartItems.value = [...cartItems.value]
|
||
uni.showToast({ title: '更新失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const toggleSelectAllInSearch = async () => {
|
||
const checked = !allSearchSelected.value
|
||
const ids: Array<string> = []
|
||
matchedCartItems.value.forEach((item: LocalCartItem) => {
|
||
item.selected = checked
|
||
ids.push(item.id)
|
||
})
|
||
cartItems.value = [...cartItems.value]
|
||
if (ids.length == 0) return
|
||
|
||
const success = await supabaseService.batchUpdateCartItemSelection(ids, checked)
|
||
if (!success) {
|
||
matchedCartItems.value.forEach((item: LocalCartItem) => {
|
||
item.selected = !checked
|
||
})
|
||
cartItems.value = [...cartItems.value]
|
||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const goToCheckoutFromSearch = () => {
|
||
if (searchSelectedCount.value == 0) {
|
||
uni.showToast({
|
||
title: '请选择商品',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
const selectedItems = searchSelectedItems.value.map((item: LocalCartItem) => {
|
||
return {
|
||
id: item.id,
|
||
product_id: item.productId,
|
||
sku_id: item.skuId,
|
||
product_name: item.name,
|
||
shop_id: item.shopId,
|
||
shop_name: item.shopName,
|
||
merchant_id: item.merchantId,
|
||
product_image: item.image,
|
||
sku_specifications: item.spec,
|
||
price: item.price,
|
||
quantity: item.quantity
|
||
}
|
||
})
|
||
|
||
uni.setStorageSync('checkout_type', 'cart')
|
||
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'
|
||
})
|
||
}
|
||
|
||
const goToProductDetailFromCart = (item: LocalCartItem) => {
|
||
const productId = item.productId == '' ? item.id : item.productId
|
||
let paramsArr: Array<string> = []
|
||
paramsArr.push('id=' + encodeURIComponent(productId))
|
||
paramsArr.push('productId=' + encodeURIComponent(productId))
|
||
paramsArr.push('price=' + item.price)
|
||
paramsArr.push('originalPrice=' + item.originalPrice)
|
||
paramsArr.push('name=' + encodeURIComponent(item.name))
|
||
paramsArr.push('image=' + encodeURIComponent(item.image))
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/product-detail?' + paramsArr.join('&')
|
||
})
|
||
}
|
||
|
||
const goToProductDetail = (product: ProductItem) => {
|
||
let url = '/pages/mall/consumer/product-detail?id=' + encodeURIComponent(product.id)
|
||
url += '&productId=' + encodeURIComponent(product.id)
|
||
url += '&price=' + product.price
|
||
url += '&name=' + encodeURIComponent(product.name)
|
||
url += '&image=' + encodeURIComponent(product.image)
|
||
uni.navigateTo({ url })
|
||
}
|
||
|
||
const addRecommendToCart = async (product: ProductItem) => {
|
||
const userId = supabaseService.getCurrentUserId()
|
||
if (userId == null || userId == '') {
|
||
goToLogin('/pages/main/cart-search/cart-search')
|
||
return
|
||
}
|
||
|
||
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' })
|
||
}
|
||
}
|
||
|
||
onLoad((options: UTSJSONObject) => {
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
statusBarHeight.value = systemInfo.statusBarHeight ?? 0
|
||
|
||
// #ifdef MP-WEIXIN
|
||
try {
|
||
const menuButton = uni.getMenuButtonBoundingClientRect()
|
||
if (menuButton != null) {
|
||
navBarRight.value = (systemInfo.screenWidth - menuButton.left) + 10
|
||
}
|
||
} catch (e) {
|
||
console.log('获取胶囊按钮信息失败:', e)
|
||
navBarRight.value = 96
|
||
}
|
||
// #endif
|
||
|
||
loadSearchHistory()
|
||
loadCartData()
|
||
|
||
const word = options.getString('keyword')
|
||
if (word != null && word != '') {
|
||
const decodedWord = decodeURIComponent(word)
|
||
keyword.value = decodedWord != null ? decodedWord : word
|
||
doSearch()
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style>
|
||
.cart-search-page {
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: #f5f5f5;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.search-header {
|
||
height: 56px;
|
||
padding: 0 12px;
|
||
background-color: #ffffff;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
box-sizing: content-box;
|
||
border-bottom: 1px solid #f1f1f1;
|
||
}
|
||
|
||
.back-btn {
|
||
width: 36px;
|
||
height: 40px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.back-icon {
|
||
font-size: 34px;
|
||
color: #222222;
|
||
line-height: 34px;
|
||
}
|
||
|
||
.search-input-wrap {
|
||
flex: 1;
|
||
height: 38px;
|
||
border-radius: 20px;
|
||
background-color: #f5f5f5;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 0 12px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.search-icon {
|
||
font-size: 15px;
|
||
color: #999999;
|
||
margin-right: 6px;
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
height: 38px;
|
||
font-size: 15px;
|
||
color: #222222;
|
||
background-color: transparent;
|
||
border: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
}
|
||
|
||
.clear-keyword {
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: 12px;
|
||
background-color: #dddddd;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.clear-keyword-text {
|
||
font-size: 16px;
|
||
color: #666666;
|
||
line-height: 16px;
|
||
}
|
||
|
||
.search-btn {
|
||
height: 36px;
|
||
padding: 0 14px;
|
||
margin-left: 8px;
|
||
border-radius: 18px;
|
||
background-color: #ff5000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.search-btn-text {
|
||
color: #ffffff;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.search-content,
|
||
.search-result-content {
|
||
flex: 1;
|
||
height: 0;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.history-section,
|
||
.discover-section {
|
||
padding: 18px 16px 0 16px;
|
||
background-color: #ffffff;
|
||
}
|
||
|
||
.discover-section {
|
||
margin-top: 12px;
|
||
padding-bottom: 18px;
|
||
}
|
||
|
||
.section-header {
|
||
height: 32px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 16px;
|
||
color: #222222;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.clear-history {
|
||
font-size: 13px;
|
||
color: #999999;
|
||
}
|
||
|
||
.history-list {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
padding-top: 8px;
|
||
}
|
||
|
||
.history-chip {
|
||
height: 30px;
|
||
padding: 0 14px;
|
||
margin-right: 10px;
|
||
margin-bottom: 10px;
|
||
border-radius: 15px;
|
||
background-color: #f5f5f5;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.history-chip-text {
|
||
font-size: 14px;
|
||
color: #333333;
|
||
}
|
||
|
||
.empty-history {
|
||
padding: 20px 0 12px 0;
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.empty-history-text {
|
||
font-size: 14px;
|
||
color: #999999;
|
||
}
|
||
|
||
.discover-grid {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
justify-content: space-between;
|
||
padding-top: 8px;
|
||
}
|
||
|
||
.discover-item {
|
||
width: 48%;
|
||
height: 44px;
|
||
margin-bottom: 8px;
|
||
border-radius: 6px;
|
||
background-color: #f7f7f7;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.discover-text {
|
||
font-size: 14px;
|
||
color: #333333;
|
||
}
|
||
|
||
.cart-match-section {
|
||
padding-top: 12px;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.result-section-title-wrap {
|
||
padding: 0 16px 10px 16px;
|
||
}
|
||
|
||
.result-section-title {
|
||
font-size: 16px;
|
||
color: #222222;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.cart-result-list {
|
||
padding: 0 12px;
|
||
}
|
||
|
||
.cart-result-card {
|
||
background-color: #ffffff;
|
||
border-radius: 12px;
|
||
padding: 12px;
|
||
margin-bottom: 12px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.result-item-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 76px;
|
||
justify-content: space-between;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.result-shop-name {
|
||
font-size: 12px;
|
||
color: #999999;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.item-select {
|
||
width: 30px;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.selected-icon {
|
||
width: 18px;
|
||
height: 18px;
|
||
background-color: #ff5000;
|
||
color: #ffffff;
|
||
border-radius: 9px;
|
||
text-align: center;
|
||
line-height: 18px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.unselected-icon {
|
||
width: 18px;
|
||
height: 18px;
|
||
border: 1px solid #dddddd;
|
||
border-radius: 9px;
|
||
}
|
||
|
||
.item-image {
|
||
width: 76px;
|
||
height: 76px;
|
||
border-radius: 8px;
|
||
margin-right: 10px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.item-name {
|
||
font-size: 14px;
|
||
color: #222222;
|
||
font-weight: 700;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.item-spec {
|
||
font-size: 12px;
|
||
color: #999999;
|
||
}
|
||
|
||
.item-footer {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
width: 100%;
|
||
}
|
||
|
||
.item-price {
|
||
font-size: 16px;
|
||
color: #ff5000;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.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: #333333;
|
||
background-color: #eeeeee;
|
||
}
|
||
|
||
.quantity-value {
|
||
min-width: 36px;
|
||
text-align: center;
|
||
font-size: 14px;
|
||
line-height: 28px;
|
||
color: #333333;
|
||
}
|
||
|
||
.no-cart-result {
|
||
padding: 54px 16px 28px 16px;
|
||
background-color: #ffffff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.warning-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.warning-icon {
|
||
width: 22px;
|
||
height: 22px;
|
||
border-radius: 11px;
|
||
background-color: #ff7a00;
|
||
color: #ffffff;
|
||
font-size: 16px;
|
||
text-align: center;
|
||
line-height: 22px;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.warning-text {
|
||
font-size: 17px;
|
||
color: #666666;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.recommend-search-section {
|
||
padding: 18px 12px 0 12px;
|
||
}
|
||
|
||
.recommend-title-wrap {
|
||
height: 36px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.line {
|
||
width: 70px;
|
||
height: 1px;
|
||
background-color: #dddddd;
|
||
}
|
||
|
||
.recommend-title {
|
||
font-size: 15px;
|
||
color: #999999;
|
||
margin: 0 12px;
|
||
}
|
||
|
||
.recommend-grid {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.recommend-card {
|
||
width: 49%;
|
||
margin-bottom: 12px;
|
||
border-radius: 12px;
|
||
background-color: #ffffff;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.recommend-image {
|
||
width: 100%;
|
||
height: 180px;
|
||
background-color: #eeeeee;
|
||
}
|
||
|
||
.recommend-info {
|
||
padding: 8px;
|
||
}
|
||
|
||
.recommend-shop-tag {
|
||
font-size: 11px;
|
||
color: #ff5000;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.recommend-name {
|
||
font-size: 15px;
|
||
color: #222222;
|
||
line-height: 21px;
|
||
height: 42px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.recommend-sales {
|
||
font-size: 12px;
|
||
color: #999999;
|
||
margin-top: 6px;
|
||
}
|
||
|
||
.recommend-price-row {
|
||
margin-top: 8px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.recommend-price {
|
||
font-size: 20px;
|
||
color: #ff5000;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.recommend-cart-btn {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 16px;
|
||
background-color: #fff2e8;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.recommend-cart-icon {
|
||
font-size: 16px;
|
||
line-height: 16px;
|
||
}
|
||
|
||
.fixed-cart-settlement-bar {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: #ffffff;
|
||
border-top: 1px solid #eeeeee;
|
||
z-index: 999;
|
||
padding-bottom: var(--window-bottom);
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.select-circle {
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 10px;
|
||
border: 1px solid #dddddd;
|
||
background-color: #ffffff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.select-circle-active {
|
||
background-color: #ff5000;
|
||
border-color: #ff5000;
|
||
}
|
||
|
||
.select-check {
|
||
font-size: 12px;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.select-all-text {
|
||
font-size: 14px;
|
||
color: #333333;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.total-label {
|
||
font-size: 14px;
|
||
color: #666666;
|
||
}
|
||
|
||
.total-price {
|
||
font-size: 20px;
|
||
color: #ff5000;
|
||
font-weight: 700;
|
||
margin-left: 3px;
|
||
}
|
||
|
||
.checkout-btn {
|
||
height: 44px;
|
||
min-width: 126px;
|
||
padding: 0 20px;
|
||
border-radius: 24px;
|
||
background-color: #ff5000;
|
||
color: #ffffff;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
line-height: 44px;
|
||
border: none;
|
||
margin: 0;
|
||
}
|
||
|
||
.checkout-btn-disabled {
|
||
background-color: #ffb6a0;
|
||
}
|
||
|
||
.bottom-safe-space {
|
||
height: 92px;
|
||
}
|
||
|
||
@media screen and (max-width: 375px) {
|
||
.search-header {
|
||
padding-left: 10px;
|
||
}
|
||
|
||
.search-btn {
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.recommend-image {
|
||
height: 160px;
|
||
}
|
||
|
||
.recommend-price,
|
||
.total-price {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.checkout-btn {
|
||
min-width: 110px;
|
||
padding: 0 14px;
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
</style> |