Files
sbnews/docs/赛事中心赛程直播赔率组件实施计划.md
2026-07-31 16:13:31 +08:00

8.0 KiB

赛事中心赛程、直播、赔率组件实施计划

For agentic workers: REQUIRED SUB-SKILL: Use executing-plans to implement this plan task-by-task. 本项目未授权子代理、自动测试、构建、提交或部署。

Goal: 在赛事中心当前页面内用一个通用组件切换赛程、直播和赔率内容,不再因点击三个 Tab 切换到世界杯主题组件。

Architecture: MatchCenterPanel.vue 只保留赛事中心外壳和世界杯专题入口,新建 MatchCenterTabsPanel.vue 管理通用筛选、三个 Tab、数据加载和内容渲染。后端新增通用直播与赔率接口,并复用现有直播地址解析、赔率盘口格式化和赛事匹配逻辑;世界杯专题接口保持兼容。

Tech Stack: PHP 8 / ThinkPHP、Vue 3 <script setup>、TypeScript、uni-app、SCSS。


Task 1: 通用直播查询

Files:

  • Modify: server/app/common/service/match/MatchLiveService.php

  • Modify: server/app/api/controller/MatchController.php

  • Step 1: 将世界杯直播查询抽成可传筛选条件的通用方法

MatchLiveService 增加:

public static function getLiveCards(int $sportType = 0, string $leagueName = ''): array

查询必须保留:

->leftJoin('match m', 'm.id = live.match_id')
->where('live.fetch_status', 1)

MatchLive 模型已启用 SoftDelete,查询自动排除已软删除记录,不额外重复拼接 delete_time 条件。

条件规则:

if ($sportType > 0) {
    $query->where('m.sport_type', $sportType);
}
if ($leagueName !== '') {
    $query->where('m.league_name', $leagueName);
}

getWorldCupLiveCards() 改为调用:

return self::getLiveCards();
  • Step 2: 新增公开接口

MatchController::$notNeedLogin 增加 liveCards,新增:

public function liveCards()
{
    $sportType = $this->request->get('sport_type/d', 0);
    $leagueName = trim($this->request->get('league_name/s', ''));

    return $this->data([
        'list' => MatchLiveService::getLiveCards($sportType, $leagueName),
    ]);
}
  • Step 3: 保持世界杯接口响应兼容

worldCupLiveCards() 继续返回 season_idlist,不改变专题页字段结构,但不再强制按世界杯赛事过滤。

Task 2: 通用赔率查询

Files:

  • Modify: server/app/api/controller/MatchController.php

  • Step 1: 抽取赔率列表构建方法

新增私有方法:

private function getOddsList(
    string $showType,
    string $sourceSite,
    string $keyword,
    int $includeSpecial,
    int $limit,
    int $sportType = 0,
    string $leagueName = '',
    bool $worldCupEventOnly = false
): array

sport_type 映射规则:

private function resolveOddsSportType(int $sportType): string
{
    $map = [1 => 'FT'];
    return $map[$sportType] ?? '';
}

sportType > 0 且无法映射时直接返回空数组。leagueName 非空时对 MatchOdds.league_name 做精确筛选。

  • Step 2: 将赛事匹配扩展为通用匹配

buildWorldCupEventMap() 改为:

private function buildOddsEventMap(int $sportType = 0, string $leagueName = '', bool $worldCupOnly = false): array

通用查询以 MatchEvent::where('is_show', 1) 为基础;根据参数增加 sport_typeleague_name 或世界杯条件,继续按标准化主客队键匹配。

  • Step 3: 新增通用赔率接口并保持专题接口

MatchController::$notNeedLogin 增加 odds

public function odds()
{
    return $this->data([
        'list' => $this->getOddsList(
            $this->request->get('show_type/s', ''),
            strtolower(trim($this->request->get('source_site/s', ''))),
            trim($this->request->get('keyword/s', '')),
            $this->request->get('include_special/d', 0),
            min(max($this->request->get('limit/d', 120), 1), 300),
            $this->request->get('sport_type/d', 0),
            trim($this->request->get('league_name/s', ''))
        ),
    ]);
}

worldCupOdds() 调用同一方法并传 worldCupEventOnly=true,继续返回 season_id

Task 3: 前端 API 类型与请求

Files:

  • Modify: uniapp/src/api/match.ts

  • Step 1: 将专题类型改成可复用类型

新增别名或通用接口:

export type MatchLiveCardItem = WorldCupLiveCardItem
export type MatchOddsMatchItem = WorldCupOddsMatchItem
  • Step 2: 增加通用请求方法
export function getMatchLiveCards(data?: {
    sport_type?: number
    league_name?: string
}) {
    return request.get({ url: '/match/liveCards', data }, { withToken: false })
}

export function getMatchOdds(data?: {
    sport_type?: number
    league_name?: string
    show_type?: 'live' | 'today' | 'early'
    source_site?: string
    keyword?: string
    include_special?: number
    limit?: number
}) {
    return request.get({ url: '/match/odds', data }, { withToken: false })
}

世界杯请求方法保持不变。

Task 4: 通用赛事中心 Tab 组件

Files:

  • Create: uniapp/src/packages_match/components/MatchCenterTabsPanel.vue

  • Modify: uniapp/src/packages_match/components/MatchCenterPanel.vue

  • Step 1: 创建组件状态

组件内部定义:

type MatchCenterTab = 'schedule' | 'live' | 'odds'
type OddsShowType = '' | 'live' | 'today' | 'early'

const activeTab = ref<MatchCenterTab>('schedule')
const currentSport = ref(0)
const currentLeague = ref('')

组件统一加载 getSportTypes()getMatchLeagues(),运动和联赛分类只在赛程 Tab 显示并刷新赛程数据。

  • Step 2: 迁移赛程列表

MatchCenterPanel.vue 迁移:

  • liveListupcomingListendedList

  • 已结束赛事分页

  • matchGroups

  • 分组折叠

  • fetchMatchList()loadMore()goDetail()

  • 当前运动和联赛筛选

  • 现有 match-card 列表模板与样式

  • Step 3: 增加直播内容

调用:

getMatchLiveCards()

直播卡复用世界杯直播卡现有的地址选择、主播信息、观看/关注人数格式化以及 H5/App 播放兼容逻辑,但不渲染世界杯主题头部。

  • Step 4: 增加赔率内容和筛选

赔率请求:

getMatchOdds({
    show_type: oddsShowType.value || undefined,
    keyword: oddsKeyword.value || undefined,
    include_special: 0,
    limit: 300
})

保留:

  • 全部/滚球盘/今日盘/早盘筛选

  • 联赛/球队搜索输入框

  • 不显示运动分类和联赛分类条

  • 筛选、清空

  • 独赢、让球、大小球盘口卡

  • 匹配到站内赛事时进入比赛详情

  • Step 5: 精简赛事中心外壳

MatchCenterPanel.vue 只保留:

<view class="match-header__title">...</view>
<view class="topic-entry" @tap="openWorldCupCenter">...</view>
<MatchCenterTabsPanel />

删除原 topic-tabs 的世界杯视图切换事件。蓝色专题卡仍调用:

emit('open-worldcup', { tab: 'schedule', cid })

Task 5: 文档同步与人工验证建议

Files:

  • Modify: 业务进度管理.md

  • Modify: 设计稿映射.md

  • Step 1: 记录完成内容

在业务进度中增加:

  • 赛事中心三个通用 Tab。

  • 通用直播和赔率接口。

  • 世界杯专题入口与专题页保持兼容。

  • 本次未执行构建、测试和部署。

  • Step 2: 更新设计稿映射

将赛事中心备注更新为“赛程/直播/赔率在当前页面内容区切换,世界杯专题卡仍进入专题页”。

  • Step 3: 建议验证命令,不自动执行
php -l server\app\api\controller\MatchController.php
php -l server\app\common\service\match\MatchLiveService.php
cd uniapp
npm run build:h5

建议浏览器人工验证:

  1. 赛事中心默认显示赛程。
  2. 三个 Tab 切换时 URL 和页面外壳不切换为世界杯主题。
  3. 直播不显示运动或联赛分类筛选,并展示全部可用直播。
  4. 赔率不显示运动或联赛分类条,盘口和联赛/球队关键词筛选有效。
  5. 世界杯专题卡仍能进入专题页。