优化-任务列表和详情页
This commit is contained in:
@@ -27,9 +27,17 @@
|
||||
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 {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hero-copy h2 {
|
||||
@@ -45,6 +53,10 @@
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.hero-copy p {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.hero-eyebrow {
|
||||
color: var(--color-primary);
|
||||
font-size: var(--text-xs);
|
||||
@@ -458,6 +470,7 @@
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.hero-head,
|
||||
.hero-inline,
|
||||
.hero-link-row,
|
||||
.flow-card-head,
|
||||
|
||||
@@ -4,7 +4,6 @@ import { onMounted, ref } from "vue";
|
||||
import { useAdminAction } from "@/composables/useAdminAction";
|
||||
import { useAdminListPage } from "@/composables/useAdminListPage";
|
||||
import AdminPageHeader from "@/components/admin/AdminPageHeader.vue";
|
||||
import AdminStatusTag from "@/components/admin/AdminStatusTag.vue";
|
||||
import {
|
||||
closeAdminTask,
|
||||
fetchAdminTasks,
|
||||
@@ -127,6 +126,56 @@ function resetFilters() {
|
||||
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(() => {
|
||||
void loadTasks(1);
|
||||
});
|
||||
@@ -136,7 +185,7 @@ onMounted(() => {
|
||||
<div class="tasks-page list-page">
|
||||
<AdminPageHeader
|
||||
title="交付任务"
|
||||
description="更紧凑地查看任务状态、角色绑定和异常信息。"
|
||||
description="查看自动履约任务、客户步骤和异常处理。"
|
||||
>
|
||||
<template #extra>
|
||||
<span class="total-badge">共 {{ pagination.total }} 条任务</span>
|
||||
@@ -148,7 +197,7 @@ onMounted(() => {
|
||||
<div class="card-header">
|
||||
<span class="card-title">查询任务</span>
|
||||
<span class="card-desc"
|
||||
>按状态、任务号、订单号、角色标识、SKU 和日期筛选。</span
|
||||
>按状态、任务号、订单号、商品和日期筛选。</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
@@ -175,13 +224,13 @@ onMounted(() => {
|
||||
/>
|
||||
<el-input
|
||||
v-model="roleId"
|
||||
placeholder="角色唯一标识"
|
||||
placeholder="角色 ID"
|
||||
clearable
|
||||
@keyup.enter="loadTasks(1)"
|
||||
/>
|
||||
<el-input
|
||||
v-model="skuCode"
|
||||
placeholder="商品 SKU"
|
||||
placeholder="商品名 / SKU"
|
||||
clearable
|
||||
@keyup.enter="loadTasks(1)"
|
||||
/>
|
||||
@@ -245,7 +294,7 @@ onMounted(() => {
|
||||
size="small"
|
||||
class="tasks-table data-table"
|
||||
>
|
||||
<el-table-column label="任务 / 商品" min-width="220">
|
||||
<el-table-column label="任务 / 订单" min-width="230">
|
||||
<template #default="{ row }">
|
||||
<div class="cell-stack">
|
||||
<RouterLink
|
||||
@@ -263,6 +312,12 @@ onMounted(() => {
|
||||
>重试 {{ row.retryCount }} 次</span
|
||||
>
|
||||
</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">{{
|
||||
row.skuName || row.skuCode
|
||||
}}</span>
|
||||
@@ -270,38 +325,19 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="进度" min-width="160">
|
||||
<el-table-column label="状态" min-width="250">
|
||||
<template #default="{ row }">
|
||||
<div class="cell-stack">
|
||||
<div class="progress-row">
|
||||
<span class="progress-label">任务</span>
|
||||
<AdminStatusTag :status="row.status" />
|
||||
</div>
|
||||
<div class="progress-row">
|
||||
<span class="progress-label">系统</span>
|
||||
<AdminStatusTag :status="row.systemBindingStatus" />
|
||||
</div>
|
||||
<div class="progress-row">
|
||||
<span class="progress-label">完整</span>
|
||||
<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 class="task-status-summary">
|
||||
<span class="task-main-chip" :data-tone="resolveTaskTone(row)">
|
||||
{{ resolveTaskMainText(row) }}
|
||||
</span>
|
||||
<p class="task-status-note">
|
||||
客户 {{ resolveCustomerStageLabel(row.userBindingStatus) }}
|
||||
· 资源 {{ resolveResourceStageLabel(row.systemBindingStatus) }}
|
||||
</p>
|
||||
<p v-if="row.roleName || row.roleId" class="task-status-note">
|
||||
角色 {{ row.roleName || "-" }} / {{ row.roleId || "-" }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -343,21 +379,7 @@ onMounted(() => {
|
||||
size="small"
|
||||
type="danger"
|
||||
plain
|
||||
@click="
|
||||
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();
|
||||
},
|
||||
})
|
||||
"
|
||||
@click="handleActionCommand('retry', row)"
|
||||
>
|
||||
重试
|
||||
</el-button>
|
||||
@@ -421,37 +443,54 @@ onMounted(() => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.progress-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
.task-status-summary {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
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;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 34px;
|
||||
min-height: 22px;
|
||||
padding: 0 7px;
|
||||
width: fit-content;
|
||||
min-height: 26px;
|
||||
padding: 0 10px;
|
||||
border-radius: var(--radius-full);
|
||||
background: #f7f9fc;
|
||||
border: 1px solid var(--border-default);
|
||||
border: 1px solid transparent;
|
||||
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);
|
||||
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;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.cell-error {
|
||||
|
||||
@@ -13,6 +13,40 @@ interface 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>
|
||||
|
||||
<template>
|
||||
@@ -22,16 +56,23 @@ defineProps<Props>()
|
||||
shadow="never"
|
||||
class="section-card hero-card"
|
||||
>
|
||||
<div class="hero-copy">
|
||||
<span class="hero-eyebrow">{{ detail.task.executorKey || 'task' }}</span>
|
||||
<h2>{{ detail.task.taskNo }}</h2>
|
||||
<p>
|
||||
订单 {{ detail.order?.platformOrderId || '-' }} · 商品
|
||||
{{ detail.orderItem?.skuName || detail.orderItem?.skuCode || '-' }}
|
||||
· 店铺
|
||||
{{ detail.order?.shopName || detail.order?.shopId || '-' }}
|
||||
</p>
|
||||
<div class="hero-head">
|
||||
<div class="hero-copy">
|
||||
<span class="hero-eyebrow">自动履约任务</span>
|
||||
<h2>{{ detail.task.taskNo }}</h2>
|
||||
<p>
|
||||
订单 {{ detail.order?.platformOrderId || '-' }} · 商品
|
||||
{{ detail.orderItem?.skuName || detail.orderItem?.skuCode || '-' }}
|
||||
· 核销店铺
|
||||
{{ detail.order?.shopName || detail.order?.shopId || '-' }}
|
||||
</p>
|
||||
</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">
|
||||
<el-card
|
||||
v-for="card in summaryCards"
|
||||
@@ -44,8 +85,8 @@ defineProps<Props>()
|
||||
</el-card>
|
||||
</div>
|
||||
<div class="hero-inline">
|
||||
<span>系统绑定 <AdminStatusTag :status="detail.task.systemBindingStatus" /></span>
|
||||
<span>完整绑定 <AdminStatusTag :status="detail.task.userBindingStatus" /></span>
|
||||
<span>资源 {{ resolveResourceStageLabel(detail.task.systemBindingStatus) }}</span>
|
||||
<span>客户 {{ resolveCustomerStageLabel(detail.task.userBindingStatus) }}</span>
|
||||
<span v-if="detail.task.lastError">最近异常 {{ detail.task.lastError }}</span>
|
||||
</div>
|
||||
<div v-if="claimUrl" class="hero-link-row">
|
||||
@@ -72,8 +113,8 @@ defineProps<Props>()
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
系统绑定:<AdminStatusTag :status="detail.task.systemBindingStatus" /> ·
|
||||
完整绑定:<AdminStatusTag :status="detail.task.userBindingStatus" />
|
||||
资源:{{ resolveResourceStageLabel(detail.task.systemBindingStatus) }} ·
|
||||
客户:{{ resolveCustomerStageLabel(detail.task.userBindingStatus) }}
|
||||
</p>
|
||||
<p>
|
||||
角色:{{ detail.task.roleName || '-' }} /
|
||||
|
||||
@@ -62,10 +62,10 @@ defineProps<Props>()
|
||||
<el-descriptions-item label="用户打开链接">
|
||||
{{ formatAdminDateTime(detail.task.claimedAt) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户提交绑定">
|
||||
<el-descriptions-item label="客户提交">
|
||||
{{ formatAdminDateTime(detail.task.roleConfirmedAt) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="完整绑定成功">
|
||||
<el-descriptions-item label="履约完成">
|
||||
{{ formatAdminDateTime(detail.task.redeemedAt) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="截图路径" :span="2">
|
||||
|
||||
@@ -27,7 +27,7 @@ interface Props {
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const collapsePanels = ['claim', 'ticket', 'cloud']
|
||||
const collapsePanels: string[] = []
|
||||
|
||||
const emit = defineEmits<{
|
||||
refreshRole: []
|
||||
@@ -64,9 +64,9 @@ function formatAccountNames(labels: string[] | undefined, keys: string[] | undef
|
||||
<el-card shadow="never" class="section-card flow-card">
|
||||
<div class="flow-card-head">
|
||||
<div>
|
||||
<h3>快手 Cloud 履约面板</h3>
|
||||
<h3>自动履约流程</h3>
|
||||
<p class="section-copy">
|
||||
这是一套独立的快手 Cloud 任务面板,优先展示角色信息与当前履约推进点,次级明细可按需展开。
|
||||
优先看当前步骤和客户角色;详细链接、核销、cloud 资源字段可按需展开。
|
||||
</p>
|
||||
</div>
|
||||
<div class="flow-head-actions">
|
||||
@@ -97,40 +97,13 @@ function formatAccountNames(labels: string[] | undefined, keys: string[] | undef
|
||||
</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">
|
||||
<el-card shadow="never" class="flow-block flow-block--spotlight">
|
||||
<section class="flow-block flow-block--spotlight">
|
||||
<div class="spotlight-head">
|
||||
<div>
|
||||
<h4>角色信息</h4>
|
||||
<h4>客户角色</h4>
|
||||
<p class="flow-copy">
|
||||
角色信息来自 cloudtentacles 的 `vn/bind_info`,客服可在客户绑定后手动刷新。
|
||||
客户绑定后刷新角色信息,确认无误后再执行发货。
|
||||
</p>
|
||||
</div>
|
||||
<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">
|
||||
{{ flow.role.errorMessage }}
|
||||
</p>
|
||||
<div v-if="roleInfoEntries.length > 0" class="flow-meta-list flow-meta-list--dense">
|
||||
<div v-for="entry in roleInfoEntries" :key="entry.key">
|
||||
<span>{{ entry.label }}</span>
|
||||
<strong>{{ entry.value }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</section>
|
||||
|
||||
<el-card shadow="never" class="flow-block flow-block--checklist">
|
||||
<h4>流程检查</h4>
|
||||
<section class="flow-block flow-block--checklist">
|
||||
<h4>流程进度</h4>
|
||||
<p class="flow-copy">
|
||||
客服可以先看这里,快速判断卡在哪一步,再决定是刷新角色、准备资源、发货还是退号。
|
||||
当前任务只需要按这个顺序推进,异常时再展开下方明细。
|
||||
</p>
|
||||
<div class="checklist-grid">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<span>客户链接</span><strong>{{ claimUrl || '-' }}</strong>
|
||||
@@ -222,7 +189,7 @@ function formatAccountNames(labels: string[] | undefined, keys: string[] | undef
|
||||
</p>
|
||||
</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>
|
||||
<span>核销码</span><strong>{{ flow.ticket.code || '-' }}</strong>
|
||||
@@ -242,7 +209,7 @@ function formatAccountNames(labels: string[] | undefined, keys: string[] | undef
|
||||
</p>
|
||||
</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>
|
||||
<span>账号优先级</span>
|
||||
|
||||
Reference in New Issue
Block a user