比赛
All checks were successful
Deploy to Server / deploy (push) Successful in 37s

This commit is contained in:
jeremygan2021
2026-03-10 13:47:28 +08:00
parent af763b1bee
commit 3d74ccc04f
9 changed files with 470 additions and 27 deletions

View File

@@ -1,6 +1,6 @@
import { View, Text, Button, Image, ScrollView } from '@tarojs/components'
import Taro, { useLoad } from '@tarojs/taro'
import { useState } from 'react'
import Taro, { useLoad, useDidShow } from '@tarojs/taro'
import { useState, useEffect } from 'react'
import { getCompetitionDetail, enrollCompetition, getMyCompetitionEnrollment, getProjects } from '../../api'
import MarkdownReader from '../../components/MarkdownReader'
import './detail.scss'
@@ -9,6 +9,7 @@ export default function CompetitionDetail() {
const [detail, setDetail] = useState<any>(null)
const [enrollment, setEnrollment] = useState<any>(null)
const [projects, setProjects] = useState<any[]>([])
const [myProject, setMyProject] = useState<any>(null)
const [activeTab, setActiveTab] = useState(0)
const [loading, setLoading] = useState(false)
@@ -21,11 +22,19 @@ export default function CompetitionDetail() {
}
})
useDidShow(() => {
// 每次显示页面时刷新一下我的项目信息(比如从编辑页返回)
if (detail?.id) {
fetchMyProject(detail.id)
}
})
const fetchDetail = async (id) => {
setLoading(true)
try {
const res = await getCompetitionDetail(id)
setDetail(res)
fetchMyProject(id)
} catch (e) {
Taro.showToast({ title: '加载详情失败', icon: 'none' })
} finally {
@@ -33,10 +42,62 @@ export default function CompetitionDetail() {
}
}
const fetchMyProject = async (competitionId) => {
try {
// 获取当前用户的所有项目,然后筛选出当前比赛的
// 或者直接调用 getProjects 并传入 contestant__user=me (如果后端支持)
// 目前后端 ProjectViewSet 默认返回所有submitted + 自己的draft/submitted
// 所以我们直接调 getProjects({ competition: competitionId }) 然后在前端找自己的
// 更好的方式:后端 ProjectViewSet 应该已经过滤了,返回列表中如果有一条是自己的,那就是自己的
// 但这里我们还是显式地请求一下,或者在 fetchProjects 的结果里找
const userInfo = Taro.getStorageSync('userInfo')
if (!userInfo) return
const res = await getProjects({ competition: competitionId })
const list = res.results || res
const myProj = list.find((p: any) => p.contestant_info?.nickname === userInfo.nickname) // 这是一个简化的判断,最好用 ID
// 由于 API 返回的 contestant_info 没有 user_id我们可能需要在 project 对象里加一个 is_mine 字段
// 或者,我们可以依赖后端返回的 contestant.user.id 与当前 user.id 比对。
// 但前端拿不到 contestant.user.id (ProjectSerializer 没返回)。
// 既然我们之前做了一个 getMyEnrollments我们可以通过 enrollment id 来匹配
// 但这里为了简便,我们可以假设 getProjects 返回的数据里,如果 contestant_info 匹配当前用户昵称... 不太靠谱
// 让我们修改 API 或者用另一种方式:
// 直接请求 getProjects带上一个特殊参数 mine=true ? 后端 ProjectViewSet 逻辑比较复杂
// 让我们回顾一下 ProjectViewSet:
// q |= Q(contestant__user=user)
// 所以返回的列表里肯定包含我的项目。
// 既然我们已经有 enrollment 信息,我们可以用 enrollment.id 来匹配 project.contestant
if (enrollment) {
const mine = list.find((p: any) => p.contestant === enrollment.id)
setMyProject(mine)
} else {
// 如果 enrollment 还没加载完,先不管,等 enrollment 加载完再匹配?
// 或者我们再次 fetchEnrollment 后再 fetchProjects
}
} catch (e) {
console.error(e)
}
}
const fetchEnrollment = async (id) => {
try {
const res = await getMyCompetitionEnrollment(id)
setEnrollment(res)
// 获取到 enrollment 后,去匹配 myProject
if (projects.length > 0) {
const mine = projects.find((p: any) => p.contestant === res.id)
setMyProject(mine)
} else {
// 如果 projects 还没加载,重新加载一次 projects 或者等待 fetchProjects 完成
// 其实 fetchProjects 也在运行,它完成后也会设置 projects
}
} catch (e) {
// 没报名则无数据,忽略
}
@@ -44,14 +105,45 @@ export default function CompetitionDetail() {
const fetchProjects = async (id) => {
try {
const res = await getProjects({ competition: id, status: 'submitted' })
// 如果后端返回了分页结果 { results: [], ... },则取 results否则直接取 res
// 注意:这里我们去掉了 status='submitted',因为我们要找自己的 draft
const res = await getProjects({ competition: id })
const list = res.results || res
setProjects(Array.isArray(list) ? list : [])
const allProjects = Array.isArray(list) ? list : []
// 过滤出 submitted 的给列表显示
const submittedProjects = allProjects.filter(p => p.status === 'submitted')
setProjects(submittedProjects)
// 尝试找自己的项目 (Draft or Submitted)
// 需要 enrollment 信息
// 这里暂时没法直接 setMyProject因为 enrollment 可能还没回来
// 我们在 useEffect 里监听 enrollment 和 projects 的变化来设置 myProject
} catch (e) {
console.error('Fetch projects failed', e)
}
}
// 监听变化设置 myProject
useEffect(() => {
if (enrollment && projects.length >= 0) { // projects could be empty
// 重新获取一次所有项目以包含 draft?
// 上面的 fetchProjects 已经把 submitted 过滤给 setProjects 了。
// 所以我们需要在 fetchProjects 里就把 allProjects 存下来?或者单独存 myProject
// 让我们重构 fetchProjects专门获取一次“我的项目”
fetchMySpecificProject(detail?.id, enrollment.id)
}
}, [enrollment])
const fetchMySpecificProject = async (compId, enrollId) => {
if (!compId || !enrollId) return
try {
const res = await getProjects({ competition: compId })
const list = res.results || res
const mine = list.find((p: any) => p.contestant === enrollId)
setMyProject(mine)
} catch (e) {}
}
const handleEnroll = async () => {
if (!detail) return
@@ -171,9 +263,27 @@ export default function CompetitionDetail() {
<View className='footer-action'>
{enrollment ? (
<Button disabled className='btn enrolled'>
{enrollment.status === 'approved' ? '已报名' : '审核中'}
</Button>
myProject ? (
<Button
className='btn enrolled'
onClick={() => Taro.navigateTo({ url: `/pages/competition/project?id=${myProject.id}` })}
>
({myProject.status === 'submitted' ? '已提交' : '草稿'})
</Button>
) : (
enrollment.status === 'approved' ? (
<Button
className='btn enrolled'
onClick={() => Taro.navigateTo({ url: `/pages/competition/project?competitionId=${detail.id}` })}
>
</Button>
) : (
<Button disabled className='btn enrolled'>
</Button>
)
)
) : (
<Button
className='btn enroll'