添加赔率
This commit is contained in:
@@ -125,6 +125,9 @@ export interface WorldCupOddsMatchItem {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export type MatchLiveCardItem = WorldCupLiveCardItem
|
||||
export type MatchOddsMatchItem = WorldCupOddsMatchItem
|
||||
|
||||
export function getMatchList(data?: Record<string, any>) {
|
||||
return request.get({ url: '/match/lists', data: data }, { withToken: false })
|
||||
}
|
||||
@@ -165,6 +168,25 @@ export function getWorldCupLiveCards() {
|
||||
return request.get({ url: '/match/worldCupLiveCards' }, { withToken: false })
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
export function getWorldCupOdds(data?: {
|
||||
show_type?: 'live' | 'today' | 'early'
|
||||
source_site?: string
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<text class="match-header__title-text">赛事中心</text>
|
||||
<AiAssistantEntry size="compact" />
|
||||
</view>
|
||||
|
||||
<view class="topic-entry" @tap="openWorldCupCenter">
|
||||
<view class="topic-entry__badge">
|
||||
<text class="topic-entry__badge-year">2026</text>
|
||||
@@ -18,146 +19,24 @@
|
||||
<u-icon name="arrow-right" size="26" color="#ffffff"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="topic-tabs">
|
||||
<view v-for="tab in worldCupQuickTabs" :key="tab.key" class="topic-tabs__item"
|
||||
@tap="openWorldCupCenter(tab.key)">
|
||||
<text>{{ tab.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view scroll-x class="match-header__tabs">
|
||||
<view class="match-header__tabs-inner">
|
||||
<view v-for="(tab, idx) in sportTabs" :key="idx" class="match-tab"
|
||||
:class="{ 'match-tab--active': currentSport === tab.value }" @tap="switchSport(tab.value)">
|
||||
<text class="match-tab__icon">{{ tab.icon }}</text>
|
||||
<text class="match-tab__label">{{ tab.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<scroll-view v-if="leagueTabs.length > 0" scroll-x class="match-header__leagues">
|
||||
<view class="match-header__leagues-inner">
|
||||
<view class="league-tab" :class="{ 'league-tab--active': currentLeague === '' }"
|
||||
@tap="switchLeague('')">
|
||||
<text>全部<text v-if="leagueTotalCount" class="league-tab__count">·{{ leagueTotalCount
|
||||
}}</text></text>
|
||||
</view>
|
||||
<view v-for="(lg, idx) in leagueTabs" :key="idx" class="league-tab"
|
||||
:class="{ 'league-tab--active': currentLeague === lg.league_name }"
|
||||
@tap="switchLeague(lg.league_name)">
|
||||
<text>{{ lg.league_name }}<text v-if="lg.count" class="league-tab__count">·{{ lg.count
|
||||
}}</text></text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="match-list" @scrolltolower="loadMore">
|
||||
<view
|
||||
v-for="group in matchGroups"
|
||||
:key="group.title"
|
||||
class="match-group"
|
||||
:class="{ 'match-group--ended': group.type === 'ended' }"
|
||||
>
|
||||
<view class="match-group__header" @tap="toggleGroup(group.type)">
|
||||
<view class="match-group__dot" :class="'match-group__dot--' + group.type"></view>
|
||||
<text class="match-group__title">{{ group.title }}</text>
|
||||
<text class="match-group__count">{{ group.items.length }}场</text>
|
||||
<view class="match-group__arrow"
|
||||
:class="{ 'match-group__arrow--collapsed': collapsedGroups[group.type] }">
|
||||
<text>▼</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-for="item in group.items" :key="item.id || item.match_id" class="match-card-wrap"
|
||||
v-show="!collapsedGroups[group.type]" @tap="goDetail(item)">
|
||||
<match-card :match="item" :show-live-ai="group.type === 'live' || group.type === 'upcoming'" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="match-loading" v-if="loading">
|
||||
<u-loading mode="circle" />
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
<view class="match-empty" v-if="!loading && !liveList.length && !upcomingList.length && !endedList.length">
|
||||
<text>暂无赛事</text>
|
||||
</view>
|
||||
<view class="match-nomore" v-if="endedNoMore && endedList.length">
|
||||
<text>没有更多赛事了</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<MatchCenterTabsPanel />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import AiAssistantEntry from '@/components/ai-assistant-entry/ai-assistant-entry.vue'
|
||||
import MatchCard from '@/components/match-card/match-card.vue'
|
||||
import { getMatchList, getMatchLeagues, getSportTypes } from '@/api/match'
|
||||
import { getArticleCate } from '@/api/news'
|
||||
|
||||
type WorldCupQuickTab = 'schedule' | 'live' | 'odds'
|
||||
import MatchCenterTabsPanel from './MatchCenterTabsPanel.vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'open-worldcup', payload: { tab: WorldCupQuickTab; cid?: number | null }): void
|
||||
(e: 'open-worldcup', payload: { tab: 'schedule'; cid?: number | null }): void
|
||||
}>()
|
||||
|
||||
const statusBarHeight = ref(0)
|
||||
const sportTabs = ref<any[]>([])
|
||||
const worldCupCateId = ref<number | null>(null)
|
||||
const worldCupQuickTabs: { key: WorldCupQuickTab; label: string }[] = [
|
||||
{ key: 'schedule', label: '赛程' },
|
||||
{ key: 'live', label: '直播' },
|
||||
{ key: 'odds', label: '赔率' }
|
||||
]
|
||||
const currentSport = ref(0)
|
||||
const currentLeague = ref('')
|
||||
const allLeagues = ref<Record<number, any[]>>({})
|
||||
const liveList = ref<any[]>([])
|
||||
const upcomingList = ref<any[]>([])
|
||||
const endedList = ref<any[]>([])
|
||||
const endedPage = ref(1)
|
||||
const endedPageSize = 20
|
||||
const endedTotal = ref(0)
|
||||
const endedNoMore = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const collapsedGroups = ref<Record<string, boolean>>({
|
||||
live: false,
|
||||
upcoming: false,
|
||||
postponed: false,
|
||||
ended: false,
|
||||
other: false
|
||||
})
|
||||
|
||||
const leagueTabs = computed(() => {
|
||||
if (currentSport.value === 0) {
|
||||
const merged: any[] = []
|
||||
Object.keys(allLeagues.value || {}).forEach((key) => {
|
||||
const items = (allLeagues.value as any)[key]
|
||||
if (Array.isArray(items)) {
|
||||
merged.push(...items)
|
||||
}
|
||||
})
|
||||
return merged
|
||||
}
|
||||
return allLeagues.value[currentSport.value] || []
|
||||
})
|
||||
|
||||
const leagueTotalCount = computed(() => {
|
||||
return leagueTabs.value.reduce((sum: number, lg: any) => sum + (lg.count || 0), 0)
|
||||
})
|
||||
|
||||
const matchGroups = computed(() => {
|
||||
const groups: { title: string; type: string; items: any[]; order: number }[] = []
|
||||
if (liveList.value.length > 0) {
|
||||
groups.push({ title: '进行中', type: 'live', items: liveList.value, order: 0 })
|
||||
}
|
||||
if (upcomingList.value.length > 0) {
|
||||
groups.push({ title: '未开始', type: 'upcoming', items: upcomingList.value, order: 1 })
|
||||
}
|
||||
if (endedList.value.length > 0) {
|
||||
groups.push({ title: '已结束', type: 'ended', items: endedList.value, order: 2 })
|
||||
}
|
||||
return groups
|
||||
})
|
||||
|
||||
const initLayout = () => {
|
||||
uni.getSystemInfo({
|
||||
@@ -168,117 +47,33 @@ const initLayout = () => {
|
||||
// #ifdef H5
|
||||
statusBarHeight.value = 0
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const fetchSportTypes = async () => {
|
||||
try {
|
||||
const data = await getSportTypes()
|
||||
sportTabs.value = data || []
|
||||
} catch (e) {
|
||||
console.error('获取运动类型失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchLeagues = async () => {
|
||||
try {
|
||||
const data = await getMatchLeagues()
|
||||
allLeagues.value = data || {}
|
||||
} catch (e) {
|
||||
console.error('获取联赛列表失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchWorldCupCateId = async () => {
|
||||
if (worldCupCateId.value) return worldCupCateId.value
|
||||
try {
|
||||
const cateList = await getArticleCate()
|
||||
const cate = (cateList || []).find((item: any) => {
|
||||
const category = (cateList || []).find((item: any) => {
|
||||
const name = item?.name || item?.label || item?.title
|
||||
return String(name) === '世界杯'
|
||||
})
|
||||
worldCupCateId.value = cate?.id || null
|
||||
} catch (e) {
|
||||
console.log('获取世界杯分类失败=>', e)
|
||||
worldCupCateId.value = category?.id || null
|
||||
} catch (error) {
|
||||
console.log('获取世界杯分类失败=>', error)
|
||||
worldCupCateId.value = null
|
||||
}
|
||||
return worldCupCateId.value
|
||||
}
|
||||
|
||||
const fetchMatchList = async (refresh = false) => {
|
||||
if (loading.value) return
|
||||
if (refresh) {
|
||||
endedPage.value = 1
|
||||
endedNoMore.value = false
|
||||
liveList.value = []
|
||||
upcomingList.value = []
|
||||
endedList.value = []
|
||||
endedTotal.value = 0
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const params: Record<string, any> = { ended_page: endedPage.value, ended_page_size: endedPageSize }
|
||||
if (currentSport.value > 0) params.sport_type = currentSport.value
|
||||
if (currentLeague.value) params.league_name = currentLeague.value
|
||||
if (!refresh) params.ended_only = 1
|
||||
const data = await getMatchList(params)
|
||||
if (refresh) {
|
||||
liveList.value = data?.live || []
|
||||
upcomingList.value = data?.upcoming || []
|
||||
}
|
||||
const newEnded = data?.ended?.lists || []
|
||||
if (refresh) {
|
||||
endedList.value = newEnded
|
||||
} else {
|
||||
endedList.value = endedList.value.concat(newEnded)
|
||||
}
|
||||
endedTotal.value = data?.ended?.count || 0
|
||||
if (newEnded.length < endedPageSize) endedNoMore.value = true
|
||||
} catch (e) {
|
||||
console.error('获取赛事失败', e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const toggleGroup = (type: string) => {
|
||||
collapsedGroups.value[type] = !collapsedGroups.value[type]
|
||||
}
|
||||
|
||||
const loadMore = () => {
|
||||
if (endedNoMore.value || loading.value) return
|
||||
endedPage.value++
|
||||
fetchMatchList()
|
||||
}
|
||||
|
||||
const switchSport = (val: number) => {
|
||||
currentSport.value = val
|
||||
currentLeague.value = ''
|
||||
fetchMatchList(true)
|
||||
}
|
||||
|
||||
const switchLeague = (name: string) => {
|
||||
currentLeague.value = name
|
||||
fetchMatchList(true)
|
||||
}
|
||||
|
||||
const goDetail = (item: any) => {
|
||||
const id = item?.id || item?.match_id
|
||||
if (!id) return
|
||||
uni.navigateTo({ url: `/pages/match_detail/match_detail?id=${id}` })
|
||||
}
|
||||
|
||||
const openWorldCupCenter = async (tab: WorldCupQuickTab = 'schedule') => {
|
||||
const openWorldCupCenter = async () => {
|
||||
const cid = await fetchWorldCupCateId()
|
||||
emit('open-worldcup', { tab, cid })
|
||||
emit('open-worldcup', { tab: 'schedule', cid })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initLayout()
|
||||
fetchSportTypes()
|
||||
fetchLeagues()
|
||||
fetchMatchList(true)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -302,40 +97,12 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
|
||||
&-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
}
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
white-space: nowrap;
|
||||
height: 80rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
&__tabs-inner {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 60rpx;
|
||||
padding: 0 12rpx;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
&__leagues {
|
||||
white-space: nowrap;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
&__leagues-inner {
|
||||
display: inline-flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
padding: 8rpx 16rpx 0;
|
||||
gap: 4rpx;
|
||||
white-space: nowrap;
|
||||
&__title-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,185 +173,4 @@ onMounted(() => {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-tabs {
|
||||
margin: 0 24rpx 16rpx;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12rpx;
|
||||
|
||||
&__item {
|
||||
height: 58rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #f1f5ff;
|
||||
color: #185dff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
line-height: 58rpx;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.league-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8rpx 18rpx;
|
||||
border-radius: 18rpx;
|
||||
font-size: 24rpx;
|
||||
color: #808080;
|
||||
background: #fff;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--active {
|
||||
background: #185dff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&__count {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.match-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0rpx;
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 18rpx;
|
||||
background: #f4f5f7;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
|
||||
&__icon {
|
||||
font-size: 32rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: #e5edff;
|
||||
|
||||
.match-tab__label {
|
||||
color: #185dff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.match-list {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
display: block;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.match-group {
|
||||
margin-bottom: 2rpx;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 18rpx 18rpx 8rpx;
|
||||
gap: 6rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 50%;
|
||||
|
||||
&--live {
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
&--upcoming {
|
||||
background: #185dff;
|
||||
}
|
||||
|
||||
&--postponed {
|
||||
background: #ff9900;
|
||||
}
|
||||
|
||||
&--ended {
|
||||
background: #999;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&__count {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
margin-left: auto;
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
transition: transform 0.25s ease;
|
||||
|
||||
&--collapsed {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&--ended {
|
||||
background: #f3f4f6;
|
||||
|
||||
.match-group__header {
|
||||
background: #f3f4f6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.match-group:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.match-card-wrap {
|
||||
position: relative;
|
||||
margin: 0 18rpx 8rpx;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: -4rpx;
|
||||
height: 1rpx;
|
||||
}
|
||||
|
||||
&:last-child::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.match-loading,
|
||||
.match-empty,
|
||||
.match-nomore {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30rpx 0;
|
||||
gap: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user