import { View, Text, Button, Image, Input, Textarea, Picker } from '@tarojs/components' import Taro, { useLoad, useShareAppMessage, useShareTimeline, useRouter } from '@tarojs/taro' import { useState } from 'react' import { getProjectDetail, createProject, updateProject, uploadProjectFile, submitProject, uploadMedia, getCompetitions, deleteProjectFile, getMyEnrollments } from '../../api' import './project.scss' export default function ProjectEdit() { const [project, setProject] = useState({ title: '', description: '', team_info: '', files: [] }) const [competitionId, setCompetitionId] = useState('') const [competitions, setCompetitions] = useState([]) const [loading, setLoading] = useState(false) const [isEdit, setIsEdit] = useState(false) const router = useRouter() useLoad((options) => { fetchCompetitions() const { id, competitionId } = options if (id) { setIsEdit(true) fetchProject(id) } else if (competitionId) { setCompetitionId(competitionId) } }) /** * 配置并监听分享给朋友的功能 */ useShareAppMessage(() => { const id = project?.id || router.params.id || '' const compId = competitionId || router.params.competitionId || '' return { title: project?.title || '提交作品', path: `/pages/competition/project?id=${id}&competitionId=${compId}`, imageUrl: project?.cover_image_url || project?.display_cover_image || '' } }) /** * 配置并监听分享到朋友圈的功能 */ useShareTimeline(() => { const id = project?.id || router.params.id || '' const compId = competitionId || router.params.competitionId || '' return { title: project?.title || '提交作品', query: `id=${id}&competitionId=${compId}`, imageUrl: project?.cover_image_url || project?.display_cover_image || '' } }) const fetchCompetitions = async () => { try { const res = await getMyEnrollments() if (res && res.length > 0) { const approvedEnrollments = res.filter((enrollment: any) => enrollment.status === 'approved') const competitions = approvedEnrollments.map((enrollment: any) => ({ id: enrollment.competition, title: enrollment.competition_title || '', status: enrollment.status })) setCompetitions(competitions) } else { setCompetitions([]) } } catch (e) { console.error('获取比赛列表失败', e) setCompetitions([]) } } const fetchProject = async (id) => { setLoading(true) try { const res = await getProjectDetail(id) setProject(res) setCompetitionId(res.competition) } catch (e) { Taro.showToast({ title: '加载项目失败', icon: 'none' }) } finally { setLoading(false) } } const handleInput = (key, value) => { setProject(prev => ({ ...prev, [key]: value })) } const handleUploadCover = async () => { try { const { tempFilePaths } = await Taro.chooseImage({ count: 1 }) if (!tempFilePaths.length) return Taro.showLoading({ title: '上传中...' }) const res = await uploadMedia(tempFilePaths[0], 'image') handleInput('cover_image_url', res.file) Taro.hideLoading() } catch (e: any) { Taro.hideLoading() const errorMsg = e.response?.error || e.message || '上传失败' Taro.showToast({ title: errorMsg, icon: 'none' }) } } const handleUploadFile = async () => { if (!project.id) { Taro.showToast({ title: '请先保存草稿再上传附件', icon: 'none' }) return } try { const res = await Taro.chooseMessageFile({ count: 1, type: 'file' }) const tempFiles = res.tempFiles if (!tempFiles.length) return Taro.showLoading({ title: '上传中...' }) const file = tempFiles[0] // @ts-ignore const result = await uploadProjectFile(file.path, project.id, file.name) // Update file list setProject(prev => ({ ...prev, files: [...(prev.files || []), result] })) Taro.hideLoading() Taro.showToast({ title: '上传成功', icon: 'success' }) } catch (e: any) { Taro.hideLoading() console.error(e) const errorMsg = e.response?.error || e.message || '上传失败' Taro.showToast({ title: errorMsg, icon: 'none' }) } } const handleDeleteFile = async (fileId) => { try { await Taro.showModal({ title: '确认删除', content: '确定要删除这个文件吗?', confirmColor: '#ff4d4f' }).then(res => { if (!res.confirm) throw new Error('cancel') }) Taro.showLoading({ title: '删除中...' }) await deleteProjectFile(fileId) setProject(prev => ({ ...prev, files: prev.files.filter(f => f.id !== fileId) })) Taro.hideLoading() Taro.showToast({ title: '删除成功', icon: 'success' }) } catch (e: any) { Taro.hideLoading() if (e.message !== 'cancel') { const errorMsg = e.response?.error || e.message || '删除失败' Taro.showToast({ title: errorMsg, icon: 'none' }) } } } const handlePreviewFile = (file) => { const fileUrl = file.file_url || file.file_url_display || '' if (!fileUrl) { Taro.showToast({ title: '文件地址无效', icon: 'none' }) return } const fileType = file.file_type || '' if (fileType === 'image') { Taro.previewImage({ urls: [fileUrl] }) } else { Taro.downloadFile({ url: fileUrl, success: (res) => { if (res.statusCode === 200) { Taro.openDocument({ filePath: res.tempFilePath, fileType: fileType === 'pdf' ? 'pdf' : fileType === 'ppt' ? 'ppt' : undefined }) } }, fail: () => { Taro.showToast({ title: '打开文件失败', icon: 'none' }) } }) } } const handleSave = async (submit = false) => { if (!project.title) { Taro.showToast({ title: '请输入项目标题', icon: 'none' }) return } setLoading(true) try { const data = { competition: competitionId, title: project.title, description: project.description, team_info: project.team_info, cover_image_url: project.cover_image_url } let res if (isEdit) { res = await updateProject(project.id, data) } else { res = await createProject(data) } if (submit) { await submitProject(res.id) Taro.showToast({ title: '提交成功', icon: 'success' }) setTimeout(() => Taro.navigateBack(), 1500) } else { Taro.showToast({ title: '保存成功', icon: 'success' }) if (!isEdit) { // 创建变编辑 setIsEdit(true) setProject(res) } } } catch (e) { Taro.showToast({ title: e.message || '操作失败', icon: 'none' }) } finally { setLoading(false) } } if (loading && !project.id && isEdit) return 加载中... return ( 所属比赛 { const idx = Number(e.detail.value) const selected = competitions[idx] if (selected) { setCompetitionId(String(selected.id)) } }} > {competitions.find(c => String(c.id) === String(competitionId))?.title || '请选择比赛'} 项目标题 handleInput('title', e.detail.value)} /> 封面图 {project.cover_image_url || project.display_cover_image ? ( ) : ( 点击上传封面 )} 项目介绍