优化订单履约后台界面
This commit is contained in:
@@ -37,6 +37,7 @@ import StatusTag from '@/components/admin/StatusTag'
|
||||
import {
|
||||
closeAdminTask,
|
||||
completeAdminTaskManualDispatch,
|
||||
consumeAdminTaskKuaishouIndustryVoucher,
|
||||
dispatchAdminTaskKuaishouCloudFulfillment,
|
||||
fetchAdminTaskDetail,
|
||||
fetchAdminTaskScreenshot,
|
||||
@@ -44,6 +45,7 @@ import {
|
||||
prepareAdminTaskKuaishouCloudFulfillment,
|
||||
rebindAdminTaskKuaishouCloudRole,
|
||||
refreshAdminTaskKuaishouCloudRoleInfo,
|
||||
resendAdminTaskKuaishouIndustryVoucherCode,
|
||||
retryAdminTask,
|
||||
returnNumberAdminTaskKuaishouCloudFulfillment,
|
||||
} from '@/services/admin'
|
||||
@@ -59,6 +61,7 @@ import {
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
type TaskEvent = AdminTaskDetail['events'][number]
|
||||
type KuaishouIndustryVoucher = NonNullable<AdminTaskDetail['kuaishouIndustryVoucher']>
|
||||
type ManualDispatchForm = {
|
||||
deliveryReference: string
|
||||
deliveredCredential: string
|
||||
@@ -252,6 +255,12 @@ export default function AdminTaskDetailPage() {
|
||||
}
|
||||
/>
|
||||
|
||||
<FulfillmentOverviewPanel
|
||||
detail={resolvedDetail}
|
||||
claimUrl={claimUrl}
|
||||
claimLinkInvalid={claimLinkInvalid}
|
||||
/>
|
||||
|
||||
<TaskActionPanel
|
||||
detail={resolvedDetail}
|
||||
canManageTaskLifecycle={canManageTaskLifecycle}
|
||||
@@ -297,6 +306,22 @@ export default function AdminTaskDetailPage() {
|
||||
`确认退还任务 ${resolvedDetail.task.taskNo} 当前使用的虚拟号吗?退号后该流程会正式收口。`,
|
||||
)
|
||||
}
|
||||
onResendIndustryVoucher={() =>
|
||||
runTaskAction(
|
||||
'resend-industry-voucher',
|
||||
() => resendAdminTaskKuaishouIndustryVoucherCode(resolvedDetail.task.taskId),
|
||||
'电子凭证发码回调已重发',
|
||||
`确认重新发送任务 ${resolvedDetail.task.taskNo} 的电子凭证发码回调吗?`,
|
||||
)
|
||||
}
|
||||
onConsumeIndustryVoucher={() =>
|
||||
runTaskAction(
|
||||
'consume-industry-voucher',
|
||||
() => consumeAdminTaskKuaishouIndustryVoucher(resolvedDetail.task.taskId),
|
||||
'电子凭证已执行核销',
|
||||
`确认立即核销任务 ${resolvedDetail.task.taskNo} 绑定的电子凭证吗?`,
|
||||
)
|
||||
}
|
||||
onMarkManualReview={() =>
|
||||
runTaskAction(
|
||||
'manual-review',
|
||||
@@ -405,6 +430,15 @@ export default function AdminTaskDetailPage() {
|
||||
<Empty description="无快手 Cloud 履约上下文" />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'industry-voucher',
|
||||
label: '电子凭证',
|
||||
children: resolvedDetail.kuaishouIndustryVoucher ? (
|
||||
<KuaishouIndustryVoucherPanel voucher={resolvedDetail.kuaishouIndustryVoucher} />
|
||||
) : (
|
||||
<Empty description="无电子凭证绑定" />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'resolution',
|
||||
label: '兑换重试',
|
||||
@@ -457,6 +491,8 @@ function TaskActionPanel({
|
||||
onPrepareKuaishouCloud,
|
||||
onDispatchKuaishouCloud,
|
||||
onReturnKuaishouCloud,
|
||||
onResendIndustryVoucher,
|
||||
onConsumeIndustryVoucher,
|
||||
onMarkManualReview,
|
||||
onCloseTask,
|
||||
}: {
|
||||
@@ -469,94 +505,147 @@ function TaskActionPanel({
|
||||
onPrepareKuaishouCloud: () => void
|
||||
onDispatchKuaishouCloud: () => void
|
||||
onReturnKuaishouCloud: () => void
|
||||
onResendIndustryVoucher: () => void
|
||||
onConsumeIndustryVoucher: () => void
|
||||
onMarkManualReview: () => void
|
||||
onCloseTask: () => void
|
||||
}) {
|
||||
const operations = detail.operations
|
||||
const loading = Boolean(actionLoadingKey)
|
||||
const hasCloudActions =
|
||||
operations.canPrepareKuaishouCloudFulfillment ||
|
||||
operations.canRebindKuaishouCloudRole ||
|
||||
operations.canDispatchKuaishouCloudFulfillment ||
|
||||
operations.canReturnKuaishouCloudFulfillment
|
||||
const hasIndustryVoucherActions =
|
||||
operations.canResendKuaishouIndustryVoucherCode ||
|
||||
operations.canConsumeKuaishouIndustryVoucher ||
|
||||
Boolean(detail.kuaishouIndustryVoucher)
|
||||
|
||||
return (
|
||||
<Card title="任务操作">
|
||||
<Space wrap>
|
||||
<div className="task-action-groups">
|
||||
{canManageTaskLifecycle ? (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<ReloadOutlined />}
|
||||
disabled={loading || !operations.canRetry}
|
||||
loading={actionLoadingKey === 'retry'}
|
||||
onClick={onRetry}
|
||||
>
|
||||
重试任务
|
||||
</Button>
|
||||
<section className="task-action-group">
|
||||
<Typography.Text type="secondary">通用</Typography.Text>
|
||||
<Space wrap>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<ReloadOutlined />}
|
||||
disabled={loading || !operations.canRetry}
|
||||
loading={actionLoadingKey === 'retry'}
|
||||
onClick={onRetry}
|
||||
>
|
||||
重试任务
|
||||
</Button>
|
||||
<Button
|
||||
icon={<UserSwitchOutlined />}
|
||||
disabled={loading || !operations.canMarkManualReview}
|
||||
loading={actionLoadingKey === 'manual-review'}
|
||||
onClick={onMarkManualReview}
|
||||
>
|
||||
转人工
|
||||
</Button>
|
||||
</Space>
|
||||
</section>
|
||||
) : null}
|
||||
{operations.canRebindKuaishouCloudRole ? (
|
||||
<Button
|
||||
icon={<SwapOutlined />}
|
||||
disabled={loading}
|
||||
loading={actionLoadingKey === 'rebind-role'}
|
||||
onClick={onRebindRole}
|
||||
>
|
||||
换绑角色
|
||||
</Button>
|
||||
|
||||
{hasCloudActions ? (
|
||||
<section className="task-action-group">
|
||||
<Typography.Text type="secondary">快手 Cloud</Typography.Text>
|
||||
<Space wrap>
|
||||
{canManageTaskLifecycle && operations.canPrepareKuaishouCloudFulfillment ? (
|
||||
<Button
|
||||
type="primary"
|
||||
ghost
|
||||
icon={<ToolOutlined />}
|
||||
disabled={loading}
|
||||
loading={actionLoadingKey === 'prepare-kuaishou-cloud'}
|
||||
onClick={onPrepareKuaishouCloud}
|
||||
>
|
||||
准备资源
|
||||
</Button>
|
||||
) : null}
|
||||
{operations.canRebindKuaishouCloudRole ? (
|
||||
<Button
|
||||
icon={<SwapOutlined />}
|
||||
disabled={loading}
|
||||
loading={actionLoadingKey === 'rebind-role'}
|
||||
onClick={onRebindRole}
|
||||
>
|
||||
换绑角色
|
||||
</Button>
|
||||
) : null}
|
||||
{canManageTaskLifecycle && operations.canDispatchKuaishouCloudFulfillment ? (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SendOutlined />}
|
||||
disabled={loading}
|
||||
loading={actionLoadingKey === 'dispatch-kuaishou-cloud'}
|
||||
onClick={onDispatchKuaishouCloud}
|
||||
>
|
||||
发货
|
||||
</Button>
|
||||
) : null}
|
||||
{canManageTaskLifecycle && operations.canReturnKuaishouCloudFulfillment ? (
|
||||
<Button
|
||||
icon={<RollbackOutlined />}
|
||||
disabled={loading}
|
||||
loading={actionLoadingKey === 'return-kuaishou-cloud'}
|
||||
onClick={onReturnKuaishouCloud}
|
||||
>
|
||||
退号
|
||||
</Button>
|
||||
) : null}
|
||||
</Space>
|
||||
</section>
|
||||
) : null}
|
||||
{canManageTaskLifecycle && operations.canPrepareKuaishouCloudFulfillment ? (
|
||||
<Button
|
||||
type="primary"
|
||||
ghost
|
||||
icon={<ToolOutlined />}
|
||||
disabled={loading}
|
||||
loading={actionLoadingKey === 'prepare-kuaishou-cloud'}
|
||||
onClick={onPrepareKuaishouCloud}
|
||||
>
|
||||
准备绑定资源
|
||||
</Button>
|
||||
) : null}
|
||||
{canManageTaskLifecycle && operations.canDispatchKuaishouCloudFulfillment ? (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SendOutlined />}
|
||||
disabled={loading}
|
||||
loading={actionLoadingKey === 'dispatch-kuaishou-cloud'}
|
||||
onClick={onDispatchKuaishouCloud}
|
||||
>
|
||||
确认绑定完成并发货
|
||||
</Button>
|
||||
) : null}
|
||||
{canManageTaskLifecycle && operations.canReturnKuaishouCloudFulfillment ? (
|
||||
<Button
|
||||
icon={<RollbackOutlined />}
|
||||
disabled={loading}
|
||||
loading={actionLoadingKey === 'return-kuaishou-cloud'}
|
||||
onClick={onReturnKuaishouCloud}
|
||||
>
|
||||
退还号码
|
||||
</Button>
|
||||
) : null}
|
||||
{canManageTaskLifecycle ? (
|
||||
<Button
|
||||
icon={<UserSwitchOutlined />}
|
||||
disabled={loading || !operations.canMarkManualReview}
|
||||
loading={actionLoadingKey === 'manual-review'}
|
||||
onClick={onMarkManualReview}
|
||||
>
|
||||
转人工
|
||||
</Button>
|
||||
|
||||
{canManageTaskLifecycle && hasIndustryVoucherActions ? (
|
||||
<section className="task-action-group">
|
||||
<Typography.Text type="secondary">电子凭证</Typography.Text>
|
||||
<Space wrap>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
disabled={loading || !operations.canResendKuaishouIndustryVoucherCode}
|
||||
loading={actionLoadingKey === 'resend-industry-voucher'}
|
||||
onClick={onResendIndustryVoucher}
|
||||
>
|
||||
重发发码
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
ghost
|
||||
icon={<CheckCircleOutlined />}
|
||||
disabled={loading || !operations.canConsumeKuaishouIndustryVoucher}
|
||||
loading={actionLoadingKey === 'consume-industry-voucher'}
|
||||
onClick={onConsumeIndustryVoucher}
|
||||
>
|
||||
核销凭证
|
||||
</Button>
|
||||
</Space>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{canCloseTasks ? (
|
||||
<Button
|
||||
danger
|
||||
icon={<CloseCircleOutlined />}
|
||||
disabled={loading || !operations.canClose}
|
||||
loading={actionLoadingKey === 'close'}
|
||||
onClick={onCloseTask}
|
||||
>
|
||||
关闭任务
|
||||
</Button>
|
||||
<section className="task-action-group">
|
||||
<Typography.Text type="secondary">收口</Typography.Text>
|
||||
<Button
|
||||
danger
|
||||
icon={<CloseCircleOutlined />}
|
||||
disabled={loading || !operations.canClose}
|
||||
loading={actionLoadingKey === 'close'}
|
||||
onClick={onCloseTask}
|
||||
>
|
||||
关闭任务
|
||||
</Button>
|
||||
</section>
|
||||
) : null}
|
||||
{!canManageTaskLifecycle && !canCloseTasks && !operations.canRebindKuaishouCloudRole ? (
|
||||
|
||||
{!canManageTaskLifecycle && !canCloseTasks && !hasCloudActions ? (
|
||||
<Typography.Text type="secondary">当前账号没有可执行的任务操作</Typography.Text>
|
||||
) : null}
|
||||
</Space>
|
||||
</div>
|
||||
{loading ? (
|
||||
<Typography.Text className="task-action-hint" type="secondary">
|
||||
操作执行中,请稍候。
|
||||
@@ -566,6 +655,236 @@ function TaskActionPanel({
|
||||
)
|
||||
}
|
||||
|
||||
function FulfillmentOverviewPanel({
|
||||
detail,
|
||||
claimUrl,
|
||||
claimLinkInvalid,
|
||||
}: {
|
||||
detail: AdminTaskDetail
|
||||
claimUrl: string
|
||||
claimLinkInvalid: boolean
|
||||
}) {
|
||||
const executor = resolveTaskExecutorDisplay(detail.task.executorKey)
|
||||
const steps = buildFulfillmentOverviewSteps(detail, claimUrl, claimLinkInvalid)
|
||||
const currentBlocker = resolveTaskBlocker(detail.task, claimUrl, claimLinkInvalid)
|
||||
|
||||
return (
|
||||
<Card
|
||||
title="履约链路总览"
|
||||
extra={<Tag color={executor.color}>{executor.label}</Tag>}
|
||||
>
|
||||
<div className="fulfillment-overview-grid">
|
||||
{steps.map((step) => (
|
||||
<div key={step.key} className="fulfillment-step-card">
|
||||
<Space size={[6, 6]} wrap>
|
||||
<StatusTag value={step.status} />
|
||||
<Typography.Text strong>{step.label}</Typography.Text>
|
||||
</Space>
|
||||
<Typography.Text type="secondary">{step.detail}</Typography.Text>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Alert
|
||||
className="platform-section-gap"
|
||||
type={currentBlocker.level}
|
||||
showIcon
|
||||
message={currentBlocker.title}
|
||||
description={currentBlocker.detail}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function buildFulfillmentOverviewSteps(
|
||||
detail: AdminTaskDetail,
|
||||
claimUrl: string,
|
||||
claimLinkInvalid: boolean,
|
||||
) {
|
||||
const task = detail.task
|
||||
const executor = resolveTaskExecutorDisplay(task.executorKey)
|
||||
const voucher = detail.kuaishouIndustryVoucher
|
||||
const manualDispatch = detail.manualDispatch
|
||||
const claimStatus = claimUrl
|
||||
? 'success'
|
||||
: claimLinkInvalid
|
||||
? 'failed'
|
||||
: detail.claimToken?.status || 'pending'
|
||||
|
||||
return [
|
||||
{
|
||||
key: 'source',
|
||||
label: '订单来源',
|
||||
status: detail.order?.payStatus || task.status,
|
||||
detail: `${detail.order?.provider || '-'} / ${detail.order?.platform || '-'} / ${task.platformOrderId || '-'}`,
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
label: '商品解析',
|
||||
status: detail.orderItem || task.skuName ? 'success' : 'pending_config',
|
||||
detail: detail.orderItem?.skuName || task.skuName || task.skuCode || '未命中履约商品',
|
||||
},
|
||||
{
|
||||
key: 'planner',
|
||||
label: '履约计划',
|
||||
status: task.executorKey ? 'success' : 'pending_config',
|
||||
detail: executor.label,
|
||||
},
|
||||
{
|
||||
key: 'prepare',
|
||||
label: '任务准备',
|
||||
status: task.resourceStatus || task.status,
|
||||
detail: resolveTaskPrepareSummary(detail),
|
||||
},
|
||||
{
|
||||
key: 'delivery',
|
||||
label: '平台执行',
|
||||
status: resolveDeliveryStepStatus(detail),
|
||||
detail: resolveDeliveryStepSummary(detail),
|
||||
},
|
||||
{
|
||||
key: 'voucher',
|
||||
label: '电子凭证',
|
||||
status: voucher ? voucher.sendCallbackStatus || voucher.status : 'skipped',
|
||||
detail: voucher
|
||||
? `发码 ${voucher.sendCallbackStatus || '-'} / 凭证 ${voucher.status || '-'}`
|
||||
: '当前任务未绑定电子凭证',
|
||||
},
|
||||
{
|
||||
key: 'claim',
|
||||
label: '交付链接',
|
||||
status: task.executorKey === 'manual_dispatch' ? 'skipped' : claimStatus,
|
||||
detail:
|
||||
task.executorKey === 'manual_dispatch'
|
||||
? '人工履约不需要客户领取链接'
|
||||
: claimUrl
|
||||
? '已有可用链接'
|
||||
: claimLinkInvalid
|
||||
? '链接已失效'
|
||||
: '等待生成或同步链接',
|
||||
},
|
||||
{
|
||||
key: 'finish',
|
||||
label: '履约收口',
|
||||
status: manualDispatch?.outcome || task.deliveryStatus || task.status,
|
||||
detail: task.resultMessage || task.lastError || '等待履约完成',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function resolveTaskBlocker(
|
||||
task: AdminTaskDetail['task'],
|
||||
claimUrl: string,
|
||||
claimLinkInvalid: boolean,
|
||||
): { level: 'success' | 'info' | 'warning' | 'error'; title: string; detail: string } {
|
||||
if (['completed', 'redeemed', 'delivered'].includes(task.status) || task.deliveryStatus === 'delivered') {
|
||||
return {
|
||||
level: 'success',
|
||||
title: '履约已完成',
|
||||
detail: task.resultMessage || '任务已经收口。',
|
||||
}
|
||||
}
|
||||
|
||||
if (['manual_review', 'retry_pending', 'failed'].includes(task.status)) {
|
||||
return {
|
||||
level: 'error',
|
||||
title: '当前卡在异常处理',
|
||||
detail: task.lastError || task.resultMessage || '需要人工检查后重试或收口。',
|
||||
}
|
||||
}
|
||||
|
||||
if (claimLinkInvalid) {
|
||||
return {
|
||||
level: 'warning',
|
||||
title: '领取链接不可用',
|
||||
detail: '链接状态不是 active,可重新生成或检查平台交付链接。',
|
||||
}
|
||||
}
|
||||
|
||||
if (!claimUrl && task.executorKey !== 'manual_dispatch') {
|
||||
return {
|
||||
level: 'warning',
|
||||
title: '等待交付链接',
|
||||
detail: '任务尚未拿到可交付链接,先检查任务准备和平台执行状态。',
|
||||
}
|
||||
}
|
||||
|
||||
if (task.customerStatus === 'waiting_customer') {
|
||||
return {
|
||||
level: 'info',
|
||||
title: '等待客户操作',
|
||||
detail: '资源已准备,客户还没有完成绑定或领取动作。',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
level: 'info',
|
||||
title: '履约推进中',
|
||||
detail: task.lastError || '当前任务没有明确异常,可按链路状态继续观察。',
|
||||
}
|
||||
}
|
||||
|
||||
function resolveTaskPrepareSummary(detail: AdminTaskDetail) {
|
||||
const flow = detail.kuaishouCloudFulfillment
|
||||
if (flow) {
|
||||
return flow.binding.bindPreparedAt
|
||||
? `绑定资源 ${flow.binding.vnPhone || flow.binding.vnId || '-'}`
|
||||
: '等待准备 Cloud 资源'
|
||||
}
|
||||
|
||||
if (detail.task.executorKey === 'manual_dispatch') {
|
||||
return '人工履约任务无需自动准备资源'
|
||||
}
|
||||
|
||||
return detail.task.resultMessage || detail.task.lastError || '等待执行器准备'
|
||||
}
|
||||
|
||||
function resolveDeliveryStepStatus(detail: AdminTaskDetail) {
|
||||
const flow = detail.kuaishouCloudFulfillment
|
||||
if (flow) {
|
||||
return flow.dispatch.status || detail.task.deliveryStatus || detail.task.status
|
||||
}
|
||||
|
||||
if (detail.manualDispatch) {
|
||||
return detail.manualDispatch.outcome || detail.task.deliveryStatus
|
||||
}
|
||||
|
||||
return detail.task.deliveryStatus || detail.task.status
|
||||
}
|
||||
|
||||
function resolveDeliveryStepSummary(detail: AdminTaskDetail) {
|
||||
const flow = detail.kuaishouCloudFulfillment
|
||||
if (flow) {
|
||||
if (flow.dispatch.dispatchAt) {
|
||||
return `已于 ${formatAdminDateTime(flow.dispatch.dispatchAt)} 发货`
|
||||
}
|
||||
return flow.dispatch.note || '等待平台发货执行'
|
||||
}
|
||||
|
||||
if (detail.manualDispatch) {
|
||||
return detail.manualDispatch.resultMessage || detail.manualDispatch.deliveryReference || '人工履约已回写'
|
||||
}
|
||||
|
||||
return detail.task.resultMessage || detail.task.lastError || '等待执行器推进'
|
||||
}
|
||||
|
||||
function resolveTaskExecutorDisplay(value: unknown) {
|
||||
const executorKey = String(value || '').trim()
|
||||
if (executorKey === 'kuaishou_ct_assisted') {
|
||||
return { label: '快手 Cloud', color: 'blue' }
|
||||
}
|
||||
if (executorKey === 'kuaishou-industry') {
|
||||
return { label: '行业电子凭证', color: 'cyan' }
|
||||
}
|
||||
if (executorKey === 'kuaishou_feifei') {
|
||||
return { label: 'kuaishou-feifei', color: 'purple' }
|
||||
}
|
||||
if (executorKey === 'manual_dispatch') {
|
||||
return { label: '人工履约', color: 'orange' }
|
||||
}
|
||||
return { label: executorKey || '未分配执行器', color: 'default' }
|
||||
}
|
||||
|
||||
function ManualDispatchPanel({
|
||||
detail,
|
||||
form,
|
||||
@@ -680,6 +999,65 @@ function ManualDispatchPanel({
|
||||
)
|
||||
}
|
||||
|
||||
function KuaishouIndustryVoucherPanel({ voucher }: { voucher: KuaishouIndustryVoucher }) {
|
||||
const consumeDetails = Array.isArray(voucher.consumeDetails) ? voucher.consumeDetails : []
|
||||
|
||||
return (
|
||||
<Card title="电子凭证">
|
||||
<Descriptions column={{ xs: 1, md: 2, xl: 3 }} bordered size="small">
|
||||
<Descriptions.Item label="订单号">{voucher.oid || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="凭证码">{voucher.voucherCode || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="序号">{voucher.unitIndex || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="凭证状态">
|
||||
<StatusTag value={voucher.status} />
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="发码回调">
|
||||
<StatusTag value={voucher.sendCallbackStatus} />
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="发码次数">{voucher.sendCallbackAttemptCount}</Descriptions.Item>
|
||||
<Descriptions.Item label="发码时间">
|
||||
{formatAdminDateTime(voucher.sendCallbackSentAt)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="核销流水">
|
||||
{voucher.consumeSerialNum || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="核销时间">
|
||||
{formatAdminDateTime(voucher.consumedAt)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="最近异常" span={3}>
|
||||
{voucher.sendCallbackLastError || '-'}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
{voucher.sendCallbackLastError ? (
|
||||
<Alert
|
||||
className="platform-section-gap"
|
||||
type="warning"
|
||||
showIcon
|
||||
message="电子凭证发码回调存在异常"
|
||||
description={voucher.sendCallbackLastError}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{consumeDetails.length > 0 ? (
|
||||
<Table<Record<string, unknown>>
|
||||
className="platform-section-gap"
|
||||
rowKey={(_, index) => String(index ?? 0)}
|
||||
pagination={false}
|
||||
dataSource={consumeDetails}
|
||||
scroll={{ x: 760 }}
|
||||
columns={[
|
||||
{
|
||||
title: '核销明细',
|
||||
render: (_, row) => <JsonPreview value={row} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : null}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function KuaishouCloudPanel({
|
||||
flow,
|
||||
canRefresh,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
SwapOutlined,
|
||||
UserSwitchOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { Alert, App, Button, Card, DatePicker, Form, Input, Select, Space, Table, Typography } from 'antd'
|
||||
import { Alert, App, Button, Card, DatePicker, Form, Input, Select, Space, Table, Tag, Typography } from 'antd'
|
||||
import type { TableColumnsType } from 'antd'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -36,6 +36,12 @@ type TaskFilterForm = {
|
||||
}
|
||||
|
||||
type TaskListAction = 'retry' | 'rebind_role' | 'manual_review' | 'close'
|
||||
type TaskListBlocker = {
|
||||
label: string
|
||||
color: string
|
||||
level: 'normal' | 'error'
|
||||
detail: string
|
||||
}
|
||||
|
||||
export default function AdminTasksPage() {
|
||||
const navigate = useNavigate()
|
||||
@@ -100,12 +106,38 @@ export default function AdminTasksPage() {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '履约 / 卡点',
|
||||
minWidth: 260,
|
||||
render: (_, row) => {
|
||||
const executor = resolveTaskExecutorDisplay(row.executorKey)
|
||||
const blocker = resolveTaskListBlocker(row)
|
||||
|
||||
return (
|
||||
<div className="cell-stack">
|
||||
<Space size={[4, 4]} wrap>
|
||||
<Tag color={executor.color}>{executor.label}</Tag>
|
||||
<Tag color={blocker.color}>{blocker.label}</Tag>
|
||||
</Space>
|
||||
<Typography.Text
|
||||
className="task-list-blocker-detail"
|
||||
type={blocker.level === 'error' ? 'danger' : 'secondary'}
|
||||
title={blocker.detail}
|
||||
ellipsis
|
||||
>
|
||||
{blocker.detail}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
minWidth: 250,
|
||||
minWidth: 280,
|
||||
render: (_, row) => (
|
||||
<Space size={[0, 6]} wrap>
|
||||
<StatusTag value={row.status} kind="task" />
|
||||
<StatusTag value={row.deliveryStatus} kind="delivery" />
|
||||
<StatusTag value={row.resourceStatus} kind="resource" />
|
||||
<StatusTag value={row.customerStatus} kind="customer" />
|
||||
</Space>
|
||||
@@ -364,7 +396,7 @@ export default function AdminTasksPage() {
|
||||
loading={query.isLoading || query.isFetching}
|
||||
columns={columns}
|
||||
dataSource={data?.items || []}
|
||||
scroll={{ x: 1240 }}
|
||||
scroll={{ x: 1500 }}
|
||||
pagination={{
|
||||
current: data?.pagination.page || page,
|
||||
pageSize: data?.pagination.pageSize || pageSize,
|
||||
@@ -389,6 +421,133 @@ function canRebindListTask(item: AdminTaskListItem, canManageTaskLifecycle: bool
|
||||
)
|
||||
}
|
||||
|
||||
function resolveTaskExecutorDisplay(value: unknown) {
|
||||
const executorKey = String(value || '').trim()
|
||||
if (executorKey === 'kuaishou_ct_assisted') {
|
||||
return { label: '快手 Cloud', color: 'blue' }
|
||||
}
|
||||
if (executorKey === 'kuaishou-industry') {
|
||||
return { label: '行业电子凭证', color: 'cyan' }
|
||||
}
|
||||
if (executorKey === 'kuaishou_feifei') {
|
||||
return { label: 'kuaishou-feifei', color: 'purple' }
|
||||
}
|
||||
if (executorKey === 'manual_dispatch') {
|
||||
return { label: '人工履约', color: 'orange' }
|
||||
}
|
||||
return { label: executorKey || '未分配执行器', color: 'default' }
|
||||
}
|
||||
|
||||
function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
||||
const taskStatus = normalizeTaskListStatus(item.status)
|
||||
const deliveryStatus = normalizeTaskListStatus(item.deliveryStatus)
|
||||
const resourceStatus = normalizeTaskListStatus(item.resourceStatus)
|
||||
const customerStatus = normalizeTaskListStatus(item.customerStatus)
|
||||
const errorText = item.lastError || item.resultMessage
|
||||
|
||||
if (
|
||||
['completed', 'redeemed'].includes(taskStatus) ||
|
||||
['delivered', 'completed', 'success', 'consumed'].includes(deliveryStatus)
|
||||
) {
|
||||
return {
|
||||
label: '已收口',
|
||||
color: 'green',
|
||||
level: 'normal',
|
||||
detail: item.resultMessage || '履约链路已完成。',
|
||||
}
|
||||
}
|
||||
|
||||
if (taskStatus === 'closed') {
|
||||
return {
|
||||
label: '已关闭',
|
||||
color: 'default',
|
||||
level: 'normal',
|
||||
detail: item.resultMessage || item.lastError || '任务已关闭,不会继续自动推进。',
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
item.lastError ||
|
||||
['manual_review', 'retry_pending', 'failed'].includes(taskStatus) ||
|
||||
['resource_exception', 'binding_exception', 'failed'].includes(resourceStatus) ||
|
||||
['customer_exception', 'failed'].includes(customerStatus) ||
|
||||
['failed'].includes(deliveryStatus)
|
||||
) {
|
||||
return {
|
||||
label: '异常处理',
|
||||
color: 'red',
|
||||
level: 'error',
|
||||
detail: errorText || '需要进入详情页按履约层级检查并重试。',
|
||||
}
|
||||
}
|
||||
|
||||
if (item.executorKey === 'manual_dispatch') {
|
||||
return {
|
||||
label: '人工履约',
|
||||
color: 'orange',
|
||||
level: 'normal',
|
||||
detail: item.resultMessage || '等待人工回写履约结果。',
|
||||
}
|
||||
}
|
||||
|
||||
if (['pending_config'].includes(resourceStatus) || !item.executorKey) {
|
||||
return {
|
||||
label: '待配置',
|
||||
color: 'gold',
|
||||
level: 'normal',
|
||||
detail: item.resultMessage || '商品还没有命中明确的履约配置。',
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
['not_started', 'pending', 'pending_prepare', 'pending_binding', 'pending_binding_prepare'].includes(
|
||||
resourceStatus,
|
||||
)
|
||||
) {
|
||||
return {
|
||||
label: '资源层',
|
||||
color: 'gold',
|
||||
level: 'normal',
|
||||
detail: item.resultMessage || '等待准备履约资源,可在详情页触发对应执行器动作。',
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
['waiting_customer', 'waiting_binding', 'waiting_user_claim', 'user_binding'].includes(
|
||||
customerStatus,
|
||||
)
|
||||
) {
|
||||
return {
|
||||
label: '客户层',
|
||||
color: 'blue',
|
||||
level: 'normal',
|
||||
detail: item.roleConfirmedAt
|
||||
? '客户已提交角色信息,等待平台履约收口。'
|
||||
: '等待客户打开链接、绑定或提交信息。',
|
||||
}
|
||||
}
|
||||
|
||||
if (deliveryStatus && !['pending', 'not_started'].includes(deliveryStatus)) {
|
||||
return {
|
||||
label: '平台层',
|
||||
color: 'cyan',
|
||||
level: 'normal',
|
||||
detail: item.resultMessage || '平台履约执行中,后续可在详情页按层查看。',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
label: '推进中',
|
||||
color: 'blue',
|
||||
level: 'normal',
|
||||
detail: item.resultMessage || '当前没有明确异常,等待任务自动推进。',
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeTaskListStatus(value: unknown) {
|
||||
return String(value || '').trim().toLowerCase()
|
||||
}
|
||||
|
||||
function cleanParams(params: Record<string, unknown>) {
|
||||
const next = new URLSearchParams()
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
|
||||
@@ -91,3 +91,26 @@ export function returnNumberAdminTaskKuaishouCloudFulfillment(taskId: number | s
|
||||
{},
|
||||
)
|
||||
}
|
||||
|
||||
export function resendAdminTaskKuaishouIndustryVoucherCode(taskId: number | string) {
|
||||
return apiPost<AdminTaskActionResponse>(
|
||||
`/api/v1/admin/tasks/${taskId}/kuaishou-industry/resend-code`,
|
||||
{},
|
||||
)
|
||||
}
|
||||
|
||||
export function consumeAdminTaskKuaishouIndustryVoucher(
|
||||
taskId: number | string,
|
||||
payload: {
|
||||
consumeType?: string
|
||||
storeName?: string
|
||||
storeAddress?: string
|
||||
expressCode?: string
|
||||
expressNo?: string
|
||||
} = {},
|
||||
) {
|
||||
return apiPost<AdminTaskActionResponse>(
|
||||
`/api/v1/admin/tasks/${taskId}/kuaishou-industry/consume`,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -286,6 +286,7 @@ select {
|
||||
.cell-stack {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.muted {
|
||||
@@ -310,6 +311,55 @@ select {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.task-action-groups {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.task-action-group {
|
||||
min-width: 0;
|
||||
min-height: 86px;
|
||||
padding: 12px;
|
||||
border: 1px solid #edf0f5;
|
||||
border-radius: 6px;
|
||||
background: #f8fafc;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.task-action-group > .ant-typography {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.task-list-blocker-detail {
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.fulfillment-overview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.fulfillment-step-card {
|
||||
min-width: 0;
|
||||
padding: 12px;
|
||||
border: 1px solid #edf0f5;
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.fulfillment-step-card .ant-typography {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.manual-dispatch-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
@@ -1003,6 +1053,7 @@ select {
|
||||
}
|
||||
|
||||
.manual-dispatch-grid,
|
||||
.fulfillment-overview-grid,
|
||||
.task-flow-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ export interface AdminTaskOperations {
|
||||
canRebindKuaishouCloudRole: boolean
|
||||
canDispatchKuaishouCloudFulfillment: boolean
|
||||
canReturnKuaishouCloudFulfillment: boolean
|
||||
canResendKuaishouIndustryVoucherCode?: boolean
|
||||
canConsumeKuaishouIndustryVoucher?: boolean
|
||||
canViewSensitiveTaskData: boolean
|
||||
}
|
||||
|
||||
@@ -201,6 +203,20 @@ export interface AdminTaskDetail {
|
||||
}
|
||||
notes: string
|
||||
}
|
||||
kuaishouIndustryVoucher?: null | {
|
||||
oid: string
|
||||
voucherCode: string
|
||||
unitIndex: number
|
||||
status: string
|
||||
sendCallbackStatus: string
|
||||
sendCallbackLastError: string
|
||||
sendCallbackAttemptCount: number
|
||||
sendCallbackSentAt: string | null
|
||||
consumeSerialNum: string
|
||||
consumeDetails: Array<Record<string, unknown>>
|
||||
consumedAt: string | null
|
||||
destroyedAt: string | null
|
||||
}
|
||||
manualDispatch: null | {
|
||||
outcome: string
|
||||
deliveryReference: string
|
||||
|
||||
Reference in New Issue
Block a user