Files
dukang/apps/mini-user/src/pages/store-package-detail/index.tsx
T
jacy a2abbb6169 feat(v3.4.14): mini-user store UX, brand config, client settings
Store list/detail package flow, configurable WeChat mini brand assets and customer service, shop H5 home refresh.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 15:21:59 +08:00

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>
);
}