feat(player): 注册账号、登录双模式与移动端性能优化

注册必填 7-32 位账号,手机号区号/本地号分存;登录默认账号模式并支持切换手机号登录;Player i18n 拆包与赛事接口优化。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-12 10:56:51 +08:00
parent 83f0f380c5
commit 312c3c5816
35 changed files with 1944 additions and 1394 deletions

View File

@@ -2,23 +2,36 @@
import { ref } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { defaultPhoneIsoForLocale, getPhoneDialFromIso } from '@thebet365/shared';
import { useAuthStore } from '../stores/auth';
import { useAppLocale } from '../composables/useAppLocale';
import LocaleSwitcher from '../components/LocaleSwitcher.vue';
import PhoneCountrySelect from '../components/PhoneCountrySelect.vue';
import RobotVerify from '../components/RobotVerify.vue';
import loginBg from '../assets/images/h5bg.png';
const { t } = useI18n();
type LoginMode = 'account' | 'phone';
const { t, locale } = useI18n();
const { syncLocaleToBackend } = useAppLocale();
const auth = useAuthStore();
const router = useRouter();
const route = useRoute();
const captchaRef = ref<InstanceType<typeof RobotVerify> | null>(null);
const username = ref('');
const loginMode = ref<LoginMode>('account');
const account = ref('');
const phone = ref('');
const countryIso = ref(defaultPhoneIsoForLocale(locale.value));
const password = ref('');
const error = ref('');
const loading = ref(false);
function switchLoginMode(mode: LoginMode) {
if (loginMode.value === mode) return;
loginMode.value = mode;
error.value = '';
}
async function submit() {
if (!captchaRef.value?.validate()) {
error.value = t('auth.captcha_wrong');
@@ -28,7 +41,10 @@ async function submit() {
loading.value = true;
error.value = '';
try {
await auth.login(username.value, password.value);
const isPhone = loginMode.value === 'phone';
const loginId = isPhone ? phone.value : account.value;
const countryCode = isPhone ? getPhoneDialFromIso(countryIso.value) : undefined;
await auth.login(loginId, password.value, countryCode);
await syncLocaleToBackend();
const redirectTo = (route.query.redirect as string) || '/';
router.push(redirectTo);
@@ -47,7 +63,6 @@ function isGuestBrowsablePath(path: string): boolean {
function continueBrowsing() {
const redirect = typeof route.query.redirect === 'string' ? route.query.redirect : '';
// redirect 是登录成功后的目标;跳过登录时只能去公开页,避免跳回需登录页形成死循环
const target = isGuestBrowsablePath(redirect) ? redirect || '/' : '/';
router.replace(target);
}
@@ -66,20 +81,42 @@ function goRegister() {
<LocaleSwitcher compact />
</div>
<form @submit.prevent="submit" class="login-form ps-gold-frame">
<div class="field">
<div v-if="loginMode === 'account'" class="field">
<label>{{ t('auth.username') }}</label>
<input
v-model="username"
v-model="account"
class="field-input"
required
autocomplete="username"
:placeholder="t('auth.login_username_placeholder')"
maxlength="32"
:placeholder="t('auth.username_placeholder')"
/>
</div>
<div v-else class="field">
<label>{{ t('auth.phone') }}</label>
<div class="inline-row">
<PhoneCountrySelect v-model="countryIso" />
<input
v-model="phone"
class="field-input"
type="tel"
required
autocomplete="tel-national"
:placeholder="t('auth.phone_local_placeholder')"
/>
</div>
</div>
<div class="field">
<label>{{ t('auth.password') }}</label>
<input v-model="password" class="field-input" type="password" required />
<input v-model="password" class="field-input" type="password" required autocomplete="current-password" />
</div>
<button type="button" class="btn-mode-switch" @click="switchLoginMode(loginMode === 'account' ? 'phone' : 'account')">
{{ loginMode === 'account' ? t('auth.login_by_phone') : t('auth.login_by_account') }}
</button>
<RobotVerify ref="captchaRef" />
<p v-if="error" class="error">{{ error }}</p>
<button type="submit" class="btn-login btn-gold-outline" :disabled="loading">
@@ -169,6 +206,22 @@ label {
letter-spacing: 0.04em;
}
.btn-mode-switch {
align-self: flex-start;
margin: -2px 0 0;
padding: 0;
border: none;
background: transparent;
color: rgba(240, 216, 117, 0.75);
font-size: 12px;
font-weight: 600;
cursor: pointer;
}
.btn-mode-switch:active {
color: rgba(240, 216, 117, 0.95);
}
.btn-login {
margin-top: 4px;
padding: 10px 14px;