初始化足球投注平台 MVP Monorepo

包含 NestJS 后端、三端前端、Prisma 数据模型、结算引擎测试与 PRD 文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-02 14:35:48 +08:00
commit 14e49374ac
118 changed files with 15944 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useAuthStore } from '../stores/auth';
import api from '../api';
const { t, locale } = useI18n();
const auth = useAuthStore();
const router = useRouter();
const profile = ref<{ wallet?: { availableBalance: string; frozenBalance: string } } | null>(null);
const transactions = ref<unknown[]>([]);
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;
});
async function changeLocale(code: string) {
locale.value = code;
localStorage.setItem('locale', code);
await api.post('/player/language', { locale: code });
}
function logout() {
auth.logout();
router.push('/login');
}
</script>
<template>
<div>
<div class="card profile-card">
<div class="username">{{ auth.user?.username }}</div>
<div class="balance-row">
<span>{{ t('wallet.balance') }}</span>
<span class="amount">{{ profile?.wallet?.availableBalance ?? '0' }}</span>
</div>
<div class="frozen">冻结: {{ profile?.wallet?.frozenBalance ?? '0' }}</div>
</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>
<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>
</div>
<button class="btn-logout" @click="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);
}
.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>