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>
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user