Files
dukang/apps/mini-user/src/pages/order-confirm-pickup/index.tsx
T
jacy ebd8c07147 数量加减:同城/跨城/现场取货确认页加减号不再置灰;点减到低于起购时 toast,数量仍可降到 1(不能到 0)。
支付后返回:支付成功进详情带 from=pay;返回一律 switchTab 首页(失败则 reLaunch),避免栈只有一页时 navigateBack 退出小程序。订单列表返回同样加固。
2026-08-03 13:22:12 +08:00

263 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useRef, useState } from 'react';
import { View, Text, Image } from '@tarojs/components';
import Taro, { useRouter } from '@tarojs/taro';
import PageShell from '../../components/PageShell';
import SubPageHeader from '../../components/SubPageHeader';
import { goLogin } from '../../lib/auth-nav';
import { buildPayUrl } from '../../lib/checkout-nav';
import { ensurePayReady } from '../../lib/pay-ready';
import { fetchUserProfile } from '../../lib/pay-wechat';
import { request, toast } from '../../lib/api';
import { getProductMainImage } from '../../lib/product-images';
type PreviewProduct = {
id: string;
name: string;
spec?: string;
subtitle?: string;
price: number;
mainImageUrl?: string | null;
carouselUrls?: string[] | null;
};
type OrderPreview = {
product: PreviewProduct;
quantity: number;
deliveryType: string;
productAmount: number;
payAmount: number;
benefitAmount: number;
quantityOk?: boolean;
quantityMessage?: string | null;
minQty?: number;
};
export default function OrderConfirmPickupPage() {
const router = useRouter();
const productId = router.params.productId ?? '';
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);
const [msg, setMsg] = useState('');
const phonePromptSkipped = useRef(false);
useEffect(() => {
if (!productId) return;
let cancelled = false;
setPreviewLoading(true);
request<OrderPreview>('/trade/orders/preview', {
method: 'POST',
data: { productId, quantity, onSitePickup: true },
})
.then((data) => {
if (!cancelled) {
setPreview(data);
setMsg(data.quantityOk === false ? data.quantityMessage || '' : '');
}
})
.catch((e) => {
if (!cancelled) {
setPreview(null);
setMsg(e instanceof Error ? e.message : '加载失败');
}
})
.finally(() => {
if (!cancelled) setPreviewLoading(false);
});
return () => {
cancelled = true;
};
}, [productId, quantity]);
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 < minQty) {
const tip = `现场提货至少购买 ${minQty} 瓶`;
toast(tip);
setMsg(tip);
if (next < 1) return;
setQuantity(next);
return;
}
setQuantity(next);
}
async function doSubmit() {
const order = await request<{ id: string }>('/trade/orders', {
method: 'POST',
data: { productId, quantity, onSitePickup: true },
});
Taro.redirectTo({
url: buildPayUrl({
orderId: order.id,
productId,
qty: String(quantity),
}),
});
}
async function submit() {
if (!canSubmit) {
if (!quantityOk) {
const tip = `现场提货至少购买 ${minQty} 瓶`;
setMsg(tip);
toast(tip);
return;
}
}
const returnPath = `/pages/order-confirm-pickup/index?productId=${productId}&qty=${quantity}`;
if (!phonePromptSkipped.current) {
try {
const profile = await fetchUserProfile();
const phoneBound =
!!profile.phoneVerified ||
(!!profile.phone && /^1[3-9]\d{9}$/.test(String(profile.phone)));
if (!phoneBound) {
const { confirm, cancel } = await Taro.showModal({
title: '建议绑定手机号',
content: '绑定后便于订单通知与售后联系;也可跳过,不绑定也能继续下单。',
confirmText: '去绑定',
cancelText: '暂不绑定',
});
if (confirm) {
goLogin(returnPath, { needPhone: '1' });
return;
}
if (cancel) {
phonePromptSkipped.current = true;
}
}
} catch {
/* 拉取档案失败不阻塞下单 */
}
}
const ready = await ensurePayReady(returnPath);
if (!ready) return;
setLoading(true);
setMsg('');
try {
await doSubmit();
} catch (e) {
setMsg(e instanceof Error ? e.message : '下单失败');
} finally {
setLoading(false);
}
}
const productImage = preview?.product ? getProductMainImage(preview.product) : '';
const submitLabel = loading
? '提交中…'
: !quantityOk
? `至少购买 ${minQty} 瓶`
: '提交订单';
return (
<PageShell variant="sub" className="order-confirm-page" hasFixedFooter>
<SubPageHeader title="现场取货确认" />
<View className="sub-page-body">
<View className="order-card">
<Text className="order-card-title">取货方式</Text>
<Text className="u-muted">现场取货 · 无需填写收货地址 · 免运费</Text>
</View>
{preview ? (
<>
<View className="order-card">
<Text className="order-card-title">商品信息</Text>
<View className="order-product-row">
<View className="order-product-thumb">
{productImage ? (
<Image
className="order-product-thumb-img"
src={productImage}
mode="aspectFill"
/>
) : null}
</View>
<View style={{ flex: 1 }}>
<Text className="order-product-name">{preview.product.name}</Text>
{preview.product.spec ? (
<Text className="u-muted">{preview.product.spec}</Text>
) : null}
<Text className="order-product-price">
¥{Number(preview.product.price).toFixed(2)}
</Text>
</View>
</View>
<View className="order-qty-row">
<Text>购买数量</Text>
<View className="order-qty-controls">
<View
className="order-qty-btn"
onClick={() => updateQuantity(quantity - 1)}
>
<Text></Text>
</View>
<Text className="order-qty-value">{quantity}</Text>
<View className="order-qty-btn" onClick={() => updateQuantity(quantity + 1)}>
<Text></Text>
</View>
</View>
</View>
{!quantityOk ? (
<Text className="order-qty-hint">
{`现场提货至少购买 ${minQty} 瓶,请调整数量`}
</Text>
) : null}
</View>
<View className="order-card">
<Text className="order-card-title">费用明细</Text>
<View className="order-row">
<Text className="order-row-label">商品金额</Text>
<Text className="order-row-value">¥{Number(preview.productAmount).toFixed(2)}</Text>
</View>
<View className="order-row">
<Text className="order-row-label">好客权益</Text>
<Text className="order-row-value--price">¥{Number(preview.benefitAmount).toFixed(2)}</Text>
</View>
<View className="order-row">
<Text className="order-row-label">运费</Text>
<Text className="order-row-value">免运费</Text>
</View>
</View>
</>
) : previewLoading ? (
<View className="u-empty">加载订单信息…</View>
) : null}
{msg ? (
<Text className="u-muted" style={{ display: 'block', marginTop: 8, textAlign: 'center' }}>
{msg}
</Text>
) : null}
</View>
<View className="order-confirm-bar">
<View className="order-confirm-total">
<Text className="order-confirm-total-label">应付合计</Text>
<Text className="order-confirm-total-value">
¥{preview ? Number(preview.payAmount).toFixed(2) : '—'}
</Text>
</View>
<View
className={`order-confirm-submit${canSubmit ? '' : ' order-confirm-submit--disabled'}`}
onClick={() => {
if (!canSubmit) return;
void submit();
}}
>
<Text>{submitLabel}</Text>
</View>
</View>
</PageShell>
);
}