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,
}))}
/>
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;