import { View, Text, Image, ScrollView } from '@tarojs/components' import Taro from '@tarojs/taro' import { useState, useEffect, useCallback, useMemo } from 'react' import './index.scss' import { PRODUCTS } from '../../../utils/productConfig' import { ProductCategory } from '../../../types' import { useSafeArea } from '../../../hooks/useSafeArea' import { useStatusBar } from '../../../hooks/useStatusBar' import { useThemeContext } from '../../../context/ThemeContext' import ThemedPageMeta from '../../../components/ThemedPageMeta' const clamp = (v: number, min: number, max: number) => Math.min(Math.max(v, min), max) const lerp = (a: number, b: number, t: number) => a + (b - a) * t const easeOut = (t: number) => 1 - Math.pow(1 - t, 3) export default function ShopDetailPage() { const { resolvedTheme } = useThemeContext() const [winHeight, setWinHeight] = useState(667) const [progress, setProgress] = useState(0) const safe = useSafeArea() // 顶部为沉浸式商品图,状态栏始终使用白字。 useStatusBar(resolvedTheme, { mode: 'light' }) const routerParams = Taro.getCurrentInstance().router const routerParamsValues = routerParams ? routerParams.params : undefined const productId = routerParamsValues ? routerParamsValues.id : '' const product: ProductCategory | undefined = PRODUCTS.find(p => p.id === productId) useEffect(() => { try { const info = Taro.getSystemInfoSync() setWinHeight(info.windowHeight) } catch (e) { // fallback to safe default } }, []) const handleScroll = useCallback((e: any) => { const st = (e.detail && e.detail.scrollTop) || 0 const total = winHeight * 1.2 const p = clamp(st / total, 0, 1) setProgress(p) }, [winHeight]) /* Scroll-progress → multi-stage hero & content animations */ const heroStyles = useMemo(() => { const p = progress // 1. Image crop insets (vh %) let topCrop = 0, bottomCrop = 0 if (p <= 0.4) { const t = easeOut(p / 0.4) topCrop = lerp(0, 32, t) bottomCrop = lerp(0, 38, t) } else if (p <= 0.55) { const t = (p - 0.4) / 0.15 topCrop = lerp(32, 34, t) bottomCrop = lerp(38, 41, t) } else { topCrop = 34; bottomCrop = 41 } // 2. Hero translateY let translateY = 0 if (p <= 0.4) translateY = 0 else if (p <= 0.7) translateY = lerp(0, -75, (p - 0.4) / 0.3) else translateY = -75 // 3. Hero bottom info opacity (always visible on open, fades after scroll) let heroInfoOpacity = 1 if (p <= 0.35) heroInfoOpacity = 1 else if (p <= 0.48) heroInfoOpacity = clamp(1 - (p - 0.35) / 0.13, 0, 1) else heroInfoOpacity = 0 // 4. Transition band opacity let bandOpacity = 0 if (p <= 0.55) bandOpacity = 0 else if (p <= 0.7) bandOpacity = (p - 0.55) / 0.15 else bandOpacity = 1 // 5. Content reveal let contentOpacity = 1, contentOffset = 0 if (p <= 0.05) { contentOpacity = 0; contentOffset = 60 } else if (p <= 0.22) { const t = (p - 0.05) / 0.17 contentOpacity = easeOut(t) contentOffset = lerp(60, 0, easeOut(t)) } // 6. Back button (fade out as hero scrolls) let backBtnOpacity = 1 if (p <= 0.15) backBtnOpacity = clamp(1 - p / 0.15, 0, 1) return { topCrop, bottomCrop, translateY, heroInfoOpacity, bandOpacity, contentOpacity, contentOffset, backBtnOpacity, visibleHeight: 100 - topCrop - bottomCrop } }, [progress]) const tone = useMemo(() => (product ? product.tone : [28, 22, 18]), [product]) if (!product) { return ( 商品不存在 ) } return ( {/* Fixed Hero */} {/* Bottom gradient overlay driven by product tone */} {product.name} {product.subtitle || product.desc} ¥{product.price} {product.originalPrice && product.originalPrice > product.price && ( ¥{product.originalPrice} )} {/* Scrollable content sits above the fixed hero */} {/* Transparent spacer pushes content below hero */} {/* Transition band — appears as hero scrolls away */} {product.name} {product.subtitle || product.desc} ¥{product.price} {product.originalPrice && product.originalPrice > product.price && ( ¥{product.originalPrice} )} {/* Main copy blocks */} {product.story && ( 设计理念 {product.story} )} {product.scene && ( 使用场景 {product.scene} )} {product.tags && product.tags.length > 0 && ( {product.tags.map(tag => ( {tag} ))} )} {product.specs && product.specs.length > 0 && ( 规格参数 {product.specs.map(([k, v]) => ( {k} {v} ))} )} {/* Back button — glass morphism circle */} Taro.navigateBack()} style={{ top: `${safe.backBtnPaddingTop}px`, opacity: heroStyles.backBtnOpacity }} > {/* Bottom CTA — fixed, glass, links to existing product page */} ¥{product.price} {product.originalPrice && product.originalPrice > product.price && ( ¥{product.originalPrice} )} Taro.navigateTo({ url: `/pages/product/index?id=${product.id}` })} > 立即定制 ) }