Files
affiliate_dash/frontend/src/pages/Orders.tsx
T
yml2213 89cdd32181 对接皮肤源头开放接口:查询、发货推送与 HMAC 签名鉴权
- 新增订单查询与发货结果推送开放接口,支持 can_ship 与幂等
- 鉴权采用 X-Api-Key + Timestamp + Nonce + HMAC-SHA256 签名
- 订单扩展发货字段与 ship_logs,管理端增加发货记录与开放文档页
2026-07-20 16:06:44 +08:00

195 lines
5.3 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react'
import {
Button,
Select,
Space,
Table,
Tag,
Typography,
message,
} from 'antd'
import { ReloadOutlined } from '@ant-design/icons'
import type { ColumnsType } from 'antd/es/table'
import dayjs from 'dayjs'
import { orderApi } from '../api'
import { useAuth } from '../store/auth'
import type { Order } from '../types'
const statusMap: Record<string, { color: string; text: string }> = {
pending: { color: 'orange', text: '待支付' },
paid: { color: 'blue', text: '已支付' },
delivering: { color: 'cyan', text: '发货中' },
delivered: { color: 'green', text: '已交付' },
ship_failed: { color: 'red', text: '发货失败' },
cancelled: { color: 'default', text: '已取消' },
}
export default function Orders() {
const { isAdmin } = useAuth()
const [list, setList] = useState<Order[]>([])
const [total, setTotal] = useState(0)
const [page, setPage] = useState(1)
const [size, setSize] = useState(10)
const [status, setStatus] = useState<string | undefined>()
const [loading, setLoading] = useState(false)
const load = useCallback(async () => {
setLoading(true)
try {
const data = await orderApi.list({ page, size, status })
setList(data.list || [])
setTotal(data.total)
} catch (e) {
message.error(e instanceof Error ? e.message : '加载失败')
} finally {
setLoading(false)
}
}, [page, size, status])
useEffect(() => {
load()
}, [load])
const changeStatus = async (id: number, next: string) => {
try {
await orderApi.updateStatus(id, next)
message.success('状态已更新')
load()
} catch (e) {
message.error(e instanceof Error ? e.message : '更新失败')
}
}
const columns: ColumnsType<Order> = [
{ title: '订单号', dataIndex: 'order_no', width: 190, ellipsis: true },
{
title: '皮肤',
dataIndex: ['skin', 'name'],
width: 140,
ellipsis: true,
render: (_, r) => r.skin?.name || `#${r.skin_id}`,
},
{
title: 'SKU',
width: 140,
ellipsis: true,
render: (_, r) => r.skin?.sku || '-',
},
{
title: '分销商',
dataIndex: ['distributor', 'nickname'],
width: 100,
ellipsis: true,
render: (_, r) => r.distributor?.nickname || r.distributor?.username || `#${r.distributor_id}`,
},
{ title: '买家', dataIndex: 'buyer_name', width: 90, ellipsis: true },
{
title: '金额',
dataIndex: 'amount',
width: 90,
render: (v: number) => `¥${Number(v ?? 0).toFixed(2)}`,
},
{
title: '状态',
dataIndex: 'status',
width: 100,
render: (v: string) => {
const s = statusMap[v] || { color: 'default', text: v }
return <Tag color={s.color}>{s.text}</Tag>
},
},
{
title: '上游单号',
dataIndex: 'provider_order_no',
width: 120,
ellipsis: true,
render: (v?: string) => v || '-',
},
{
title: '时间',
dataIndex: 'created_at',
width: 160,
render: (v: string) => dayjs(v).format('YYYY-MM-DD HH:mm'),
},
]
if (isAdmin) {
columns.push({
title: '操作',
key: 'action',
width: 220,
render: (_, record) => (
<Space>
{record.status === 'pending' && (
<>
<Button type="link" size="small" onClick={() => changeStatus(record.id, 'paid')}>
标记已付
</Button>
<Button type="link" size="small" danger onClick={() => changeStatus(record.id, 'cancelled')}>
取消
</Button>
</>
)}
{(record.status === 'paid' || record.status === 'ship_failed' || record.status === 'delivering') && (
<Button type="link" size="small" onClick={() => changeStatus(record.id, 'delivered')}>
标记交付
</Button>
)}
</Space>
),
})
}
return (
<div>
<Space style={{ marginBottom: 16, width: '100%', justifyContent: 'space-between' }}>
<Typography.Title level={4} style={{ margin: 0 }}>
订单管理
</Typography.Title>
<Space>
<Select
allowClear
placeholder="订单状态"
style={{ width: 140 }}
value={status}
onChange={(v) => {
setPage(1)
setStatus(v)
}}
options={[
{ value: 'pending', label: '待支付' },
{ value: 'paid', label: '已支付' },
{ value: 'delivering', label: '发货中' },
{ value: 'delivered', label: '已交付' },
{ value: 'ship_failed', label: '发货失败' },
{ value: 'cancelled', label: '已取消' },
]}
/>
<Button icon={<ReloadOutlined />} onClick={load}>
刷新
</Button>
</Space>
</Space>
<Table
rowKey="id"
loading={loading}
columns={columns}
dataSource={list}
tableLayout="fixed"
scroll={{ x: 1100 }}
pagination={{
current: page,
pageSize: size,
total,
showSizeChanger: true,
onChange: (p, s) => {
setPage(p)
setSize(s)
},
}}
/>
</div>
)
}