- 将获取 STS Token 的接口地址由 paopao-api 改为 guilixu-api - 更新图片上传接口地址为新的 guilixu-api 域名 - 修改用户推广页面中邀请码链接和二维码接口的域名 - 更改注册页微信登录接口请求的域名为 guilixu-api
276 lines
12 KiB
TypeScript
276 lines
12 KiB
TypeScript
import React, { useEffect, useState } from 'react'
|
|
import { View, Text, Image, ScrollView } from '@tarojs/components'
|
|
import Taro from '@tarojs/taro'
|
|
import { useCartContext } from '@/contexts/CartContext'
|
|
import { useUserContext } from '@/contexts/UserContext'
|
|
import { pageShopGoods } from '@/api/shop/shopGoods'
|
|
import type { ShopGoods } from '@/api/shop/shopGoods/model'
|
|
import EmptyState from '@/components/common/EmptyState'
|
|
import Loading from '@/components/common/Loading'
|
|
import { isGuest } from '@/utils/auth'
|
|
import { requireLogin } from '@/utils/login-guard'
|
|
|
|
definePageConfig({
|
|
navigationBarTitleText: '购物车',
|
|
})
|
|
|
|
const CartPage: React.FC = () => {
|
|
const { items, selectedCount, selectedPrice, updateQuantity, toggleSelect, selectAll, removeItem, refresh, loading, removeSelected, addItem } = useCartContext()
|
|
const { isLoggedIn } = useUserContext()
|
|
const [recommendGoods, setRecommendGoods] = useState<ShopGoods[]>([])
|
|
const [recommendPage, setRecommendPage] = useState(1)
|
|
const [refreshingRecommend, setRefreshingRecommend] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if (isLoggedIn) {
|
|
refresh()
|
|
fetchRecommendGoods(1)
|
|
}
|
|
}, [isLoggedIn])
|
|
|
|
const fetchRecommendGoods = async (page?: number) => {
|
|
const targetPage = page || recommendPage
|
|
try {
|
|
setRefreshingRecommend(true)
|
|
const res = await pageShopGoods({ page: targetPage, limit: 6 })
|
|
if (res && res.list && res.list.length > 0) {
|
|
setRecommendGoods(res.list || [])
|
|
setRecommendPage(targetPage + 1)
|
|
} else {
|
|
// 没有更多数据时回到第一页重新请求
|
|
if (targetPage > 1) {
|
|
const firstRes = await pageShopGoods({ page: 1, limit: 6 })
|
|
setRecommendGoods(firstRes?.list || [])
|
|
setRecommendPage(2)
|
|
} else {
|
|
setRecommendGoods([])
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('获取推荐商品失败', err)
|
|
} finally {
|
|
setRefreshingRecommend(false)
|
|
}
|
|
}
|
|
|
|
// 换一批
|
|
const handleRefreshRecommend = () => {
|
|
if (!refreshingRecommend) {
|
|
fetchRecommendGoods()
|
|
}
|
|
}
|
|
|
|
const handleCheckout = () => {
|
|
if (!requireLogin({ action: 'checkout', redirect: '/pages/shop/cart' })) return
|
|
if (selectedCount === 0) {
|
|
Taro.showToast({ title: '请选择商品', icon: 'none' })
|
|
return
|
|
}
|
|
Taro.navigateTo({ url: '/pages/shop/checkout' })
|
|
}
|
|
|
|
// 未登录状态:展示"购物车是空的"+ 推荐位商品(游客可正常浏览),
|
|
// 不再直接弹登录页,避免被审核判"拒绝服务"。
|
|
if (!isLoggedIn) {
|
|
return (
|
|
<View className='min-h-screen bg-gray-50 flex flex-col'>
|
|
<ScrollView scrollY className='flex-1'>
|
|
<View className='flex items-center justify-center py-16'>
|
|
<EmptyState
|
|
text='购物车是空的'
|
|
actionText='去登录后查看购物车'
|
|
onAction={() => requireLogin({ action: 'viewOrder', redirect: '/pages/shop/cart', silent: true })}
|
|
/>
|
|
</View>
|
|
{recommendGoods.length > 0 && (
|
|
<View className='p-3'>
|
|
<View className='flex items-center justify-between mb-2'>
|
|
<Text className='text-sm font-medium text-gray-700'>猜你喜欢</Text>
|
|
<Text className='text-xs text-gray-400' onClick={handleRefreshRecommend}>
|
|
{refreshingRecommend ? '加载中...' : '换一批'}
|
|
</Text>
|
|
</View>
|
|
<ScrollView scrollX className='whitespace-nowrap' style={{ width: '100%', height: '180px' }}>
|
|
<View className='flex gap-2' style={{ display: 'inline-flex' }}>
|
|
{recommendGoods.map(goods => (
|
|
<View
|
|
key={goods.goodsId}
|
|
className='bg-white rounded-lg p-2 inline-block'
|
|
style={{ width: '110px', flexShrink: 0 }}
|
|
onClick={() => Taro.navigateTo({ url: `/pages/shop/product-detail?id=${goods.goodsId}` })}
|
|
>
|
|
<Image
|
|
className='w-full rounded-md bg-gray-100'
|
|
src={goods.image || ''}
|
|
mode='aspectFill'
|
|
style={{ height: '90px' }}
|
|
/>
|
|
<Text className='text-xs text-gray-700 mt-1 block' style={{ width: '100px' }} ellipsizeMode='tail' numberOfLines={1}>
|
|
{goods.name || goods.goodsName || '商品名称'}
|
|
</Text>
|
|
<View className='flex items-center justify-between mt-1'>
|
|
<Text className='text-xs text-gray-400'>
|
|
登录后查看价格
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</ScrollView>
|
|
</View>
|
|
)}
|
|
</ScrollView>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<View className="flex flex-col min-h-screen">
|
|
{/* 主要内容区域 - 使用ScrollView实现滚动 */}
|
|
<ScrollView scrollY className="flex-1 bg-gray-50">
|
|
{items.length === 0 && !loading ? (
|
|
<View className="flex items-center justify-center py-20">
|
|
<EmptyState text="购物车是空的" actionText="去逛逛" onAction={() => Taro.switchTab({ url: '/pages/index/index' })} />
|
|
</View>
|
|
) : (
|
|
<View className="p-3">
|
|
{/* 全选 */}
|
|
<View className="flex items-center justify-between mb-3">
|
|
<View className="flex items-center gap-2">
|
|
<View
|
|
className="w-5 h-5 rounded-full border border-gray-300 flex items-center justify-center"
|
|
onClick={() => selectAll(!items.every(i => i.checked))}
|
|
style={{ backgroundColor: items.length > 0 && items.every(i => i.checked) ? '#0e932e' : 'transparent' }}
|
|
>
|
|
{items.length > 0 && items.every(i => i.checked) && <Text className="text-white text-xs">✓</Text>}
|
|
</View>
|
|
<Text className="text-sm text-gray-600">全选</Text>
|
|
</View>
|
|
{selectedCount > 0 && (
|
|
<View onClick={removeSelected}>
|
|
<Text className="text-xs text-gray-400">删除选中</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
|
|
{/* 商品列表 */}
|
|
{loading ? (
|
|
<Loading />
|
|
) : (
|
|
items.map((item) => (
|
|
<View key={`${item.goodsId}-${item.skuId}`} className="bg-white rounded-lg p-3 mb-2 flex gap-3">
|
|
<View
|
|
className="w-5 h-5 rounded-full border border-gray-300 flex items-center justify-center mt-2 flex-shrink-0"
|
|
onClick={() => toggleSelect(item.goodsId, item.skuId)}
|
|
style={{ backgroundColor: item.checked ? '#0e932e' : 'transparent' }}
|
|
>
|
|
{item.checked && <Text className="text-white text-xs">✓</Text>}
|
|
</View>
|
|
<Image
|
|
className="w-20 h-20 rounded-md bg-gray-100 flex-shrink-0"
|
|
src={item.goodsImage || item.product?.image || ''}
|
|
mode="aspectFill"
|
|
/>
|
|
<View className="flex-1 flex flex-col justify-between">
|
|
<Text className="text-sm text-gray-700 flex-1 line-clamp-2">
|
|
{item.goodsName || item.product?.name || item.product?.goodsName || '商品名称'}
|
|
</Text>
|
|
{item.skuSpec && (
|
|
<Text className="text-xs text-gray-400 mb-1">{item.skuSpec}</Text>
|
|
)}
|
|
<View className="flex justify-between items-center">
|
|
<Text className="text-sm font-bold text-red-500">
|
|
¥{item.skuPrice || item.sku?.price || item.product?.salePrice || item.product?.price || '0'}
|
|
</Text>
|
|
<View className="flex items-center gap-3">
|
|
<View className="w-6 h-6 rounded bg-gray-100 flex items-center justify-center" onClick={() => updateQuantity(item.goodsId, item.skuId, item.quantity - 1)}>
|
|
<Text className="text-gray-500 text-sm">-</Text>
|
|
</View>
|
|
<Text className="text-sm w-6 text-center">{item.quantity}</Text>
|
|
<View className="w-6 h-6 rounded bg-gray-100 flex items-center justify-center" onClick={() => updateQuantity(item.goodsId, item.skuId, item.quantity + 1)}>
|
|
<Text className="text-gray-500 text-sm">+</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
<View className="flex items-center ml-1 flex-shrink-0" onClick={() => removeItem(item.goodsId, item.skuId)}>
|
|
<Text className="text-gray-300 text-sm">✕</Text>
|
|
</View>
|
|
</View>
|
|
))
|
|
)}
|
|
|
|
{/* 推荐商品 */}
|
|
{recommendGoods.length > 0 && (
|
|
<View className="mt-3">
|
|
<View className="flex items-center justify-between mb-2">
|
|
<Text className="text-sm font-medium text-gray-700">猜你喜欢</Text>
|
|
<Text className="text-xs text-gray-400" onClick={handleRefreshRecommend}>
|
|
{refreshingRecommend ? '加载中...' : '换一批'}
|
|
</Text>
|
|
</View>
|
|
<ScrollView scrollX className="whitespace-nowrap" style={{ width: '100%', height: '180px' }}>
|
|
<View className="flex gap-2" style={{ display: 'inline-flex' }}>
|
|
{recommendGoods.map(goods => (
|
|
<View
|
|
key={goods.id}
|
|
className="bg-white rounded-lg p-2 inline-block"
|
|
style={{ width: '110px', flexShrink: 0 }}
|
|
onClick={() => Taro.navigateTo({ url: `/pages/shop/product-detail?id=${goods.goodsId}` })}
|
|
>
|
|
<Image
|
|
className="w-full rounded-md bg-gray-100"
|
|
src={goods.image || ''}
|
|
mode="aspectFill"
|
|
style={{ height: '90px' }}
|
|
/>
|
|
<Text className="text-xs text-gray-700 mt-1 block" style={{ width: '100px' }} ellipsizeMode="tail" numberOfLines={1}>
|
|
{goods.name || goods.title || '商品名称'}
|
|
</Text>
|
|
<View className="flex items-center justify-between mt-1">
|
|
<Text className="text-xs font-bold text-red-500">
|
|
¥{goods.salePrice || goods.price || '0'}
|
|
</Text>
|
|
<View
|
|
className="w-5 h-5 rounded-full flex items-center justify-center"
|
|
style={{ backgroundColor: '#0e932e' }}
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
addItem(goods, undefined, 1)
|
|
}}
|
|
>
|
|
<Text className="text-white text-xs">+</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</ScrollView>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)}
|
|
</ScrollView>
|
|
|
|
{/* 底部结算栏 */}
|
|
{items.length > 0 && (
|
|
<View className="flex items-center justify-between bg-white border-t border-gray-100 px-3 py-3">
|
|
<View className="flex items-center gap-2">
|
|
<Text className="text-sm text-gray-500">合计:</Text>
|
|
<Text className="text-lg font-bold text-red-500">¥{selectedPrice}</Text>
|
|
</View>
|
|
<View
|
|
className="px-6 py-2 rounded-full text-white text-sm font-medium"
|
|
style={{ backgroundColor: selectedCount > 0 ? '#0e932e' : '#ccc' }}
|
|
onClick={handleCheckout}
|
|
>
|
|
<Text>结算({selectedCount})</Text>
|
|
</View>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export default CartPage
|