Files
order_site/apps/frontend/src/composables/useTencentBrowserSessionPage.ts
T
2026-04-13 21:48:23 +08:00

242 lines
5.9 KiB
TypeScript

import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { showError, showSuccess } from '@/lib/feedback'
import {
createTencentBrowserSession,
refreshTencentBrowserSessionPage,
removeTencentBrowserSession,
} from '@/services/tencent/session'
import { redeemTencentBrowserSession } from '@/services/tencent/redeem'
import type { TencentBrowserSessionData, TencentLoginType } from '@/types/tencent/session'
import { notifyTencentActionError } from './tencent/session-errors'
import { useTencentBrowserSessionPolling } from './tencent/useTencentBrowserSessionPolling'
import { useTencentBrowserSessionPresentation } from './tencent/useTencentBrowserSessionPresentation'
const DEFAULT_LOGIN_TYPE: TencentLoginType = 'qq'
export function useTencentBrowserSessionPage() {
const sessionLoading = ref(false)
const redeemLoading = ref(false)
const loginType = ref<TencentLoginType>(DEFAULT_LOGIN_TYPE)
const session = ref<TencentBrowserSessionData | null>(null)
const redeemCode = ref('')
const maxAttempts = ref(6)
const roleConfirmed = ref(false)
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'))
const loginTabs = [
{ value: 'qq' as const, label: 'QQ账号登录' },
{ value: 'wx' as const, label: '微信账号登录' },
]
const {
refreshSession,
resetPolling,
resetPollingWarning,
restartPollingIfActive,
sessionNotice,
startPolling,
} = useTencentBrowserSessionPolling({
session,
sessionLoading,
notifyActionError: notifyTencentActionError,
})
const {
canRedeem,
initButtonLabel,
qrImage,
redeemBlockedReason,
redeemButtonLabel,
resultFacts,
roleFacts,
scanInstruction,
screenshotEmptyMessage,
screenshotEmptyTitle,
screenshotUrl,
statusLabel,
} = useTencentBrowserSessionPresentation({
activityInfo,
hasSession,
loginTypeLabel,
redeemLoading,
roleConfirmed,
roleReady,
session,
sessionLoading,
sessionNotice,
})
watch(
() =>
[
session.value?.sessionId || '',
activityInfo.value?.nickname || '',
activityInfo.value?.role?.roleId || '',
activityInfo.value?.role?.roleName || '',
activityInfo.value?.role?.area || '',
activityInfo.value?.role?.partition || '',
].join('|'),
() => {
roleConfirmed.value = false
},
)
async function createSessionFlow(nextLoginType = loginType.value) {
resetPolling()
resetPollingWarning()
sessionLoading.value = true
try {
loginType.value = nextLoginType
await teardownSession()
const response = await createTencentBrowserSession({
loginType: nextLoginType,
})
if (response.code !== 0) {
throw new Error(response.msg || '创建浏览器会话失败')
}
session.value = response.data
startPolling()
} catch (error) {
notifyTencentActionError(error, '创建浏览器会话失败')
} finally {
sessionLoading.value = false
}
}
async function reloadSessionPage() {
if (!session.value?.sessionId) {
return
}
resetPolling()
resetPollingWarning()
sessionLoading.value = true
try {
const response = await refreshTencentBrowserSessionPage(session.value.sessionId)
if (response.code !== 0) {
throw new Error(response.msg || '刷新后端页面失败')
}
session.value = response.data
restartPollingIfActive(response.data)
} catch (error) {
notifyTencentActionError(error, '刷新后端页面失败')
await refreshSession({ silent: true })
restartPollingIfActive()
} finally {
sessionLoading.value = false
}
}
async function switchLoginType(nextLoginType: TencentLoginType) {
if (nextLoginType === loginType.value) {
return
}
loginType.value = nextLoginType
if (session.value?.sessionId) {
await createSessionFlow(nextLoginType)
}
}
async function redeemNow() {
if (!session.value?.sessionId) {
showError('请先创建浏览器会话')
return
}
if (!redeemCode.value.trim()) {
showError('请输入兑换码')
return
}
redeemLoading.value = true
resetPolling()
resetPollingWarning()
try {
const response = await redeemTencentBrowserSession(session.value.sessionId, {
code: redeemCode.value.trim(),
maxAttempts: maxAttempts.value,
})
if (response.code !== 0) {
throw new Error(response.msg || '浏览器兑换失败')
}
session.value = response.data
showSuccess(response.msg || '兑换完成')
} catch (error) {
notifyTencentActionError(error, '浏览器兑换失败')
await refreshSession({ silent: true })
} finally {
redeemLoading.value = false
}
}
async function teardownSession() {
if (!session.value?.sessionId) {
return
}
const sessionId = session.value.sessionId
session.value = null
try {
await removeTencentBrowserSession(sessionId)
} catch {
// ignore close failures on local teardown
}
}
onBeforeUnmount(() => {
resetPolling()
void teardownSession()
})
return {
activityInfo,
canRedeem,
teardownSession,
createSessionFlow,
hasSession,
initButtonLabel,
loginTabs,
loginType,
loginTypeLabel,
maxAttempts,
qrImage,
redeemBlockedReason,
redeemButtonLabel,
redeemCode,
redeemLoading,
redeemNow,
reloadSessionPage,
resultFacts,
roleConfirmed,
roleFacts,
scanInstruction,
screenshotEmptyMessage,
screenshotEmptyTitle,
screenshotUrl,
session,
sessionLoading,
sessionNotice,
statusLabel,
switchLoginType,
}
}