feat: WC2026 赛事 seed、生产上线初始化脚本与目录归档
重构 seed 为 WC2026 72 场小组赛与 48 强优胜盘;新增 production 模式仅保留 admin 与赛事示例;提供 prod-init-db 全量重置脚本;管理端 i18n 分包与赛事归档能力。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import { useAppLocale } from '../composables/useAppLocale';
|
||||
import { useSmsCode } from '../composables/useSmsCode';
|
||||
import LocaleSwitcher from '../components/LocaleSwitcher.vue';
|
||||
import PhoneCountrySelect from '../components/PhoneCountrySelect.vue';
|
||||
import GoldSpinner from '../components/GoldSpinner.vue';
|
||||
import loginBg from '../assets/images/h5bg.webp';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
@@ -104,7 +105,11 @@ const fieldError = () => {
|
||||
<div class="login-lang">
|
||||
<LocaleSwitcher compact />
|
||||
</div>
|
||||
<form @submit.prevent="submit" class="login-form ps-gold-frame">
|
||||
<form @submit.prevent="submit" class="login-form ps-gold-frame" :class="{ '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>
|
||||
</div>
|
||||
<h2 class="form-title">{{ t('auth.register') }}</h2>
|
||||
|
||||
<div class="field">
|
||||
@@ -116,6 +121,7 @@ const fieldError = () => {
|
||||
autocomplete="username"
|
||||
maxlength="32"
|
||||
minlength="7"
|
||||
:disabled="loading"
|
||||
:placeholder="t('auth.username_register_placeholder')"
|
||||
/>
|
||||
</div>
|
||||
@@ -130,6 +136,7 @@ const fieldError = () => {
|
||||
type="tel"
|
||||
required
|
||||
autocomplete="tel-national"
|
||||
:disabled="loading"
|
||||
:placeholder="t('auth.phone_local_placeholder')"
|
||||
/>
|
||||
</div>
|
||||
@@ -144,40 +151,55 @@ const fieldError = () => {
|
||||
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') }}
|
||||
<span v-if="sending" class="btn-inline-loading">
|
||||
<GoldSpinner :size="14" :active="true" />
|
||||
{{ t('auth.sending_sms') }}
|
||||
</span>
|
||||
<span v-else>{{ countdown > 0 ? `${countdown}s` : t('auth.send_sms') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>{{ t('auth.password') }}</label>
|
||||
<input v-model="password" class="field-input" type="password" required autocomplete="new-password" minlength="8" />
|
||||
<input v-model="password" class="field-input" type="password" required autocomplete="new-password" minlength="8" :disabled="loading" />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>{{ t('auth.confirm_password') }}</label>
|
||||
<input v-model="confirmPassword" class="field-input" type="password" required autocomplete="new-password" minlength="8" />
|
||||
<input v-model="confirmPassword" class="field-input" type="password" required autocomplete="new-password" minlength="8" :disabled="loading" />
|
||||
</div>
|
||||
|
||||
<div class="field field-optional">
|
||||
<label>{{ t('auth.invite_code') }} <span class="optional-tag">{{ t('auth.optional') }}</span></label>
|
||||
<input v-model="inviteCode" class="field-input" autocomplete="off" />
|
||||
<input v-model="inviteCode" class="field-input" autocomplete="off" :disabled="loading" />
|
||||
</div>
|
||||
|
||||
<p v-if="fieldError()" class="error">{{ fieldError() }}</p>
|
||||
|
||||
<button type="submit" class="btn-login btn-gold-outline" :disabled="loading">
|
||||
{{ t('auth.register_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.registering') }}
|
||||
</span>
|
||||
<span v-else>{{ t('auth.register_btn') }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn-skip" @click="goLogin">
|
||||
<button type="button" class="btn-skip" :disabled="loading" @click="goLogin">
|
||||
{{ t('auth.have_account') }}
|
||||
</button>
|
||||
</form>
|
||||
@@ -212,6 +234,7 @@ const fieldError = () => {
|
||||
}
|
||||
|
||||
.login-form {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
display: flex;
|
||||
@@ -220,6 +243,40 @@ const fieldError = () => {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.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(8, 8, 8, 0.72);
|
||||
backdrop-filter: blur(2px);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.form-busy-text {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: rgba(240, 216, 117, 0.95);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.btn-inline-loading {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
margin: 0 0 2px;
|
||||
font-size: 16px;
|
||||
@@ -315,6 +372,10 @@ label {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-login.is-loading:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.btn-skip {
|
||||
margin-top: 2px;
|
||||
padding: 8px 14px;
|
||||
|
||||
Reference in New Issue
Block a user