Files
sbnews/uniapp/src/components/match-card/match-card.vue
T
2026-06-11 12:15:29 +08:00

592 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="mc" :class="'mc--' + cardType">
<!-- 顶部赛事标签行 -->
<view class="mc__tags">
<view class="mc__tags-left">
<text class="mc__tag-label">{{ match.league_name || '赛事' }}</text>
<view class="mc__tag-pill">
<text>联赛</text>
</view>
<view v-if="match.round_name" class="mc__tag-pill">
<text>{{ match.round_name }}</text>
</view>
</view>
<view class="mc__tags-right">
<text
v-if="showTagTime"
class="mc__time"
:class="'mc__time--' + cardType"
>
{{ scheduleTime }}
</text>
<view v-if="match.is_hot" class="mc__tag-hot">
<text>热门</text>
</view>
</view>
</view>
<!-- 中间主客队 + 比分 -->
<view v-if="match.status === 1" class="mc__body-live" :style="liveStyle">
<view class="mc__bg-left"></view>
<view class="mc__bg-right"></view>
<view class="mc__home mc__home--live">
<image v-if="match.home_icon" class="mc__icon" :src="match.home_icon" mode="aspectFit" />
<text class="mc__name mc__name--white">{{ match.home_team }}</text>
<text class="mc__eng mc__eng--white">{{ match.home_team_en || '' }}</text>
</view>
<view class="mc__vs-area mc__vs-area--live">
<text class="mc__num mc__num--white">{{ match.home_score ?? 0 }}</text>
<text class="mc__vs-text mc__vs-text--live">VS</text>
<text class="mc__num mc__num--white">{{ match.away_score ?? 0 }}</text>
</view>
<view class="mc__away mc__away--live">
<image v-if="match.away_icon" class="mc__icon" :src="match.away_icon" mode="aspectFit" />
<text class="mc__name mc__name--white">{{ match.away_team }}</text>
<text class="mc__eng mc__eng--white">{{ match.away_team_en || '' }}</text>
</view>
</view>
<view v-else-if="match.status === 0" class="mc__body-upcoming">
<view class="mc__row">
<view class="mc__team-row">
<image v-if="match.home_icon" class="mc__icon-sm" :src="match.home_icon" mode="aspectFit" />
<view class="mc__team-info">
<text class="mc__tname">{{ match.home_team }}</text>
<text class="mc__teng">{{ match.home_team_en || '' }}</text>
</view>
</view>
<view class="mc__odds-item">
<text class="mc__odds-label">主胜</text>
<text class="mc__odds-val">{{ match.home_odds || '0.00' }}</text>
</view>
</view>
<view class="mc__row mc__row--mid">
<view class="mc__vs-center">
<text class="mc__vs-text">VS</text>
</view>
<view class="mc__odds-item">
<text class="mc__odds-label">平局</text>
<text class="mc__odds-val">{{ match.draw_odds || '0.00' }}</text>
</view>
</view>
<view class="mc__row">
<view class="mc__team-row mc__team-row--away">
<image v-if="match.away_icon" class="mc__icon-sm" :src="match.away_icon" mode="aspectFit" />
<view class="mc__team-info">
<text class="mc__tname">{{ match.away_team }}</text>
<text class="mc__teng">{{ match.away_team_en || '' }}</text>
</view>
</view>
<view class="mc__odds-item">
<text class="mc__odds-label">客胜</text>
<text class="mc__odds-val">{{ match.away_odds || '0.00' }}</text>
</view>
</view>
</view>
<view v-else class="mc__body">
<view class="mc__home">
<image v-if="match.home_icon" class="mc__icon" :src="match.home_icon" mode="aspectFit" />
<text class="mc__name">{{ match.home_team }}</text>
<text class="mc__eng">{{ match.home_team_en || '' }}</text>
</view>
<view class="mc__vs-area">
<text class="mc__num">{{ match.home_score ?? 0 }}</text>
<text class="mc__vs-text">VS</text>
<text class="mc__num">{{ match.away_score ?? 0 }}</text>
</view>
<view class="mc__away">
<image v-if="match.away_icon" class="mc__icon" :src="match.away_icon" mode="aspectFit" />
<text class="mc__name">{{ match.away_team }}</text>
<text class="mc__eng">{{ match.away_team_en || '' }}</text>
</view>
</view>
<!-- 底部状态 + 时间 -->
<view class="mc__footer" :class="{ 'mc__footer--live-ai': canShowLiveAi }">
<view class="mc__badge" :class="'mc__badge--' + cardType">
<text>{{ statusLabel }}</text>
</view>
<text v-if="showFooterTime" class="mc__time" :class="'mc__time--' + cardType">{{ scheduleTime }}</text>
<view v-if="canShowLiveAi" class="mc__ai-entry" @tap.stop="goAiAnalysis">
<text class="mc__ai-entry-badge">AI</text>
<text class="mc__ai-entry-text">分析</text>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
const props = defineProps<{
match: any
showLiveAi?: boolean
}>()
const statusTypeMap: Record<number, string> = { 1: 'live', 0: 'upcoming', 3: 'postponed', 2: 'ended' }
const statusTextMap: Record<number, string> = { 1: '进行中', 0: '未开始', 3: '推迟', 2: '已结束' }
const sportColors: Record<number, { left: string; right: string }> = {
1: { left: '#008248', right: '#00471b' },
2: { left: '#006BB7', right: '#D0202D' },
4: { left: '#6B3FA0', right: '#3F1D6B' },
5: { left: '#C47A20', right: '#8B5512' }
}
const defaultSportColor = { left: '#008248', right: '#00471b' }
const liveStyle = computed(() => {
const c = sportColors[props.match.sport_type] || defaultSportColor
return {
'--bg-left-color': c.left,
'--bg-right-color': c.right
}
})
const cardType = computed(() => statusTypeMap[props.match.status] || 'other')
const canShowLiveAi = computed(() => {
return !!props.showLiveAi && ['live', 'upcoming'].includes(cardType.value) && !!props.match?.id
})
const showTagTime = computed(() => {
return ['live', 'upcoming', 'ended'].includes(cardType.value)
})
const showFooterTime = computed(() => {
return !showTagTime.value
})
const statusLabel = computed(() => {
if (props.match.status === 1) return '· 进行中 ·'
return statusTextMap[props.match.status] || '未开始'
})
const scheduleTime = computed(() => {
if (!props.match.match_time) return ''
const d = new Date(props.match.match_time * 1000)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const date = String(d.getDate()).padStart(2, '0')
const hours = String(d.getHours()).padStart(2, '0')
const minutes = String(d.getMinutes()).padStart(2, '0')
return `${year}-${month}-${date} ${hours}:${minutes}`
})
const goAiAnalysis = () => {
const id = props.match?.id
if (!id) return
uni.navigateTo({ url: `/pages/ai_analysis/ai_analysis?type=match&id=${id}` })
}
</script>
<style lang="scss" scoped>
.mc {
padding: 20rpx 26rpx 10rpx;
background: #fff;
border-radius: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 10rpx;
&--ended {
background: #f3f4f6;
}
&__tags {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
&__tags-left {
display: flex;
align-items: center;
gap: 12rpx;
}
&__tags-right {
display: flex;
align-items: center;
gap: 10rpx;
justify-content: flex-end;
margin-left: 12rpx;
}
&__tag-label {
font-size: 24rpx;
color: #808080;
line-height: 1.4;
}
&__tag-pill {
display: flex;
align-items: center;
justify-content: center;
padding: 4rpx 8rpx;
background: #e5edff;
border-radius: 6rpx;
text {
font-size: 20rpx;
color: #185dff;
line-height: 1.4;
}
}
&__tag-hot {
display: flex;
align-items: center;
justify-content: center;
padding: 4rpx 8rpx;
background: #ffe5e5;
border-radius: 6rpx;
text {
font-size: 20rpx;
color: #ee3835;
line-height: 1.4;
}
}
&__body-upcoming {
display: flex;
flex-direction: column;
gap: 6rpx;
width: 100%;
}
&__row {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
&--mid {
padding-left: 56rpx;
}
}
&__team-row {
display: flex;
align-items: center;
gap: 8rpx;
}
&__team-info {
display: flex;
align-items: baseline;
gap: 8rpx;
}
&__icon-sm {
width: 48rpx;
height: 48rpx;
flex-shrink: 0;
}
&__tname {
font-size: 28rpx;
font-weight: 500;
color: #000;
line-height: 1.4;
}
&__teng {
font-size: 24rpx;
color: #000;
line-height: 1.4;
text-transform: capitalize;
}
&__vs-center {
display: flex;
align-items: center;
}
&__odds-item {
display: flex;
align-items: baseline;
gap: 4rpx;
}
&__odds-label {
font-size: 24rpx;
color: #808080;
line-height: 1.4;
}
&__odds-val {
font-size: 28rpx;
font-weight: 500;
color: #185dff;
line-height: 1.4;
width: 64rpx;
text-align: right;
}
&__body {
display: flex;
align-items: flex-start;
justify-content: space-between;
width: 100%;
}
&__body-live {
position: relative;
display: flex;
align-items: flex-start;
justify-content: space-between;
width: 100%;
overflow: hidden;
border-radius: 24rpx;
}
&__bg-left {
position: absolute;
top: 0;
left: -8%;
width: 56%;
height: 100%;
background: var(--bg-left-color, #008248);
transform: skewX(-35deg);
z-index: 0;
}
&__bg-right {
position: absolute;
top: 0;
right: -8%;
width: 56%;
height: 100%;
background: var(--bg-right-color, #00471b);
transform: skewX(-35deg);
z-index: 0;
}
&__home {
position: relative;
z-index: 1;
flex: 1;
display: flex;
flex-direction: column;
gap: 4rpx;
min-width: 0;
&--live {
padding: 20rpx 0 20rpx 40rpx;
}
}
&__away {
position: relative;
z-index: 1;
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4rpx;
min-width: 0;
&--live {
padding: 20rpx 40rpx 20rpx 0;
}
}
&__icon {
width: 64rpx;
height: 64rpx;
flex-shrink: 0;
}
&__name {
font-size: 32rpx;
font-weight: 500;
color: #000;
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
&--white {
color: #fff;
}
}
&__eng {
font-size: 24rpx;
color: #000;
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
&--white {
color: #fff;
}
}
&__vs-area {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 32rpx;
padding: 0 24rpx;
flex-shrink: 0;
align-self: center;
&--live {
padding: 0 24rpx;
}
}
&__num {
font-size: 36rpx;
font-weight: 600;
color: #000;
line-height: 1.4;
&--white {
color: #fff;
}
}
&__vs-text {
font-size: 24rpx;
font-weight: 600;
color: #808080;
line-height: 1.4;
&--live {
color: #fff;
text-shadow: 0 4rpx 6rpx rgba(0, 0, 0, 0.25);
-webkit-text-stroke: 2rpx #ee3835;
paint-order: stroke fill;
background: linear-gradient(180deg, #ee3835 0%, #185dff 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-stroke-color: transparent;
}
}
&__footer {
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
&--live-ai {
width: 100%;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
}
&__ai-entry {
display: flex;
align-items: center;
justify-content: center;
gap: 6rpx;
min-width: 152rpx;
padding: 6rpx 14rpx;
border-radius: 999rpx;
background: #f4f7ff;
&-badge {
min-width: 40rpx;
height: 40rpx;
padding: 0 10rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, #185dff 0%, #4f7dff 100%);
font-size: 22rpx;
font-weight: 700;
color: #fff;
line-height: 40rpx;
text-align: center;
}
&-text {
font-size: 24rpx;
font-weight: 600;
color: #185dff;
line-height: 1.2;
}
}
&__badge {
display: flex;
align-items: center;
padding: 8rpx 32rpx;
border-radius: 24rpx;
text {
font-size: 24rpx;
font-weight: 500;
line-height: 1.4;
}
&--upcoming {
background: #e5edff;
text {
color: #185dff;
}
}
&--live {
background: #185dff;
text {
color: #fff;
}
}
&--ended {
background: #808080;
text {
color: #fff;
}
}
&--postponed {
background: #fff1dd;
text {
color: #ff9900;
}
}
&--other {
background: #f0f0f0;
text {
color: #666;
}
}
}
&__time {
font-size: 24rpx;
color: #808080;
line-height: 1.4;
text-align: center;
&--live {
color: #185dff;
}
&--upcoming {
color: #185dff;
}
&--ended {
color: #6b7280;
}
}
}
</style>