This commit is contained in:
102
miniprogram/src/pages/competition/detail.scss
Normal file
102
miniprogram/src/pages/competition/detail.scss
Normal file
@@ -0,0 +1,102 @@
|
||||
.comp-detail {
|
||||
background-color: #000;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 80px;
|
||||
|
||||
.banner {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px;
|
||||
background: #111;
|
||||
border-radius: 16px 16px 0 0;
|
||||
margin-top: -24px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 14px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background: #333;
|
||||
color: #ccc;
|
||||
margin-left: 12px;
|
||||
white-space: nowrap;
|
||||
|
||||
&.registration { background: #07c160; color: #fff; }
|
||||
&.submission { background: #1890ff; color: #fff; }
|
||||
&.judging { background: #faad14; color: #fff; }
|
||||
&.ended { background: #ff4d4f; color: #fff; }
|
||||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 32px;
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
border-left: 4px solid #00b96b;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
rich-text {
|
||||
font-size: 16px;
|
||||
color: #ccc;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-action {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #1f1f1f;
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid #333;
|
||||
z-index: 100;
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
border-radius: 24px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
background: #00b96b;
|
||||
border: none;
|
||||
|
||||
&.disabled {
|
||||
background: #333;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.enrolled {
|
||||
background: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
111
miniprogram/src/pages/competition/detail.tsx
Normal file
111
miniprogram/src/pages/competition/detail.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import { View, Text, Button, Image, ScrollView, RichText } from '@tarojs/components'
|
||||
import Taro, { useLoad } from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
import { getCompetitionDetail, enrollCompetition, getMyCompetitionEnrollment } from '../../api'
|
||||
import './detail.scss'
|
||||
|
||||
export default function CompetitionDetail() {
|
||||
const [detail, setDetail] = useState<any>(null)
|
||||
const [enrollment, setEnrollment] = useState<any>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
useLoad((options) => {
|
||||
const { id } = options
|
||||
if (id) {
|
||||
fetchDetail(id)
|
||||
fetchEnrollment(id)
|
||||
}
|
||||
})
|
||||
|
||||
const fetchDetail = async (id) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await getCompetitionDetail(id)
|
||||
setDetail(res)
|
||||
} catch (e) {
|
||||
Taro.showToast({ title: '加载详情失败', icon: 'none' })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchEnrollment = async (id) => {
|
||||
try {
|
||||
const res = await getMyCompetitionEnrollment(id)
|
||||
setEnrollment(res)
|
||||
} catch (e) {
|
||||
// 没报名则无数据,忽略
|
||||
}
|
||||
}
|
||||
|
||||
const handleEnroll = async () => {
|
||||
if (!detail) return
|
||||
try {
|
||||
await enrollCompetition(detail.id, { role: 'contestant' })
|
||||
Taro.showToast({ title: '报名成功', icon: 'success' })
|
||||
fetchEnrollment(detail.id)
|
||||
} catch (e) {
|
||||
Taro.showToast({ title: e.message || '报名失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const map = {
|
||||
'registration': '报名中',
|
||||
'submission': '作品提交中',
|
||||
'judging': '评审中',
|
||||
'ended': '已结束',
|
||||
}
|
||||
return map[status] || status
|
||||
}
|
||||
|
||||
if (loading || !detail) return <View className='loading'>加载中...</View>
|
||||
|
||||
return (
|
||||
<ScrollView scrollY className='comp-detail'>
|
||||
<Image
|
||||
className='banner'
|
||||
mode='aspectFill'
|
||||
src={detail.display_cover_image || 'https://via.placeholder.com/400x200'}
|
||||
/>
|
||||
|
||||
<View className='content'>
|
||||
<View className='header'>
|
||||
<Text className='title'>{detail.title}</Text>
|
||||
<Text className={`status ${detail.status}`}>{getStatusText(detail.status)}</Text>
|
||||
</View>
|
||||
|
||||
<View className='section'>
|
||||
<Text className='section-title'>简介</Text>
|
||||
<RichText nodes={detail.description} />
|
||||
</View>
|
||||
|
||||
<View className='section'>
|
||||
<Text className='section-title'>规则</Text>
|
||||
<RichText nodes={detail.rule_description} />
|
||||
</View>
|
||||
|
||||
<View className='section'>
|
||||
<Text className='section-title'>参赛条件</Text>
|
||||
<RichText nodes={detail.condition_description} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='footer-action'>
|
||||
{enrollment ? (
|
||||
<Button disabled className='btn enrolled'>
|
||||
{enrollment.status === 'approved' ? '已报名' : '审核中'}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
className='btn enroll'
|
||||
onClick={handleEnroll}
|
||||
disabled={detail.status !== 'registration'}
|
||||
>
|
||||
{detail.status === 'registration' ? '立即报名' : '报名未开始/已结束'}
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
85
miniprogram/src/pages/competition/index.scss
Normal file
85
miniprogram/src/pages/competition/index.scss
Normal file
@@ -0,0 +1,85 @@
|
||||
.competition-page {
|
||||
background-color: #000;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
|
||||
.comp-list {
|
||||
.comp-card {
|
||||
background: #1f1f1f;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 24px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
|
||||
.cover {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 16px;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
flex: 1;
|
||||
margin-right: 12px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background: #333;
|
||||
color: #ccc;
|
||||
|
||||
&.registration { background: #07c160; color: #fff; }
|
||||
&.submission { background: #1890ff; color: #fff; }
|
||||
&.judging { background: #faad14; color: #fff; }
|
||||
&.ended { background: #ff4d4f; color: #fff; }
|
||||
}
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: 1px solid #333;
|
||||
padding-top: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.time {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import './index.scss'
|
||||
export default function CompetitionList() {
|
||||
const [competitions, setCompetitions] = useState<any[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [debugMsg, setDebugMsg] = useState('')
|
||||
|
||||
useLoad(() => {
|
||||
fetchCompetitions()
|
||||
@@ -14,12 +15,21 @@ export default function CompetitionList() {
|
||||
|
||||
const fetchCompetitions = async () => {
|
||||
setLoading(true)
|
||||
setDebugMsg('开始加载...')
|
||||
try {
|
||||
console.log('Fetching competitions...')
|
||||
const res = await getCompetitions()
|
||||
console.log('Competitions res:', res)
|
||||
setDebugMsg(`请求成功: 数量 ${res?.results?.length}`)
|
||||
|
||||
if (res && res.results) {
|
||||
setCompetitions(res.results)
|
||||
} else {
|
||||
setDebugMsg(`数据格式异常: ${JSON.stringify(res)}`)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Fetch failed:', e)
|
||||
setDebugMsg(`请求失败: ${e.errMsg || JSON.stringify(e)}`)
|
||||
Taro.showToast({ title: '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
@@ -32,6 +42,7 @@ export default function CompetitionList() {
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const map = {
|
||||
'published': '即将开始',
|
||||
'registration': '报名中',
|
||||
'submission': '作品提交中',
|
||||
'judging': '评审中',
|
||||
@@ -66,7 +77,12 @@ export default function CompetitionList() {
|
||||
</View>
|
||||
))}
|
||||
{!loading && competitions.length === 0 && (
|
||||
<View className='empty'>暂无比赛</View>
|
||||
<View className='empty'>
|
||||
<Text>暂无比赛</Text>
|
||||
<View style={{ marginTop: 20, color: '#666', fontSize: 12, wordBreak: 'break-all', padding: 20 }}>
|
||||
调试信息: {debugMsg}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
@@ -34,6 +34,7 @@ export default function UserIndex() {
|
||||
const goWithdraw = () => Taro.navigateTo({ url: '/subpackages/distributor/withdraw' })
|
||||
|
||||
const goActivityList = (tab = 'all') => Taro.navigateTo({ url: `/subpackages/forum/activity/index?tab=${tab}` })
|
||||
const goCompetitionList = () => Taro.navigateTo({ url: '/pages/competition/index' })
|
||||
|
||||
const handleAddress = async () => {
|
||||
try {
|
||||
@@ -260,6 +261,7 @@ export default function UserIndex() {
|
||||
{ title: '我的订单', icon: '📦', action: goOrders },
|
||||
{ title: '地址管理', icon: '📝', action: handleAddress },
|
||||
{ title: '活动管理', icon: '⌚️', action: () => goActivityList('mine') },
|
||||
{ title: '赛事中心', icon: '🏆', action: goCompetitionList },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user