From 49a5c057db2005d41f22b28892aeff075a23d6f6 Mon Sep 17 00:00:00 2001 From: jacy <18049821889@163.com> Date: Sun, 2 Aug 2026 17:54:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=E5=95=86=E5=93=81?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E4=B8=8A=E4=B8=8B=E6=BB=9A=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/mini-user/src/pages/home/index.tsx | 197 +++++++++++++++++------- apps/mini-user/src/styles/home.css | 26 ++++ 2 files changed, 166 insertions(+), 57 deletions(-) diff --git a/apps/mini-user/src/pages/home/index.tsx b/apps/mini-user/src/pages/home/index.tsx index 1b3409c..a6274be 100644 --- a/apps/mini-user/src/pages/home/index.tsx +++ b/apps/mini-user/src/pages/home/index.tsx @@ -1,6 +1,12 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { View, Text, Image, Swiper, SwiperItem } from '@tarojs/components'; -import Taro, { useDidShow, usePullDownRefresh, useShareAppMessage, useShareTimeline } from '@tarojs/taro'; +import Taro, { + useDidShow, + usePageScroll, + usePullDownRefresh, + useShareAppMessage, + useShareTimeline, +} from '@tarojs/taro'; import PageShell from '../../components/PageShell'; import TabMainHeader from '../../components/TabMainHeader'; import CouponBadge from '../../components/CouponBadge'; @@ -42,13 +48,25 @@ const AROMA_TABS = [ { key: 'NONGXIANG', label: '浓香型' }, ] as const; +type AromaKey = (typeof AROMA_TABS)[number]['key']; + +/** sticky 香型导航高度(与 CSS 大致一致),锚点滚动时预留 */ +const AROMA_NAV_OFFSET_PX = 44; + +function aromaSectionId(key: AromaKey) { + return `aroma-section-${key}`; +} + export default function HomePage() { - const [tab, setTab] = useState('QINGXIANG'); + const [activeAroma, setActiveAroma] = useState('QINGXIANG'); const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); const [displayCity, setDisplayCity] = useState('郑州市'); const [cityCode, setCityCode] = useState('410100'); const [miniHome, setMiniHome] = useState({ banners: [], footerUrl: null }); + const scrollingToRef = useRef(null); + const scrollLockTimerRef = useRef | null>(null); + const lastScrollSyncAtRef = useRef(0); const loadMiniHome = useCallback(() => { return request<{ miniHome?: MiniHomeConfig }>('/common/client-config') @@ -126,7 +144,19 @@ export default function HomePage() { Taro.navigateTo({ url: returnPath }); } - const filtered = products.filter((p) => p.aromaType === tab); + const productsByAroma = useMemo(() => { + const map: Record = { + QINGXIANG: [], + JIANGXIANG: [], + NONGXIANG: [], + }; + for (const p of products) { + const key = p.aromaType as AromaKey; + if (key in map) map[key].push(p); + } + return map; + }, [products]); + const banners = miniHome.banners; const footerUrl = miniHome.footerUrl; @@ -147,6 +177,100 @@ export default function HomePage() { imageUrl: sharePayload.imgUrl, })); + function scrollToAroma(key: AromaKey) { + setActiveAroma(key); + scrollingToRef.current = key; + if (scrollLockTimerRef.current) clearTimeout(scrollLockTimerRef.current); + scrollLockTimerRef.current = setTimeout(() => { + scrollingToRef.current = null; + }, 450); + + const query = Taro.createSelectorQuery(); + query.select(`#${aromaSectionId(key)}`).boundingClientRect(); + query.selectViewport().scrollOffset(); + query.exec((res) => { + const rect = res?.[0] as { top?: number } | undefined; + const viewport = res?.[1] as { scrollTop?: number } | undefined; + if (rect?.top == null || viewport?.scrollTop == null) return; + const scrollTop = Math.max(0, viewport.scrollTop + rect.top - AROMA_NAV_OFFSET_PX); + void Taro.pageScrollTo({ scrollTop, duration: 280 }); + }); + } + + usePageScroll(() => { + if (scrollingToRef.current) return; + const now = Date.now(); + if (now - lastScrollSyncAtRef.current < 80) return; + lastScrollSyncAtRef.current = now; + const query = Taro.createSelectorQuery(); + AROMA_TABS.forEach((t) => { + query.select(`#${aromaSectionId(t.key)}`).boundingClientRect(); + }); + query.exec((rects) => { + if (!Array.isArray(rects) || rects.length === 0) return; + let next: AromaKey = AROMA_TABS[0].key; + for (let i = 0; i < AROMA_TABS.length; i++) { + const rect = rects[i] as { top?: number } | null; + if (!rect || rect.top == null) continue; + // 区块顶进入导航下方一带时视为当前香型 + if (rect.top <= AROMA_NAV_OFFSET_PX + 24) { + next = AROMA_TABS[i].key; + } + } + setActiveAroma((prev) => (prev === next ? prev : next)); + }); + }); + + function renderProductCard(p: Product) { + const thumb = getProductMainImage(p); + const spec = p.subtitle || p.spec || ''; + return ( + + openProductDetail(p.id)}> + + {thumb ? ( + + ) : ( + + )} + + + + {p.name} + ¥{Number(p.price).toFixed(0)} + + {spec ? {spec} : null} + + + + + {p.allowOnSitePickup ? ( + { + e.stopPropagation?.(); + void goOnSitePickup(p.id); + }} + > + 现场取货 + + ) : null} + { + e.stopPropagation?.(); + openProductDetail(p.id); + }} + > + 立即购买 + + + + + + ); + } + return ( @@ -175,8 +299,8 @@ export default function HomePage() { {AROMA_TABS.map((t) => ( setTab(t.key)} + className={`home-aroma-tab${activeAroma === t.key ? ' home-aroma-tab--active' : ''}`} + onClick={() => scrollToAroma(t.key)} > {t.label} @@ -190,59 +314,18 @@ export default function HomePage() { {!loading && products.length === 0 ? ( 当前城市暂无在售商品 ) : null} - {!loading && products.length > 0 && filtered.length === 0 ? ( - 该香型暂未上线 - ) : null} {!loading && - filtered.map((p) => { - const thumb = getProductMainImage(p); - const spec = p.subtitle || p.spec || ''; + products.length > 0 && + AROMA_TABS.map((t) => { + const list = productsByAroma[t.key]; return ( - - openProductDetail(p.id)} - > - - {thumb ? ( - - ) : ( - - )} - - - - {p.name} - ¥{Number(p.price).toFixed(0)} - - {spec ? {spec} : null} - - - - - {p.allowOnSitePickup ? ( - { - e.stopPropagation?.(); - void goOnSitePickup(p.id); - }} - > - 现场取货 - - ) : null} - { - e.stopPropagation?.(); - openProductDetail(p.id); - }} - > - 立即购买 - - - - + + {t.label} + {list.length === 0 ? ( + 该香型暂未上线 + ) : ( + list.map((p) => renderProductCard(p)) + )} ); })} diff --git a/apps/mini-user/src/styles/home.css b/apps/mini-user/src/styles/home.css index 14a24f2..2aef509 100644 --- a/apps/mini-user/src/styles/home.css +++ b/apps/mini-user/src/styles/home.css @@ -110,6 +110,32 @@ padding: 8px var(--space-page) 12px; } +.home-aroma-section { + display: flex; + flex-direction: column; + gap: 10px; + scroll-margin-top: 48px; +} + +.home-aroma-section + .home-aroma-section { + margin-top: 8px; + padding-top: 12px; + border-top: 1px solid rgba(0, 0, 0, 0.04); +} + +.home-aroma-section-title { + font-family: var(--font-headline); + font-size: 15px; + font-weight: 700; + line-height: 22px; + color: var(--color-on-surface); + padding: 2px 2px 0; +} + +.home-empty--section { + padding: 16px 0 8px; +} + .home-product-card { background: var(--color-card); border-radius: 12px;