作废链接和取消订单

This commit is contained in:
yml2213
2026-08-14 12:58:20 +08:00
parent 33a30fcd99
commit ee3e427ab7
11 changed files with 364 additions and 39 deletions
+2
View File
@@ -85,6 +85,8 @@ export const merchantApi = {
game_account?: string
note?: string
}) => request.post('/merchant/orders/manual', data).then((r) => r.data.data as CreateManualOrderResult),
cancelOrder: (orderNo: string, reason: string) =>
request.post(`/merchant/orders/${encodeURIComponent(orderNo)}/cancel`, { reason }).then((r) => r.data.data as { order: FulfillmentOrder; refund_amount: number }),
getDeliveryLink: (orderNo: string) =>
request.get(`/merchant/orders/${encodeURIComponent(orderNo)}/delivery-link`).then((r) => r.data.data as DeliveryLinkResult),
revokeDeliveryLink: (orderNo: string) =>
+8 -2
View File
@@ -131,7 +131,7 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
message.success('发货链接已作废')
patchDeliveryLinkState(orderNo, { delivery_link_revoked_at: revokedAt })
})
.catch((e) => message.error(e instanceof Error ? e.message : '作废失败'))
.catch((e) => message.error(e instanceof Error ? e.message : '链接作废失败'))
}, [patchDeliveryLinkState])
const restoreDeliveryLink = useCallback((orderNo: string) => {
@@ -355,6 +355,12 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
loadOrders(1, orders.size, orderSource, filters).catch((e) => message.error(e instanceof Error ? e.message : '订单加载失败'))
}
const cancelOrder = useCallback(async (orderNo: string, reason: string) => {
const result = await merchantApi.cancelOrder(orderNo, reason)
message.success(`订单已取消,已退还 ${Number(result.refund_amount || 0)} 积分`)
await loadOrders()
}, [loadOrders])
const submitAPIClient = async () => {
const values = await apiClientForm.validateFields()
try {
@@ -510,7 +516,7 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
<ProductsTab loading={loading} products={products} canManage={canManage} onToggleProduct={openProductToggle} onLoadProducts={loadProducts} />
) },
{ key: 'orders', label: '发货订单', disabled: !hasFeature('orders'), children: (
<OrdersTab loading={loading} orders={orders} orderSource={orderSource} filters={orderFilters} canManage={canOrderManage} onCreateManualOrder={openManualOrderCreate} onOrderSourceChange={changeOrderSource} onFilterChange={changeOrderFilters} onLoadOrders={loadOrders} onCopyLink={copyDeliveryLink} onOpenLink={openDeliveryLink} onRevokeLink={revokeDeliveryLink} onRestoreLink={restoreDeliveryLink} />
<OrdersTab loading={loading} orders={orders} orderSource={orderSource} filters={orderFilters} canManage={canOrderManage} onCreateManualOrder={openManualOrderCreate} onOrderSourceChange={changeOrderSource} onFilterChange={changeOrderFilters} onLoadOrders={loadOrders} onCopyLink={copyDeliveryLink} onOpenLink={openDeliveryLink} onRevokeLink={revokeDeliveryLink} onRestoreLink={restoreDeliveryLink} onCancelOrder={cancelOrder} />
) },
{ key: 'wallet', label: '钱包', disabled: !hasFeature('wallet'), children: (
<WalletTab loading={loading} wallet={wallet} canFinance={canFinance} ledger={ledger} form={walletFilterForm} onFilter={handleWalletFilter} onResetFilter={handleWalletFilterReset} onLoadLedger={handleLoadLedger} />
+101 -12
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { Button, Card, Col, Form, Input, Popconfirm, Row, Select, Space, Table, Tabs, Tag, Typography, message } from 'antd'
import { Alert, Button, Card, Col, Form, Input, Modal, Popconfirm, Row, Select, Space, Table, Tabs, Tag, Typography, message } from 'antd'
import type { FormInstance } from 'antd'
import {
ClockCircleOutlined,
@@ -183,6 +183,7 @@ export function OrdersTab({
onOpenLink,
onRevokeLink,
onRestoreLink,
onCancelOrder,
}: {
loading: boolean
orders: PageResult<FulfillmentOrder>
@@ -197,8 +198,12 @@ export function OrdersTab({
onOpenLink: (orderNo: string) => void
onRevokeLink: (orderNo: string) => void
onRestoreLink: (orderNo: string) => void
onCancelOrder: (orderNo: string, reason: string) => Promise<void> | void
}) {
const [keyword, setKeyword] = useState(filters.keyword)
const [cancelling, setCancelling] = useState<FulfillmentOrder | null>(null)
const [cancelReason, setCancelReason] = useState('')
const [cancelSubmitting, setCancelSubmitting] = useState(false)
useEffect(() => {
setKeyword(filters.keyword)
@@ -206,6 +211,22 @@ export function OrdersTab({
const submitKeyword = () => onFilterChange({ ...filters, keyword: keyword.trim() })
const submitCancel = async () => {
if (!cancelling) return
if (!cancelReason.trim()) {
message.warning('请填写取消原因')
return
}
setCancelSubmitting(true)
try {
await onCancelOrder(cancelling.order_no, cancelReason.trim())
setCancelling(null)
setCancelReason('')
} finally {
setCancelSubmitting(false)
}
}
const orderColumns: ColumnsType<FulfillmentOrder> = [
{
title: '平台订单号',
@@ -237,16 +258,55 @@ export function OrdersTab({
{
title: '发货链接',
key: 'delivery_link',
width: 150,
render: (_, record) => record.delivery_link_revoked_at ? (
<Button type="link" size="small" onClick={() => onRestoreLink(record.order_no)}></Button>
) : (
<Space size={4} wrap>
<Button type="link" size="small" onClick={() => onCopyLink(record.order_no)}></Button>
<Button type="link" size="small" onClick={() => onOpenLink(record.order_no)}></Button>
<Button type="link" danger size="small" onClick={() => onRevokeLink(record.order_no)}></Button>
</Space>
),
width: 160,
render: (_, record) => {
if (record.order_status === 'cancelled') {
return <Typography.Text type="secondary"></Typography.Text>
}
if (record.delivery_link_revoked_at) {
return <Button type="link" size="small" onClick={() => onRestoreLink(record.order_no)}></Button>
}
return (
<Space size={4} wrap>
<Button type="link" size="small" onClick={() => onCopyLink(record.order_no)}></Button>
<Button type="link" size="small" onClick={() => onOpenLink(record.order_no)}></Button>
<Button
type="link"
danger
size="small"
title="仅作废发货链接,不影响订单状态与积分"
onClick={() => onRevokeLink(record.order_no)}
>
</Button>
</Space>
)
},
},
{
title: '操作',
key: 'action',
width: 110,
fixed: 'right',
render: (_, record) => {
if (!canManage) return '-'
if (record.order_status === 'paid' || record.order_status === 'ship_failed') {
return (
<Button
type="link"
danger
size="small"
onClick={() => {
setCancelling(record)
setCancelReason('')
}}
>
</Button>
)
}
return '-'
},
},
]
@@ -300,9 +360,38 @@ export function OrdersTab({
columns={orderColumns}
dataSource={orders.list}
tableLayout="fixed"
scroll={{ x: 1660 }}
scroll={{ x: 1780 }}
pagination={pageConfig(orders, onLoadOrders)}
/>
<Modal
title={`取消订单:${cancelling?.order_no || ''}`}
open={!!cancelling}
confirmLoading={cancelSubmitting}
onOk={submitCancel}
onCancel={() => {
setCancelling(null)
setCancelReason('')
}}
okText="确认取消并退款"
okButtonProps={{ danger: true }}
destroyOnClose
>
<Alert
type="warning"
showIcon
message="取消后订单将不可再发货"
description="已扣积分将全额退还商户钱包,商品库存自动回补;已提交上游发货的失败订单不可取消。"
style={{ marginBottom: 16 }}
/>
<Input.TextArea
rows={3}
maxLength={512}
showCount
value={cancelReason}
onChange={(e) => setCancelReason(e.target.value)}
placeholder="请填写取消原因(必填)"
/>
</Modal>
</Space>
)
}