feat(admin,player,api): 玩家账号密码管理与代理上下分
新增玩家头像、可查密码与全局改密/改账号开关;玩家资料页合并账号密码展示;代理直属玩家列表支持自定义上下分。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,14 +4,34 @@ import { useI18n } from 'vue-i18n';
|
||||
import emptyMatchesImg from '../assets/images/empty-matches.svg';
|
||||
import BannerCarousel from '../components/BannerCarousel.vue';
|
||||
import { usePlayerHome } from '../composables/usePlayerHome';
|
||||
import { teamFlagUrl } from '../utils/teamFlag';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t, locale } = useI18n();
|
||||
const router = useRouter();
|
||||
const { banners, hotMatches, loading } = usePlayerHome();
|
||||
|
||||
function goMatch(id: string) {
|
||||
router.push(`/match/${id}`);
|
||||
}
|
||||
|
||||
function formatKickoff(startTime: string) {
|
||||
return new Date(startTime).toLocaleString(locale.value, {
|
||||
year: 'numeric',
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
}
|
||||
|
||||
function homeFlag(match: (typeof hotMatches.value)[number]) {
|
||||
return teamFlagUrl(match.homeTeamCode, match.homeTeamName);
|
||||
}
|
||||
|
||||
function awayFlag(match: (typeof hotMatches.value)[number]) {
|
||||
return teamFlagUrl(match.awayTeamCode, match.awayTeamName);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -19,9 +39,23 @@ function goMatch(id: string) {
|
||||
<BannerCarousel :banners="banners" />
|
||||
|
||||
<h2 class="section-title">{{ t('home.hot_matches') }}</h2>
|
||||
<div v-for="match in 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>
|
||||
<div
|
||||
v-for="match in hotMatches"
|
||||
:key="match.id"
|
||||
class="card match-card"
|
||||
@click="goMatch(match.id)"
|
||||
>
|
||||
<div class="match-info">
|
||||
<div class="match-teams">{{ match.homeTeamName }} vs {{ match.awayTeamName }}</div>
|
||||
<div class="match-time">{{ formatKickoff(match.startTime) }}</div>
|
||||
</div>
|
||||
<div class="match-flags" aria-hidden="true">
|
||||
<img v-if="homeFlag(match)" :src="homeFlag(match)" alt="" class="flag" />
|
||||
<span v-else class="flag-ph">⚽</span>
|
||||
<span class="vs">VS</span>
|
||||
<img v-if="awayFlag(match)" :src="awayFlag(match)" alt="" class="flag" />
|
||||
<span v-else class="flag-ph">⚽</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!loading && !hotMatches.length" class="empty">
|
||||
@@ -32,10 +66,83 @@ function goMatch(id: string) {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.match-card { cursor: pointer; transition: border-color 0.2s, box-shadow 0.2s; }
|
||||
.match-card:active { border-color: var(--border-gold-soft); }
|
||||
.match-teams { font-weight: 800; margin-bottom: 8px; font-size: 16px; }
|
||||
.match-time { font-size: 13px; color: var(--text-muted); font-weight: 500; }
|
||||
.empty { text-align: center; color: var(--text-muted); padding: 40px 20px; font-weight: 600; }
|
||||
.empty-icon { width: 96px; height: 96px; margin-bottom: 14px; }
|
||||
.match-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.match-card:active {
|
||||
border-color: var(--border-gold-soft);
|
||||
}
|
||||
|
||||
.match-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.match-teams {
|
||||
font-weight: 800;
|
||||
margin-bottom: 8px;
|
||||
font-size: 16px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.match-time {
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.match-flags {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 8px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.flag {
|
||||
width: 32px;
|
||||
height: 22px;
|
||||
object-fit: cover;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.flag-ph {
|
||||
width: 32px;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.vs {
|
||||
font-size: 11px;
|
||||
font-weight: 900;
|
||||
color: var(--primary-light);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
padding: 40px 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user