import { useCallback, useEffect, useMemo, useState } from 'react'; import { View, Text, Image, Input } from '@tarojs/components'; import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro'; import PageShell from '../../components/PageShell'; import TabMainHeader from '../../components/TabMainHeader'; import RegionPicker from '../../components/RegionPicker'; import CategoryPicker, { EMPTY_CATEGORY, formatCategoryLabel, type CategorySelection, type StoreCategoryNode, } from '../../components/CategoryPicker'; import { DEFAULT_REGION, formatRegionLabel, matchesRegionFilter, type RegionSelection, } from '../../lib/region-data'; import UserTabBar, { shouldRenderPageTabBar, syncTabBarSelected } from '../../components/UserTabBar'; import { getCityCodeForCatalog, resolveUserCity } from '../../lib/user-location'; import { FALLBACK_CITY_CODE } from '../../lib/product-images'; import { request, toast } from '../../lib/api'; type Store = { id: string; name: string; address?: string; province?: string; cityName?: string; district?: string; coverUrl?: string | null; openTime?: string | null; closeTime?: string | null; status?: string; categoryId?: string | null; category?: { id?: string; name?: string; parentId?: string | null } | null; }; const MOCK_DISTANCES = ['800m', '1.2km', '3.5km', '1.5km', '2.0km']; export default function StoresPage() { const [stores, setStores] = useState([]); const [loading, setLoading] = useState(true); const [keywordInput, setKeywordInput] = useState(''); const [keyword, setKeyword] = useState(''); const [region, setRegion] = useState(DEFAULT_REGION); const [regionOpen, setRegionOpen] = useState(false); const [category, setCategory] = useState(EMPTY_CATEGORY); const [categoryOpen, setCategoryOpen] = useState(false); const [categoryTree, setCategoryTree] = useState([]); const [cityCode, setCityCode] = useState(FALLBACK_CITY_CODE); const regionLabel = formatRegionLabel(region); const categoryLabel = formatCategoryLabel(category); const childIdsByParent = useMemo(() => { const map = new Map(); for (const root of categoryTree) { map.set( root.id, (root.children ?? []).map((c) => c.id), ); } return map; }, [categoryTree]); useDidShow(() => { syncTabBarSelected(1); void resolveUserCity().then((resolved) => { setRegion(resolved.region); setCityCode(getCityCodeForCatalog(resolved)); }); }); useEffect(() => { void request('/store-categories') .then((tree) => setCategoryTree(Array.isArray(tree) ? tree : [])) .catch(() => setCategoryTree([])); }, []); const loadStores = useCallback(() => { setLoading(true); const path = cityCode ? `/stores?cityCode=${encodeURIComponent(cityCode)}` : '/stores'; return request(path) .then((list) => setStores(Array.isArray(list) ? list : [])) .catch((e) => toast(e instanceof Error ? e.message : '加载失败')) .finally(() => setLoading(false)); }, [cityCode]); useEffect(() => { void loadStores(); }, [loadStores]); usePullDownRefresh(() => { void (async () => { try { const resolved = await resolveUserCity(); setRegion(resolved.region); const nextCode = getCityCodeForCatalog(resolved); setCityCode(nextCode); setLoading(true); const path = nextCode ? `/stores?cityCode=${encodeURIComponent(nextCode)}` : '/stores'; const list = await request(path); setStores(Array.isArray(list) ? list : []); } catch (e) { toast(e instanceof Error ? e.message : '加载失败'); } finally { setLoading(false); Taro.stopPullDownRefresh(); } })(); }); function matchesCategory(store: Store): boolean { if (!category.parentId) return true; const storeCatId = String(store.categoryId || store.category?.id || ''); const storeParentId = String(store.category?.parentId || ''); if (category.childId) { return storeCatId === category.childId; } if (storeParentId && storeParentId === category.parentId) return true; const siblings = childIdsByParent.get(category.parentId) ?? []; return siblings.includes(storeCatId); } const filtered = stores.filter((s) => { if (!matchesRegionFilter(s, region)) return false; if (!matchesCategory(s)) return false; if (!keyword.trim()) return true; const q = keyword.trim(); return s.name.includes(q) || (s.address ?? '').includes(q) || (s.district ?? '').includes(q); }); function applySearch() { setKeyword(keywordInput.trim()); } function resetFilters() { setKeywordInput(''); setKeyword(''); setCategory(EMPTY_CATEGORY); setRegion(DEFAULT_REGION); } function formatHours(store: Store) { if (store.openTime && store.closeTime) { return `营业时间: ${store.openTime}-${store.closeTime}`; } return '营业时间: 10:00-22:00'; } return ( setKeywordInput(e.detail.value)} onConfirm={applySearch} /> setRegionOpen(true)}> {regionLabel} setCategoryOpen(true)}> {categoryLabel} 重置 {loading ? 加载中… : null} {!loading && filtered.length === 0 ? 暂无营业中门店 : null} {!loading && filtered.map((s, index) => ( Taro.navigateTo({ url: `/pages/store-detail/index?id=${s.id}` })} > {s.coverUrl ? ( ) : ( )} {s.name} {s.district ? `${s.district} · ` : ''} {s.address || '地址待完善'} {formatHours(s)} {MOCK_DISTANCES[index % MOCK_DISTANCES.length]} { e.stopPropagation(); Taro.navigateTo({ url: '/pages/redeem/index' }); }} > 去核销 ))} {shouldRenderPageTabBar() ? : null} setRegionOpen(false)} onConfirm={(next) => setRegion(next)} /> setCategoryOpen(false)} onConfirm={(next) => setCategory(next)} /> ); }