This commit is contained in:
2026-06-30 10:33:56 +08:00
commit 6e047dc0a5
607 changed files with 65966 additions and 0 deletions
@@ -0,0 +1,154 @@
import { useEffect, useState } from 'react';
import { Link, useNavigate, useParams } from 'react-router-dom';
import AppImage from '@dukang/shared-ui/AppImage';
import ProductCarousel from '../components/ProductCarousel';
import { request } from '../lib/api';
import { getProductCarouselImages, getProductDetailImages } from '../lib/product-images';
type Product = {
name: string;
subtitle?: string;
price: number;
benefitAmount?: number;
};
const FEATURES = [
{ icon: 'water_drop', title: '泉水酿造', desc: '甘冽清甜 灵动自然' },
{ icon: 'grain', title: '精选五谷', desc: '传统比例 匠心发酵' },
] as const;
export default function ProductDetailPage() {
const { id } = useParams();
const navigate = useNavigate();
const [product, setProduct] = useState<Product | null>(null);
const [headerSolid, setHeaderSolid] = useState(false);
const imageIndex = id ? Math.max(0, Number(id) - 1) : 0;
useEffect(() => {
if (id) request<Product>('USER_H5', `/catalog/products/${id}`).then(setProduct);
}, [id]);
useEffect(() => {
function onScroll() {
setHeaderSolid(window.scrollY > 100);
}
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
if (!product) return <div className="empty">...</div>;
const benefit = Number(product.benefitAmount ?? product.price);
const carouselImages = getProductCarouselImages(imageIndex);
const detailImages = getProductDetailImages(imageIndex);
return (
<div className="product-detail-page">
<header className={`product-detail-header${headerSolid ? ' solid' : ''}`}>
<button
type="button"
className="product-detail-header-btn"
aria-label="返回"
onClick={() => navigate('/')}
>
<span className="material-symbols-outlined">arrow_back</span>
</button>
<h1 className={`app-page-title product-detail-header-title${headerSolid ? ' visible' : ''}`}>
{product.name}
</h1>
<button
type="button"
className="product-detail-header-btn"
aria-label="分享"
onClick={() => {}}
>
<span className="material-symbols-outlined">share</span>
</button>
</header>
<main className="product-detail-main">
<section className="product-detail-hero">
<ProductCarousel images={carouselImages} alt={product.name} variant="detail" />
</section>
<section className="product-detail-info">
<div className="product-detail-price">
<span className="product-detail-price-symbol">¥</span>
<span className="product-detail-price-value">{product.price}</span>
</div>
<h2 className="product-detail-name">{product.name}</h2>
{product.subtitle && (
<p className="product-detail-subtitle">{product.subtitle}</p>
)}
<div className="product-detail-promo">
<div className="product-detail-promo-glow" aria-hidden />
<div className="product-detail-promo-head">
<div className="product-detail-promo-icon">
<span className="material-symbols-outlined fill-icon">confirmation_number</span>
</div>
<h3 className="product-detail-promo-title">
·
<span className="product-detail-promo-amount">¥{benefit}</span>
</h3>
</div>
<p className="product-detail-promo-desc">
</p>
<span className="material-symbols-outlined product-detail-promo-deco" aria-hidden>
restaurant
</span>
</div>
</section>
<section className="product-detail-content">
<div className="product-detail-section-head">
<span className="product-detail-section-bar" />
<h3></h3>
</div>
{detailImages[0] && (
<AppImage src={detailImages[0]} alt="" wrapperClassName="product-detail-banner" />
)}
<div className="product-detail-copy">
<div className="product-detail-story">
<h4> · </h4>
<p>
</p>
</div>
<div className="product-detail-features">
{FEATURES.map((f) => (
<div key={f.title} className="product-detail-feature">
<span className="material-symbols-outlined">{f.icon}</span>
<div className="product-detail-feature-title">{f.title}</div>
<div className="product-detail-feature-desc">{f.desc}</div>
</div>
))}
</div>
</div>
{detailImages.length > 1 && (
<AppImage src={detailImages[1]} alt="" wrapperClassName="product-detail-banner" />
)}
</section>
</main>
<nav className="product-detail-bar">
<Link to="/" className="product-detail-bar-home">
<span className="material-symbols-outlined">home</span>
<span></span>
</Link>
<button
type="button"
className="product-detail-buy-btn"
onClick={() => navigate(`/order/confirm?productId=${id}&qty=2`)}
>
</button>
</nav>
</div>
);
}