From bbd0991e5283b234dbc1a3c1e0fdb754581749ab Mon Sep 17 00:00:00 2001 From: yml Date: Tue, 19 May 2026 19:34:40 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E6=8F=90=E5=8F=96us?= =?UTF-8?q?eAdminAction/useAdminListQuerySync=20+=20=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E8=A7=86=E5=9B=BE=E5=88=B0useAdminListPage?= =?UTF-8?q?=20+=20=E4=BF=AE=E5=A4=8DformatAutoDeliveryReason=E7=BC=BA?= =?UTF-8?q?=E5=A4=B1case=20+=20=E6=B6=88=E9=99=A4=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E5=87=BD=E6=95=B0=20+=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8DCSS=E7=B1=BB=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/styles/admin-config-pages.css | 8 +- .../src/views/admin/tasks/AdminTasksView.vue | 13 +- .../composables/useKuaishouCloudClaim.ts | 330 +++++++++--------- 3 files changed, 183 insertions(+), 168 deletions(-) diff --git a/apps/frontend/src/styles/admin-config-pages.css b/apps/frontend/src/styles/admin-config-pages.css index 6b2bc7f0..a582ab35 100644 --- a/apps/frontend/src/styles/admin-config-pages.css +++ b/apps/frontend/src/styles/admin-config-pages.css @@ -147,12 +147,12 @@ } /* ---------- 数据表 (用于 el-table,具体单元格样式见各页面 :deep() 规则) ---------- */ -.data-table { +.data-table--config { width: 100%; margin-top: 12px; } -.cell-stack { +.cell-stack--config { display: grid; gap: 4px; } @@ -233,4 +233,6 @@ margin: 10px 0 0; font-size: var(--text-xs); } -.mt-4 { margin-top: 16px; } +.mt-4 { + margin-top: 16px; +} diff --git a/apps/frontend/src/views/admin/tasks/AdminTasksView.vue b/apps/frontend/src/views/admin/tasks/AdminTasksView.vue index 93df3b12..e4de8c53 100644 --- a/apps/frontend/src/views/admin/tasks/AdminTasksView.vue +++ b/apps/frontend/src/views/admin/tasks/AdminTasksView.vue @@ -309,10 +309,18 @@ function resetFilters() { > 绑定 {{ - formatBindingRoleSummary(row.bindingSummary.roleKeys) + formatBindingRoleSummary( + row.bindingSummary.roleKeys, + "无绑定角色" + ) }} {{ formatBindingCountSummary(row.bindingSummary) }} - > diff --git a/apps/frontend/src/views/claim/kuaishou-cloud/composables/useKuaishouCloudClaim.ts b/apps/frontend/src/views/claim/kuaishou-cloud/composables/useKuaishouCloudClaim.ts index 869abf69..90decbe7 100644 --- a/apps/frontend/src/views/claim/kuaishou-cloud/composables/useKuaishouCloudClaim.ts +++ b/apps/frontend/src/views/claim/kuaishou-cloud/composables/useKuaishouCloudClaim.ts @@ -1,325 +1,331 @@ -import { computed, onBeforeUnmount, onMounted, ref } from 'vue' -import QRCode from 'qrcode' -import { ElMessageBox } from 'element-plus' +import { computed, onBeforeUnmount, onMounted, ref } from "vue"; +import QRCode from "qrcode"; +import { ElMessageBox } from "element-plus"; -import { showError, showSuccess } from '@/lib/feedback' +import { showError, showSuccess } from "@/lib/feedback"; +import { formatAdminDateTime } from "@/utils/admin-time"; import { confirmKuaishouCloudClaimRole, fetchClaimDetail, redeemKuaishouCloudClaim, verifyKuaishouCloudClaimTicket, -} from '@/services/claim' -import type { ClaimDetailData } from '@/types/claim' +} from "@/services/claim"; +import type { ClaimDetailData } from "@/types/claim"; export function useKuaishouCloudClaim(token: () => string) { - const loading = ref(true) - const submitting = ref(false) - const refreshingRole = ref(false) - const confirmingRole = ref(false) - const redeeming = ref(false) - const errorMessage = ref('') - const detail = ref(null) - const ticketCode = ref('') - const qrCodeDataUrl = ref('') - let pollTimer = 0 + const loading = ref(true); + const submitting = ref(false); + const refreshingRole = ref(false); + const confirmingRole = ref(false); + const redeeming = ref(false); + const errorMessage = ref(""); + const detail = ref(null); + const ticketCode = ref(""); + const qrCodeDataUrl = ref(""); + let pollTimer = 0; // ── derived data ──────────────────────────────────────── - const flow = computed(() => detail.value?.kuaishouCloudFulfillment || null) - const order = computed(() => detail.value?.order || null) - const orderItem = computed(() => detail.value?.orderItem || null) - const task = computed(() => detail.value?.task || null) + const flow = computed(() => detail.value?.kuaishouCloudFulfillment || null); + const order = computed(() => detail.value?.order || null); + const orderItem = computed(() => detail.value?.orderItem || null); + const task = computed(() => detail.value?.task || null); // ── computed status flags ─────────────────────────────── - const roleName = computed(() => flow.value?.role.name || flow.value?.binding.roleName || '') - const roleId = computed(() => flow.value?.role.rid || flow.value?.binding.roleId || '') - const isTicketVerified = computed(() => flow.value?.ticket.status === 'verified') + const roleName = computed( + () => flow.value?.role.name || flow.value?.binding.roleName || "" + ); + const roleId = computed( + () => flow.value?.role.rid || flow.value?.binding.roleId || "" + ); + const isTicketVerified = computed( + () => flow.value?.ticket.status === "verified" + ); const isBindUrlExpired = computed(() => { - const expiresAt = String(flow.value?.binding.bindExpiresAt || '').trim() + const expiresAt = String(flow.value?.binding.bindExpiresAt || "").trim(); if (!expiresAt) { - return false + return false; } - const expiresTime = Date.parse(expiresAt) - return Number.isFinite(expiresTime) && expiresTime <= Date.now() - }) + const expiresTime = Date.parse(expiresAt); + return Number.isFinite(expiresTime) && expiresTime <= Date.now(); + }); const isBindingPrepared = computed( () => - flow.value?.binding.prepareStatus === 'ready' && - Boolean(String(flow.value?.binding.bindUrl || '').trim()) && - !isBindUrlExpired.value, - ) + flow.value?.binding.prepareStatus === "ready" && + Boolean(String(flow.value?.binding.bindUrl || "").trim()) && + !isBindUrlExpired.value + ); - const isBindingPreparing = computed(() => flow.value?.binding.prepareStatus === 'pending') - const canEnterBindingStep = computed(() => isTicketVerified.value) - const isRoleReady = computed(() => Boolean(roleName.value || roleId.value)) - const isRoleConfirmed = computed(() => String(task.value?.status || '').trim() === 'role_confirmed') - const isDispatched = computed(() => String(flow.value?.dispatch.status || '').trim() === 'success') + const isBindingPreparing = computed( + () => flow.value?.binding.prepareStatus === "pending" + ); + const canEnterBindingStep = computed(() => isTicketVerified.value); + const isRoleReady = computed(() => Boolean(roleName.value || roleId.value)); + const isRoleConfirmed = computed( + () => String(task.value?.status || "").trim() === "role_confirmed" + ); + const isDispatched = computed( + () => String(flow.value?.dispatch.status || "").trim() === "success" + ); const isCompleted = computed( () => - String(task.value?.status || '').trim() === 'completed' || - String(flow.value?.consume.status || '').trim() === 'success', - ) + String(task.value?.status || "").trim() === "completed" || + String(flow.value?.consume.status || "").trim() === "success" + ); const hasRedeemResult = computed(() => { - const status = String(task.value?.status || '').trim() + const status = String(task.value?.status || "").trim(); return ( isDispatched.value || - ['dispatched_pending_return', 'completed', 'manual_review', 'failed'].includes(status) - ) - }) + [ + "dispatched_pending_return", + "completed", + "manual_review", + "failed", + ].includes(status) + ); + }); const canSubmitTicket = computed( () => - !['completed', 'manual_review', 'closed', 'expired'].includes( - String(task.value?.status || '').trim(), - ) && !submitting.value, - ) + !["completed", "manual_review", "closed", "expired"].includes( + String(task.value?.status || "").trim() + ) && !submitting.value + ); const currentStep = computed(() => { if (hasRedeemResult.value) { - return 4 + return 4; } if (isRoleConfirmed.value) { - return 3 + return 3; } if (canEnterBindingStep.value) { - return 2 + return 2; } - return 1 - }) + return 1; + }); const progressText = computed(() => { if (hasRedeemResult.value) { - return '兑换结果已生成' + return "兑换结果已生成"; } if (isRoleConfirmed.value) { - return '角色已确认,等待兑换' + return "角色已确认,等待兑换"; } if (isTicketVerified.value) { - return isBindingPrepared.value ? '请完成扫码绑定' : '绑定链接刷新中,请稍候' + return isBindingPrepared.value + ? "请完成扫码绑定" + : "绑定链接刷新中,请稍候"; } - return '等待提交核销码' - }) + return "等待提交核销码"; + }); const resultTitle = computed(() => { if (isCompleted.value) { - return '兑换成功' + return "兑换成功"; } if (isDispatched.value) { - return '兑换请求已提交' + return "兑换请求已提交"; } - return '结果已记录' - }) + return "结果已记录"; + }); const resultDescription = computed(() => { if (isCompleted.value) { - return '当前兑换流程已经完成。发货、退号和核销结果会保存在后台任务界面。' + return "当前兑换流程已经完成。发货、退号和核销结果会保存在后台任务界面。"; } - return '你的兑换请求已经提交。发货、退号和核销结果会由系统保存在后台任务界面,无需在此页面等待。' - }) + return "你的兑换请求已经提交。发货、退号和核销结果会由系统保存在后台任务界面,无需在此页面等待。"; + }); // ── actions ───────────────────────────────────────────── async function generateQRCode(url: string) { if (!url) { - qrCodeDataUrl.value = '' - return + qrCodeDataUrl.value = ""; + return; } try { qrCodeDataUrl.value = await QRCode.toDataURL(url, { width: 280, margin: 2, color: { - dark: '#0f172a', - light: '#ffffff', + dark: "#0f172a", + light: "#ffffff", }, - }) + }); } catch (error) { - qrCodeDataUrl.value = '' - console.error('生成二维码失败:', error) + qrCodeDataUrl.value = ""; + console.error("生成二维码失败:", error); } } async function applyDetail(nextDetail: ClaimDetailData) { - detail.value = nextDetail + detail.value = nextDetail; if (!ticketCode.value && nextDetail.kuaishouCloudFulfillment?.ticket.code) { - ticketCode.value = nextDetail.kuaishouCloudFulfillment.ticket.code + ticketCode.value = nextDetail.kuaishouCloudFulfillment.ticket.code; } - await generateQRCode(String(nextDetail.kuaishouCloudFulfillment?.binding.bindUrl || '').trim()) + await generateQRCode( + String(nextDetail.kuaishouCloudFulfillment?.binding.bindUrl || "").trim() + ); } async function loadDetail(options: { silent?: boolean } = {}) { if (!options.silent) { - loading.value = true + loading.value = true; } - errorMessage.value = '' + errorMessage.value = ""; try { - const response = await fetchClaimDetail(token()) - await applyDetail(response.data) - syncPolling() + const response = await fetchClaimDetail(token()); + await applyDetail(response.data); + syncPolling(); } catch (error) { - errorMessage.value = error instanceof Error ? error.message : '读取领取信息失败' - stopPolling() + errorMessage.value = + error instanceof Error ? error.message : "读取领取信息失败"; + stopPolling(); } finally { if (!options.silent) { - loading.value = false + loading.value = false; } } } async function submitTicket() { - const normalizedTicketCode = ticketCode.value.trim() + const normalizedTicketCode = ticketCode.value.trim(); if (!normalizedTicketCode) { - showError('请输入核销码') - return + showError("请输入核销码"); + return; } - submitting.value = true + submitting.value = true; try { const response = await verifyKuaishouCloudClaimTicket(token(), { ticketCode: normalizedTicketCode, - }) - await applyDetail(response.data) - showSuccess('核销码验证通过,系统已开始准备绑定资源') - syncPolling() + }); + await applyDetail(response.data); + showSuccess("核销码验证通过,系统已开始准备绑定资源"); + syncPolling(); } catch (error) { - showError(error instanceof Error ? error.message : '核销码验证失败') + showError(error instanceof Error ? error.message : "核销码验证失败"); } finally { - submitting.value = false + submitting.value = false; } } async function refreshRole() { - refreshingRole.value = true + refreshingRole.value = true; try { - await loadDetail({ silent: true }) + await loadDetail({ silent: true }); if (isRoleReady.value) { - showSuccess('角色信息已刷新') + showSuccess("角色信息已刷新"); } else { showError( - flow.value?.role.errorMessage || '暂时还没有识别到角色信息,请完成绑定后稍等片刻再试', - ) + flow.value?.role.errorMessage || + "暂时还没有识别到角色信息,请完成绑定后稍等片刻再试" + ); } } finally { - refreshingRole.value = false + refreshingRole.value = false; } } async function confirmRole() { - confirmingRole.value = true + confirmingRole.value = true; try { - const response = await confirmKuaishouCloudClaimRole(token()) - await applyDetail(response.data) - showSuccess('角色已确认,进入下一步') - syncPolling() + const response = await confirmKuaishouCloudClaimRole(token()); + await applyDetail(response.data); + showSuccess("角色已确认,进入下一步"); + syncPolling(); } catch (error) { - showError(error instanceof Error ? error.message : '确认角色失败') + showError(error instanceof Error ? error.message : "确认角色失败"); } finally { - confirmingRole.value = false + confirmingRole.value = false; } } async function confirmRedeem() { try { await ElMessageBox.confirm( - '兑换后不可取消,也不可退货。请确认角色和商品信息完全正确。', - '确认兑换', + "兑换后不可取消,也不可退货。请确认角色和商品信息完全正确。", + "确认兑换", { - confirmButtonText: '确认兑换', - cancelButtonText: '取消', - type: 'warning', + confirmButtonText: "确认兑换", + cancelButtonText: "取消", + type: "warning", center: true, - }, - ) + } + ); } catch { - return + return; } - redeeming.value = true + redeeming.value = true; try { - const response = await redeemKuaishouCloudClaim(token()) - await applyDetail(response.data) - showSuccess('兑换请求已提交') - syncPolling() + const response = await redeemKuaishouCloudClaim(token()); + await applyDetail(response.data); + showSuccess("兑换请求已提交"); + syncPolling(); } catch (error) { - showError(error instanceof Error ? error.message : '兑换失败') + showError(error instanceof Error ? error.message : "兑换失败"); } finally { - redeeming.value = false + redeeming.value = false; } } function openBindUrl(useCurrentPage = false) { - const bindUrl = String(flow.value?.binding.bindUrl || '').trim() + const bindUrl = String(flow.value?.binding.bindUrl || "").trim(); if (!bindUrl) { - showError('绑定链接还没准备好,请稍后刷新') - return + showError("绑定链接还没准备好,请稍后刷新"); + return; } if (useCurrentPage) { - window.location.assign(bindUrl) - return + window.location.assign(bindUrl); + return; } - window.open(bindUrl, '_blank', 'noopener,noreferrer') + window.open(bindUrl, "_blank", "noopener,noreferrer"); } // ── polling ───────────────────────────────────────────── function stopPolling() { if (pollTimer) { - window.clearInterval(pollTimer) - pollTimer = 0 + window.clearInterval(pollTimer); + pollTimer = 0; } } function syncPolling() { - const taskStatus = String(task.value?.status || '').trim() + const taskStatus = String(task.value?.status || "").trim(); const shouldPoll = - Boolean(flow.value) && !hasRedeemResult.value && !['closed', 'expired'].includes(taskStatus) + Boolean(flow.value) && + !hasRedeemResult.value && + !["closed", "expired"].includes(taskStatus); if (!shouldPoll) { - stopPolling() - return + stopPolling(); + return; } if (pollTimer) { - return + return; } pollTimer = window.setInterval(() => { - void loadDetail({ silent: true }) - }, 4000) - } - - // ── utility ───────────────────────────────────────────── - - function formatDateTime(value: string | null) { - if (!value) { - return '-' - } - try { - return new Date(value).toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - }) - } catch { - return value - } + void loadDetail({ silent: true }); + }, 4000); } // ── lifecycle ─────────────────────────────────────────── onMounted(() => { - void loadDetail() - }) + void loadDetail(); + }); onBeforeUnmount(() => { - stopPolling() - }) + stopPolling(); + }); return { // state @@ -363,6 +369,6 @@ export function useKuaishouCloudClaim(token: () => string) { confirmRedeem, openBindUrl, // utility - formatDateTime, - } + formatAdminDateTime, + }; }