import { computed, onBeforeUnmount, ref, watch } from 'vue' import { showSuccess } from '@/lib/feedback' import { confirmClaimRole, createClaimSession, fetchClaimDetail, fetchClaimSessionSummary, refreshClaimSession, removeClaimSession, redeemClaim, } from '@/services/claim' import type { ClaimDetailData } from '@/types/claim' import type { TencentLoginType } from '@/types/tencent/session' import { notifyTencentActionError } from './tencent/session-errors' import { useSessionPolling, useSessionQrDisplay, useSessionResultFacts, buildRedeemBlockedReason, resolveTaskStatusLabel, syncLoginTypeFromDetail, mergeSessionDetail, shouldRefreshQrImage, shouldKeepPolling, DEFAULT_LOGIN_TYPE, } from './shared' export function useClaimPage(token: string) { const detailLoading = ref(true) const sessionLoading = ref(false) const redeemLoading = ref(false) const roleConfirmLoading = ref(false) const loginType = ref(DEFAULT_LOGIN_TYPE) const detail = ref(null) const roleConfirmed = ref(false) // ── Derived state ────────────────────────────────────────────────── const session = computed(() => detail.value?.session || null) const task = computed(() => detail.value?.task || null) const order = computed(() => detail.value?.order || null) const orderItem = computed(() => detail.value?.orderItem || null) const result = computed(() => detail.value?.result || null) const tokenStatus = computed(() => detail.value?.tokenStatus || 'active') const activityInfo = computed(() => session.value?.activityInfo || null) const roleReady = computed(() => Boolean(activityInfo.value?.role?.ready)) const hasSession = computed(() => Boolean(session.value?.sessionId)) const loginTypeLabel = computed(() => (loginType.value === 'wx' ? '微信' : 'QQ')) // ── Polling ──────────────────────────────────────────────────────── const { pollWarningMessage, startPolling, resetPolling, resetPollingWarning, handleSilentPollingError, } = useSessionPolling({ hasSession, shouldKeepPolling: computed(() => shouldKeepPolling(detail.value)), onPollTick: async () => { await refreshSessionSummary({ silent: true }) }, pollWarningPrefix: '领取状态刷新失败', }) const sessionNotice = computed( () => pollWarningMessage.value || session.value?.notice || task.value?.lastError || '', ) // ── QR display ───────────────────────────────────────────────────── const { qrImage, qrFigureStyle, qrPreviewWidth, handleQrImageLoad } = useSessionQrDisplay({ session, }) // ── Presentation ─────────────────────────────────────────────────── const screenshotUrl = computed(() => result.value?.screenshotUrl || '') const showScreenshot = computed(() => Boolean(screenshotUrl.value)) const loginTabs = [ { value: 'qq' as const, label: 'QQ账号登录' }, { value: 'wx' as const, label: '微信账号登录' }, ] const statusLabel = computed(() => resolveTaskStatusLabel(task.value?.status, session.value?.status, 'claim'), ) const initButtonLabel = computed(() => `开始初始化 ${loginTypeLabel.value} 登录`) const scanInstruction = computed( () => `请使用${loginTypeLabel.value}扫描二维码,并在手机上确认登录`, ) // Claim pages show 4 role facts, but original only had 3 (no area). // Override roleFacts to match original exactly: const claimRoleFacts = computed(() => [ { label: '登录昵称', value: activityInfo.value?.nickname || '等待扫码登录', }, { label: '当前角色', value: activityInfo.value?.role?.roleName || '后端浏览器同步中', accent: true, }, { label: '角色 ID', value: activityInfo.value?.role?.roleId || '未识别', }, { label: '任务状态', value: statusLabel.value, }, ]) const { resultFacts } = useSessionResultFacts({ order, orderItem, result, }) const canConfirmRole = computed(() => Boolean( task.value && !task.value.requiresSupportReview && hasSession.value && roleReady.value && task.value.status === 'claimed', ), ) const canRedeem = computed(() => Boolean( task.value && !task.value.requiresSupportReview && hasSession.value && roleConfirmed.value && !redeemLoading.value && (task.value.status === 'role_confirmed' || task.value.status === 'redeeming'), ), ) const redeemBlockedReason = computed(() => buildRedeemBlockedReason({ detail, hasSession, loginTypeLabel, redeemLoading, roleReady, roleConfirmed, sessionNotice, context: 'claim', tokenStatus, requiresSupportReview: computed(() => Boolean(task.value?.requiresSupportReview)), }), ) const redeemButtonLabel = computed(() => task.value?.requiresSupportReview ? '等待客服兑换' : '开始兑换', ) const screenshotEmptyTitle = computed(() => task.value?.status === 'redeemed' ? '本次没有可展示的截图' : '等待截图', ) const screenshotEmptyMessage = computed(() => task.value?.status === 'redeemed' ? '本次兑换可能未生成截图,或截图产物还未同步完成。' : '领取完成后,如本次生成了结果截图,这里会展示。', ) // ── Watchers ─────────────────────────────────────────────────────── watch( () => [ task.value?.taskId || '', activityInfo.value?.nickname || '', activityInfo.value?.role?.roleId || '', activityInfo.value?.role?.roleName || '', activityInfo.value?.role?.area || '', activityInfo.value?.role?.partition || '', ].join('|'), () => { roleConfirmed.value = Boolean( task.value?.status === 'role_confirmed' || task.value?.status === 'redeeming' || task.value?.status === 'redeemed', ) }, { immediate: true }, ) // ── Helpers ──────────────────────────────────────────────────────── function applyLoginTypeFromDetail(nextDetail: ClaimDetailData) { loginType.value = syncLoginTypeFromDetail(nextDetail, loginType.value) } function restartPollingIfNeeded(nextDetail = detail.value) { resetPolling() if (shouldKeepPolling(nextDetail)) { startPolling() } } // ── API flows ───────────────────────────────────────────────────── async function loadDetail() { detailLoading.value = true try { const response = await fetchClaimDetail(token) detail.value = mergeSessionDetail(detail.value, response.data) applyLoginTypeFromDetail(response.data) restartPollingIfNeeded() } catch (error) { notifyTencentActionError(error, '领取详情加载失败') } finally { detailLoading.value = false } } async function createSessionFlow(nextLoginType = loginType.value) { sessionLoading.value = true resetPolling() resetPollingWarning() try { loginType.value = nextLoginType const response = await createClaimSession(token, nextLoginType, { forceRecreate: hasSession.value, }) detail.value = mergeSessionDetail(detail.value, response.data) applyLoginTypeFromDetail(response.data) startPolling() } catch (error) { notifyTencentActionError(error, '创建领取会话失败') } finally { sessionLoading.value = false } } async function reloadSessionPage() { if (!hasSession.value) { return false } sessionLoading.value = true resetPolling() resetPollingWarning() try { const response = await refreshClaimSession(token) detail.value = mergeSessionDetail(detail.value, response.data) applyLoginTypeFromDetail(response.data) restartPollingIfNeeded(response.data) return true } catch (error) { notifyTencentActionError(error, '刷新后端页面失败') await refreshSessionSummary({ silent: true }) restartPollingIfNeeded() return false } finally { sessionLoading.value = false } } async function closeSessionFlow({ silent = false } = {}) { if (!hasSession.value) { return false } if (!silent) { sessionLoading.value = true } resetPolling() resetPollingWarning() try { const response = await removeClaimSession(token) detail.value = mergeSessionDetail(detail.value, response.data) applyLoginTypeFromDetail(response.data) if (!silent) { showSuccess(response.msg || '领取会话已关闭') } return true } catch (error) { if (!silent) { notifyTencentActionError(error, '关闭领取会话失败') } return false } finally { if (!silent) { sessionLoading.value = false } } } async function refreshSessionSummary({ silent = false } = {}) { if (!hasSession.value) { return false } if (!silent) { sessionLoading.value = true resetPollingWarning() } try { const currentSession = detail.value?.session || null const response = await fetchClaimSessionSummary(token) let nextDetail = mergeSessionDetail(detail.value, response.data) detail.value = nextDetail applyLoginTypeFromDetail(nextDetail) if (shouldRefreshQrImage(currentSession, response.data.session)) { const fullResponse = await fetchClaimDetail(token) nextDetail = mergeSessionDetail(nextDetail, fullResponse.data) detail.value = nextDetail applyLoginTypeFromDetail(nextDetail) } if (silent) { resetPollingWarning() } return true } catch (error) { if (silent) { handleSilentPollingError(error) } else { notifyTencentActionError(error, '领取会话状态刷新失败') } return false } finally { if (!silent) { sessionLoading.value = false } } } async function confirmRoleNow() { if (!canConfirmRole.value) { return } roleConfirmLoading.value = true try { const response = await confirmClaimRole(token) detail.value = mergeSessionDetail(detail.value, response.data) roleConfirmed.value = true showSuccess(response.msg || '角色已确认') restartPollingIfNeeded(response.data) } catch (error) { notifyTencentActionError(error, '角色确认失败') await refreshSessionSummary({ silent: true }) } finally { roleConfirmLoading.value = false } } async function redeemNow() { if (!canRedeem.value) { return } redeemLoading.value = true resetPolling() resetPollingWarning() try { const response = await redeemClaim(token) detail.value = mergeSessionDetail(detail.value, response.data) showSuccess(response.msg || '兑换完成') restartPollingIfNeeded(response.data) } catch (error) { notifyTencentActionError(error, '领取兑换失败') await refreshSessionSummary({ silent: true }) restartPollingIfNeeded() } finally { redeemLoading.value = false } } async function switchLoginType(nextLoginType: TencentLoginType) { if (nextLoginType === loginType.value) { return } if (hasSession.value) { await createSessionFlow(nextLoginType) return } loginType.value = nextLoginType } // ── Lifecycle ────────────────────────────────────────────────────── loadDetail() onBeforeUnmount(() => { resetPolling() }) return { detailLoading, sessionLoading, redeemLoading, roleConfirmLoading, loginType, loginTypeLabel, loginTabs, detail, task, order, orderItem, activityInfo, hasSession, qrImage, qrFigureStyle, qrPreviewWidth, statusLabel, session, sessionNotice, roleFacts: claimRoleFacts, resultFacts, roleConfirmed, roleReady, canConfirmRole, canRedeem, redeemBlockedReason, redeemButtonLabel, initButtonLabel, scanInstruction, screenshotEmptyTitle, screenshotEmptyMessage, screenshotUrl, showScreenshot, createSessionFlow, reloadSessionPage, closeSessionFlow, refreshSessionSummary, switchLoginType, confirmRoleNow, redeemNow, handleQrImageLoad, } }