This commit is contained in:
yml
2026-04-08 16:30:42 +08:00
commit 313c036845
131 changed files with 22393 additions and 0 deletions
@@ -0,0 +1,299 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import TencentAuthCard from '@/components/tencent/TencentAuthCard.vue'
import TencentRedeemPanel from '@/components/tencent/TencentRedeemPanel.vue'
import TencentResultPanel from '@/components/tencent/TencentResultPanel.vue'
import { useTencentBrowserSessionPage } from '@/composables/useTencentBrowserSessionPage'
const {
activityInfo,
canRedeem,
createSessionFlow,
hasSession,
initButtonLabel,
loginTabs,
loginType,
loginTypeLabel,
maxAttempts,
qrImage,
redeemButtonLabel,
redeemBlockedReason,
redeemCode,
redeemLoading,
redeemNow,
reloadSessionPage,
resultFacts,
roleConfirmed,
roleFacts,
scanInstruction,
screenshotEmptyMessage,
screenshotEmptyTitle,
screenshotUrl,
session,
sessionLoading,
sessionNotice,
statusLabel,
switchLoginType,
} = useTencentBrowserSessionPage()
const qrPreviewVisible = ref(false)
const screenshotPreviewVisible = ref(false)
const qrImageNaturalWidth = ref(0)
const currentLoginTabLabel = computed(
() => loginTabs.find((tab) => tab.value === loginType.value)?.label ?? loginTabs[0].label,
)
watch(qrImage, () => {
qrImageNaturalWidth.value = 0
})
function handleQrImageLoad(event: Event) {
const target = event.target
if (!(target instanceof HTMLImageElement)) {
return
}
qrImageNaturalWidth.value = target.naturalWidth || 0
}
const qrDisplayWidth = computed(() => {
const naturalWidth = qrImageNaturalWidth.value
if (!naturalWidth) {
return 220
}
if (naturalWidth < 160) {
return Math.min(naturalWidth * 2, 220)
}
return Math.min(naturalWidth, 240)
})
const qrFigureStyle = computed(() => ({
width: `${qrDisplayWidth.value}px`,
maxWidth: '100%',
}))
const qrPreviewWidth = computed(() => `${Math.min(Math.max(qrDisplayWidth.value + 120, 360), 480)}px`)
const showScreenshot = computed(() => Boolean(screenshotUrl.value))
</script>
<template>
<main class="browser-page">
<section class="workspace">
<div class="page-toolbar">
<div class="brand-chip">腾讯浏览器兑换</div>
<div class="toolbar-actions">
<div class="status-badge">{{ statusLabel }}</div>
<RouterLink class="back-link" to="/">返回首页</RouterLink>
</div>
</div>
<div class="layout-grid">
<TencentAuthCard
:current-login-tab-label="currentLoginTabLabel"
:has-session="hasSession"
:init-button-label="initButtonLabel"
:login-tabs="loginTabs"
:login-type="loginType"
:login-type-label="loginTypeLabel"
:qr-figure-style="qrFigureStyle"
:qr-image="qrImage"
:scan-instruction="scanInstruction"
:session-status="session?.status || ''"
:session-id="session?.sessionId || ''"
:session-loading="sessionLoading"
:session-notice="sessionNotice"
@create-session="createSessionFlow"
@open-qr-preview="qrPreviewVisible = true"
@qr-image-load="handleQrImageLoad"
@reload-session="reloadSessionPage"
@switch-login-type="switchLoginType"
/>
<aside class="control-column">
<TencentRedeemPanel
:can-redeem="canRedeem"
:login-type-label="loginTypeLabel"
:max-attempts="maxAttempts"
:redeem-blocked-reason="redeemBlockedReason"
:redeem-button-label="redeemButtonLabel"
:redeem-code="redeemCode"
:redeem-loading="redeemLoading"
:role-confirmed="roleConfirmed"
:role-facts="roleFacts"
:role-ready="Boolean(activityInfo?.role?.ready)"
:status-label="statusLabel"
@redeem="redeemNow"
@update:max-attempts="maxAttempts = $event"
@update:redeem-code="redeemCode = $event"
@update:role-confirmed="roleConfirmed = $event"
/>
</aside>
</div>
<TencentResultPanel
:result-facts="resultFacts"
:screenshot-empty-message="screenshotEmptyMessage"
:screenshot-empty-title="screenshotEmptyTitle"
:screenshot-url="screenshotUrl"
:show-screenshot="showScreenshot"
@open-screenshot-preview="screenshotPreviewVisible = true"
/>
</section>
<el-dialog
v-model="qrPreviewVisible"
align-center
class="preview-dialog"
:width="qrPreviewWidth"
>
<div class="qr-preview-body">
<img
class="preview-image qr-preview-image"
:src="qrImage"
:alt="`Tencent ${loginTypeLabel} QR Preview`"
/>
</div>
</el-dialog>
<el-dialog
v-model="screenshotPreviewVisible"
align-center
class="preview-dialog"
width="1080px"
>
<img class="preview-image" :src="screenshotUrl" alt="Tencent Screenshot Preview" />
</el-dialog>
</main>
</template>
<style scoped>
.browser-page {
min-height: 100vh;
padding: 22px 16px 34px;
font-family: 'Avenir Next', 'PingFang SC', 'Helvetica Neue', sans-serif;
background:
radial-gradient(circle at top left, rgba(116, 168, 255, 0.12), transparent 28%),
radial-gradient(circle at bottom right, rgba(248, 186, 103, 0.12), transparent 24%),
linear-gradient(180deg, #f4f7fb 0%, #edf2f8 100%);
}
.workspace {
width: min(1120px, 100%);
margin: 0 auto;
display: grid;
gap: 16px;
}
.page-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.toolbar-actions {
display: flex;
align-items: center;
gap: 10px;
}
.brand-chip {
display: inline-flex;
align-items: center;
min-height: 38px;
padding: 0 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(86, 108, 138, 0.12);
color: #2f425d;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.04em;
}
.status-badge,
.back-link {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 38px;
padding: 0 14px;
border-radius: 999px;
border: 1px solid rgba(86, 108, 138, 0.14);
background: rgba(255, 255, 255, 0.92);
color: #31445e;
text-decoration: none;
font-size: 13px;
}
.status-badge {
font-weight: 700;
color: #5f6f87;
}
.layout-grid {
display: grid;
grid-template-columns: minmax(340px, 392px) minmax(0, 1fr);
gap: 16px;
align-items: stretch;
}
.control-column {
display: grid;
gap: 0;
}
.preview-image {
display: block;
width: 100%;
height: auto;
}
:deep(.preview-dialog .el-dialog) {
border-radius: 28px;
overflow: hidden;
}
:deep(.preview-dialog .el-dialog__body) {
padding: 0;
background: #f5f7fb;
}
.qr-preview-body {
display: grid;
place-items: center;
padding: 24px;
}
.qr-preview-image {
width: auto;
max-width: 100%;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
image-rendering: pixelated;
}
@media (max-width: 1140px) {
.layout-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 760px) {
.browser-page {
padding: 16px;
}
.page-toolbar,
.toolbar-actions {
flex-direction: column;
align-items: flex-start;
}
}
</style>