重构:迁移3个列表视图到useAdminListPage + 提取useAdminAction组合式函数
This commit is contained in:
@@ -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<string, string> = {}
|
||||
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 }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,183 +1,234 @@
|
||||
export function formatStatusWithRaw(status: string, labelMap: Record<string, string> = {}) {
|
||||
const normalized = String(status || '').trim()
|
||||
export function formatStatusWithRaw(
|
||||
status: string,
|
||||
labelMap: Record<string, string> = {}
|
||||
) {
|
||||
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<string, string> = {
|
||||
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<string, string> = {
|
||||
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<string, string> = {
|
||||
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<string, unknown>) {
|
||||
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"
|
||||
? "当前任务已交付,等待订单统一自动发货"
|
||||
: "当前任务尚未完成交付";
|
||||
}
|
||||
|
||||
@@ -1,98 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
|
||||
import { fetchAdminOrderDetail } from '@/services/admin'
|
||||
import type { AdminOrderDetail } from '@/types/admin'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
import { formatAdminWebhookEventType } from '@/utils/admin-webhook'
|
||||
import AdminStatusTag from "@/components/admin/AdminStatusTag.vue";
|
||||
import { fetchAdminOrderDetail } from "@/services/admin";
|
||||
import type { AdminOrderDetail } from "@/types/admin";
|
||||
import {
|
||||
formatAutoDeliveryReason,
|
||||
formatBindingRoleSummary,
|
||||
formatBindingCountSummary,
|
||||
resolveTaskAutoDeliveryStatus,
|
||||
formatTaskAutoDeliveryReason,
|
||||
} from "@/utils/admin-display";
|
||||
import { formatAdminDateTime } from "@/utils/admin-time";
|
||||
import { formatAdminWebhookEventType } from "@/utils/admin-webhook";
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(true)
|
||||
const errorMessage = ref('')
|
||||
const detail = ref<AdminOrderDetail | null>(null)
|
||||
const route = useRoute();
|
||||
const loading = ref(true);
|
||||
const errorMessage = ref("");
|
||||
const detail = ref<AdminOrderDetail | null>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await fetchAdminOrderDetail(String(route.params.orderId || ''))
|
||||
detail.value = response.data
|
||||
const response = await fetchAdminOrderDetail(
|
||||
String(route.params.orderId || "")
|
||||
);
|
||||
detail.value = response.data;
|
||||
} catch (error) {
|
||||
errorMessage.value = error instanceof Error ? error.message : '读取订单详情失败'
|
||||
errorMessage.value =
|
||||
error instanceof Error ? error.message : "读取订单详情失败";
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function formatJsonBlock(value: Record<string, unknown>) {
|
||||
return JSON.stringify(value || {}, null, 2)
|
||||
return JSON.stringify(value || {}, null, 2);
|
||||
}
|
||||
|
||||
function formatSpecSummary(spec: Record<string, unknown>) {
|
||||
const entries = Object.entries(spec || {})
|
||||
.filter(([, value]) => value !== null && value !== undefined && String(value).trim() !== '')
|
||||
.filter(
|
||||
([, value]) =>
|
||||
value !== null && value !== undefined && String(value).trim() !== ""
|
||||
)
|
||||
.slice(0, 4)
|
||||
.map(([key, value]) => `${key}: ${String(value)}`)
|
||||
.map(([key, value]) => `${key}: ${String(value)}`);
|
||||
|
||||
return entries.length > 0 ? entries.join(' | ') : '-'
|
||||
}
|
||||
|
||||
function formatBindingRoleSummary(roleKeys: string[]) {
|
||||
return Array.isArray(roleKeys) && roleKeys.length > 0 ? roleKeys.join(' / ') : '-'
|
||||
}
|
||||
|
||||
function formatTaskBindingCountSummary(task: AdminOrderDetail['tasks'][number]) {
|
||||
const summary = task.bindingSummary
|
||||
return `共 ${summary.totalBindingCount} 条 · 预占 ${summary.reservedBindingCount} · 已消费 ${summary.consumedBindingCount} · 已释放 ${summary.releasedBindingCount}`
|
||||
}
|
||||
|
||||
function formatAutoDeliveryReason(value: string) {
|
||||
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 接口时发生异常'
|
||||
default:
|
||||
return normalized || '-'
|
||||
}
|
||||
}
|
||||
|
||||
function resolveTaskAutoDeliveryStatus(task: AdminOrderDetail['tasks'][number]) {
|
||||
if (task.agisoAutoDelivery?.status) {
|
||||
return task.agisoAutoDelivery.status
|
||||
}
|
||||
|
||||
return task.deliveryStatus === 'delivered' ? 'pending' : 'waiting'
|
||||
}
|
||||
|
||||
function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
|
||||
if (task.agisoAutoDelivery?.errorMessage) {
|
||||
return task.agisoAutoDelivery.errorMessage
|
||||
}
|
||||
|
||||
if (task.agisoAutoDelivery?.reason) {
|
||||
return formatAutoDeliveryReason(task.agisoAutoDelivery.reason)
|
||||
}
|
||||
|
||||
return task.deliveryStatus === 'delivered'
|
||||
? '当前任务已交付,等待订单统一自动发货'
|
||||
: '当前任务尚未完成交付'
|
||||
return entries.length > 0 ? entries.join(" | ") : "-";
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="admin-panel">
|
||||
<AdminPageHeader title="订单详情" description="查看订单原始信息、商品子项和关联任务。" />
|
||||
<AdminPageHeader
|
||||
title="订单详情"
|
||||
description="查看订单原始信息、商品子项和关联任务。"
|
||||
/>
|
||||
|
||||
<p v-if="errorMessage" class="error-copy">{{ errorMessage }}</p>
|
||||
|
||||
@@ -105,37 +69,50 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
|
||||
<h2>{{ detail.order.platformOrderId }}</h2>
|
||||
<p class="subtitle">
|
||||
{{ detail.order.platform }} /
|
||||
{{ detail.order.shopName || detail.order.shopId || '未识别店铺' }}
|
||||
{{ detail.order.shopName || detail.order.shopId || "未识别店铺" }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="status-stack">
|
||||
<AdminStatusTag :status="detail.order.payStatus" />
|
||||
<AdminStatusTag :status="detail.order.orderStatus" />
|
||||
<AdminStatusTag :status="detail.order.bindingSummary.systemBindingStatus" />
|
||||
<AdminStatusTag :status="detail.order.bindingSummary.userBindingStatus" />
|
||||
<AdminStatusTag
|
||||
:status="detail.order.bindingSummary.systemBindingStatus"
|
||||
/>
|
||||
<AdminStatusTag
|
||||
:status="detail.order.bindingSummary.userBindingStatus"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-grid">
|
||||
<article class="info-tile">
|
||||
<span>商品标题</span>
|
||||
<strong>{{ detail.order.itemSummary || '-' }}</strong>
|
||||
<strong>{{ detail.order.itemSummary || "-" }}</strong>
|
||||
<small>来自订单详情 title / 商品标题字段</small>
|
||||
</article>
|
||||
<article class="info-tile">
|
||||
<span>订单金额</span>
|
||||
<strong>{{ detail.order.totalAmount }} {{ detail.order.currency }}</strong>
|
||||
<small>支付时间:{{ formatAdminDateTime(detail.order.paidAt) }}</small>
|
||||
<strong
|
||||
>{{ detail.order.totalAmount }}
|
||||
{{ detail.order.currency }}</strong
|
||||
>
|
||||
<small
|
||||
>支付时间:{{ formatAdminDateTime(detail.order.paidAt) }}</small
|
||||
>
|
||||
</article>
|
||||
<article class="info-tile">
|
||||
<span>买家信息</span>
|
||||
<strong>{{ detail.order.buyerName || '-' }}</strong>
|
||||
<small>ID:{{ detail.order.buyerId || '-' }}</small>
|
||||
<strong>{{ detail.order.buyerName || "-" }}</strong>
|
||||
<small>ID:{{ detail.order.buyerId || "-" }}</small>
|
||||
</article>
|
||||
<article class="info-tile">
|
||||
<span>联系方式</span>
|
||||
<strong>{{ detail.order.receiverContact || '-' }}</strong>
|
||||
<small>店铺:{{ detail.order.shopName || detail.order.shopId || '-' }}</small>
|
||||
<strong>{{ detail.order.receiverContact || "-" }}</strong>
|
||||
<small
|
||||
>店铺:{{
|
||||
detail.order.shopName || detail.order.shopId || "-"
|
||||
}}</small
|
||||
>
|
||||
</article>
|
||||
<article class="info-tile">
|
||||
<span>任务进度</span>
|
||||
@@ -154,10 +131,16 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
|
||||
</article>
|
||||
<article class="info-tile">
|
||||
<span>库存绑定</span>
|
||||
<strong>{{ detail.order.bindingSummary.totalBindingCount }} 条绑定记录</strong>
|
||||
<strong
|
||||
>{{
|
||||
detail.order.bindingSummary.totalBindingCount
|
||||
}}
|
||||
条绑定记录</strong
|
||||
>
|
||||
<small
|
||||
>预占 {{ detail.order.bindingSummary.reservedBindingCount }} · 已消费
|
||||
{{ detail.order.bindingSummary.consumedBindingCount }} · 已释放
|
||||
>预占 {{ detail.order.bindingSummary.reservedBindingCount }} ·
|
||||
已消费 {{ detail.order.bindingSummary.consumedBindingCount }} ·
|
||||
已释放
|
||||
{{ detail.order.bindingSummary.releasedBindingCount }}</small
|
||||
>
|
||||
</article>
|
||||
@@ -169,15 +152,20 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
|
||||
<strong>
|
||||
{{
|
||||
detail.order.agisoAutoDelivery.errorMessage ||
|
||||
formatAutoDeliveryReason(detail.order.agisoAutoDelivery.reason) ||
|
||||
'全部任务交付后会自动触发'
|
||||
formatAutoDeliveryReason(
|
||||
detail.order.agisoAutoDelivery.reason
|
||||
) ||
|
||||
"全部任务交付后会自动触发"
|
||||
}}
|
||||
</strong>
|
||||
<small>
|
||||
已交付 {{ detail.order.agisoAutoDelivery.deliveredTaskCount }}/{{
|
||||
detail.order.agisoAutoDelivery.totalTaskCount
|
||||
}}
|
||||
· 最近执行 {{ formatAdminDateTime(detail.order.agisoAutoDelivery.updatedAt) }}
|
||||
· 最近执行
|
||||
{{
|
||||
formatAdminDateTime(detail.order.agisoAutoDelivery.updatedAt)
|
||||
}}
|
||||
</small>
|
||||
</article>
|
||||
</div>
|
||||
@@ -193,13 +181,17 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
|
||||
<template #default="{ row }">{{ row.platform }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="店铺 ID">
|
||||
<template #default="{ row }">{{ row.shopId || '-' }}</template>
|
||||
<template #default="{ row }">{{ row.shopId || "-" }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">{{ formatAdminDateTime(row.createdAt) }}</template>
|
||||
<template #default="{ row }">{{
|
||||
formatAdminDateTime(row.createdAt)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="更新时间" width="180">
|
||||
<template #default="{ row }">{{ formatAdminDateTime(row.updatedAt) }}</template>
|
||||
<template #default="{ row }">{{
|
||||
formatAdminDateTime(row.updatedAt)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
@@ -209,13 +201,15 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
|
||||
<el-table :data="detail.items" size="small">
|
||||
<el-table-column prop="skuCode" label="SKU" />
|
||||
<el-table-column prop="itemTitle" label="标题">
|
||||
<template #default="{ row }">{{ row.itemTitle || '-' }}</template>
|
||||
<template #default="{ row }">{{ row.itemTitle || "-" }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="skuName" label="商品" />
|
||||
<el-table-column prop="deliveryMode" label="发放方式" />
|
||||
<el-table-column prop="quantity" label="数量" width="80" />
|
||||
<el-table-column label="规格摘要">
|
||||
<template #default="{ row }">{{ formatSpecSummary(row.spec) }}</template>
|
||||
<template #default="{ row }">{{
|
||||
formatSpecSummary(row.spec)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
@@ -225,7 +219,9 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
|
||||
<el-table :data="detail.tasks" size="small">
|
||||
<el-table-column label="任务号">
|
||||
<template #default="{ row }">
|
||||
<RouterLink :to="`/admin/tasks/${row.taskId}`">{{ row.taskNo }}</RouterLink>
|
||||
<RouterLink :to="`/admin/tasks/${row.taskId}`">{{
|
||||
row.taskNo
|
||||
}}</RouterLink>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态">
|
||||
@@ -248,22 +244,24 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
|
||||
<div class="tile-tag-line">
|
||||
<AdminStatusTag :status="resolveTaskAutoDeliveryStatus(row)" />
|
||||
</div>
|
||||
<div class="binding-copy">{{ formatTaskAutoDeliveryReason(row) }}</div>
|
||||
<div class="binding-copy">
|
||||
{{ formatTaskAutoDeliveryReason(row) }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存绑定">
|
||||
<template #default="{ row }">
|
||||
<div>{{ formatTaskBindingCountSummary(row) }}</div>
|
||||
<div>{{ formatBindingCountSummary(row.bindingSummary) }}</div>
|
||||
<div class="binding-copy">
|
||||
角色 {{ formatBindingRoleSummary(row.bindingSummary.roleKeys) }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="登录方式">
|
||||
<template #default="{ row }">{{ row.loginType || '-' }}</template>
|
||||
<template #default="{ row }">{{ row.loginType || "-" }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="错误">
|
||||
<template #default="{ row }">{{ row.lastError || '-' }}</template>
|
||||
<template #default="{ row }">{{ row.lastError || "-" }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
@@ -279,16 +277,21 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型">
|
||||
<template #default="{ row }">{{ formatAdminWebhookEventType(row.eventType) }}</template>
|
||||
<template #default="{ row }">{{
|
||||
formatAdminWebhookEventType(row.eventType)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="平台 / 店铺">
|
||||
<template #default="{ row }"
|
||||
>{{ row.platform }} / {{ row.shopName || row.shopId || '-' }}</template
|
||||
>{{ row.platform }} /
|
||||
{{ row.shopName || row.shopId || "-" }}</template
|
||||
>
|
||||
</el-table-column>
|
||||
<el-table-column label="验签">
|
||||
<template #default="{ row }">
|
||||
<AdminStatusTag :status="row.signatureValid ? 'success' : 'failed'" />
|
||||
<AdminStatusTag
|
||||
:status="row.signatureValid ? 'success' : 'failed'"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="处理">
|
||||
@@ -297,7 +300,9 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="时间" width="180">
|
||||
<template #default="{ row }">{{ formatAdminDateTime(row.createdAt) }}</template>
|
||||
<template #default="{ row }">{{
|
||||
formatAdminDateTime(row.createdAt)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<el-empty description="当前订单还没有关联的 webhook 记录" />
|
||||
@@ -315,7 +320,6 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
|
||||
</template>
|
||||
|
||||
<el-empty v-else description="订单不存在或已被删除" />
|
||||
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
import { useAdminListPage } from '@/composables/useAdminListPage'
|
||||
import AdminPageHeader from '@/components/admin/AdminPageHeader.vue'
|
||||
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
|
||||
import { fetchAdminOrders } from '@/services/admin'
|
||||
import type { AdminOrderListItem } from '@/types/admin'
|
||||
import { adminPayStatusOptions } from '@/utils/admin-options'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
import { useAdminListPage } from "@/composables/useAdminListPage";
|
||||
import { useAdminListQuerySync } from "@/composables/useAdminListQuerySync";
|
||||
import AdminPageHeader from "@/components/admin/AdminPageHeader.vue";
|
||||
import AdminStatusTag from "@/components/admin/AdminStatusTag.vue";
|
||||
import { fetchAdminOrders } from "@/services/admin";
|
||||
import type { AdminOrderListItem } from "@/types/admin";
|
||||
import { adminPayStatusOptions } from "@/utils/admin-options";
|
||||
import { formatAdminDateTime } from "@/utils/admin-time";
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { readQueryString, readQueryPage, syncRouteQuery } =
|
||||
useAdminListQuerySync(route, router);
|
||||
|
||||
const platformOrderId = ref(readQueryString('platformOrderId'))
|
||||
const payStatus = ref(readQueryString('payStatus'))
|
||||
const skuCode = ref(readQueryString('skuCode'))
|
||||
const platformOrderId = ref(readQueryString("platformOrderId"));
|
||||
const payStatus = ref(readQueryString("payStatus"));
|
||||
const skuCode = ref(readQueryString("skuCode"));
|
||||
const dateRange = ref<string[]>(
|
||||
[readQueryString('dateFrom'), readQueryString('dateTo')].filter(Boolean),
|
||||
)
|
||||
[readQueryString("dateFrom"), readQueryString("dateTo")].filter(Boolean)
|
||||
);
|
||||
|
||||
const {
|
||||
loading,
|
||||
@@ -27,7 +30,7 @@ const {
|
||||
pagination,
|
||||
loadPage: loadOrders,
|
||||
} = useAdminListPage<AdminOrderListItem>({
|
||||
defaultErrorMessage: '读取订单列表失败',
|
||||
defaultErrorMessage: "读取订单列表失败",
|
||||
fetchPage: (page, pageSize) =>
|
||||
fetchAdminOrders({
|
||||
page,
|
||||
@@ -35,114 +38,93 @@ const {
|
||||
platformOrderId: platformOrderId.value.trim(),
|
||||
payStatus: payStatus.value.trim(),
|
||||
skuCode: skuCode.value.trim(),
|
||||
dateFrom: dateRange.value[0] || '',
|
||||
dateTo: dateRange.value[1] || '',
|
||||
dateFrom: dateRange.value[0] || "",
|
||||
dateTo: dateRange.value[1] || "",
|
||||
}),
|
||||
})
|
||||
});
|
||||
|
||||
const summary = computed(() => ({
|
||||
paidCount: items.value.filter((item) => item.payStatus === 'paid').length,
|
||||
refundedCount: items.value.filter((item) => item.payStatus === 'refunded').length,
|
||||
bindingReadyCount: items.value.filter((item) => item.completedBindingTaskCount > 0).length,
|
||||
paidCount: items.value.filter((item) => item.payStatus === "paid").length,
|
||||
refundedCount: items.value.filter((item) => item.payStatus === "refunded")
|
||||
.length,
|
||||
bindingReadyCount: items.value.filter(
|
||||
(item) => item.completedBindingTaskCount > 0
|
||||
).length,
|
||||
issueCount: items.value.filter((item) => hasOrderIssue(item)).length,
|
||||
}))
|
||||
}));
|
||||
|
||||
function resetFilters() {
|
||||
platformOrderId.value = ''
|
||||
payStatus.value = ''
|
||||
skuCode.value = ''
|
||||
dateRange.value = []
|
||||
void applyFilters(1)
|
||||
platformOrderId.value = "";
|
||||
payStatus.value = "";
|
||||
skuCode.value = "";
|
||||
dateRange.value = [];
|
||||
void applyFilters(1);
|
||||
}
|
||||
|
||||
async function applyFilters(page = 1) {
|
||||
await syncRouteQuery(page)
|
||||
await loadOrders(page)
|
||||
}
|
||||
|
||||
function readQueryString(key: string) {
|
||||
const value = route.query[key]
|
||||
const rawValue = Array.isArray(value) ? value[0] : value
|
||||
return typeof rawValue === 'string' ? rawValue.trim() : ''
|
||||
}
|
||||
|
||||
function readQueryPage() {
|
||||
const page = Number(readQueryString('page'))
|
||||
return Number.isInteger(page) && page > 0 ? page : 1
|
||||
}
|
||||
|
||||
function syncRouteQuery(page: number) {
|
||||
const query: Record<string, string> = {}
|
||||
addQueryValue(query, 'platformOrderId', platformOrderId.value)
|
||||
addQueryValue(query, 'payStatus', payStatus.value)
|
||||
addQueryValue(query, 'skuCode', skuCode.value)
|
||||
addQueryValue(query, 'dateFrom', dateRange.value[0] || '')
|
||||
addQueryValue(query, 'dateTo', dateRange.value[1] || '')
|
||||
|
||||
if (page > 1) {
|
||||
query.page = String(page)
|
||||
}
|
||||
|
||||
return router.replace({ query })
|
||||
}
|
||||
|
||||
function addQueryValue(query: Record<string, string>, key: string, value: string) {
|
||||
const trimmed = value.trim()
|
||||
if (trimmed) {
|
||||
query[key] = trimmed
|
||||
}
|
||||
await syncRouteQuery(page, {
|
||||
platformOrderId: platformOrderId.value,
|
||||
payStatus: payStatus.value,
|
||||
skuCode: skuCode.value,
|
||||
dateFrom: dateRange.value[0] || "",
|
||||
dateTo: dateRange.value[1] || "",
|
||||
});
|
||||
await loadOrders(page);
|
||||
}
|
||||
|
||||
function resolveBuyerLabel(item: AdminOrderListItem) {
|
||||
return item.buyerName || item.buyerId || '未识别'
|
||||
return item.buyerName || item.buyerId || "未识别";
|
||||
}
|
||||
|
||||
function resolveShopLabel(item: AdminOrderListItem) {
|
||||
return item.shopName || item.shopId || '未识别店铺'
|
||||
return item.shopName || item.shopId || "未识别店铺";
|
||||
}
|
||||
|
||||
function resolveItemSummary(item: AdminOrderListItem) {
|
||||
return item.itemSummary || '未识别商品'
|
||||
return item.itemSummary || "未识别商品";
|
||||
}
|
||||
|
||||
function resolveBindingTone(completedCount: number, totalCount: number) {
|
||||
if (completedCount <= 0) return 'warning'
|
||||
return completedCount === totalCount ? 'success' : 'primary'
|
||||
if (completedCount <= 0) return "warning";
|
||||
return completedCount === totalCount ? "success" : "primary";
|
||||
}
|
||||
|
||||
function resolveBindingSummary(item: AdminOrderListItem) {
|
||||
return `共 ${item.totalBindingCount} 条 · 预占 ${item.reservedBindingCount} · 已消耗 ${item.consumedBindingCount} · 已释放 ${item.releasedBindingCount}`
|
||||
return `共 ${item.totalBindingCount} 条 · 预占 ${item.reservedBindingCount} · 已消耗 ${item.consumedBindingCount} · 已释放 ${item.releasedBindingCount}`;
|
||||
}
|
||||
|
||||
function hasOrderIssue(item: AdminOrderListItem) {
|
||||
return (
|
||||
['refunded', 'failed', 'unpaid'].includes(item.payStatus) ||
|
||||
item.systemBindingStatus === 'binding_exception' ||
|
||||
item.userBindingStatus === 'binding_exception'
|
||||
)
|
||||
["refunded", "failed", "unpaid"].includes(item.payStatus) ||
|
||||
item.systemBindingStatus === "binding_exception" ||
|
||||
item.userBindingStatus === "binding_exception"
|
||||
);
|
||||
}
|
||||
|
||||
function resolveFollowUpText(item: AdminOrderListItem) {
|
||||
if (
|
||||
item.userBindingStatus === 'binding_exception' ||
|
||||
item.systemBindingStatus === 'binding_exception'
|
||||
item.userBindingStatus === "binding_exception" ||
|
||||
item.systemBindingStatus === "binding_exception"
|
||||
)
|
||||
return '绑定异常'
|
||||
if (item.completedBindingTaskCount <= 0) return '等待完整绑定'
|
||||
if (item.systemBoundTaskCount < item.taskCount) return '系统绑定未完成'
|
||||
return '链路正常'
|
||||
return "绑定异常";
|
||||
if (item.completedBindingTaskCount <= 0) return "等待完整绑定";
|
||||
if (item.systemBoundTaskCount < item.taskCount) return "系统绑定未完成";
|
||||
return "链路正常";
|
||||
}
|
||||
|
||||
function resolveFollowUpTone(item: AdminOrderListItem) {
|
||||
if (hasOrderIssue(item)) return 'danger'
|
||||
if (item.completedBindingTaskCount <= 0 || item.systemBoundTaskCount < item.taskCount)
|
||||
return 'warning'
|
||||
return 'success'
|
||||
if (hasOrderIssue(item)) return "danger";
|
||||
if (
|
||||
item.completedBindingTaskCount <= 0 ||
|
||||
item.systemBoundTaskCount < item.taskCount
|
||||
)
|
||||
return "warning";
|
||||
return "success";
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void applyFilters(readQueryPage())
|
||||
})
|
||||
void applyFilters(readQueryPage());
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -160,7 +142,9 @@ onMounted(() => {
|
||||
<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">
|
||||
@@ -171,7 +155,12 @@ onMounted(() => {
|
||||
clearable
|
||||
@keyup.enter="applyFilters(1)"
|
||||
/>
|
||||
<el-select v-model="payStatus" class="filter-control" placeholder="支付状态" clearable>
|
||||
<el-select
|
||||
v-model="payStatus"
|
||||
class="filter-control"
|
||||
placeholder="支付状态"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in adminPayStatusOptions"
|
||||
:key="opt.value"
|
||||
@@ -197,7 +186,9 @@ onMounted(() => {
|
||||
/>
|
||||
<div class="filter-buttons">
|
||||
<el-button round @click="resetFilters">重置</el-button>
|
||||
<el-button round type="primary" @click="applyFilters(1)">查询</el-button>
|
||||
<el-button round type="primary" @click="applyFilters(1)"
|
||||
>查询</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -213,7 +204,9 @@ onMounted(() => {
|
||||
<span class="summary-metric" data-tone="primary"
|
||||
>完整绑定 <strong>{{ summary.bindingReadyCount }}</strong></span
|
||||
>
|
||||
<span class="summary-metric" :data-tone="summary.issueCount > 0 ? 'warning' : 'muted'"
|
||||
<span
|
||||
class="summary-metric"
|
||||
:data-tone="summary.issueCount > 0 ? 'warning' : 'muted'"
|
||||
>需关注 <strong>{{ summary.issueCount }}</strong></span
|
||||
>
|
||||
</div>
|
||||
@@ -240,10 +233,17 @@ onMounted(() => {
|
||||
<template v-else>
|
||||
<div class="orders-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="orders-table data-table">
|
||||
<el-table
|
||||
:data="items"
|
||||
stripe
|
||||
size="small"
|
||||
class="orders-table data-table"
|
||||
>
|
||||
<el-table-column label="订单信息" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<div class="cell-stack">
|
||||
@@ -255,7 +255,7 @@ onMounted(() => {
|
||||
{{ row.platformOrderId }}
|
||||
</RouterLink>
|
||||
<div class="cell-meta-row">
|
||||
<span class="compact-chip">{{ row.platform || '-' }}</span>
|
||||
<span class="compact-chip">{{ row.platform || "-" }}</span>
|
||||
<span class="compact-chip">{{ resolveShopLabel(row) }}</span>
|
||||
<span class="compact-chip compact-chip--muted"
|
||||
>买家 {{ resolveBuyerLabel(row) }}</span
|
||||
@@ -273,9 +273,11 @@ onMounted(() => {
|
||||
<span class="cell-subline"
|
||||
>{{ row.itemCount }} 项,合计 {{ row.totalQuantity }} 件</span
|
||||
>
|
||||
<span class="cell-subline" :title="resolveBindingSummary(row)">{{
|
||||
resolveBindingSummary(row)
|
||||
}}</span>
|
||||
<span
|
||||
class="cell-subline"
|
||||
:title="resolveBindingSummary(row)"
|
||||
>{{ resolveBindingSummary(row) }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -293,22 +295,40 @@ onMounted(() => {
|
||||
<template #default="{ row }">
|
||||
<div class="cell-stack">
|
||||
<div class="status-stack">
|
||||
<AdminStatusTag :status="row.systemBindingStatus" :show-raw="false" />
|
||||
<AdminStatusTag :status="row.userBindingStatus" :show-raw="false" />
|
||||
<AdminStatusTag
|
||||
:status="row.systemBindingStatus"
|
||||
:show-raw="false"
|
||||
/>
|
||||
<AdminStatusTag
|
||||
:status="row.userBindingStatus"
|
||||
:show-raw="false"
|
||||
/>
|
||||
</div>
|
||||
<span class="follow-up-chip" :data-tone="resolveFollowUpTone(row)">{{
|
||||
resolveFollowUpText(row)
|
||||
}}</span>
|
||||
<span
|
||||
class="follow-up-chip"
|
||||
:data-tone="resolveFollowUpTone(row)"
|
||||
>{{ resolveFollowUpText(row) }}</span
|
||||
>
|
||||
<div class="progress-row-stack">
|
||||
<span
|
||||
class="progress-chip"
|
||||
:data-tone="resolveBindingTone(row.completedBindingTaskCount, row.taskCount)"
|
||||
:data-tone="
|
||||
resolveBindingTone(
|
||||
row.completedBindingTaskCount,
|
||||
row.taskCount
|
||||
)
|
||||
"
|
||||
>
|
||||
完整 {{ row.completedBindingTaskCount }}/{{ row.taskCount }}
|
||||
</span>
|
||||
<span
|
||||
class="progress-chip"
|
||||
:data-tone="resolveBindingTone(row.systemBoundTaskCount, row.taskCount)"
|
||||
:data-tone="
|
||||
resolveBindingTone(
|
||||
row.systemBoundTaskCount,
|
||||
row.taskCount
|
||||
)
|
||||
"
|
||||
>
|
||||
系统 {{ row.systemBoundTaskCount }}/{{ row.taskCount }}
|
||||
</span>
|
||||
@@ -319,15 +339,25 @@ onMounted(() => {
|
||||
<el-table-column label="金额 / 时间" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<div class="cell-stack">
|
||||
<span class="cell-title amount-text">{{ row.totalAmount }} {{ row.currency }}</span>
|
||||
<span class="cell-subline">支付 {{ formatAdminDateTime(row.paidAt) }}</span>
|
||||
<span class="cell-subline">更新 {{ formatAdminDateTime(row.updatedAt) }}</span>
|
||||
<span class="cell-title amount-text"
|
||||
>{{ row.totalAmount }} {{ row.currency }}</span
|
||||
>
|
||||
<span class="cell-subline"
|
||||
>支付 {{ formatAdminDateTime(row.paidAt) }}</span
|
||||
>
|
||||
<span class="cell-subline"
|
||||
>更新 {{ formatAdminDateTime(row.updatedAt) }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<RouterLink :to="`/admin/orders/${row.orderId}`" class="detail-link">详情</RouterLink>
|
||||
<RouterLink
|
||||
:to="`/admin/orders/${row.orderId}`"
|
||||
class="detail-link"
|
||||
>详情</RouterLink
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -347,7 +377,7 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@import '../../../styles/admin-list-pages.css';
|
||||
@import "../../../styles/admin-list-pages.css";
|
||||
|
||||
.filter-grid {
|
||||
grid-template-columns:
|
||||
@@ -378,32 +408,32 @@ onMounted(() => {
|
||||
border: 1px solid transparent;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.progress-chip[data-tone='success'] {
|
||||
.progress-chip[data-tone="success"] {
|
||||
color: #0f766e;
|
||||
background: var(--color-success-light);
|
||||
border-color: #a6f4c5;
|
||||
}
|
||||
.progress-chip[data-tone='primary'] {
|
||||
.progress-chip[data-tone="primary"] {
|
||||
color: var(--color-primary-dark);
|
||||
background: var(--color-primary-light);
|
||||
border-color: #b2ddff;
|
||||
}
|
||||
.progress-chip[data-tone='warning'] {
|
||||
.progress-chip[data-tone="warning"] {
|
||||
color: #b54708;
|
||||
background: var(--color-warning-light);
|
||||
border-color: #fedf89;
|
||||
}
|
||||
.follow-up-chip[data-tone='success'] {
|
||||
.follow-up-chip[data-tone="success"] {
|
||||
color: #0f766e;
|
||||
background: var(--color-success-light);
|
||||
border-color: #a6f4c5;
|
||||
}
|
||||
.follow-up-chip[data-tone='warning'] {
|
||||
.follow-up-chip[data-tone="warning"] {
|
||||
color: #b54708;
|
||||
background: var(--color-warning-light);
|
||||
border-color: #fedf89;
|
||||
}
|
||||
.follow-up-chip[data-tone='danger'] {
|
||||
.follow-up-chip[data-tone="danger"] {
|
||||
color: var(--color-danger-dark);
|
||||
background: var(--color-danger-light);
|
||||
border-color: #fecaca;
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
import type { AdminTaskActionResponse, AdminTaskListItem } from "@/types/admin";
|
||||
import { hasAdminRole } from "@/utils/admin-auth";
|
||||
import {
|
||||
formatBindingCountSummary,
|
||||
formatBindingRoleSummary,
|
||||
formatBindingCountSummary,
|
||||
} from "@/utils/admin-display";
|
||||
import { adminTaskStatusOptions } from "@/utils/admin-options";
|
||||
import { formatAdminDateTime } from "@/utils/admin-time";
|
||||
@@ -63,11 +63,6 @@ const {
|
||||
|
||||
const { actionLoadingId, runAction } = useAdminAction();
|
||||
|
||||
import {
|
||||
formatBindingCountSummary,
|
||||
formatBindingRoleSummary,
|
||||
} from "@/utils/admin-display";
|
||||
|
||||
function handleActionCommand(
|
||||
actionKey: AdminTaskActionKey,
|
||||
item: AdminTaskListItem
|
||||
@@ -312,13 +307,19 @@ function resetFilters() {
|
||||
:title="row.inventoryDisplayMasked || '-'"
|
||||
>凭据 {{ row.inventoryDisplayMasked || "-" }}</span
|
||||
>
|
||||
<span class="cell-subline" :title="formatBindingRoles(row)"
|
||||
>绑定 {{ formatBindingRoles(row) }}</span
|
||||
<span
|
||||
class="cell-subline"
|
||||
:title="formatBindingRoleSummary(row.bindingSummary.roleKeys)"
|
||||
>绑定
|
||||
{{
|
||||
formatBindingRoleSummary(row.bindingSummary.roleKeys)
|
||||
}}</span
|
||||
>
|
||||
<span
|
||||
class="cell-subline cell-subline--strong"
|
||||
:title="formatBindingCountSummary(row)"
|
||||
>{{ formatBindingCountSummary(row) }}</span
|
||||
:title="formatBindingCountSummary(row.bindingSummary)"
|
||||
>{{ formatBindingCountSummary(row.bindingSummary) }}</span
|
||||
>
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,32 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
import { useAdminListPage } from '@/composables/useAdminListPage'
|
||||
import AdminPageHeader from '@/components/admin/AdminPageHeader.vue'
|
||||
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
|
||||
import { showConfirm, showError, showSuccess } from '@/lib/feedback'
|
||||
import { fetchAdminWebhookEvents, replayAdminWebhookEvent } from '@/services/admin'
|
||||
import type { AdminWebhookEventListItem } from '@/types/admin'
|
||||
import { hasAdminRole } from '@/utils/admin-auth'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
import { formatAdminWebhookEventType } from '@/utils/admin-webhook'
|
||||
import { adminWebhookProcessedOptions, adminWebhookVisibilityOptions } from '@/utils/admin-options'
|
||||
import { useAdminListPage } from "@/composables/useAdminListPage";
|
||||
import { useAdminListQuerySync } from "@/composables/useAdminListQuerySync";
|
||||
import AdminPageHeader from "@/components/admin/AdminPageHeader.vue";
|
||||
import AdminStatusTag from "@/components/admin/AdminStatusTag.vue";
|
||||
import { showConfirm, showError, showSuccess } from "@/lib/feedback";
|
||||
import {
|
||||
fetchAdminWebhookEvents,
|
||||
replayAdminWebhookEvent,
|
||||
} from "@/services/admin";
|
||||
import type { AdminWebhookEventListItem } from "@/types/admin";
|
||||
import { hasAdminRole } from "@/utils/admin-auth";
|
||||
import { formatAdminDateTime } from "@/utils/admin-time";
|
||||
import { formatAdminWebhookEventType } from "@/utils/admin-webhook";
|
||||
import {
|
||||
adminWebhookProcessedOptions,
|
||||
adminWebhookVisibilityOptions,
|
||||
} from "@/utils/admin-options";
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { readQueryString, readQueryPage, syncRouteQuery } =
|
||||
useAdminListQuerySync(route, router);
|
||||
|
||||
const actionLoadingId = ref<number | null>(null)
|
||||
const provider = ref(readQueryString('provider'))
|
||||
const platform = ref(readQueryString('platform'))
|
||||
const platformOrderId = ref(readQueryString('platformOrderId'))
|
||||
const processed = ref(readQueryString('processed'))
|
||||
const visibility = ref(readQueryString('visibility') || 'important')
|
||||
const relatedOrderId = ref(readQueryString('relatedOrderId'))
|
||||
const actionLoadingId = ref<number | null>(null);
|
||||
const provider = ref(readQueryString("provider"));
|
||||
const platform = ref(readQueryString("platform"));
|
||||
const platformOrderId = ref(readQueryString("platformOrderId"));
|
||||
const processed = ref(readQueryString("processed"));
|
||||
const visibility = ref(readQueryString("visibility") || "important");
|
||||
const relatedOrderId = ref(readQueryString("relatedOrderId"));
|
||||
const dateRange = ref<string[]>(
|
||||
[readQueryString('dateFrom'), readQueryString('dateTo')].filter(Boolean),
|
||||
)
|
||||
const canReplay = hasAdminRole('admin')
|
||||
[readQueryString("dateFrom"), readQueryString("dateTo")].filter(Boolean)
|
||||
);
|
||||
const canReplay = hasAdminRole("admin");
|
||||
|
||||
const {
|
||||
loading,
|
||||
@@ -35,7 +44,7 @@ const {
|
||||
pagination,
|
||||
loadPage: loadEvents,
|
||||
} = useAdminListPage<AdminWebhookEventListItem>({
|
||||
defaultErrorMessage: '读取 webhook 列表失败',
|
||||
defaultErrorMessage: "读取 webhook 列表失败",
|
||||
fetchPage: (page, pageSize) =>
|
||||
fetchAdminWebhookEvents({
|
||||
page,
|
||||
@@ -46,99 +55,76 @@ const {
|
||||
processed: processed.value.trim(),
|
||||
visibility: visibility.value.trim(),
|
||||
relatedOrderId: relatedOrderId.value.trim(),
|
||||
dateFrom: dateRange.value[0] || '',
|
||||
dateTo: dateRange.value[1] || '',
|
||||
dateFrom: dateRange.value[0] || "",
|
||||
dateTo: dateRange.value[1] || "",
|
||||
}),
|
||||
})
|
||||
});
|
||||
|
||||
const summary = computed(() => ({
|
||||
processedCount: items.value.filter((item) => item.processed).length,
|
||||
failedCount: items.value.filter((item) => !item.processed || Boolean(item.processError)).length,
|
||||
importantCount: items.value.filter((item) => item.visibilityLevel === 'important').length,
|
||||
}))
|
||||
failedCount: items.value.filter(
|
||||
(item) => !item.processed || Boolean(item.processError)
|
||||
).length,
|
||||
importantCount: items.value.filter(
|
||||
(item) => item.visibilityLevel === "important"
|
||||
).length,
|
||||
}));
|
||||
|
||||
async function submitReplay(item: AdminWebhookEventListItem) {
|
||||
try {
|
||||
await showConfirm(`确认重放 webhook #${item.eventId} 吗?`, '确认重放', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '继续重放',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
await showConfirm(`确认重放 webhook #${item.eventId} 吗?`, "确认重放", {
|
||||
type: "warning",
|
||||
confirmButtonText: "继续重放",
|
||||
cancelButtonText: "取消",
|
||||
});
|
||||
} catch {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
actionLoadingId.value = item.eventId
|
||||
actionLoadingId.value = item.eventId;
|
||||
try {
|
||||
await replayAdminWebhookEvent(item.eventId)
|
||||
showSuccess('Webhook 已重放')
|
||||
await loadEvents()
|
||||
await replayAdminWebhookEvent(item.eventId);
|
||||
showSuccess("Webhook 已重放");
|
||||
await loadEvents();
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : '重放 webhook 失败')
|
||||
showError(error instanceof Error ? error.message : "重放 webhook 失败");
|
||||
} finally {
|
||||
actionLoadingId.value = null
|
||||
actionLoadingId.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
provider.value = ''
|
||||
platform.value = ''
|
||||
platformOrderId.value = ''
|
||||
processed.value = ''
|
||||
visibility.value = 'important'
|
||||
relatedOrderId.value = ''
|
||||
dateRange.value = []
|
||||
void applyFilters(1)
|
||||
provider.value = "";
|
||||
platform.value = "";
|
||||
platformOrderId.value = "";
|
||||
processed.value = "";
|
||||
visibility.value = "important";
|
||||
relatedOrderId.value = "";
|
||||
dateRange.value = [];
|
||||
void applyFilters(1);
|
||||
}
|
||||
|
||||
async function applyFilters(page = 1) {
|
||||
await syncRouteQuery(page)
|
||||
await loadEvents(page)
|
||||
}
|
||||
|
||||
function readQueryString(key: string) {
|
||||
const value = route.query[key]
|
||||
const rawValue = Array.isArray(value) ? value[0] : value
|
||||
return typeof rawValue === 'string' ? rawValue.trim() : ''
|
||||
}
|
||||
|
||||
function readQueryPage() {
|
||||
const page = Number(readQueryString('page'))
|
||||
return Number.isInteger(page) && page > 0 ? page : 1
|
||||
}
|
||||
|
||||
function syncRouteQuery(page: number) {
|
||||
const query: Record<string, string> = {}
|
||||
addQueryValue(query, 'provider', provider.value)
|
||||
addQueryValue(query, 'platform', platform.value)
|
||||
addQueryValue(query, 'platformOrderId', platformOrderId.value)
|
||||
addQueryValue(query, 'processed', processed.value)
|
||||
addQueryValue(query, 'visibility', visibility.value)
|
||||
addQueryValue(query, 'relatedOrderId', relatedOrderId.value)
|
||||
addQueryValue(query, 'dateFrom', dateRange.value[0] || '')
|
||||
addQueryValue(query, 'dateTo', dateRange.value[1] || '')
|
||||
|
||||
if (page > 1) {
|
||||
query.page = String(page)
|
||||
}
|
||||
|
||||
return router.replace({ query })
|
||||
}
|
||||
|
||||
function addQueryValue(query: Record<string, string>, key: string, value: string) {
|
||||
const trimmed = value.trim()
|
||||
if (trimmed) {
|
||||
query[key] = trimmed
|
||||
}
|
||||
await syncRouteQuery(page, {
|
||||
provider: provider.value,
|
||||
platform: platform.value,
|
||||
platformOrderId: platformOrderId.value,
|
||||
processed: processed.value,
|
||||
visibility: visibility.value,
|
||||
relatedOrderId: relatedOrderId.value,
|
||||
dateFrom: dateRange.value[0] || "",
|
||||
dateTo: dateRange.value[1] || "",
|
||||
});
|
||||
await loadEvents(page);
|
||||
}
|
||||
|
||||
function formatVisibilityLabel(value: string) {
|
||||
return value === 'ignored' ? '已忽略' : '重要'
|
||||
return value === "ignored" ? "已忽略" : "重要";
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void applyFilters(readQueryPage())
|
||||
})
|
||||
void applyFilters(readQueryPage());
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -157,7 +143,9 @@ onMounted(() => {
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">筛选 Webhook</span>
|
||||
<span class="card-desc">按服务商、平台、处理状态、可见级别等条件筛选。</span>
|
||||
<span class="card-desc"
|
||||
>按服务商、平台、处理状态、可见级别等条件筛选。</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<div class="filter-grid">
|
||||
@@ -175,7 +163,12 @@ onMounted(() => {
|
||||
clearable
|
||||
@keyup.enter="applyFilters(1)"
|
||||
/>
|
||||
<el-select v-model="processed" class="filter-control" placeholder="处理结果" clearable>
|
||||
<el-select
|
||||
v-model="processed"
|
||||
class="filter-control"
|
||||
placeholder="处理结果"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in adminWebhookProcessedOptions"
|
||||
:key="opt.value"
|
||||
@@ -183,7 +176,12 @@ onMounted(() => {
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select v-model="visibility" class="filter-control" placeholder="可见级别" clearable>
|
||||
<el-select
|
||||
v-model="visibility"
|
||||
class="filter-control"
|
||||
placeholder="可见级别"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in adminWebhookVisibilityOptions"
|
||||
:key="opt.value"
|
||||
@@ -216,7 +214,9 @@ onMounted(() => {
|
||||
/>
|
||||
<div class="filter-buttons">
|
||||
<el-button round @click="resetFilters">重置</el-button>
|
||||
<el-button round type="primary" @click="applyFilters(1)">查询</el-button>
|
||||
<el-button round type="primary" @click="applyFilters(1)"
|
||||
>查询</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -252,19 +252,32 @@ onMounted(() => {
|
||||
element-loading-text="Webhook 列表加载中"
|
||||
>
|
||||
<template v-if="items.length === 0 && !loading">
|
||||
<el-empty description="当前筛选条件下暂无 Webhook 事件" :image-size="60" />
|
||||
<el-empty
|
||||
description="当前筛选条件下暂无 Webhook 事件"
|
||||
:image-size="60"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="orders-toolbar table-toolbar">
|
||||
<strong>Webhook 列表</strong>
|
||||
<span>第 {{ pagination.page }} 页,当前展示 {{ items.length }} 条</span>
|
||||
<span
|
||||
>第 {{ pagination.page }} 页,当前展示 {{ items.length }} 条</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<el-table :data="items" stripe size="small" class="orders-table data-table">
|
||||
<el-table
|
||||
:data="items"
|
||||
stripe
|
||||
size="small"
|
||||
class="orders-table data-table"
|
||||
>
|
||||
<el-table-column label="事件ID" width="100">
|
||||
<template #default="{ row }">
|
||||
<RouterLink :to="`/admin/webhook-events/${row.eventId}`" class="order-no id-link">
|
||||
<RouterLink
|
||||
:to="`/admin/webhook-events/${row.eventId}`"
|
||||
class="order-no id-link"
|
||||
>
|
||||
#{{ row.eventId }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
@@ -272,22 +285,26 @@ onMounted(() => {
|
||||
<el-table-column label="事件类型" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div class="cell-stack">
|
||||
<span class="cell-title">{{ formatAdminWebhookEventType(row.eventType) }}</span>
|
||||
<span class="cell-title">{{
|
||||
formatAdminWebhookEventType(row.eventType)
|
||||
}}</span>
|
||||
<div class="cell-meta-row">
|
||||
<span class="compact-chip">{{ row.provider || '-' }}</span>
|
||||
<span class="compact-chip">{{ row.platform || '-' }}</span>
|
||||
<span class="compact-chip">{{ row.provider || "-" }}</span>
|
||||
<span class="compact-chip">{{ row.platform || "-" }}</span>
|
||||
<span class="compact-chip compact-chip--muted">{{
|
||||
row.shopName || row.shopId || '-'
|
||||
row.shopName || row.shopId || "-"
|
||||
}}</span>
|
||||
</div>
|
||||
<span v-if="row.aopic" class="cell-subline">aopic {{ row.aopic }}</span>
|
||||
<span v-if="row.aopic" class="cell-subline"
|
||||
>aopic {{ row.aopic }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="平台订单号" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<div class="cell-stack">
|
||||
<span class="cell-title">{{ row.platformOrderId || '-' }}</span>
|
||||
<span class="cell-title">{{ row.platformOrderId || "-" }}</span>
|
||||
<span v-if="row.totalAmount" class="cell-subline"
|
||||
>{{ row.totalAmount }} {{ row.currency }}</span
|
||||
>
|
||||
@@ -308,18 +325,28 @@ onMounted(() => {
|
||||
<template #default="{ row }">
|
||||
<div class="cell-stack">
|
||||
<div class="status-stack">
|
||||
<AdminStatusTag :status="row.processed ? 'success' : 'failed'" />
|
||||
<AdminStatusTag :status="row.signatureValid ? 'success' : 'failed'" />
|
||||
<AdminStatusTag
|
||||
:status="row.processed ? 'success' : 'failed'"
|
||||
/>
|
||||
<AdminStatusTag
|
||||
:status="row.signatureValid ? 'success' : 'failed'"
|
||||
/>
|
||||
</div>
|
||||
<span class="cell-subline">{{ formatVisibilityLabel(row.visibilityLevel) }}</span>
|
||||
<span v-if="row.processError" class="error-inline">{{ row.processError }}</span>
|
||||
<span class="cell-subline">{{
|
||||
formatVisibilityLabel(row.visibilityLevel)
|
||||
}}</span>
|
||||
<span v-if="row.processError" class="error-inline">{{
|
||||
row.processError
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<div class="cell-stack">
|
||||
<span class="cell-title">{{ formatAdminDateTime(row.createdAt) }}</span>
|
||||
<span class="cell-title">{{
|
||||
formatAdminDateTime(row.createdAt)
|
||||
}}</span>
|
||||
<span class="cell-subline">#{{ row.eventId }}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -327,7 +354,9 @@ onMounted(() => {
|
||||
<el-table-column label="操作" width="130" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="action-stack">
|
||||
<RouterLink :to="`/admin/webhook-events/${row.eventId}`" class="detail-link"
|
||||
<RouterLink
|
||||
:to="`/admin/webhook-events/${row.eventId}`"
|
||||
class="detail-link"
|
||||
>详情</RouterLink
|
||||
>
|
||||
<el-button
|
||||
@@ -357,7 +386,7 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@import '../../../styles/admin-list-pages.css';
|
||||
@import "../../../styles/admin-list-pages.css";
|
||||
|
||||
.filter-grid {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
|
||||
Reference in New Issue
Block a user