优化了qq微信切换

This commit is contained in:
yml2213
2026-04-13 21:48:23 +08:00
parent dc8a014601
commit bc0d6fc8aa
10 changed files with 453 additions and 67 deletions
@@ -23,6 +23,7 @@ const props = defineProps<{
}>()
const emit = defineEmits<{
closeSession: []
createSession: [loginType: TencentLoginType]
openQrPreview: []
qrImageLoad: [event: Event]
@@ -110,23 +111,33 @@ const emit = defineEmits<{
<el-button
:loading="props.sessionLoading"
class="primary-action"
round
size="large"
type="primary"
@click="emit('createSession', props.loginType)"
>
{{ props.hasSession ? '重新生成二维码' : props.initButtonLabel }}
</el-button>
<el-button
:disabled="!props.hasSession"
:loading="props.sessionLoading"
class="secondary-action"
round
size="large"
@click="emit('reloadSession')"
>
刷新后端页面
</el-button>
<div class="secondary-actions">
<el-button
:disabled="!props.hasSession"
:loading="props.sessionLoading"
class="secondary-action"
size="large"
@click="emit('reloadSession')"
>
刷新后端页面
</el-button>
<el-button
:disabled="!props.hasSession"
:loading="props.sessionLoading"
class="tertiary-action"
size="large"
@click="emit('closeSession')"
>
关闭当前会话
</el-button>
</div>
</div>
</div>
</section>
@@ -334,30 +345,78 @@ const emit = defineEmits<{
.action-row {
margin-top: 16px;
display: grid;
grid-template-columns: minmax(0, 1.45fr) minmax(132px, 0.95fr);
gap: 10px;
display: flex;
flex-direction: column;
gap: 12px;
}
.primary-action :deep(span) {
font-weight: 700;
letter-spacing: 0.01em;
}
.action-row :deep(.el-button) {
width: 100%;
min-width: 0;
margin: 0;
height: 52px;
border-radius: 18px;
}
.action-row :deep(.el-button > span) {
white-space: nowrap;
}
.secondary-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.primary-action,
.secondary-action {
.secondary-action,
.tertiary-action {
min-width: 0;
}
.action-row :deep(.primary-action.el-button) {
box-shadow: 0 16px 30px rgba(91, 143, 240, 0.24);
}
.action-row :deep(.primary-action.el-button:not(.is-disabled)) {
background: linear-gradient(135deg, #6da9fb 0%, #4f85eb 100%);
border-color: transparent;
}
.action-row :deep(.secondary-action.el-button),
.action-row :deep(.tertiary-action.el-button) {
background: rgba(248, 251, 255, 0.98);
border: 1px solid #d9e4f2;
color: #4a5e79;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
}
.action-row :deep(.secondary-action.el-button:hover),
.action-row :deep(.secondary-action.el-button:focus-visible) {
border-color: #9dc0f6;
color: #2859b8;
background: #f3f8ff;
}
.action-row :deep(.tertiary-action.el-button:hover),
.action-row :deep(.tertiary-action.el-button:focus-visible) {
border-color: #efb4b4;
color: #b43c3c;
background: #fff6f6;
}
.action-row :deep(.secondary-action.el-button.is-disabled),
.action-row :deep(.tertiary-action.el-button.is-disabled) {
background: #f8fafc;
border-color: #e5ebf3;
color: #9aa8bc;
}
@media (max-width: 760px) {
.auth-card {
padding: 18px;
@@ -372,13 +431,7 @@ const emit = defineEmits<{
font-size: 15px;
}
.session-strip,
.action-row {
flex-direction: column;
align-items: flex-start;
}
.action-row {
.secondary-actions {
grid-template-columns: 1fr;
}
+147 -15
View File
@@ -6,10 +6,16 @@ import {
createClaimSession,
fetchClaimDetail,
fetchClaimSessionSummary,
refreshClaimSession,
removeClaimSession,
redeemClaim,
} from '@/services/claim'
import type { ClaimDetailData, ClaimTaskStatus } from '@/types/claim'
import type { TencentLoginType } from '@/types/tencent/session'
import type {
TencentBrowserSessionData,
TencentBrowserSessionSummaryData,
TencentLoginType,
} from '@/types/tencent/session'
import { notifyTencentActionError } from './tencent/session-errors'
@@ -163,7 +169,7 @@ export function useClaimPage(token: string) {
)
function applyLoginTypeFromDetail(nextDetail: ClaimDetailData) {
loginType.value = syncLoginTypeFromDetail(nextDetail)
loginType.value = syncLoginTypeFromDetail(nextDetail, loginType.value)
}
watch(
@@ -182,6 +188,10 @@ export function useClaimPage(token: string) {
{ immediate: true },
)
watch(qrImage, () => {
qrImageNaturalWidth.value = 0
})
async function loadDetail() {
detailLoading.value = true
@@ -209,7 +219,9 @@ export function useClaimPage(token: string) {
try {
loginType.value = nextLoginType
const response = await createClaimSession(token, nextLoginType)
const response = await createClaimSession(token, nextLoginType, {
forceRecreate: hasSession.value,
})
if (response.code !== 0) {
throw new Error(response.msg || '创建领取会话失败')
@@ -225,6 +237,75 @@ export function useClaimPage(token: string) {
}
}
async function reloadSessionPage() {
if (!hasSession.value) {
return false
}
sessionLoading.value = true
resetPolling()
resetPollingWarning()
try {
const response = await refreshClaimSession(token)
if (response.code !== 0) {
throw new Error(response.msg || '刷新后端页面失败')
}
detail.value = mergeClaimDetailData(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)
if (response.code !== 0) {
throw new Error(response.msg || '关闭领取会话失败')
}
detail.value = mergeClaimDetailData(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
@@ -236,14 +317,28 @@ export function useClaimPage(token: string) {
}
try {
const currentSession = detail.value?.session || null
const response = await fetchClaimSessionSummary(token)
if (response.code !== 0) {
throw new Error(response.msg || '领取会话状态刷新失败')
}
detail.value = mergeClaimDetailData(detail.value, response.data)
applyLoginTypeFromDetail(response.data)
let nextDetail = mergeClaimDetailData(detail.value, response.data)
detail.value = nextDetail
applyLoginTypeFromDetail(nextDetail)
if (shouldRefreshClaimQrImage(currentSession, response.data)) {
const fullResponse = await fetchClaimDetail(token)
if (fullResponse.code !== 0) {
throw new Error(fullResponse.msg || '领取二维码刷新失败')
}
nextDetail = mergeClaimDetailData(nextDetail, fullResponse.data)
detail.value = nextDetail
applyLoginTypeFromDetail(nextDetail)
}
if (silent) {
resetPollingWarning()
@@ -324,11 +419,12 @@ export function useClaimPage(token: string) {
return
}
loginType.value = nextLoginType
if (hasSession.value) {
await createSessionFlow(nextLoginType)
return
}
loginType.value = nextLoginType
}
function startPolling() {
@@ -461,6 +557,8 @@ export function useClaimPage(token: string) {
screenshotUrl,
showScreenshot,
createSessionFlow,
reloadSessionPage,
closeSessionFlow,
refreshSessionSummary,
switchLoginType,
confirmRoleNow,
@@ -469,9 +567,17 @@ export function useClaimPage(token: string) {
}
}
function syncLoginTypeFromDetail(detail: ClaimDetailData) {
function syncLoginTypeFromDetail(detail: ClaimDetailData, fallback: TencentLoginType = DEFAULT_LOGIN_TYPE) {
const nextLoginType = String(detail.session?.loginType || detail.task.loginType || '').trim()
return nextLoginType === 'wx' ? 'wx' : 'qq'
if (nextLoginType === 'wx') {
return 'wx'
}
if (nextLoginType === 'qq') {
return 'qq'
}
return fallback
}
function shouldKeepPolling(detail: ClaimDetailData | null) {
@@ -516,17 +622,17 @@ function resolveTaskStatusLabel(taskStatus?: string, sessionStatus?: string) {
}
function mergeClaimDetailData(current: ClaimDetailData | null, next: ClaimDetailData) {
if (!current?.session) {
return next
}
if (!next.session) {
if (next.session === null) {
return {
...next,
session: current.session,
session: null,
}
}
if (!current?.session) {
return next
}
return {
...next,
session: {
@@ -542,3 +648,29 @@ function mergeClaimDetailData(current: ClaimDetailData | null, next: ClaimDetail
},
}
}
function shouldRefreshClaimQrImage(
currentSession: ClaimDetailData['session'],
nextDetail: ClaimDetailData,
) {
if (!currentSession || !nextDetail.session) {
return false
}
return shouldRefreshQrImage(currentSession, nextDetail.session)
}
function shouldRefreshQrImage(
current: TencentBrowserSessionData | TencentBrowserSessionSummaryData,
next: TencentBrowserSessionData | TencentBrowserSessionSummaryData,
) {
if (!next.artifacts?.hasQrImage) {
return false
}
if (!current.qrImageBase64) {
return true
}
return Boolean(next.qrUpdatedAt && next.qrUpdatedAt !== current.qrUpdatedAt)
}
@@ -210,6 +210,7 @@ export function useTencentBrowserSessionPage() {
return {
activityInfo,
canRedeem,
teardownSession,
createSessionFlow,
hasSession,
initButtonLabel,
+18 -3
View File
@@ -1,4 +1,4 @@
import { apiGet, apiPost } from '@/lib/http'
import { apiDelete, apiGet, apiPost } from '@/lib/http'
import type { ClaimDetailData } from '@/types/claim'
import type { TencentLoginType } from '@/types/tencent/session'
@@ -6,14 +6,29 @@ export function fetchClaimDetail(token: string) {
return apiGet<ClaimDetailData>(`/api/v1/claim/${token}`)
}
export function createClaimSession(token: string, loginType: TencentLoginType) {
return apiPost<ClaimDetailData>(`/api/v1/claim/${token}/session`, { loginType })
export function createClaimSession(
token: string,
loginType: TencentLoginType,
options: { forceRecreate?: boolean } = {},
) {
return apiPost<ClaimDetailData>(`/api/v1/claim/${token}/session`, {
loginType,
forceRecreate: Boolean(options.forceRecreate),
})
}
export function fetchClaimSessionSummary(token: string) {
return apiGet<ClaimDetailData>(`/api/v1/claim/${token}/session/summary`)
}
export function refreshClaimSession(token: string) {
return apiPost<ClaimDetailData>(`/api/v1/claim/${token}/session/refresh`, {})
}
export function removeClaimSession(token: string) {
return apiDelete<ClaimDetailData>(`/api/v1/claim/${token}/session`)
}
export function confirmClaimRole(token: string) {
return apiPost<ClaimDetailData>(`/api/v1/claim/${token}/confirm-role`, { confirm: true })
}
@@ -6,10 +6,12 @@ export type TencentLoginType = 'qq' | 'wx'
export type TencentBrowserSessionStatus =
| 'waiting_scan'
| 'scanned'
| 'expired'
| 'logged_in'
| 'ready_to_redeem'
| 'redeeming'
| 'redeemed'
| 'closed'
| 'failed'
| (string & {})
+4 -2
View File
@@ -43,7 +43,8 @@ const {
screenshotUrl,
showScreenshot,
createSessionFlow,
refreshSessionSummary,
reloadSessionPage,
closeSessionFlow,
switchLoginType,
confirmRoleNow,
redeemNow,
@@ -127,9 +128,10 @@ const helperText = computed(() => {
:session-loading="sessionLoading"
:session-notice="sessionNotice"
@create-session="createSessionFlow"
@close-session="closeSessionFlow"
@open-qr-preview="qrPreviewVisible = true"
@qr-image-load="handleQrImageLoad"
@reload-session="refreshSessionSummary()"
@reload-session="reloadSessionPage"
@switch-login-type="switchLoginType"
/>
@@ -35,6 +35,7 @@ const {
sessionNotice,
statusLabel,
switchLoginType,
teardownSession,
} = useTencentBrowserSessionPage()
const qrPreviewVisible = ref(false)
@@ -109,6 +110,7 @@ const showScreenshot = computed(() => Boolean(screenshotUrl.value))
:session-loading="sessionLoading"
:session-notice="sessionNotice"
@create-session="createSessionFlow"
@close-session="teardownSession"
@open-qr-preview="qrPreviewVisible = true"
@qr-image-load="handleQrImageLoad"
@reload-session="reloadSessionPage"