重构:迁移3个列表视图到useAdminListPage + 提取useAdminAction组合式函数

This commit is contained in:
yml
2026-05-19 19:27:43 +08:00
parent 110d4b9d2b
commit 671b1640aa
4 changed files with 306 additions and 266 deletions
@@ -1,51 +1,56 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { ref } from "vue";
import AdminPageHeader from '@/components/admin/AdminPageHeader.vue'
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
import { showConfirm, showError, showSuccess } from '@/lib/feedback'
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,
markAdminTaskManualReview,
regenerateAdminTaskClaimLink,
retryAdminTask,
} from '@/services/admin'
import type { AdminPagination, AdminTaskActionResponse, AdminTaskListItem } from '@/types/admin'
import { hasAdminRole } from '@/utils/admin-auth'
import { adminTaskStatusOptions } from '@/utils/admin-options'
import { formatAdminDateTime } from '@/utils/admin-time'
} from "@/services/admin";
import type { AdminTaskActionResponse, AdminTaskListItem } from "@/types/admin";
import { hasAdminRole } from "@/utils/admin-auth";
import {
formatBindingCountSummary,
formatBindingRoleSummary,
} from "@/utils/admin-display";
import { adminTaskStatusOptions } from "@/utils/admin-options";
import { formatAdminDateTime } from "@/utils/admin-time";
const loading = ref(true)
const actionLoadingId = ref<number | null>(null)
const errorMessage = ref('')
const status = ref('')
const taskNo = ref('')
const platformOrderId = ref('')
const roleId = ref('')
const skuCode = ref('')
const dateFrom = ref('')
const dateTo = ref('')
const items = ref<AdminTaskListItem[]>([])
const lastClaimUrl = ref('')
const pagination = ref<AdminPagination>({
page: 1,
pageSize: 20,
total: 0,
})
const canManageTaskLifecycle = hasAdminRole('operator')
const canCloseTasks = hasAdminRole('support')
const status = ref("");
const taskNo = ref("");
const platformOrderId = ref("");
const roleId = ref("");
const skuCode = ref("");
const dateFrom = ref("");
const dateTo = ref("");
const lastClaimUrl = ref("");
type AdminTaskActionKey = 'retry' | 'regenerate_claim_link' | 'manual_review' | 'close'
const canManageTaskLifecycle = hasAdminRole("operator");
const canCloseTasks = hasAdminRole("support");
async function loadTasks(page = pagination.value.page) {
loading.value = true
errorMessage.value = ''
type AdminTaskActionKey =
| "retry"
| "regenerate_claim_link"
| "manual_review"
| "close";
try {
const response = await fetchAdminTasks({
const {
loading,
errorMessage,
items,
pagination,
loadPage: loadTasks,
} = useAdminListPage<AdminTaskListItem>({
defaultErrorMessage: "读取任务列表失败",
fetchPage: (page, pageSize) =>
fetchAdminTasks({
page,
pageSize: pagination.value.pageSize,
pageSize,
status: status.value.trim(),
taskNo: taskNo.value.trim(),
platformOrderId: platformOrderId.value.trim(),
@@ -53,105 +58,82 @@ async function loadTasks(page = pagination.value.page) {
skuCode: skuCode.value.trim(),
dateFrom: dateFrom.value.trim(),
dateTo: dateTo.value.trim(),
})
items.value = response.data.items
pagination.value = response.data.pagination
} catch (error) {
errorMessage.value = error instanceof Error ? error.message : '读取任务列表失败'
} finally {
loading.value = false
}
}
}),
});
function formatBindingRoles(item: AdminTaskListItem) {
const roles = item.bindingSummary.roleKeys
if (!Array.isArray(roles) || roles.length === 0) return '无绑定角色'
return roles.join(' / ')
}
const { actionLoadingId, runAction } = useAdminAction();
function formatBindingCountSummary(item: AdminTaskListItem) {
const summary = item.bindingSummary
return `${summary.totalBindingCount} 条 · 预占 ${summary.reservedBindingCount} · 已消费 ${summary.consumedBindingCount} · 已释放 ${summary.releasedBindingCount}`
}
import {
formatBindingCountSummary,
formatBindingRoleSummary,
} from "@/utils/admin-display";
onMounted(() => {
void loadTasks()
})
async function runAction(
taskId: number,
action: () => Promise<{ data: AdminTaskActionResponse }>,
successMessage: string,
confirmText: string,
function handleActionCommand(
actionKey: AdminTaskActionKey,
item: AdminTaskListItem
) {
try {
await showConfirm(confirmText, '确认操作', {
type: 'warning',
confirmButtonText: '继续执行',
cancelButtonText: '取消',
})
} catch {
return
if (actionKey === "retry") {
void runAction({
id: item.taskId,
action: async () => {
const response = await retryAdminTask(item.taskId);
if (response.data.claimUrl) lastClaimUrl.value = response.data.claimUrl;
},
successMessage: "任务已重试",
confirmText: `确认重试任务 ${item.taskNo} 吗?`,
onAfterAction: async () => {
await loadTasks();
},
});
return;
}
actionLoadingId.value = taskId
try {
const response = await action()
if (response.data.claimUrl) lastClaimUrl.value = response.data.claimUrl
showSuccess(successMessage)
await loadTasks()
} catch (error) {
showError(error instanceof Error ? error.message : '操作失败')
} finally {
actionLoadingId.value = null
if (actionKey === "regenerate_claim_link") {
void runAction({
id: item.taskId,
action: async () => {
const response = await regenerateAdminTaskClaimLink(item.taskId);
if (response.data.claimUrl) lastClaimUrl.value = response.data.claimUrl;
},
successMessage: "领取链接已重新生成",
confirmText: `确认重新生成任务 ${item.taskNo} 的领取链接吗?旧链接会失效。`,
onAfterAction: async () => {
await loadTasks();
},
});
return;
}
}
function handleActionCommand(actionKey: AdminTaskActionKey, item: AdminTaskListItem) {
if (actionKey === 'retry') {
void runAction(
item.taskId,
() => retryAdminTask(item.taskId),
'任务已重试',
`确认重试任务 ${item.taskNo} 吗?`,
)
return
if (actionKey === "manual_review") {
void runAction({
id: item.taskId,
action: () => markAdminTaskManualReview(item.taskId),
successMessage: "任务已转人工处理",
confirmText: `确认将任务 ${item.taskNo} 转为人工处理吗?`,
onAfterAction: async () => {
await loadTasks();
},
});
return;
}
if (actionKey === 'regenerate_claim_link') {
void runAction(
item.taskId,
() => regenerateAdminTaskClaimLink(item.taskId),
'领取链接已重新生成',
`确认重新生成任务 ${item.taskNo} 的领取链接吗?旧链接会失效。`,
)
return
}
if (actionKey === 'manual_review') {
void runAction(
item.taskId,
() => markAdminTaskManualReview(item.taskId),
'任务已转人工处理',
`确认将任务 ${item.taskNo} 转为人工处理吗?`,
)
return
}
void runAction(
item.taskId,
() => closeAdminTask(item.taskId),
'任务已关闭',
`确认关闭任务 ${item.taskNo} 吗?关闭后不会自动继续推进。`,
)
void runAction({
id: item.taskId,
action: () => closeAdminTask(item.taskId),
successMessage: "任务已关闭",
confirmText: `确认关闭任务 ${item.taskNo} 吗?关闭后不会自动继续推进。`,
onAfterAction: async () => {
await loadTasks();
},
});
}
function resetFilters() {
status.value = ''
taskNo.value = ''
platformOrderId.value = ''
roleId.value = ''
skuCode.value = ''
dateFrom.value = ''
dateTo.value = ''
void loadTasks(1)
status.value = "";
taskNo.value = "";
platformOrderId.value = "";
roleId.value = "";
skuCode.value = "";
dateFrom.value = "";
dateTo.value = "";
void loadTasks(1);
}
</script>
@@ -170,7 +152,9 @@ function resetFilters() {
<template #header>
<div class="card-header">
<span class="card-title">查询任务</span>
<span class="card-desc">按状态任务号订单号角色标识SKU 和日期筛选</span>
<span class="card-desc"
>按状态任务号订单号角色标识SKU 和日期筛选</span
>
</div>
</template>
<div class="filter-grid">
@@ -200,7 +184,12 @@ function resetFilters() {
clearable
@keyup.enter="loadTasks(1)"
/>
<el-input v-model="skuCode" placeholder="商品 SKU" clearable @keyup.enter="loadTasks(1)" />
<el-input
v-model="skuCode"
placeholder="商品 SKU"
clearable
@keyup.enter="loadTasks(1)"
/>
<el-date-picker
v-model="dateFrom"
type="date"
@@ -250,10 +239,17 @@ function resetFilters() {
<template v-else>
<div class="tasks-toolbar table-toolbar">
<strong>任务列表</strong>
<span> {{ pagination.page }} 当前展示 {{ items.length }} </span>
<span
> {{ pagination.page }} 当前展示 {{ items.length }} </span
>
</div>
<el-table :data="items" stripe size="small" class="tasks-table data-table">
<el-table
:data="items"
stripe
size="small"
class="tasks-table data-table"
>
<el-table-column label="任务 / 商品" min-width="220">
<template #default="{ row }">
<div class="cell-stack">
@@ -268,12 +264,14 @@ function resetFilters() {
<span class="compact-chip" :title="row.platformOrderId"
>订单 {{ row.platformOrderId }}</span
>
<span class="compact-chip compact-chip--muted">重试 {{ row.retryCount }} </span>
<span class="compact-chip compact-chip--muted"
>重试 {{ row.retryCount }} </span
>
</div>
<span class="cell-title" :title="row.skuName || row.skuCode">{{
row.skuName || row.skuCode
}}</span>
<span class="cell-subline">SKU {{ row.skuCode || '-' }}</span>
<span class="cell-subline">SKU {{ row.skuCode || "-" }}</span>
</div>
</template>
</el-table-column>
@@ -298,15 +296,21 @@ function resetFilters() {
<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-title"
:title="row.roleName || row.roleId || '-'"
>{{ row.roleName || "-" }}</span
>
<span class="cell-subline">登录 {{ row.loginType || '-' }}</span>
<span class="cell-subline" :title="row.inventoryDisplayMasked || '-'"
>凭据 {{ row.inventoryDisplayMasked || '-' }}</span
<span class="cell-subline" :title="row.roleId || '-'"
>角色ID {{ row.roleId || "-" }}</span
>
<span class="cell-subline"
>登录 {{ row.loginType || "-" }}</span
>
<span
class="cell-subline"
:title="row.inventoryDisplayMasked || '-'"
>凭据 {{ row.inventoryDisplayMasked || "-" }}</span
>
<span class="cell-subline" :title="formatBindingRoles(row)"
>绑定 {{ formatBindingRoles(row) }}</span
@@ -322,8 +326,12 @@ function resetFilters() {
<el-table-column label="时间" min-width="180">
<template #default="{ row }">
<div class="cell-stack">
<span class="cell-subline">创建 {{ formatAdminDateTime(row.createdAt) }}</span>
<span class="cell-subline">更新 {{ formatAdminDateTime(row.updatedAt) }}</span>
<span class="cell-subline"
>创建 {{ formatAdminDateTime(row.createdAt) }}</span
>
<span class="cell-subline"
>更新 {{ formatAdminDateTime(row.updatedAt) }}</span
>
<span v-if="row.claimedAt" class="cell-subline"
>领取 {{ formatAdminDateTime(row.claimedAt) }}</span
>
@@ -340,32 +348,46 @@ function resetFilters() {
<template #default="{ row }">
<div class="cell-stack">
<span class="cell-error" :title="row.lastError || '-'">{{
row.lastError || '当前无异常'
row.lastError || "当前无异常"
}}</span>
<div class="action-stack">
<el-button
v-if="canManageTaskLifecycle"
:disabled="
row.executorKey === 'manual_dispatch' ||
!['retry_pending', 'manual_review', 'waiting_inventory'].includes(row.status)
![
'retry_pending',
'manual_review',
'waiting_inventory',
].includes(row.status)
"
:loading="actionLoadingId === row.taskId"
size="small"
type="danger"
plain
@click="
runAction(
row.taskId,
() => retryAdminTask(row.taskId),
'任务已重试',
`确认重试任务 ${row.taskNo} 吗?`,
)
runAction({
id: row.taskId,
action: async () => {
const response = await retryAdminTask(row.taskId);
if (response.data.claimUrl)
lastClaimUrl.value = response.data.claimUrl;
},
successMessage: '任务已重试',
confirmText: `确认重试任务 ${row.taskNo} 吗?`,
onAfterAction: async () => {
await loadTasks();
},
})
"
>
重试
</el-button>
<el-button
v-if="canManageTaskLifecycle && row.executorKey !== 'manual_dispatch'"
v-if="
canManageTaskLifecycle &&
row.executorKey !== 'manual_dispatch'
"
size="small"
@click="handleActionCommand('regenerate_claim_link', row)"
>
@@ -379,7 +401,10 @@ function resetFilters() {
转人工
</el-button>
<el-button
v-if="canCloseTasks && !['redeemed', 'closed'].includes(row.status)"
v-if="
canCloseTasks &&
!['redeemed', 'closed'].includes(row.status)
"
size="small"
type="danger"
@click="handleActionCommand('close', row)"
@@ -407,13 +432,12 @@ function resetFilters() {
</template>
<style scoped>
@import '../../../styles/admin-list-pages.css';
@import "../../../styles/admin-list-pages.css";
.filter-grid {
grid-template-columns: minmax(150px, 0.75fr) minmax(220px, 1fr) minmax(220px, 1fr) minmax(
210px,
0.95fr
);
grid-template-columns:
minmax(150px, 0.75fr) minmax(220px, 1fr) minmax(220px, 1fr)
minmax(210px, 0.95fr);
}
.filter-grid :deep(.el-date-editor) {
width: 100%;