diff --git a/server/app/api/controller/MatchController.php b/server/app/api/controller/MatchController.php index 3be854c..e9373fd 100644 --- a/server/app/api/controller/MatchController.php +++ b/server/app/api/controller/MatchController.php @@ -96,7 +96,7 @@ class MatchController extends BaseApiController $endedPageSize = $this->request->get('ended_page_size/d', 20); $endedOnly = $this->request->get('ended_only/d', 0); - $field = 'id,league_name,league_icon,home_team,home_icon,home_score,away_team,away_icon,away_score,sport_type,status,match_time,current_minute,half_score,home_odds,draw_odds,away_odds,home_corner,away_corner,home_yellow,away_yellow,home_red,away_red,is_hot,round_name'; + $field = 'id,competition_id,league_name,league_icon,home_team,home_icon,home_score,away_team,away_icon,away_score,sport_type,status,match_time,current_minute,half_score,home_odds,draw_odds,away_odds,home_corner,away_corner,home_yellow,away_yellow,home_red,away_red,is_hot,round_name'; // 基础筛选 $baseWhere = [['is_show', '=', 1]]; @@ -121,6 +121,7 @@ class MatchController extends BaseApiController // 加载更多时只返回已结束数据 if ($endedOnly) { + $ended = $this->attachMoneylineOdds($ended); return $this->data([ 'ended' => [ 'lists' => $ended, @@ -139,6 +140,13 @@ class MatchController extends BaseApiController $upcoming = MatchEvent::where($baseWhere)->where('status', 0) ->field($field)->order('match_time asc')->limit(20)->select()->toArray(); + $liveCount = count($live); + $upcomingCount = count($upcoming); + $matches = $this->attachMoneylineOdds(array_merge($live, $upcoming, $ended)); + $live = array_slice($matches, 0, $liveCount); + $upcoming = array_slice($matches, $liveCount, $upcomingCount); + $ended = array_slice($matches, $liveCount + $upcomingCount); + return $this->data([ 'live' => $live, 'upcoming' => $upcoming, @@ -151,6 +159,106 @@ class MatchController extends BaseApiController ]); } + private function attachMoneylineOdds(array $matches): array + { + if (empty($matches)) { + return []; + } + + $homeTeams = []; + $awayTeams = []; + $matchKeys = []; + foreach ($matches as $index => &$match) { + $match['home_odds'] = $this->normalizeMoneylineOdds($match['home_odds'] ?? ''); + $match['draw_odds'] = $this->normalizeMoneylineOdds($match['draw_odds'] ?? ''); + $match['away_odds'] = $this->normalizeMoneylineOdds($match['away_odds'] ?? ''); + + if ((int)($match['sport_type'] ?? 0) !== 1) { + continue; + } + $key = $this->buildEventOddsMatchKey($match); + if ($key === '') { + continue; + } + $matchKeys[$index] = $key; + $homeTeams[] = trim((string)($match['home_team'] ?? '')); + $awayTeams[] = trim((string)($match['away_team'] ?? '')); + } + unset($match); + + $homeTeams = array_values(array_unique(array_filter($homeTeams))); + $awayTeams = array_values(array_unique(array_filter($awayTeams))); + if (empty($matchKeys) || empty($homeTeams) || empty($awayTeams)) { + return $matches; + } + + $rows = MatchOdds::where('sport_type', 'FT') + ->whereIn('home_team', $homeTeams) + ->whereIn('away_team', $awayTeams) + ->where(function ($query) { + $query->where('home_win_odds', '<>', '') + ->whereOr('draw_odds', '<>', '') + ->whereOr('away_win_odds', '<>', ''); + }) + ->field('home_team,away_team,match_time_text,home_win_odds,draw_odds,away_win_odds,update_time') + ->order('update_time desc') + ->select() + ->toArray(); + + $oddsMap = []; + $scoreMap = []; + foreach ($rows as $row) { + $key = $this->buildOddsMatchKey($row); + if (!in_array($key, $matchKeys, true)) { + continue; + } + $odds = [ + 'home_odds' => $this->normalizeMoneylineOdds($row['home_win_odds'] ?? ''), + 'draw_odds' => $this->normalizeMoneylineOdds($row['draw_odds'] ?? ''), + 'away_odds' => $this->normalizeMoneylineOdds($row['away_win_odds'] ?? ''), + ]; + $score = count(array_filter($odds, static fn($value) => $value !== '')); + if ($score === 0 || $score <= ($scoreMap[$key] ?? -1)) { + continue; + } + $oddsMap[$key] = $odds; + $scoreMap[$key] = $score; + } + + foreach ($matchKeys as $index => $key) { + if (!isset($oddsMap[$key])) { + continue; + } + $matches[$index] = array_merge($matches[$index], $oddsMap[$key]); + } + return $matches; + } + + private function buildEventOddsMatchKey(array $match): string + { + $teamPairKey = $this->buildTeamPairKey( + (string)($match['home_team'] ?? ''), + (string)($match['away_team'] ?? '') + ); + $timestamp = (int)($match['match_time'] ?? 0); + if ($teamPairKey === '' || $timestamp <= 0) { + return ''; + } + if ($this->isWorldCupMatch($match)) { + $timestamp += 8 * 60 * 60; + } + return $teamPairKey . '|' . date('m-d', $timestamp); + } + + private function normalizeMoneylineOdds($value): string + { + $value = trim((string)$value); + if ($value === '' || !is_numeric($value) || (float)$value <= 0) { + return ''; + } + return $value; + } + public function detail() { $id = $this->request->get('id/d'); diff --git a/uniapp/src/components/match-card/match-card.vue b/uniapp/src/components/match-card/match-card.vue index 2ffd3ca..579fc61 100644 --- a/uniapp/src/components/match-card/match-card.vue +++ b/uniapp/src/components/match-card/match-card.vue @@ -53,18 +53,18 @@ {{ match.home_team_en || '' }} - + 主胜 - {{ match.home_odds || '0.00' }} + {{ homeWinOdds }} VS - - 平局 - {{ match.draw_odds || '0.00' }} + + + {{ drawOdds }} @@ -75,9 +75,9 @@ {{ match.away_team_en || '' }} - + 客胜 - {{ match.away_odds || '0.00' }} + {{ awayWinOdds }} @@ -142,6 +142,16 @@ const liveStyle = computed(() => { const cardType = computed(() => statusTypeMap[props.match.status] || 'other') +const normalizeMoneylineOdds = (value: unknown) => { + const text = String(value ?? '').trim() + const number = Number(text) + return text && Number.isFinite(number) && number > 0 ? text : '' +} + +const homeWinOdds = computed(() => normalizeMoneylineOdds(props.match?.home_odds)) +const drawOdds = computed(() => normalizeMoneylineOdds(props.match?.draw_odds)) +const awayWinOdds = computed(() => normalizeMoneylineOdds(props.match?.away_odds)) + const canShowLiveAi = computed(() => { return !!props.showLiveAi && ['live', 'upcoming'].includes(cardType.value) && !!props.match?.id }) diff --git a/uniapp/src/components/match-odds-detail/match-odds-detail.vue b/uniapp/src/components/match-odds-detail/match-odds-detail.vue index 3c5c108..1fb923a 100644 --- a/uniapp/src/components/match-odds-detail/match-odds-detail.vue +++ b/uniapp/src/components/match-odds-detail/match-odds-detail.vue @@ -230,30 +230,42 @@ onMounted(() => { } .match-odds-detail__tabs-inner { - display: inline-flex; - gap: 12rpx; + width: 100%; + min-width: 100%; + display: flex; + gap: 24rpx; } .match-odds-detail__tab { + position: relative; + flex: 1 0 112rpx; min-width: 112rpx; - height: 58rpx; - padding: 0 22rpx; + height: 64rpx; + padding: 0; display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; - border: 1rpx solid #e2e8f0; - border-radius: 8rpx; - background: #ffffff; color: #64748b; font-size: 24rpx; + white-space: nowrap; } .match-odds-detail__tab--active { - border-color: #2563eb; - background: #2563eb; - color: #ffffff; + color: #2563eb; font-weight: 600; + + &::after { + position: absolute; + left: 50%; + bottom: 0; + width: 32rpx; + height: 4rpx; + border-radius: 2rpx; + background: #2563eb; + content: ''; + transform: translateX(-50%); + } } .match-odds-detail__state { diff --git a/uniapp/src/pages/match_detail/match_detail.vue b/uniapp/src/pages/match_detail/match_detail.vue index 8424b46..aa09ddc 100644 --- a/uniapp/src/pages/match_detail/match_detail.vue +++ b/uniapp/src/pages/match_detail/match_detail.vue @@ -136,13 +136,15 @@ - - - {{ tab.label }} - + + + + {{ tab.label }} + + - + @@ -2147,21 +2149,32 @@ onUnload(() => { /* ====== Tab 导航栏 ====== */ .detail-tabs { - display: flex; + width: 100%; + white-space: nowrap; background: #fff; border-bottom: 1rpx solid #f0f0f0; - padding: 0 12rpx; + box-sizing: border-box; + + &__inner { + min-width: 100%; + padding: 0 24rpx; + display: inline-flex; + align-items: stretch; + justify-content: space-between; + gap: 32rpx; + box-sizing: border-box; + } &__item { - flex: 1; - display: flex; + position: relative; + flex: 0 0 auto; + padding: 20rpx 0; + display: inline-flex; align-items: center; justify-content: center; - gap: 6rpx; - padding: 20rpx 0; font-size: 26rpx; color: #999; - position: relative; + white-space: nowrap; transition: color 0.2s; &--active { @@ -2183,6 +2196,9 @@ onUnload(() => { } &__dot { + position: absolute; + top: 18rpx; + right: -14rpx; width: 10rpx; height: 10rpx; border-radius: 50%;