首页推荐修改
This commit is contained in:
@@ -235,6 +235,17 @@
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
&__loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20rpx 0 8rpx;
|
||||
}
|
||||
|
||||
&__loading-text {
|
||||
font-size: 24rpx;
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
&__login-btn {
|
||||
margin-top: 24rpx;
|
||||
padding: 14rpx 60rpx;
|
||||
|
||||
+123
-54
@@ -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>
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { get } from '@/utils/request'
|
||||
import type { PaginatedData } from '@/types/api'
|
||||
import type { CartSummary } from '@/types/cart'
|
||||
import type { Product } from '@/types/product'
|
||||
|
||||
/** 特价推荐列表参数 */
|
||||
export interface SpecialListParams {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 特价推荐列表响应(分页 + 购物车悬浮球汇总)。
|
||||
* 行结构与 /mini/product/list 一致:商品基础字段 + price + cart_id/cart_quantity
|
||||
*/
|
||||
export interface SpecialListData extends PaginatedData<Product> {
|
||||
/**
|
||||
* 购物车悬浮球汇总(未登录返回零值结构;旧版本后端可能未返回,调用方判空)。
|
||||
* 接口文档示例为 count/quantity/amount 简写,与 /mini/home 等接口的 total_* 命名不一致,
|
||||
* 统一经 normalizeSpecialCart 转换后再写入 store
|
||||
*/
|
||||
cart?: CartSummary | { count: number; quantity: string; amount: string }
|
||||
}
|
||||
|
||||
/** 特价推荐商品列表(免登录;携带门店 token 时返回等级价与购物车字段):GET /mini/special/list */
|
||||
export function getSpecialListApi(params: SpecialListParams = {}) {
|
||||
return get<SpecialListData>('/mini/special/list', { data: params })
|
||||
}
|
||||
|
||||
/** 特价推荐响应附带的悬浮球汇总归一化(兼容 count/quantity/amount 与 total_* 两种命名) */
|
||||
export function normalizeSpecialCart(cart: SpecialListData['cart']): CartSummary | null {
|
||||
if (!cart) return null
|
||||
const raw = cart as Record<string, unknown>
|
||||
const count = raw.total_count ?? raw.count
|
||||
const quantity = raw.total_quantity ?? raw.quantity
|
||||
const amount = raw.total_amount ?? raw.amount
|
||||
if (count == null || quantity == null || amount == null) return null
|
||||
return {
|
||||
total_count: Number(count),
|
||||
total_quantity: String(quantity),
|
||||
total_amount: String(amount),
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ const LOGIN_PATH = '/pages/login/index'
|
||||
/** 默认请求超时(ms) */
|
||||
const DEFAULT_TIMEOUT = 15000
|
||||
/** 接口根地址(uploadFile 等原生请求同样使用) */
|
||||
// export const BASE_URL = "http://localhost:8000/index.php"
|
||||
export const BASE_URL = "https://purchase.henanklkj.com/index.php"
|
||||
export const BASE_URL = "http://localhost:8000/index.php"
|
||||
// export const BASE_URL = "https://purchase.henanklkj.com/index.php"
|
||||
|
||||
/**
|
||||
* HTTP 状态码 → 错误提示映射
|
||||
|
||||
Reference in New Issue
Block a user