优化-任务列表和详情页

This commit is contained in:
yml
2026-05-27 14:00:30 +08:00
parent 52b39c161a
commit 5583c60e72
5 changed files with 196 additions and 136 deletions
@@ -27,9 +27,17 @@
linear-gradient(135deg, rgba(255, 255, 255, 0.98), rgba(239, 246, 255, 0.96)); linear-gradient(135deg, rgba(255, 255, 255, 0.98), rgba(239, 246, 255, 0.96));
} }
.hero-head {
display: flex;
justify-content: space-between;
gap: var(--space-4);
align-items: flex-start;
}
.hero-copy { .hero-copy {
display: grid; display: grid;
gap: 6px; gap: 6px;
min-width: 0;
} }
.hero-copy h2 { .hero-copy h2 {
@@ -45,6 +53,10 @@
color: var(--text-secondary); color: var(--text-secondary);
} }
.hero-copy p {
line-height: 1.6;
}
.hero-eyebrow { .hero-eyebrow {
color: var(--color-primary); color: var(--color-primary);
font-size: var(--text-xs); font-size: var(--text-xs);
@@ -458,6 +470,7 @@
} }
@media (max-width: 900px) { @media (max-width: 900px) {
.hero-head,
.hero-inline, .hero-inline,
.hero-link-row, .hero-link-row,
.flow-card-head, .flow-card-head,
@@ -4,7 +4,6 @@ import { onMounted, ref } from "vue";
import { useAdminAction } from "@/composables/useAdminAction"; import { useAdminAction } from "@/composables/useAdminAction";
import { useAdminListPage } from "@/composables/useAdminListPage"; import { useAdminListPage } from "@/composables/useAdminListPage";
import AdminPageHeader from "@/components/admin/AdminPageHeader.vue"; import AdminPageHeader from "@/components/admin/AdminPageHeader.vue";
import AdminStatusTag from "@/components/admin/AdminStatusTag.vue";
import { import {
closeAdminTask, closeAdminTask,
fetchAdminTasks, fetchAdminTasks,
@@ -127,6 +126,56 @@ function resetFilters() {
void loadTasks(1); void loadTasks(1);
} }
function resolveResourceStageLabel(status: string) {
const normalized = String(status || "").trim();
const labelMap: Record<string, string> = {
system_bound: "资源已准备",
pending_binding: "待准备资源",
pending_binding_prepare: "待准备资源",
retry_pending: "待重试",
manual_review: "人工处理",
binding_exception: "资源异常",
completed: "已完成",
not_started: "未开始",
};
return labelMap[normalized] || normalized || "-";
}
function resolveCustomerStageLabel(status: string) {
const normalized = String(status || "").trim();
const labelMap: Record<string, string> = {
not_started: "待打开链接",
waiting_user_claim: "待打开链接",
waiting_binding: "待客户绑定",
link_opened: "已打开链接",
user_binding: "客户绑定中",
binding_confirmed: "已提交",
binding_in_progress: "处理中",
binding_completed: "客户已完成",
binding_exception: "客户步骤异常",
completed: "已完成",
};
return labelMap[normalized] || normalized || "-";
}
function resolveTaskMainText(item: AdminTaskListItem) {
if (item.lastError) return "需要关注";
if (["failed", "manual_review", "retry_pending"].includes(item.status)) return "待处理";
if (["redeemed", "completed", "closed"].includes(item.status)) return "已完成";
if (item.userBindingStatus === "waiting_binding") return "等待客户绑定";
if (item.systemBindingStatus === "system_bound") return "资源已准备";
if (item.status === "pending_binding_prepare") return "待准备资源";
return item.status || "处理中";
}
function resolveTaskTone(item: AdminTaskListItem) {
if (item.lastError || ["failed", "manual_review", "retry_pending"].includes(item.status)) return "danger";
if (["redeemed", "completed", "closed"].includes(item.status)) return "success";
return "warning";
}
onMounted(() => { onMounted(() => {
void loadTasks(1); void loadTasks(1);
}); });
@@ -136,7 +185,7 @@ onMounted(() => {
<div class="tasks-page list-page"> <div class="tasks-page list-page">
<AdminPageHeader <AdminPageHeader
title="交付任务" title="交付任务"
description="更紧凑地查看任务状态、角色绑定和异常信息。" description="查看自动履约任务、客户步骤和异常处理。"
> >
<template #extra> <template #extra>
<span class="total-badge"> {{ pagination.total }} 条任务</span> <span class="total-badge"> {{ pagination.total }} 条任务</span>
@@ -148,7 +197,7 @@ onMounted(() => {
<div class="card-header"> <div class="card-header">
<span class="card-title">查询任务</span> <span class="card-title">查询任务</span>
<span class="card-desc" <span class="card-desc"
>按状态任务号订单号角色标识SKU 和日期筛选</span >按状态任务号订单号商品和日期筛选</span
> >
</div> </div>
</template> </template>
@@ -175,13 +224,13 @@ onMounted(() => {
/> />
<el-input <el-input
v-model="roleId" v-model="roleId"
placeholder="角色唯一标识" placeholder="角色 ID"
clearable clearable
@keyup.enter="loadTasks(1)" @keyup.enter="loadTasks(1)"
/> />
<el-input <el-input
v-model="skuCode" v-model="skuCode"
placeholder="商品 SKU" placeholder="商品名 / SKU"
clearable clearable
@keyup.enter="loadTasks(1)" @keyup.enter="loadTasks(1)"
/> />
@@ -245,7 +294,7 @@ onMounted(() => {
size="small" size="small"
class="tasks-table data-table" class="tasks-table data-table"
> >
<el-table-column label="任务 / 商品" min-width="220"> <el-table-column label="任务 / 订单" min-width="230">
<template #default="{ row }"> <template #default="{ row }">
<div class="cell-stack"> <div class="cell-stack">
<RouterLink <RouterLink
@@ -263,6 +312,12 @@ onMounted(() => {
>重试 {{ row.retryCount }} </span >重试 {{ row.retryCount }} </span
> >
</div> </div>
</div>
</template>
</el-table-column>
<el-table-column label="商品" min-width="210">
<template #default="{ row }">
<div class="cell-stack">
<span class="cell-title" :title="row.skuName || row.skuCode">{{ <span class="cell-title" :title="row.skuName || row.skuCode">{{
row.skuName || row.skuCode row.skuName || row.skuCode
}}</span> }}</span>
@@ -270,38 +325,19 @@ onMounted(() => {
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="进度" min-width="160"> <el-table-column label="状态" min-width="250">
<template #default="{ row }"> <template #default="{ row }">
<div class="cell-stack"> <div class="task-status-summary">
<div class="progress-row"> <span class="task-main-chip" :data-tone="resolveTaskTone(row)">
<span class="progress-label">任务</span> {{ resolveTaskMainText(row) }}
<AdminStatusTag :status="row.status" /> </span>
</div> <p class="task-status-note">
<div class="progress-row"> 客户 {{ resolveCustomerStageLabel(row.userBindingStatus) }}
<span class="progress-label">系统</span> · 资源 {{ resolveResourceStageLabel(row.systemBindingStatus) }}
<AdminStatusTag :status="row.systemBindingStatus" /> </p>
</div> <p v-if="row.roleName || row.roleId" class="task-status-note">
<div class="progress-row"> 角色 {{ row.roleName || "-" }} / {{ row.roleId || "-" }}
<span class="progress-label">完整</span> </p>
<AdminStatusTag :status="row.userBindingStatus" />
</div>
</div>
</template>
</el-table-column>
<el-table-column label="角色信息" min-width="180">
<template #default="{ row }">
<div class="cell-stack">
<span
class="cell-title"
:title="row.roleName || row.roleId || '-'"
>{{ row.roleName || "-" }}</span
>
<span class="cell-subline" :title="row.roleId || '-'"
>角色ID {{ row.roleId || "-" }}</span
>
<span class="cell-subline"
>登录 {{ row.loginType || "-" }}</span
>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
@@ -343,21 +379,7 @@ onMounted(() => {
size="small" size="small"
type="danger" type="danger"
plain plain
@click=" @click="handleActionCommand('retry', row)"
runAction({
id: row.taskId,
action: async () => {
const response = await retryAdminTask(row.taskId);
if (response.data.claimUrl)
lastClaimUrl = response.data.claimUrl;
},
successMessage: '任务已重试',
confirmText: `确认重试任务 ${row.taskNo} 吗?`,
onAfterAction: async () => {
await loadTasks();
},
})
"
> >
重试 重试
</el-button> </el-button>
@@ -421,37 +443,54 @@ onMounted(() => {
width: 100%; width: 100%;
} }
.progress-row { .task-status-summary {
display: flex; display: grid;
align-items: center; gap: 6px;
gap: var(--space-2);
min-width: 0; min-width: 0;
} }
.progress-row :deep(.el-tag) {
min-width: 0;
max-width: calc(100% - 42px);
}
.progress-row :deep(.el-tag__content) {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.progress-label { .task-main-chip {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-width: 34px; width: fit-content;
min-height: 22px; min-height: 26px;
padding: 0 7px; padding: 0 10px;
border-radius: var(--radius-full); border-radius: var(--radius-full);
background: #f7f9fc; border: 1px solid transparent;
border: 1px solid var(--border-default); font-size: var(--text-xs);
font-weight: 800;
white-space: nowrap;
}
.task-main-chip[data-tone="success"] {
color: #0f766e;
background: var(--color-success-light);
border-color: #a6f4c5;
}
.task-main-chip[data-tone="warning"] {
color: #b54708;
background: var(--color-warning-light);
border-color: #fedf89;
}
.task-main-chip[data-tone="danger"] {
color: var(--color-danger-dark);
background: var(--color-danger-light);
border-color: #fecaca;
}
.task-status-note {
margin: 0;
color: var(--text-secondary); color: var(--text-secondary);
font-size: 11px; font-size: var(--text-xs);
line-height: 1.5;
}
.task-status-note + .task-status-note {
color: var(--text-primary);
font-weight: 700; font-weight: 700;
flex: 0 0 auto;
} }
.cell-error { .cell-error {
@@ -13,6 +13,40 @@ interface Props {
} }
defineProps<Props>() defineProps<Props>()
function resolveResourceStageLabel(status: string) {
const normalized = String(status || '').trim()
const labelMap: Record<string, string> = {
system_bound: '资源已准备',
pending_binding: '待准备资源',
pending_binding_prepare: '待准备资源',
retry_pending: '待重试',
manual_review: '人工处理',
binding_exception: '资源异常',
completed: '已完成',
not_started: '未开始',
}
return labelMap[normalized] || normalized || '-'
}
function resolveCustomerStageLabel(status: string) {
const normalized = String(status || '').trim()
const labelMap: Record<string, string> = {
not_started: '待打开链接',
waiting_user_claim: '待打开链接',
waiting_binding: '待客户绑定',
link_opened: '已打开链接',
user_binding: '客户绑定中',
binding_confirmed: '已提交',
binding_in_progress: '处理中',
binding_completed: '客户已完成',
binding_exception: '客户步骤异常',
completed: '已完成',
}
return labelMap[normalized] || normalized || '-'
}
</script> </script>
<template> <template>
@@ -22,16 +56,23 @@ defineProps<Props>()
shadow="never" shadow="never"
class="section-card hero-card" class="section-card hero-card"
> >
<div class="hero-head">
<div class="hero-copy"> <div class="hero-copy">
<span class="hero-eyebrow">{{ detail.task.executorKey || 'task' }}</span> <span class="hero-eyebrow">自动履约任务</span>
<h2>{{ detail.task.taskNo }}</h2> <h2>{{ detail.task.taskNo }}</h2>
<p> <p>
订单 {{ detail.order?.platformOrderId || '-' }} · 商品 订单 {{ detail.order?.platformOrderId || '-' }} · 商品
{{ detail.orderItem?.skuName || detail.orderItem?.skuCode || '-' }} {{ detail.orderItem?.skuName || detail.orderItem?.skuCode || '-' }}
· 店铺 · 核销店铺
{{ detail.order?.shopName || detail.order?.shopId || '-' }} {{ detail.order?.shopName || detail.order?.shopId || '-' }}
</p> </p>
</div> </div>
<div class="status-stack">
<AdminStatusTag :status="detail.task.status" :show-raw="false" />
<AdminStatusTag :status="detail.order?.payStatus || ''" :show-raw="false" />
</div>
</div>
<div class="hero-meta-grid"> <div class="hero-meta-grid">
<el-card <el-card
v-for="card in summaryCards" v-for="card in summaryCards"
@@ -44,8 +85,8 @@ defineProps<Props>()
</el-card> </el-card>
</div> </div>
<div class="hero-inline"> <div class="hero-inline">
<span>系统绑定 <AdminStatusTag :status="detail.task.systemBindingStatus" /></span> <span>资源 {{ resolveResourceStageLabel(detail.task.systemBindingStatus) }}</span>
<span>完整绑定 <AdminStatusTag :status="detail.task.userBindingStatus" /></span> <span>客户 {{ resolveCustomerStageLabel(detail.task.userBindingStatus) }}</span>
<span v-if="detail.task.lastError">最近异常 {{ detail.task.lastError }}</span> <span v-if="detail.task.lastError">最近异常 {{ detail.task.lastError }}</span>
</div> </div>
<div v-if="claimUrl" class="hero-link-row"> <div v-if="claimUrl" class="hero-link-row">
@@ -72,8 +113,8 @@ defineProps<Props>()
/> />
</p> </p>
<p> <p>
系统绑定<AdminStatusTag :status="detail.task.systemBindingStatus" /> · 资源{{ resolveResourceStageLabel(detail.task.systemBindingStatus) }} ·
完整绑定<AdminStatusTag :status="detail.task.userBindingStatus" /> 客户{{ resolveCustomerStageLabel(detail.task.userBindingStatus) }}
</p> </p>
<p> <p>
角色{{ detail.task.roleName || '-' }} / 角色{{ detail.task.roleName || '-' }} /
@@ -62,10 +62,10 @@ defineProps<Props>()
<el-descriptions-item label="用户打开链接"> <el-descriptions-item label="用户打开链接">
{{ formatAdminDateTime(detail.task.claimedAt) }} {{ formatAdminDateTime(detail.task.claimedAt) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="户提交绑定"> <el-descriptions-item label="户提交">
{{ formatAdminDateTime(detail.task.roleConfirmedAt) }} {{ formatAdminDateTime(detail.task.roleConfirmedAt) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="完整绑定成功"> <el-descriptions-item label="履约完成">
{{ formatAdminDateTime(detail.task.redeemedAt) }} {{ formatAdminDateTime(detail.task.redeemedAt) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="截图路径" :span="2"> <el-descriptions-item label="截图路径" :span="2">
@@ -27,7 +27,7 @@ interface Props {
defineProps<Props>() defineProps<Props>()
const collapsePanels = ['claim', 'ticket', 'cloud'] const collapsePanels: string[] = []
const emit = defineEmits<{ const emit = defineEmits<{
refreshRole: [] refreshRole: []
@@ -64,9 +64,9 @@ function formatAccountNames(labels: string[] | undefined, keys: string[] | undef
<el-card shadow="never" class="section-card flow-card"> <el-card shadow="never" class="section-card flow-card">
<div class="flow-card-head"> <div class="flow-card-head">
<div> <div>
<h3>快手 Cloud 履约面板</h3> <h3>自动履约流程</h3>
<p class="section-copy"> <p class="section-copy">
这是一套独立的快手 Cloud 任务面板优先展示角色信息与当前履约推进点次级明细可按需展开 优先看当前步骤和客户角色详细链接核销cloud 资源字段可按需展开
</p> </p>
</div> </div>
<div class="flow-head-actions"> <div class="flow-head-actions">
@@ -97,40 +97,13 @@ function formatAccountNames(labels: string[] | undefined, keys: string[] | undef
</div> </div>
</div> </div>
<div class="redeem-summary-grid">
<el-card shadow="never" class="metric-card">
<span>准备状态</span>
<div class="auto-delivery-tag-line">
<AdminStatusTag :status="flow.binding.prepareStatus" />
</div>
</el-card>
<el-card shadow="never" class="metric-card">
<span>发货状态</span>
<div class="auto-delivery-tag-line">
<AdminStatusTag :status="flow.dispatch.status" />
</div>
</el-card>
<el-card shadow="never" class="metric-card">
<span>退号状态</span>
<div class="auto-delivery-tag-line">
<AdminStatusTag :status="flow.returnNumber.status" />
</div>
</el-card>
<el-card shadow="never" class="metric-card">
<span>当前任务状态</span>
<div class="auto-delivery-tag-line">
<AdminStatusTag :status="detail.task.status" />
</div>
</el-card>
</div>
<div class="kuaishou-focus-grid"> <div class="kuaishou-focus-grid">
<el-card shadow="never" class="flow-block flow-block--spotlight"> <section class="flow-block flow-block--spotlight">
<div class="spotlight-head"> <div class="spotlight-head">
<div> <div>
<h4>角色信息</h4> <h4>客户角色</h4>
<p class="flow-copy"> <p class="flow-copy">
角色信息来自 cloudtentacles `vn/bind_info`客服可在客户绑定后手动刷新 客户绑定后刷新角色信息确认无误后再执行发货
</p> </p>
</div> </div>
<div class="spotlight-actions"> <div class="spotlight-actions">
@@ -163,18 +136,12 @@ function formatAccountNames(labels: string[] | undefined, keys: string[] | undef
<p v-if="flow.role?.errorMessage" class="role-warning"> <p v-if="flow.role?.errorMessage" class="role-warning">
{{ flow.role.errorMessage }} {{ flow.role.errorMessage }}
</p> </p>
<div v-if="roleInfoEntries.length > 0" class="flow-meta-list flow-meta-list--dense"> </section>
<div v-for="entry in roleInfoEntries" :key="entry.key">
<span>{{ entry.label }}</span>
<strong>{{ entry.value }}</strong>
</div>
</div>
</el-card>
<el-card shadow="never" class="flow-block flow-block--checklist"> <section class="flow-block flow-block--checklist">
<h4>流程检查</h4> <h4>流程进度</h4>
<p class="flow-copy"> <p class="flow-copy">
客服可以先看这里快速判断卡在哪一步再决定是刷新角色准备资源发货还是退号 当前任务只需要按这个顺序推进异常时再展开下方明细
</p> </p>
<div class="checklist-grid"> <div class="checklist-grid">
<div v-for="item in checklist" :key="item.key" class="checklist-item"> <div v-for="item in checklist" :key="item.key" class="checklist-item">
@@ -185,11 +152,11 @@ function formatAccountNames(labels: string[] | undefined, keys: string[] | undef
<strong>{{ item.detail }}</strong> <strong>{{ item.detail }}</strong>
</div> </div>
</div> </div>
</el-card> </section>
</div> </div>
<el-collapse :model-value="collapsePanels" class="flow-collapse"> <el-collapse :model-value="collapsePanels" class="flow-collapse">
<el-collapse-item name="claim" title="客户领取与绑定"> <el-collapse-item name="claim" title="客户领取与绑定明细">
<div class="flow-meta-list flow-meta-list--two-column"> <div class="flow-meta-list flow-meta-list--two-column">
<div> <div>
<span>客户链接</span><strong>{{ claimUrl || '-' }}</strong> <span>客户链接</span><strong>{{ claimUrl || '-' }}</strong>
@@ -222,7 +189,7 @@ function formatAccountNames(labels: string[] | undefined, keys: string[] | undef
</p> </p>
</el-collapse-item> </el-collapse-item>
<el-collapse-item name="ticket" title="核销与商品信息"> <el-collapse-item name="ticket" title="核销与商品明细">
<div class="flow-meta-list flow-meta-list--two-column"> <div class="flow-meta-list flow-meta-list--two-column">
<div> <div>
<span>核销码</span><strong>{{ flow.ticket.code || '-' }}</strong> <span>核销码</span><strong>{{ flow.ticket.code || '-' }}</strong>
@@ -242,7 +209,7 @@ function formatAccountNames(labels: string[] | undefined, keys: string[] | undef
</p> </p>
</el-collapse-item> </el-collapse-item>
<el-collapse-item name="cloud" title="Cloud 资源与发货"> <el-collapse-item name="cloud" title="Cloud 资源与发货明细">
<div class="flow-meta-list flow-meta-list--two-column"> <div class="flow-meta-list flow-meta-list--two-column">
<div> <div>
<span>账号优先级</span> <span>账号优先级</span>