197 lines
6.6 KiB
TypeScript
197 lines
6.6 KiB
TypeScript
import { View, Text, Image, Button, ScrollView } from '@tarojs/components'
|
|
import Taro, { useLoad, useShareAppMessage, useShareTimeline, useRouter } from '@tarojs/taro'
|
|
import { useState } from 'react'
|
|
import { getProjectDetail, getComments } from '../../api'
|
|
import MarkdownReader from '../../components/MarkdownReader'
|
|
import './project-detail.scss'
|
|
|
|
export default function ProjectDetail() {
|
|
const [project, setProject] = useState<any>(null)
|
|
const [comments, setComments] = useState<any[]>([])
|
|
const [loading, setLoading] = useState(false)
|
|
const router = useRouter()
|
|
|
|
useLoad((options) => {
|
|
const { id } = options
|
|
if (id) {
|
|
fetchProject(id)
|
|
fetchComments(id)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 配置并监听分享给朋友的功能
|
|
*/
|
|
useShareAppMessage(() => {
|
|
const id = project?.id || router.params.id || ''
|
|
return {
|
|
title: project?.title || '项目详情',
|
|
path: `/pages/competition/project-detail?id=${id}`,
|
|
imageUrl: project?.display_cover_image || project?.cover_image_url || ''
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 配置并监听分享到朋友圈的功能
|
|
*/
|
|
useShareTimeline(() => {
|
|
const id = project?.id || router.params.id || ''
|
|
return {
|
|
title: project?.title || '项目详情',
|
|
query: `id=${id}`,
|
|
imageUrl: project?.display_cover_image || project?.cover_image_url || ''
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 获取项目详情
|
|
* @param id 项目ID
|
|
*/
|
|
const fetchProject = async (id) => {
|
|
setLoading(true)
|
|
try {
|
|
const res = await getProjectDetail(id)
|
|
setProject(res)
|
|
} catch (e) {
|
|
Taro.showToast({ title: '加载项目详情失败', icon: 'none' })
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取项目评语
|
|
* @param id 项目ID
|
|
*/
|
|
const fetchComments = async (id) => {
|
|
try {
|
|
const res = await getComments({ project: id })
|
|
const list = res.results || res.data || res || []
|
|
setComments(Array.isArray(list) ? list : [])
|
|
} catch (e) {
|
|
console.error('获取评语失败', e)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 打开/下载附件
|
|
* @param file 文件对象
|
|
*/
|
|
const handleOpenFile = (file) => {
|
|
if (!file.file) return
|
|
|
|
// 如果是图片,预览
|
|
if (file.file.match(/\.(jpg|jpeg|png|gif)$/i)) {
|
|
Taro.previewImage({ urls: [file.file] })
|
|
return
|
|
}
|
|
|
|
// 其他文件尝试下载打开
|
|
Taro.showLoading({ title: '下载中...' })
|
|
Taro.downloadFile({
|
|
url: file.file,
|
|
success: (res) => {
|
|
const filePath = res.tempFilePath
|
|
Taro.openDocument({
|
|
filePath,
|
|
success: () => console.log('打开文档成功'),
|
|
fail: (err) => {
|
|
console.error(err)
|
|
Taro.showToast({ title: '打开文件失败', icon: 'none' })
|
|
}
|
|
})
|
|
},
|
|
fail: () => {
|
|
Taro.showToast({ title: '下载文件失败', icon: 'none' })
|
|
},
|
|
complete: () => {
|
|
Taro.hideLoading()
|
|
}
|
|
})
|
|
}
|
|
|
|
if (loading || !project) return <View className='loading'>加载中...</View>
|
|
|
|
return (
|
|
<ScrollView scrollY className='project-detail'>
|
|
<Image
|
|
className='cover'
|
|
mode='aspectFill'
|
|
src={project.display_cover_image || project.cover_image_url || 'https://via.placeholder.com/400x200'}
|
|
/>
|
|
|
|
<View className='content'>
|
|
<View className='header'>
|
|
<Text className='title'>{project.title}</Text>
|
|
<View className='author'>
|
|
<Image className='avatar' src={project.contestant_info?.avatar_url || ''} />
|
|
<Text className='name'>{project.contestant_info?.nickname || '参赛者'}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View className='section'>
|
|
<Text className='section-title'>项目介绍</Text>
|
|
<View className='text-content'>
|
|
{project.description ? <MarkdownReader content={project.description} /> : <Text className='empty'>暂无介绍</Text>}
|
|
</View>
|
|
</View>
|
|
|
|
<View className='section'>
|
|
<Text className='section-title'>团队介绍</Text>
|
|
<View className='text-content'>
|
|
<Text>{project.team_info || '暂无团队信息'}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View className='section'>
|
|
<Text className='section-title'>项目附件</Text>
|
|
{project.files && project.files.length > 0 ? (
|
|
<View className='file-list'>
|
|
{project.files.map((file, index) => (
|
|
<View key={index} className='file-item' onClick={() => handleOpenFile(file)}>
|
|
<View style={{ flex: 1 }}>
|
|
<Text className='file-name'>{file.name || '附件 ' + (index + 1)}</Text>
|
|
{file.file_size_display && (
|
|
<Text style={{ fontSize: '12px', color: '#999' }}>{file.file_size_display}</Text>
|
|
)}
|
|
</View>
|
|
<Text className='file-action'>查看</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
) : (
|
|
<Text className='empty'>暂无附件</Text>
|
|
)}
|
|
</View>
|
|
|
|
<View className='section comments-section'>
|
|
<Text className='section-title'>评委评语</Text>
|
|
{comments.length > 0 ? (
|
|
<View className='comment-list'>
|
|
{comments.map((c) => (
|
|
<View key={c.id} className='comment-item'>
|
|
<View className='comment-header'>
|
|
<View className='judge-info'>
|
|
<Text className='judge-name'>{c.judge_name || '评委'}</Text>
|
|
{c.score && (
|
|
<View className='judge-score-box'>
|
|
<Text className='score-num'>{c.score}</Text>
|
|
<Text className='score-unit'>分</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
<Text className='comment-time'>{c.created_at?.substring(0, 16)}</Text>
|
|
</View>
|
|
<Text className='comment-content'>{c.content}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
) : (
|
|
<Text className='empty'>暂无评语</Text>
|
|
)}
|
|
</View>
|
|
</View>
|
|
</ScrollView>
|
|
)
|
|
}
|