diff --git a/apps/frontend/src/components.d.ts b/apps/frontend/src/components.d.ts index 8bdc7fc8..10668ffa 100644 --- a/apps/frontend/src/components.d.ts +++ b/apps/frontend/src/components.d.ts @@ -32,6 +32,7 @@ declare module 'vue' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] + ElImage: typeof import('element-plus/es')['ElImage'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElMenu: typeof import('element-plus/es')['ElMenu'] diff --git a/apps/frontend/src/views/claim/kuaishou-cloud/KuaishouCloudClaimView.vue b/apps/frontend/src/views/claim/kuaishou-cloud/KuaishouCloudClaimView.vue index 69e8dc94..da17cf77 100644 --- a/apps/frontend/src/views/claim/kuaishou-cloud/KuaishouCloudClaimView.vue +++ b/apps/frontend/src/views/claim/kuaishou-cloud/KuaishouCloudClaimView.vue @@ -52,6 +52,8 @@ const claim = useKuaishouCloudClaim(() => props.token) :is-binding-prepared="claim.isBindingPrepared.value" :is-binding-preparing="claim.isBindingPreparing.value" :is-role-ready="claim.isRoleReady.value" + :role-name="claim.roleName.value" + :role-id="claim.roleId.value" :refreshing-role="claim.refreshingRole.value" :confirming-role="claim.confirmingRole.value" :flow="claim.flow.value" diff --git a/apps/frontend/src/views/claim/kuaishou-cloud/components/ClaimBindingStep.vue b/apps/frontend/src/views/claim/kuaishou-cloud/components/ClaimBindingStep.vue index 5c2e28d3..768539e6 100644 --- a/apps/frontend/src/views/claim/kuaishou-cloud/components/ClaimBindingStep.vue +++ b/apps/frontend/src/views/claim/kuaishou-cloud/components/ClaimBindingStep.vue @@ -3,11 +3,16 @@ import { Loading, RefreshRight } from '@element-plus/icons-vue' import type { ClaimKuaishouCloudFlowInfo } from '@/types/claim' +const bindingGuideImageUrl = '/kuaishou-cloud-guide/4.jpg' +const bindingGuidePreviewImages = [bindingGuideImageUrl] + defineProps<{ qrCodeDataUrl: string isBindingPrepared: boolean isBindingPreparing: boolean isRoleReady: boolean + roleName: string + roleId: string refreshingRole: boolean confirmingRole: boolean flow: ClaimKuaishouCloudFlowInfo @@ -47,19 +52,36 @@ defineEmits<{
绑定操作指引

保存二维码,Q 玩的用 Q 扫,V 玩的用 V 扫,扫了后绑定角色。

+ 点击右侧图片查看大图
- 扫码绑定角色操作指引 +
+
+ 当前角色 + {{ roleName || '待识别' }} +
+
+ 角色 ID + {{ roleId || '-' }} +
+
+

{{ flow.role.errorMessage }}

- 如果当前角色不正确,请重新打开绑定页完成重新绑定,然后刷新角色信息。 + 系统会自动刷新角色信息。如果当前角色不正确,请重新打开绑定页完成重新绑定。

最近刷新:{{ formatDateTime(flow.role.refreshedAt) }};链接有效期:{{ @@ -183,14 +205,60 @@ defineEmits<{ line-height: 1.7; } +.guide-copy span { + color: #64748b; + font-size: 13px; +} + .guide-image { width: 100%; + aspect-ratio: 1 / 1; border-radius: 14px; border: 1px solid #dbeafe; background: #ffffff; + cursor: zoom-in; + overflow: hidden; +} + +.guide-image :deep(img) { + width: 100%; + height: 100%; object-fit: cover; } +.role-panel { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; + padding: 14px; + border: 1px solid #bfdbfe; + border-radius: 16px; + background: #eff6ff; +} + +.role-panel.pending { + border-color: #e2e8f0; + background: #f8fafc; +} + +.role-panel__item { + min-width: 0; + display: flex; + flex-direction: column; + gap: 4px; +} + +.role-panel__item span { + color: #64748b; + font-size: 13px; +} + +.role-panel__item strong { + color: #0f172a; + font-size: 17px; + overflow-wrap: anywhere; +} + .action-row { display: flex; gap: 12px; @@ -224,6 +292,10 @@ defineEmits<{ grid-template-columns: 1fr; } + .role-panel { + grid-template-columns: 1fr; + } + .guide-image { max-width: 320px; justify-self: center; 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 84ffb424..2309a2a3 100644 --- a/apps/frontend/src/views/claim/kuaishou-cloud/composables/useKuaishouCloudClaim.ts +++ b/apps/frontend/src/views/claim/kuaishou-cloud/composables/useKuaishouCloudClaim.ts @@ -12,6 +12,13 @@ import { } from "@/services/claim"; import type { ClaimDetailData } from "@/types/claim"; +const BINDING_PREPARE_POLL_MS = 5_000; +const ROLE_FAST_POLL_MS = 10_000; +const ROLE_SLOW_POLL_MS = 30_000; +const ROLE_IDLE_POLL_MS = 60_000; +const ROLE_FAST_WINDOW_MS = 3 * 60_000; +const ROLE_IDLE_WINDOW_MS = 10 * 60_000; + export function useKuaishouCloudClaim(token: () => string) { const loading = ref(true); const submitting = ref(false); @@ -23,6 +30,8 @@ export function useKuaishouCloudClaim(token: () => string) { const ticketCode = ref(""); const qrCodeDataUrl = ref(""); let pollTimer = 0; + let rolePollBaselineAt = 0; + let rolePollBaselineKey = ""; // ── derived data ──────────────────────────────────────── @@ -294,28 +303,74 @@ export function useKuaishouCloudClaim(token: () => string) { function stopPolling() { if (pollTimer) { - window.clearInterval(pollTimer); + window.clearTimeout(pollTimer); pollTimer = 0; } } function syncPolling() { - const taskStatus = String(task.value?.status || "").trim(); - const shouldPoll = - Boolean(flow.value) && - !hasRedeemResult.value && - !["closed", "expired"].includes(taskStatus); - - if (!shouldPoll) { + const delay = resolveNextPollDelay(); + if (!delay) { stopPolling(); return; } - if (pollTimer) { - return; - } - pollTimer = window.setInterval(() => { + + stopPolling(); + pollTimer = window.setTimeout(() => { + pollTimer = 0; void loadDetail({ silent: true }); - }, 4000); + }, delay); + } + + function resolveNextPollDelay() { + const taskStatus = String(task.value?.status || "").trim(); + + if ( + !flow.value || + hasRedeemResult.value || + ["closed", "expired", "manual_review", "failed"].includes(taskStatus) + ) { + return 0; + } + + if (currentStep.value === 2) { + if (!isBindingPrepared.value) { + return BINDING_PREPARE_POLL_MS; + } + + return resolveRolePollDelay(); + } + + if (currentStep.value === 3) { + return 0; + } + + return 0; + } + + function resolveRolePollDelay() { + const nextRoleKey = [roleName.value, roleId.value].join("::"); + const now = Date.now(); + + if (nextRoleKey !== rolePollBaselineKey) { + rolePollBaselineKey = nextRoleKey; + rolePollBaselineAt = now; + } + + if (!rolePollBaselineAt) { + rolePollBaselineAt = now; + } + + const elapsedMs = now - rolePollBaselineAt; + if (elapsedMs < ROLE_FAST_WINDOW_MS) { + return ROLE_FAST_POLL_MS; + } + + if (elapsedMs < ROLE_IDLE_WINDOW_MS) { + return ROLE_SLOW_POLL_MS; + } + + return ROLE_IDLE_POLL_MS; } // ── lifecycle ───────────────────────────────────────────