diff --git a/apps/admin/src/App.vue b/apps/admin/src/App.vue index 086c21a..9a78bba 100644 --- a/apps/admin/src/App.vue +++ b/apps/admin/src/App.vue @@ -418,7 +418,7 @@ body { -webkit-text-fill-color: #fff !important; } -.el-button { background: #141414 !important; border-color: #2a2a2a !important; color: #aaa !important; } +.el-button { background: #141414 !important; border-color: #2a2a2a !important; color: #aaa !important; transition: background 0.15s, border-color 0.15s, color 0.15s, box-shadow 0.15s !important; } .el-button:hover { background: #1e1e1e !important; border-color: #3a3a3a !important; color: #fff !important; } .el-button--primary { background: var(--primary-grad) !important; @@ -435,18 +435,104 @@ body { box-shadow: 0 1px 0 rgba(255, 255, 255, 0.18) inset, 0 4px 14px rgba(0, 0, 0, 0.5), 0 0 24px rgba(47, 181, 106, 0.28) !important; } .el-button--success { - background: var(--green-surface) !important; - border: 1px solid var(--green-border) !important; - color: var(--green-text) !important; - backdrop-filter: blur(6px); + background: linear-gradient(165deg, #42b86e 0%, #248f54 52%, #1a6b40 100%) !important; + border: 1px solid rgba(77, 214, 138, 0.35) !important; + color: #ffffff !important; + font-weight: 700 !important; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.12) inset, 0 2px 8px rgba(0, 0, 0, 0.35) !important; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); } .el-button--success:hover { - background: rgba(36, 143, 84, 0.35) !important; + background: linear-gradient(165deg, #52cc7e 0%, #2ea864 52%, #1f7a48 100%) !important; border-color: rgba(120, 230, 170, 0.45) !important; - color: #d4fde5 !important; + color: #ffffff !important; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.16) inset, 0 4px 12px rgba(0, 0, 0, 0.4), 0 0 18px rgba(47, 181, 106, 0.22) !important; +} +.el-button--warning { + background: linear-gradient(165deg, #e8a820 0%, #c48412 52%, #9a6508 100%) !important; + border: 1px solid rgba(251, 191, 36, 0.4) !important; + color: #ffffff !important; + font-weight: 700 !important; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.1) inset, 0 2px 8px rgba(0, 0, 0, 0.35) !important; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); +} +.el-button--warning:hover { + background: linear-gradient(165deg, #f0b830 0%, #d49218 52%, #aa720a 100%) !important; + border-color: rgba(251, 191, 36, 0.55) !important; + color: #ffffff !important; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.14) inset, 0 4px 12px rgba(0, 0, 0, 0.4), 0 0 16px rgba(251, 191, 36, 0.18) !important; } -.el-button--warning { background: rgba(251,191,36,0.1) !important; border-color: rgba(251,191,36,0.35) !important; color: #fbbf24 !important; } .el-button--danger { background: rgba(255,69,58,0.1) !important; border-color: rgba(255,69,58,0.35) !important; color: #ff453a !important; } + +/* ── Disabled: muted ghost, clearly non-interactive ── */ +.el-button.is-disabled, +.el-button.is-disabled:hover, +.el-button.is-disabled:focus, +.el-button:disabled { + cursor: not-allowed !important; + pointer-events: none; + transform: none !important; + filter: none; + box-shadow: none !important; + text-shadow: none !important; + opacity: 1 !important; +} + +.el-button.is-disabled, +.el-button.is-disabled:hover, +.el-button:disabled { + background: rgba(255, 255, 255, 0.04) !important; + border-color: rgba(255, 255, 255, 0.08) !important; + color: rgba(255, 255, 255, 0.28) !important; +} + +.el-button--primary.is-disabled, +.el-button--primary.is-disabled:hover, +.el-button--primary:disabled { + background: rgba(36, 143, 84, 0.1) !important; + border-color: rgba(36, 143, 84, 0.14) !important; + color: rgba(154, 232, 188, 0.32) !important; +} + +.el-button--primary.is-plain.is-disabled, +.el-button--primary.is-plain.is-disabled:hover, +.el-button--primary.is-plain:disabled { + background: rgba(36, 143, 84, 0.06) !important; + border-color: rgba(36, 143, 84, 0.12) !important; + color: rgba(154, 232, 188, 0.28) !important; +} + +.el-button--success.is-disabled, +.el-button--success.is-disabled:hover, +.el-button--success:disabled { + background: rgba(36, 143, 84, 0.08) !important; + border-color: rgba(36, 143, 84, 0.12) !important; + color: rgba(154, 232, 188, 0.28) !important; +} + +.el-button--warning.is-disabled, +.el-button--warning.is-disabled:hover, +.el-button--warning:disabled { + background: rgba(196, 132, 18, 0.1) !important; + border-color: rgba(196, 132, 18, 0.14) !important; + color: rgba(251, 191, 36, 0.28) !important; +} + +.el-button--danger.is-plain.is-disabled, +.el-button--danger.is-plain.is-disabled:hover, +.el-button--danger.is-plain:disabled { + background: rgba(255, 69, 58, 0.06) !important; + border-color: rgba(255, 69, 58, 0.1) !important; + color: rgba(255, 107, 98, 0.28) !important; +} + +.el-button--danger.is-disabled, +.el-button--danger.is-disabled:hover, +.el-button--danger:disabled { + background: rgba(255, 69, 58, 0.06) !important; + border-color: rgba(255, 69, 58, 0.1) !important; + color: rgba(255, 107, 98, 0.28) !important; +} .el-button--primary.is-plain { background: rgba(36, 143, 84, 0.12) !important; border-color: var(--green-border) !important; @@ -459,15 +545,16 @@ body { color: #d4fde5 !important; } .el-button--danger.is-plain { - background: rgba(255, 69, 58, 0.08) !important; - border-color: rgba(255, 69, 58, 0.35) !important; + background: rgba(255, 69, 58, 0.14) !important; + border-color: rgba(255, 69, 58, 0.45) !important; color: #ff6b62 !important; + font-weight: 600 !important; box-shadow: none !important; } .el-button--danger.is-plain:hover { - background: rgba(255, 69, 58, 0.16) !important; - border-color: rgba(255, 120, 110, 0.5) !important; - color: #ff8a82 !important; + background: rgba(255, 69, 58, 0.24) !important; + border-color: rgba(255, 120, 110, 0.55) !important; + color: #ff9a92 !important; } .el-button.is-text, .el-button.is-link.el-button--default { @@ -481,26 +568,6 @@ body { color: #d4fde5 !important; background: rgba(36, 143, 84, 0.1) !important; } -.el-button--primary.is-plain { - background: rgba(36, 143, 84, 0.12) !important; - border-color: var(--green-border) !important; - color: var(--green-text) !important; -} -.el-button--primary.is-plain:hover { - background: rgba(36, 143, 84, 0.22) !important; - border-color: rgba(120, 230, 170, 0.45) !important; - color: #d4fde5 !important; -} -.el-button--danger.is-plain { - background: rgba(255, 69, 58, 0.08) !important; - border-color: rgba(255, 69, 58, 0.35) !important; - color: #ff6961 !important; -} -.el-button--danger.is-plain:hover { - background: rgba(255, 69, 58, 0.18) !important; - border-color: rgba(255, 120, 110, 0.5) !important; - color: #ff8a82 !important; -} .el-tag { border-radius: 4px !important; font-size: 11px !important; font-weight: 600 !important; } .el-tag--success { diff --git a/apps/admin/src/components/DashboardSubNav.vue b/apps/admin/src/components/DashboardSubNav.vue new file mode 100644 index 0000000..d9f42c9 --- /dev/null +++ b/apps/admin/src/components/DashboardSubNav.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/apps/admin/src/components/LogoUrlField.vue b/apps/admin/src/components/LogoUrlField.vue index 4aa3ab6..7cddae2 100644 --- a/apps/admin/src/components/LogoUrlField.vue +++ b/apps/admin/src/components/LogoUrlField.vue @@ -1,5 +1,6 @@ + + + @@ -406,9 +446,37 @@ function isLeagueExpanded(id: string) { 0 - + + + + +
@@ -425,7 +493,7 @@ function isLeagueExpanded(id: string) {
- + @@ -437,14 +505,19 @@ function isLeagueExpanded(id: string) { - + -

{{ t('match.hint.create_league') }}

+

{{ t('match.hint.create_league') }}

@@ -452,7 +525,7 @@ function isLeagueExpanded(id: string) { @@ -476,22 +549,56 @@ function isLeagueExpanded(id: string) { style="width: 100%" /> - - - - - - +
+ +
+
{{ t('match.field.home_team') }}
+ + + + + + + + + + + + + + + +
+ +
+
{{ t('match.field.away_team') }}
+ + + + + + + + + + + + + + + +
+
@@ -502,37 +609,35 @@ function isLeagueExpanded(id: string) { {{ t('user.btn.create') }}
- - -

{{ t('match.import_hint') }}

- - -
diff --git a/apps/admin/src/views/dashboard/DashboardPlayers.vue b/apps/admin/src/views/dashboard/DashboardPlayers.vue new file mode 100644 index 0000000..b8c465b --- /dev/null +++ b/apps/admin/src/views/dashboard/DashboardPlayers.vue @@ -0,0 +1,191 @@ + + + + + diff --git a/apps/admin/src/views/dashboard/dashboard-board.css b/apps/admin/src/views/dashboard/dashboard-board.css new file mode 100644 index 0000000..8f1d30a --- /dev/null +++ b/apps/admin/src/views/dashboard/dashboard-board.css @@ -0,0 +1,179 @@ +.dashboard-page { + padding-bottom: 32px; +} + +.state-card { + border-radius: 14px; + border: 1px solid #2a2220; + background: rgba(255, 69, 58, 0.06); + text-align: center; + padding: 8px 0 4px; +} + +.state-title { + font-size: 15px; + font-weight: 700; + color: #ff8a80; + margin-bottom: 6px; +} + +.state-hint { + font-size: 13px; + color: #888; + margin-bottom: 14px; +} + +.overview-board { + border-radius: 14px; + border: 1px solid #1e1e1e; + background: linear-gradient(180deg, rgba(36, 143, 84, 0.06) 0%, rgba(0, 0, 0, 0) 120px); + margin-bottom: 28px; +} + +.overview-board :deep(.el-card__body) { + padding: 20px 22px 16px; +} + +.board-head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + margin: -4px 0 14px; + flex-wrap: wrap; +} + +.board-hint { + font-size: 12px; + color: #666; +} + +.dash-updated { + font-size: 11px; + color: #555; + letter-spacing: 0.02em; +} + +.kpi-grid { + display: grid; + gap: 10px; + margin-bottom: 10px; +} + +.kpi-primary { + grid-template-columns: repeat(4, 1fr); +} + +.kpi-secondary { + grid-template-columns: repeat(4, 1fr); + margin-bottom: 18px; +} + +.kpi-cell { + padding: 12px 14px; + border-radius: 10px; + border: 1px solid #222; + background: rgba(255, 255, 255, 0.03); +} + +.kpi-cell.compact { + padding: 10px 12px; +} + +.kpi-cell--link { + cursor: pointer; + transition: border-color 0.15s, background 0.15s; +} + +.kpi-cell--link:hover, +.kpi-cell--link:focus-visible { + border-color: rgba(77, 214, 138, 0.35); + background: rgba(36, 143, 84, 0.1); + outline: none; +} + +.kpi-label { + display: block; + font-size: 11px; + color: #666; + margin-bottom: 6px; +} + +.kpi-value { + display: block; + font-size: 22px; + font-weight: 800; + color: var(--green-text); + line-height: 1.15; + letter-spacing: -0.5px; +} + +.kpi-value.sm { + font-size: 17px; +} + +.kpi-sub { + display: block; + font-size: 11px; + color: #555; + margin-top: 4px; +} + +.kpi-delta { + display: inline-block; + margin-top: 6px; + font-size: 10px; + font-weight: 600; + color: #888; + padding: 2px 6px; + border-radius: 4px; + background: rgba(255, 255, 255, 0.04); +} + +.kpi-delta.up { + color: #4ade80; +} + +.kpi-delta.down { + color: #f87171; +} + +.charts-stack { + border-top: 1px solid #1a1a1a; + padding-top: 12px; +} + +.chart-main-caption { + font-size: 11px; + color: #555; + text-align: center; + margin: -8px 0 8px; +} + +.charts-stack :deep(.chart-panel) { + border: none; + background: transparent; + padding: 8px 0 0; +} + +.charts-stack :deep(.chart-title:empty) { + display: none; +} + +.chart-dist { + margin-top: 4px; +} + +@media (max-width: 1200px) { + .kpi-primary, + .kpi-secondary { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 640px) { + .kpi-primary, + .kpi-secondary { + grid-template-columns: 1fr; + } +} diff --git a/apps/admin/src/views/match-form.ts b/apps/admin/src/views/match-form.ts index 921aa29..89d2aa4 100644 --- a/apps/admin/src/views/match-form.ts +++ b/apps/admin/src/views/match-form.ts @@ -83,6 +83,8 @@ export type AdminMatchDetail = { isHot: boolean; displayOrder: number; startTime: string; + leagueId?: string; + leagueCode?: string; leagueEn: string; leagueZh: string; leagueMs: string; @@ -127,7 +129,7 @@ export function normalizeStartTimeForApi(value: string): string { export function formFromDetail(d: AdminMatchDetail): MatchCreateForm { return { - leagueId: '', + leagueId: d.leagueId ?? '', leagueEn: d.leagueEn, leagueZh: d.leagueZh, leagueMs: d.leagueMs ?? '', @@ -246,3 +248,47 @@ export function buildPlatformPayload(form: MatchCreateForm) { awayTeamLogoUrl: form.awayTeamLogoUrl.trim() || undefined, }; } + +/** 编辑单场基本信息(不含联赛字段,联赛在赛事列表单独维护) */ +export function buildMatchUpdatePayload(form: MatchCreateForm) { + if (!form.startTime.trim()) { + throw new FormValidationError('err.kickoff_required'); + } + const homeCode = form.homeTeamCode.trim().toUpperCase(); + const awayCode = form.awayTeamCode.trim().toUpperCase(); + if (homeCode && awayCode) { + if (homeCode === awayCode) { + throw new FormValidationError('err.teams_same'); + } + } else if (homeCode || awayCode) { + throw new FormValidationError('err.team_country_required'); + } else { + const homeOk = form.homeTeamZh.trim() || form.homeTeamEn.trim() || form.homeTeamMs.trim(); + const awayOk = form.awayTeamZh.trim() || form.awayTeamEn.trim() || form.awayTeamMs.trim(); + if (!homeOk || !awayOk) { + throw new FormValidationError('err.team_country_required'); + } + const homeKey = `${form.homeTeamZh.trim()}|${form.homeTeamEn.trim()}|${form.homeTeamMs.trim()}`.toLowerCase(); + const awayKey = `${form.awayTeamZh.trim()}|${form.awayTeamEn.trim()}|${form.awayTeamMs.trim()}`.toLowerCase(); + if (homeKey === awayKey) { + throw new FormValidationError('err.teams_same'); + } + } + + return { + homeTeamEn: form.homeTeamEn.trim(), + homeTeamZh: form.homeTeamZh.trim(), + homeTeamMs: form.homeTeamMs.trim() || undefined, + awayTeamEn: form.awayTeamEn.trim(), + awayTeamZh: form.awayTeamZh.trim(), + awayTeamMs: form.awayTeamMs.trim() || undefined, + startTime: normalizeStartTimeForApi(form.startTime), + isHot: form.isHot, + displayOrder: form.displayOrder, + matchName: form.matchName.trim() || undefined, + stage: form.stage.trim() || undefined, + groupName: form.groupName.trim() || undefined, + homeTeamLogoUrl: form.homeTeamLogoUrl.trim() || undefined, + awayTeamLogoUrl: form.awayTeamLogoUrl.trim() || undefined, + }; +} diff --git a/apps/admin/src/views/matches/LeagueMatchesPanel.vue b/apps/admin/src/views/matches/LeagueMatchesPanel.vue index a996f5c..296a72f 100644 --- a/apps/admin/src/views/matches/LeagueMatchesPanel.vue +++ b/apps/admin/src/views/matches/LeagueMatchesPanel.vue @@ -193,11 +193,6 @@ defineExpose({ reload: load });
- + +