forgeplus-react/src/military/task/taskDetail/index.jsx

601 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { forwardRef, useEffect, useState, useCallback, useMemo } from 'react';
import { Form, Input, Button, Modal, Checkbox, Tooltip } from 'antd';
import classNames from 'classnames';
import moment from 'moment';
import { Link } from "react-router-dom";
import { formatDuring, getImageUrl } from 'educoder';
import Upload from '../../components/Upload';
import StatusNav from '../../components/statusNav';
import ItemListPaper from '../components/itemListPaper';
import ProofModal from '../components/proofModal';
import { getTaskDetail, getTaskCategory, getTaskPaper, makePublic, addPaper, getAgreement, agreement, checkAgreement, checkHavePaper, addExpertReview, followTask, unfollowTask } from '../api';
import { taskModeIdArr, applyStatusArr, applyStatusAllArr, agreementContent, paperCheckTextArr, surplusTime } from '../static';
import { httpUrl } from '../fetch';
import './index.scss';
import { getRules } from 'src/military/expert/api';
const { TextArea } = Input;
const taskModeNameArr = [];
for (const item of taskModeIdArr) {
taskModeNameArr[item.dicItemCode] = item.dicItemName;
}
const applyStatusAllNameArr = [];
for (const item of applyStatusAllArr) {
applyStatusAllNameArr[item.dicItemCode] = item.dicItemName;
}
export default Form.create()(
forwardRef((props, ref) => {
const { match, current_user, form, history, showNotification, mygetHelmetapi, showLoginDialog } = props;
const id = match.params.taskId;
const { getFieldDecorator, validateFields, setFieldsValue } = form;
const [detailData, setDetailData] = useState({});
const [taskCategoryValueArr, setTaskCategoryValueArr] = useState([]);
const [fileList, setFileList] = useState(null);
const [applyModal, setApplyModal] = useState(false);
const [applyContent, setApplyContent] = useState({ title: '应征投稿协议内容', content: agreementContent });
const [agreementCheckBox, setAgreementCheckBox] = useState(false);
const [signAgreement, setSignAgreement] = useState(false);
const [isPaper, setIsPaper] = useState(false);
const [paperUploadLoading, setPaperUploadLoading] = useState(false);
const [status, setStatus] = useState('');
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [dataList, setDataList] = useState([]);
const [loading, setLoading] = useState(false);
const [reload, setReload] = useState(0);
const [relaodChildList, setRelaodChildList] = useState(0);
const [visibleProofs, setVisibleProofs] = useState(false);
// 已发布评审任务 评审规则
const [publishedReviewRules, setPublishedReviewRules] = useState(undefined);
// useEffect(()=>{
// !current_user.login&&showLoginDialog();
// },[current_user.login]);
// 获取任务领域配置数据
useEffect(() => {
getTaskCategory().then(data => {
if (data) {
const taskCategoryValueArr = [];
for (const item of data) {
taskCategoryValueArr[item.id] = item.name;
}
setTaskCategoryValueArr(taskCategoryValueArr);
}
});
}, []);
// 获取本任务详情
useEffect(() => {
id && getTaskDetail(id).then(data => {
if (!data) {
history.push('/task');
}
setDetailData(data || {});
if (data && data.assignRuleAndExperts) {
getRules({ containerId: data.id, containerType: 1, statusString: '-1,1,2', }).then(response => {
response && setPublishedReviewRules(response.data || undefined);
})
}
});
}, [id, reload]);
// 检查用户是否同意协议
useEffect(() => {
current_user.login && id && checkAgreement(id).then(res => {
if (res && res.data && res.data.status === 1) {
setSignAgreement(true);
}
})
}, [current_user.login]);
// 检查用户是否上传成果
useEffect(() => {
current_user.login && id && checkHavePaper(id).then(res => {
if (res && res.data && res.data.status === 1) {
setIsPaper(true);
}
})
}, [current_user.login]);
const taskLimit = useMemo(() => {
if (current_user.admin) {
return true;
}
if (detailData.user) {
return current_user.login === detailData.user.login
}
}, [detailData, current_user])
// 获取协议内容
useEffect(() => {
applyModal && current_user.login && getAgreement(1).then(res => {
if (res && res.data) {
setApplyContent({
title: res.data.title,
content: res.data.content,
});
}
});
}, [applyModal, current_user.login]);
// 获取成果列表
useEffect(() => {
// 等加载完成果详情再加载成果列表
if (detailData.id) {
setLoading(true);
let params = {
taskId: id,
orderBy: '',
pageSize: 10,
curPage,
status,
}
getTaskPaper(params).then(data => {
if (data && Array.isArray(data.rows)) {
for (const item of data.rows) {
item.detail = item.paperDetail ? item.paperDetail.content : "";
}
data.rows.sort((a, b) => {
return b.status - a.status
});
}
setDataList(data.rows || []);
setLoading(false);
setTotal(data.total);
});
}
}, [id, status, curPage, reload, relaodChildList, detailData, current_user.login]);
// 流程步骤显示,返回剩余时间
const process = useCallback((title, status, days) => {
let { surplus,
surplusTimetext,
delayTime
} = surplusTime(detailData);
return (
<li key={title} className={classNames({ 'active': (detailData.currentStatus !== 9 && detailData.currentStatus >= status), 'except-close': (status === 8 && detailData.exceptClosedBoolean) })} >
<span>{title}</span>
{detailData.status !== status && days ? <p className="color-grey-6 font-12">{days}</p> : ''}
{/* 因为有时只延期几秒或者几分钟时后端没有返回延期,所以这里加一个判断 */}
{detailData.status === status && days ? <p className="color-grey-6 font-12">{delayTime}</p> : ''}
{detailData.status === status && detailData.cancelStatus === 1 && surplus > 0 && <p className="delay-text">(手动延期)</p>}
{detailData.status === status && detailData.delayed && detailData.cancelStatus === 0 && surplus > 0 && detailData.delayCount && <p className="delay-text">(系统自动延期)</p>}
</li>
)
}, [detailData]);
function downFile(item) {
if (!current_user.login) {
showLoginDialog();
return;
}
let url = httpUrl + '/busiAttachments/download/' + item.id;
window.open(url);
}
const helper = useCallback(
(label, name, rules, widget) => (
<Form.Item label={label}>
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
</Form.Item>
),
[]
);
// 上传附件后得到的文件数组
function UploadFunc(fileList) {
setFileList(fileList);
let files = [];
for (const item of fileList) {
files.push(item.id || (item.response.data && item.response.data.id));
}
setFieldsValue({
files: files.join()
});
}
// 提交成果
function saveItem() {
validateFields((error, values) => {
if (!error) {
let params = {
...values,
taskId: id
};
if (!params.files){
delete params.files;
}
setPaperUploadLoading(true);
addPaper(params).then((res) => {
setPaperUploadLoading(false);
if (res.message === 'success') {
showNotification('成果提交成功');
setIsPaper(true);
setReload(Math.random());
setCurPage(1);
}
});
}
})
}
function changeOptionId(option) {
setStatus(option.dicItemCode.toString() || '');
setCurPage(1);
}
// 签订协议
function agreementSign() {
if (!agreementCheckBox) {
showNotification("请阅读并同意本电子协议内容!");
return;
}
agreement(id).then(res => {
if (res.message === 'success') {
Modal.success({
content: '签订成功!',
});
setApplyModal(false);
setSignAgreement(true);
}
});
}
function showUser() {
if (dataList.length === 0) {
Modal.info({
title: '提示',
content: '暂无应征者提交',
});
} else {
Modal.confirm({
title: '提示',
content: '确认公示应征者信息',
onOk: () => {
makePublic(id).then(res => {
if (res && res.message === 'success') {
setReload(Math.random());
} else {
showNotification('操作失败');
}
})
}
})
}
}
function addExpertReviewModal() {
Modal.confirm({
title: '提示',
content: '确定将此创客任务添加专家评审流程?',
onOk: () => {
addExpertReview(id, 1).then(res => {
if (res && res.message === 'success') {
setReload(Math.random());
} else {
showNotification('操作失败');
}
})
}
})
}
const reloadList = useCallback(() => {
setRelaodChildList(Math.random());
});
const reloadDetail = useCallback(() => {
setReload(Math.random());
}, []);
function certificationCheck() {
if (current_user.authentication || current_user.enterpriseCertification) {
setApplyModal(true);
} else {
Modal.confirm({
content: "请先完成实名认证再提交需求申请,是否前往认证?",
onOk() {
window.location.href = `${mygetHelmetapi && mygetHelmetapi.main_web_site_url}/users/${current_user.login}/profiles`;
}
})
}
}
const signContent = useCallback(() => {
if (signAgreement && isPaper) {
let checkStatustext;
for (const item of dataList) {
if (item.user.login === current_user.login) {
checkStatustext = paperCheckTextArr[item.checkStatus];
}
}
return <div className="edu-back-white padding30 mt20 font-16 color-orange text-center">
{checkStatustext || paperCheckTextArr[2]}
</div>
} else if (signAgreement) {
return (<div className="edu-back-white padding30 mt20">
<div className="font-16 font-bd">我要应征投稿</div>
{helper(
"",
"content",
[{ required: true, message: "请根据具体要求提交有效的稿件才能打动需求方哟成果描述不能超过250字哟" },
{ max: 250, message: '长度不能超过250个字符' }],
<TextArea
placeholder="请根据具体要求提交有效的稿件,才能打动需求方哟!"
autoSize={{ minRows: 6 }}
className="applyText"
/>
)}
<Form.Item >
<Upload
className="commentStyle"
load={UploadFunc}
size={50}
showNotification={showNotification}
actionUrl={httpUrl}
fileList={fileList}
/>
{getFieldDecorator('files', {
validateFirst: true
})(<Input style={{ display: 'none' }} />)}
</Form.Item>
<Button className="mr20" type={"primary"} loading={paperUploadLoading} onClick={() => { saveItem() }}>提交</Button>
</div>
)
} else if (detailData.user && (current_user.login !== detailData.user.login)) {
return <div className="edu-back-white padding30 mt20 text-center">
{/* 为创客大赛暂时disable应征投稿 */}
<Button className="mr20" type={"primary"} onClick={certificationCheck} disabled={detailData.enterpriseName == "taohuayuan"}>我要应征投稿</Button>
</div>
}
}, [signAgreement, isPaper, current_user, detailData, dataList]);
function goUser(login) {
window.location.href = mygetHelmetapi && mygetHelmetapi.main_web_site_url + `/accounts/${login}`;
}
function goUserProfiles() {
window.open(mygetHelmetapi && mygetHelmetapi.main_web_site_url + `/users/${current_user.login}/profiles`);
}
function backPublicEnd(makePublicAt, makePublicDays) {
return moment(new Date(makePublicAt).getTime() + makePublicDays * 24 * 3600 * 1000).format('YYYY-MM-DD HH:mm:ss');
}
function follow() {
followTask({
userId: current_user.user_id,
watchableId: id,
watchableType: "MakerTask"
}).then(res => {
if (res.message == 'success') {
setReload(Math.random());
}
})
}
function unfollow() {
unfollowTask({
userId: current_user.user_id,
watchableId: id,
watchableType: "MakerTask"
}).then(res => {
if (res.message == 'success') {
setReload(Math.random());
}
})
}
return (
<div className="centerbox task-detail">
<div className="head-navigation">
<Link to="/task">创客空间 &gt;</Link>
<Link to="/task">任务大厅 &gt;</Link>
&nbsp;任务编号{detailData.number}
</div>
<div className="edu-back-white padding30">
<div className="df mb20">
<div className="mr30">
<a onClick={() => { goUser(detailData.user.login) }} alt="用户头像">
<img alt="头像加载失败" className="bor-radius-all" height="60" src={detailData.user && getImageUrl(detailData.user.logo)} width="60" />
</a>
<p className="lineh-20 mt10 edu-txt-center">{detailData && detailData.user && (detailData.user.nickname || detailData.user.login)}</p>
</div>
<div className="flex1">
<div className="task-title mb10">
<span>
<span className="font-18 mr20 font-bd task-tit-text fl" >{detailData.name}</span>
<span className="task_tag">{taskCategoryValueArr[detailData.categoryId]}</span>
</span>
<span>
{
current_user && current_user.roles && current_user.roles.includes('管理员') && <Link class="adminEdit" to={`/task/taskEdit/${id}`}>编辑</Link>
}
<span className='detail_tag_btn '>
{
detailData.canFollow ? <span className='detail_tag_btn_name' onClick={follow}>
<i className="iconfont icon-kongxing font-16 mr3"></i>
关注
</span> : <span className='detail_tag_btn_name' onClick={unfollow}>
<i className="iconfont icon-shixing color-orange font-16 mr3"></i>
取消关注
</span>
}
<span className="detail_tag_btn_count">{detailData.followCount}</span>
</span>
</span>
</div>
<div className="clearfix flex1">
<ul className="fl">
<li><span className="mr10 color-grey9">悬赏模式</span><span className="color-grey3">{taskModeNameArr[detailData.taskModeId]}</span></li>
<li><span className="mr10 color-grey9">任务编号</span><span className="color-grey3">{detailData.number}</span></li>
<li><span className="mr10 color-grey9">发布时间</span><span className="color-grey3">{detailData.publishedAt || detailData.createdAt}</span></li>
</ul>
<div className="fr edu-txt-right mt10">
<div className="color-orange font-bd lineh-30"><span className="font-18"></span><span className="font-28">{detailData.bounty}</span></div>
</div>
</div>
</div>
</div>
{
detailData.status === 5 &&
<p className="color-orange mb10 task_tip fl">
<i className="iconfont icon-laba fl mr5 color-orange font-15"></i>稿{detailData.makePublicDays}{backPublicEnd(detailData.makePublicAt, detailData.makePublicDays)}
</p>}
<div className="clearfix tasks_status_father mb30" style={{ background: "#FAFAFA" }}>
<ul className="tasks_status clearfix">
<li className="active"><span>需求提报</span></li>
{process('成果提交', 3, detailData.collectingDays)}
{process('成果评选', 4, detailData.choosingDays)}
{process('结果公示', 5, detailData.makePublicDays)}
{process('任务协议签订', 6, detailData.signingDays)}
{process('支付', 7, detailData.payingDays)}
{
detailData.exceptClosedBoolean ? process('任务关闭', 8) : process('任务完成', 8)
}
</ul>
</div>
<div className="font-16 font-bd">任务详情</div>
{/* 富文本内容插入 */}
<div className="content-text editor-w-text" dangerouslySetInnerHTML={{ __html: detailData.description }}></div>
{detailData.uploadFileNumbers && <React.Fragment>
<div className="font-16 font-bd">任务文件</div>
{
detailData.tasksAttachments && detailData.tasksAttachments.map(item => {
return <div className="file-list-box" key={item.id}>
<a onClick={() => { downFile(item) }}><i className="iconfont icon-fujian color-green font-14 mr3"></i>
{item.fileName} </a>
<span className="ml10 color-grey-9">({item.fileSizeString})</span>
</div>
})
}
</React.Fragment>
}
{/* 配合创客大赛,暂时注释,以后还原 */}
{/* <div className="font-16 font-bd mt10">知识产权说明:</div>
<p className="color-grey-6 lineh-20 padding10-15 mb10">
1、参赛作品一经采用其所有权、修改权和使用权均归主办方所有设计者不得再在任何地方使用<br />
2、应征者所提交的作品必须由应征者本人创作或参与创作应征者应确认其作品的原创性主办单位不承担因作品侵犯他人或单位的权利而产生的法律责任其法律责任由应征者本人承担。
</p>
<div className="font-16 font-bd">交稿声明:</div>
<p className="color-grey-6 lineh-20 padding10-15 mb10">
应征者提交的稿件必须是设计作品,广告等无效交稿一律不采用!
</p> */}
{publishedReviewRules && <React.Fragment>
<div className="font-16 font-bd">评审规则</div>
<p className="color-grey-6 lineh-20 padding10-15 mb10">{publishedReviewRules.rule}</p>
<div className="font-16 font-bd">评分标准</div>
<p className="color-grey-6 lineh-20 padding10-15 mb10">{publishedReviewRules.criterias.map(item => { return <p>{item}</p> })}</p>
<div className="font-16 font-bd">评审时间</div>
<p className="color-grey-6 lineh-20 padding10-15 mb10">{publishedReviewRules.reviewData}</p>
</React.Fragment>}
</div>
{(!current_user.enterpriseCertification && !current_user.authentication) && current_user.login && <div className="edu-back-white padding30 mt20 font-16 text-center mb50">
<a onClick={goUserProfiles} className="color-blue_41">请先进行实名认证</a>
</div>}
{/* {!current_user.login &&<div className="edu-back-white padding30 mt20 font-16 text-center mb50">
<span className="color-blue_41">创客任务仅限登录用户查看,请先注册登录红山开源账号!</span>
</div>} */}
{(current_user.enterpriseCertification || current_user.authentication) && detailData.status === 3 && (!detailData.exceptClosedBoolean) && signContent()}
<div className="applyList edu-back-white padding30 mt20">
<div className="font-16 font-bd">交稿
{/* {dataList.length} */}
{!detailData.showUserStatus && <Tooltip placement="top" title={"不公示应征者姓名"}>
<i data-tip-down="不公示应征者姓名" className="iconfont icon-yincang1 color-grey9 font-20 ml5"></i>
</Tooltip>}
{detailData.status === 4 && dataList.length && (!detailData.isProofBoolean) && (!detailData.expertReview) && detailData.user && (current_user.admin || current_user.login === detailData.user.login) ?
<a className="line_1 color-blue fr ml20" onClick={() => { setVisibleProofs(true) }}>上传佐证材料</a> : ''}
{dataList.length > 0 && taskLimit && <a className="line_1 color-blue fr ml20" onClick={() => { window.open(`${httpUrl}/api/paper/papers/download/${id}`) }}>一键导出成果物 &gt;&gt;</a>}
{(!detailData.showUserStatus) && !detailData.expertReview && taskLimit && <a className="fr color-orange ml20" onClick={showUser}>应征者名单公示 &gt;&gt;</a>}
{/* [添加专家评审流程]按钮入口,仅管理员可见 */}
{taskLimit && !detailData.expertReview && detailData.status < 4 && <a className="fr color-orange ml20" onClick={addExpertReviewModal}>添加专家评审流程</a>}
</div>
<StatusNav
key={'applyStatus'}
type={'applyStatus'}
options={applyStatusArr}
changeOptionId={changeOptionId}
/>
<ItemListPaper
current_user={current_user}
list={dataList}
itemClick={dataList}
curPage={curPage}
total={total}
changePage={(page) => { setCurPage(page) }}
loading={loading}
applyStatusAllNameArr={applyStatusAllNameArr}
reloadList={reloadList}
showNotification={showNotification}
detailStatus={detailData.status}
agreementSigning={detailData.agreementSigning}
expertReview={detailData.expertReview}
mygetHelmetapi={mygetHelmetapi}
/>
</div>
<Modal
title={applyContent.title}
visible={applyModal}
onOk={agreementSign}
onCancel={() => { setApplyModal(false) }}
className="form-edit-modal"
width='60vw'
>
<div className="new_li markdown-body editormd-html-preview agreement-content" dangerouslySetInnerHTML={{ __html: applyContent.content }}></div>
<div className="mt5 mb10 pl20 pr20 ml15">
<Checkbox checked={agreementCheckBox} onChange={(e) => { setAgreementCheckBox(e.target.checked) }}>我已阅读并同意本电子协议内容</Checkbox>
</div>
</Modal>
{
visibleProofs && <ProofModal
taskId={id}
taskModeId={detailData.taskModeId}
visible={visibleProofs}
changeVisible={setVisibleProofs}
showNotification={showNotification}
reloadList={reloadDetail}
/>}
</div>
)
})
)