From c59b7ade29cc0d543cd95704f90b3c3d4294afca Mon Sep 17 00:00:00 2001 From: hajimi Date: Sun, 21 Jun 2026 12:57:27 +0800 Subject: [PATCH] fix: align uniapp live stream metadata fields --- qa/frontend/test_match_detail_live_static.py | 26 +++++++- uniapp/src/api/match.ts | 15 +++++ .../match-live-popup/match-live-popup.vue | 65 +++++++++++-------- .../src/pages/match_detail/match_detail.vue | 14 +++- 4 files changed, 89 insertions(+), 31 deletions(-) diff --git a/qa/frontend/test_match_detail_live_static.py b/qa/frontend/test_match_detail_live_static.py index d9c335b..8a0cc35 100644 --- a/qa/frontend/test_match_detail_live_static.py +++ b/qa/frontend/test_match_detail_live_static.py @@ -1,4 +1,5 @@ from pathlib import Path +import re ROOT = Path(__file__).resolve().parents[2] @@ -18,12 +19,33 @@ def main() -> None: popup_source = read_text(MATCH_LIVE_POPUP) api_source = read_text(MATCH_API) - assert "live_list" in detail_source, "match detail should handle live_list" + assert 'v-if="liveList.length"' in detail_source, "match detail should render live_list block with v-if" + assert 'v-else-if="thirdPartyLiveUrl"' in detail_source, "match detail should keep thirdPartyLiveUrl fallback with v-else-if" assert "MatchLivePopup" in detail_source, "match detail should use MatchLivePopup" - assert "play_url_m3u8" in popup_source, "popup should support play_url_m3u8" + assert "fetch_status" in detail_source, "match detail should reference fetch_status" + assert "last_fetch_at" in detail_source, "match detail should reference last_fetch_at" + assert "update_time" in detail_source, "match detail should reference update_time" + assert "default_play_url" in popup_source, "popup should prefer default_play_url" assert "stream_options" in popup_source, "popup should support stream_options" assert "/pages/webview/webview?url=" in popup_source, "selected stream should route through existing webview page" + field_names = [ + "play_url_m3u8", + "play_url_hd_m3u8", + "play_url_flv", + "play_url_hd_flv", + ] + for field_name in field_names: + assert field_name in popup_source, f"popup should cover {field_name}" + assert field_name in api_source, f"match live types should cover {field_name}" + + assert re.search(r"const\s+defaultPlayUrl\s*=\s*normalizeUrl\(props\.line\?\.default_play_url\)", popup_source), \ + "popup should explicitly prefer props.line?.default_play_url" + assert re.search(r"defaultPlayUrl.*?resetSelected", popup_source, re.S), \ + "popup selection reset should prefer default_play_url" + assert re.search(r"fetch_status.*last_fetch_at", popup_source, re.S) or "update_time" in popup_source, \ + "popup metadata should use fetch_status and last_fetch_at/update_time" + assert "/match/detail" in api_source, "match detail API path should stay on /match/detail" diff --git a/uniapp/src/api/match.ts b/uniapp/src/api/match.ts index b2acc4b..667038e 100644 --- a/uniapp/src/api/match.ts +++ b/uniapp/src/api/match.ts @@ -1,6 +1,7 @@ import request from '@/utils/request' export interface MatchLiveStreamOption { + id?: number | string title?: string name?: string label?: string @@ -9,12 +10,21 @@ export interface MatchLiveStreamOption { url?: string play_url?: string play_url_m3u8?: string + play_url_hd_m3u8?: string + play_url_flv?: string + play_url_hd_flv?: string default_play_url?: string live_url?: string + source_url?: string + fetch_status?: string | number + last_fetch_at?: string | number + update_time?: string | number + stream_options?: MatchLiveStreamOption[] [key: string]: any } export interface MatchLiveLineItem { + id?: number | string title?: string name?: string label?: string @@ -28,8 +38,13 @@ export interface MatchLiveLineItem { source_url?: string default_play_url?: string play_url_m3u8?: string + play_url_hd_m3u8?: string + play_url_flv?: string + play_url_hd_flv?: string play_url?: string live_url?: string + fetch_status?: string | number + last_fetch_at?: string | number stream_options?: MatchLiveStreamOption[] [key: string]: any } diff --git a/uniapp/src/components/match-live-popup/match-live-popup.vue b/uniapp/src/components/match-live-popup/match-live-popup.vue index 4843cae..44ef0ec 100644 --- a/uniapp/src/components/match-live-popup/match-live-popup.vue +++ b/uniapp/src/components/match-live-popup/match-live-popup.vue @@ -67,6 +67,16 @@ const normalizeUrl = (value: unknown) => { return text } +const getFetchStatusText = (value: unknown) => { + const text = normalizeText(value) + if (!text) return '' + const normalized = text.toLowerCase() + if (normalized === 'success' || normalized === '1') return '已抓取' + if (normalized === 'pending' || normalized === '0') return '待抓取' + if (normalized === 'failed' || normalized === '-1') return '抓取失败' + return text +} + const formatMetaTime = (value: unknown) => { const text = normalizeText(value) if (!text) return '' @@ -101,8 +111,8 @@ const lineTitle = computed(() => { const lineMetaText = computed(() => { const line = props.line || {} const parts = [ - normalizeText(line.status_text) || normalizeText(line.status_desc) || normalizeText(line.status), - formatMetaTime(line.updated_at || line.update_time), + getFetchStatusText(line.fetch_status), + formatMetaTime(line.last_fetch_at || line.update_time), ].filter(Boolean) return parts.length ? parts.join(' · ') : '请选择可用直播线路后打开' }) @@ -111,12 +121,18 @@ const sourceUrl = computed(() => normalizeUrl(props.line?.source_url)) const buildStreamOption = (option: MatchLiveStreamOption | undefined, index: number) => { if (!option) return null - const url = - normalizeUrl(option.play_url_m3u8) || - normalizeUrl(option.play_url) || - normalizeUrl(option.url) || - normalizeUrl(option.default_play_url) || - normalizeUrl(option.live_url) + const url = ( + [ + option.default_play_url, + option.play_url_m3u8, + option.play_url_hd_m3u8, + option.play_url_flv, + option.play_url_hd_flv, + option.play_url, + option.url, + option.live_url, + ].map((item) => normalizeUrl(item)).find(Boolean) || '' + ) if (!url) return null return { ...option, @@ -136,6 +152,15 @@ const streamOptions = computed(() => { const line = props.line || {} const options: PopupStreamOption[] = [] const seen = new Set() + const fallbackFields: Array<{ field: keyof MatchLiveLineItem; label: string }> = [ + { field: 'default_play_url', label: '默认线路' }, + { field: 'play_url_m3u8', label: 'M3U8 线路' }, + { field: 'play_url_hd_m3u8', label: '高清 M3U8' }, + { field: 'play_url_flv', label: 'FLV 线路' }, + { field: 'play_url_hd_flv', label: '高清 FLV' }, + { field: 'play_url', label: '备用线路' }, + { field: 'live_url', label: '直播地址' }, + ] const pushOption = (option: MatchLiveStreamOption | undefined, index: number) => { const built = buildStreamOption(option, index) @@ -148,25 +173,11 @@ const streamOptions = computed(() => { line.stream_options.forEach((item, index) => pushOption(item, index)) } - const defaultPlayUrl = normalizeUrl(line.default_play_url) - if (defaultPlayUrl && !seen.has(defaultPlayUrl)) { - pushOption({ label: '默认线路', url: defaultPlayUrl }, options.length) - } - - const playUrlM3u8 = normalizeUrl(line.play_url_m3u8) - if (playUrlM3u8 && !seen.has(playUrlM3u8)) { - pushOption({ label: 'M3U8 线路', play_url_m3u8: playUrlM3u8 }, options.length) - } - - const playUrl = normalizeUrl(line.play_url) - if (playUrl && !seen.has(playUrl)) { - pushOption({ label: '备用线路', play_url: playUrl }, options.length) - } - - const liveUrl = normalizeUrl(line.live_url) - if (liveUrl && !seen.has(liveUrl)) { - pushOption({ label: '直播地址', live_url: liveUrl }, options.length) - } + fallbackFields.forEach(({ field, label }) => { + const value = normalizeUrl(line[field]) + if (!value || seen.has(value)) return + pushOption({ label, [field]: value }, options.length) + }) return options }) diff --git a/uniapp/src/pages/match_detail/match_detail.vue b/uniapp/src/pages/match_detail/match_detail.vue index 457fea7..add4972 100644 --- a/uniapp/src/pages/match_detail/match_detail.vue +++ b/uniapp/src/pages/match_detail/match_detail.vue @@ -514,6 +514,16 @@ const awaySubs = computed(() => lineupList.value.filter((p: any) => p.team_side const normalizeLiveText = (value: unknown) => String(value || '').trim() +const getLiveFetchStatusText = (value: unknown) => { + const text = normalizeLiveText(value) + if (!text) return '' + const normalized = text.toLowerCase() + if (normalized === 'success' || normalized === '1') return '已抓取' + if (normalized === 'pending' || normalized === '0') return '待抓取' + if (normalized === 'failed' || normalized === '-1') return '抓取失败' + return text +} + const getLiveLineTitle = (item: MatchLiveLineItem) => { return ( normalizeLiveText(item?.title) || @@ -541,8 +551,8 @@ const formatLiveLineUpdate = (value: unknown) => { const getLiveLineMeta = (item: MatchLiveLineItem) => { const parts = [ - normalizeLiveText(item?.status_text) || normalizeLiveText(item?.status_desc) || normalizeLiveText(item?.status), - formatLiveLineUpdate(item?.updated_at || item?.update_time), + getLiveFetchStatusText(item?.fetch_status), + formatLiveLineUpdate(item?.last_fetch_at || item?.update_time), ].filter(Boolean) return parts.length ? parts.join(' · ') : '点击选择直播线路' }