import React, { useState, useEffect, useMemo } from 'react'; import { Form, Input, Button, Tabs, DatePicker, InputNumber, Upload, Card, message, Space, Checkbox, Divider, Select, Alert } from 'antd'; import { UploadOutlined, PlusOutlined, MinusCircleOutlined } from '@ant-design/icons'; import axios from 'axios'; import API_BASE_URL from '../config'; import UserSearchSelect from '../components/UserSearchSelect'; import { maskPhone } from '../utils/phoneMask'; import { achievementTypes, uploadTips, contributorTips, dateTips, nameLabels, contributorLabels, dateLabels, roleOptions, contributorAddButtonLabels, contributorMainLabels, nameTips, sourceTips } from '../constants/achievements'; const { Option } = Select; const UploadPage = () => { const [form] = Form.useForm(); const [type, setType] = useState('project'); const [loading, setLoading] = useState(false); const [fileList, setFileList] = useState([]); // 项目类专用文件列表 const [projectProofFiles, setProjectProofFiles] = useState([]); // 立项证明 const [projectPlanFiles, setProjectPlanFiles] = useState([]); // 实施方案 const [projectSummaryFiles, setProjectSummaryFiles] = useState([]); // 验收总结 const [dicts, setDicts] = useState({ organizations: [], 'award-types': [], 'award-levels': [], 'paper-types': [], 'standard-types': [], 'project-categories': [] }); const fetchDicts = useMemo(() => async () => { const types = ['organizations', 'award-types', 'award-levels', 'paper-types', 'standard-types', 'project-categories']; const token = localStorage.getItem('token'); try { const results = await Promise.all( types.map(t => axios.get(`${API_BASE_URL}/api/dictionaries/${t}`, { headers: { Authorization: `Bearer ${token}` } })) ); const newDicts = {}; types.forEach((t, index) => { newDicts[t] = results[index].data; }); setDicts(newDicts); } catch (error) { console.error('获取字典失败', error); } }, []); useEffect(() => { fetchDicts(); }, [fetchDicts]); const renderDictSelectWithCustom = (name, label, dictData, rules = []) => { return ( <> prev[name] !== current[name]} > {({ getFieldValue }) => getFieldValue(name) === '其他' ? ( ) : null } ); }; const onFinish = async (values) => { setLoading(true); // 处理“其他”选项的自定义输入 const customFields = ['first_unit', 'assignee', 'owner_unit']; customFields.forEach(field => { if (values[field] === '其他') { values[field] = values[`${field}_custom`]; } delete values[`${field}_custom`]; }); // 前端去重校验 const contributors = values.contributors || []; const phoneSet = new Set(); for (const c of contributors) { if (c.user) { const u = JSON.parse(c.user); if (phoneSet.has(u.phone)) { message.error(`完成人列表中存在重复的员工:${u.real_name} (${maskPhone(u.phone)})。请删除重复项。`); setLoading(false); return; } phoneSet.add(u.phone); } } // 排名去重校验 if (!['project', 'transformation'].includes(type)) { const rankingSet = new Set(); for (const c of contributors) { if (c.ranking) { if (rankingSet.has(c.ranking)) { message.error(`完成人排名不能重复:第 ${c.ranking} 名出现了多次。`); setLoading(false); return; } rankingSet.add(c.ranking); } } } // 附件校验 if (type === 'project') { if (projectProofFiles.length === 0) { message.error('请上传立项证明'); setLoading(false); return; } if (projectPlanFiles.length === 0) { message.error('请上传实施方案'); setLoading(false); return; } if (projectSummaryFiles.length === 0) { message.error('请上传验收总结'); setLoading(false); return; } } else { if (fileList.length === 0) { message.error('请至少上传一个附件'); setLoading(false); return; } } const formData = new FormData(); formData.append('type', type); const finalContributors = (values.contributors || []).map(c => { const u = c.user ? JSON.parse(c.user) : {}; return { name: u.real_name, phone: u.phone, role: c.role, isMain: c.isMain, ranking: c.ranking }; }).filter(c => c.name); const { name, achievement_date, remarks, contributors: _ignoredContributors, approver, ...details } = values; formData.append('name', name); formData.append('contributors', JSON.stringify(finalContributors)); formData.append('achievement_date', achievement_date ? achievement_date.format('YYYY-MM-DD') : ''); formData.append('remarks', remarks || ''); // 处理签批领导 if (type === 'report' && approver) { try { const approverUser = JSON.parse(approver); details.approver_id = approverUser.id; details.approver_name = approverUser.real_name; } catch (e) { console.error('解析签批领导信息失败', e); } } formData.append('details', JSON.stringify(details)); if (type === 'project') { // 项目类附件处理:添加前缀以区分 projectProofFiles.forEach(file => { const newFile = new File([file.originFileObj], `[立项证明]-${file.name}`, { type: file.type }); formData.append('attachments', newFile); }); projectPlanFiles.forEach(file => { const newFile = new File([file.originFileObj], `[实施方案]-${file.name}`, { type: file.type }); formData.append('attachments', newFile); }); projectSummaryFiles.forEach(file => { const newFile = new File([file.originFileObj], `[验收总结]-${file.name}`, { type: file.type }); formData.append('attachments', newFile); }); } else { fileList.forEach(file => { formData.append('attachments', file.originFileObj); }); } try { const token = localStorage.getItem('token'); await axios.post(`${API_BASE_URL}/api/achievements`, formData, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'multipart/form-data' } }); message.success('成果上传成功,请等待管理员审核'); form.resetFields(); setFileList([]); setProjectProofFiles([]); setProjectPlanFiles([]); setProjectSummaryFiles([]); } catch (error) { message.error(error.response?.data?.message || '上传失败'); } finally { setLoading(false); } }; const renderCommonFields = () => { const hasRanking = !['project', 'transformation'].includes(type); const useCheckbox = ['project', 'transformation'].includes(type); const isPaper = type === 'paper'; return ( <> 成果名称 {nameTips[type] && ( )} 完成人信息 {contributorTips[type] && ( )} {(fields, { add, remove }) => ( <> {fields.length > 0 && ( *姓名 {hasRanking && ( *排名 )} {isPaper && ( *角色 )} {useCheckbox && ( {contributorMainLabels[type] || contributorMainLabels.default} )} )} {fields.map(({ key, name, ...restField }) => ( {hasRanking && ( )} {isPaper && ( )} {useCheckbox && ( {contributorMainLabels[type] || contributorMainLabels.default} )} {fields.length > 1 && ( remove(name)} /> )} ))} )} 成果信息 {dateTips[type] && ( )} ); }; const renderSpecificFields = () => { switch (type) { case 'paper': return ( <> {renderDictSelectWithCustom('first_unit', '完成单位', dicts['organizations'], [{ required: true, message: '请选择完成单位' }])} ); case 'project': return ( <> {sourceTips[type] && ( )} ); case 'award': return ( <> ); case 'standard': return ( <> ); case 'monograph': return ( <> ); case 'report': return ( <> ); case 'plan': return null; case 'patent': return ( <> {renderDictSelectWithCustom('assignee', '权利人', dicts['organizations'], [{ required: true, message: '请选择权利人' }])} ); case 'transformation': return ( <> ); case 'software': return ( <> {renderDictSelectWithCustom('owner_unit', '著作权人', dicts['organizations'], [{ required: true, message: '请选择著作权人' }])} ); default: return

未知类型

; } }; return ( { setType(key); form.resetFields(); setFileList([]); setProjectProofFiles([]); setProjectPlanFiles([]); setProjectSummaryFiles([]); }} items={achievementTypes.map(t => ({ key: t.key, label: t.label, }))} />
{renderCommonFields()} {renderSpecificFields()} 附件上传 {type === 'project' ? ( <> false} fileList={projectProofFiles} onChange={({ fileList }) => setProjectProofFiles(fileList)} multiple > false} fileList={projectPlanFiles} onChange={({ fileList }) => setProjectPlanFiles(fileList)} multiple > false} fileList={projectSummaryFiles} onChange={({ fileList }) => setProjectSummaryFiles(fileList)} multiple > ) : ( {uploadTips[type] && ( )} false} fileList={fileList} onChange={({ fileList }) => setFileList(fileList)} multiple > )}
); }; export default UploadPage;