优化任务列表表格 UI:收敛列信息并修复 Tag 撑满底色
- 拆分渠道与卡点职责,卡点文案运营化 - 状态列收敛为主状态 + 三层 Tooltip - 操作列仅展示可用动作,异常行浅红高亮 - 修复 cell-stack grid 下 Tag 被横向拉满的样式问题
This commit is contained in:
@@ -5,7 +5,20 @@ import {
|
|||||||
SwapOutlined,
|
SwapOutlined,
|
||||||
UserSwitchOutlined,
|
UserSwitchOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { Alert, App, Button, Card, Form, Input, Select, Space, Table, Tag, Typography } from 'antd'
|
import {
|
||||||
|
Alert,
|
||||||
|
App,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
} from 'antd'
|
||||||
import type { TableColumnsType } from 'antd'
|
import type { TableColumnsType } from 'antd'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
@@ -45,8 +58,10 @@ type TaskListAction = 'retry' | 'rebind_role' | 'manual_review' | 'close'
|
|||||||
type TaskListBlocker = {
|
type TaskListBlocker = {
|
||||||
label: string
|
label: string
|
||||||
color: string
|
color: string
|
||||||
level: 'normal' | 'error'
|
level: 'normal' | 'error' | 'done'
|
||||||
detail: string
|
detail: string
|
||||||
|
/** 列表副文案:完成态默认隐藏,异常/进行中显示 */
|
||||||
|
showDetail: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AdminTasksPage() {
|
export default function AdminTasksPage() {
|
||||||
@@ -92,7 +107,7 @@ export default function AdminTasksPage() {
|
|||||||
const columns: TableColumnsType<AdminTaskListItem> = [
|
const columns: TableColumnsType<AdminTaskListItem> = [
|
||||||
{
|
{
|
||||||
title: '任务 / 订单',
|
title: '任务 / 订单',
|
||||||
minWidth: 230,
|
minWidth: 210,
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<div className="cell-stack">
|
<div className="cell-stack">
|
||||||
<Typography.Link onClick={() => navigate(`/admin/tasks/${row.taskId}`)}>
|
<Typography.Link onClick={() => navigate(`/admin/tasks/${row.taskId}`)}>
|
||||||
@@ -104,27 +119,37 @@ export default function AdminTasksPage() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '商品',
|
title: '商品',
|
||||||
minWidth: 210,
|
minWidth: 200,
|
||||||
render: (_, row) => (
|
|
||||||
<div className="cell-stack">
|
|
||||||
<span>{row.skuName || '-'}</span>
|
|
||||||
<span className="muted">{row.skuCode || row.executorKey}</span>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '履约 / 卡点',
|
|
||||||
minWidth: 260,
|
|
||||||
render: (_, row) => {
|
render: (_, row) => {
|
||||||
const executor = resolveTaskExecutorDisplay(row.executorKey)
|
const executor = resolveTaskExecutorDisplay(row.executorKey)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="cell-stack">
|
||||||
|
<span>{row.skuName || '-'}</span>
|
||||||
|
<span className="muted">{row.skuCode || '-'}</span>
|
||||||
|
<Tag className="task-list-executor-tag" color={executor.color} title={executor.key}>
|
||||||
|
{executor.label}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '当前卡点',
|
||||||
|
minWidth: 200,
|
||||||
|
render: (_, row) => {
|
||||||
const blocker = resolveTaskListBlocker(row)
|
const blocker = resolveTaskListBlocker(row)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="cell-stack">
|
<div className="cell-stack">
|
||||||
<Space size={[4, 4]} wrap>
|
<Tag
|
||||||
<Tag color={executor.color}>{executor.label}</Tag>
|
className="task-list-blocker-tag"
|
||||||
<Tag color={blocker.color}>{blocker.label}</Tag>
|
color={blocker.color}
|
||||||
</Space>
|
title={blocker.detail}
|
||||||
|
>
|
||||||
|
{blocker.label}
|
||||||
|
</Tag>
|
||||||
|
{blocker.showDetail ? (
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
className="task-list-blocker-detail"
|
className="task-list-blocker-detail"
|
||||||
type={blocker.level === 'error' ? 'danger' : 'secondary'}
|
type={blocker.level === 'error' ? 'danger' : 'secondary'}
|
||||||
@@ -133,45 +158,64 @@ export default function AdminTasksPage() {
|
|||||||
>
|
>
|
||||||
{blocker.detail}
|
{blocker.detail}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
minWidth: 280,
|
minWidth: 120,
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<Space size={[0, 6]} wrap>
|
<Tooltip
|
||||||
<StatusTag value={row.status} kind="task" />
|
placement="topLeft"
|
||||||
|
title={
|
||||||
|
<div className="task-list-status-tooltip">
|
||||||
|
<div className="task-list-status-tooltip-row">
|
||||||
|
<span>交付</span>
|
||||||
<StatusTag value={row.deliveryStatus} kind="delivery" />
|
<StatusTag value={row.deliveryStatus} kind="delivery" />
|
||||||
|
</div>
|
||||||
|
<div className="task-list-status-tooltip-row">
|
||||||
|
<span>资源</span>
|
||||||
<StatusTag value={row.resourceStatus} kind="resource" />
|
<StatusTag value={row.resourceStatus} kind="resource" />
|
||||||
|
</div>
|
||||||
|
<div className="task-list-status-tooltip-row">
|
||||||
|
<span>客户</span>
|
||||||
<StatusTag value={row.customerStatus} kind="customer" />
|
<StatusTag value={row.customerStatus} kind="customer" />
|
||||||
</Space>
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="task-list-status-main">
|
||||||
|
<StatusTag value={row.status} kind="task" />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '角色',
|
title: '角色',
|
||||||
minWidth: 180,
|
minWidth: 140,
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<div className="cell-stack">
|
<div className="cell-stack">
|
||||||
<span>{row.roleName || '-'}</span>
|
<span>{row.roleName || '-'}</span>
|
||||||
<span className="muted">{row.roleId || row.loginType || '-'}</span>
|
{row.roleId || row.loginType ? (
|
||||||
|
<span className="muted">{row.roleId || row.loginType}</span>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '时间',
|
title: '更新时间',
|
||||||
minWidth: 220,
|
minWidth: 160,
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<div className="cell-stack">
|
<span title={`创建 ${formatAdminDateTime(row.createdAt)}`}>
|
||||||
<span>{formatAdminDateTime(row.updatedAt)}</span>
|
{formatAdminDateTime(row.updatedAt)}
|
||||||
<span className="muted">创建 {formatAdminDateTime(row.createdAt)}</span>
|
</span>
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '异常 / 操作',
|
title: '操作',
|
||||||
minWidth: 260,
|
minWidth: 200,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render: (_, row) => {
|
render: (_, row) => {
|
||||||
const rowLoading = actionLoadingId === row.taskId
|
const rowLoading = actionLoadingId === row.taskId
|
||||||
@@ -181,23 +225,28 @@ export default function AdminTasksPage() {
|
|||||||
row.executorKey !== 'manual_dispatch' &&
|
row.executorKey !== 'manual_dispatch' &&
|
||||||
['retry_pending', 'manual_review'].includes(row.status)
|
['retry_pending', 'manual_review'].includes(row.status)
|
||||||
const canRebind = canRebindListTask(row, canManageTaskLifecycle)
|
const canRebind = canRebindListTask(row, canManageTaskLifecycle)
|
||||||
|
const canManualReview =
|
||||||
|
canManageTaskLifecycle &&
|
||||||
|
!['redeemed', 'closed', 'completed', 'manual_review'].includes(row.status)
|
||||||
const canClose = canCloseTasks && !['redeemed', 'closed'].includes(row.status)
|
const canClose = canCloseTasks && !['redeemed', 'closed'].includes(row.status)
|
||||||
const hasActions = canManageTaskLifecycle || canRebind || canClose
|
const hasActions = canRetry || canRebind || canManualReview || canClose
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="cell-stack">
|
<div className="cell-stack">
|
||||||
<Typography.Text type={row.lastError ? 'danger' : 'secondary'} ellipsis>
|
{row.lastError ? (
|
||||||
{row.lastError || '当前无异常'}
|
<Typography.Text type="danger" title={row.lastError} ellipsis>
|
||||||
|
{row.lastError}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
|
) : null}
|
||||||
{hasActions ? (
|
{hasActions ? (
|
||||||
<Space size={[6, 6]} wrap>
|
<Space size={[6, 6]} wrap>
|
||||||
{canManageTaskLifecycle ? (
|
{canRetry ? (
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
danger
|
danger
|
||||||
ghost
|
ghost
|
||||||
icon={<ReloadOutlined />}
|
icon={<ReloadOutlined />}
|
||||||
disabled={actionRunning || !canRetry}
|
disabled={actionRunning}
|
||||||
loading={rowLoading}
|
loading={rowLoading}
|
||||||
onClick={() => handleActionCommand('retry', row)}
|
onClick={() => handleActionCommand('retry', row)}
|
||||||
>
|
>
|
||||||
@@ -215,7 +264,7 @@ export default function AdminTasksPage() {
|
|||||||
换绑
|
换绑
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
{canManageTaskLifecycle ? (
|
{canManualReview ? (
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
icon={<UserSwitchOutlined />}
|
icon={<UserSwitchOutlined />}
|
||||||
@@ -239,7 +288,9 @@ export default function AdminTasksPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</Space>
|
</Space>
|
||||||
) : null}
|
) : (
|
||||||
|
<span className="muted">—</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -402,7 +453,11 @@ export default function AdminTasksPage() {
|
|||||||
loading={query.isLoading || query.isFetching}
|
loading={query.isLoading || query.isFetching}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={data?.items || []}
|
dataSource={data?.items || []}
|
||||||
scroll={{ x: 1500 }}
|
scroll={{ x: 1280 }}
|
||||||
|
size="middle"
|
||||||
|
rowClassName={(row) =>
|
||||||
|
resolveTaskListBlocker(row).level === 'error' ? 'task-list-row-error' : ''
|
||||||
|
}
|
||||||
pagination={buildAdminTablePagination({
|
pagination={buildAdminTablePagination({
|
||||||
current: data?.pagination.page || page,
|
current: data?.pagination.page || page,
|
||||||
pageSize: data?.pagination.pageSize || pageSize,
|
pageSize: data?.pagination.pageSize || pageSize,
|
||||||
@@ -428,18 +483,22 @@ function canRebindListTask(item: AdminTaskListItem, canManageTaskLifecycle: bool
|
|||||||
function resolveTaskExecutorDisplay(value: unknown) {
|
function resolveTaskExecutorDisplay(value: unknown) {
|
||||||
const executorKey = String(value || '').trim()
|
const executorKey = String(value || '').trim()
|
||||||
if (executorKey === 'kuaishou_ct_assisted') {
|
if (executorKey === 'kuaishou_ct_assisted') {
|
||||||
return { label: 'kuaishou-lewan', color: 'blue' }
|
return { key: executorKey, label: '乐玩云', color: 'blue' }
|
||||||
}
|
}
|
||||||
if (executorKey === 'kuaishou-industry') {
|
if (executorKey === 'kuaishou-industry') {
|
||||||
return { label: '行业电子凭证', color: 'cyan' }
|
return { key: executorKey, label: '行业凭证', color: 'cyan' }
|
||||||
}
|
}
|
||||||
if (executorKey === 'kuaishou_feifei') {
|
if (executorKey === 'kuaishou_feifei') {
|
||||||
return { label: 'kuaishou-feifei', color: 'purple' }
|
return { key: executorKey, label: '飞飞', color: 'purple' }
|
||||||
}
|
}
|
||||||
if (executorKey === 'manual_dispatch') {
|
if (executorKey === 'manual_dispatch') {
|
||||||
return { label: '人工履约', color: 'orange' }
|
return { key: executorKey, label: '人工履约', color: 'orange' }
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: executorKey,
|
||||||
|
label: executorKey || '未分配',
|
||||||
|
color: 'default',
|
||||||
}
|
}
|
||||||
return { label: executorKey || '未分配执行器', color: 'default' }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
||||||
@@ -454,10 +513,11 @@ function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
|||||||
['delivered', 'completed', 'success', 'consumed'].includes(deliveryStatus)
|
['delivered', 'completed', 'success', 'consumed'].includes(deliveryStatus)
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
label: '已收口',
|
label: '已完成',
|
||||||
color: 'green',
|
color: 'green',
|
||||||
level: 'normal',
|
level: 'done',
|
||||||
detail: item.resultMessage || '履约链路已完成。',
|
detail: item.resultMessage || '履约链路已完成。',
|
||||||
|
showDetail: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,8 +525,9 @@ function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
|||||||
return {
|
return {
|
||||||
label: '已关闭',
|
label: '已关闭',
|
||||||
color: 'default',
|
color: 'default',
|
||||||
level: 'normal',
|
level: 'done',
|
||||||
detail: item.resultMessage || item.lastError || '任务已关闭,不会继续自动推进。',
|
detail: item.resultMessage || item.lastError || '任务已关闭,不会继续自动推进。',
|
||||||
|
showDetail: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,10 +539,11 @@ function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
|||||||
['failed'].includes(deliveryStatus)
|
['failed'].includes(deliveryStatus)
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
label: '异常处理',
|
label: '需处理',
|
||||||
color: 'red',
|
color: 'red',
|
||||||
level: 'error',
|
level: 'error',
|
||||||
detail: errorText || '需要进入详情页按履约层级检查并重试。',
|
detail: errorText || '需要进入详情页按履约层级检查并重试。',
|
||||||
|
showDetail: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,6 +553,7 @@ function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
|||||||
color: 'orange',
|
color: 'orange',
|
||||||
level: 'normal',
|
level: 'normal',
|
||||||
detail: item.resultMessage || '等待人工回写履约结果。',
|
detail: item.resultMessage || '等待人工回写履约结果。',
|
||||||
|
showDetail: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,6 +563,7 @@ function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
|||||||
color: 'gold',
|
color: 'gold',
|
||||||
level: 'normal',
|
level: 'normal',
|
||||||
detail: item.resultMessage || '商品还没有命中明确的履约配置。',
|
detail: item.resultMessage || '商品还没有命中明确的履约配置。',
|
||||||
|
showDetail: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,10 +573,11 @@ function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
label: '资源层',
|
label: '等资源',
|
||||||
color: 'gold',
|
color: 'gold',
|
||||||
level: 'normal',
|
level: 'normal',
|
||||||
detail: item.resultMessage || '等待准备履约资源,可在详情页触发对应执行器动作。',
|
detail: item.resultMessage || '等待准备履约资源,可在详情页触发对应动作。',
|
||||||
|
showDetail: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,29 +587,32 @@ function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
label: '客户层',
|
label: '等客户',
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
level: 'normal',
|
level: 'normal',
|
||||||
detail: item.roleConfirmedAt
|
detail: item.roleConfirmedAt
|
||||||
? '客户已提交角色信息,等待平台履约收口。'
|
? '客户已提交角色信息,等待平台履约收口。'
|
||||||
: '等待客户打开链接、绑定或提交信息。',
|
: '等待客户打开链接、绑定或提交信息。',
|
||||||
|
showDetail: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deliveryStatus && !['pending', 'not_started'].includes(deliveryStatus)) {
|
if (deliveryStatus && !['pending', 'not_started'].includes(deliveryStatus)) {
|
||||||
return {
|
return {
|
||||||
label: '平台层',
|
label: '平台处理中',
|
||||||
color: 'cyan',
|
color: 'cyan',
|
||||||
level: 'normal',
|
level: 'normal',
|
||||||
detail: item.resultMessage || '平台履约执行中,后续可在详情页按层查看。',
|
detail: item.resultMessage || '平台履约执行中,可在详情页查看各层进度。',
|
||||||
|
showDetail: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: '推进中',
|
label: '进行中',
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
level: 'normal',
|
level: 'normal',
|
||||||
detail: item.resultMessage || '当前没有明确异常,等待任务自动推进。',
|
detail: item.resultMessage || '当前没有明确异常,等待任务自动推进。',
|
||||||
|
showDetail: Boolean(item.resultMessage),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -354,7 +354,47 @@ select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task-list-blocker-detail {
|
.task-list-blocker-detail {
|
||||||
max-width: 240px;
|
max-width: 220px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-blocker-tag,
|
||||||
|
.task-list-executor-tag {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-inline-end: 0 !important;
|
||||||
|
justify-self: start;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-status-main {
|
||||||
|
display: inline-flex;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-status-tooltip {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-status-tooltip-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-status-tooltip-row > span:first-child {
|
||||||
|
min-width: 2em;
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-row-error > td {
|
||||||
|
background: #fff7f7 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-row-error:hover > td {
|
||||||
|
background: #ffefef !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fulfillment-overview-grid {
|
.fulfillment-overview-grid {
|
||||||
|
|||||||
Reference in New Issue
Block a user