feat(player): 完善 H5 投注端与 API 演示数据
- 球赛/串关/优胜冠军、赛事详情、历史投注与个人资料编辑 - 固定顶栏、公告与底栏,仅内容区滚动 - 底部导航与站点 favicon 使用 logo,登录页精简 - API 种子、冠军盘与历史注单增强 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,23 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useRouter, RouterLink } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import api from '../api';
|
||||
import { formatMoney } from '../utils/localeDisplay';
|
||||
import LocaleFlag from '../components/LocaleFlag.vue';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const auth = useAuthStore();
|
||||
const router = useRouter();
|
||||
const profile = ref<{ wallet?: { availableBalance: string; frozenBalance: string } } | null>(null);
|
||||
const transactions = ref<unknown[]>([]);
|
||||
const auth = useAuthStore();
|
||||
|
||||
const locales = [
|
||||
{ code: 'zh-CN', label: '中文' },
|
||||
{ code: 'en-US', label: 'EN' },
|
||||
{ code: 'ms-MY', label: 'BM' },
|
||||
] as const;
|
||||
|
||||
const profile = ref<{
|
||||
username?: string;
|
||||
wallet?: { availableBalance: string; frozenBalance: string };
|
||||
} | null>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
const [prof, txns] = await Promise.all([
|
||||
api.get('/player/profile'),
|
||||
api.get('/player/wallet/transactions'),
|
||||
]);
|
||||
profile.value = prof.data.data;
|
||||
transactions.value = txns.data.data.items;
|
||||
const { data } = await api.get('/player/profile');
|
||||
profile.value = data.data;
|
||||
});
|
||||
|
||||
async function changeLocale(code: string) {
|
||||
@@ -33,54 +40,200 @@ function logout() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="card profile-card">
|
||||
<div class="username">{{ auth.user?.username }}</div>
|
||||
<div class="profile-page">
|
||||
<div class="summary-card">
|
||||
<p class="username">{{ profile?.username }}</p>
|
||||
<div class="balance-row">
|
||||
<span>{{ t('wallet.balance') }}</span>
|
||||
<span class="amount">{{ profile?.wallet?.availableBalance ?? '0' }}</span>
|
||||
<span class="balance-label">
|
||||
<LocaleFlag :locale="locale" :size="16" />
|
||||
{{ t('wallet.balance') }}
|
||||
</span>
|
||||
<span class="amount">{{ formatMoney(profile?.wallet?.availableBalance, locale) }}</span>
|
||||
</div>
|
||||
<div class="frozen">冻结: {{ profile?.wallet?.frozenBalance ?? '0' }}</div>
|
||||
<p class="frozen">
|
||||
{{ t('wallet.unsettled') }}: {{ formatMoney(profile?.wallet?.frozenBalance, locale) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>语言</h3>
|
||||
<div class="lang-btns">
|
||||
<button @click="changeLocale('zh-CN')" :class="{ active: locale === 'zh-CN' }">中文</button>
|
||||
<button @click="changeLocale('en-US')" :class="{ active: locale === 'en-US' }">English</button>
|
||||
<button @click="changeLocale('ms-MY')" :class="{ active: locale === 'ms-MY' }">BM</button>
|
||||
</div>
|
||||
</div>
|
||||
<section class="settings-group">
|
||||
<RouterLink to="/profile/edit" class="settings-cell">
|
||||
<span class="cell-label">{{ t('profile.edit') }}</span>
|
||||
<span class="cell-chevron" aria-hidden="true">›</span>
|
||||
</RouterLink>
|
||||
|
||||
<div class="card">
|
||||
<h3>账变记录</h3>
|
||||
<div v-for="tx in transactions as Array<{ transactionType: string; amount: string; createdAt: string }>" :key="(tx as { transactionId?: string }).transactionId" class="tx-row">
|
||||
<span>{{ tx.transactionType }}</span>
|
||||
<span :class="parseFloat(tx.amount) >= 0 ? 'pos' : 'neg'">{{ tx.amount }}</span>
|
||||
<span class="tx-time">{{ new Date(tx.createdAt).toLocaleString() }}</span>
|
||||
<div class="settings-cell settings-cell--stack">
|
||||
<div class="cell-head">
|
||||
<span class="cell-label">{{ t('profile.language') }}</span>
|
||||
</div>
|
||||
<div class="lang-segment" role="group" :aria-label="t('profile.language')">
|
||||
<button
|
||||
v-for="item in locales"
|
||||
:key="item.code"
|
||||
type="button"
|
||||
class="lang-opt"
|
||||
:class="{ active: locale === item.code }"
|
||||
@click="changeLocale(item.code)"
|
||||
>
|
||||
<LocaleFlag :locale="item.code" :size="14" />
|
||||
<span>{{ item.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button class="btn-logout" @click="logout">退出登录</button>
|
||||
<button type="button" class="logout-btn" @click="logout">
|
||||
{{ t('auth.logout') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.profile-card { margin-bottom: 12px; }
|
||||
.username { font-size: 18px; font-weight: 600; margin-bottom: 12px; }
|
||||
.balance-row { display: flex; justify-content: space-between; font-size: 16px; }
|
||||
.amount { color: #ffd700; font-weight: 700; }
|
||||
.frozen { font-size: 12px; color: var(--text-muted); margin-top: 4px; }
|
||||
h3 { font-size: 14px; margin-bottom: 12px; }
|
||||
.lang-btns { display: flex; gap: 8px; }
|
||||
.lang-btns button {
|
||||
flex: 1; padding: 8px; background: var(--bg-hover); color: #fff;
|
||||
border-radius: 6px; border: 1px solid var(--border);
|
||||
.profile-page {
|
||||
padding: 8px 0 12px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
color: var(--primary-light);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.balance-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.balance-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.amount {
|
||||
color: var(--primary-light);
|
||||
font-weight: 800;
|
||||
font-size: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.frozen {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.settings-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
padding: 0 16px;
|
||||
background: none;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.settings-cell:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.settings-cell:active {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.settings-cell--stack {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 12px 16px 14px;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.cell-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cell-label {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.cell-chevron {
|
||||
color: var(--text-muted);
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.lang-segment {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.lang-opt {
|
||||
flex: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
min-height: 34px;
|
||||
padding: 0 6px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border);
|
||||
background: #0a0a0a;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.lang-opt.active {
|
||||
border-color: var(--border-gold-soft);
|
||||
color: var(--primary-light);
|
||||
background: rgba(212, 175, 55, 0.1);
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
min-height: 44px;
|
||||
padding: 0 16px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg-card);
|
||||
color: var(--danger);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.logout-btn:active {
|
||||
background: rgba(255, 69, 58, 0.08);
|
||||
}
|
||||
.lang-btns button.active { border-color: var(--primary); color: var(--primary); }
|
||||
.tx-row { display: flex; justify-content: space-between; font-size: 13px; padding: 8px 0; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
|
||||
.pos { color: var(--primary); }
|
||||
.neg { color: var(--danger); }
|
||||
.tx-time { width: 100%; font-size: 11px; color: var(--text-muted); }
|
||||
.btn-logout { width: 100%; margin-top: 16px; padding: 12px; background: var(--bg-card); color: var(--danger); border-radius: 6px; border: 1px solid var(--border); }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user