ae7c63c08d
CI / verify (pull_request) Has been cancelled
Share PullToRefresh for partner/shop H5; enable Taro pull-down on mini-user lists. Co-authored-by: Cursor <cursoragent@cursor.com>
154 lines
5.3 KiB
TypeScript
154 lines
5.3 KiB
TypeScript
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
import { View, Text } from '@tarojs/components';
|
|
import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro';
|
|
import PageShell from '../../components/PageShell';
|
|
import TabMainHeader from '../../components/TabMainHeader';
|
|
import CouponBadge from '../../components/CouponBadge';
|
|
import ProductCarousel from '../../components/ProductCarousel';
|
|
import UserTabBar, { shouldRenderPageTabBar, syncTabBarSelected } from '../../components/UserTabBar';
|
|
import { request, toast } from '../../lib/api';
|
|
import { getProductImages } from '../../lib/product-images';
|
|
import { getCityCodeForCatalog, resolveUserCity } from '../../lib/user-location';
|
|
type Product = {
|
|
id: string;
|
|
name: string;
|
|
subtitle?: string;
|
|
price: number;
|
|
benefitDisplay?: number;
|
|
mainImageUrl?: string | null;
|
|
carouselUrls?: string[] | null;
|
|
aromaType: string;
|
|
};
|
|
|
|
const AROMA_TABS = [
|
|
{ key: 'QINGXIANG', label: '清香型' },
|
|
{ key: 'JIANGXIANG', label: '酱香型' },
|
|
{ key: 'NONGXIANG', label: '浓香型' },
|
|
] as const;
|
|
|
|
export default function HomePage() {
|
|
const [tab, setTab] = useState('QINGXIANG');
|
|
const [products, setProducts] = useState<Product[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [displayCity, setDisplayCity] = useState('郑州市');
|
|
const [cityCode, setCityCode] = useState('410100');
|
|
|
|
useDidShow(() => {
|
|
syncTabBarSelected(0);
|
|
void resolveUserCity().then((resolved) => {
|
|
setDisplayCity(resolved.displayCity);
|
|
setCityCode(getCityCodeForCatalog(resolved));
|
|
});
|
|
});
|
|
|
|
const loadProducts = useCallback(() => {
|
|
setLoading(true);
|
|
return request<Product[]>(`/catalog/products?cityCode=${encodeURIComponent(cityCode)}`)
|
|
.then((list) => setProducts(Array.isArray(list) ? list : []))
|
|
.catch((e) => toast(e instanceof Error ? e.message : '加载失败'))
|
|
.finally(() => setLoading(false));
|
|
}, [cityCode]);
|
|
|
|
useEffect(() => {
|
|
void loadProducts();
|
|
}, [loadProducts]);
|
|
|
|
usePullDownRefresh(() => {
|
|
void (async () => {
|
|
try {
|
|
const resolved = await resolveUserCity();
|
|
setDisplayCity(resolved.displayCity);
|
|
const nextCode = getCityCodeForCatalog(resolved);
|
|
setCityCode(nextCode);
|
|
setLoading(true);
|
|
const list = await request<Product[]>(
|
|
`/catalog/products?cityCode=${encodeURIComponent(nextCode)}`,
|
|
);
|
|
setProducts(Array.isArray(list) ? list : []);
|
|
} catch (e) {
|
|
toast(e instanceof Error ? e.message : '加载失败');
|
|
} finally {
|
|
setLoading(false);
|
|
Taro.stopPullDownRefresh();
|
|
}
|
|
})();
|
|
});
|
|
|
|
const availableAromas = useMemo(
|
|
() =>
|
|
AROMA_TABS.filter((item) =>
|
|
products.some((product) => product.aromaType === item.key),
|
|
),
|
|
[products],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (loading || availableAromas.length === 0) return;
|
|
if (!availableAromas.some((item) => item.key === tab)) {
|
|
setTab(availableAromas[0].key);
|
|
}
|
|
}, [availableAromas, loading, tab]);
|
|
|
|
function openProductDetail(id: string) {
|
|
Taro.navigateTo({ url: `/pages/product-detail/index?id=${id}` });
|
|
}
|
|
|
|
const filtered = products.filter((p) => p.aromaType === tab);
|
|
|
|
return (
|
|
<PageShell variant="tab" className="home-page no-tab-header">
|
|
<TabMainHeader title="杜康好客" />
|
|
|
|
<View className="home-aroma-nav">
|
|
<View className="home-aroma-tabs">
|
|
{availableAromas.map((t) => (
|
|
<Text
|
|
key={t.key}
|
|
className={`home-aroma-tab${tab === t.key ? ' home-aroma-tab--active' : ''}`}
|
|
onClick={() => setTab(t.key)}
|
|
>
|
|
{t.label}
|
|
</Text>
|
|
))}
|
|
</View>
|
|
<Text className="home-aroma-city">{displayCity}</Text>
|
|
</View>
|
|
|
|
<View className="home-product-list">
|
|
{loading ? <View className="home-empty">加载中…</View> : null}
|
|
{!loading && products.length === 0 ? (
|
|
<View className="home-empty">当前城市暂无在售商品</View>
|
|
) : null}
|
|
{!loading &&
|
|
filtered.map((p) => {
|
|
const images = getProductImages(p);
|
|
return (
|
|
<View key={p.id} className="home-product-card">
|
|
<View onClick={() => openProductDetail(p.id)}>
|
|
<ProductCarousel images={images} alt={p.name} variant="home" />
|
|
<View className="home-product-body">
|
|
<View className="home-product-row">
|
|
<Text className="home-product-name">{p.name}</Text>
|
|
<Text className="home-product-price">¥{Number(p.price).toFixed(2)}</Text>
|
|
</View>
|
|
{p.subtitle ? <Text className="home-product-sub">{p.subtitle}</Text> : null}
|
|
<View className="home-product-footer">
|
|
<CouponBadge amount={p.benefitDisplay ?? p.price} label="好客权益" />
|
|
</View>
|
|
</View>
|
|
</View>
|
|
<View className="home-product-actions">
|
|
<Text className="home-buy-btn" onClick={() => openProductDetail(p.id)}>
|
|
立即购买
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
})}
|
|
</View>
|
|
|
|
{shouldRenderPageTabBar() ? <UserTabBar selected={0} /> : null}
|
|
</PageShell>
|
|
);
|
|
}
|