974 lines
26 KiB
Vue
974 lines
26 KiB
Vue
<template>
|
||
<div class="contest-detail-page">
|
||
<a-spin :spinning="loading">
|
||
<!-- 顶部海报区域 -->
|
||
<div class="header-section">
|
||
<!-- 返回按钮 -->
|
||
<a-button class="back-button" type="text" @click="$router.back()">
|
||
<template #icon>
|
||
<ArrowLeftOutlined />
|
||
</template>
|
||
返回
|
||
</a-button>
|
||
|
||
<!-- 海报图片 -->
|
||
<div
|
||
class="poster-container"
|
||
:style="{
|
||
backgroundImage:
|
||
contest?.posterUrl || contest?.coverUrl
|
||
? `url(${contest.posterUrl || contest.coverUrl})`
|
||
: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||
}"
|
||
>
|
||
<div class="poster-placeholder">
|
||
<span>赛事海报</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 导航栏和报名信息 -->
|
||
<div class="nav-bar-section">
|
||
<div class="nav-tabs-wrapper">
|
||
<a-tabs
|
||
v-model:activeKey="activeTab"
|
||
class="nav-tabs"
|
||
@change="handleTabChange"
|
||
>
|
||
<a-tab-pane key="info" tab="赛事信息" />
|
||
<a-tab-pane
|
||
key="notices"
|
||
:tab="`通知公告${
|
||
notices.length > 0 ? ` (${notices.length})` : ''
|
||
}`"
|
||
/>
|
||
<a-tab-pane
|
||
key="results"
|
||
:tab="`赛事结果${
|
||
results.length > 0 ? ` (${results.length})` : ''
|
||
}`"
|
||
/>
|
||
</a-tabs>
|
||
</div>
|
||
|
||
<div class="registration-info-bar">
|
||
<div class="registration-time-info">
|
||
<span class="time-label">报名时间</span>
|
||
<span v-if="isRegistering" class="countdown-text">
|
||
距离报名截止还有 {{ daysRemaining }} 天
|
||
</span>
|
||
</div>
|
||
<div class="registration-time-range">
|
||
{{
|
||
formatDateRange(
|
||
contest?.registerStartTime,
|
||
contest?.registerEndTime
|
||
)
|
||
}}
|
||
</div>
|
||
<a-button
|
||
v-if="isRegistering && !hasRegistered"
|
||
type="primary"
|
||
size="large"
|
||
class="register-button"
|
||
@click="handleRegister"
|
||
>
|
||
立即报名
|
||
</a-button>
|
||
<a-button
|
||
v-else-if="hasRegistered && canViewRegistration"
|
||
type="default"
|
||
size="large"
|
||
class="register-button"
|
||
@click="handleViewRegistration"
|
||
>
|
||
查看报名
|
||
</a-button>
|
||
<a-button
|
||
v-else
|
||
type="default"
|
||
size="large"
|
||
class="register-button"
|
||
disabled
|
||
>
|
||
立即报名
|
||
</a-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 主标题 -->
|
||
<div v-if="contest" class="title-section">
|
||
<h1 class="contest-title">{{ contest.contestName }}</h1>
|
||
</div>
|
||
|
||
<!-- 内容区域 - 两列布局 -->
|
||
<div v-if="contest" class="content-section">
|
||
<a-row :gutter="24">
|
||
<!-- 左侧:竞赛信息 -->
|
||
<a-col :xs="24" :lg="16">
|
||
<div class="info-content">
|
||
<div class="section-header">| 竞赛信息</div>
|
||
|
||
<!-- 赛事信息 Tab 内容 -->
|
||
<div v-if="activeTab === 'info'" class="info-detail">
|
||
<div v-if="contest.content" class="info-section">
|
||
<h3 class="section-title">一、大赛介绍</h3>
|
||
<div class="section-content" v-html="contest.content"></div>
|
||
</div>
|
||
|
||
<div v-if="(contest as any).theme" class="info-section">
|
||
<h3 class="section-title">二、大赛主题</h3>
|
||
<div class="section-content">
|
||
{{ (contest as any).theme }}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-section">
|
||
<h3 class="section-title">三、参赛对象</h3>
|
||
<div class="section-content">
|
||
在创新创业应用领域具有先进技术解决方案和成功实践经验的创业团队、优秀人才均可报名参赛。
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
v-if="
|
||
(contest as any).tracks && (contest as any).tracks.length
|
||
"
|
||
class="info-section"
|
||
>
|
||
<h3 class="section-title">四、大赛赛道</h3>
|
||
<div class="section-content">
|
||
<a-tag
|
||
v-for="track in (contest as any).tracks"
|
||
:key="track"
|
||
class="track-tag"
|
||
>
|
||
{{ track }}
|
||
</a-tag>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-section">
|
||
<h3 class="section-title">五、参赛规则</h3>
|
||
<div class="section-content">
|
||
<div class="rule-item">
|
||
<strong>(一)参赛团队:</strong>
|
||
<p>
|
||
1.参赛团队应由1-8人组成,一名参赛人员仅允许参与一支参赛队伍。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 通知公告 Tab 内容 -->
|
||
<div v-if="activeTab === 'notices'" class="notices-content">
|
||
<a-list
|
||
:data-source="notices"
|
||
:loading="noticesLoading"
|
||
item-layout="vertical"
|
||
>
|
||
<template #renderItem="{ item }">
|
||
<a-list-item>
|
||
<a-list-item-meta>
|
||
<template #title>
|
||
<a-space>
|
||
<span>{{ item.title }}</span>
|
||
<a-tag :color="getNoticeTypeColor(item.noticeType)">
|
||
{{ getNoticeTypeText(item.noticeType) }}
|
||
</a-tag>
|
||
<a-tag v-if="item.priority > 0" color="red">
|
||
优先级: {{ item.priority }}
|
||
</a-tag>
|
||
</a-space>
|
||
</template>
|
||
<template #description>
|
||
<div>{{ item.content }}</div>
|
||
<div style="margin-top: 8px; color: #999">
|
||
发布时间: {{ formatDateTime(item.publishTime) }}
|
||
</div>
|
||
</template>
|
||
</a-list-item-meta>
|
||
</a-list-item>
|
||
</template>
|
||
<template #empty>
|
||
<a-empty description="暂无公告" />
|
||
</template>
|
||
</a-list>
|
||
</div>
|
||
|
||
<!-- 赛事结果 Tab 内容 -->
|
||
<div v-if="activeTab === 'results'" class="results-content">
|
||
<div v-if="contest.resultState === 'published'">
|
||
<a-spin :spinning="resultsLoading">
|
||
<a-table
|
||
:columns="resultColumns"
|
||
:data-source="results"
|
||
:pagination="resultsPagination"
|
||
row-key="id"
|
||
@change="handleResultsTableChange"
|
||
>
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.key === 'rank'">
|
||
<a-tag :color="getRankColor(record.rank)">
|
||
{{ record.rank || "-" }}
|
||
</a-tag>
|
||
</template>
|
||
<template v-else-if="column.key === 'author'">
|
||
{{
|
||
record.registration?.user?.nickname ||
|
||
record.registration?.team?.teamName ||
|
||
"-"
|
||
}}
|
||
</template>
|
||
<template v-else-if="column.key === 'award'">
|
||
<a-tag
|
||
v-if="record.awardName"
|
||
:color="getAwardColor(record.awardName)"
|
||
>
|
||
{{ record.awardName }}
|
||
</a-tag>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</template>
|
||
</a-table>
|
||
</a-spin>
|
||
</div>
|
||
<a-empty v-else description="结果尚未公布" />
|
||
</div>
|
||
</div>
|
||
</a-col>
|
||
|
||
<!-- 右侧:组织信息 -->
|
||
<a-col :xs="24" :lg="8">
|
||
<div class="org-info-card">
|
||
<div class="info-item">
|
||
<div class="info-label">| 发布者</div>
|
||
<div class="info-value">
|
||
<div v-if="(contest as any).publisher">
|
||
{{ (contest as any).publisher }}
|
||
</div>
|
||
<div v-else>-</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-item">
|
||
<div class="info-label">| 类型</div>
|
||
<div class="info-value">
|
||
<a-tag
|
||
:color="
|
||
contest.contestType === 'individual' ? 'blue' : 'green'
|
||
"
|
||
>
|
||
{{
|
||
contest.contestType === "individual" ? "个人赛" : "团队赛"
|
||
}}
|
||
</a-tag>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-item">
|
||
<div class="info-label">| 主办单位</div>
|
||
<div class="info-value">
|
||
<template
|
||
v-if="contest.organizers && contest.organizers.length"
|
||
>
|
||
<div
|
||
v-for="org in contest.organizers"
|
||
:key="org"
|
||
class="org-item"
|
||
>
|
||
{{ org }}
|
||
</div>
|
||
</template>
|
||
<span v-else>-</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-item">
|
||
<div class="info-label">| 协办单位</div>
|
||
<div class="info-value">
|
||
<template
|
||
v-if="contest.coOrganizers && contest.coOrganizers.length"
|
||
>
|
||
<div
|
||
v-for="org in contest.coOrganizers"
|
||
:key="org"
|
||
class="org-item"
|
||
>
|
||
{{ org }}
|
||
</div>
|
||
</template>
|
||
<span v-else>-</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-item">
|
||
<div class="info-label">| 赞助单位</div>
|
||
<div class="info-value">
|
||
<template v-if="contest.sponsors && contest.sponsors.length">
|
||
<div
|
||
v-for="sp in contest.sponsors"
|
||
:key="sp"
|
||
class="org-item"
|
||
>
|
||
{{ sp }}
|
||
</div>
|
||
</template>
|
||
<span v-else>-</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-item">
|
||
<div class="info-label">| 报名时间</div>
|
||
<div class="info-value">
|
||
{{ formatDateTime(contest.registerStartTime) }} 至
|
||
{{ formatDateTime(contest.registerEndTime) }}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-item">
|
||
<div class="info-label">| 比赛时间</div>
|
||
<div class="info-value">
|
||
{{ formatDateTime(contest.startTime) }} 至
|
||
{{ formatDateTime(contest.endTime) }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</a-col>
|
||
</a-row>
|
||
</div>
|
||
|
||
<a-empty v-else description="比赛不存在" />
|
||
</a-spin>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, computed } from "vue"
|
||
import { useRoute, useRouter } from "vue-router"
|
||
import { message } from "ant-design-vue"
|
||
import { ArrowLeftOutlined } from "@ant-design/icons-vue"
|
||
import dayjs from "dayjs"
|
||
import { useAuthStore } from "@/stores/auth"
|
||
import {
|
||
contestsApi,
|
||
noticesApi,
|
||
resultsApi,
|
||
registrationsApi,
|
||
type Contest,
|
||
type ContestNotice,
|
||
type ContestResult,
|
||
} from "@/api/contests"
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const authStore = useAuthStore()
|
||
const tenantCode = route.params.tenantCode as string
|
||
|
||
const loading = ref(false)
|
||
const noticesLoading = ref(false)
|
||
const resultsLoading = ref(false)
|
||
const contest = ref<Contest | null>(null)
|
||
const notices = ref<ContestNotice[]>([])
|
||
const results = ref<ContestResult[]>([])
|
||
const activeTab = ref("info")
|
||
const hasRegistered = ref(false)
|
||
const myRegistration = ref<any>(null)
|
||
|
||
// 检查是否有查看报名的权限
|
||
const canViewRegistration = computed(() => {
|
||
const permissions = authStore.user?.permissions || []
|
||
return permissions.includes('registration:read') || permissions.includes('registration:create')
|
||
})
|
||
|
||
const contestId = Number(route.params.id)
|
||
|
||
const resultsPagination = ref({
|
||
current: 1,
|
||
pageSize: 20,
|
||
total: 0,
|
||
})
|
||
|
||
const resultColumns = [
|
||
{
|
||
title: "排名",
|
||
key: "rank",
|
||
dataIndex: "rank",
|
||
width: 80,
|
||
},
|
||
{
|
||
title: "作品名称",
|
||
key: "title",
|
||
dataIndex: "title",
|
||
width: 200,
|
||
},
|
||
{
|
||
title: "作者",
|
||
key: "author",
|
||
width: 150,
|
||
},
|
||
{
|
||
title: "最终得分",
|
||
key: "finalScore",
|
||
dataIndex: "finalScore",
|
||
width: 120,
|
||
sorter: true,
|
||
},
|
||
{
|
||
title: "奖项",
|
||
key: "award",
|
||
width: 120,
|
||
},
|
||
]
|
||
|
||
// 格式化日期
|
||
const formatDate = (dateStr?: string) => {
|
||
if (!dateStr) return "-"
|
||
return dayjs(dateStr).format("YYYY-MM-DD")
|
||
}
|
||
|
||
// 格式化日期时间
|
||
const formatDateTime = (dateStr?: string) => {
|
||
if (!dateStr) return "-"
|
||
return dayjs(dateStr).format("YYYY-MM-DD HH:mm")
|
||
}
|
||
|
||
// 格式化日期范围
|
||
const formatDateRange = (startDate?: string, endDate?: string) => {
|
||
if (!startDate || !endDate) return "-"
|
||
return `${formatDate(startDate)} ~ ${formatDate(endDate)}`
|
||
}
|
||
|
||
// 判断是否在报名中
|
||
const isRegistering = computed(() => {
|
||
if (!contest.value) return false
|
||
const now = dayjs()
|
||
const start = dayjs(contest.value.registerStartTime)
|
||
const end = dayjs(contest.value.registerEndTime)
|
||
return now.isAfter(start) && now.isBefore(end)
|
||
})
|
||
|
||
// 判断报名是否已结束
|
||
const isRegisterEnded = computed(() => {
|
||
if (!contest.value) return false
|
||
const now = dayjs()
|
||
const end = dayjs(contest.value.registerEndTime)
|
||
return now.isAfter(end)
|
||
})
|
||
|
||
// 计算距离报名截止还有几天
|
||
const daysRemaining = computed(() => {
|
||
if (!contest.value || !isRegistering.value) return 0
|
||
const now = dayjs()
|
||
const end = dayjs(contest.value.registerEndTime)
|
||
const diff = end.diff(now, "day")
|
||
return diff > 0 ? diff : 0
|
||
})
|
||
|
||
// 获取作品类型文本
|
||
const getWorkTypeText = (type?: string) => {
|
||
switch (type) {
|
||
case "image":
|
||
return "图片"
|
||
case "video":
|
||
return "视频"
|
||
case "document":
|
||
return "文档"
|
||
case "code":
|
||
return "代码"
|
||
case "other":
|
||
return "其他"
|
||
default:
|
||
return type || "-"
|
||
}
|
||
}
|
||
|
||
// 获取公告类型颜色
|
||
const getNoticeTypeColor = (type?: string) => {
|
||
switch (type) {
|
||
case "urgent":
|
||
return "red"
|
||
case "system":
|
||
return "blue"
|
||
default:
|
||
return "default"
|
||
}
|
||
}
|
||
|
||
// 获取公告类型文本
|
||
const getNoticeTypeText = (type?: string) => {
|
||
switch (type) {
|
||
case "urgent":
|
||
return "紧急通知"
|
||
case "system":
|
||
return "系统公告"
|
||
default:
|
||
return "普通公告"
|
||
}
|
||
}
|
||
|
||
// 计算报名阶段状态
|
||
const getRegisterStateColor = () => {
|
||
if (!contest.value) return "default"
|
||
const now = dayjs()
|
||
const start = dayjs(contest.value.registerStartTime)
|
||
const end = dayjs(contest.value.registerEndTime)
|
||
if (now.isBefore(start)) return "default"
|
||
if (now.isAfter(end)) return "orange"
|
||
return "processing"
|
||
}
|
||
|
||
const getRegisterStateText = () => {
|
||
if (!contest.value) return "-"
|
||
const now = dayjs()
|
||
const start = dayjs(contest.value.registerStartTime)
|
||
const end = dayjs(contest.value.registerEndTime)
|
||
if (now.isBefore(start)) return "未开始"
|
||
if (now.isAfter(end)) return "已结束"
|
||
return "进行中"
|
||
}
|
||
|
||
// 计算作品提交阶段状态
|
||
const getSubmitStateColor = () => {
|
||
if (!contest.value) return "default"
|
||
const now = dayjs()
|
||
const start = dayjs(contest.value.submitStartTime)
|
||
const end = dayjs(contest.value.submitEndTime)
|
||
if (now.isBefore(start)) return "default"
|
||
if (now.isAfter(end)) return "orange"
|
||
return "processing"
|
||
}
|
||
|
||
const getSubmitStateText = () => {
|
||
if (!contest.value) return "-"
|
||
const now = dayjs()
|
||
const start = dayjs(contest.value.submitStartTime)
|
||
const end = dayjs(contest.value.submitEndTime)
|
||
if (now.isBefore(start)) return "未开始"
|
||
if (now.isAfter(end)) return "已结束"
|
||
return "进行中"
|
||
}
|
||
|
||
// 获取排名颜色
|
||
const getRankColor = (rank?: number) => {
|
||
if (!rank) return "default"
|
||
if (rank === 1) return "gold"
|
||
if (rank === 2) return "default"
|
||
if (rank === 3) return "orange"
|
||
return "blue"
|
||
}
|
||
|
||
// 获取奖项颜色
|
||
const getAwardColor = (award?: string) => {
|
||
if (!award) return "default"
|
||
if (award.includes("一等奖") || award.includes("金奖")) return "gold"
|
||
if (award.includes("二等奖") || award.includes("银奖")) return "default"
|
||
if (award.includes("三等奖") || award.includes("铜奖")) return "orange"
|
||
return "blue"
|
||
}
|
||
|
||
// 加载比赛详情
|
||
const fetchContestDetail = async () => {
|
||
loading.value = true
|
||
try {
|
||
contest.value = await contestsApi.getDetail(contestId)
|
||
// 检查是否已报名
|
||
await checkRegistration()
|
||
} catch (error: any) {
|
||
message.error(error?.response?.data?.message || "获取比赛详情失败")
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 检查是否已报名
|
||
const checkRegistration = async () => {
|
||
if (!authStore.user) return
|
||
try {
|
||
const response = await registrationsApi.getList({
|
||
contestId,
|
||
userId: authStore.user.id,
|
||
page: 1,
|
||
pageSize: 1,
|
||
})
|
||
if (response.list && response.list.length > 0) {
|
||
hasRegistered.value = true
|
||
myRegistration.value = response.list[0]
|
||
}
|
||
} catch (error) {
|
||
console.error("检查报名状态失败:", error)
|
||
}
|
||
}
|
||
|
||
// 加载公告列表
|
||
const fetchNotices = async () => {
|
||
noticesLoading.value = true
|
||
try {
|
||
notices.value = await noticesApi.getList(contestId)
|
||
} catch (error: any) {
|
||
message.error("获取公告列表失败")
|
||
} finally {
|
||
noticesLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 加载赛事结果
|
||
const fetchResults = async () => {
|
||
if (!contest.value || contest.value.resultState !== "published") return
|
||
resultsLoading.value = true
|
||
try {
|
||
const response = await resultsApi.getResults(
|
||
contestId,
|
||
resultsPagination.value.current,
|
||
resultsPagination.value.pageSize
|
||
)
|
||
results.value = response.list || []
|
||
resultsPagination.value.total = response.total || 0
|
||
} catch (error: any) {
|
||
message.error("获取赛事结果失败")
|
||
} finally {
|
||
resultsLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 结果表格变化处理
|
||
const handleResultsTableChange = (pag: any) => {
|
||
resultsPagination.value.current = pag.current || 1
|
||
resultsPagination.value.pageSize = pag.pageSize || 20
|
||
fetchResults()
|
||
}
|
||
|
||
// 立即报名 - 跳转到报名页面
|
||
const handleRegister = () => {
|
||
if (!authStore.user) {
|
||
message.warning("请先登录")
|
||
router.push(`/${tenantCode}/login`)
|
||
return
|
||
}
|
||
|
||
if (!contest.value) return
|
||
|
||
// 根据比赛类型跳转到对应的报名页面
|
||
if (contest.value.contestType === "team") {
|
||
router.push(`/${tenantCode}/contests/${contestId}/register/team`)
|
||
} else {
|
||
router.push(`/${tenantCode}/contests/${contestId}/register/individual`)
|
||
}
|
||
}
|
||
|
||
// 查看报名 - 跳转到报名页面
|
||
const handleViewRegistration = () => {
|
||
if (!contest.value) return
|
||
|
||
// 根据比赛类型跳转到对应的报名页面
|
||
if (contest.value.contestType === "team") {
|
||
router.push(`/${tenantCode}/contests/${contestId}/register/team`)
|
||
} else {
|
||
router.push(`/${tenantCode}/contests/${contestId}/register/individual`)
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchContestDetail()
|
||
fetchNotices()
|
||
})
|
||
|
||
// 监听tab切换,切换到结果tab时加载结果
|
||
const handleTabChange = (key: string) => {
|
||
activeTab.value = key
|
||
if (key === "results" && contest.value?.resultState === "published") {
|
||
fetchResults()
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.contest-detail-page {
|
||
min-height: 100vh;
|
||
background-color: #f0f2f5;
|
||
|
||
.header-section {
|
||
position: relative;
|
||
background: #fff;
|
||
margin-bottom: 0;
|
||
|
||
.back-button {
|
||
position: absolute;
|
||
top: 24px;
|
||
left: 24px;
|
||
z-index: 100;
|
||
color: #fff;
|
||
font-size: 16px;
|
||
|
||
&:hover {
|
||
color: #1890ff;
|
||
}
|
||
}
|
||
|
||
.poster-container {
|
||
width: 100%;
|
||
height: 400px;
|
||
background-size: cover;
|
||
background-position: center;
|
||
background-repeat: no-repeat;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
|
||
.poster-placeholder {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
color: #fff;
|
||
font-size: 18px;
|
||
}
|
||
}
|
||
|
||
.nav-bar-section {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 16px 24px;
|
||
background: #fff;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
|
||
.nav-tabs-wrapper {
|
||
flex: 1;
|
||
|
||
:deep(.ant-tabs-nav) {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
:deep(.ant-tabs-tab) {
|
||
padding: 12px 24px;
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
.registration-info-bar {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
gap: 8px;
|
||
|
||
.registration-time-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
|
||
.time-label {
|
||
font-size: 14px;
|
||
color: #8c8c8c;
|
||
}
|
||
|
||
.countdown-text {
|
||
font-size: 14px;
|
||
color: #1890ff;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.registration-time-range {
|
||
font-size: 14px;
|
||
color: #595959;
|
||
}
|
||
|
||
.register-button {
|
||
min-width: 120px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.title-section {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 24px 24px 0;
|
||
|
||
.contest-title {
|
||
font-size: 32px;
|
||
font-weight: 600;
|
||
color: #262626;
|
||
margin: 0;
|
||
line-height: 1.5;
|
||
}
|
||
}
|
||
|
||
.content-section {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 24px;
|
||
|
||
.info-content {
|
||
background: #fff;
|
||
padding: 24px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
|
||
.section-header {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #262626;
|
||
margin-bottom: 24px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 2px solid #1890ff;
|
||
}
|
||
|
||
.info-detail {
|
||
.info-section {
|
||
margin-bottom: 32px;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #262626;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.section-content {
|
||
font-size: 15px;
|
||
color: #595959;
|
||
line-height: 1.8;
|
||
|
||
.track-tag {
|
||
margin-right: 8px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.rule-item {
|
||
margin-bottom: 16px;
|
||
|
||
strong {
|
||
color: #262626;
|
||
}
|
||
|
||
p {
|
||
margin: 8px 0 0 24px;
|
||
color: #595959;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.notices-content,
|
||
.results-content {
|
||
padding-top: 16px;
|
||
}
|
||
}
|
||
|
||
.org-info-card {
|
||
background: #fff;
|
||
padding: 24px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
|
||
.info-item {
|
||
margin-bottom: 24px;
|
||
padding-bottom: 24px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
padding-bottom: 0;
|
||
border-bottom: none;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #262626;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 14px;
|
||
color: #595959;
|
||
line-height: 1.8;
|
||
|
||
.org-item {
|
||
margin-bottom: 8px;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 响应式设计
|
||
@media (max-width: 992px) {
|
||
.contest-detail-page {
|
||
.header-section {
|
||
.nav-bar-section {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 16px;
|
||
|
||
.registration-info-bar {
|
||
width: 100%;
|
||
align-items: flex-start;
|
||
}
|
||
}
|
||
}
|
||
|
||
.content-section {
|
||
.info-content,
|
||
.org-info-card {
|
||
margin-bottom: 24px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.contest-detail-page {
|
||
.header-section {
|
||
.poster-container {
|
||
height: 250px;
|
||
}
|
||
|
||
.nav-bar-section {
|
||
padding: 12px 16px;
|
||
|
||
.nav-tabs-wrapper {
|
||
:deep(.ant-tabs-tab) {
|
||
padding: 8px 16px;
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.title-section {
|
||
padding: 16px 16px 0;
|
||
|
||
.contest-title {
|
||
font-size: 24px;
|
||
}
|
||
}
|
||
|
||
.content-section {
|
||
padding: 16px;
|
||
|
||
.info-content,
|
||
.org-info-card {
|
||
padding: 16px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|