a2abbb6169
Store list/detail package flow, configurable WeChat mini brand assets and customer service, shop H5 home refresh. Co-authored-by: Cursor <cursoragent@cursor.com>
176 lines
5.6 KiB
TypeScript
176 lines
5.6 KiB
TypeScript
import { useCallback, useState } from 'react';
|
|
import { View, Text, Image } from '@tarojs/components';
|
|
import Taro, { useLoad, useRouter } from '@tarojs/taro';
|
|
import PageShell from '../../components/PageShell';
|
|
import PageNavBar from '../../components/PageNavBar';
|
|
import { request, toast } from '../../lib/api';
|
|
|
|
type StorePackage = {
|
|
name: string;
|
|
price: string | number;
|
|
dishes: string;
|
|
usableTime?: string | null;
|
|
otherNotes?: string | null;
|
|
imageUrl?: string | null;
|
|
};
|
|
|
|
type Store = {
|
|
id: string;
|
|
name: string;
|
|
packages?: StorePackage[] | null;
|
|
};
|
|
|
|
function pickStoreId(raw?: string | null) {
|
|
return String(raw || '')
|
|
.trim()
|
|
.replace(/[^\d]/g, '');
|
|
}
|
|
|
|
function parsePackageIndex(raw?: string | null) {
|
|
const n = Number.parseInt(String(raw ?? ''), 10);
|
|
return Number.isFinite(n) && n >= 0 ? n : -1;
|
|
}
|
|
|
|
function formatPriceYuan(price: string | number) {
|
|
const n = typeof price === 'number' ? price : Number(price);
|
|
if (!Number.isFinite(n)) return '0';
|
|
if (Math.abs(n - Math.round(n)) < 1e-9) return String(Math.round(n));
|
|
return n.toFixed(2).replace(/\.?0+$/, '');
|
|
}
|
|
|
|
export default function StorePackageDetailPage() {
|
|
const router = useRouter();
|
|
const [storeId, setStoreId] = useState(() => pickStoreId(router.params.storeId));
|
|
const [storeName, setStoreName] = useState('');
|
|
const [pkg, setPkg] = useState<StorePackage | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [loadError, setLoadError] = useState('');
|
|
|
|
const loadPackage = useCallback(async (sid: string, index: number) => {
|
|
if (!sid) {
|
|
setLoading(false);
|
|
setLoadError('缺少门店参数');
|
|
return;
|
|
}
|
|
if (index < 0) {
|
|
setLoading(false);
|
|
setLoadError('套餐不存在');
|
|
return;
|
|
}
|
|
setLoadError('');
|
|
setLoading(true);
|
|
try {
|
|
const data = await request<Store>(`/stores/${sid}`);
|
|
const packages = data?.packages ?? [];
|
|
const item = packages[index];
|
|
if (!data?.id || !item) {
|
|
setPkg(null);
|
|
setLoadError('套餐不存在或已下架');
|
|
toast('套餐不存在或已下架');
|
|
return;
|
|
}
|
|
setStoreName(data.name);
|
|
setPkg(item);
|
|
} catch (e) {
|
|
const msg = e instanceof Error ? e.message : '加载失败';
|
|
setLoadError(msg);
|
|
toast(msg);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useLoad((options) => {
|
|
const sid = pickStoreId(options?.storeId || router.params.storeId);
|
|
const index = parsePackageIndex(options?.index ?? router.params.index);
|
|
setStoreId(sid);
|
|
void loadPackage(sid, index);
|
|
});
|
|
|
|
function goBack() {
|
|
const pages = Taro.getCurrentPages();
|
|
if (pages.length > 1) Taro.navigateBack();
|
|
else if (storeId) {
|
|
Taro.redirectTo({ url: `/pages/store-detail/index?id=${storeId}` });
|
|
} else {
|
|
Taro.switchTab({ url: '/pages/stores/index' });
|
|
}
|
|
}
|
|
|
|
function previewImage(url: string) {
|
|
Taro.previewImage({ urls: [url], current: url }).catch(() => toast('无法预览图片'));
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<PageShell variant="scroll" className="store-package-detail-page">
|
|
<PageNavBar title="套餐详情" solid onBack={goBack} />
|
|
<View className="page-with-nav-bar u-empty">加载中…</View>
|
|
</PageShell>
|
|
);
|
|
}
|
|
|
|
if (!pkg) {
|
|
return (
|
|
<PageShell variant="scroll" className="store-package-detail-page">
|
|
<PageNavBar title="套餐详情" solid onBack={goBack} />
|
|
<View className="page-with-nav-bar u-empty">{loadError || '套餐不存在'}</View>
|
|
</PageShell>
|
|
);
|
|
}
|
|
|
|
const imageUrl = (pkg.imageUrl || '').trim();
|
|
|
|
return (
|
|
<PageShell variant="scroll" className="store-package-detail-page">
|
|
<PageNavBar title={pkg.name} solid titleVisible onBack={goBack} />
|
|
|
|
<View className="store-package-detail-body">
|
|
<View className="store-package-detail-inner">
|
|
<View className="store-package-detail-header">
|
|
<View className="store-package-detail-title-row">
|
|
<Text className="store-package-detail-title">{pkg.name}</Text>
|
|
<Text className="store-package-detail-price">¥{formatPriceYuan(pkg.price)}</Text>
|
|
</View>
|
|
{storeName ? (
|
|
<Text className="store-package-detail-store">{storeName}</Text>
|
|
) : null}
|
|
</View>
|
|
|
|
{imageUrl ? (
|
|
<View
|
|
className="store-package-detail-photo"
|
|
onClick={() => previewImage(imageUrl)}
|
|
>
|
|
<Image
|
|
className="store-package-detail-photo-image"
|
|
src={imageUrl}
|
|
mode="widthFix"
|
|
/>
|
|
</View>
|
|
) : null}
|
|
|
|
<View className="store-package-detail-content">
|
|
<View className="store-detail-package-field">
|
|
<Text className="store-detail-package-field-label">菜品</Text>
|
|
<Text className="store-detail-package-field-value">{pkg.dishes || '—'}</Text>
|
|
</View>
|
|
{pkg.usableTime ? (
|
|
<View className="store-detail-package-field">
|
|
<Text className="store-detail-package-field-label">使用时间</Text>
|
|
<Text className="store-detail-package-field-value">{pkg.usableTime}</Text>
|
|
</View>
|
|
) : null}
|
|
{pkg.otherNotes ? (
|
|
<View className="store-detail-package-field">
|
|
<Text className="store-detail-package-field-label">其他说明</Text>
|
|
<Text className="store-detail-package-field-value">{pkg.otherNotes}</Text>
|
|
</View>
|
|
) : null}
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</PageShell>
|
|
);
|
|
}
|