首页推荐修改

This commit is contained in:
liu
2026-09-03 19:37:42 +08:00
parent 9d893b4ed4
commit e97cc128ec
4 changed files with 179 additions and 56 deletions
+123 -54
View File
@@ -1,13 +1,14 @@
import { useCallback, useMemo, useState } from 'react'
import Taro, { useDidShow } from '@tarojs/taro'
import { useCallback, useMemo, useRef, useState } from 'react'
import Taro, { useDidShow, useReachBottom } from '@tarojs/taro'
import { View, Text, Image } from '@tarojs/components'
import { Grid, GridItem, Icon, Search, Swiper, SwiperItem } from '@antmjs/vantui'
import useCartStore from '@/stores/cart/useCartStore'
import { getHomeConfigApi } from '@/services/home'
import type { HomeConfig } from '@/services/home'
import { getProductListApi } from '@/services/product'
import { getSpecialListApi, normalizeSpecialCart } from '@/services/special'
import { getProductCover } from '@/types/product'
import type { Product, ProductCartPatch } from '@/types/product'
import { getToken } from '@/utils/request'
import CartBall from '@/components/CartBall'
import CartStepper from '@/components/CartStepper'
import {formatRetailPrice, formatSpec} from '@/utils/format'
@@ -17,6 +18,9 @@ import './index.less'
const PENDING_CATEGORY_KEY = 'product_category_id'
const PENDING_KEYWORD_KEY = 'product_keyword'
/** 特价推荐每页条数 */
const SPECIAL_PAGE_SIZE = 10
/** tabBar 页面路径(link 跳转需改用 switchTab */
const TAB_PATHS = [
'pages/index/index',
@@ -45,8 +49,17 @@ export default function IndexPage() {
/** 首页配置(轮播图 / 宫格导航 / 促销卡片) */
const [config, setConfig] = useState<HomeConfig>({ banners: [], navs: [], promos: [] })
/** 推荐商品 */
const [products, setProducts] = useState<Product[]>([])
/** 特价推荐商品(后台「客户端配置 → 特价推荐」标记,价格为登录门店的等级价) */
const [specials, setSpecials] = useState<Product[]>([])
/** 特价推荐分页 */
const [specialPage, setSpecialPage] = useState(1)
const [specialHasMore, setSpecialHasMore] = useState(false)
/** 加载更多中(首屏重置不展示,避免已渲染列表下方闪烁) */
const [specialLoading, setSpecialLoading] = useState(false)
/** 特价推荐请求序号(返回 tab 重置与上拉加载并发时,仅采用最后一次响应) */
const specialSeqRef = useRef(0)
/** 是否有「加载更多」请求进行中 */
const specialLoadingRef = useRef(false)
/** 搜索框输入 */
const [keyword, setKeyword] = useState('')
@@ -54,7 +67,13 @@ export default function IndexPage() {
useDidShow(() => {
loadHomeConfig()
loadRecommend()
loadSpecials(1, true)
})
/** 上拉加载更多特价推荐 */
useReachBottom(() => {
if (!specialHasMore) return
loadSpecials(specialPage + 1, false)
})
/** 首页配置聚合数据(响应附带悬浮球汇总) */
@@ -69,15 +88,37 @@ export default function IndexPage() {
}
}, [setSummary])
/** 推荐商品 */
const loadRecommend = useCallback(async () => {
try {
const res = await getProductListApi({ page: 1, pageSize: 10 })
setProducts(res.data.data)
} catch {
// 错误已由 request 层 toast
}
}, [])
/**
* 特价推荐商品(reset 时回到第一页整体替换)。
* 行结构与 /mini/product/list 一致;响应附带悬浮球汇总
*/
const loadSpecials = useCallback(
async (pageNum: number, reset: boolean) => {
if (!reset && specialLoadingRef.current) return
const seq = ++specialSeqRef.current
specialLoadingRef.current = true
if (!reset) setSpecialLoading(true)
try {
const res = await getSpecialListApi({ page: pageNum, pageSize: SPECIAL_PAGE_SIZE })
if (seq !== specialSeqRef.current) return // 已有更新的请求,丢弃本次响应
const { data, total, cart } = res.data
setSpecials(prev => (reset ? data : [...prev, ...data]))
setSpecialPage(pageNum)
setSpecialHasMore(pageNum * SPECIAL_PAGE_SIZE < total)
// 列表响应附带悬浮球汇总(旧版本后端可能未返回)
const summary = normalizeSpecialCart(cart)
if (summary) setSummary(summary)
} catch {
// 错误已由 request 层 toast
} finally {
if (seq === specialSeqRef.current) {
specialLoadingRef.current = false
setSpecialLoading(false)
}
}
},
[setSummary],
)
/**
* 后台配置的 link 统一跳转:
@@ -115,9 +156,9 @@ export default function IndexPage() {
goProduct(keyword.trim())
}, [goProduct, keyword])
/** 行内加减购确认后回写推荐商品项的购物车字段 */
/** 行内加减购确认后回写特价商品项的购物车字段 */
const handleRowSync = useCallback((productId: number, patch: ProductCartPatch) => {
setProducts(prev => prev.map(p => (p.id === productId ? { ...p, ...patch } : p)))
setSpecials(prev => prev.map(p => (p.id === productId ? { ...p, ...patch } : p)))
}, [])
/** 快捷加购(用返回的购物车行回写,卡片随即展示加减器) */
@@ -135,6 +176,16 @@ export default function IndexPage() {
[addItem, handleRowSync],
)
/** 无价格时点击:未登录引导登录,已登录但未设等级价提示原因 */
const handlePriceGuide = useCallback((e: any) => {
e.stopPropagation()
if (getToken()) {
Taro.showToast({ title: '该商品暂未设置等级价', icon: 'none' })
} else {
Taro.navigateTo({ url: '/pages/login/index' })
}
}, [])
return (
<View className='home-page'>
{/* ========== 自定义顶部导航栏 ========== */}
@@ -237,57 +288,75 @@ export default function IndexPage() {
</View>
)}
{/* ========== 推荐商品 ========== */}
{/* ========== 特价推荐 ========== */}
<View className='home-recommend'>
<View className='home-recommend__header'>
<View className='home-recommend__title-wrap'>
<View className='home-recommend__title-bar' />
<Text className='home-recommend__title'></Text>
<Text className='home-recommend__title'></Text>
</View>
<Text className='home-recommend__more' onClick={() => goProduct()}> </Text>
</View>
{ products.length === 0 ? (
{ specials.length === 0 ? (
<View className='home-recommend__empty'>
<Text className='home-recommend__empty-text'></Text>
<Text className='home-recommend__empty-text'></Text>
</View>
) : (
<View className='product-grid'>
{products.map(product => (
<View key={product.id} className='product-card' onClick={() => goDetail(product.id)}>
<Image
className='product-card__image'
src={getProductCover(product) || 'https://img.yzcdn.cn/vant/cat.jpeg'}
mode='aspectFill'
lazyLoad
/>
<View className='product-card__info'>
<Text className='product-card__name'>{product.name}</Text>
<View className='product-card__spec'>
{formatSpec(product.spec, product.unit)}{' '}
{product.price !== null && <>
{formatRetailPrice(product.price, product.spec)} {product.price_unit}
</>}
</View>
<View className='product-card__bottom'>
{product.price !== null ? (
<Text className='product-card__price'>{product.price}</Text>
) : (
<Text className='product-card__price product-card__price--none'></Text>
)}
{/* 已加购展示行内加减器,否则展示快捷加购按钮 */}
{Number(product.cart_quantity ?? 0) > 0 ? (
<CartStepper product={product} onSync={handleRowSync} />
) : (
<View className='product-card__add' onClick={e => handleQuickAdd(product, e)}>
<Text className='product-card__add-icon'></Text>
</View>
)}
<>
<View className='product-grid'>
{specials.map(product => (
<View key={product.id} className='product-card' onClick={() => goDetail(product.id)}>
<Image
className='product-card__image'
src={getProductCover(product) || 'https://img.yzcdn.cn/vant/cat.jpeg'}
mode='aspectFill'
lazyLoad
/>
<View className='product-card__info'>
<Text className='product-card__name'>{product.name}</Text>
<View className='product-card__spec'>
{formatSpec(product.spec, product.unit)}{' '}
{product.price !== null && <>
{formatRetailPrice(product.price, product.spec)} {product.price_unit}
</>}
</View>
<View className='product-card__bottom'>
{product.price !== null ? (
<Text className='product-card__price'>{product.price}</Text>
) : (
<Text
className='product-card__price product-card__price--none'
onClick={handlePriceGuide}
>
</Text>
)}
{/* 已加购展示行内加减器,否则展示快捷加购按钮 */}
{Number(product.cart_quantity ?? 0) > 0 ? (
<CartStepper product={product} onSync={handleRowSync} />
) : (
<View className='product-card__add' onClick={e => handleQuickAdd(product, e)}>
<Text className='product-card__add-icon'></Text>
</View>
)}
</View>
</View>
</View>
))}
</View>
{/* 加载更多状态 */}
{specialLoading && (
<View className='home-recommend__loading'>
<Text className='home-recommend__loading-text'></Text>
</View>
))}
</View>
)}
{!specialHasMore && specialPage > 1 && (
<View className='home-recommend__loading'>
<Text className='home-recommend__loading-text'></Text>
</View>
)}
</>
)}
</View>