清理快手前端无用入口

This commit is contained in:
yml
2026-05-25 20:59:57 +08:00
parent db22ff691b
commit dc099ae6f8
65 changed files with 86 additions and 7279 deletions
@@ -1,481 +0,0 @@
<script setup lang="ts">
import type { TencentLoginType } from '@/types/tencent/session'
type LoginTab = {
value: TencentLoginType
label: string
}
const props = defineProps<{
currentLoginTabLabel: string
hasSession: boolean
initButtonLabel: string
loginTabs: LoginTab[]
loginType: TencentLoginType
loginTypeLabel: string
qrCopyButtonLabel?: string
qrCopyEnabled?: boolean
qrFigureStyle: { width: string; maxWidth: string }
qrImage: string
scanInstruction: string
sessionStatus?: string
sessionId: string
sessionLoading: boolean
sessionNotice: string
}>()
const emit = defineEmits<{
closeSession: []
copyQr: []
createSession: [loginType: TencentLoginType]
openQrPreview: []
qrImageLoad: [event: Event]
reloadSession: []
switchLoginType: [loginType: TencentLoginType]
}>()
</script>
<template>
<section class="auth-card">
<div class="login-tabs">
<button
v-for="tab in props.loginTabs"
:key="tab.value"
:class="['login-tab', { active: props.loginType === tab.value }]"
type="button"
@click="emit('switchLoginType', tab.value)"
>
{{ tab.label }}
</button>
</div>
<div class="qr-stage">
<button
v-if="props.qrImage && props.sessionStatus === 'waiting_scan'"
class="qr-figure"
:style="props.qrFigureStyle"
type="button"
@click="emit('openQrPreview')"
@dblclick.prevent.stop="emit('copyQr')"
>
<img
:src="props.qrImage"
:alt="`Tencent ${props.loginTypeLabel} Login QR`"
decoding="async"
@load="emit('qrImageLoad', $event)"
/>
</button>
<div v-else-if="props.sessionStatus === 'scanned'" class="status-state confirm-state">
<strong>确认中</strong>
<p>扫码已完成请在手机上点击确认登录确认后页面会继续同步角色信息</p>
</div>
<div
v-else-if="
['logged_in', 'ready_to_redeem', 'redeeming', 'redeemed'].includes(
String(props.sessionStatus || ''),
)
"
class="status-state success-state"
>
<strong>已完成登录</strong>
<p>二维码已自动收起,当前会话已登录,正在同步角色或等待后续兑换操作。</p>
</div>
<div v-else class="qr-empty">
<strong>{{ props.hasSession ? '二维码生成中' : props.currentLoginTabLabel }}</strong>
<p>
{{
props.hasSession
? '浏览器会话已启动,等待二维码加载。'
: `先选择${props.loginTypeLabel},再点击下方按钮开始初始化。`
}}
</p>
</div>
</div>
<div class="auth-copy">
<p class="scan-hint">
{{
props.hasSession ? props.scanInstruction : `已选择${props.loginTypeLabel},等待开始初始化`
}}
</p>
<p
v-if="props.qrCopyEnabled && props.qrImage && props.sessionStatus === 'waiting_scan'"
class="scan-tip"
>
双击二维码可直接复制到剪贴板,也可以使用下方按钮复制。
</p>
<p class="scan-note">
{{
props.sessionNotice ||
(props.hasSession
? `请使用${props.loginTypeLabel}扫码登录`
: `点击“${props.initButtonLabel}”后,页面才会真正创建浏览器会话`)
}}
</p>
</div>
<div class="auth-footer">
<div class="session-strip">
<span>当前会话</span>
<strong>{{ props.sessionId || '尚未创建' }}</strong>
</div>
<div class="action-row">
<el-button
:loading="props.sessionLoading"
class="primary-action"
size="large"
type="primary"
@click="emit('createSession', props.loginType)"
>
{{ props.hasSession ? '重新生成二维码' : props.initButtonLabel }}
</el-button>
<el-button
v-if="props.qrCopyEnabled"
:disabled="!props.qrImage || props.sessionStatus !== 'waiting_scan'"
:loading="props.sessionLoading"
class="copy-action"
size="large"
@click="emit('copyQr')"
>
{{ props.qrCopyButtonLabel || '复制二维码' }}
</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>
</template>
<style scoped>
.auth-card {
display: flex;
flex-direction: column;
height: 100%;
padding: 22px;
border-radius: 30px;
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(86, 108, 138, 0.1);
box-shadow: 0 22px 70px rgba(30, 49, 78, 0.08);
backdrop-filter: blur(18px);
}
.login-tabs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0;
padding: 0;
border-radius: 16px;
overflow: hidden;
border: 1px solid #dbe4ee;
background: #f8fafc;
}
.login-tab {
min-height: 60px;
padding: 0 16px;
border: none;
background: transparent;
color: #606f84;
cursor: pointer;
font-size: 17px;
font-weight: 700;
transition:
background-color 180ms ease,
color 180ms ease,
box-shadow 180ms ease;
}
.login-tab.active {
color: #ffffff;
background: linear-gradient(180deg, #6ba5f7 0%, #5b8ff0 100%);
box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.12);
}
.qr-stage {
margin-top: 18px;
padding: 18px 16px;
border-radius: 20px;
border: 1px solid rgba(86, 108, 138, 0.08);
background:
linear-gradient(180deg, rgba(245, 248, 252, 0.98), rgba(252, 253, 255, 0.96)),
radial-gradient(circle at top, rgba(101, 146, 233, 0.08), transparent 48%);
display: grid;
place-items: center;
}
.qr-figure {
display: block;
width: min(220px, 100%);
padding: 0;
border: none;
background: transparent;
cursor: zoom-in;
}
.qr-figure img {
display: block;
width: 100%;
height: auto;
border-radius: 16px;
border: 1px solid #d9e2ef;
box-shadow: 0 10px 24px rgba(31, 50, 79, 0.08);
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
image-rendering: pixelated;
}
.qr-empty {
width: min(220px, 100%);
min-height: 220px;
padding: 18px;
border-radius: 16px;
border: 1px dashed #d4ddeb;
background: rgba(247, 250, 254, 0.92);
display: grid;
place-items: center;
text-align: center;
}
.qr-empty strong {
font-size: 18px;
color: #27384f;
}
.qr-empty p {
margin: 10px 0 0;
color: #67778d;
font-size: 14px;
line-height: 1.6;
}
.status-state {
width: min(220px, 100%);
min-height: 220px;
padding: 18px;
border-radius: 16px;
display: grid;
place-items: center;
text-align: center;
}
.status-state strong {
font-size: 20px;
}
.status-state p {
margin: 10px 0 0;
font-size: 14px;
line-height: 1.7;
}
.confirm-state {
border: 1px solid #f9c74f;
background: linear-gradient(180deg, rgba(255, 247, 214, 0.95), rgba(255, 251, 235, 0.98));
}
.confirm-state strong {
color: #9a3412;
}
.confirm-state p {
color: #b45309;
}
.success-state {
border: 1px solid #86efac;
background: linear-gradient(180deg, rgba(220, 252, 231, 0.96), rgba(240, 253, 244, 0.98));
}
.success-state strong {
color: #166534;
}
.success-state p {
color: #15803d;
}
.auth-copy {
margin-top: 18px;
}
.scan-hint {
margin: 0;
font-size: 16px;
font-weight: 700;
color: #243852;
}
.scan-note {
margin: 8px 0 0;
color: #64748b;
line-height: 1.7;
}
.scan-tip {
margin: 8px 0 0;
color: #2859b8;
line-height: 1.6;
font-size: 13px;
}
.auth-footer {
margin-top: auto;
padding-top: 20px;
}
.session-strip {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
min-height: 46px;
padding: 0 16px;
border-radius: 16px;
background: #f6f9fd;
border: 1px solid #e2e9f2;
}
.session-strip span {
color: #64748b;
font-size: 13px;
}
.session-strip strong {
display: block;
min-width: 0;
flex: 1;
text-align: right;
color: #22354e;
font-size: 13px;
font-family: 'JetBrains Mono', 'SFMono-Regular', ui-monospace, monospace;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.action-row {
margin-top: 16px;
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,
.copy-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(.copy-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(.copy-action.el-button:hover),
.action-row :deep(.copy-action.el-button:focus-visible),
.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(.copy-action.el-button.is-disabled),
.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;
}
.login-tab {
font-size: 15px;
min-height: 54px;
}
.scan-hint {
font-size: 15px;
}
.secondary-actions {
grid-template-columns: 1fr;
}
.action-row :deep(.el-button) {
width: 100%;
}
}
</style>
@@ -1,288 +0,0 @@
<script setup lang="ts">
type FactItem = {
label: string
value: string
accent?: boolean
}
const props = defineProps<{
canRedeem: boolean
canConfirmRole?: boolean
confirmActionLabel?: string
confirmActionLoading?: boolean
confirmActionVisible?: boolean
confirmReadonly?: boolean
hideRedeemForm?: boolean
loginTypeLabel: string
maxAttempts: number
redeemBlockedReason: string
redeemButtonLabel: string
redeemCode: string
redeemLoading: boolean
roleConfirmed: boolean
roleFacts: FactItem[]
roleReady: boolean
statusLabel: string
}>()
const emit = defineEmits<{
confirmRole: []
redeem: []
'update:maxAttempts': [value: number]
'update:redeemCode': [value: string]
'update:roleConfirmed': [value: boolean]
}>()
</script>
<template>
<section class="panel-card">
<div class="section-head">
<strong>角色与兑换</strong>
</div>
<div class="inline-summary">
<div class="inline-summary-item">
<span>会话状态</span>
<strong>{{ props.statusLabel }}</strong>
</div>
<div class="inline-summary-item">
<span>登录方式</span>
<strong>{{ props.loginTypeLabel }}</strong>
</div>
</div>
<div class="fact-grid role-grid">
<article
v-for="fact in props.roleFacts"
:key="fact.label"
:class="['fact-card', { accent: fact.accent }]"
>
<span>{{ fact.label }}</span>
<strong>{{ fact.value }}</strong>
</article>
</div>
<div v-if="props.confirmActionVisible" class="warning-action-box">
<div class="warning-copy">
<strong>请先确认当前角色与大区准确无误</strong>
<p>确认后将按当前识别到的角色继续兑换选错角色可能导致发放错误</p>
</div>
<el-button
:disabled="!props.canConfirmRole"
:loading="props.confirmActionLoading"
class="warning-action-button"
round
size="large"
type="danger"
@click="emit('confirmRole')"
>
{{ props.confirmActionLabel || '确认当前角色' }}
</el-button>
</div>
<div v-else-if="props.confirmReadonly && !props.roleConfirmed" class="confirm-box">
当前商品需要客服复核完成扫码登录后请联系人工继续
</div>
<div v-else class="confirm-box">
<el-checkbox
:model-value="props.roleConfirmed"
:disabled="props.confirmReadonly || !props.roleReady"
@update:model-value="emit('update:roleConfirmed', Boolean($event))"
>
我已确认当前角色与大区无误
</el-checkbox>
</div>
<el-form v-if="!props.hideRedeemForm" label-position="top" class="redeem-form">
<el-form-item label="兑换码">
<el-input
:model-value="props.redeemCode"
placeholder="请输入兑换码"
@update:model-value="emit('update:redeemCode', String($event))"
/>
</el-form-item>
<div class="field-row compact">
<el-form-item label="验证码重试次数">
<el-input-number
:max="12"
:min="1"
:model-value="props.maxAttempts"
@update:model-value="emit('update:maxAttempts', Number($event || 1))"
/>
</el-form-item>
</div>
</el-form>
<div class="redeem-actions">
<el-button
:disabled="!props.canRedeem"
:loading="props.redeemLoading"
class="redeem-button"
round
size="large"
type="success"
@click="emit('redeem')"
>
{{ props.redeemButtonLabel }}
</el-button>
</div>
<p class="helper-copy">
{{ props.redeemBlockedReason || '当前状态允许直接发起兑换' }}
</p>
</section>
</template>
<style scoped>
.panel-card {
height: 100%;
padding: 22px;
border-radius: 30px;
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(86, 108, 138, 0.1);
box-shadow: 0 22px 70px rgba(30, 49, 78, 0.08);
backdrop-filter: blur(18px);
}
.section-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
}
.section-head strong {
display: block;
font-size: 16px;
color: #22354e;
}
.inline-summary {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.inline-summary-item {
padding: 12px 14px;
border-radius: 14px;
background: #f7f9fc;
}
.inline-summary-item span,
.fact-card span {
display: block;
font-size: 12px;
color: #708096;
letter-spacing: 0.02em;
}
.inline-summary-item strong,
.fact-card strong {
display: block;
margin-top: 6px;
color: #1f324a;
line-height: 1.5;
word-break: break-word;
}
.fact-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.role-grid {
margin-top: 12px;
}
.fact-card {
padding: 14px;
border-radius: 14px;
background: #f7f9fc;
}
.fact-card.accent {
background: linear-gradient(180deg, rgba(86, 143, 244, 0.14), rgba(86, 143, 244, 0.08));
}
.confirm-box {
margin-top: 14px;
padding: 12px 14px;
border-radius: 14px;
background: rgba(95, 150, 245, 0.08);
border: 1px solid rgba(95, 150, 245, 0.14);
}
.warning-action-box {
margin-top: 14px;
padding: 16px;
border-radius: 18px;
background: linear-gradient(180deg, rgba(254, 226, 226, 0.92), rgba(254, 242, 242, 0.96));
border: 1px solid rgba(248, 113, 113, 0.3);
display: grid;
gap: 12px;
}
.warning-copy strong {
display: block;
color: #991b1b;
font-size: 15px;
}
.warning-copy p {
margin: 6px 0 0;
color: #b91c1c;
line-height: 1.7;
font-size: 13px;
}
.warning-action-button {
min-width: 220px;
justify-self: start;
}
.redeem-form {
margin-top: 14px;
}
.field-row.compact {
display: grid;
grid-template-columns: 220px;
gap: 12px;
}
.redeem-actions {
margin-top: 16px;
display: grid;
gap: 10px;
}
.redeem-button {
min-width: 220px;
}
.helper-copy {
margin: 12px 0 0;
color: #64748b;
line-height: 1.7;
}
@media (max-width: 1140px) {
.inline-summary,
.fact-grid,
.role-grid,
.field-row.compact {
grid-template-columns: 1fr;
}
}
@media (max-width: 760px) {
.panel-card {
padding: 18px;
}
}
</style>
@@ -1,158 +0,0 @@
<script setup lang="ts">
type FactItem = {
label: string
value: string
}
const props = defineProps<{
resultFacts: FactItem[]
screenshotEmptyMessage: string
screenshotEmptyTitle: string
screenshotUrl: string
showScreenshot: boolean
}>()
const emit = defineEmits<{
openScreenshotPreview: []
}>()
</script>
<template>
<section class="result-card">
<div class="section-head">
<strong>兑换结果</strong>
</div>
<div class="result-layout">
<div class="fact-grid result-grid">
<article v-for="fact in props.resultFacts" :key="fact.label" class="fact-card">
<span>{{ fact.label }}</span>
<strong>{{ fact.value }}</strong>
</article>
</div>
<button
v-if="props.showScreenshot"
class="screenshot-figure"
type="button"
@click="emit('openScreenshotPreview')"
>
<img :src="props.screenshotUrl" alt="Tencent redeem screenshot" />
</button>
<div v-else class="empty-block">
<strong>{{ props.screenshotEmptyTitle }}</strong>
<p>{{ props.screenshotEmptyMessage }}</p>
</div>
</div>
</section>
</template>
<style scoped>
.result-card {
padding: 22px 22px 20px;
border-radius: 30px;
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(86, 108, 138, 0.1);
box-shadow: 0 22px 70px rgba(30, 49, 78, 0.08);
backdrop-filter: blur(18px);
}
.section-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
}
.section-head strong {
display: block;
font-size: 16px;
color: #22354e;
}
.result-layout {
display: grid;
gap: 14px;
}
.fact-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.fact-card {
padding: 14px;
border-radius: 14px;
background: #f7f9fc;
}
.fact-card span {
display: block;
font-size: 12px;
color: #708096;
letter-spacing: 0.02em;
}
.fact-card strong {
display: block;
margin-top: 6px;
color: #1f324a;
line-height: 1.5;
word-break: break-word;
}
.screenshot-figure {
display: block;
width: 100%;
padding: 0;
border: 1px solid #e2e9f2;
border-radius: 22px;
overflow: hidden;
background: #f4f8fc;
cursor: zoom-in;
}
.screenshot-figure img {
display: block;
width: 100%;
height: auto;
}
.empty-block {
margin-top: 14px;
min-height: 180px;
border-radius: 18px;
display: grid;
place-items: center;
text-align: center;
padding: 24px;
background: #f8fbff;
border: 1px dashed #d5dfec;
}
.empty-block strong {
color: #2c4058;
font-size: 16px;
}
.empty-block p {
margin: 8px 0 0;
color: #6b7a91;
line-height: 1.7;
}
@media (max-width: 1140px) {
.fact-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 760px) {
.result-card {
padding: 18px;
}
}
</style>