feat(store): dual business hours, avg price, and status lock
CI / verify (pull_request) Has been cancelled
CI / verify (pull_request) Has been cancelled
Support 1-2 hour segments and optional avgPrice; show bank settlement on HQ store detail; enforce pickup min 2 bottles; Chinese order statuses; block shop reopen after permanent close. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,7 +35,7 @@ type OrderPreview = {
|
||||
export default function OrderConfirmPickupPage() {
|
||||
const router = useRouter();
|
||||
const productId = router.params.productId ?? '';
|
||||
const [quantity, setQuantity] = useState(Math.max(1, Number(router.params.qty || 1)));
|
||||
const [quantity, setQuantity] = useState(Math.max(2, Number(router.params.qty || 2)));
|
||||
const [preview, setPreview] = useState<OrderPreview | null>(null);
|
||||
const [previewLoading, setPreviewLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -70,12 +70,12 @@ export default function OrderConfirmPickupPage() {
|
||||
};
|
||||
}, [productId, quantity]);
|
||||
|
||||
const minQty = preview?.minQty ?? 1;
|
||||
const minQty = preview?.minQty ?? 2;
|
||||
const quantityOk = preview ? preview.quantityOk !== false && quantity >= minQty : false;
|
||||
const canSubmit = !!preview && quantityOk && !loading && !previewLoading;
|
||||
|
||||
function updateQuantity(next: number) {
|
||||
if (next < 1) return;
|
||||
if (next < minQty) return;
|
||||
setQuantity(next);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ export default function OrderConfirmPickupPage() {
|
||||
|
||||
async function submit() {
|
||||
if (!canSubmit) {
|
||||
if (!quantityOk) setMsg(`现场取货至少购买 ${minQty} 瓶`);
|
||||
if (!quantityOk) setMsg(`现场提货至少购买 ${minQty} 瓶`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,8 +185,8 @@ export default function OrderConfirmPickupPage() {
|
||||
<Text>购买数量</Text>
|
||||
<View className="order-qty-controls">
|
||||
<View
|
||||
className={`order-qty-btn${quantity <= 1 ? ' order-qty-btn--disabled' : ''}`}
|
||||
onClick={() => updateQuantity(quantity - 1)}
|
||||
className={`order-qty-btn${quantity <= minQty ? ' order-qty-btn--disabled' : ''}`}
|
||||
onClick={() => updateQuantity(Math.max(minQty, quantity - 1))}
|
||||
>
|
||||
<Text>−</Text>
|
||||
</View>
|
||||
|
||||
@@ -6,6 +6,8 @@ import SubPageHeader from '../../components/SubPageHeader';
|
||||
import { request, toast } from '../../lib/api';
|
||||
import { buildPayUrl } from '../../lib/checkout-nav';
|
||||
|
||||
import { ORDER_STATUS_LABELS } from '@dukang/shared-types';
|
||||
|
||||
const TABS = [
|
||||
{ key: 'all', label: '全部订单' },
|
||||
{ key: 'pending_pay', label: '待付款' },
|
||||
@@ -13,22 +15,12 @@ const TABS = [
|
||||
{ key: 'completed', label: '已完成' },
|
||||
] as const;
|
||||
|
||||
const STATUS_LABELS: Record<string, string> = {
|
||||
PENDING_PAY: '待付款',
|
||||
PENDING_SHIP: '已付款',
|
||||
OUT_WAREHOUSE: '已付款',
|
||||
SHIPPED: '已付款',
|
||||
DELIVERED: '已付款',
|
||||
COMPLETED: '已完成',
|
||||
CANCELLED: '已取消',
|
||||
};
|
||||
|
||||
function orderStatusLabel(tab: string, status?: string): string {
|
||||
if (tab !== 'all') {
|
||||
return TABS.find((t) => t.key === tab)?.label || status || '';
|
||||
}
|
||||
if (!status) return '';
|
||||
return STATUS_LABELS[status] || status;
|
||||
return ORDER_STATUS_LABELS[status] || status;
|
||||
}
|
||||
|
||||
type OrderItem = {
|
||||
|
||||
@@ -23,6 +23,9 @@ type Store = {
|
||||
carouselUrls?: string[] | null;
|
||||
openTime?: string | null;
|
||||
closeTime?: string | null;
|
||||
openTime2?: string | null;
|
||||
closeTime2?: string | null;
|
||||
avgPrice?: number | null;
|
||||
category?: { name: string } | null;
|
||||
};
|
||||
|
||||
@@ -112,8 +115,17 @@ export default function StoreDetailPage() {
|
||||
{store.address || '地址待完善'}
|
||||
</Text>
|
||||
<Text className="store-detail-meta">
|
||||
营业时间: {store.openTime && store.closeTime ? `${store.openTime}-${store.closeTime}` : '10:00-22:00'}
|
||||
营业时间:{' '}
|
||||
{(() => {
|
||||
const parts: string[] = [];
|
||||
if (store.openTime && store.closeTime) parts.push(`${store.openTime}-${store.closeTime}`);
|
||||
if (store.openTime2 && store.closeTime2) parts.push(`${store.openTime2}-${store.closeTime2}`);
|
||||
return parts.length ? parts.join(',') : '10:00-22:00';
|
||||
})()}
|
||||
</Text>
|
||||
{store.avgPrice != null && Number(store.avgPrice) > 0 ? (
|
||||
<Text className="store-detail-meta">人均约 ¥{Number(store.avgPrice).toFixed(0)}</Text>
|
||||
) : null}
|
||||
{store.phone ? <Text className="store-detail-meta">电话: {store.phone}</Text> : null}
|
||||
<View className="store-detail-tags">
|
||||
{store.category?.name ? (
|
||||
|
||||
@@ -31,6 +31,9 @@ type Store = {
|
||||
coverUrl?: string | null;
|
||||
openTime?: string | null;
|
||||
closeTime?: string | null;
|
||||
openTime2?: string | null;
|
||||
closeTime2?: string | null;
|
||||
avgPrice?: number | null;
|
||||
status?: string;
|
||||
categoryId?: string | null;
|
||||
category?: { id?: string; name?: string; parentId?: string | null } | null;
|
||||
@@ -142,10 +145,10 @@ export default function StoresPage() {
|
||||
}
|
||||
|
||||
function formatHours(store: Store) {
|
||||
if (store.openTime && store.closeTime) {
|
||||
return `营业时间: ${store.openTime}-${store.closeTime}`;
|
||||
}
|
||||
return '营业时间: 10:00-22:00';
|
||||
const parts: string[] = [];
|
||||
if (store.openTime && store.closeTime) parts.push(`${store.openTime}-${store.closeTime}`);
|
||||
if (store.openTime2 && store.closeTime2) parts.push(`${store.openTime2}-${store.closeTime2}`);
|
||||
return parts.length ? `营业时间: ${parts.join(',')}` : '营业时间: 10:00-22:00';
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -204,6 +207,9 @@ export default function StoresPage() {
|
||||
{s.address || '地址待完善'}
|
||||
</Text>
|
||||
<Text className="store-card-meta">{formatHours(s)}</Text>
|
||||
{s.avgPrice != null && Number(s.avgPrice) > 0 ? (
|
||||
<Text className="store-card-meta">人均¥{Number(s.avgPrice).toFixed(0)}</Text>
|
||||
) : null}
|
||||
<View className="store-card-footer">
|
||||
<Text className="store-card-distance">{MOCK_DISTANCES[index % MOCK_DISTANCES.length]}</Text>
|
||||
<Text
|
||||
|
||||
Reference in New Issue
Block a user