优化订单履约后台界面

This commit is contained in:
yml2213
2026-07-09 09:51:54 +08:00
parent 9a24268b5a
commit 71602683d2
5 changed files with 702 additions and 75 deletions
@@ -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]) => {
+23
View File
@@ -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,
)
}
+51
View File
@@ -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;
}
+16
View File
@@ -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