feat(i18n): 管理端与玩家端三语支持(中/英/马来语)
- 管理后台 adminT 文案库、结算与代理端页面、表单校验 - 玩家端 vue-i18n 补全首页/公告/串关与 ms 文案 - Element Plus ms 语言包与共享 locale 工具
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
import api from '../api';
|
||||
import emptyMatchesImg from '../assets/images/empty-matches.svg';
|
||||
import BannerCarousel from '../components/BannerCarousel.vue';
|
||||
@@ -49,7 +52,7 @@ function goMatch(id: string) {
|
||||
<div>
|
||||
<BannerCarousel :banners="displayBanners" />
|
||||
|
||||
<h2 class="section-title">热门赛事</h2>
|
||||
<h2 class="section-title">{{ t('home.hot_matches') }}</h2>
|
||||
<div v-for="match in home?.hotMatches || []" :key="match.id" class="card match-card" @click="goMatch(match.id)">
|
||||
<div class="match-teams">{{ match.homeTeamName }} vs {{ match.awayTeamName }}</div>
|
||||
<div class="match-time">{{ new Date(match.startTime).toLocaleString() }}</div>
|
||||
@@ -57,7 +60,7 @@ function goMatch(id: string) {
|
||||
|
||||
<div v-if="home && !home.hotMatches?.length" class="empty">
|
||||
<img :src="emptyMatchesImg" alt="" class="empty-icon" />
|
||||
<p>暂无赛事</p>
|
||||
<p>{{ t('home.no_matches') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,10 +3,13 @@ import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useAppLocale } from '../composables/useAppLocale';
|
||||
import LocaleSwitcher from '../components/LocaleSwitcher.vue';
|
||||
import RobotVerify from '../components/RobotVerify.vue';
|
||||
import loginBg from '../assets/images/h5bg.png';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { initFromUser } = useAppLocale();
|
||||
const auth = useAuthStore();
|
||||
const router = useRouter();
|
||||
const captchaRef = ref<InstanceType<typeof RobotVerify> | null>(null);
|
||||
@@ -25,6 +28,7 @@ async function submit() {
|
||||
error.value = '';
|
||||
try {
|
||||
await auth.login(username.value, password.value);
|
||||
initFromUser(auth.user?.locale);
|
||||
router.push('/');
|
||||
} catch (e: unknown) {
|
||||
error.value = (e as { response?: { data?: { error?: string } } })?.response?.data?.error || 'Login failed';
|
||||
@@ -36,6 +40,9 @@ async function submit() {
|
||||
|
||||
<template>
|
||||
<div class="login-page" :style="{ backgroundImage: `url(${loginBg})` }">
|
||||
<div class="login-lang">
|
||||
<LocaleSwitcher compact />
|
||||
</div>
|
||||
<form @submit.prevent="submit" class="login-form ps-gold-frame">
|
||||
<label>{{ t('auth.username') }}</label>
|
||||
<input v-model="username" class="ps-gold-input" required />
|
||||
@@ -51,7 +58,15 @@ async function submit() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-lang {
|
||||
position: absolute;
|
||||
top: max(12px, env(safe-area-inset-top));
|
||||
right: 16px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.login-page {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 100dvh;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -24,7 +24,8 @@ interface Market {
|
||||
id: string;
|
||||
marketType: string;
|
||||
period: string;
|
||||
lineValue?: string;
|
||||
lineValue?: string | number | null;
|
||||
allowParlay?: boolean;
|
||||
selections: Selection[];
|
||||
}
|
||||
|
||||
@@ -212,6 +213,11 @@ function toggleSelection(sel: Selection, market: Market) {
|
||||
selectionName: sel.selectionName,
|
||||
odds: parseFloat(sel.odds),
|
||||
marketType: market.marketType,
|
||||
lineValue:
|
||||
market.lineValue != null && market.lineValue !== ''
|
||||
? parseFloat(String(market.lineValue))
|
||||
: null,
|
||||
allowParlay: market.allowParlay,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,16 +6,12 @@ import api from '../api';
|
||||
import { formatMoney } from '../utils/localeDisplay';
|
||||
import LocaleFlag from '../components/LocaleFlag.vue';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useAppLocale } from '../composables/useAppLocale';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const router = useRouter();
|
||||
const auth = useAuthStore();
|
||||
|
||||
const locales = [
|
||||
{ code: 'zh-CN', label: '中文' },
|
||||
{ code: 'en-US', label: 'EN' },
|
||||
{ code: 'ms-MY', label: 'BM' },
|
||||
] as const;
|
||||
const { locales, setLocale, initFromUser } = useAppLocale();
|
||||
|
||||
const profile = ref<{
|
||||
username?: string;
|
||||
@@ -25,12 +21,11 @@ const profile = ref<{
|
||||
onMounted(async () => {
|
||||
const { data } = await api.get('/player/profile');
|
||||
profile.value = data.data;
|
||||
initFromUser(data.data?.locale);
|
||||
});
|
||||
|
||||
async function changeLocale(code: string) {
|
||||
locale.value = code;
|
||||
localStorage.setItem('locale', code);
|
||||
await api.post('/player/language', { locale: code });
|
||||
await setLocale(code);
|
||||
}
|
||||
|
||||
function logout() {
|
||||
@@ -79,6 +74,19 @@ function logout() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-cell settings-cell--stack rules-cell">
|
||||
<div class="cell-head">
|
||||
<span class="cell-label">{{ t('profile.rules_title') }}</span>
|
||||
</div>
|
||||
<div class="rules-body">
|
||||
<p>{{ t('profile.rules_p1') }}</p>
|
||||
<p>{{ t('profile.rules_p2') }}</p>
|
||||
<p>{{ t('profile.rules_p3') }}</p>
|
||||
<p>{{ t('profile.rules_p4') }}</p>
|
||||
<p>{{ t('profile.rules_p5') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button type="button" class="logout-btn" @click="logout">
|
||||
@@ -236,4 +244,19 @@ function logout() {
|
||||
.logout-btn:active {
|
||||
background: rgba(255, 69, 58, 0.08);
|
||||
}
|
||||
|
||||
.rules-body {
|
||||
padding: 0 0 12px;
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.rules-body p {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.rules-body p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user