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%;