feat: multi-tier agent hierarchy, wallet ledger, and player UX polish

Add configurable agent max level and default sub-agent credit ratio, per-agent block direct player login on suspend, admin/agent wallet transaction views, and match detail my-bets section with refreshed player card styling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-10 16:15:34 +08:00
parent 641c92a5f5
commit ef6b15f119
39 changed files with 2398 additions and 410 deletions

View File

@@ -2,8 +2,7 @@
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { teamFlagUrl } from '../utils/teamFlag';
import StatusWatermark from './StatusWatermark.vue';
import { matchPhaseLabel, matchPhaseVariant, type MatchPhase } from '../utils/matchPhase';
import { matchPhaseLabel, type MatchPhase } from '../utils/matchPhase';
const props = defineProps<{
match: {
@@ -76,12 +75,9 @@ const liveScoreText = computed(() => {
:class="{ 'match-card--phase': phase !== 'open' }"
@click="emit('bet', match.id)"
>
<StatusWatermark
v-if="phase !== 'open'"
:label="phaseLabel"
:variant="matchPhaseVariant(phase)"
size="sm"
/>
<span v-if="phase === 'open'" class="status-tag status-tag--open">待开赛</span>
<span v-else-if="phase === 'settled'" class="status-tag status-tag--settled">{{ phaseLabel }}</span>
<span v-else class="status-tag status-tag--pending">{{ phaseLabel }}</span>
<div class="teams-row">
<div class="team">
@@ -127,8 +123,8 @@ const liveScoreText = computed(() => {
<style scoped>
.match-card {
position: relative;
background: #0d0d0d;
border: 1px solid rgba(255, 215, 0, 0.25);
background: linear-gradient(180deg, #1a1a1a 0%, #0d0d0d 100%);
border: 1px solid rgba(140, 140, 140, 0.35);
border-radius: 6px;
padding: 10px 8px 10px;
display: flex;
@@ -140,8 +136,17 @@ const liveScoreText = computed(() => {
cursor: pointer;
}
.match-card::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(100, 100, 100, 0.08) 0%, transparent 50%, rgba(80, 80, 80, 0.05) 100%);
pointer-events: none;
z-index: 0;
}
.match-card--phase {
border-color: rgba(140, 140, 140, 0.35);
/* inherits base dark style */
}
.teams-row {
@@ -170,7 +175,7 @@ const liveScoreText = computed(() => {
justify-content: center;
font-size: 17px;
font-weight: 900;
color: var(--primary-light);
color: #fff;
line-height: 1.2;
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.95);
word-break: break-word;
@@ -210,7 +215,7 @@ const liveScoreText = computed(() => {
.kickoff {
font-size: 13px;
font-weight: 700;
color: #b0a060;
color: #fff;
line-height: 1;
white-space: nowrap;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
@@ -233,6 +238,40 @@ const liveScoreText = computed(() => {
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
}
.status-tag {
position: absolute;
top: 0;
right: 0;
z-index: 3;
font-size: 10px;
font-weight: 800;
padding: 3px 10px;
border-radius: 0 6px 0 8px;
line-height: 1.3;
white-space: nowrap;
letter-spacing: 0.02em;
}
.status-tag--open {
background: linear-gradient(135deg, #e8c84a, #d4a017);
color: #1a1a1a;
box-shadow: 0 2px 6px rgba(212, 160, 23, 0.4);
}
.status-tag--settled {
background: linear-gradient(180deg, #2a2a3a 0%, #1a1a28 100%);
color: #8a9ab8;
border-bottom: 1px solid rgba(120, 140, 180, 0.3);
border-left: 1px solid rgba(120, 140, 180, 0.3);
}
.status-tag--pending {
background: linear-gradient(180deg, #3a3a2a 0%, #28251a 100%);
color: #c8a84e;
border-bottom: 1px solid rgba(200, 168, 78, 0.3);
border-left: 1px solid rgba(200, 168, 78, 0.3);
}
.bet-btn {
position: relative;
z-index: 2;