From e97cc128ec64f2bac2b335906c0a40de76178edc Mon Sep 17 00:00:00 2001 From: liu <2302563948@qq.com> Date: Thu, 3 Sep 2026 19:37:42 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E9=A1=B5=E6=8E=A8=E8=8D=90=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/index/index.less | 11 +++ src/pages/index/index.tsx | 177 ++++++++++++++++++++++++++----------- src/services/special.ts | 43 +++++++++ src/utils/request.ts | 4 +- 4 files changed, 179 insertions(+), 56 deletions(-) create mode 100644 src/services/special.ts diff --git a/src/pages/index/index.less b/src/pages/index/index.less index bfda37f..f6bdc6f 100644 --- a/src/pages/index/index.less +++ b/src/pages/index/index.less @@ -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; diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx index 8372f42..f7d103d 100644 --- a/src/pages/index/index.tsx +++ b/src/pages/index/index.tsx @@ -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({ banners: [], navs: [], promos: [] }) - /** 推荐商品 */ - const [products, setProducts] = useState([]) + /** 特价推荐商品(后台「客户端配置 → 特价推荐」标记,价格为登录门店的等级价) */ + const [specials, setSpecials] = useState([]) + /** 特价推荐分页 */ + 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 ( {/* ========== 自定义顶部导航栏 ========== */} @@ -237,57 +288,75 @@ export default function IndexPage() { )} - {/* ========== 推荐商品 ========== */} + {/* ========== 特价推荐 ========== */} - 推荐商品 + 特价推荐 goProduct()}>查看更多 › - { products.length === 0 ? ( + { specials.length === 0 ? ( - 暂无商品 + 暂无特价商品 ) : ( - - {products.map(product => ( - goDetail(product.id)}> - - - {product.name} - - {formatSpec(product.spec, product.unit)}{' '} - {product.price !== null && <> - 单价:{formatRetailPrice(product.price, product.spec)} {product.price_unit} - } - - - {product.price !== null ? ( - ¥{product.price} - ) : ( - 登陆后查看价格 - )} - {/* 已加购展示行内加减器,否则展示快捷加购按钮 */} - {Number(product.cart_quantity ?? 0) > 0 ? ( - - ) : ( - handleQuickAdd(product, e)}> - - - )} + <> + + {specials.map(product => ( + goDetail(product.id)}> + + + {product.name} + + {formatSpec(product.spec, product.unit)}{' '} + {product.price !== null && <> + 单价:{formatRetailPrice(product.price, product.spec)} {product.price_unit} + } + + + {product.price !== null ? ( + ¥{product.price} + ) : ( + + 登录后查看价格 + + )} + {/* 已加购展示行内加减器,否则展示快捷加购按钮 */} + {Number(product.cart_quantity ?? 0) > 0 ? ( + + ) : ( + handleQuickAdd(product, e)}> + + + )} + + ))} + + {/* 加载更多状态 */} + {specialLoading && ( + + 加载中… - ))} - + )} + {!specialHasMore && specialPage > 1 && ( + + 已加载全部特价商品 + + )} + )} diff --git a/src/services/special.ts b/src/services/special.ts new file mode 100644 index 0000000..099ff05 --- /dev/null +++ b/src/services/special.ts @@ -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 { + /** + * 购物车悬浮球汇总(未登录返回零值结构;旧版本后端可能未返回,调用方判空)。 + * 接口文档示例为 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('/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 + 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), + } +} diff --git a/src/utils/request.ts b/src/utils/request.ts index df6beb4..1093a8a 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -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 状态码 → 错误提示映射