feat;提交管理端和城市合伙人端
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { View, Text, Button } from '@tarojs/components';
|
||||
import { useRouter } from '@tarojs/taro';
|
||||
import HqHeader from '../../components/HqHeader';
|
||||
import { request, toast } from '../../lib/api';
|
||||
import { ORDER_STATUS_LABELS, badgeClass, fmtMoney, fmtTime } from '../../lib/constants';
|
||||
|
||||
type StatusLog = { fromStatus?: string; toStatus?: string; createdAt: string };
|
||||
type OrderDetail = {
|
||||
id: string;
|
||||
orderNo: string;
|
||||
status: string;
|
||||
payAmount: number | string;
|
||||
totalAmount?: number | string;
|
||||
quantity?: number;
|
||||
receiverName?: string;
|
||||
receiverPhone?: string;
|
||||
receiverAddress?: string;
|
||||
createdAt: string;
|
||||
product?: { name: string; skuCode: string };
|
||||
city?: { name: string };
|
||||
delivery?: { provider?: string; trackingNo?: string } | null;
|
||||
statusLogs?: StatusLog[];
|
||||
};
|
||||
|
||||
const NEXT: Record<string, Array<{ status: string; label: string }>> = {
|
||||
PENDING_SHIP: [{ status: 'OUT_WAREHOUSE', label: '标记出库' }],
|
||||
OUT_WAREHOUSE: [{ status: 'SHIPPING', label: '标记配送中' }],
|
||||
SHIPPING: [{ status: 'PENDING_RECEIVE', label: '标记待收货' }],
|
||||
PENDING_RECEIVE: [{ status: 'COMPLETED', label: '标记已完成' }],
|
||||
};
|
||||
|
||||
export default function OrderDetailPage() {
|
||||
const router = useRouter();
|
||||
const id = router.params.id;
|
||||
const [order, setOrder] = useState<OrderDetail | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
function load() {
|
||||
if (!id) return;
|
||||
request<OrderDetail>(`/admin/orders/${id}`).then(setOrder).catch(() => undefined);
|
||||
}
|
||||
|
||||
useEffect(load, [id]);
|
||||
|
||||
async function transition(status: string) {
|
||||
if (!id || saving) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
const d = await request<OrderDetail>(`/admin/orders/${id}/status`, { method: 'PUT', data: { status } });
|
||||
setOrder(d);
|
||||
toast('订单状态已更新', 'success');
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : '操作失败');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
const actions = order ? NEXT[order.status] ?? [] : [];
|
||||
|
||||
return (
|
||||
<View className="hq-page" style={actions.length ? 'padding-bottom:calc(96px + var(--hq-safe-bottom))' : ''}>
|
||||
<HqHeader title="订单详情" back />
|
||||
|
||||
{!order && <View className="hq-empty">加载中…</View>}
|
||||
|
||||
{order && (
|
||||
<>
|
||||
<View className="hq-card">
|
||||
<View className="hq-row">
|
||||
<Text style="font-size:13px;color:var(--hq-muted)">{order.orderNo}</Text>
|
||||
<Text className={`hq-badge ${badgeClass(order.status)}`}>
|
||||
{ORDER_STATUS_LABELS[order.status] || order.status}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style="display:block;margin-top:12px;font-size:15px;font-weight:600">{order.product?.name || '—'}</Text>
|
||||
<Text className="hq-muted" style="font-size:12px">SKU {order.product?.skuCode || '—'} · 数量 {order.quantity ?? 1}</Text>
|
||||
<View className="hq-row" style="margin-top:12px">
|
||||
<Text className="hq-muted" style="font-size:13px">实付金额</Text>
|
||||
<Text style="font-size:18px;font-weight:700;color:var(--hq-red)">¥{fmtMoney(order.payAmount)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="hq-card">
|
||||
<View className="hq-row" style="margin-bottom:8px">
|
||||
<Text className="hq-muted" style="font-size:13px">收货人</Text>
|
||||
<Text style="font-size:14px">{order.receiverName || '—'} {order.receiverPhone || ''}</Text>
|
||||
</View>
|
||||
<View className="hq-row" style="margin-bottom:8px">
|
||||
<Text className="hq-muted" style="font-size:13px">收货地址</Text>
|
||||
<Text style="font-size:14px;text-align:right;max-width:60%">{order.receiverAddress || '—'}</Text>
|
||||
</View>
|
||||
<View className="hq-row" style="margin-bottom:8px">
|
||||
<Text className="hq-muted" style="font-size:13px">所属城市</Text>
|
||||
<Text style="font-size:14px">{order.city?.name || '—'}</Text>
|
||||
</View>
|
||||
<View className="hq-row">
|
||||
<Text className="hq-muted" style="font-size:13px">物流</Text>
|
||||
<Text style="font-size:14px">{order.delivery?.provider || '—'} {order.delivery?.trackingNo || ''}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text className="hq-section-title">状态流转</Text>
|
||||
<View className="hq-card">
|
||||
{(order.statusLogs ?? []).length === 0 && <Text className="hq-muted">暂无记录</Text>}
|
||||
{(order.statusLogs ?? []).map((log, i) => (
|
||||
<View key={i} className="hq-row" style="padding:8px 0;border-bottom:1px solid var(--hq-line)">
|
||||
<Text style="font-size:13px">
|
||||
{ORDER_STATUS_LABELS[log.toStatus || ''] || log.toStatus}
|
||||
</Text>
|
||||
<Text className="hq-muted" style="font-size:12px">{fmtTime(log.createdAt)}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{actions.length > 0 && (
|
||||
<View className="hq-footer-bar">
|
||||
{actions.map((a) => (
|
||||
<Button
|
||||
key={a.status}
|
||||
className="hq-btn hq-btn--primary hq-btn--block"
|
||||
disabled={saving}
|
||||
onClick={() => transition(a.status)}
|
||||
>
|
||||
{a.label}
|
||||
</Button>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { View, Text, ScrollView } from '@tarojs/components';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import HqHeader from '../../components/HqHeader';
|
||||
import { request, type Paginated } from '../../lib/api';
|
||||
import { useHqSession } from '../../lib/session';
|
||||
import { ORDER_STATUS_LABELS, badgeClass, fmtMoney, fmtTime } from '../../lib/constants';
|
||||
|
||||
type OrderRow = {
|
||||
id: string;
|
||||
orderNo: string;
|
||||
status: string;
|
||||
deliveryType?: string;
|
||||
payAmount: number | string;
|
||||
receiverName?: string;
|
||||
receiverPhone?: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
const TABS = [
|
||||
{ key: '', label: '全部' },
|
||||
{ key: 'PENDING_PAY', label: '待付款' },
|
||||
{ key: 'PENDING_SHIP', label: '待发货' },
|
||||
{ key: 'SHIPPING', label: '配送中' },
|
||||
{ key: 'COMPLETED', label: '已完成' },
|
||||
{ key: 'REFUNDING', label: '退款中' },
|
||||
];
|
||||
|
||||
export default function OrdersPage() {
|
||||
useHqSession();
|
||||
const [status, setStatus] = useState('');
|
||||
const [rows, setRows] = useState<OrderRow[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
function load() {
|
||||
setLoading(true);
|
||||
const qs = new URLSearchParams({ pageSize: '50' });
|
||||
if (status) qs.set('status', status);
|
||||
request<Paginated<OrderRow>>(`/admin/orders?${qs.toString()}`)
|
||||
.then((d) => {
|
||||
setRows(d.items);
|
||||
setTotal(d.total);
|
||||
})
|
||||
.catch(() => setRows([]))
|
||||
.finally(() => setLoading(false));
|
||||
}
|
||||
|
||||
useEffect(load, [status]);
|
||||
useDidShow(load);
|
||||
|
||||
return (
|
||||
<View className="hq-page">
|
||||
<HqHeader title="订单中心" back />
|
||||
|
||||
<ScrollView scrollX enhanced showScrollbar={false} className="hq-tabs">
|
||||
{TABS.map((t) => (
|
||||
<View
|
||||
key={t.key}
|
||||
className={`hq-tab${status === t.key ? ' hq-tab--active' : ''}`}
|
||||
onClick={() => setStatus(t.key)}
|
||||
>
|
||||
<Text>{t.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
|
||||
<Text className="hq-section-title">
|
||||
<Text>订单列表</Text>
|
||||
<Text className="hq-muted" style="font-size:12px;font-weight:400">共 {total} 单</Text>
|
||||
</Text>
|
||||
|
||||
{loading && <View className="hq-empty">加载中…</View>}
|
||||
{!loading && rows.length === 0 && <View className="hq-empty">暂无订单</View>}
|
||||
|
||||
{rows.map((o) => (
|
||||
<View
|
||||
key={o.id}
|
||||
className="hq-card"
|
||||
style="margin-top:8px;margin-bottom:0"
|
||||
onClick={() => Taro.navigateTo({ url: `/pages/orders/detail?id=${o.id}` })}
|
||||
>
|
||||
<View className="hq-row">
|
||||
<Text style="font-size:13px;color:var(--hq-muted)">{o.orderNo}</Text>
|
||||
<Text className={`hq-badge ${badgeClass(o.status)}`}>{ORDER_STATUS_LABELS[o.status] || o.status}</Text>
|
||||
</View>
|
||||
<View className="hq-row" style="margin-top:10px">
|
||||
<Text style="font-size:14px">{o.receiverName || '—'} · {o.receiverPhone || ''}</Text>
|
||||
<Text style="font-size:16px;font-weight:700;color:var(--hq-red)">¥{fmtMoney(o.payAmount)}</Text>
|
||||
</View>
|
||||
<Text className="hq-muted" style="font-size:11px;display:block;margin-top:6px">{fmtTime(o.createdAt)}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user