From 563038aa40e27b2e7629ddad30a5784d86b0b8c2 Mon Sep 17 00:00:00 2001 From: yml Date: Tue, 19 May 2026 19:29:14 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E8=BF=81=E7=A7=BB3?= =?UTF-8?q?=E4=B8=AA=E5=88=97=E8=A1=A8=E8=A7=86=E5=9B=BE=E5=88=B0useAdminL?= =?UTF-8?q?istPage=20+=20=E6=8F=90=E5=8F=96useAdminAction=E7=BB=84?= =?UTF-8?q?=E5=90=88=E5=BC=8F=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/composables/useAdminListQuerySync.ts | 30 ++ apps/frontend/src/styles/admin-list-pages.css | 25 +- apps/frontend/src/utils/admin-display.ts | 289 ++++++++++-------- .../admin/orders/AdminOrderDetailView.vue | 218 ++++++------- .../views/admin/orders/AdminOrdersView.vue | 258 +++++++++------- .../src/views/admin/tasks/AdminTasksView.vue | 21 +- .../webhook-events/AdminWebhookEventsView.vue | 255 +++++++++------- 7 files changed, 629 insertions(+), 467 deletions(-) create mode 100644 apps/frontend/src/composables/useAdminListQuerySync.ts diff --git a/apps/frontend/src/composables/useAdminListQuerySync.ts b/apps/frontend/src/composables/useAdminListQuerySync.ts new file mode 100644 index 00000000..de0227f7 --- /dev/null +++ b/apps/frontend/src/composables/useAdminListQuerySync.ts @@ -0,0 +1,30 @@ +import type { RouteLocationNormalizedLoaded, Router } from 'vue-router' + +export interface ListQueryFilters { + [key: string]: string | string[] | undefined +} + +export function useAdminListQuerySync(route: RouteLocationNormalizedLoaded, router: Router) { + function readQueryString(key: string): string { + const value = route.query[key] + const rawValue = Array.isArray(value) ? value[0] : value + return typeof rawValue === 'string' ? rawValue.trim() : '' + } + + function readQueryPage(): number { + const page = Number(readQueryString('page')) + return Number.isInteger(page) && page > 0 ? page : 1 + } + + async function syncRouteQuery(page: number, filters: ListQueryFilters = {}) { + const query: Record = {} + if (page > 1) { query.page = String(page) } + for (const [key, value] of Object.entries(filters)) { + const trimmed = (Array.isArray(value) ? value.join(',') : value)?.trim() + if (trimmed) { query[key] = trimmed } + } + await router.replace({ path: route.path, query }) + } + + return { readQueryString, readQueryPage, syncRouteQuery } +} diff --git a/apps/frontend/src/styles/admin-list-pages.css b/apps/frontend/src/styles/admin-list-pages.css index 67bff397..9c484777 100644 --- a/apps/frontend/src/styles/admin-list-pages.css +++ b/apps/frontend/src/styles/admin-list-pages.css @@ -106,27 +106,27 @@ line-height: 1; } -.summary-metric[data-tone='success'] { +.summary-metric[data-tone="success"] { color: var(--color-success-dark); background: var(--color-success-light); border-color: #bbf7d0; } -.summary-metric[data-tone='danger'] { +.summary-metric[data-tone="danger"] { color: var(--color-danger-dark); background: var(--color-danger-light); border-color: #fecaca; } -.summary-metric[data-tone='primary'] { +.summary-metric[data-tone="primary"] { color: var(--color-primary-dark); background: var(--color-primary-light); border-color: #bfdbfe; } -.summary-metric[data-tone='warning'] { +.summary-metric[data-tone="warning"] { color: var(--color-warning-dark); background: var(--color-warning-light); border-color: #fed7aa; } -.summary-metric[data-tone='muted'] { +.summary-metric[data-tone="muted"] { color: var(--text-secondary); background: #f8fafc; border-color: var(--border-default); @@ -267,6 +267,17 @@ gap: 6px; align-items: center; } +.action-stack--nowrap { + flex-wrap: nowrap; + white-space: nowrap; +} +.action-stack--vertical { + flex-direction: column; + align-items: center; +} +.action-stack--tight { + gap: var(--space-2); +} .status-stack { display: flex; @@ -306,4 +317,6 @@ flex: 1 1 auto; } } -.mt-4 { margin-top: 16px; } +.mt-4 { + margin-top: 16px; +} diff --git a/apps/frontend/src/utils/admin-display.ts b/apps/frontend/src/utils/admin-display.ts index 5bec4e00..9dead2ba 100644 --- a/apps/frontend/src/utils/admin-display.ts +++ b/apps/frontend/src/utils/admin-display.ts @@ -1,183 +1,234 @@ -export function formatStatusWithRaw(status: string, labelMap: Record = {}) { - const normalized = String(status || '').trim() +export function formatStatusWithRaw( + status: string, + labelMap: Record = {} +) { + const normalized = String(status || "").trim(); if (!normalized) { - return '-' + return "-"; } - const key = normalized.toLowerCase() - const label = labelMap[key] + const key = normalized.toLowerCase(); + const label = labelMap[key]; if (!label || label === normalized) { - return normalized + return normalized; } - return `${label} (${normalized})` + return `${label} (${normalized})`; } export function formatAuditAction(action: string) { const labelMap: Record = { - admin_user_created: '创建后台用户', - admin_user_role_updated: '修改用户角色', - admin_user_status_updated: '修改用户状态', - admin_user_password_reset: '重置用户密码', - task_release_inventory: '释放任务库存', - task_release_inventory_binding: '释放任务库存绑定', - task_regenerate_claim_link: '重发领取链接', - task_closed: '关闭任务', - task_mark_manual_review: '转人工处理', - task_complete_manual_dispatch: '完成人工履约', - inventory_item_released: '释放库存项', - inventory_item_invalidated: '作废库存项', - webhook_replayed: '重放 Webhook', - platform_shop_config_updated: '更新店铺配置', - platform_fulfillment_config_updated: '更新履约配置', - } + admin_user_created: "创建后台用户", + admin_user_role_updated: "修改用户角色", + admin_user_status_updated: "修改用户状态", + admin_user_password_reset: "重置用户密码", + task_release_inventory: "释放任务库存", + task_release_inventory_binding: "释放任务库存绑定", + task_regenerate_claim_link: "重发领取链接", + task_closed: "关闭任务", + task_mark_manual_review: "转人工处理", + task_complete_manual_dispatch: "完成人工履约", + inventory_item_released: "释放库存项", + inventory_item_invalidated: "作废库存项", + webhook_replayed: "重放 Webhook", + platform_shop_config_updated: "更新店铺配置", + platform_fulfillment_config_updated: "更新履约配置", + }; - return formatStatusWithRaw(action, labelMap) + return formatStatusWithRaw(action, labelMap); } export function formatAuditTargetType(targetType: string) { const labelMap: Record = { - admin_user: '后台用户', - task: '交付任务', - task_inventory_binding: '任务库存绑定', - inventory_item: '库存项', - webhook_event: 'Webhook 事件', - platform_config: '平台配置', - } + admin_user: "后台用户", + task: "交付任务", + task_inventory_binding: "任务库存绑定", + inventory_item: "库存项", + webhook_event: "Webhook 事件", + platform_config: "平台配置", + }; - return formatStatusWithRaw(targetType, labelMap) + return formatStatusWithRaw(targetType, labelMap); } export function formatTaskEventType(eventType: string) { const labelMap: Record = { - manual_dispatch_completed: '人工履约已回写', - inventory_binding_released: '库存绑定已释放', - claim_redeem_code_used: '兑换码已使用', - claim_redeem_code_invalid: '兑换码错误', - claim_redeem_inventory_replaced: '已切换新兑换码', - claim_redeem_completed: '兑换链路完成', - agiso_auto_delivery_success: '咸鱼自动发货成功', - agiso_auto_delivery_failed: '咸鱼自动发货失败', - agiso_auto_delivery_skipped: '咸鱼自动发货已跳过', - } + manual_dispatch_completed: "人工履约已回写", + inventory_binding_released: "库存绑定已释放", + claim_redeem_code_used: "兑换码已使用", + claim_redeem_code_invalid: "兑换码错误", + claim_redeem_inventory_replaced: "已切换新兑换码", + claim_redeem_completed: "兑换链路完成", + agiso_auto_delivery_success: "咸鱼自动发货成功", + agiso_auto_delivery_failed: "咸鱼自动发货失败", + agiso_auto_delivery_skipped: "咸鱼自动发货已跳过", + }; - return formatStatusWithRaw(eventType, labelMap) + return formatStatusWithRaw(eventType, labelMap); } export function formatRedeemOutcomeLabel(value: string) { - const normalized = String(value || '').trim() + const normalized = String(value || "").trim(); switch (normalized) { - case 'success': - return '兑换成功' - case 'code_used': - return '兑换码已使用' - case 'code_invalid': - return '兑换码错误' - case 'captcha_rejected': - return '验证码错误' - case 'failed': - return '兑换失败' + case "success": + return "兑换成功"; + case "code_used": + return "兑换码已使用"; + case "code_invalid": + return "兑换码错误"; + case "captcha_rejected": + return "验证码错误"; + case "failed": + return "兑换失败"; default: - return normalized || '-' + return normalized || "-"; } } export function formatRedeemResolutionStatus(value: string) { - return value === 'success' ? '已完成' : value === 'failed' ? '失败收口' : value || '-' + return value === "success" + ? "已完成" + : value === "failed" + ? "失败收口" + : value || "-"; } export function formatAutoDeliveryTrigger(value: string) { - const normalized = String(value || '').trim() + const normalized = String(value || "").trim(); switch (normalized) { - case 'claim_redeemed': - return '用户兑换完成后触发' - case 'manual_dispatch_completed': - return '人工履约完成后触发' - case 'task_delivered': - return '任务交付完成后触发' + case "claim_redeemed": + return "用户兑换完成后触发"; + case "manual_dispatch_completed": + return "人工履约完成后触发"; + case "task_delivered": + return "任务交付完成后触发"; default: - return normalized || '-' + return normalized || "-"; } } export function formatAutoDeliveryReason(value: string) { - const normalized = String(value || '').trim() + const normalized = String(value || "").trim(); switch (normalized) { - case 'waiting_other_tasks': - return '订单下仍有任务未完成交付' - case 'already_sent': - return '该订单已经执行过自动发货' - case 'auto_delivery_disabled': - return '自动发货开关已关闭' - case 'missing_config': - return '缺少 Agiso 自动发货配置' - case 'request_failed': - return 'Agiso 接口返回失败' - case 'request_error': - return '请求 Agiso 接口时发生异常' - case 'delivery_not_confirmed': - return '接口返回成功,但订单状态未确认进入已发货' - case 'task_not_delivered': - return '当前任务尚未进入已交付状态' - case 'not_supported': - return '当前订单不支持 Agiso 咸鱼自动发货' + case "waiting_other_tasks": + return "订单下仍有任务未完成交付"; + case "already_sent": + return "该订单已经执行过自动发货"; + case "auto_delivery_disabled": + return "自动发货开关已关闭"; + case "missing_config": + return "缺少 Agiso 自动发货配置"; + case "request_failed": + return "Agiso 接口返回失败"; + case "request_error": + return "请求 Agiso 接口时发生异常"; + case "delivery_not_confirmed": + return "接口返回成功,但订单状态未确认进入已发货"; + case "task_not_delivered": + return "当前任务尚未进入已交付状态"; + case "not_supported": + return "当前订单不支持 Agiso 咸鱼自动发货"; default: - return normalized || '-' + return normalized || "-"; } } export function formatKuaishouRoleInfoLabel(value: string) { - const normalized = String(value || '').trim() + const normalized = String(value || "").trim(); switch (normalized) { - case 'name': - return '角色名称' - case 'rid': - return '角色 ID' + case "name": + return "角色名称"; + case "rid": + return "角色 ID"; default: - return normalized || '-' + return normalized || "-"; } } export function formatTaskEventPayload(payload: Record) { const parts = [ - payload.outcome ? `结果 ${payload.outcome}` : '', - payload.resultCode ? `代码 ${payload.resultCode}` : '', - payload.resultMessage ? `说明 ${payload.resultMessage}` : '', - payload.codeMasked ? `凭据 ${payload.codeMasked}` : '', - payload.previousCodeMasked ? `旧码 ${payload.previousCodeMasked}` : '', - payload.nextCodeMasked ? `新码 ${payload.nextCodeMasked}` : '', - payload.inventoryItemId ? `库存项 #${payload.inventoryItemId}` : '', - payload.previousInventoryItemId ? `旧库存 #${payload.previousInventoryItemId}` : '', - payload.nextInventoryItemId ? `新库存 #${payload.nextInventoryItemId}` : '', - payload.ticketCodeMasked ? `卡券号 ${payload.ticketCodeMasked}` : '', - payload.vnId ? `虚拟号 #${payload.vnId}` : '', - payload.vnPhoneMasked ? `号码 ${payload.vnPhoneMasked}` : '', - payload.bindUrl ? `绑定链接 ${payload.bindUrl}` : '', - payload.sendType ? `发送类型 ${payload.sendType}` : '', - payload.note ? `备注 ${payload.note}` : '', + payload.outcome ? `结果 ${payload.outcome}` : "", + payload.resultCode ? `代码 ${payload.resultCode}` : "", + payload.resultMessage ? `说明 ${payload.resultMessage}` : "", + payload.codeMasked ? `凭据 ${payload.codeMasked}` : "", + payload.previousCodeMasked ? `旧码 ${payload.previousCodeMasked}` : "", + payload.nextCodeMasked ? `新码 ${payload.nextCodeMasked}` : "", + payload.inventoryItemId ? `库存项 #${payload.inventoryItemId}` : "", + payload.previousInventoryItemId + ? `旧库存 #${payload.previousInventoryItemId}` + : "", + payload.nextInventoryItemId ? `新库存 #${payload.nextInventoryItemId}` : "", + payload.ticketCodeMasked ? `卡券号 ${payload.ticketCodeMasked}` : "", + payload.vnId ? `虚拟号 #${payload.vnId}` : "", + payload.vnPhoneMasked ? `号码 ${payload.vnPhoneMasked}` : "", + payload.bindUrl ? `绑定链接 ${payload.bindUrl}` : "", + payload.sendType ? `发送类型 ${payload.sendType}` : "", + payload.note ? `备注 ${payload.note}` : "", payload.previousOutcome - ? `切换原因 ${formatRedeemOutcomeLabel(String(payload.previousOutcome || ''))}` - : '', - payload.deliveryReference ? `单号 ${payload.deliveryReference}` : '', - payload.platformOrderId ? `平台单 ${payload.platformOrderId}` : '', - payload.trigger ? `触发 ${formatAutoDeliveryTrigger(String(payload.trigger || ''))}` : '', - payload.reason ? `原因 ${payload.reason}` : '', - payload.purchaseTriggered === true ? '已触发购买' : '', - payload.usedKnapsack === true ? '使用背包库存' : '', - payload.responseStatus ? `HTTP ${payload.responseStatus}` : '', - payload.requestId ? `请求 ${payload.requestId}` : '', - payload.errorMessage ? `错误 ${payload.errorMessage}` : '', - ].filter(Boolean) + ? `切换原因 ${formatRedeemOutcomeLabel( + String(payload.previousOutcome || "") + )}` + : "", + payload.deliveryReference ? `单号 ${payload.deliveryReference}` : "", + payload.platformOrderId ? `平台单 ${payload.platformOrderId}` : "", + payload.trigger + ? `触发 ${formatAutoDeliveryTrigger(String(payload.trigger || ""))}` + : "", + payload.reason ? `原因 ${payload.reason}` : "", + payload.purchaseTriggered === true ? "已触发购买" : "", + payload.usedKnapsack === true ? "使用背包库存" : "", + payload.responseStatus ? `HTTP ${payload.responseStatus}` : "", + payload.requestId ? `请求 ${payload.requestId}` : "", + payload.errorMessage ? `错误 ${payload.errorMessage}` : "", + ].filter(Boolean); if (parts.length > 0) { - return parts.join(' · ') + return parts.join(" · "); } - return JSON.stringify(payload || {}) + return JSON.stringify(payload || {}); +} + +export function formatBindingRoleSummary( + roleKeys: string[] | undefined | null, + emptyLabel = "-" +) { + if (!Array.isArray(roleKeys) || roleKeys.length === 0) return emptyLabel; + return roleKeys.join(" / "); +} + +export function formatBindingCountSummary(summary: { + totalBindingCount: number; + reservedBindingCount: number; + consumedBindingCount: number; + releasedBindingCount: number; +}) { + return `共 ${summary.totalBindingCount} 条 · 预占 ${summary.reservedBindingCount} · 已消费 ${summary.consumedBindingCount} · 已释放 ${summary.releasedBindingCount}`; +} + +export function resolveTaskAutoDeliveryStatus(task: { + agisoAutoDelivery?: { status?: string } | null; + deliveryStatus: string; +}) { + if (task.agisoAutoDelivery?.status) return task.agisoAutoDelivery.status; + return task.deliveryStatus === "delivered" ? "pending" : "waiting"; +} + +export function formatTaskAutoDeliveryReason(task: { + agisoAutoDelivery?: { errorMessage?: string; reason?: string } | null; + deliveryStatus: string; +}) { + if (task.agisoAutoDelivery?.errorMessage) + return task.agisoAutoDelivery.errorMessage; + if (task.agisoAutoDelivery?.reason) + return formatAutoDeliveryReason(task.agisoAutoDelivery.reason); + return task.deliveryStatus === "delivered" + ? "当前任务已交付,等待订单统一自动发货" + : "当前任务尚未完成交付"; } diff --git a/apps/frontend/src/views/admin/orders/AdminOrderDetailView.vue b/apps/frontend/src/views/admin/orders/AdminOrderDetailView.vue index d276fbce..4da543a3 100644 --- a/apps/frontend/src/views/admin/orders/AdminOrderDetailView.vue +++ b/apps/frontend/src/views/admin/orders/AdminOrderDetailView.vue @@ -1,98 +1,62 @@ - + - + @@ -279,16 +277,21 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) { - + {{ row.platform }} / + {{ row.shopName || row.shopId || "-" }} @@ -297,7 +300,9 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) { - + - @@ -380,7 +384,11 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) { .info-tile { padding: 14px var(--space-4); border-radius: var(--radius-md); - background: linear-gradient(180deg, rgba(247, 250, 255, 0.98), rgba(241, 245, 249, 0.92)); + background: linear-gradient( + 180deg, + rgba(247, 250, 255, 0.98), + rgba(241, 245, 249, 0.92) + ); border: 1px solid var(--border-default); display: grid; gap: 6px; diff --git a/apps/frontend/src/views/admin/orders/AdminOrdersView.vue b/apps/frontend/src/views/admin/orders/AdminOrdersView.vue index 31c48ebe..f0d9adb1 100644 --- a/apps/frontend/src/views/admin/orders/AdminOrdersView.vue +++ b/apps/frontend/src/views/admin/orders/AdminOrdersView.vue @@ -1,24 +1,27 @@