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 ───────────────────────────────────────────