feat(player): theme-2 PC auth layout and correct-score quick bet popover

This commit is contained in:
mars
2026-07-07 14:41:43 +08:00
parent 9be3f87b2b
commit 49594110a5
11 changed files with 583 additions and 403 deletions

View File

@@ -0,0 +1,169 @@
<script setup lang="ts">
/**
* PC auth shell — admin-style card: large logo left, form slot right (theme-2).
*/
import LocaleSwitcher from '../LocaleSwitcher.vue';
withDefaults(
defineProps<{
wide?: boolean;
}>(),
{ wide: false },
);
</script>
<template>
<div class="desktop-auth-page">
<div class="ambient-glow ambient-glow--1" aria-hidden="true"></div>
<div class="ambient-glow ambient-glow--2" aria-hidden="true"></div>
<div class="auth-wrap">
<div class="auth-card" :class="{ 'auth-card--wide': wide }">
<div class="form-lang">
<LocaleSwitcher compact />
</div>
<div class="form-body">
<aside class="form-brand">
<img src="/logo.png" alt="TheBet365" class="logo-large" />
</aside>
<div class="form-fields">
<slot />
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.desktop-auth-page {
position: relative;
min-height: 100dvh;
background: linear-gradient(160deg, #f5f7fa 0%, #e8edf2 55%, #f5f7fa 100%);
overflow: hidden;
}
.desktop-auth-page::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 70% 60% at 50% 20%, rgba(0, 102, 204, 0.08) 0%, transparent 70%);
pointer-events: none;
}
.ambient-glow {
position: absolute;
border-radius: 50%;
filter: blur(70px);
pointer-events: none;
opacity: 0.35;
z-index: 0;
}
.ambient-glow--1 {
width: 360px;
height: 360px;
top: 8%;
left: 10%;
background: radial-gradient(circle, rgba(0, 91, 159, 0.22) 0%, transparent 70%);
}
.ambient-glow--2 {
width: 320px;
height: 320px;
bottom: 10%;
right: 8%;
background: radial-gradient(circle, rgba(0, 102, 204, 0.16) 0%, transparent 70%);
}
.auth-wrap {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
min-height: 100dvh;
padding: 24px 20px;
overflow-y: auto;
}
.auth-card {
width: 100%;
max-width: 720px;
background: rgba(255, 255, 255, 0.96);
border: 1px solid var(--border);
border-radius: 12px;
box-shadow: var(--shadow-lg);
overflow: hidden;
}
.auth-card--wide {
max-width: 820px;
}
.form-lang {
display: flex;
justify-content: flex-end;
padding: 14px 18px 0;
}
.form-body {
display: grid;
grid-template-columns: minmax(200px, 260px) 1fr;
align-items: stretch;
}
.form-brand {
display: flex;
align-items: center;
justify-content: center;
padding: 28px 20px;
background: linear-gradient(180deg, rgba(0, 102, 204, 0.05) 0%, rgba(232, 237, 242, 0.65) 100%);
border-right: 1px solid var(--border);
}
.logo-large {
width: 100%;
max-width: 220px;
height: auto;
object-fit: contain;
filter: drop-shadow(0 6px 20px rgba(0, 91, 159, 0.18));
}
.form-fields {
display: flex;
flex-direction: column;
min-width: 0;
padding: 22px 24px 24px;
}
@media (max-width: 640px) {
.auth-wrap {
align-items: flex-start;
padding: 18px;
}
.auth-card,
.auth-card--wide {
max-width: 420px;
}
.form-body {
grid-template-columns: 1fr;
}
.form-brand {
padding: 20px 24px 12px;
border-right: none;
border-bottom: 1px solid var(--border);
}
.logo-large {
max-width: 180px;
}
.form-fields {
padding: 18px 20px 22px;
}
}
</style>

View File

@@ -2,6 +2,7 @@
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { groupCorrectScoreSelections, type CsSelection } from '../../utils/correctScoreLayout';
import { useDesktopBetPopover } from '../../composables/useDesktopBetPopover';
const props = defineProps<{
marketType: string;
@@ -24,6 +25,7 @@ const emit = defineEmits<{
}>();
const { t } = useI18n();
const { cancelClose } = useDesktopBetPopover();
const columns = computed(() =>
groupCorrectScoreSelections(
@@ -48,7 +50,11 @@ function formatOdds(odds: string) {
</script>
<template>
<div class="cs-panel" :class="{ 'cs-panel--locked': locked, 'cs-panel--dense': dense }">
<div
class="cs-panel"
:class="{ 'cs-panel--locked': locked, 'cs-panel--dense': dense }"
@mouseenter="cancelClose"
>
<div class="cols-head">
<span>{{ t('bet.col_home') }}</span>
<span>{{ t('bet.col_draw') }}</span>
@@ -65,6 +71,7 @@ function formatOdds(odds: string) {
:class="{ selected: isSelected(sel.id), 'score-card--locked': locked }"
:data-bet-selection-id="sel.id"
:disabled="locked"
@mouseenter="onPick(sel, $event)"
@click="onPick(sel, $event)"
>
<span class="score-line">{{ sel.scoreDisplay }}</span>

View File

@@ -164,6 +164,7 @@ export default {
},
auth:
{ login: 'Login',
logging_in: 'Logging in…',
register: 'Create Account',
logout: 'Log out',
username: 'Username',
@@ -218,6 +219,7 @@ export default {
forgot_password: 'Forgot password?',
forgot_password_title: 'Reset password',
reset_password_btn: 'Reset password',
resetting_password: 'Resetting…',
reset_success: 'Password reset. Please sign in with your new password. Contact your agent if you cannot log in.',
reset_failed: 'Reset failed. Please try again.',
phone_not_registered: 'This phone number is not registered',

View File

@@ -177,6 +177,7 @@ export default {
},
auth: {
login: 'Log Masuk',
logging_in: 'Sedang log masuk…',
register: 'Daftar Akaun',
logout: 'Log Keluar',
username: 'Nama Pengguna',
@@ -231,6 +232,7 @@ export default {
forgot_password: 'Lupa kata laluan?',
forgot_password_title: 'Tetapkan semula kata laluan',
reset_password_btn: 'Tetapkan semula',
resetting_password: 'Sedang tetapkan semula…',
reset_success: 'Kata laluan ditetapkan semula. Sila log masuk dengan kata laluan baharu. Hubungi ejen anda jika tidak dapat log masuk.',
reset_failed: 'Gagal menetapkan semula. Sila cuba lagi.',
phone_not_registered: 'Nombor telefon ini belum didaftarkan',

View File

@@ -164,6 +164,7 @@ export default {
},
auth: {
login: '登录',
logging_in: '登录中…',
register: '注册账号',
logout: '退出登录',
username: '账号',
@@ -218,6 +219,7 @@ export default {
forgot_password: '忘记密码?',
forgot_password_title: '重置密码',
reset_password_btn: '重置密码',
resetting_password: '重置中…',
reset_success: '密码已重置,请使用新密码登录。若无法登录请联系代理。',
reset_failed: '重置失败,请重试',
phone_not_registered: '该手机号未注册',

View File

@@ -7,8 +7,11 @@ import { useSmsCode } from '../composables/useSmsCode';
import api from '../api';
import LocaleSwitcher from '../components/LocaleSwitcher.vue';
import PhoneCountrySelect from '../components/PhoneCountrySelect.vue';
import GoldSpinner from '../components/GoldSpinner.vue';
import loginBg from '../assets/images/zt2.webp';
const props = withDefaults(defineProps<{ desktop?: boolean }>(), { desktop: false });
const { t, locale } = useI18n();
const router = useRouter();
const route = useRoute();
@@ -98,12 +101,16 @@ function goLogin() {
</script>
<template>
<div class="login-page" :style="{ backgroundImage: `url(${loginBg})` }">
<div class="login-lang">
<div class="login-page" :class="{ 'login-page--desktop': props.desktop }" :style="props.desktop ? undefined : { backgroundImage: `url(${loginBg})` }">
<div v-if="!props.desktop" class="login-lang">
<LocaleSwitcher compact />
</div>
<div class="login-stack">
<form @submit.prevent="submit" class="login-form ps-gold-frame">
<form @submit.prevent="submit" class="login-form" :class="{ 'ps-gold-frame': !props.desktop, 'is-busy': loading }">
<div v-if="loading" class="form-busy-overlay" aria-hidden="true">
<GoldSpinner :size="40" :active="true" />
<p class="form-busy-text">{{ t('auth.resetting_password') }}</p>
</div>
<h2 class="form-title">{{ t('auth.forgot_password_title') }}</h2>
<div class="field">
@@ -116,6 +123,7 @@ function goLogin() {
type="tel"
required
autocomplete="tel-national"
:disabled="loading"
:placeholder="t('auth.phone_local_placeholder')"
/>
</div>
@@ -130,12 +138,13 @@ function goLogin() {
inputmode="numeric"
maxlength="6"
autocomplete="one-time-code"
:disabled="loading"
:placeholder="t('auth.sms_code_placeholder')"
/>
<button
type="button"
class="btn-secondary"
:disabled="sending || countdown > 0 || !phone.trim()"
:disabled="sending || countdown > 0 || !phone.trim() || loading"
@click="sendCode"
>
{{ countdown > 0 ? `${countdown}s` : t('auth.send_sms') }}
@@ -152,6 +161,7 @@ function goLogin() {
required
autocomplete="new-password"
minlength="8"
:disabled="loading"
:placeholder="t('auth.password_placeholder')"
/>
</div>
@@ -165,15 +175,26 @@ function goLogin() {
required
autocomplete="new-password"
minlength="8"
:disabled="loading"
/>
</div>
<p v-if="fieldError()" class="error">{{ fieldError() }}</p>
<button type="submit" class="btn-login btn-gold-outline" :disabled="loading">
{{ t('auth.reset_password_btn') }}
<button
type="submit"
class="btn-login btn-gold-outline"
:class="{ 'is-loading': loading }"
:disabled="loading"
:aria-busy="loading"
>
<span v-if="loading" class="btn-inline-loading">
<GoldSpinner :size="18" :active="true" />
{{ t('auth.resetting_password') }}
</span>
<span v-else>{{ t('auth.reset_password_btn') }}</span>
</button>
<button type="button" class="btn-skip" @click="goLogin">
<button type="button" class="btn-skip" :disabled="loading" @click="goLogin">
{{ t('auth.back_to_login') }}
</button>
</form>
@@ -205,6 +226,22 @@ function goLogin() {
background-repeat: no-repeat;
}
.login-page--desktop {
min-height: auto;
height: auto;
width: 100%;
align-items: stretch;
justify-content: flex-start;
padding: 0;
background-color: transparent;
overflow: visible;
}
.login-page--desktop .login-stack {
max-width: none;
width: 100%;
}
.login-stack {
position: relative;
z-index: 1;
@@ -213,6 +250,7 @@ function goLogin() {
}
.login-form {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
@@ -220,6 +258,48 @@ function goLogin() {
padding: 14px;
}
.login-form.is-busy {
pointer-events: none;
}
.form-busy-overlay {
position: absolute;
inset: 0;
z-index: 3;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
border-radius: inherit;
background: rgba(255, 255, 255, 0.88);
backdrop-filter: blur(4px);
pointer-events: all;
}
.form-busy-text {
margin: 0;
font-size: 13px;
font-weight: 700;
color: var(--primary);
letter-spacing: 0.04em;
}
.btn-inline-loading {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.login-page--desktop .login-form {
gap: 10px;
padding: 0;
border: none !important;
background: transparent !important;
box-shadow: none !important;
}
.form-title {
margin: 0 0 2px;
font-size: 16px;
@@ -228,6 +308,56 @@ function goLogin() {
text-align: center;
}
.login-page--desktop .form-title {
margin: 0 0 8px;
font-size: 18px;
text-align: left;
color: var(--primary);
}
.login-page--desktop .field {
gap: 8px;
}
.login-page--desktop label {
font-size: 13px;
}
.login-page--desktop .field-input,
.login-page--desktop .btn-secondary {
height: 46px;
}
.login-page--desktop .btn-login {
margin-top: 10px;
min-height: 50px;
font-size: 15px;
}
.login-page--desktop .btn-skip {
margin-top: 6px;
padding: 12px 16px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--bg-card);
}
.login-page--desktop .btn-skip:hover:not(:disabled) {
border-color: var(--border-active);
background: var(--surface-accent);
}
.login-page--desktop .error {
padding: 6px 10px;
border-radius: var(--radius-sm);
background: rgba(220, 38, 38, 0.08);
border-left: 2px solid var(--danger);
}
.btn-login.is-loading:disabled {
opacity: 1;
}
.field {
display: flex;
flex-direction: column;

View File

@@ -8,8 +8,11 @@ import { useAppLocale } from '../composables/useAppLocale';
import LocaleSwitcher from '../components/LocaleSwitcher.vue';
import PhoneCountrySelect from '../components/PhoneCountrySelect.vue';
import RobotVerify from '../components/RobotVerify.vue';
import GoldSpinner from '../components/GoldSpinner.vue';
import loginBg from '../assets/images/zt2.webp';
const props = withDefaults(defineProps<{ desktop?: boolean }>(), { desktop: false });
type LoginMode = 'account' | 'phone';
const { t, locale } = useI18n();
@@ -85,17 +88,27 @@ function goRegister() {
</script>
<template>
<div class="login-page" :style="{ backgroundImage: `url(${loginBg})` }">
<div class="login-lang">
<div class="login-page" :class="{ 'login-page--desktop': props.desktop }" :style="props.desktop ? undefined : { backgroundImage: `url(${loginBg})` }">
<div v-if="!props.desktop" class="login-lang">
<LocaleSwitcher compact />
</div>
<div class="login-stack">
<form @submit.prevent="submit" class="login-form ps-gold-frame">
<form
@submit.prevent="submit"
class="login-form"
:class="{ 'ps-gold-frame': !props.desktop, 'is-busy': loading }"
>
<div v-if="loading" class="form-busy-overlay" aria-hidden="true">
<GoldSpinner :size="40" :active="true" />
<p class="form-busy-text">{{ t('auth.logging_in') }}</p>
</div>
<h2 v-if="props.desktop" class="form-title">{{ t('auth.login') }}</h2>
<div class="auth-tabs">
<button
type="button"
class="auth-tab"
:class="{ active: loginMode === 'account' }"
:disabled="loading"
@click="switchLoginMode('account')"
>
{{ t('auth.login_by_account') }}
@@ -104,6 +117,7 @@ function goRegister() {
type="button"
class="auth-tab"
:class="{ active: loginMode === 'phone' }"
:disabled="loading"
@click="switchLoginMode('phone')"
>
{{ t('auth.login_by_phone') }}
@@ -118,6 +132,7 @@ function goRegister() {
required
autocomplete="username"
maxlength="32"
:disabled="loading"
:placeholder="t('auth.username_placeholder')"
/>
</div>
@@ -132,6 +147,7 @@ function goRegister() {
type="tel"
required
autocomplete="tel-national"
:disabled="loading"
:placeholder="t('auth.phone_local_placeholder')"
/>
</div>
@@ -139,25 +155,38 @@ function goRegister() {
<div class="field">
<label>{{ t('auth.password') }}</label>
<input v-model="password" class="field-input ps-gold-input" type="password" required autocomplete="current-password" />
<input v-model="password" class="field-input ps-gold-input" type="password" required autocomplete="current-password" :disabled="loading" />
</div>
<button type="button" class="btn-forgot" @click="goForgotPassword">
<button type="button" class="btn-forgot" :disabled="loading" @click="goForgotPassword">
{{ t('auth.forgot_password') }}
</button>
<RobotVerify ref="captchaRef" />
<p v-if="resetSuccess" class="success">{{ t('auth.reset_success') }}</p>
<p v-if="error" class="error">{{ error }}</p>
<button type="submit" class="btn-login btn-gold-outline" :disabled="loading">
{{ t('auth.login') }}
<button
type="submit"
class="btn-login btn-gold-outline"
:class="{ 'is-loading': loading }"
:disabled="loading"
:aria-busy="loading"
>
<span v-if="loading" class="btn-inline-loading">
<GoldSpinner :size="18" :active="true" />
{{ t('auth.logging_in') }}
</span>
<span v-else>{{ t('auth.login') }}</span>
</button>
<button v-if="props.desktop" type="button" class="btn-skip" :disabled="loading" @click="goRegister">
{{ t('auth.go_register') }}
</button>
</form>
<div class="login-actions">
<button type="button" class="btn-skip" @click="goRegister">
<div v-if="!props.desktop" class="login-actions">
<button type="button" class="btn-skip" :disabled="loading" @click="goRegister">
{{ t('auth.go_register') }}
</button>
<button type="button" class="btn-skip-light" @click="continueBrowsing">
<button type="button" class="btn-skip-light" :disabled="loading" @click="continueBrowsing">
{{ t('auth.continue_browsing') }}
</button>
</div>
@@ -189,6 +218,22 @@ function goRegister() {
background-repeat: no-repeat;
}
.login-page--desktop {
min-height: auto;
height: auto;
width: 100%;
align-items: stretch;
justify-content: flex-start;
padding: 0;
background-color: transparent;
overflow: visible;
}
.login-page--desktop .login-stack {
max-width: none;
width: 100%;
}
.login-stack {
position: relative;
z-index: 1;
@@ -200,6 +245,7 @@ function goRegister() {
}
.login-form {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
@@ -207,6 +253,118 @@ function goRegister() {
padding: 14px;
}
.login-form.is-busy {
pointer-events: none;
}
.form-busy-overlay {
position: absolute;
inset: 0;
z-index: 3;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
border-radius: inherit;
background: rgba(255, 255, 255, 0.88);
backdrop-filter: blur(4px);
pointer-events: all;
}
.form-busy-text {
margin: 0;
font-size: 13px;
font-weight: 700;
color: var(--primary);
letter-spacing: 0.04em;
}
.btn-inline-loading {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.login-page--desktop .login-form {
gap: 10px;
padding: 0;
border: none !important;
background: transparent !important;
box-shadow: none !important;
}
.form-title {
margin: 0 0 8px;
font-size: 18px;
font-weight: 800;
color: var(--primary);
text-align: left;
letter-spacing: 0.02em;
}
.login-page--desktop .field {
gap: 8px;
}
.login-page--desktop label {
font-size: 13px;
}
.login-page--desktop .field-input {
height: 46px;
padding: 0 14px;
}
.login-page--desktop .auth-tabs {
margin-bottom: 8px;
}
.login-page--desktop .btn-login {
margin-top: 10px;
min-height: 50px;
font-size: 15px;
}
.login-page--desktop .btn-skip {
margin-top: 6px;
padding: 12px 16px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--bg-card);
}
.login-page--desktop .btn-skip:hover:not(:disabled) {
border-color: var(--border-active);
background: var(--surface-accent);
}
.login-page--desktop .error {
padding: 6px 10px;
border-radius: var(--radius-sm);
background: rgba(220, 38, 38, 0.08);
border-left: 2px solid var(--danger);
}
.login-page--desktop .success {
padding: 6px 10px;
border-radius: var(--radius-sm);
background: rgba(5, 150, 105, 0.08);
border-left: 2px solid var(--success);
}
.btn-login.is-loading:disabled {
opacity: 1;
}
.auth-tab:disabled,
.btn-forgot:disabled,
.btn-skip:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.login-actions {
display: flex;
flex-direction: column;

View File

@@ -11,6 +11,8 @@ import PhoneCountrySelect from '../components/PhoneCountrySelect.vue';
import GoldSpinner from '../components/GoldSpinner.vue';
import loginBg from '../assets/images/zt2.webp';
const props = withDefaults(defineProps<{ desktop?: boolean }>(), { desktop: false });
const { t, locale } = useI18n();
const { initFromUser } = useAppLocale();
const auth = useAuthStore();
@@ -101,12 +103,16 @@ const fieldError = () => {
</script>
<template>
<div class="login-page" :style="{ backgroundImage: `url(${loginBg})` }">
<div class="login-lang">
<div class="login-page" :class="{ 'login-page--desktop': props.desktop }" :style="props.desktop ? undefined : { backgroundImage: `url(${loginBg})` }">
<div v-if="!props.desktop" class="login-lang">
<LocaleSwitcher compact />
</div>
<div class="login-stack">
<form @submit.prevent="submit" class="login-form ps-gold-frame" :class="{ 'is-busy': loading }">
<form
@submit.prevent="submit"
class="login-form"
:class="{ 'ps-gold-frame': !props.desktop, 'is-busy': loading }"
>
<div v-if="loading" class="form-busy-overlay" aria-hidden="true">
<GoldSpinner :size="40" :active="true" />
<p class="form-busy-text">{{ t('auth.registering') }}</p>
@@ -200,8 +206,11 @@ const fieldError = () => {
</span>
<span v-else>{{ t('auth.register_btn') }}</span>
</button>
<button v-if="props.desktop" type="button" class="btn-skip" :disabled="loading" @click="goLogin">
{{ t('auth.have_account') }}
</button>
</form>
<div class="login-actions">
<div v-if="!props.desktop" class="login-actions">
<button type="button" class="btn-skip" :disabled="loading" @click="goLogin">
{{ t('auth.have_account') }}
</button>
@@ -237,6 +246,22 @@ const fieldError = () => {
background-repeat: no-repeat;
}
.login-page--desktop {
min-height: auto;
height: auto;
width: 100%;
align-items: stretch;
justify-content: flex-start;
padding: 0;
background-color: transparent;
overflow: visible;
}
.login-page--desktop .login-stack {
max-width: none;
width: 100%;
}
.login-stack {
position: relative;
z-index: 1;
@@ -298,6 +323,60 @@ const fieldError = () => {
gap: 8px;
}
.login-page--desktop .login-form {
gap: 10px;
padding: 0;
border: none !important;
background: transparent !important;
box-shadow: none !important;
}
.login-page--desktop .form-title {
margin: 0 0 8px;
font-size: 18px;
text-align: left;
color: var(--primary);
}
.login-page--desktop .field {
gap: 8px;
}
.login-page--desktop label {
font-size: 13px;
}
.login-page--desktop .field-input,
.login-page--desktop .btn-secondary {
height: 46px;
}
.login-page--desktop .btn-login {
margin-top: 10px;
min-height: 50px;
font-size: 15px;
}
.login-page--desktop .btn-skip {
margin-top: 6px;
padding: 12px 16px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--bg-card);
}
.login-page--desktop .btn-skip:hover:not(:disabled) {
border-color: var(--border-active);
background: var(--surface-accent);
}
.login-page--desktop .error {
padding: 6px 10px;
border-radius: var(--radius-sm);
background: rgba(220, 38, 38, 0.08);
border-left: 2px solid var(--danger);
}
.form-title {
margin: 0 0 2px;
font-size: 16px;

View File

@@ -1,121 +1,10 @@
<script setup lang="ts">
/**
* Desktop Forgot Password View
*/
import { RouterLink } from 'vue-router';
import { useI18n } from 'vue-i18n';
import DesktopAuthLayout from '../../components/desktop/DesktopAuthLayout.vue';
import ForgotPasswordView from '../ForgotPasswordView.vue';
const { t } = useI18n();
</script>
<template>
<div class="desktop-auth-shell">
<RouterLink to="/login" class="auth-back-link">{{ t('common.back') }}</RouterLink>
<div class="auth-brand">
<div class="brand-inner">
<img src="/logo.png" alt="TheBet365" class="brand-logo" />
<h1 class="brand-name">THEBET365</h1>
<p class="brand-tagline">重置您的登录密码</p>
</div>
</div>
<div class="auth-card-wrap">
<div class="auth-card">
<ForgotPasswordView />
</div>
</div>
</div>
<DesktopAuthLayout wide>
<ForgotPasswordView desktop />
</DesktopAuthLayout>
</template>
<style scoped>
.desktop-auth-shell {
display: flex;
min-height: 100vh;
background: var(--desktop-bg);
position: relative;
}
.auth-back-link {
position: absolute;
top: 20px;
left: 24px;
z-index: 2;
color: var(--text-muted);
font-size: 14px;
font-weight: 700;
text-decoration: none;
transition: color 0.2s;
}
.auth-back-link:hover {
color: var(--text);
}
.auth-brand {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--bg-card) 0%, var(--bg-card) 50%, var(--bg-card) 100%);
border-right: 1px solid var(--desktop-border);
position: relative;
overflow: hidden;
}
.auth-brand::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 60% 60% at 50% 40%, rgba(0, 102, 204,0.06) 0%, transparent 70%);
pointer-events: none;
}
.brand-inner {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
text-align: center;
position: relative;
z-index: 1;
padding: 40px;
}
.brand-logo {
width: 64px;
height: 64px;
object-fit: contain;
filter: drop-shadow(0 4px 16px rgba(0, 102, 204,0.3));
}
.brand-name {
font-size: 28px;
font-weight: 900;
letter-spacing: 0.12em;
color: var(--primary-light);
margin: 0;
}
.brand-tagline {
font-size: 14px;
color: var(--text-muted);
margin: 0;
font-weight: 600;
}
.auth-card-wrap {
width: 480px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
background: var(--desktop-bg);
overflow-y: auto;
}
.auth-card {
width: 100%;
max-width: 380px;
}
</style>

View File

@@ -1,156 +1,10 @@
<script setup lang="ts">
/**
* Desktop Login View
* Reuses the existing LoginView form in a centered two-column auth layout.
*/
import { RouterLink } from 'vue-router';
import { useI18n } from 'vue-i18n';
import DesktopAuthLayout from '../../components/desktop/DesktopAuthLayout.vue';
import LoginView from '../LoginView.vue';
const { t } = useI18n();
</script>
<template>
<div class="desktop-auth-shell">
<RouterLink to="/" class="auth-back-link">{{ t('common.back') }}</RouterLink>
<div class="auth-brand">
<div class="brand-inner">
<img src="/logo.png" alt="TheBet365" class="brand-logo" />
<h1 class="brand-name">THEBET365</h1>
<p class="brand-tagline">专业体育赛事投注平台</p>
<div class="brand-features">
<div class="feature-item">
<span class="feature-icon"></span>
<span>实时赔率更新</span>
</div>
<div class="feature-item">
<span class="feature-icon">🔒</span>
<span>安全稳定平台</span>
</div>
<div class="feature-item">
<span class="feature-icon">💰</span>
<span>快速充值提款</span>
</div>
</div>
</div>
</div>
<div class="auth-card-wrap">
<div class="auth-card">
<LoginView />
</div>
</div>
</div>
<DesktopAuthLayout>
<LoginView desktop />
</DesktopAuthLayout>
</template>
<style scoped>
.desktop-auth-shell {
display: flex;
min-height: 100vh;
background: var(--desktop-bg);
position: relative;
}
.auth-back-link {
position: absolute;
top: 20px;
left: 24px;
z-index: 2;
color: var(--text-muted);
font-size: 14px;
font-weight: 700;
text-decoration: none;
transition: color 0.2s;
}
.auth-back-link:hover {
color: var(--text);
}
.auth-brand {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--bg-card) 0%, var(--bg-card) 50%, var(--bg-card) 100%);
border-right: 1px solid var(--desktop-border);
position: relative;
overflow: hidden;
}
.auth-brand::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 60% 60% at 50% 40%, rgba(0, 102, 204,0.06) 0%, transparent 70%);
pointer-events: none;
}
.brand-inner {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
text-align: center;
position: relative;
z-index: 1;
padding: 40px;
}
.brand-logo {
width: 64px;
height: 64px;
object-fit: contain;
filter: drop-shadow(0 4px 16px rgba(0, 102, 204,0.3));
}
.brand-name {
font-size: 28px;
font-weight: 900;
letter-spacing: 0.12em;
color: var(--primary-light);
margin: 0;
text-shadow: 0 2px 12px rgba(0, 102, 204,0.25);
}
.brand-tagline {
font-size: 14px;
color: var(--text-muted);
margin: 0;
font-weight: 600;
}
.brand-features {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 24px;
}
.feature-item {
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
color: var(--text-muted);
font-weight: 600;
}
.feature-icon {
font-size: 16px;
}
.auth-card-wrap {
width: 480px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
background: var(--desktop-bg);
}
.auth-card {
width: 100%;
max-width: 380px;
}
</style>

View File

@@ -1,122 +1,10 @@
<script setup lang="ts">
/**
* Desktop Register View
* Reuses the existing RegisterView form in a centered desktop auth layout.
*/
import { RouterLink } from 'vue-router';
import { useI18n } from 'vue-i18n';
import DesktopAuthLayout from '../../components/desktop/DesktopAuthLayout.vue';
import RegisterView from '../RegisterView.vue';
const { t } = useI18n();
</script>
<template>
<div class="desktop-auth-shell">
<RouterLink to="/" class="auth-back-link">{{ t('common.back') }}</RouterLink>
<div class="auth-brand">
<div class="brand-inner">
<img src="/logo.png" alt="TheBet365" class="brand-logo" />
<h1 class="brand-name">THEBET365</h1>
<p class="brand-tagline">加入我们开始精彩体育投注</p>
</div>
</div>
<div class="auth-card-wrap">
<div class="auth-card">
<RegisterView />
</div>
</div>
</div>
<DesktopAuthLayout wide>
<RegisterView desktop />
</DesktopAuthLayout>
</template>
<style scoped>
.desktop-auth-shell {
display: flex;
min-height: 100vh;
background: var(--desktop-bg);
position: relative;
}
.auth-back-link {
position: absolute;
top: 20px;
left: 24px;
z-index: 2;
color: var(--text-muted);
font-size: 14px;
font-weight: 700;
text-decoration: none;
transition: color 0.2s;
}
.auth-back-link:hover {
color: var(--text);
}
.auth-brand {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--bg-card) 0%, var(--bg-card) 50%, var(--bg-card) 100%);
border-right: 1px solid var(--desktop-border);
position: relative;
overflow: hidden;
}
.auth-brand::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 60% 60% at 50% 40%, rgba(0, 102, 204,0.06) 0%, transparent 70%);
pointer-events: none;
}
.brand-inner {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
text-align: center;
position: relative;
z-index: 1;
padding: 40px;
}
.brand-logo {
width: 64px;
height: 64px;
object-fit: contain;
filter: drop-shadow(0 4px 16px rgba(0, 102, 204,0.3));
}
.brand-name {
font-size: 28px;
font-weight: 900;
letter-spacing: 0.12em;
color: var(--primary-light);
margin: 0;
}
.brand-tagline {
font-size: 14px;
color: var(--text-muted);
margin: 0;
font-weight: 600;
}
.auth-card-wrap {
width: 520px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
background: var(--desktop-bg);
overflow-y: auto;
}
.auth-card {
width: 100%;
max-width: 420px;
}
</style>