no message
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
# 赛事中心赛程、直播、赔率组件实施计划
|
||||
|
||||
> **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` 增加:
|
||||
|
||||
```php
|
||||
public static function getLiveCards(int $sportType = 0, string $leagueName = ''): array
|
||||
```
|
||||
|
||||
查询必须保留:
|
||||
|
||||
```php
|
||||
->leftJoin('match m', 'm.id = live.match_id')
|
||||
->where('live.fetch_status', 1)
|
||||
```
|
||||
|
||||
`MatchLive` 模型已启用 `SoftDelete`,查询自动排除已软删除记录,不额外重复拼接 `delete_time` 条件。
|
||||
|
||||
条件规则:
|
||||
|
||||
```php
|
||||
if ($sportType > 0) {
|
||||
$query->where('m.sport_type', $sportType);
|
||||
}
|
||||
if ($leagueName !== '') {
|
||||
$query->where('m.league_name', $leagueName);
|
||||
}
|
||||
```
|
||||
|
||||
`getWorldCupLiveCards()` 改为调用:
|
||||
|
||||
```php
|
||||
return self::getLiveCards();
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 新增公开接口**
|
||||
|
||||
在 `MatchController::$notNeedLogin` 增加 `liveCards`,新增:
|
||||
|
||||
```php
|
||||
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_id` 和 `list`,不改变专题页字段结构,但不再强制按世界杯赛事过滤。
|
||||
|
||||
### Task 2: 通用赔率查询
|
||||
|
||||
**Files:**
|
||||
- Modify: `server/app/api/controller/MatchController.php`
|
||||
|
||||
- [ ] **Step 1: 抽取赔率列表构建方法**
|
||||
|
||||
新增私有方法:
|
||||
|
||||
```php
|
||||
private function getOddsList(
|
||||
string $showType,
|
||||
string $sourceSite,
|
||||
string $keyword,
|
||||
int $includeSpecial,
|
||||
int $limit,
|
||||
int $sportType = 0,
|
||||
string $leagueName = '',
|
||||
bool $worldCupEventOnly = false
|
||||
): array
|
||||
```
|
||||
|
||||
`sport_type` 映射规则:
|
||||
|
||||
```php
|
||||
private function resolveOddsSportType(int $sportType): string
|
||||
{
|
||||
$map = [1 => 'FT'];
|
||||
return $map[$sportType] ?? '';
|
||||
}
|
||||
```
|
||||
|
||||
当 `sportType > 0` 且无法映射时直接返回空数组。`leagueName` 非空时对 `MatchOdds.league_name` 做精确筛选。
|
||||
|
||||
- [ ] **Step 2: 将赛事匹配扩展为通用匹配**
|
||||
|
||||
将 `buildWorldCupEventMap()` 改为:
|
||||
|
||||
```php
|
||||
private function buildOddsEventMap(int $sportType = 0, string $leagueName = '', bool $worldCupOnly = false): array
|
||||
```
|
||||
|
||||
通用查询以 `MatchEvent::where('is_show', 1)` 为基础;根据参数增加 `sport_type`、`league_name` 或世界杯条件,继续按标准化主客队键匹配。
|
||||
|
||||
- [ ] **Step 3: 新增通用赔率接口并保持专题接口**
|
||||
|
||||
在 `MatchController::$notNeedLogin` 增加 `odds`。
|
||||
|
||||
```php
|
||||
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: 将专题类型改成可复用类型**
|
||||
|
||||
新增别名或通用接口:
|
||||
|
||||
```ts
|
||||
export type MatchLiveCardItem = WorldCupLiveCardItem
|
||||
export type MatchOddsMatchItem = WorldCupOddsMatchItem
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 增加通用请求方法**
|
||||
|
||||
```ts
|
||||
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: 创建组件状态**
|
||||
|
||||
组件内部定义:
|
||||
|
||||
```ts
|
||||
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` 迁移:
|
||||
|
||||
- `liveList`、`upcomingList`、`endedList`
|
||||
- 已结束赛事分页
|
||||
- `matchGroups`
|
||||
- 分组折叠
|
||||
- `fetchMatchList()`、`loadMore()`、`goDetail()`
|
||||
- 当前运动和联赛筛选
|
||||
- 现有 `match-card` 列表模板与样式
|
||||
|
||||
- [ ] **Step 3: 增加直播内容**
|
||||
|
||||
调用:
|
||||
|
||||
```ts
|
||||
getMatchLiveCards()
|
||||
```
|
||||
|
||||
直播卡复用世界杯直播卡现有的地址选择、主播信息、观看/关注人数格式化以及 H5/App 播放兼容逻辑,但不渲染世界杯主题头部。
|
||||
|
||||
- [ ] **Step 4: 增加赔率内容和筛选**
|
||||
|
||||
赔率请求:
|
||||
|
||||
```ts
|
||||
getMatchOdds({
|
||||
show_type: oddsShowType.value || undefined,
|
||||
keyword: oddsKeyword.value || undefined,
|
||||
include_special: 0,
|
||||
limit: 300
|
||||
})
|
||||
```
|
||||
|
||||
保留:
|
||||
|
||||
- 全部/滚球盘/今日盘/早盘筛选
|
||||
- 联赛/球队搜索输入框
|
||||
- 不显示运动分类和联赛分类条
|
||||
- 筛选、清空
|
||||
- 独赢、让球、大小球盘口卡
|
||||
- 匹配到站内赛事时进入比赛详情
|
||||
|
||||
- [ ] **Step 5: 精简赛事中心外壳**
|
||||
|
||||
`MatchCenterPanel.vue` 只保留:
|
||||
|
||||
```vue
|
||||
<view class="match-header__title">...</view>
|
||||
<view class="topic-entry" @tap="openWorldCupCenter">...</view>
|
||||
<MatchCenterTabsPanel />
|
||||
```
|
||||
|
||||
删除原 `topic-tabs` 的世界杯视图切换事件。蓝色专题卡仍调用:
|
||||
|
||||
```ts
|
||||
emit('open-worldcup', { tab: 'schedule', cid })
|
||||
```
|
||||
|
||||
### Task 5: 文档同步与人工验证建议
|
||||
|
||||
**Files:**
|
||||
- Modify: `业务进度管理.md`
|
||||
- Modify: `设计稿映射.md`
|
||||
|
||||
- [ ] **Step 1: 记录完成内容**
|
||||
|
||||
在业务进度中增加:
|
||||
|
||||
- 赛事中心三个通用 Tab。
|
||||
- 通用直播和赔率接口。
|
||||
- 世界杯专题入口与专题页保持兼容。
|
||||
- 本次未执行构建、测试和部署。
|
||||
|
||||
- [ ] **Step 2: 更新设计稿映射**
|
||||
|
||||
将赛事中心备注更新为“赛程/直播/赔率在当前页面内容区切换,世界杯专题卡仍进入专题页”。
|
||||
|
||||
- [ ] **Step 3: 建议验证命令,不自动执行**
|
||||
|
||||
```powershell
|
||||
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. 世界杯专题卡仍能进入专题页。
|
||||
Reference in New Issue
Block a user