Merge pull request 'pre-release merge' (#77) from pre_dev_military into dev_military

This commit is contained in:
baladiwei 2021-09-26 18:42:07 +08:00
commit 16ca2a359e
76 changed files with 9122 additions and 111 deletions

3
.gitignore vendored
View File

@ -86,3 +86,6 @@ typings/
.DS_Store
.idea/*
package.json
package-lock.json

View File

@ -101,9 +101,13 @@ module.exports = {
extensions: [".web.js", ".mjs", ".js", ".json", ".web.jsx", ".jsx"],
alias: {
educoder: __dirname + "/../src/common/educoder.js",
src: path.join(paths.appSrc), // 整个源代码目录
forge: path.join(paths.appSrc, 'forge'),
military: path.join(paths.appSrc, 'military'),
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
"react-native": "react-native-web",
'react-dom': '@hot-loader/react-dom',
},
plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/).

View File

@ -87,6 +87,9 @@ module.exports = {
extensions: [".web.js", ".mjs", ".js", ".json", ".web.jsx", ".jsx"],
alias: {
educoder: __dirname + "/../src/common/educoder.js",
src: path.join(paths.appSrc),
forge: path.join(paths.appSrc, 'forge'),
military: path.join(paths.appSrc, 'military'),
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
"react-native": "react-native-web",

179
package-lock.json generated
View File

@ -261,6 +261,25 @@
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
<<<<<<< HEAD
}
}
},
"@babel/runtime-corejs3": {
"version": "7.15.3",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.3.tgz",
"integrity": "sha512-30A3lP+sRL6ml8uhoJSs+8jwpKzbw8CqBvDc1laeptxPm5FahumJxirigcbD2qTs71Sonvj1cyZB0OKGAmxQ+A==",
"requires": {
"core-js-pure": "^3.16.0",
"regenerator-runtime": "^0.13.4"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
=======
>>>>>>> 01f71bca87fac88ba9ad56c16260521c47f4ca6c
}
}
},
@ -362,6 +381,18 @@
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
},
"@hot-loader/react-dom": {
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/@hot-loader/react-dom/-/react-dom-16.14.0.tgz",
"integrity": "sha512-EN9czvcLsMYmSDo5yRKZOAq3ZGRlDpad1gPtX0NdMMomJXcPE3yFSeFzE94X/NjOaiSVimB7LuqPYpkWVaIi4Q==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
}
},
"@hypnosphi/create-react-context": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz",
@ -3908,6 +3939,14 @@
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
<<<<<<< HEAD
},
"core-js-pure": {
"version": "3.16.4",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.16.4.tgz",
"integrity": "sha512-bY1K3/1Jy9D8Jd12eoeVahNXHLfHFb4TXWI8SQ4y8bImR9qDPmGITBAfmcffTkgUvbJn87r8dILOTWW5kZzkgA=="
=======
>>>>>>> 01f71bca87fac88ba9ad56c16260521c47f4ca6c
},
"core-util-is": {
"version": "1.0.2",
@ -3990,6 +4029,61 @@
"object-assign": "^4.1.1"
}
},
<<<<<<< HEAD
"cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"dev": true,
"requires": {
"cross-spawn": "^7.0.1"
},
"dependencies": {
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
}
},
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"requires": {
"shebang-regex": "^3.0.0"
}
},
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}
}
}
},
=======
>>>>>>> 01f71bca87fac88ba9ad56c16260521c47f4ca6c
"cross-spawn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
@ -16217,6 +16311,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
<<<<<<< HEAD
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
@ -16245,6 +16340,36 @@
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true,
"requires": {
=======
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"wrap-ansi": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true,
"requires": {
>>>>>>> 01f71bca87fac88ba9ad56c16260521c47f4ca6c
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
@ -18975,6 +19100,51 @@
"makeerror": "1.0.x"
}
},
"wangeditor": {
"version": "4.7.7",
"resolved": "https://registry.npmjs.org/wangeditor/-/wangeditor-4.7.7.tgz",
"integrity": "sha512-eUpgP7i4pCYIjxjGMMcSUtkHUCkdX7BBwLZE6umseoK9hs3qIMu1tQ95FG48NJwL71JyAjt5c1gB56PzducL3Q==",
"requires": {
"@babel/runtime": "^7.11.2",
"@babel/runtime-corejs3": "^7.11.2",
"tslib": "^2.1.0"
},
"dependencies": {
"@babel/runtime": {
"version": "7.15.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz",
"integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
}
}
},
"wangeditor-for-react": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/wangeditor-for-react/-/wangeditor-for-react-1.5.3.tgz",
"integrity": "sha512-nnfZW6leWrVP5UFLEl93UmquSxgfkMpRM45nRusuA/QA+OPIKh0+JW4ZSmSjpmolhT8//iyQOry1I5EuVFwgog==",
"requires": {
"react": "^17.0.2",
"wangeditor": "^4.7.5"
},
"dependencies": {
"react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
}
}
},
"warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
@ -19075,13 +19245,22 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
<<<<<<< HEAD
"dev": true,
"optional": true
=======
"dev": true
>>>>>>> 01f71bca87fac88ba9ad56c16260521c47f4ca6c
},
"is-glob": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dev": true,
<<<<<<< HEAD
"optional": true,
=======
>>>>>>> 01f71bca87fac88ba9ad56c16260521c47f4ca6c
"requires": {
"is-extglob": "^2.1.1"
}

View File

@ -107,6 +107,7 @@
"styled-components": "^4.4.1",
"sw-precache-webpack-plugin": "0.11.4",
"url-loader": "0.6.2",
"wangeditor-for-react": "^1.4.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"webpack-manifest-plugin": "^2.2.0",
@ -115,7 +116,7 @@
},
"scripts": {
"start": "node --max_old_space_size=15360 scripts/start.js",
"build": "NODE_ENV=production node --max_old_space_size=15360 scripts/build.js",
"build": "cross-env NODE_ENV=production node --max_old_space_size=15360 scripts/build.js",
"test-build": "NODE_ENV=testBuild node --max_old_space_size=15360 scripts/build.js",
"pre-build": "NODE_ENV=preBuild node --max_old_space_size=15360 scripts/build.js",
"gen_stats": "NODE_ENV=production webpack --profile --config=./config/webpack.config.prod.js --json > stats.json",
@ -182,6 +183,7 @@
"port": "3007",
"devDependencies": {
"@babel/runtime": "7.0.0-beta.51",
"@hot-loader/react-dom": "^16.14.0",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-plugin-import": "^1.13.0",
@ -191,6 +193,7 @@
"babel-preset-stage-2": "^6.24.1",
"compression-webpack-plugin": "^1.1.12",
"concat": "^1.0.3",
"cross-env": "^7.0.3",
"happypack": "^5.0.1",
"mockjs": "^1.1.0",
"node-sass": "^4.12.0",

BIN
public/favicon.back.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
public/favicon.ico Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -50,6 +50,11 @@ const Notice = Loadable({
loader: () => import('./military/notice'),
loading: Loading,
})
//任务/需求
const Task = Loadable({
loader: () => import('./military/task'),
loading: Loading,
})
//403页面
const Shixunauthority = Loadable({
loader: () => import('./modules/403/Shixunauthority'),
@ -254,6 +259,9 @@ class App extends Component {
}
}>
</Route>
{/*任务*/}
<Route path="/task" component={Task} />
{/*403*/}
<Route path="/403" component={Shixunauthority} />

View File

@ -1,4 +1,4 @@
import moment from "moment";
import moment from "moment";
// 处理整点 半点
// 取传入时间往后的第一个半点
@ -12,7 +12,7 @@ export function handleDateString(dateString) {
if (miniute < 30 || miniute == 60) {
return [ar[0], '30'].join(':')
}
if (miniute < 60) {
if (miniute < 60) {
// 加一个小时
const tempStr = [ar[0], '00'].join(':');
const format = "YYYY-MM-DD HH:mm";
@ -20,7 +20,7 @@ export function handleDateString(dateString) {
_moment.add(1, 'hours')
return _moment.format(format)
}
return dateString
}
@ -38,62 +38,91 @@ export function getNextHalfHourOfMoment(moment) {
return moment
}
 export function formatSeconds(value) {
export function formatSeconds(value) {
        var theTime = parseInt(value);// 秒
        var middle= 0;// 分
        var hour= 0;// 小时
    
        if(theTime > 60) {
            middle= parseInt(theTime/60);
            theTime = parseInt(theTime%60);
            if(middle> 60) {
                hour= parseInt(middle/60);
                middle= parseInt(middle%60);
            }
        }
        var result = ""+parseInt(theTime)+"秒";
        if(middle > 0) {
if(hour>0){
result = ""+parseInt(middle)+"分";
}else{
result = ""+parseInt(middle)+"分"+result;
}
            
        }
        if(hour> 0) {
            result = ""+parseInt(hour)+"小时"+result;
        }
        return result;
    }
var theTime = parseInt(value);// 秒
var middle = 0;// 分
var hour = 0;// 小时
export function formatDuring(mss){
var days = parseInt(mss / (1000 * 60 * 60 * 24));
var hours = parseInt((mss % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = parseInt((mss % (1000 * 60 * 60)) / (1000 * 60));
// console.log("formatDuringformatDuring");
// console.log(days);
// console.log(hours);
// console.log(minutes);
// console.log(Math.abs(days));
// console.log(Math.abs(hours));
// console.log(Math.abs(minutes));
if (theTime > 60) {
middle = parseInt(theTime / 60);
theTime = parseInt(theTime % 60);
if (middle > 60) {
hour = parseInt(middle / 60);
middle = parseInt(middle % 60);
}
}
var result = "" + parseInt(theTime) + "秒";
if (middle > 0) {
if (hour > 0) {
result = "" + parseInt(middle) + "分";
} else {
result = "" + parseInt(middle) + "分" + result;
}
try {
days = Math.abs(days);
} catch (e) {
}
try {
hours = Math.abs(hours);
} catch (e) {
}
try {
minutes = Math.abs(minutes);
} catch (e) {
}
return days + "天" + hours + "小时" + minutes + "分";
}
if (hour > 0) {
result = "" + parseInt(hour) + "小时" + result;
}
return result;
}
export function formatDuring(s) {
s = Math.abs(s);
let days = Math.floor(s / (60 * 60 * 24));
let hours = Math.floor((s % (60 * 60 * 24)) / (60 * 60));
let minutes = Math.floor((s % (60 * 60)) / (60));
let second = Math.floor(s % 60);
if (days) {
if(hours){
return days + "天" + hours + "小时";
}
return days + "天";
}
if (hours) {
if(minutes){
return hours + "小时" + minutes + "分";
}
return hours + "小时" ;
}
if (minutes) {
return minutes + "分";
}
return second + "秒";
}
/*
返回多久以前
backDate以前的某个日期
*/
export function timeAgo(backDate) {
try {
moment(backDate);
} catch (e) {
return;
}
let time = new Date() - moment(backDate);
var days = Math.floor(time / (1000 * 60 * 60 * 24));
var hours = Math.floor((time % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = Math.floor((time % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((time % (1000 * 60 * 60)) / 1000);
if (time <= 0) {
return "刚刚";
}
if (days) {
return days + "天前";
}
if (hours) {
return hours + "小时前";
}
if (minutes) {
return minutes + "分前";
}
if (seconds) {
return seconds + "秒前";
}
}

View File

@ -12,6 +12,7 @@ export function getImageUrl(path) {
// https://testbdweb.trustie.net
// const local = 'http://localhost:3000'
const local = 'http://39.105.176.215:49999';
if (isDev) {
return `${local}/${path}`
}

View File

@ -27,7 +27,7 @@ export {
markdownToHTML, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll, isImageExtension,
downloadFile, sortDirections, validateLength, mdJSONParse, exportMdtoHtml
} from './TextUtil'
export { handleDateString, getNextHalfHourOfMoment, formatDuring, formatSeconds } from './DateUtil'
export { handleDateString, getNextHalfHourOfMoment, formatDuring, formatSeconds ,timeAgo} from './DateUtil'
export { configShareForIndex, configShareForPaths, configShareForShixuns, configShareForCourses, configShareForCustom } from './util/ShareUtil'

View File

@ -0,0 +1,90 @@
import React, { useEffect, useState } from "react";
import { Upload, Button } from 'antd';
import { appendFileSizeToUploadFileAll } from 'educoder';
import { httpUrl } from '../fetch';
function Uploads({ className, size, actionUrl, fileList, showNotification, load }) {
const [files, setFiles] = useState(undefined);
useEffect(() => {
if (fileList) {
init();
}
}, [fileList]);
function init() {
let f = appendFileSizeToUploadFileAll(fileList);
setFiles(f);
}
function onAttachmentRemove(file) {
if (!file.percent || file.percent === 100) {
deleteAttachment(file);
return false;
}
}
function deleteAttachment(file) {
let id = (file.response && file.response.data && file.response.data.id) || file.id;
//
let nf = files.filter(item => {
let itemId = (item.response && item.response.data && item.response.data.id) || item.id;
return itemId !== id;
});
setFiles(nf);
backFiles(nf);
}
function backFiles(fileList) {
let filesId = [];
for (const item of fileList) {
if (item) {
let itemId = (item.response && item.response.data && item.response.data.id) || item.id;
itemId && filesId.push(itemId);
}
}
load && load(fileList, filesId.join());
}
function handleChange(info) {
if (info.file.status === 'uploading' || info.file.status === 'done' || info.file.status === 'removed') {
let fileList = info.fileList;
setFiles(appendFileSizeToUploadFileAll(fileList));
if (info.file.response) {
for (let i = 0; i < fileList.length; i++) {
if (fileList[i].response && !fileList[i].response.data) {
fileList.splice(i, 1);
}
}
backFiles(fileList);
if (!info.file.response.data) {
info.file.response && showNotification(info.file.response.message)
}
}
}
}
function beforeUpload(file) {
const isLt100M = file.size / 1024 / 1024 < size;
if (!isLt100M) {
showNotification(`文件大小必须小于${size}MB!`);
}
return isLt100M;
}
const upload = {
name: 'file',
fileList: files,
action: (httpUrl || actionUrl) + `/busiAttachments/upload`,
onChange: handleChange,
onRemove: onAttachmentRemove,
beforeUpload: beforeUpload,
};
return (
<Upload {...upload} className={className}>
<Button type="primary">点击上传</Button>
<span className="ml10 color-grey-9">(你可以上传小于<span className="color-red">{size}MB</span>的文件)</span>
</Upload>
)
}
export default Uploads;

View File

@ -0,0 +1,28 @@
import React, { useEffect, useState,memo } from 'react';
import classNames from 'classnames';
import './index.scss';
export default memo((props) => {
const { title, options, changeOptionId, type ,size} = props;
const [option, setOption] = useState({ code: "", dicItemName: "" ,dicItemCode:""});
useEffect(() => {
changeOptionId(option, type);
}, [option])
return (
<div className={classNames({"choose-box":true,"choose-box-big":size})}>
<div className="choose-title">{title}</div>
<div className="choose-list">
<div className={classNames({ "choose-item-checked": option.dicItemCode === "", "choose-item": true })} key={"all"} onClick={() => { setOption({ dicItemCode: "", dicItemName: "" }) }}>全部</div>
{
options.map((item) => {
return <div className={classNames({ "choose-item-checked": option.dicItemCode === item.dicItemCode, "choose-item": true })} key={item.dicItemCode} onClick={() => { setOption(item) }} >{item.dicItemName}</div>
})
}
</div>
</div>
)
})

View File

@ -0,0 +1,47 @@
.nav-content {
margin:20px 0;
padding:1rem 0.2rem;
background: #fff;
box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.05);
border-radius: 5px;
}
.choose-box {
padding: .35em 0;
display: flex;
justify-content: start;
font-size: .85rem;
}
.choose-title {
width: 6.5em;
text-align: center;
color: #333;
font-weight: 500;
flex:none;
}
.choose-box-big{
font-size: 1rem;
.choose-title{
text-align: right;
}
}
.choose-list {
display: flex;
justify-content: start;
flex-wrap: wrap;
}
.choose-item {
color:#666;
text-align: center;
padding: 0 15px ;
cursor: pointer;
&:hover{
color: #1B8FFF;
}
}
.choose-item-checked {
background: #f7f7f7;
color: #1B8FFF;
}

View File

@ -0,0 +1,25 @@
import { httpUrl } from '../fetch';
export const editorConfig = {
placeholder: '请输入',
uploadImgServer: httpUrl + '/busiAttachments/upload',
uploadFileName: 'file',
uploadImgHeaders: {
'X-Requested-With': 'XMLHttpRequest'
},
excludeMenus: [
'list',
'todo',
'emoticon',
'video'
],
uploadImgHooks: {
// 图片上传并返回了结果,想要自己把图片插入到编辑器中
customInsert: function (insertImgFn, result) {
// insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
if (result && result.data && result.data.id) {
insertImgFn(`${httpUrl}/busiAttachments/view/${result.data.id}`);
}
}
},
}

View File

@ -0,0 +1,54 @@
import React, { useEffect, useState, memo } from 'react';
import { Icon, } from 'antd';
import classNames from 'classnames';
import './index.scss';
export default memo((props) => {
const { options, changeOptionId, type } = props;
const [myOptions, setMyOptions] = useState(()=>{
return JSON.parse(JSON.stringify(options));
});
const [option, setOption] = useState({
name: '综合',
type: 'default',
});
useEffect(() => {
changeOptionId(option, type);
}, [option]);
function itemClick(activeItem) {
const newOption = {
...activeItem,
desc: !activeItem.desc
};
for (const item of myOptions) {
if (item.type === activeItem.type) {
item.desc = newOption.desc;
}
}
setOption(newOption);
setMyOptions(myOptions);
}
console.log('-----options----')
return (
<div className="sort-box">
{
myOptions.map((item) => {
return <div className={classNames({ "sort-item-checked": option.type === item.type, "sort-item": true })} key={item.type} onClick={() => { itemClick(item) }} >
{item.name}
{
item.icon && <span className="caret-up-down">
<Icon type="caret-up" className={classNames({ "caret-checked": !item.desc })} />
<Icon type="caret-down" className={classNames({ "caret-checked": item.desc })} />
</span>}
</div>
})
}
</div>
)
})

View File

@ -0,0 +1,40 @@
.sort-box {
display: flex;
justify-content: start;
align-items: center;
margin-left: 10px;
font-size: 1rem;
.sort-item {
display: flex;
align-items: center;
color: #333;
padding: 0 20px;
background-color: #fff;
&:hover {
background-color: #fff;
color: #409eff;
cursor: pointer;
}
}
.sort-item-checked {
// background-color: #409eff;
// color: #fff;
color: #409eff;
}
.caret-up-down {
display: inline-flex;
flex-flow: column nowrap;
margin-left:.25em;
font-size: .75em;
color:#ccc;
}
.caret-checked{
color: #409eff;
}
.anticon-caret-up{
margin-bottom: -.15em;
}
.anticon-caret-down{
margin-top: -.15em;
}
}

View File

@ -0,0 +1,26 @@
import React, { useEffect, useState ,memo} from 'react';
import classNames from 'classnames';
import './index.scss';
export default memo((props) => {
const { options, changeOptionId, type } = props;
const [option, setOption] = useState({ code: "", dicItemName: "" ,dicItemCode:""});
useEffect(() => {
changeOptionId(option, type);
}, [option])
return (
<div className="status-list">
<div className={classNames({ "status-item-checked": option.dicItemCode === "", "status-item": true })} key={"all"} onClick={() => { setOption({ dicItemCode: "", dicItemName: "" }) }}>全部</div>
{
options.map((item) => {
return <div className={classNames({ "status-item-checked": option.dicItemCode === item.dicItemCode, "status-item": true })} key={item.dicItemCode} onClick={() => { setOption(item) }} >{item.dicItemName}</div>
})
}
</div>
)
})

View File

@ -0,0 +1,21 @@
.status-list {
padding: 1rem 0;
display: flex;
justify-content: start;
flex-wrap: wrap;
}
.status-item {
font-size: 14px;
color: #666;
text-align: center;
padding: 3px 15px;
cursor: pointer;
&:hover {
color: #4cacff;
}
}
.status-item-checked {
background: #f7f7f7;
color: #4cacff ;
}

View File

@ -1,40 +1,47 @@
import { notification } from 'antd';
import { notification,message } from 'antd';
import axios from 'axios';
import cookie from 'react-cookies';
let actionUrl = '';
if (window.location.href.indexOf('localhost') > -1) {
actionUrl='https://taskapi.osredm.com';
// actionUrl='http://192.168.31.47:8081';
}else if(window.location.href.indexOf('192.168.31.48') > -1){
actionUrl='https://taskapi.osredm.com';
axios.defaults.withCredentials = true;
}else if(window.location.href.indexOf('noticeweb.osredm') > -1){
actionUrl="https://taskapi.osredm.com";
axios.defaults.withCredentials = true;
}else if(window.location.href.indexOf('forge.osredm.com')>-1){
actionUrl="https://info.osredm.com";
actionUrl="https://task.osredm.com";
axios.defaults.withCredentials = true;
}
export const httpUrl=actionUrl;
// export const httpUrl = 'http://106.75.31.211:58088'; //可视化
// export const httpUrl = 'http://117.50.100.12:8008'; //测试环境
// export const httpUrl = 'https://info.osredm.com/'; //生产环境
export const httpUrl = actionUrl;
const TokenKey = 'autologin_forge_military';
axios.defaults.withCredentials = true;
// 创建axios实例
const service = axios.create({
baseURL: httpUrl,
timeout: 5000 // 请求超时时间
timeout: 10000, // 请求超时时间
});
// request拦截器
service.interceptors.request.use(config => {
if (cookie.load(TokenKey)) {
console.log(cookie.load(TokenKey));
config.headers['Authorization'] = cookie.load(TokenKey); // 让每个请求携带自定义token 请根据实际情况自行修改
}
if (window.location.port === "3007") {
// 模拟token为登录用户
const taskToken = sessionStorage.taskToken;
if (config.url.indexOf('?') === -1) {
config.url = `${config.url}?token=${taskToken}`;
} else {
config.url = `${config.url}&token=${taskToken}`;
}
}
return config;
}, error => {
// Do something with request error
@ -48,14 +55,21 @@ service.interceptors.response.use(
if (res.status === 400) {
notification.open({
message: "提示",
description: '请求错误',
description: res.data.message || '验证失败',
});
return Promise.reject('error');
}
if (res.status === 401) {
notification.open({
message: "提示",
description: '未授权,请登录!',
description: res.data.message || '未授权,请登录!',
});
return Promise.reject('error');
}
if (res.status === 403) {
notification.open({
message: "提示",
description: res.data.message || '无权限',
});
return Promise.reject('error');
}
@ -77,10 +91,30 @@ service.interceptors.response.use(
},
error => {
console.log(error);
notification.open({
message: "提示",
description: error.message,
});
let res = error.response||{};
if (res.status === 400) {
notification.open({
message: "提示",
description: res.data.message || '操作失败',
});
return Promise.reject('error');
}
if (res.status === 401) {
notification.open({
message: "提示",
description: res.data.message || '登录信息已过期',
});
return Promise.reject('error');
}
if (res.status === 403) {
notification.open({
message: "提示",
description: res.data.message || '无权限!',
});
window.location.href="/403";
return Promise.reject('error');
}
return Promise.reject(error);
}
);

View File

@ -1,17 +1,32 @@
// 本模块公共样式
.color-grey3 {
color: #333;
}
.color-grey9 {
color: #999;
}
.color-orange {
color: #ff6800;
}
.color-deep-blue {
color: #1b8fff;
}
.centerbox {
width: 1200px;
margin: 40px auto;
position: relative;
}
.head-navigation{
position: absolute;
top:-2.3em;
span{
.head-navigation {
position: absolute;
top: -2.3em;
// width: 80vw;
// max-width: 1280px;
// margin: 40px auto;
// position: relative;
span {
display: inline-flex;
align-items: center;
&:hover{
&:hover {
color: #409eff;
}
}
@ -58,6 +73,17 @@
color: #fff;
}
// 内容标题左侧样式
.center-left-but {
display: flex;
justify-content: start;
align-items: center;
margin-left: 20px;
font-size: 16px;
font-weight: 600;
}
// 内容标题右侧样式
.center-right-but {
display: flex;
@ -70,18 +96,6 @@
cursor: pointer;
}
// 通用标签样式
.list-tag {
display: inline-block;
margin-right: 10px;
background: #cfe9ff;
color: #0089ff;
font-size: 14px;
border-radius: 3px;
padding: 2px 5px;
line-height: 25px;
}
// 文件预览modal样式
.file-modal {
width: 800px !important;
@ -109,24 +123,24 @@
max-width: 500px;
}
.link{
.link {
color: #0089ff;
cursor: pointer;
&:hover{
color:#509eff;
&:hover {
color: #509eff;
}
}
.color-grey-a{
.color-grey-a {
color: #aaa;
}
.greater{
.greater {
position: relative;
top:-1px;
top: -1px;
}
.none_panels{
.none_panels {
display: flex;
justify-content: center;
align-items: center;
@ -134,20 +148,48 @@
height: 40vh;
}
.newFooter .footerInfos>ul {
.newFooter .footerInfos > ul {
padding: 0 40px;
box-sizing: border-box;
max-width: 25%;
text-align: left;
}
.ant-modal-footer {
text-align: center;
border: 0;
}
// 头像
.head-log-big {
width: 4rem;
height: 4rem;
margin-right: 0.5rem;
border-radius: 50%;
}
.head-log-middle {
width: 3rem;
height: 3rem;
margin-right: 0.35rem;
border-radius: 50%;
}
.head-log-small {
width: 1.25rem;
height: 1.25rem;
margin-right: 0.35rem;
border-radius: 50%;
}
// 富文本样式
.w-e-text table td, .w-e-text table th{
.w-e-text table td,
.w-e-text table th {
height: 30px;
}
.editor-w-text {
table td, table th{
table td,
table th {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
padding: 3px 5px;
@ -172,16 +214,18 @@
font-size: 100%;
background-color: #f1f1f1;
}
a{
a {
color: #409eff;
cursor: pointer;
}
}
.text-center{
text-align: center;
}
@media screen and (max-width: 1200px){
@media screen and (max-width: 1200px) {
.centerbox {
width: 98%;
}
}
}

View File

@ -1,4 +1,4 @@
import fetch from '../fetch';
import fetch from './fetch';
import { notification } from 'antd';

View File

@ -0,0 +1,122 @@
import { notification,message } from 'antd';
import axios from 'axios';
import cookie from 'react-cookies';
let actionUrl = '';
if (window.location.href.indexOf('localhost') > -1) {
actionUrl='https://taskapi.osredm.com';
// actionUrl='http://192.168.31.47:8081';
}else if(window.location.href.indexOf('192.168.31.48') > -1){
actionUrl='https://taskapi.osredm.com';
axios.defaults.withCredentials = true;
}else if(window.location.href.indexOf('noticeweb.osredm') > -1){
actionUrl="https://taskapi.osredm.com";
axios.defaults.withCredentials = true;
}else if(window.location.href.indexOf('forge.osredm.com')>-1){
actionUrl="https://info.osredm.com";
axios.defaults.withCredentials = true;
}
export const httpUrl = actionUrl;
const TokenKey = 'autologin_forge_military';
// 创建axios实例
const service = axios.create({
baseURL: httpUrl,
timeout: 10000, // 请求超时时间
});
// request拦截器
service.interceptors.request.use(config => {
if (cookie.load(TokenKey)) {
console.log(cookie.load(TokenKey));
config.headers['Authorization'] = cookie.load(TokenKey); // 让每个请求携带自定义token 请根据实际情况自行修改
}
if (window.location.port === "3007") {
// 模拟token为登录用户
const taskToken = sessionStorage.taskToken;
if (config.url.indexOf('?') === -1) {
config.url = `${config.url}?token=${taskToken}`;
} else {
config.url = `${config.url}&token=${taskToken}`;
}
}
return config;
}, error => {
// Do something with request error
console.log(error); // for debug
Promise.reject(error);
});
// respone拦截器
service.interceptors.response.use(
response => {
const res = response;
if (res.status === 400) {
notification.open({
message: "提示",
description: res.data.message || '验证失败',
});
return Promise.reject('error');
}
if (res.status === 401) {
notification.open({
message: "提示",
description: res.data.message || '未授权,请登录!',
});
return Promise.reject('error');
}
if (res.status === 403) {
notification.open({
message: "提示",
description: res.data.message || '无权限',
});
return Promise.reject('error');
}
if (res.status === 40001) {
notification.open({
message: "提示",
description: '账户或密码错误!',
});
return Promise.reject('error');
}
if (response.status !== 200 && res.status !== 200) {
notification.open({
message: "提示",
description: res.message,
});
} else {
return response.data;
}
},
error => {
console.log(error);
let res = error.response||{};
if (res.status === 400) {
notification.open({
message: "提示",
description: res.data.message || '操作失败',
});
return Promise.reject('error');
}
if (res.status === 401) {
notification.open({
message: "提示",
description: res.data.message || '登录信息已过期',
});
return Promise.reject('error');
}
if (res.status === 403) {
notification.open({
message: "提示",
description: res.data.message || '无权限!',
});
window.location.href="/403";
return Promise.reject('error');
}
return Promise.reject(error);
}
);
export default service;

View File

@ -148,7 +148,16 @@ export default Form.create()(({ match, history, showNotification, form }) => {
{
helper('联系方式',
'contactInfo',
[{ required: true, message: "请输入联系方式" }, { max: 100, message: '不能超过100字符' }],
[{ required: true, message: "请输入联系方式" },
{ max: 100, message: '不能超过100字符' },
{ validator: (rule,val,callback) =>{
var pattern = /^((\+)?86|((\+)?86)?)0?1[3458]\d{9}$/;
if(pattern.test(val)){
callback();
}else {
callback('请输入正确的联系方式!');
}
}}],
<Input
placeholder="请输入联系方式"
/>

118
src/military/task.js Normal file
View File

@ -0,0 +1,118 @@
import React, { Component, useEffect, useState } from "react";
import { Route, Switch } from "react-router-dom";
import { withRouter } from "react-router";
import { SnackbarHOC } from "educoder";
import { CNotificationHOC } from "../modules/courses/common/CNotificationHOC";
import { TPMIndexHOC } from "../modules/tpm/TPMIndexHOC";
import Loadable from "react-loadable";
import Loading from "../Loading";
import { getUserInfo } from './task/api';
import { ImageLayerOfCommentHOC } from "../modules/page/layers/ImageLayerOfCommentHOC";
import './index.scss';
const TaskList = Loadable({
loader: () => import("./task/taskList"),
loading: Loading,
});
const TaskDetail = Loadable({
loader: () => import("./task/taskDetail"),
loading: Loading,
});
const TaskEdit = Loadable({
loader: () => import("./task/taskEdit"),
loading: Loading,
});
const MyTask = Loadable({
loader: () => import("./task/myTask"),
loading: Loading,
});
const TaskAdminRouter = Loadable({
loader: () => import("./task/taskAdminRouter"),
loading: Loading,
});
const Index = (propsTransmit) => {
// 开发时,从代理的位置获取用户信息
const [currentUser, setCurrentUser] = useState(propsTransmit.current_user);
// const isDev = window.location.href.indexOf('3007') > -1 ? true : false;
useEffect(() => {
getUserInfo().then(res => {
if (res && res.data) {
setCurrentUser(res.data);
}
})
}, [])
let propsF = { ...propsTransmit };
propsF.current_user = currentUser;
return (
<div className="newMain clearfix">
<Switch {...propsF}>
{/* 任务详情 */}
<Route
path="/task/taskDetail/:taskId"
render={(props) => (
<TaskDetail {...propsF} {...props} />
)}
></Route>
{/* 新增任务 */}
<Route
path="/task/taskAdd"
render={(props) => (
<TaskEdit {...propsF} {...props} />
)}
></Route>
{/* 编辑任务 */}
<Route
path="/task/taskEdit/:taskId"
render={(props) => (
<TaskEdit {...propsF} {...props} />
)}
></Route>
{/* 我的任务 */}
<Route
path="/task/myTask"
render={(props) => (
<MyTask {...propsF} {...props} />
)}
></Route>
{/* 管理员管理 */}
<Route
path="/task/:admin"
render={(props) => (
<TaskAdminRouter {...propsF} {...props} />
)}
></Route>
{/* 任务列表 */}
<Route
path="/task"
render={(props) => (
<TaskList {...propsF} {...props} />
)}
></Route>
</Switch>
</div>
);
}
// }
export default withRouter(
ImageLayerOfCommentHOC({
imgSelector: ".imageLayerParent img, .imageLayerParent .imageTarget",
parentSelector: ".newMain",
})(CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(Index))))
);

View File

@ -0,0 +1,206 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Input, Button, Form, Select } from 'antd';
import ItemAgreementManage from '../components/itemAgreementManage';
import StatusNav from '../../components/statusNav';
import { agreementArr } from '../static';
import { agreementList } from '../api';
import '../index.scss';
const Option = Select.Option;
const agreementOptionArr = agreementArr.slice(0, 2);
export default Form.create()(({ form, showNotification, match, history }) => {
const { getFieldDecorator, validateFields, setFieldsValue, } = form;
const [approve, setApprove] = useState(1);
const [loading, setLoading] = useState(false);
const [searchObj, setSearchObj] = useState({});
const [status, setStatus] = useState('2');
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [taskList, setTaskList] = useState([]);
const [type, setType] = useState('1');
const [reload, setReload] = useState(0);
const [loadPaper, setLoadPaper] = useState(0);
// 加载审核协议列表
useEffect(() => {
const params = {
...searchObj,
status,
type,
currentPage: curPage,
pageSize: 10,
};
setLoading(true);
agreementList(params).then(data => {
if (data) {
if (loadPaper === 0 && data.rows.length === 0) {
setLoadPaper(1);
} else {
setLoadPaper(2);
}
setTaskList(data.rows);
setTotal(data.total);
}
setLoading(false);
});
}, [reload, status, curPage, searchObj, type]);
// 如果第一次加载任务协议审核发现没有数据,那么切换成审核成果协议列表
useEffect(() => {
if (loadPaper === 1) {
const params = {
status: '2',
type: '2',
currentPage: 1,
pageSize: 10,
};
setLoading(true);
agreementList(params).then(data => {
if (data && data.rows.length > 0) {
setTaskList(data.rows);
setTotal(data.total);
setType('2');
setLoadPaper(2);
}
setLoading(false);
});
}
}, [loadPaper]);
// form表单公共处理函数
const helper = useCallback(
(name, rules, widget, initialValue) => (
<Form.Item>
{getFieldDecorator(name, { rules, initialValue, validateFirst: true, })(widget)}
</Form.Item>
), []);
function onSearch() {
validateFields((err, values) => {
if (!err) {
setSearchObj(values);
}
});
}
// 修改选项
const changeOptionId = useCallback((option) => {
setStatus(option.dicItemCode.toString() || '0,1');
setCurPage(1);
}, []);
// 修改状态
function changeApprove(approve) {
setApprove(approve);
setCurPage(1);
if (approve === 1) {
setStatus('2');
} else {
setStatus('0,1');
}
}
// 清除查询内容
function clearSearch() {
setFieldsValue({
taskNumber: '',
taskName: '',
userName: ''
});
setSearchObj({});
}
// 刷新数据
const reloadList = useCallback(() => {
setReload(Math.random());
}, []);
return (
<div className="centerbox task-manage">
<div className="center-screen" >
<div className="center-left-but">
{helper(
"type",
[],
<Select
showArrow
placeholder="请选择协议"
onChange={(type) => { setType(type) }}
>
<Option key={'1'}>委托协议</Option>
<Option key={'2'}>成果协议</Option>
</Select>,
type
)}
<Button className="circle-button" type={approve === 1 ? 'primary' : ''} onClick={() => { changeApprove(1) }}>待审批</Button>
<Button className="circle-button" type={approve === 2 ? 'primary' : ''} onClick={() => { changeApprove(2) }}>已审批</Button>
</div>
<div className="center-right-but">
{helper(
"taskNumber",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入任务编号进行检索"
/>
)}
{helper(
"taskName",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入任务名称进行检索"
/>
)}
{helper(
"userName",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入发布人名称进行检索"
/>
)}
<Button className="mr10" type="primary" onClick={onSearch}>搜索</Button>
<Button className="mr10" type="" onClick={clearSearch}>清除</Button>
</div>
</div>
<div className="center-content">
{
approve === 2 && <StatusNav
key={'status'}
type={'status'}
options={agreementOptionArr}
changeOptionId={changeOptionId}
/>
}
<ItemAgreementManage
list={taskList}
curPage={curPage}
total={total}
changePage={(page) => { setCurPage(page) }}
loading={loading}
showNotification={showNotification}
reloadList={reloadList}
/>
</div>
</div>
)
}
)

571
src/military/task/api.js Normal file
View File

@ -0,0 +1,571 @@
import fetch, { } from '../fetch';
import { notification } from 'antd';
import { func } from 'prop-types';
// 获取字典分类列表
export function getDictionary(id) {
return fetch({
url: '/dicItem/getData?dicTypeCode=' + id,
method: 'get'
});
}
// 获取用户信息
export function getUserInfo() {
return fetch({
url: '/user/getUserInfo',
method: 'get'
});
}
// 获取用户企业信息
export function getCompanyInfo() {
return fetch({
url: '/api/tasks/getEnterpriseUserInfo',
method: 'get'
});
}
// 获取任务领域
export async function getTaskCategory() {
let res = await fetch({
url: '/api/taskCategory/getTaskCategory',
method: 'get',
});
if (Array.isArray(res.data.rows)) {
return res.data.rows;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 任务列表查询
export async function getTaskList(params) {
let res = await fetch({
url: '/api/tasks/',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 管理员任务列表查询
export async function getTaskAdminList(params) {
let res = await fetch({
url: '/api/tasks/backend/list',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 我的任务列表查询
export async function getMyTaskList(params) {
let res = await fetch({
url: '/api/myTasks/',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 我参与的任务列表查询
export async function getJoinTaskList(params) {
let res = await fetch({
url: '/api/myTasks/myPapers',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 详情查询
export async function getTaskDetail(id) {
let res = await fetch({
url: '/api/tasks/getTask/' + id,
method: 'get',
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
//新增任务
export function addTask(data) {
return fetch({
url: '/api/tasks/add',
method: 'post',
data: data
});
}
//更新任务
export function updateTask(data) {
return fetch({
url: '/api/tasks/',
method: 'PUT',
data: data
});
}
//删除
export function deleteTask(id) {
return fetch({
url: '/api/tasks/' + id,
method: 'DELETE',
});
}
//新增成果
export function addPaper(data) {
return fetch({
url: '/api/paper/',
method: 'post',
data: data
});
}
// 任务成果
export async function getTaskPaper(params) {
let res = await fetch({
url: '/api/paper/',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 审核任务成果
export async function readyCheckPapers(params) {
let res = await fetch({
url: '/api/paper/admin/readyCheckPapers',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 我的成果
export async function myPapers(params) {
let res = await fetch({
url: '/api/paper/my',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
//更新成果
export function updatePaper(data) {
return fetch({
url: '/api/paper/',
method: 'put',
data: data
});
}
// 删除
export function deletePaper(id) {
return fetch({
url: `api/paper/${id}`,
method: 'delete',
});
}
//应征者名单公示
export function makePublic(id) {
return fetch({
url: `/api/tasks/makeApplicantListPublic/${id}`,
method: 'put',
});
}
//举报成果
export function reportPaper(data) {
return fetch({
url: `/api/paper/${data.paperId}/paperReport`,
method: 'post',
data: data
});
}
//点赞成果
export function thumbUpPaper(id) {
return fetch({
url: `/api/paper/${id}/thumbUp`,
method: 'post',
data: { paperId: id }
});
}
// 检查用户是否同意协议
export function checkAgreement(taskId) {
return fetch({
url: `/api/paper/${taskId}/agreement`,
method: 'get'
});
}
// 检查用户是否提交了成果
export function checkHavePaper(taskId) {
return fetch({
url: `/api/paper/${taskId}/check`,
method: 'get'
});
}
// 获取协议
export function getAgreement() {
return fetch({
url: '/api/paper/agreementSettings/1',
method: 'get'
});
}
// 同意协议
export function agreement(taskId) {
return fetch({
url: `/api/paper/${taskId}/agreement`,
method: 'post',
data: { taskId }
});
}
// 新增评论
export function commentAdd(data) {
return fetch({
url: `/api/paper/${data.paperId}/comment`,
method: 'post',
data,
});
}
//管理员审核任务
export function checkTask(data) {
return fetch({
url: `/api/tasks/backend/adminCheck`,
method: 'post',
data: data
});
}
// 审核成果/评论
export function checkPaper(data) {
return fetch({
url: `/api/paper/admin/complainPaper/${data.paperId}`,
method: 'post',
data: data.auditingVo
});
}
// 成果申诉
export function complainPaper(data) {
return fetch({
url: `/api/paper/complainInfo/${data.paperId}`,
method: 'post',
data: data.params
});
}
// 审核申诉材料列表查询
export async function complainPaperList(params) {
let res = await fetch({
url: '/api/paper/admin/readyComplaintPapers',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 审核申诉材料
export function checkComplain(data) {
return fetch({
url: `/api/paper/admin/complainMaterial/${data.paperId}`,
method: 'post',
data: data.auditingVo
});
}
// 佐证上传
export function proofAdd(data) {
return fetch({
url: `/api/taskResultProof/addTaskResultProof`,
method: 'post',
data,
});
}
// 审核佐证材料列表查询
export async function proofList(params) {
let res = await fetch({
url: '/api/tasks/backend/proofList',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 审核佐证
export function checkProof(data) {
return fetch({
url: `/api/taskResultProof/adminCheckResultAndProof`,
method: 'post',
data,
});
}
// 应征者公示期申诉
export function publicityComplain(data) {
return fetch({
url: `/api/myTasks/applicantComplaintDuringPublicity`,
method: 'post',
data,
});
}
// 审核公示期申诉列表查询
export async function publicityComplainList(params) {
let res = await fetch({
url: '/api/tasks/backend/complaintMaterialList',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 审核公示期申诉
export function checkPublicity(data) {
return fetch({
url: `/api/tasks/backend/adminCheck/complaintMaterialDuringPublicity`,
method: 'post',
data,
});
}
// 选择签订协议的方式
export function signMethod(data) {
return fetch({
url: `/api/sign/method/${data.taskId}/${data.method}`,
method: 'post',
});
}
// 上传委托协议
export function uploadAgreeRequire(data) {
return fetch({
url: `/api/sign/task/contract/${data.taskId}`,
method: 'post',
data: data.params
});
}
// 审核委托协议列表
export async function agreementList(params) {
let res = await fetch({
url: '/api/sign/task/contract',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 审核协议
export function adminCheckAgreement(data) {
return fetch({
url: `/api/sign/admin/${data.type === 1 ? 'task' : 'paper'}/contract/${data.agreementId}`,
method: 'post',
data: data.params
});
}
// 上传委托协议
export function uploadAgreePaper(data) {
return fetch({
url: `/api/sign/paper/contract/${data.paperId}`,
method: 'post',
data: data.params
});
}
// 上传凭证列表查询
export async function uploadPayProofList(params) {
let res = await fetch({
url: '/api/sign/admin/paper/payOrders',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 管理员上传支付凭证
export function uploadPayProof(data){
return fetch({
url: `/api/sign/admin/paper/payment/${data.paperId}`,
method: 'post',
data: data.params
});
}
// 胜出者确认收款
export function confirmReceipt(paperId) {
return fetch({
url: `/api/sign/paper/money/${paperId}`,
method: 'post',
});
}
// 下载协议签订凭证
export function downAgreement(params) {
return fetch({
url: `/api/myTasks/getWinnersContractsByTaskId`,
method: 'get',
params,
});
}
// 管理员修改任务公示方式
export function changeShowUserMode(data) {
return fetch({
url: `/api/tasks/backend/changeShowUserMode`,
method: 'post',
data,
});
}
// 延期列表查询
export async function delayList(params) {
let res = await fetch({
url: '/api/tasks/backend/admin/delayList',
method: 'get',
params,
});
if (res.data) {
return res.data;
} else {
notification.open({
message: "提示",
description: res.message || '请求错误',
});
}
}
// 延期
export function delayTask(data) {
return fetch({
url: `/api/tasks/backend/admin/task/delay/${data.taskId}`,
method: 'post',
data:data.params,
});
}
// 关闭
export function closeTask(taskId) {
return fetch({
url: `/api/tasks/backend/admin/task/close/${taskId}`,
method: 'post',
});
}

View File

@ -0,0 +1,135 @@
import React, { useMemo } from 'react';
import { Menu, Dropdown, } from 'antd';
import { current_main_site_url,main_web_site_url} from '../../static';
import './index.scss';
const { SubMenu } = Menu;
export default props => {
const projectMenu = useMemo(() => {
return <Menu>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${current_main_site_url}/admins`}>项目管理后台</a></Menu.Item>
</Menu>
});
const taskMenu = useMemo(() => {
return <Menu>
<SubMenu title="基础数据" >
<Menu.Item><a href={`${main_web_site_url}/admin/categories/list`}>任务领域</a></Menu.Item>
<Menu.Item><a href={`${main_web_site_url}/admin/industries/list`}>行业信息</a></Menu.Item>
<Menu.Item><a href={`${main_web_site_url}/admin/placements/list`}>职位信息</a></Menu.Item>
<Menu.Item><a href={`${main_web_site_url}/admin/task_templates/list`}>需求导入模板</a></Menu.Item>
<Menu.Item><a href={`${main_web_site_url}/admin/agreement_setting`}>签订协议内容</a></Menu.Item>
<Menu.Item><a href={`${main_web_site_url}/admin/sign_agreement_setting`}>应征投稿协议内容</a></Menu.Item>
</SubMenu>
<SubMenu title="代办事项" >
<Menu.Item><a href={`/task/delayManage`}>延期任务处理</a></Menu.Item>
<Menu.Item><a href={`${main_web_site_url}/admin/audit_files`}>协议签订凭证上传</a></Menu.Item>
<Menu.Item><a href="/task/payProof">支付报酬凭证上传</a></Menu.Item>
</SubMenu>
<Menu.Item><a href="/task/taskAdmin">创客列表</a></Menu.Item>
<Menu.Item><a href="/task/paperManage">创意征集评论</a></Menu.Item>
</Menu>
});
const competitionMenu = useMemo(() => {
return <Menu>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/admin/competitions/list`}>竞赛列表</a></Menu.Item>
</Menu>
});
const userMenu = useMemo(() => {
return <Menu>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/managements/users`}>用户列表</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/admin/entities`}>主体信息列表</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/managements/users_trial`}>试用授权列表</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/managements/auto_users_trial`}>自动授权列表</a></Menu.Item>
</Menu>
});
const forumMenu = useMemo(() => {
return <Menu>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/managements/messages_list`}>帖子</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/managements/apply_destroy_memos`}>申请删帖</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/managements/memo_reply_list`}>回复</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/admin/forum_sections`}>版块配置</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/admin/banned_users`}>禁言列表</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/admin/forum_applies`}>版主审批</a></Menu.Item>
</Menu>
});
const checkMenu = useMemo(() => {
return <Menu>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/managements/enterprise_authentication`}>企业认证</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/admin/reviews/projects_list`}>开源项目</a></Menu.Item>
<Menu.Item><a href="/task/taskManage">统筹任务发布审批</a></Menu.Item>
<Menu.Item><a href="/task/paperComplain">成果上传申诉审批</a></Menu.Item>
<Menu.Item><a href="/task/publicityComplain">公示期成果申诉审批</a></Menu.Item>
<Menu.Item><a href="/task/agreementManage">协议审批</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/admin/tasks/report_result_tasks`}>成果举报申诉</a></Menu.Item>
<Menu.Item><a href="/task/proofManage">评选佐证材料</a></Menu.Item>
</Menu>
});
const limitsMenu = useMemo(() => {
return <Menu>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/managements/user_admin_roles`}>权限组配置</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/managements/admin_role_permissions`}>权限操作配置</a></Menu.Item>
</Menu>
});
const configMenu = useMemo(() => {
return <Menu>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/admin/about_infos/new"`}>关于我们</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/admin/home_sections"`}>首页版块</a></Menu.Item>
<Menu.Item><a target="_blank" rel="noopener noreferrer" href={`${main_web_site_url}/admin/partners`}>合作伙伴</a></Menu.Item>
</Menu>
});
return (
<div className="centerbox managements_menus clearfix">
<Dropdown key={'projectMenu'} overlay={projectMenu} placement="bottomLeft">
<div className="drop-div">
项目
</div>
</Dropdown>
<Dropdown key={'taskMenu'} overlay={taskMenu} placement="bottomLeft">
<div className="drop-div">
创客
</div>
</Dropdown>
<Dropdown key={'competitionMenu'} overlay={competitionMenu} placement="bottomLeft">
<div className="drop-div">竞赛</div>
</Dropdown>
<Dropdown key={'userMenu'} overlay={userMenu} placement="bottomLeft">
<div className="drop-div">用户</div>
</Dropdown>
<Dropdown key={'forumMenu'} overlay={forumMenu} placement="bottomLeft">
<div className="drop-div">论坛交流</div>
</Dropdown>
<Dropdown key={'checkMenu'} overlay={checkMenu} placement="bottomLeft">
<div className="drop-div">审批</div>
</Dropdown>
<Dropdown key={'limitsMenu'} overlay={limitsMenu} placement="bottomLeft">
<div className="drop-div">权限管理</div>
</Dropdown>
<Dropdown key={'configMenu'} overlay={configMenu} placement="bottomLeft">
<div className="drop-div">网站配置</div>
</Dropdown>
</div>
)
}

View File

@ -0,0 +1,24 @@
.managements_menus {
background: #fff;
border: 1px solid #eee;
.drop-div {
position: relative;
float: left;
width: 108px;
text-align: center;
padding: 15px 0px;
box-sizing: border-box;
position: relative;
cursor: pointer;
&::after {
content: "";
position: absolute;
right: 1px;
top: 20px;
width: 1px;
height: 20px;
background-color: #ddd;
}
}
}

View File

@ -0,0 +1,75 @@
import React, { useState, } from 'react';
import { Modal, Form, Input, } from 'antd';
import Upload from 'military/components/Upload';
import { uploadAgreePaper } from "../../api";
import '../../index.scss';
export default Form.create()(props => {
const { visible, setVisible, checkedItem, form, showNotification ,reloadList } = props;
const { getFieldDecorator, validateFields, setFieldsValue, } = form;
const [fileList, setFileList] = useState([]);
//
function uploadFunc(fileList, files) {
setFileList(fileList);
setFieldsValue({
files,
});
}
function uploadAgree() {
validateFields((err, values) => {
if (!err) {
uploadAgreePaper({
paperId: checkedItem.id,
params: {
files: values.files,
}
}).then(res => {
if (res.message === 'success') {
setFieldsValue({
files: '',
});
setVisible(false);
showNotification("上传协议成功!");
reloadList();
} else {
showNotification(res.message || "上传协议失败")
}
})
}
})
}
return (
<Modal
title="签订协议"
visible={visible}
onOk={uploadAgree}
onCancel={() => { setVisible(false) }}
className="form-edit-modal"
>
<div className="task-popup-content">
{/* paperAuditing */}
{checkedItem.paperAuditing && <p className=" mb10 color-orange task_tip">审核意见{checkedItem.paperAuditing.message}</p>}
<a href="http://117.50.100.12:8000/attachments/download/523/%E5%88%9B%E5%AE%A2%E4%BB%BB%E5%8A%A1%E5%88%97%E8%A1%A8_2019-07-26_20-53.xlsx" className="icon icon-attachment font-13 color-blue" length="32">协议样板.word</a>
<Form.Item className="upload-form" label="附件上传" required={true}>
<Upload
load={uploadFunc}
size={50}
showNotification={showNotification}
fileList={fileList}
/>
{getFieldDecorator('files', {
rules: [{ required: visible, message: "请上传文件" }],
validateFirst: true
})(<Input style={{ display: 'none' }} />)}
</Form.Item>
</div>
</Modal>
)
}
)

View File

@ -0,0 +1,109 @@
import React, { useState, useCallback, useMemo} from 'react';
import { Modal, Form, Input, } from 'antd';
import Upload from 'military/components/Upload';
import { complainPaper, publicityComplain, } from "../../api";
import '../../index.scss';
const { TextArea } = Input;
export default Form.create()(props => {
const { visible, setVisible, checkedItem, detailStatus, form, showNotification, reloadList } = props;
const { getFieldDecorator, validateFields, setFieldsValue, } = form;
const [fileList, setFileList] = useState([]);
//
function uploadFunc(fileList, files) {
setFileList(fileList);
setFieldsValue({
files,
});
}
const helper = useCallback(
(name, rules, widget) => (
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
</Form.Item>
),
[]
);
function complain() {
validateFields((error, values) => {
if (!error) {
if (detailStatus === 5) {
publicityComplain({
content: values.complainValue,
files: values.files,
paperId: checkedItem.id,
}).then(res => {
complainDeal(res);
});
} else {
complainPaper({
paperId: checkedItem.id,
params: {
content: values.complainValue,
files: values.files,
}
}).then(res => {
complainDeal(res);
});
}
}
});
}
function complainDeal(res) {
if (res && res.message === 'success') {
showNotification('申诉提交成功');
setVisible(false);
setFileList(null);
setFieldsValue({
files: ''
});
reloadList();
}
}
return (
<Modal
title="申诉"
visible={visible}
onOk={complain}
onCancel={() => { setVisible(false) }}
className="form-edit-modal"
>
{checkedItem.checkStatus == 2 && detailStatus === 3 && <p className=" mb10 color-orange task_tip">审核意见{checkedItem.auditing.message}</p>}
<p className="edu-txt-center lineh-20 mb10">你的申诉信息将发送给平台管理员</p>
<p className="edu-txt-center lineh-20">请如实填写有效的申诉原由我们将尽快完成审核</p>
{
helper('complainValue', [{ required: visible, message: "(必填)请在此输入发起申诉的原因最大限制100个字符" },
{ max: 100, message: '长度不能超过100个字符' }],
<TextArea
placeholder="(必填)请在此输入发起申诉的原因最大限制100个字符"
autoSize={{ minRows: 6 }}
className="applyText"
/>
)
}
<Form.Item className="upload-form" label="附件上传" required={true}>
<Upload
className="commentStyle"
load={uploadFunc}
size={50}
showNotification={showNotification}
fileList={fileList}
/>
{getFieldDecorator('files', {
rules: [{ required: visible, message: "请上传文件" }],
validateFirst: true
})(<Input style={{ display: 'none' }} />)}
</Form.Item>
</Modal>
)
}
)

View File

@ -0,0 +1,208 @@
import React, { useEffect, useState, useCallback } from 'react';
import { Pagination, Modal, Input, Form, } from 'antd';
import { Link } from "react-router-dom";
import Nodata from 'forge/Nodata';
import Loading from "src/Loading";
import { timeAgo, getImageUrl } from 'educoder';
import { adminCheckAgreement } from '../../api';
import { httpUrl } from 'military/fetch';
import './index.scss';
const { TextArea } = Input;
export default Form.create()((props) => {
const { form, list, curPage, total, changePage, loading, showNotification, reloadList } = props;
const { getFieldDecorator, validateFields, setFieldsValue } = form;
const [checkedItem, setCheckedItem] = useState({});
const [page, setPage] = useState(1);
const [visible, setVisible] = useState(false);
const pageSize = props.pageSize || 10;
useEffect(() => {
changePage(page);
}, [page]);
function refuseClick(item) {
setCheckedItem(item);
setVisible(true);
}
function dealAction() {
validateFields((error, values) => {
if (!error) {
adminCheckAgreement({
agreementId: checkedItem.id,
type: checkedItem.type,
params: {
pass: 0,
message: values.message
}
}).then(res => {
if (res && res.message === 'success') {
showNotification('操作成功');
reloadList();
setFieldsValue({
message: '',
});
setVisible(false);
}
});
}
});
}
function agreeClick(item) {
Modal.confirm({
title: '确认审批通过?',
onOk() {
adminCheckAgreement({
agreementId: item.id,
type: item.type,
params: {
pass: 1,
}
}).then(res => {
if (res && res.message === 'success') {
showNotification('操作成功');
reloadList();
}
})
}
});
}
function goUser(login) {
window.location.href = `/users/${login}`;
}
function goUserMes(login) {
window.location.href = `/users/${login}/message_detail`;
}
function downFile(item) {
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>
),
[]
);
return (
loading ? <Loading /> :
<React.Fragment>
{
list.map(item => {
return (
<div className="list-box" key={item.id}>
<img alt="" className="radius mr15" height="50px" src={item.user && getImageUrl(item.user.logo)} width="50px" />
<div className="flex1">
<li className="clearfix mb20">
<a className="user-box fl mr15 color-grey-3 font-16" onClick={() => { goUser(item.user.login) }}>{item.user && (item.user.nickname || item.user.login)}</a>
<span className="fl color-grey-9 mt3 mr15">{timeAgo(item.createdAt)}</span>
<span className="fr">
{item.status === 1 && <span className="spanTitle color-grey-6 fl ml20">已同意</span>}
{item.status === 0 && <span className="spanTitle color-red fl ml20">已驳回</span>}
{
item.status === 2 && <React.Fragment>
<a className="edu-default-btn edu-orangeline-btn ml20 fl" onClick={() => { goUserMes(item.user.login) }}>私信</a>
<a className="edu-default-btn edu-blueline-btn ml20 fl" onClick={() => { agreeClick(item) }}>同意</a>
<a className="edu-default-btn edu-greyline-btn ml20 fl" onClick={() => { refuseClick(item) }}>驳回</a>
</React.Fragment>
}
</span>
</li>
<div className="clearfix">
<div className="width100 lineh-35">
<span className="color-grey-9 fl">任务名称</span>
<span className="fl lineh-35 ml5">
<Link className="primary-link" to={`/task/taskDetail/${item.taskId}`}>{item.taskName}</Link>
</span>
</div>
<div className="clearfix"></div>
<div className="width100 lineh-35">
<span className="color-grey-9 fl">任务编号</span>
<span className="fl lineh-35 ml5">
{item.taskNumber}
</span>
</div>
<div className="clearfix"></div>
{
item.paperNumber && <React.Fragment>
<div className="width100 lineh-35">
<span className="color-grey-9 fl">成果编号</span>
<span className="fl lineh-35 ml5">
{item.paperNumber}
</span>
</div>
<div className="clearfix"></div>
</React.Fragment>
}
<div className="width100 lineh-35 clearfix">
<span className="color-grey-9 fl">协议文件</span>
{
item.busiAttachments && item.busiAttachments.map(fileItem => {
return <span className="file-list-prof " key={fileItem.id}>
<a onClick={() => { downFile(fileItem) }}><i className="iconfont icon-fujian color-green font-14 mr3"></i>
{fileItem.fileName} </a>
<span className="ml10 color-grey-9">({fileItem.fileSizeString})</span>
</span>
})
}
</div>
{/* <div className="width100 lineh-35">
<span className="color-grey-9 fl">驳回原因</span>
<span className="infos_item">{item.content}</span>
</div> */}
</div>
</div>
</div>
)
})
}
{list.length > 0 ?
<div className="edu-txt-center mt20 mb20">
{total > pageSize && <Pagination
showQuickJumper
onChange={(page) => { setPage(page) }}
current={curPage}
total={total}
showTotal={total => `${total}`}
/>}
</div> :
<Nodata _html="暂无数据" />}
<Modal
title="驳回协议"
visible={visible}
onOk={dealAction}
onCancel={() => { setVisible(false) }}
className="form-edit-modal"
>
{
helper('驳回原因', 'message', [{ required: visible, message: "请输入驳回的原因" }, { max: 200, message: '不能超过200字符' }],
<TextArea
placeholder="(必填)我想说点什么呢,200字以内"
autoSize={{ minRows: 6 }}
className="applyText"
/>
)
}
</Modal>
</React.Fragment>
)
}
)

View File

@ -0,0 +1,52 @@
.list-box {
position: relative;
display: flex;
justify-content: space-between;
padding: 20px;
margin: 0 1.5rem;
background: #fff;
border-bottom: 1px solid #dedede;
a.edu-orangeline-btn {
padding: 0px 10px;
}
}
a.primary-link {
color: #1890ff;
}
.infos_item {
float: left;
max-width: 273px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #343434;
margin-right: 6px;
line-height: 35px;
margin-left: 5px;
}
.file-list-prof{
background: #fafafa;
padding:.25rem .5rem;
margin-right:.5rem;
}
.form-edit-modal {
.ant-form-item{
display: flex;
}
.ant-form-item-label{
min-width: 5rem;
}
.ant-form-item-control-wrapper{
width: 75%;
display: inline-block;
}
.ant-input-number{
width: 50%;
}
}

View File

@ -0,0 +1,269 @@
import React, { useEffect, useState, useCallback } from 'react';
import { Pagination, Modal, Input, DatePicker, Form } from 'antd';
import { Link } from "react-router-dom";
import moment from 'moment';
import Nodata from 'forge/Nodata';
import Loading from "src/Loading";
import { getImageUrl, formatDuring } from 'educoder';
import { taskStatusAllArr } from '../../static';
import { delayTask, closeTask } from '../../api';
import './index.scss';
const format = "YYYY-MM-DD";
const statusArr = [];
for (const item of taskStatusAllArr) {
statusArr[item.dicItemCode] = item.dicItemName;
}
const classArr = ['', 'list-done', 'list-error', 'list-red', 'list-yellow', 'list-pay', '', 'list-pay', 'list-gray',];
function getSomeDayAfter(time, nDay) {
return moment(new Date(time).setDate(new Date(time).getDate() + nDay)).format('YYYY-MM-DD HH:mm');
}
export default Form.create()((props) => {
const { list, curPage, total, changePage, loading, showNotification, reloadList, form } = props;
const { getFieldDecorator, validateFields } = form;
const [checkedItem, setCheckedItem] = useState('');
const [visible, setVisible] = useState(false);
const pageSize = props.pageSize || 10;
function closeClick(item) {
Modal.confirm({
title: '是否关闭?',
content: <p class="font-14 lineh-25 mb10 edu-txt-center">关闭后该任务立即结束无法重新打开</p>,
onOk() {
closeTask(item.id).then(res => {
if (res && res.message === 'success') {
showNotification('操作成功');
reloadList();
}
})
}
});
}
function delayClick(item) {
setCheckedItem(item);
setVisible(true);
}
function delayTime() {
validateFields((err, values) => {
if (!err) {
delayTask({
taskId: checkedItem.id,
params: {
delayedTo: moment(values.delayedTo).format(format)
},
}).then(res => {
if (res && res.message === 'success') {
showNotification('操作成功');
reloadList();
setVisible(false);
}
});
}
});
}
function goUser(login) {
window.location.href = `/users/${login}`;
}
function goUserMes(login) {
window.location.href = `/users/${login}/message_detail`;
}
const helper = useCallback(
(label, name, rules, widget, initialValue) => (
<Form.Item label={label}>
{getFieldDecorator(name, { rules, initialValue, validateFirst: true, })(widget)}
</Form.Item>
), []);
const surplusTime = useCallback((item) => {
let surplus;
switch (item.currentStatus) {
case 3:
surplus = item.collectingDays * 24 * 3600 - (new Date() - new Date(item.publishedAt || item.createdAt)) / 1000;
break;
case 4:
surplus = item.choosingDays * 24 * 3600 - (new Date() - new Date(item.collectingCompleteAt)) / 1000;
break;
case 5:
surplus = item.makePublicDays * 24 * 3600 - (new Date() - new Date(item.makePublicAt)) / 1000;
break;
case 6:
surplus = item.signingDays * 24 * 3600 - (new Date() - new Date(item.publicityCompleteAt)) / 1000;
break;
case 7:
surplus = item.payingDays * 24 * 3600 - (new Date() - new Date(item.signingCompleteAt)) / 1000;
break;
default:
surplus = 0;
}
let surplusTimetext = formatDuring(surplus);
return surplus > 0 ? '剩余' + surplusTimetext : <span>延期 <span className="color-red">{surplusTimetext}</span></span>;
}, []);
const stopTime = useCallback((item) => {
switch (item.currentStatus) {
case 3:
return getSomeDayAfter(item.publishedAt, item.collectingDays);
case 4:
return getSomeDayAfter(item.collectingCompleteAt, item.choosingDays);
case 5:
return getSomeDayAfter(item.makePublicAt, item.makePublicDays);
case 6:
return getSomeDayAfter(item.publicityCompleteAt, item.signingDays);
case 7:
return getSomeDayAfter(item.signingCompleteAt, item.payingDays);
case 8:
return item.payingCompleteAt;
default:
return item.expiredAt;
}
})
function disabledDate(current) {
return current && current < moment().endOf('day');
}
return (
loading ? <Loading /> :
<React.Fragment>
{
list.map(item => {
return (
<div className="list-box" key={item.id}>
<img alt="" className="radius mr15" height="50px" src={item.user && getImageUrl(item.user.logo)} width="50px" onClick={() => { goUser(item.user.login) }} />
<div className="flex1">
<div className="clearfix">
<span className="lineh-35" style={{ display: "inline-flex" }}>
<span className="color-grey-9 fl">任务编号:</span>
<span className="infos_item mr15">{item.number}</span>
<span className="fl lineh-35 ml5">
<Link className="primary-link" to={`/task/taskDetail/${item.id}`}>{item.name}</Link>
</span>
<span>{item.currentStatus > 0 && <span className={classArr[item.currentStatus] + ' status-tag'}>{statusArr[item.currentStatus]}</span>}</span>
</span>
<span className="fr">
{item.cancelStatus === 1 && <span className="spanTitle color-red fl ml20">延期</span>}
{item.cancelStatus === 2 && <span className="spanTitle color-grey-6 fl ml20">关闭</span>}
{
item.cancelStatus === 0 && <React.Fragment>
<a className="edu-default-btn edu-orangeline-btn ml20 fl" onClick={() => { goUserMes(item.user.login) }}>私信</a>
<a className="edu-default-btn edu-blueline-btn ml20 fl" onClick={() => { delayClick(item) }}>延期</a>
<a className="edu-default-btn edu-greyline-btn ml20 fl" onClick={() => { closeClick(item) }}>关闭</a>
</React.Fragment>
}
</span>
</div>
<div className="clearfix">
<span className="with40 fl lineh-35">
<span className="color-grey-9 fl">主体名称:</span>
<span className="infos_item">{item.enterpriseName}</span>
</span>
<span className="with30 fl lineh-35">
<span className="color-grey-9 fl">发布方式:</span>
<span className="infos_item mr15 fl">{item.publishMode === 1 ? '统筹任务' : '自主提交'}</span>
</span>
</div>
<div className="clearfix">
<span className="with40 fl lineh-35">
<span className="color-grey-9 fl">联系手机:</span>
<span className="infos_item">{item.user.phone}位置分析{item.belongTo}</span>
</span>
<span className="with40 fl lineh-35 color-orange">
{
item.status === 4 && item.papersCount > 0 && (!item.isProofBoolean) && '未上传佐证材料'
}
{item.status === 6 && item.agreementSigning === 0 && '未选择协议签订方式'}
{
item.status === 6 && item.agreementSigning === 2 && (item.contractStatus === null || item.contractStatus === 0) && '未上传委托协议'
}
{
item.status === 6 && item.agreementSigning === 2 && item.contractStatus === 2 && '已上传委托协议'
}
{
item.status === 7 && item.agreementSigning === 2 && '未上传支付报酬凭证'
}
</span>
</div>
<div className="clearfix">
<span className="with40 fl lineh-35">
<span className="color-grey-9 fl">截止时间:</span>
<span className="infos_item">{stopTime(item)}</span>
{[3, 4, 5, 6, 7].includes(item.currentStatus) && <span className="ml10">{surplusTime(item)}</span>}
</span>
{/* <span className="with40 lineh-35" style={{ display: "inline-flex" }}>
<span className="color-grey-9 fl">任务编号:</span>
<span className="infos_item mr15">{item.number}</span>
<span className="fl lineh-35 ml5">
<Link className="primary-link" to={`/task/taskDetail/${item.id}`}>{item.name}</Link>
</span>
</span> */}
</div>
</div>
</div>
)
})
}
{list.length > 0 ?
<div className="edu-txt-center mt20 mb20">
{total > pageSize && <Pagination
showQuickJumper
onChange={(page) => { changePage(page) }}
current={curPage}
total={total}
showTotal={total => `${total}`}
/>}
</div> :
<Nodata _html="暂无数据" />}
<Modal
title="请输入延期截止的具体时间"
visible={visible}
onOk={delayTime}
onCancel={() => { setVisible(false) }}
className="time-edit-modal"
>
{helper(
"",
"delayedTo",
[{ required: true, message: "请选择日期" }],
<DatePicker
format={format}
placeholder="请选择日期"
disabledDate={disabledDate}
/>,
moment(new Date(), format)
)}
</Modal>
</React.Fragment>
)
}
)

View File

@ -0,0 +1,50 @@
.list-box {
position: relative;
display: flex;
justify-content: space-between;
padding: 20px;
margin: 0 1.5rem;
background: #fff;
border-bottom: 1px solid #dedede;
a.edu-orangeline-btn {
padding: 0px 10px;
}
}
a.primary-link {
color: #1890ff;
}
.infos_item {
float: left;
max-width: 273px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #343434;
margin-right: 6px;
line-height: 35px;
margin-left: 5px;
}
.file-list-prof{
background: #fafafa;
padding:.25rem .5rem;
}
.status-tag {
display: inline-block;
padding: 0px 10px;
margin-left: .625rem;
background: #f8c753;
font-size: 12px;
color: #fff;
border-radius: 14px;
line-height: 22px;
height: 22px;
}
.time-edit-modal .ant-modal-body{
width: 200px;
margin:0 auto;
}

View File

@ -0,0 +1,351 @@
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { Form, Modal, Input, Pagination, Radio, Table } from 'antd';
import { Link } from "react-router-dom";
import Nodata from 'forge/Nodata';
import Loading from "src/Loading";
import Upload from 'military/components/Upload';
import ProofModal from '../proofModal';
import { publishModeArr, taskStatusAllArr, myPaperStatusArr } from '../../static';
import { signMethod, uploadAgreeRequire, downAgreement } from "../../api";
import { httpUrl } from 'military/fetch';
import '../../index.scss';
import './index.scss';
const statusArr = [];
for (const item of taskStatusAllArr) {
statusArr[item.dicItemCode] = item.dicItemName;
}
export default Form.create()((props) => {
const { form, list, curPage, total, changePage, taskCategoryValueArr, loading, publish, showNotification, reloadList, joinTask } = props;
const { getFieldDecorator, validateFields, setFieldsValue, } = form;
const [page, setPage] = useState(1);
const [visibleProofs, setVisibleProofs] = useState(false);
const [taskId, setTaskId] = useState();
const [checkItem, setCheckItem] = useState({});
const [taskModeId, setTaskModeId] = useState('');
const pageSize = props.pageSize || 10;
const [visibleMethod, setVisibleMethod] = useState(false);
const [visibleAgree, setVisibleAgree] = useState(false);
const [fileList, setFileList] = useState(null);
const [dowloadTaskId, setDowloadTaskId] = useState('');
const [visibleDownload, setVisibleDownload] = useState(false);
const [uploadList, setUploadList] = useState([]);
useEffect(() => {
changePage(page);
}, [page]);
useEffect(() => {
visibleDownload && downAgreement({ taskId: dowloadTaskId }).then(res => {
if (res && res.message === "success") {
setUploadList(res.data);
}
})
}, [visibleDownload])
function uploadProofs(item) {
setVisibleProofs(true);
setTaskId(item.id);
setTaskModeId(item.taskModeId);
}
function adviceModal(advice) {
Modal.info({
title: '审核意见',
content: advice
})
}
function signMethodModal(item) {
setVisibleMethod(true);
setTaskId(item.id);
}
function uploadAgree(item) {
setVisibleAgree(true);
setTaskId(item.id);
setCheckItem(item);
}
function chooseMethod() {
validateFields((err, values) => {
if (!err) {
signMethod({
taskId,
method: values.method,
}).then(res => {
if (res && res.message === 'success') {
showNotification('选择协议签订方式成功');
setVisibleMethod(false);
setFieldsValue({
method: ''
});
reloadList();
} else {
showNotification((res && res.message) || '操作失败');
}
})
}
})
}
const helper = useCallback(
(name, rules, widget) => (
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true, })(widget)}
</Form.Item>
), []);
function uploadAgreeList() {
validateFields((err, values) => {
if (!err) {
uploadAgreeRequire({
taskId,
params: {
files: values.files
}
}).then(res => {
if (res && res.message === "success") {
setFieldsValue({
files: ''
});
reloadList();
setVisibleAgree(false);
showNotification('上传协议成功');
} else {
showNotification((res && res.message) || '上传协议失败');
}
})
}
});
}
function uploadFunc(fileList, files) {
setFileList(fileList);
setFieldsValue({
files,
})
}
const columns = useMemo(() => {
return [
{
title: '成果物编号',
dataIndex: 'paperNumber',
},
{
title: '凭证文件',
dataIndex: 'fileName',
render: (text, record) => {
return <a className="line_1 color-grey3" onClick={() => { download(record.winnerContracts[0].id) }}> {record.winnerContracts && record.winnerContracts[0].fileName}</a >
}
},
{
title: '操作',
dataIndex: 'action',
render: (text, record) => {
return <a className="line_1 color-grey3" onClick={(e) => { download(record.winnerContracts[0].id) }}> 下载凭证</a >
}
}
];
}, []);
function download(id) {
window.open(httpUrl + '/busiAttachments/download/' + id);
}
return (
<React.Fragment>
<ul className="df mt10 needs_condition_content_nav">
<li key={1} className="with35 edu-txt-left">任务</li>
<li key={2} className="with15">类型</li>
{publish && <li key={3} className="with10 draft_only">应征投稿</li>}
<li key={4} className="with10">金额</li>
<li key={5} className="flex1">任务状态</li>
<li key={6} className="with15">操作</li>
</ul>
{loading ? <Loading /> : <React.Fragment>
{
list.map(item => {
return (
<div key={item.id} className="needs_condition_content">
<p className="needs_condition_content_t color-dark-grey">
任务编号{item.number}
<i className="ml20 mr5 iconfont icon-shijian color-grey9 font-16"></i>
{item.createdAt}
</p>
<ul className="df">
<li key={1} className="mytask-title with35 edu-txt-left font-16 font-bd" >
<Link className="color-grey3 font-16 font-bd" to={`/task/taskDetail/${item.id}`}>{item.name}</Link>
{
joinTask ? <React.Fragment>
{item.myPaperStatus === 0 && <span className="list_status s_orange ml10">{myPaperStatusArr[item.myPaperStatus]}</span>}
{item.myPaperStatus === 1 && <span className="list_status s_grey ml10">{myPaperStatusArr[item.myPaperStatus]}</span>}
{item.myPaperStatus === 2 && <span className="list_status s_red ml10">{myPaperStatusArr[item.myPaperStatus]}</span>}
{item.status === 6 && (item.agreementSigning === 1 || (item.agreementSigning === 2 && item.contractStatus === 1)) && <span className="list_status s_orange ml10">待签订协议</span>}
</React.Fragment> : <React.Fragment>
{item.status === 6 && (item.agreementSigning === 1 || (item.agreementSigning === 2 && item.auditing && item.auditing.pass === 1)) && <span className="list_status s_orange ml10">待签订协议</span>}
</React.Fragment>
}
</li>
<li key={2} className="with15 flex-column">
<span className="line_1">{taskCategoryValueArr[item.categoryId]}</span>
<span className="line_1">{publishModeArr[item.publishMode]}</span>
</li>
{publish && <li key={3} className="with10 draft_only">{item.papersCount || 0}</li>}
<li key={4} className="with10 color-orange">{item.bounty}</li>
<li className="flex1">
<span>
<span className="line_1">{statusArr[item.status]}</span>
</span>
</li>
<li key={5} className="with15 flex-column">
{
joinTask ? <Link className="line_1 color-grey3" to={`/task/taskDetail/${item.id}`}>查看详情</Link>
:
<React.Fragment>
{
item.status === 0 || item.status === 9 ?
<Link className="line_1 color-grey3" to={`/task/taskEdit/${item.id}`}>编辑草稿</Link> :
<Link className="line_1 color-grey3" to={`/task/taskDetail/${item.id}`}>查看详情</Link>
}
{
item.status === 4 && item.papersCount > 0 && (!item.isProofBoolean) && (!item.isProofBoolean)&&
<a onClick={() => { uploadProofs(item) }} className="line_1 color-blue">上传佐证材料</a>
}
{
(!item.isProofBoolean) && (item.taskResultProof && item.taskResultProof.status === 0) &&
<a onClick={() => { adviceModal(item.advice) }} className="line_1 color-blue">佐证被拒原因</a>
}
{(item.status === 2 || item.status === 9) && <a onClick={() => { adviceModal(item.repairAdvice) }} className="line_1 color-blue">审核意见</a>}
{item.status === 6 && item.agreementSigning === 0 && <a className="line_1 color-blue" onClick={() => { signMethodModal(item) }}>选择协议签订方式</a>}
{item.status === 6 && item.agreementSigning === 2 && (item.contractStatus === null || item.contractStatus === 0) && <a className="line_1 color-blue" onClick={() => { uploadAgree(item) }}>上传委托协议</a>}
{item.status === 6 && item.agreementSigning === 2 && [1, 2].includes(item.contractStatus) && <a className="line_1 color-grey-9" >已上传委托协议</a>}
{item.status === 6 && item.agreementSigning === 1 && <span className="line_1 color-grey-9">已选择自主签订协议</span>}
{item.status === 7 && <span className="line_1 color-grey-9">{item.agreementSigning === 1 ? '待胜出者确认收款' : '待平台上传支付凭证'}</span>}
{item.status === 7 && <a className="line_1 color-blue" onClick={() => { setDowloadTaskId(item.id); setVisibleDownload(true); }}>下载协议签订凭证</a>}
</React.Fragment>
}
</li>
</ul>
</div>
)
})
}
{list.length > 0 ?
<div className="edu-txt-center mt20 mb20">
{total > pageSize && <Pagination
showQuickJumper
onChange={(page) => { setPage(page) }}
current={curPage}
total={total}
showTotal={total => `${total}`}
/>}
</div> :
<Nodata _html="暂无数据" />}
</React.Fragment>
}
{visibleProofs && <ProofModal
taskId={taskId}
taskModeId={taskModeId}
visible={visibleProofs}
changeVisible={setVisibleProofs}
showNotification={showNotification}
reloadList={reloadList}
/>}
<Modal
title="选择签订协议方式"
visible={visibleMethod}
onOk={chooseMethod}
onCancel={() => { setVisibleMethod(false) }}
className="form-edit-modal"
>
{
helper('method', [{ required: visibleMethod, message: '请选择签订协议的方式' }],
<Radio.Group className="vertical-radio">
<Radio value={2}>
<p>委托平台签订</p>
<p className="color-grey-9">以平台名义与评选胜出者签订协议需要经平台管理员审批需要发布方将奖励经费划归平台在审核通过后平台自动发送电子协议</p>
</Radio>
<Radio value={1}>
<p>自主签订</p>
<p className="color-grey-9">通过线下的方式以发布方名义与评选胜出者签订协议</p>
</Radio>
</Radio.Group>)
}
</Modal>
<Modal
title="上传委托协议"
visible={visibleAgree}
onOk={uploadAgreeList}
onCancel={() => { setVisibleAgree(false) }}
className="form-edit-modal"
>
{checkItem.auditing && <p className="color-orange mb10 task_tip">
审核意见{checkItem.auditing.message}
</p>}
<Form.Item className="upload-form" label="协议上传" required={true}>
<Upload
load={uploadFunc}
size={50}
showNotification={showNotification}
fileList={fileList}
/>
{/* 用一个隐藏的input实现上传文件的必填校验 */}
{getFieldDecorator('files', {
rules: [{ required: visibleAgree, message: "请上传文件" }],
validateFirst: true
})(<Input style={{ display: 'none' }} />)}
</Form.Item>
</Modal>
<Modal
title="下载协议签订凭证"
visible={visibleDownload}
onOk={() => { setVisibleDownload(false) }}
onCancel={() => { setVisibleDownload(false) }}
className="form-edit-modal"
>
<Table
loading={loading}
rowKey={(row) => row.id}
dataSource={uploadList}
columns={columns}
pagination={false}
/>
</Modal>
</React.Fragment>
)
}
)

View File

@ -0,0 +1,106 @@
.needs_condition_content_nav {
padding: 0px 20px;
background: #f7f7f7;
height: 50px;
line-height: 50px;
color: #656565;
text-align: center;
margin-bottom: 10px;
}
.needs_condition_content {
border: 1px solid #eaeaea;
border-radius: 4px;
margin-bottom: 20px;
ul {
margin: 0;
padding: 20px;
box-sizing: border-box;
li {
text-align: center;
justify-content: center;
align-items: center;
display: -webkit-flex;
}
.mytask-title {
justify-content: left;
a {
word-break: break-all;
}
span {
flex: 0 0 auto;
}
}
}
.list_status {
border-radius: 10px;
padding: 0px 10px;
height: 22px;
line-height: 22px;
display: inline-block;
color: #fff;
background: #fa6400;
margin-left: 0.625rem;
font-size: 0.8rem;
}
.s_orange {
background: #ffb121;
}
.s_red {
background: #fe0e36;
}
.s_blue {
background: #4cacff;
}
.s_grey {
background: #bababa;
}
.needs_condition_content_t {
height: 40px;
padding: 0px 20px;
background: #f7f7f7;
color: #333;
border-bottom: 1px solid #eaeaea;
line-height: 40px;
}
}
.tab-list-box {
position: relative;
display: flex;
justify-content: space-between;
padding: 20px;
background: #fff;
border-bottom: 1px solid #dedede;
}
.flex-column {
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
}
.vertical-radio {
.ant-radio-wrapper {
display: flex;
line-height: 2rem;
align-items: flex-start;
.ant-radio {
margin-top: 0.5rem;
}
p {
white-space: normal;
word-break: break-all;
}
}
}

View File

@ -0,0 +1,297 @@
import React, { useEffect, useState, useCallback } from 'react';
import { Pagination, Modal, Form, Input, Button } from 'antd';
import ReactWEditor from 'wangeditor-for-react';
import { timeAgo, getImageUrl } from 'educoder';
import Nodata from 'forge/Nodata';
import Loading from "src/Loading";
import { editorConfig } from 'military/components/config';
import AgreementModal from '../agreementModal';
import ComplainModal from '../complainModal';
import { reportPaper, thumbUpPaper, commentAdd, confirmReceipt } from '../../api';
import { paperCheckStatusArr } from '../../static';
import { httpUrl } from '../../../fetch';
import winpng from '../../image/winner.png';
import './index.scss';
const { TextArea } = Input;
const paperCheckStatus = [];
for (const item of paperCheckStatusArr) {
paperCheckStatus[item.dicItemCode] = item.dicItemName;
}
export default Form.create()((props) => {
const { list, curPage, total, changePage, loading, applyStatusAllNameArr, reloadList, showNotification, current_user, form, detailStatus } = props;
const { getFieldDecorator, validateFields, setFieldsValue } = form;
const [page, setPage] = useState(1);
const pageSize = props.pageSize || 10;
const [checkedItem, setCheckedItem] = useState({});
const [reportVisible, setReportVisible] = useState(false);
const [commentHtml, setCommentHtml] = useState('');
const [commentId, setCommentId] = useState(undefined);
const [complainVisible, setComplainVisible] = useState(false);
const [agreeVisible, setAgreeVisible] = useState(false);
const [loadingChild, setLoadingChild] = useState(false);
useEffect(() => {
changePage(page);
}, [page]);
function downFile(item) {
let url = httpUrl + '/busiAttachments/download/' + item.id;
window.open(url);
}
function report() {
validateFields((error, values) => {
if (!error) {
setLoadingChild(true);
reportPaper({
paperId: checkedItem.id,
content: values.reportValue
}).then(res => {
if (res && res.message === 'success') {
showNotification('举报成功');
setReportVisible(false);
setFieldsValue({
reportValue: '',
complainValue: '',
});
}
setLoadingChild(false);
});
}
});
}
function thumbUp(id) {
setLoadingChild(true);
thumbUpPaper(id).then(res => {
reloadList();
setLoadingChild(false);
});
}
function commentPush(item) {
if (!commentHtml) {
showNotification("请输入评价");
return;
}
if (commentHtml.length > 10000) {
showNotification("评论过长,请减少评论内容或者简化评论格式");
return;
}
commentAdd({
content: commentHtml,
paperId: item.id,
taskId: item.taskId
}).then(res => {
if (res.message === 'success') {
closeComment();
reloadList();
}
});
}
function closeComment() {
setCommentId(undefined);
setCommentHtml('');
}
function commentEdit(id) {
setCommentId(id);
setCommentHtml('');
}
function goUser(login) {
window.location.href = `/users/${login}`;
}
function confirmReceiptModal(paperId) {
Modal.confirm({
title: '确认收款',
content: '确认已经收到任务报酬',
onOk: () => {
confirmReceipt(paperId).then(res => {
if (res && res.message === "success") {
showNotification("您已确认收款!");
reloadList();
}
})
}
})
}
const helper = useCallback(
(name, rules, widget) => (
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
</Form.Item>
),
[]
);
return (
<React.Fragment>
{loading || loadingChild ? <Loading /> :
<React.Fragment>
{
list.map(item => {
return (
<div className="fileComments df" key={item.id}>
<img alt="头像加载失败" src={item.user && getImageUrl(item.user.logo)} width="50" height="50" className="bor-radius-all mr20" />
<div className="flex1">
<ul>
<li className="fl pr">
<span className={item.user.nickname === "******" ? "font-16 mr20 color-grey3" : "user-box font-16 mr20 color-grey3 clickable"} onClick={() => { item.user.nickname === "******" ? "" : goUser(item.user.login) }}>
{item.user.nickname || item.user.login}
</span>
<span className="color-grey9">{timeAgo(item.createdAt)}</span>
{item.status === 2 ? <img alt="胜出" className="mr5" src={winpng} /> : <span className="color-blue ml10">{item.checkStatus !== 1 ? paperCheckStatus[item.checkStatus] : applyStatusAllNameArr[item.status]}</span>}
</li>
<li className="fr">
{((item.needComplain && detailStatus === 3) || (detailStatus === 5 && item.publicTaskComplain && item.checkStatus === 1 && item.status !== 2)) && (current_user.login === item.user.login) &&
<a className="base_smallBtn blue_line_btn fl" onClick={() => { setComplainVisible(true); setCheckedItem(item) }}>申诉</a>}
{item.status === 2 && detailStatus === 6 && (current_user.login === item.user.login) && (!item.sign) && (item.canApplicantSign || item.canApplicantSignByPlatform) &&
<a className="base_smallBtn blue_line_btn fl" onClick={() => { setAgreeVisible(true); setCheckedItem(item) }}>签订协议</a>}
{item.status === 2 && detailStatus === 7 && (current_user.login === item.user.login) && (item.task && item.task.agreementSigning === 1) && (!item.isPay) &&
<a className="base_smallBtn blue_line_btn fl" onClick={() => { confirmReceiptModal(item.id) }}>确认收款</a>}
</li>
</ul>
<div className="paper-detail-content markdown-body editormd-html-preview editor-w-text" dangerouslySetInnerHTML={{ __html: item.paperDetail ? item.paperDetail.content : '' }}></div>
<div className="attachments" >
{
item.paperDetail && item.paperDetail.busiAttachments && item.paperDetail.busiAttachments.map(fileItem => {
return <div className="file-list-box" key={fileItem.id}>
<a onClick={() => { downFile(fileItem) }}><i className="iconfont icon-fujian color-green font-14 mr3"></i>
{fileItem.fileName} </a>
<span className="ml10 color-grey-9">({fileItem.fileSizeString})</span>
</div>
})
}
</div>
{item.comments && item.comments.length > 0 && <div className="padding20 fileCommentsList">
{item.comments.map(commentsItem => {
return <div className="comments-item" key={commentsItem.id}>
<div className="comments-author">
<span className="color-grey3 font-bd mr20">{commentsItem.user.nickname || commentsItem.user.login}</span>
<span className="color-grey9">{timeAgo(commentsItem.createdAt)}</span>
</div>
<div className="editor-w-text comments-content" dangerouslySetInnerHTML={{ __html: commentsItem.details && commentsItem.details.content }}>
</div>
</div>
})
}
</div>}
<li className="clearfix color-grey-6 mt10 mb20">
<span className="mr50 fl"><span className="color-grey9">成果编号</span>#{item.number}</span>
<span className="mr50 fl"><span className="color-grey9">提交时间</span>{item.createdAt}</span>
<span className="fl"><span className="color-grey9">稿件状态</span>{item.read ? '雇主已浏览' : '雇主未浏览'}</span>
{
item.user.nickname !== "******" && <span className="fr">
{[4, 5].includes(detailStatus) && <a className="mr20" onClick={() => { setReportVisible(true); setCheckedItem(item) }}><i className="iconfont icon-jinggao font-15 mr3"></i>举报</a>}
<a className="mr20" onClick={() => { commentEdit(item.id) }}><i className="iconfont icon-huifu1 font-15 mr3"></i>{item.comments ? item.comments.length : 0}</a>
<a onClick={() => { thumbUp(item.id) }}><i className="iconfont icon-dianzan11 font-16 mr3"></i>{item.thumbsUp}</a>
</span>
}
</li>
{commentId === item.id && <React.Fragment>
<ReactWEditor
config={{
...editorConfig,
placeholder: "请输入评论",
}}
onChange={(html) => { setCommentHtml(html) }}
/>
<Button className="mt20 mr20 mb20 fr" type={"primary"} onClick={() => { commentPush(item) }}>发表</Button>
<Button className="mt20 mr20 mb20 fr" onClick={closeComment}>取消</Button>
</React.Fragment>}
</div>
</div>
)
})
}
{list.length > 0 ?
<div className="edu-txt-center mt20 mb20">
{total > pageSize && <Pagination
showQuickJumper
onChange={(page) => { setPage(page) }}
current={curPage}
total={total}
showTotal={total => `${total}`}
/>}
</div> :
<Nodata _html="暂无数据" />}
</React.Fragment>
}
<Modal
title="举报"
visible={reportVisible}
onOk={report}
onCancel={() => { setReportVisible(false) }}
className="form-edit-modal"
>
<div className="task-popup-content">
<p className="edu-txt-center lineh-20 mb10">你的举报信息将发送给平台管理员</p>
<p className="edu-txt-center lineh-20">请如实填写有效的举报原由我们将尽快完成审核</p>
{
helper('reportValue', [{ required: reportVisible, message: "(必填)请在此输入发起举报的原因最大限制100个字符" },
{ max: 100, message: '长度不能超过100个字符' }],
<TextArea
placeholder="(必填)请在此输入发起举报的原因最大限制100个字符"
autoSize={{ minRows: 6 }}
className="applyText"
/>
)
}
</div>
</Modal>
<ComplainModal
visible={complainVisible}
setVisible={setComplainVisible}
checkedItem={checkedItem}
detailStatus={detailStatus}
showNotification={showNotification}
reloadList={reloadList}
/>
{agreeVisible && <AgreementModal
paperId={checkedItem.id}
checkedItem={checkedItem}
visible={agreeVisible}
setVisible={setAgreeVisible}
showNotification={showNotification}
reloadList={reloadList}
/>}
</React.Fragment>
)
}
)

View File

@ -0,0 +1,46 @@
.blue_line_btn {
color: #4cacff !important;
border: 1px solid #4cacff;
background: #fff !important;
}
.base_smallBtn {
color: #ccc;
font-size: 14px;
background: #ccc;
border-radius: 4px;
height: 30px;
line-height: 30px;
display: block;
width: 100px;
box-sizing: border-box;
text-align: center;
margin-top: 1.25rem;
}
.fileCommentsList {
background: #fafafa;
padding-top: .25rem;
}
.comments-item{
margin-top:1rem;
}
.comments-content {
text-align: left;
font-size: 16px;
line-height: 1.6;
padding: 20px;
overflow: auto;
width: 100%;
background-color: #fff;
}
.small-head{
max-width: 2rem;
}
.clickable:hover{
color: #4cacff;
cursor: pointer;
}

View File

@ -0,0 +1,76 @@
import React, { useEffect, useState } from 'react';
import { Icon, Pagination } from 'antd';
import { getImageUrl } from 'educoder';
import Nodata from 'forge/Nodata';
import { taskStatusAllArr } from '../../static';
import Loading from "src/Loading";
import './index.scss';
const statusArr = [];
for (const item of taskStatusAllArr) {
statusArr[item.dicItemCode] = item.dicItemName;
}
const classArr = ['', 'list-done', 'list-error', 'list-red', 'list-yellow', 'list-pay', '', 'list-pay', 'list-gray',];
export default (props) => {
const { list, itemClick, curPage, total, changePage, loading } = props;
const [page, setPage] = useState(1);
const pageSize = props.pageSize || 10;
useEffect(() => {
changePage(page);
}, [page]);
function goUser(login) {
window.location.href = `/users/${login}`;
}
return (
loading ? <Loading /> :
<React.Fragment>
{
list.map(item => {
return (
<div className="list-box" key={item.id}>
<div className="list-content">
<div className="list-title mb10" onClick={() => { itemClick(item.id) }}>
<div className="title-content text-ellipsis">{item.name}</div>
{item.status && <span className={classArr[item.status]}>{item.exceptClosedBoolean?'已关闭':statusArr[item.status]}</span>}
{(item.status !== 8) && item.delayTime.indexOf('延期') > -1 && <span className="list-yellow">延期中</span>}
</div>
<div className="list-other">
<span className="user-box mr30" onClick={() => { goUser(item.user.login) }}>
<img alt="" className="head-log-small" src={getImageUrl(item.user.logo)} />
<span>{item.user.nickname || item.user.login}</span>
</span>
<span className="font-14 mr30"><i className="iconfont icon-dianjiliang mr3 font-12" />{item.visits || 0}</span>
<span className="font-14 mr30"><Icon type="user" /><span className="color-orange">{item.papersCount || 0}</span>人参与</span>
{(item.status !== 8) && <span className="color-orange mr30 font-14"><i className="mr5 iconfont icon-shijian color-grey9 font-14"></i>{item.delayTime}</span>}
</div>
</div>
<span className="price color-deep-blue ">
<span className="font-16"></span>
<span className="font-24">{item.bounty}</span>
</span>
</div>
)
})
}
{list.length > 0 ?
<div className="edu-txt-center mt20 mb20">
{total > pageSize && <Pagination
showQuickJumper
onChange={(page) => { setPage(page) }}
current={curPage}
total={total}
showTotal={total => `${total}`}
/>}
</div> :
<Nodata _html="暂无数据" />}
</React.Fragment>
)
}

View File

@ -0,0 +1,57 @@
.list-box {
position: relative;
display: flex;
justify-content: space-between;
padding: 20px;
margin: 0 1.5rem;
background: #fff;
border-bottom: 1px solid #dedede;
}
.user-box{
cursor: pointer;
&:hover{
color: #409eff;
}
.head-log-small{
position: relative;
top:-2px;
}
}
.price {
font-weight: 500;
padding-top: 10px;
}
.list-title {
display: flex;
align-items: center;
font-size: 1rem;
color: #333;
cursor: pointer;
font-weight: 500;
.title-content{
display: inline-block;
max-width: 80%;
}
}
.list-title:hover {
color: #409eff;
}
.list-title span {
padding: 0px 10px;
margin-left: .625rem;
background: #f8c753;
font-size: .8rem;
line-height: 1.8;
color: #fff;
border-radius: 14px;
}
.list-other {
font-size: 12px;
color: #888;
i {
font-size: 14px;
margin-right: 5px;
}
}

View File

@ -0,0 +1,157 @@
import React, { useEffect, useState, useCallback } from 'react';
import { Pagination, Modal, Input, Button, Form, InputNumber } from 'antd';
import { Link } from "react-router-dom";
import Nodata from 'forge/Nodata';
import Loading from "src/Loading";
import Upload from 'military/components/Upload';
import { timeAgo, getImageUrl } from 'educoder';
import { uploadPayProof } from '../../api';
import './index.scss';
export default Form.create()((props) => {
const { form, list, curPage, total, changePage, loading, showNotification, reloadList } = props;
const { getFieldDecorator, validateFields, setFieldsValue } = form;
const [checkedItem, setCheckedItem] = useState({});
const [visible, setVisible] = useState(false);
const [page, setPage] = useState(1);
const [fileList, setFileList] = useState(null);
const pageSize = props.pageSize || 10;
useEffect(() => {
changePage(page);
}, [page]);
function goUser(login) {
window.location.href = `/users/${login}`;
}
function goUserMes(login) {
window.location.href = `/users/${login}/message_detail`;
}
//
function uploadFunc(fileList, files) {
setFileList(fileList);
setFieldsValue({ files });
}
function uploadPay() {
validateFields((err, values) => {
if (!err) {
uploadPayProof({
paperId: checkedItem.id,
params: {
files: values.files,
}
}).then(res => {
if (res.message === 'success') {
setFieldsValue({
files: '',
});
setFileList([]);
reloadList();
setVisible(false);
showNotification("上传支付凭证成功!");
} else {
showNotification(res.message || "上传支付凭证失败")
}
})
}
})
}
return (
loading ? <Loading /> :
<React.Fragment>
{
list.map(item => {
return (
<div className="list-box" key={item.id}>
<img alt="" className="radius mr15" height="50px" src={item.user && getImageUrl(item.user.logo)} width="50px" />
<div className="flex1">
<li className="clearfix mb20">
<a className="user-box fl mr15 color-grey-3 font-16" onClick={() => { goUser(item.user.login) }}>{item.user && (item.user.nickname || item.user.login)}</a>
<span className="fl color-grey-9 mt3 mr15">{timeAgo(item.createdAt)}</span>
<span className="infos_item color-grey-9 fl">发布方式:</span>
<span className="infos_item mr15 fl">{item.task && item.task.publishMode === 1 ? '统筹任务' : '自主提交'}</span>
<span className="fr"><a className="edu-default-btn edu-orangeline-btn ml20 fl" onClick={() => { item.user && goUserMes(item.user.login) }}>私信</a></span>
</li>
<div className="clearfix">
<div className="width100 lineh-35" >
<span className="with40 fl lineh-35">
<span className="color-grey-9 fl">任务编号:</span>
<span className="fl lineh-35 ml5">
<Link className="primary-link" to={`/task/taskDetail/${item.taskId}`}>{item.task.name}</Link>
</span>
</span>
<span className="with40 fl lineh-35">
<span className="color-grey-9 fl">任务编号:</span>
<span className="infos_item">{item.task.number}</span>
</span>
</div>
<div className="clearfix">
<span className="with40 fl lineh-35" style={{ display: "inline-flex" }}>
<span className="color-grey-9 fl">联系手机:</span>
<span className="infos_item">{item.user.phone}</span>
</span>
</div>
<div className="clearfix"></div>
{!item.isPay && (item.status !== 8) && <div className="width100 lineh-35 clearfix">
<Button type="primary" onClick={() => { setVisible(true); setCheckedItem(item) }}>上传支付报酬凭证</Button>
</div>}
</div>
</div>
</div>
)
})
}
{list.length > 0 ?
<div className="edu-txt-center mt20 mb20">
{total > pageSize && <Pagination
showQuickJumper
onChange={(page) => { setPage(page) }}
current={curPage}
total={total}
showTotal={total => `${total}`}
/>}
</div> :
<Nodata _html="暂无数据" />}
<Modal
title="上传支付报酬凭证"
visible={visible}
onOk={uploadPay}
onCancel={() => { setVisible(false) }}
className="form-edit-modal"
>
<div className="task-popup-content">
<Form.Item className="upload-form" label="附件上传" required={true}>
<Upload
className="commentStyle"
load={uploadFunc}
size={50}
showNotification={showNotification}
fileList={fileList}
/>
{getFieldDecorator('files', {
rules: [{ required: visible, message: "请上传文件" }],
validateFirst: true
})(<Input style={{ display: 'none' }} />)}
</Form.Item>
</div>
</Modal>
</React.Fragment>
)
}
)

View File

@ -0,0 +1,52 @@
.list-box {
position: relative;
display: flex;
justify-content: space-between;
padding: 20px;
margin: 0 1.5rem;
background: #fff;
border-bottom: 1px solid #dedede;
a.edu-orangeline-btn {
padding: 0px 10px;
}
}
a.primary-link {
color: #1890ff;
}
.infos_item {
float: left;
max-width: 273px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #343434;
margin-right: 6px;
line-height: 35px;
margin-left: 5px;
}
.file-list-prof{
background: #fafafa;
padding:.25rem .5rem;
margin-right:.5rem;
}
.form-edit-modal {
.ant-form-item{
display: flex;
}
.ant-form-item-label{
min-width: 5rem;
}
.ant-form-item-control-wrapper{
width: 75%;
display: inline-block;
}
.ant-input-number{
width: 50%;
}
}

View File

@ -0,0 +1,182 @@
import React, { useEffect, useState, } from 'react';
import { Pagination, Modal, Input } from 'antd';
import { Link } from "react-router-dom";
import Nodata from 'forge/Nodata';
import Loading from "src/Loading";
import { timeAgo, getImageUrl } from 'educoder';
import { checkProof } from '../../api';
import { httpUrl } from 'military/fetch';
import './index.scss';
const { TextArea } = Input;
export default (props) => {
const { list, curPage, total, changePage, loading, showNotification, reloadList } = props;
const [checkedItem, setCheckedItem] = useState('');
const [page, setPage] = useState(1);
const [visible, setVisible] = useState(false);
const [advice, setRepairAdvice] = useState('');
const pageSize = props.pageSize || 10;
useEffect(() => {
changePage(page);
}, [page]);
function agreeClick(item) {
Modal.confirm({
title: '是否确认审批通过?',
onOk() {
checkProof({
id: item.taskResultProof.id,
isPassed: 1,
taskId: item.id,
advice: "",
}).then(res => {
if (res && res.message === 'success') {
showNotification('操作成功');
reloadList();
}
})
}
});
}
function refuseClick(item) {
setCheckedItem(item);
setVisible(true);
}
function refuse() {
if (!advice) {
showNotification('请输入拒绝的理由');
return;
}
checkProof({
id: checkedItem.taskResultProof.id,
taskId: checkedItem.id,
isPassed: 0,
advice: advice
}).then(res => {
if (res && res.message === 'success') {
showNotification('操作成功');
reloadList();
setRepairAdvice('');
setVisible(false);
}
});
}
function goUser(login) {
window.location.href = `/users/${login}`;
}
function goUserMes(login) {
window.location.href = `/users/${login}/message_detail`;
}
function downFile(item) {
let url = httpUrl + '/busiAttachments/download/' + item.id;
window.open(url);
}
return (
loading ? <Loading /> :
<React.Fragment>
{
list.map(item => {
return (
<div className="list-box" key={item.id}>
<img alt="" className="radius mr15" height="50px" src={item.user && getImageUrl(item.user.logo)} width="50px" />
<div className="flex1">
<li className="clearfix mb20">
<a className="user-box fl mr15 color-grey-3 font-16" onClick={() => { goUser(item.user.login) }}>{item.user.nickname || item.user.login}</a>
<span className="fl color-grey-9 mt3 mr15">{timeAgo(item.createdAt)}</span>
<span className="with40 fl lineh-35">
<span className="color-grey-9 fl">主体名称:</span>
<span className="infos_item">{item.enterpriseName}</span>
</span>
<span className="fr">
{item.taskResultProof.status === 1 && <span className="spanTitle color-grey-6 fl ml20">已同意</span>}
{item.taskResultProof.status === 0 && <span className="spanTitle color-red fl ml20">已拒绝</span>}
{
item.taskResultProof.status === 2 && <React.Fragment>
<a className="edu-default-btn edu-orangeline-btn ml20 fl" onClick={() => { goUserMes(item.user.login) }}>私信</a>
<a className="edu-default-btn edu-blueline-btn ml20 fl" onClick={() => { agreeClick(item) }}>同意</a>
<a className="edu-default-btn edu-greyline-btn ml20 fl" onClick={() => { refuseClick(item) }}>拒绝</a>
</React.Fragment>
}
</span>
</li>
<ul className="clearfix">
<div className="width100 lineh-35" style={{ display: "inline-flex" }}>
<span className="color-grey-9 fl">任务编号:</span>
<span className="infos_item mr15">{item.number}</span>
<span className="fl lineh-35 ml5">
<Link className="primary-link" to={`/task/taskDetail/${item.id}`}>{item.name}</Link>
</span>
</div>
<div className="clearfix"></div>
<div className="width100 lineh-35">
<span className="color-grey-9 fl">评选胜出{item.taskResultProof.winnerName.split(',').length}</span>
<span className="infos_item">{item.taskResultProof.winnerName}</span>
</div>
<div className="clearfix"></div>
<div className="width100 lineh-35 clearfix">
<span className="color-grey-9 fl">佐证材料</span>
{
item.taskProofAttachments && item.taskProofAttachments.map(fileItem => {
return <span className="file-list-prof " key={fileItem.id}>
<a onClick={() => { downFile(fileItem) }}><i className="iconfont icon-fujian color-green font-14 mr3"></i>
{fileItem.fileName} </a>
<span className="ml10 color-grey-9">({fileItem.fileSizeString})</span>
</span>
})
}
</div>
{item.taskResultProof.status === 0 && <p className="color-orange lineh-35 "><span className="fl color-orange mr5">拒绝原因:</span>{item.advice}</p>}
</ul>
</div>
</div>
)
})
}
{list.length > 0 ?
<div className="edu-txt-center mt20 mb20">
{total > pageSize && <Pagination
showQuickJumper
onChange={(page) => { setPage(page) }}
current={curPage}
total={total}
showTotal={total => `${total}`}
/>}
</div> :
<Nodata _html="暂无数据" />}
<Modal
title="拒绝原因"
visible={visible}
onOk={refuse}
onCancel={() => { setVisible(false) }}
className="form-edit-modal"
>
<TextArea
value={advice}
placeholder="(必填)我想说点什么呢,200字以内"
autoSize={{ minRows: 6 }}
className="applyText"
onChange={(e) => { setRepairAdvice(e.target.value) }}
maxLength={200}
/>
</Modal>
</React.Fragment>
)
}

View File

@ -0,0 +1,33 @@
.list-box {
position: relative;
display: flex;
justify-content: space-between;
padding: 20px;
margin: 0 1.5rem;
background: #fff;
border-bottom: 1px solid #dedede;
a.edu-orangeline-btn {
padding: 0px 10px;
}
}
a.primary-link {
color: #1890ff;
}
.infos_item {
float: left;
max-width: 273px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #343434;
margin-right: 6px;
line-height: 35px;
margin-left: 5px;
}
.file-list-prof{
background: #fafafa;
padding:.25rem .5rem;
}

View File

@ -0,0 +1,228 @@
import React, { useEffect, useState, useCallback } from 'react';
import { Pagination, Modal, Input, Radio, Form, InputNumber } from 'antd';
import { Link } from "react-router-dom";
import Nodata from 'forge/Nodata';
import Loading from "src/Loading";
import { timeAgo, getImageUrl } from 'educoder';
import { checkPublicity } from '../../api';
import { httpUrl } from 'military/fetch';
import './index.scss';
const { TextArea } = Input;
export default Form.create()((props) => {
const { form, list, curPage, total, changePage, loading, showNotification, reloadList } = props;
const { getFieldDecorator, validateFields, setFieldsValue } = form;
const [checkedItem, setCheckedItem] = useState('');
const [page, setPage] = useState(1);
const [visible, setVisible] = useState(false);
const pageSize = props.pageSize || 10;
const [isAccepted, setIsAccepted] = useState('');
const [isReconsidered, setIsReconsidered] = useState('');
const [isChanged, setIsChanged] = useState('');
useEffect(() => {
changePage(page);
}, [page]);
function deal(item) {
setCheckedItem(item);
setVisible(true);
}
function dealAction() {
validateFields((error, values) => {
if (!error) {
checkPublicity({
complaintMaterialId: checkedItem.id,
...values,
}).then(res => {
if (res && res.message === 'success') {
showNotification('操作成功');
reloadList();
setFieldsValue({
isAccepted: '',
reason: '',
});
setVisible(false);
}
});
}
});
}
function goUser(login) {
window.location.href = `/users/${login}`;
}
function goUserMes(login) {
window.location.href = `/users/${login}/message_detail`;
}
function downFile(item) {
let url = httpUrl + '/busiAttachments/download/' + item.id;
window.open(url);
}
const helper = useCallback(
(label, name, rules, widget, rightDom) => (
<Form.Item label={label}>
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
{rightDom}
</Form.Item>
),
[]
);
return (
loading ? <Loading /> :
<React.Fragment>
{
list.map(item => {
return (
<div className="list-box" key={item.id}>
<img alt="头像加载失败" className="radius mr15" height="50px" src={item.user && getImageUrl(item.user.logo)} width="50px" />
<div className="flex1">
<li className="clearfix mb20">
<a className="user-box fl mr15 color-grey-3 font-16" onClick={() => { goUser(item.user.login) }}>{item.user && (item.user.nickname || item.user.login)}</a>
<span className="fl color-grey-9 mt3 mr15">{timeAgo(item.createdAt)}</span>
<span className="fr">
<a className="edu-default-btn edu-orangeline-btn ml20 fl" onClick={() => { goUserMes(item.user.login) }}>私信</a>
{item.status === 2 && <a className="edu-default-btn edu-blueline-btn ml20 fl" onClick={() => { deal(item) }}>处理</a>}
</span>
</li>
<div className="clearfix">
{/* <div className="width100">
<span className="with40 fl lineh-35">
<span className="color-grey-9 fl">联系手机:</span>
<span className="infos_item">{item.contactPhone}位置分析河北-唐山</span>
</span>
<span className="inline-block lineh-35">
<span className="color-grey-9 fl">IP地址:</span>
<span className="infos_item">{item.ip}位置分析北京-北京</span>
</span>
</div> */}
<div className="clearfix"></div>
<div className="width100 lineh-35">
<span className="color-grey-9 fl">成果编号</span>
<span className="fl lineh-35 ml5">
<Link className="primary-link" to={`/task/taskDetail/${item.taskId}`}>{item.paperNumber}</Link>
</span>
</div>
<div className="clearfix"></div>
<div className="width100 lineh-35 clearfix">
<span className="color-grey-9 fl">申诉材料</span>
{
item.materials && item.materials.map(fileItem => {
return <span className="file-list-prof " key={fileItem.id}>
<a onClick={() => { downFile(fileItem) }}><i className="iconfont icon-fujian color-green font-14 mr3"></i>
{fileItem.fileName} </a>
<span className="ml10 color-grey-9">({fileItem.fileSizeString})</span>
</span>
})
}
</div>
<div className="width100 lineh-35">
<span className="color-grey-9 fl">申诉原因</span>
<span className="infos_item">{item.content}</span>
</div>
{/* {item.taskResultProof.status === 0 && <p className="color-orange lineh-35 "><span className="fl color-orange mr5">拒绝原因:</span>{item.advice}</p>} */}
</div>
</div>
</div>
)
})
}
{list.length > 0 ?
<div className="edu-txt-center mt20 mb20">
{total > pageSize && <Pagination
showQuickJumper
onChange={(page) => { setPage(page) }}
current={curPage}
total={total}
showTotal={total => `${total}`}
/>}
</div> :
<Nodata _html="暂无数据" />}
<Modal
title="处理申诉"
visible={visible}
onOk={dealAction}
onCancel={() => { setVisible(false) }}
className="form-edit-modal"
>
{
helper('是否受理', 'isAccepted', [{ required: true, message: "请选择是否受理" }],
<Radio.Group
className="mb10"
onChange={(e) => { setIsAccepted(e.target.value); setIsReconsidered(''); setIsChanged(''); }}
>
<Radio value={'1'}>受理</Radio>
<Radio value={'0'}>不受理</Radio>
</Radio.Group>
)
}
{
isAccepted === '1' && helper('复议评审', 'isReconsidered', [{ required: true, message: "请选择是否启动复议评审" }],
<Radio.Group
className="mb10"
onChange={(e) => { setIsReconsidered(e.target.value); setIsChanged(''); }}
>
<Radio value={'1'}>启动</Radio>
<Radio value={'0'}>不启动</Radio>
</Radio.Group>
)
}
{
isAccepted === '1' && isReconsidered === '0' && helper('公示结果', 'isChanged', [{ required: true, message: "请选择是否更改公示结果" }],
<Radio.Group
className="mb10"
onChange={(e) => { setIsChanged(e.target.value) }}
>
<Radio value={'1'}>更改</Radio>
<Radio value={'0'}>不更改</Radio>
</Radio.Group>
)
}
{
isAccepted === '1' && isReconsidered === '0' && isChanged === '1' && helper('延长公示', 'delayDuration',
[{ required: true, message: "请输入延长天数" }],
<InputNumber
placeholder="您打算延长多少天呢"
max={150}
min={0}
/>,
' 天'
)
}
{
helper('原因', 'reason', [{ required: true, message: "请输入原因" }, { max: 200, message: '不能超过200字符' }],
<TextArea
placeholder="(必填)我想说点什么呢,200字以内"
autoSize={{ minRows: 6 }}
className="applyText"
/>
)
}
</Modal>
</React.Fragment>
)
}
)

View File

@ -0,0 +1,52 @@
.list-box {
position: relative;
display: flex;
justify-content: space-between;
padding: 20px;
margin: 0 1.5rem;
background: #fff;
border-bottom: 1px solid #dedede;
a.edu-orangeline-btn {
padding: 0px 10px;
}
}
a.primary-link {
color: #1890ff;
}
.infos_item {
float: left;
max-width: 90%;
overflow: hidden;
// white-space: nowrap;
text-overflow: ellipsis;
color: #343434;
margin-right: 6px;
line-height: 35px;
margin-left: 5px;
}
.file-list-prof{
background: #fafafa;
padding:.25rem .5rem;
margin-right:.5rem;
}
.form-edit-modal {
.ant-form-item{
display: flex;
}
.ant-form-item-label{
min-width: 5rem;
}
.ant-form-item-control-wrapper{
width: 75%;
display: inline-block;
}
.ant-input-number{
width: 50%;
}
}

View File

@ -0,0 +1,223 @@
import React, { useEffect, useState } from 'react';
import { Pagination, Modal, Input, Radio, Button } from 'antd';
import { Link } from "react-router-dom";
import Nodata from 'forge/Nodata';
import Loading from "src/Loading";
import { timeAgo, getImageUrl } from 'educoder';
import { main_web_site_url } from '../../static';
import { checkTask } from '../../api';
import './index.scss';
const { TextArea } = Input;
export default (props) => {
const { list, categoryArr, curPage, total, changePage, loading, showNotification, reloadList } = props;
const [checkedId, setCheckedId] = useState('');
const [page, setPage] = useState(1);
const [visible, setVisible] = useState(false);
const [isRepairDocument, setIsRepairDocument] = useState('0');
const [repairAdvice, setRepairAdvice] = useState('');
const pageSize = props.pageSize || 10;
useEffect(() => {
changePage(page);
}, [page]);
function agreeClick(id) {
Modal.confirm({
title: '是否确认审批通过?',
onOk() {
checkTask({
id,
isPassed: 1,
}).then(res => {
if (res && res.message === 'success') {
showNotification('操作成功');
reloadList();
}
})
}
});
}
function refuseClick(id) {
setCheckedId(id);
setVisible(true);
}
function refuse() {
if (!repairAdvice) {
showNotification('请输入拒绝的理由');
return;
}
checkTask({
id: checkedId,
isPassed: 0,
isRepairDocument,
repairAdvice: repairAdvice
}).then(res => {
if (res && res.message === 'success') {
showNotification('操作成功');
reloadList();
setRepairAdvice('');
setVisible(false);
}
});
}
function goUser(login) {
window.location.href = `/users/${login}`;
}
function goUserMes(login) {
window.location.href = `/users/${login}/message_detail`;
}
function scale(scale) {
if (scale > 10000) {
return '10000人以上';
} else if (scale > 1000) {
return '1001 - 10000人';
} else if (scale > 100) {
return '101 - 1000人';
} else if (scale > 10) {
return '11 - 99人';
} else {
return '0 - 10人';
}
}
return (
loading ? <Loading /> :
<React.Fragment>
{
list.map(item => {
return (
<div className="list-box" key={item.id}>
<img alt="头像加载失败" className="radius mr15" height="50px" src={item.user && getImageUrl(item.user.logo)} width="50px" />
<div className="flex1">
<li className="clearfix mb20">
<a className="user-box fl mr15 color-grey-3 font-16" onClick={() => { goUser(item.user.login) }}>{item.user.nickname || item.user.login}</a>
<span className="fl color-grey-9 mt3">{timeAgo(item.createdAt)}</span>
<span className="fr">
{item.status > 2 && item.status !== 9 && <span className="spanTitle color-grey-6 fl ml20">已同意</span>}
{item.status === 9 && <span className="spanTitle color-yellow fl ml20">已处理</span>}
{item.status === 2 && <span className="spanTitle color-red fl ml20">已拒绝</span>}
{
item.status === 1 && <React.Fragment>
<a className="edu-default-btn edu-orangeline-btn ml20 fl" onClick={() => { goUserMes(item.user.login) }}>私信</a>
<a className="edu-default-btn edu-blueline-btn ml20 fl" onClick={() => { agreeClick(item.id) }}>同意</a>
<a className="edu-default-btn edu-greyline-btn ml20 fl" onClick={() => { refuseClick(item.id) }}>拒绝</a>
</React.Fragment>
}
</span>
</li>
<ul className="clearfix">
<div className="width100">
<span className="with40 fl inline-block lineh-35">
<span className="color-grey-9 fl">任务名称:</span>
<span className="fl lineh-35 ml5">
<Link className="primary-link" to={`/task/taskDetail/${item.id}`}>{item.name}</Link>
</span>
</span>
<span className=" lineh-35">
<span className="color-grey-9 fl">任务编号:</span>
<span className="infos_item">{item.number}</span>
</span>
{item.status === 1 && <div className="fr text-center">
<Button type="primary" onClick={() => { window.open(`${main_web_site_url}/admin/tasks/export_task_files.zip?task_id=${item.id}`) }} className="edu-default-btn edu-blueback-btn">导出任务需求材料</Button>
</div>}
</div>
<div className="clearfix"></div>
<div className="width100">
<div className="width100">
<span className="with40 fl lineh-35">
<span className="color-grey-9 fl">主体名称:</span>
<span className="infos_item">{item.enterpriseName}</span>
</span>
<span className="inline-block lineh-35">
<span className="color-grey-9 fl">信用代码:</span>
<span className="infos_item">{item.enterpriseSimple ? item.enterpriseSimple.creditCode : ''}</span>
</span>
</div>
</div>
<div className="clearfix"></div>
<div className="width100">
<span className="with40 fl lineh-35">
<span className="color-grey-9 fl">联系手机:</span>
<span className="infos_item">{item.contactPhone}{item.belongTo && `(位置分析:${item.belongTo}`}</span>
</span>
<span className="inline-block lineh-35">
<span className="color-grey-9 fl">IP地址:</span>
<span className="infos_item">{item.ip}{item.belongTo && `(位置分析:${item.belongTo}`}</span>
</span>
</div>
<div className="clearfix"></div>
<div className="width100">
<span className="with40 fl lineh-35">
<span className="color-grey-9 fl">所在行业:</span>
<span className="infos_item" style={{ maxWidth: '100px' }}>{categoryArr[item.categoryId]}</span>
</span>
{
item.enterpriseSimple && <span className="with25 fl lineh-35">
<span className="color-grey-9 fl">企业规模:</span>
<span className="infos_item" style={{ maxWidth: '100px' }}>{scale(item.enterpriseSimple.scale)}</span>
</span>}
{/* <span className="inline-block lineh-35">
<span className="color-grey-9 fl">所在职位:</span>
<span className="infos_item">普通员工</span>
</span> */}
</div>
</ul>
</div>
</div>
)
})
}
{list.length > 0 ?
<div className="edu-txt-center mt20 mb20">
{total > pageSize && <Pagination
showQuickJumper
onChange={(page) => { setPage(page) }}
current={curPage}
total={total}
showTotal={total => `${total}`}
/>}
</div> :
<Nodata _html="暂无数据" />}
<Modal
title="拒绝原因"
visible={visible}
onOk={refuse}
onCancel={() => { setVisible(false) }}
className="form-edit-modal"
>
<Radio.Group
className="mb10"
value={isRepairDocument}
onChange={(e) => { setIsRepairDocument(e.target.value) }}>
<Radio value={'0'}>不允许重新发布</Radio>
<Radio value={'1'}>允许修缮后重新发布</Radio>
</Radio.Group>
<TextArea
value={repairAdvice}
placeholder="(必填)我想说点什么呢,200字以内"
autoSize={{ minRows: 6 }}
className="applyText"
onChange={(e) => { setRepairAdvice(e.target.value) }}
maxLength={200}
/>
</Modal>
</React.Fragment>
)
}

View File

@ -0,0 +1,28 @@
.list-box {
position: relative;
display: flex;
justify-content: space-between;
padding: 20px;
margin: 0 1.5rem;
background: #fff;
border-bottom: 1px solid #dedede;
a.edu-orangeline-btn {
padding: 0px 10px;
}
}
a.primary-link {
color: #1890ff;
}
.infos_item {
float: left;
max-width: 273px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #343434;
margin-right: 6px;
line-height: 35px;
margin-left: 5px;
}

View File

@ -0,0 +1,229 @@
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { Modal, Table, Form, Input, Button, Pagination } from 'antd';
import Upload from 'military/components/Upload';
import { readyCheckPapers, proofAdd } from "../../api";
import { httpUrl } from 'military/fetch';
import '../../index.scss';
import './index.scss';
export default Form.create()(props => {
const { changeVisible, taskId, taskModeId, visible, form, showNotification, reloadList } = props;
const { getFieldDecorator, validateFields, setFieldsValue, getFieldsValue } = form;
const pageSize = props.pageSize || 10;
const [searchObj, setSearchObj] = useState({});
const [loading, setLoading] = useState(false);
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [dataList, setDataList] = useState([]);
const [fileList, setFileList] = useState([]);
const [selectedRows, setSelectedRows] = useState([]);
//
useEffect(() => {
setLoading(true);
let params = {
...searchObj,
taskId,
checkStatus: '1',
pageSize,
curPage,
status: '',
}
taskId && readyCheckPapers(params).then(data => {
if (data && Array.isArray(data.rows)) {
for (const item of data.rows) {
item.detail = item.paperDetail && item.paperDetail.content;
}
}
setDataList(data.rows || []);
setLoading(false);
setTotal(data.total);
});
}, [taskId, curPage, searchObj]);
const helper = useCallback(
(name, rules, widget) => (
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true, })(widget)}
</Form.Item>
), []);
function onSearch() {
let values = getFieldsValue(['number', 'userName']);
setSearchObj(values);
setCurPage(1);
}
function clearSearch() {
setFieldsValue({
number: '',
userName: '',
});
setSearchObj({});
setCurPage(1);
}
//
function UploadFunc(fileList, files) {
setFileList(fileList);
setFieldsValue({
files
})
}
const columns = useMemo(() => {
return [{
title: '作者',
dataIndex: 'name',
render: (text, record) => {
return record.user ? record.user.nickname || record.user.login : ''
}
},
{
title: '成果物编号',
dataIndex: 'number',
},
{
title: '更新时间',
dataIndex: 'updatedAt',
},
{
title: '操作',
dataIndex: 'action',
render: (text, record) => {
// return <Link className="line_1 color-grey3" to={`/task/taskDetail/${record.task.id}`}></Link>
return <a className="line_1 color-grey3" onClick={(e) => { goToDeatil(e, record.task.id) }}> 查看详情</a >
}
}];
}, [taskId]);
const rowSelection = useMemo(() => {
return {
type: taskModeId === 1 ? 'radio' : 'checkbox',
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRows(selectedRows);
},
}
}, [taskModeId]);
function goToDeatil(e, taskId) {
e.stopPropagation();
window.location.href = `/task/taskDetail/${taskId}`;
}
const handleRow = record => {
return {
onClick: event => {
let chooseDom = event.currentTarget.getElementsByClassName("ant-checkbox-wrapper")[0] || event.currentTarget.getElementsByClassName("ant-radio-wrapper")[0];
chooseDom && chooseDom.click();
},
};
};
function proofItem() {
if (!selectedRows.length) {
showNotification("请至少选择一条数据!");
return;
}
validateFields((err, values) => {
if (!err) {
let paperNumber = [];
let winnerIds = [];
let winnerName = [];
for (const item of selectedRows) {
paperNumber.push(item.number);
winnerIds.push(item.user.id);
winnerName.push(item.user.nickname || item.user.login);
}
let params = {
paperNumber: paperNumber.join(),
winnerIds: winnerIds.join(),
winnerName: winnerName.join(),
proofFileNumbers: values.files,
status: 2,
taskId,
};
proofAdd(params).then(res => {
if (res && res.message === 'success') {
changeVisible(false);
reloadList();
} else {
showNotification((res && res.message) || "评选提交失败");
}
});
}
});
}
return (
<Modal
title="上传评选佐证材料"
visible={visible}
onOk={proofItem}
onCancel={() => { changeVisible(false) }}
className="proof-modal"
draggable={true}
>
<h3 className="margin10">选出胜出者<span className="color-red">*</span></h3>
<div className="center-right-but">
{helper(
"number",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="请输入成果编号进行检索"
/>
)}
{helper(
"userName",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="请输入作者名称进行检索"
/>
)}
<Button className="mr10" type="primary" onClick={onSearch}>搜索</Button>
<Button className="mr10" type="" onClick={clearSearch}>清除</Button>
</div>
<Table
loading={loading}
rowKey={(row) => row.id}
dataSource={dataList}
columns={columns}
pagination={false}
rowSelection={rowSelection}
onRow={handleRow}
/>
{total > 10 &&
<Pagination
onChange={(page) => { setCurPage(page) }}
current={curPage}
total={total}
/>}
<Form.Item className="upload-form" label="上传附件" required={true} >
<Upload
className="commentStyle"
load={UploadFunc}
size={50}
showNotification={showNotification}
actionUrl={httpUrl}
fileList={fileList}
/>
{getFieldDecorator('files', {
rules: [{ required: visible, message: "请上传文件" }],
validateFirst: true
})(<Input style={{ display: 'none' }} />)}
</Form.Item>
</Modal>
)
}
)

View File

@ -0,0 +1,34 @@
.proof-modal {
min-width: 800px;
width: 80vw;
.ant-form-item-control {
width: 200px;
}
.center-right-but {
.ant-form-item {
margin: 0 1rem 0 0;
}
}
.ant-table-thead > tr > th,
tr > td {
text-align: center;
}
.commentStyle {
.ant-upload {
max-width: 500px;
width: 60vw;
margin-top: 1rem ;
}
.ant-upload-list{
max-width: 500px;
width: 60vw;
}
}
.ant-form-item{
margin-bottom: 0;
}
.upload-form .ant-form-item-label{
padding-top:.5rem;
}
}

View File

@ -0,0 +1,128 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Input, Button } from 'antd';
import ItemDelayManage from '../components/itemDelayManage';
import StatusNav from '../../components/statusNav';
import { delayTaskArr, delayDealArr } from '../static';
import { delayList, } from '../api';
import '../index.scss';
const { Search } = Input;
export default ({ current_user, showNotification, history }) => {
const [operationStatus, setOperationStatus] = useState(0);
const [publishMode, setPublishMode] = useState('');
const [cancelStatus, setCancelStatus] = useState('');
const [loading, setLoading] = useState(false);
const [searchInput, setSearchInput] = useState('');
const [searchInputCurrent, setSearchInputCurrent] = useState('');
const [status, setStatus] = useState('');
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [taskList, setTaskList] = useState([]);
const [reload, setReload] = useState(0);
useEffect(() => {
const params = {
searchInput:searchInputCurrent,
operationStatus,
status,
curPage,
pageSize: 10,
publishMode,
cancelStatus,
};
setLoading(true);
delayList(params).then(data => {
if (data) {
setTaskList(data.rows);
setTotal(data.total);
}
setLoading(false);
})
}, [reload, searchInput, operationStatus, publishMode, cancelStatus, status, curPage,]);
const changeTaskStatus = useCallback((option) => {
setStatus(option.dicItemCode.toString() || '');
setCurPage(1);
setCancelStatus('');
}, []);
const changeDealStatus = useCallback((option) => {
setCancelStatus(option.dicItemCode.toString() || '');
setCurPage(1);
setStatus('');
}, []);
function changeOperationStatus(operationStatus) {
setOperationStatus(operationStatus);
setCurPage(1);
setStatus('');
}
const reloadList = useCallback(() => {
setReload(Math.random());
}, [])
return (
<div className="centerbox task-manage">
<div className="center-screen" >
<div className="center-left-but">
<Button className="circle-button" type={operationStatus === 0 ? 'primary' : ''} onClick={() => { changeOperationStatus(0) }}>待处理</Button>
<Button className="circle-button" type={operationStatus === 1 ? 'primary' : ''} onClick={() => { changeOperationStatus(1) }}>已处理</Button>
</div>
<div className="center-right-but">
<Search
maxLength={20}
style={{ width: "300px" }}
placeholder="请输入任务编号/任务名称关键字"
onChange={(e) => setSearchInputCurrent(e.target.value)}
onSearch={(value) => { setSearchInput(value); setCurPage(1); }}
/>
</div>
</div>
<div className="center-screen flex-column" >
<div className="center-left-but">
<Button className="circle-button" type={publishMode === '' ? 'primary' : ''} onClick={() => { setPublishMode('') }}>全部</Button>
<Button className="circle-button" type={publishMode === 1 ? 'primary' : ''} onClick={() => { setPublishMode(1) }}>统筹任务</Button>
<Button className="circle-button" type={publishMode === 0 ? 'primary' : ''} onClick={() => { setPublishMode(0) }}>自主提交任务</Button>
</div>
{
operationStatus === 0 ? <StatusNav
key={'status'}
type={'status'}
options={delayTaskArr}
changeOptionId={changeTaskStatus}
/> : <StatusNav
key={'dealStatus'}
type={'dealStatus'}
options={delayDealArr}
changeOptionId={changeDealStatus}
/>
}
</div>
<div className="center-content">
<ItemDelayManage
list={taskList}
curPage={curPage}
total={total}
changePage={(page) => { setCurPage(page) }}
loading={loading}
showNotification={showNotification}
reloadList={reloadList}
/>
</div>
</div>
)
}

BIN
src/military/task/image/head.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -0,0 +1,107 @@
// 本功能模块公告样式
.task-manage {
margin-top: 1.25rem;
.status-list {
margin: 0 1.5rem;
}
.center-screen {
display: flex;
justify-content: space-between;
margin: 1.25rem 0;
padding: 1rem 0;
background-color: #fff;
.ant-form-item {
margin: 0 1rem 0 0;
}
.ant-form-item-control-wrapper {
display: inline-block;
}
.status-item{
margin-right: 2rem;
}
}
.center-left-but {
.circle-button {
border-radius: 1rem;
margin-right: 2rem;
}
}
.flex-column{
flex-direction: column;
.status-list{
padding-bottom: 0;
}
}
.ant-select {
min-width: 200px;
}
.ant-table-thead th {
text-align: center;
// &:first-child {
// text-align: left;
// }
}
td {
text-align: center;
color: #333;
&:first-child {
// text-align: left;
font-size: 1rem;
}
}
.text-ellipsis-2 {
text-overflow: -o-ellipsis-lastline;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
}
span.list-yellow {
background: #fa6400;
}
span.list-red {
background: #fe0e36;
}
span.list-orange {
background: #ffb121;
}
span.list-done {
background: #35d77e;
}
span.list-pay {
background: #1ad757;
}
span.list-error {
background: #f56c6c;
}
span.list-gray {
background: #bababa;
}
.ant-pagination {
margin: 2rem auto;
text-align: center;
}
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-all;
}
.upload-form {
display: flex;
.ant-form-item-control-wrapper {
max-width: 80%;
}
}

View File

@ -0,0 +1,191 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Tabs, Input, } from 'antd';
import StatusNav from '../../components/statusNav';
import ItemListMyTask from '../components/itemListMyTask';
import JoinTask from './joinTask';
import { taskStatusAllArr, } from '../static';
import { getTaskCategory, getMyTaskList } from '../api';
import './index.scss';
const Search = Input.Search;
const { TabPane } = Tabs;
const publishStatusArr = taskStatusAllArr.slice(3, 9);
const unpublishStatusArr = taskStatusAllArr.slice(0, 3);
unpublishStatusArr.push({ dicItemCode: '9', name: "待修缮", dicItemName: "待修缮" });
const statusArr = [];
for (const item of taskStatusAllArr) {
statusArr[item.dicItemCode] = item.dicItemName;
}
export default ({ location, history, current_user, showNotification }) => {
console.log(current_user);
let defaultValue = decodeURI(location.search.split("=")[1] || "");
const [identity, setIdentity] = useState('1');
const [taskCategoryValueArr, setTaskCategoryValueArr] = useState([]);
const [statusType, setStatusType] = useState(() => {
return defaultValue === 'false' ? '2' : '1';
});
const [statusString, setStatusString] = useState(() => {
return defaultValue === 'false' ? '0,1,2,9' : '3,4,5,6,7,8';
});
const [searchInput, setSearchInput] = useState('');
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [myTaskList, setMyTaskList] = useState([]);
const [loading, setLoading] = useState(false);
const [reload, setReload] = useState(0);
//
useEffect(() => {
getTaskCategory().then(data => {
if (data) {
const taskCategoryValueArr = [];
for (const item of data) {
taskCategoryValueArr[item.id] = item.name;
}
setTaskCategoryValueArr(taskCategoryValueArr);
}
});
}, []);
useEffect(() => {
const params = {
statusString,
searchInput,
orderBy: 'createdAtDesc',
curPage,
pageSize: 10,
};
setLoading(true);
getMyTaskList(params).then(data => {
if (data) {
setMyTaskList(data.rows);
setTotal(data.total);
}
setLoading(false);
})
}, [statusString, searchInput, curPage, reload]);
function taskClick(id) {
history.push(`/task/taskDetail/${id}`);
}
const changeOptionId = useCallback((option, type) => {
if (type === 'publishStatus') {
setStatusString(option.dicItemCode.toString() || '3,4,5,6,7,8');
} else if (type === 'unpublishStatus') {
setStatusString(option.dicItemCode.toString() || '0,1,2,9');
}
setCurPage(1);
}, [])
function changeIdentity(key) {
setIdentity(key);
if (key === '1') {
setStatusString('3,4,5,6,7,8');
} else {
setStatusString('0,1,2,9');
}
setCurPage(1);
}
function changeStatusType(key) {
if (key === '1') {
setStatusString('3,4,5,6,7,8');
} else {
setStatusString('0,1,2,9');
}
setCurPage(1);
setStatusType(key);
}
const reloadList = useCallback(() => {
setReload(Math.random());
}, []);
return (
<div className="centerbox my-task">
<Tabs defaultActiveKey={identity} onChange={changeIdentity}>
<TabPane tab="我是雇主" key="1">
<Tabs className="childTab"
activeKey={statusType}
onChange={changeStatusType}
tabBarExtraContent={
<Search
maxLength={20}
style={{ width: "300px" }}
placeholder="请输入任务编号/任务名称关键字"
onSearch={(value) => { setSearchInput(value); setCurPage(1) }}
/>
}
>
<TabPane tab="我发布的任务" key="1">
{
identity === '1' && statusType === '1' && <StatusNav
key={'publishStatus'}
type={'publishStatus'}
options={publishStatusArr}
changeOptionId={changeOptionId}
/>
}
<ItemListMyTask
list={myTaskList}
itemClick={taskClick}
curPage={curPage}
total={total}
taskCategoryValueArr={taskCategoryValueArr}
changePage={(page) => { setCurPage(page) }}
loading={loading}
publish={true}
showNotification={showNotification}
reloadList={reloadList}
/>
</TabPane>
<TabPane tab="任务草稿" key="2">
{
identity === '1' && statusType === '2' && <StatusNav
key={'unpublishStatus'}
type={'unpublishStatus'}
options={unpublishStatusArr}
changeOptionId={changeOptionId}
/>
}
<ItemListMyTask
list={myTaskList}
itemClick={taskClick}
curPage={curPage}
total={total}
taskCategoryValueArr={taskCategoryValueArr}
changePage={(page) => { setCurPage(page) }}
loading={loading}
showNotification={showNotification}
reloadList={reloadList}
/>
</TabPane>
</Tabs>
</TabPane>
<TabPane tab="我是创客" key="2">
<JoinTask
taskCategoryValueArr={taskCategoryValueArr}
showNotification={showNotification}
/>
</TabPane>
</Tabs>
</div>
)
}

View File

@ -0,0 +1,62 @@
.my-task {
margin-top: 1.25rem;
.ant-tabs-top-bar {
background: #fff;
text-align: center;
}
.ant-tabs-tab {
height: 4.5rem;
line-height: 3rem;
font-size: 1.15rem;
font-weight: 500;
}
.ant-tabs-extra-content {
line-height: 4.5rem;
}
.childTab {
background: #fff;
.ant-tabs-top-bar {
padding: 0 1.8rem 0 0.8rem;
background: #fff;
text-align: left;
}
.ant-tabs-tab {
height: 4.5rem;
line-height: 3rem;
font-size: 1rem;
font-weight: normal;
}
.ant-tabs-ink-bar {
visibility: hidden;
}
.ant-tabs-tabpane {
padding: 0 1.8rem 1rem;
line-height: 2;
}
}
.ant-table-thead th{
text-align: center;
&:first-child{
text-align: left;
}
}
td{
text-align: center;
color: #333;
&:first-child{
text-align: left;
font-weight: bold;
font-size: 1rem;
}
}
.action-td{
// display: flex;
// flex-flow: column nowrap;
.ant-btn{
margin:.25rem;
}
}
}

View File

@ -0,0 +1,283 @@
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import { Tabs, Input, Table, Pagination, Modal, Button } from 'antd';
import { Link } from "react-router-dom";
import ChooseNav from '../../components/chooseNav';
import ItemListMyTask from '../components/itemListMyTask';
import AgreementModal from '../components/agreementModal';
import ComplainModal from '../components/complainModal';
import { applyStatusAllArr, paperCheckStatusArr, applyStatusArr, publishModeArr, taskStatusAllArr } from '../static';
import { getJoinTaskList, myPapers, confirmReceipt } from '../api';
import './index.scss';
const Search = Input.Search;
const { TabPane } = Tabs;
const publishStatusArr = taskStatusAllArr.slice(3, 9);
const statusArr = [];
for (const item of applyStatusAllArr) {
statusArr[item.dicItemCode] = item.dicItemName;
}
const paperCheckStatus = [];
for (const item of paperCheckStatusArr) {
paperCheckStatus[item.dicItemCode] = item.dicItemName;
}
export default ({ taskCategoryValueArr, showNotification }) => {
const [paperStatusString, setPaperStatusString] = useState('0,1,2,3,4');
const [taskStatusString, setTaskStatusString] = useState('3,4,5,6,7,8');
const [requirePaper, setRequirePaper] = useState('1');
const [searchInput, setSearchInput] = useState('');
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [taskList, setTaskList] = useState([]);
const [loading, setLoading] = useState(false);
const [curPagePaper, setCurPagePaper] = useState(1);
const [totalPaper, setTotalPaper] = useState(0);
const [paperList, setPaperList] = useState([]);
const [checkedItem, setCheckedItem] = useState({});
const [complainVisible, setComplainVisible] = useState(false);
const [agreeVisible, setAgreeVisible] = useState(false);
const [reload, setReload] = useState(0);
useEffect(() => {
if (requirePaper === '1') {
const params = {
paperStatusString,
taskStatusString,
searchInput,
curPage,
pageSize: 10,
};
setLoading(true);
getJoinTaskList(params).then(data => {
if (data) {
setTaskList(data.rows);
setTotal(data.total);
}
setLoading(false);
});
}
}, [requirePaper, paperStatusString, taskStatusString, searchInput, curPage]);
useEffect(() => {
if (requirePaper === '2') {
const params = {
status: paperStatusString,
searchInput,
curPage,
pageSize: 10,
};
setLoading(true);
myPapers(params).then(data => {
if (data) {
setPaperList(data.rows);
setTotalPaper(data.total);
}
setLoading(false);
})
}
}, [requirePaper, paperStatusString, searchInput, curPagePaper, reload]);
const columns = useMemo(() => {
return [
{
title: '任务/编号名称',
dataIndex: 'name',
width: '30%',
render: (text, record) => {
return <Link className="color-grey3 font-16 font-bd" to={`/task/taskDetail/${record.task.id}`}>{record.task.number + " " + record.task.name}</Link>
}
},
{
title: '成果编号',
dataIndex: 'number',
},
{
title: '上传文件',
dataIndex: 'files',
render: (text) => {
return text ? text.split(',').length : 0
}
},
{
title: '提交时间',
dataIndex: 'createdAt',
},
{
title: '应征状态',
dataIndex: 'status',
render: (text, record) => {
return text === 0 ? paperCheckStatus[record.checkStatus] : statusArr[text]
}
},
{
title: '操作',
key: 'action',
className: 'action-td',
render: (text, record) => (
<React.Fragment>
{/* <Link className="line_1 color-grey3" to={`/task/taskDetail/${record.task.id}`}>查看详情</Link> */}
<Button size="small" onClick={() => { window.location.href = `/task/taskDetail/${record.task.id}` }}>查看详情</Button>
{((record.needComplain && record.task.status === 3) || (record.task.status === 5 && record.publicTaskComplain && record.status !== 2)) &&
<Button type="danger" size="small" onClick={() => { setComplainVisible(true); setCheckedItem(record) }}>申诉</Button>
}
{record.status === 2 && record.task.status === 6 && (!record.sign) && (record.canApplicantSign || record.canApplicantSignByPlatform) &&
< Button type="primary" size="small" onClick={() => { setAgreeVisible(true); setCheckedItem(record) }}>签订协议</Button>
}
{
record.status === 2 && (!record.pay) && record.task.status === 7 && record.task.agreementSigning === 1 &&
<Button type="primary" size="small" onClick={() => { confirmReceiptModal(record.id) }}>确认收款</Button>
}
</React.Fragment >
),
},
];
}, [taskCategoryValueArr, publishModeArr, statusArr]);
function confirmReceiptModal(paperId) {
Modal.confirm({
title: '确认收款',
content: '确认已经收到任务报酬',
onOk: () => {
confirmReceipt(paperId).then(res => {
if (res && res.message === "success") {
showNotification("您已确认收款!");
reloadList();
}
})
}
})
}
const changeOptionId = useCallback((option, type) => {
if (type === 'taskStatus') {
setTaskStatusString(option.dicItemCode.toString() || '3,4,5,6,7,8');
} else if (type === 'applyStatus') {
setPaperStatusString(option.dicItemCode.toString() || '0,1,2,3,4');
}
setCurPage(1);
}, [])
function changeRequirePaper(key) {
if (key === '1') {
setPaperStatusString('0,1,2,3,4');
} else {
setTaskStatusString('3,4,5,6,7,8');
setPaperStatusString('0,1,2,3,4');
}
setCurPage(1);
setRequirePaper(key);
}
const reloadList = useCallback(() => {
setReload(Math.random());
}, [])
return (
<Tabs className="childTab"
activeKey={requirePaper}
onChange={changeRequirePaper}
tabBarExtraContent={
<Search
maxLength={20}
style={{ width: "300px" }}
placeholder="请输入任务编号/任务名称关键字"
onSearch={(value) => { setSearchInput(value); setCurPage(1); }}
/>
}
>
<TabPane tab="我参加的任务" key="1">
{requirePaper === '1' &&
<React.Fragment>
<ChooseNav
key={'taskStatus'}
type={'taskStatus'}
title={'任务状态:'}
options={publishStatusArr}
changeOptionId={changeOptionId}
/>
<ChooseNav
key={'applyStatus'}
type={'applyStatus'}
title={'应征状态:'}
options={applyStatusArr}
changeOptionId={changeOptionId}
/>
</React.Fragment>
}
<ItemListMyTask
list={taskList}
curPage={curPage}
total={total}
taskCategoryValueArr={taskCategoryValueArr}
changePage={(page) => { setCurPage(page) }}
loading={loading}
joinTask={true}
/>
</TabPane>
<TabPane tab="我的成果" key="2">
{
requirePaper === '2' && <ChooseNav
key={'applyStatus'}
type={'applyStatus'}
title={'应征状态:'}
options={applyStatusArr}
changeOptionId={changeOptionId}
/>
}
<Table
loading={loading}
rowKey={(row) => row.id}
dataSource={paperList}
columns={columns}
pagination={false}
className="mt10"
/>
{taskList.length > 10 &&
<Pagination
onChange={(page) => { setCurPagePaper(page) }}
current={curPage}
total={totalPaper}
/>}
<ComplainModal
visible={complainVisible}
setVisible={setComplainVisible}
checkedItem={checkedItem}
detailStatus={checkedItem.task && checkedItem.task.status}
showNotification={showNotification}
reloadList={reloadList}
/>
{agreeVisible && <AgreementModal
checkedItem={checkedItem}
visible={agreeVisible}
setVisible={setAgreeVisible}
showNotification={showNotification}
reloadList={reloadList}
/>}
</TabPane>
</Tabs>
)
}

View File

@ -0,0 +1,263 @@
import React, { useCallback, useMemo, useEffect, useState } from 'react';
import { Input, Button, Form, Table, Pagination, Modal } from 'antd';
import { Link } from "react-router-dom";
import StatusNav from '../../components/statusNav';
import { complainPaperList, checkComplain } from '../api';
import { paperComplainStatusArr } from '../static';
import { httpUrl } from 'military/fetch';
import '../index.scss';
const TextArea = Input.TextArea;
const paperComplainStatus = [];
for (const item of paperComplainStatusArr) {
paperComplainStatus[item.dicItemCode] = item.dicItemName;
}
const paperComplain = paperComplainStatusArr.slice(0, 2);
export default Form.create()(({ form, showNotification, history}) => {
const { getFieldDecorator, validateFields, setFieldsValue, } = form;
const [approve, setApprove] = useState(1);
const [loading, setLoading] = useState(false);
// const [searchObj, setSearchObj] = useState({});
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [taskList, setTaskList] = useState([]);
const [reload, setReload] = useState(0);
const [visible, setVisible] = useState(false);
const [activeId, setActiveId] = useState('');
const [status, setStatus] = useState('2');
useEffect(() => {
const params = {
// ...searchObj,
status,
curPage,
pageSize: 10,
};
setLoading(true);
complainPaperList(params).then(data => {
if (data) {
setTaskList(data.rows);
setTotal(data.total);
}
setLoading(false);
})
}, [reload, status, curPage]);
const helper = useCallback(
(name, rules, widget) => (
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true, })(widget)}
</Form.Item>
), []);
// function onSearch() {
// validateFields((err, values) => {
// if (!err) {
// setSearchObj({ numberInput: values.numberInput });
// }
// });
// }
// function clearSearch() {
// setFieldsValue({
// numberInput: '',
// });
// setSearchObj({});
// }
function changeApprove(approve) {
setApprove(approve);
setCurPage(1);
if (approve === 1) {
setStatus('2');
} else {
setStatus('0,1');
}
}
function downFile(item) {
let url = httpUrl + '/busiAttachments/download/' + item.id;
window.open(url);
}
const columns = useMemo(() => {
return [
{
title: '序号',
dataIndex: 'index',
render: (text, record, index) => {
return index + 1
}
},
{
title: '申诉内容',
dataIndex: 'content',
width: "30%",
},
{
title: '申诉文件',
dataIndex: 'materials',
width: "20%",
render: (text, record) => {
return <div>
{
Array.isArray(record.materials) && record.materials.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>
})
}
</div>
}
},
{
title: '提交者',
dataIndex: 'user',
render: (text, record) => {
return record.user.nickname || record.user.login
}
},
{
title: '提交时间',
dataIndex: 'createdAt',
},
{
title: '审核状态',
dataIndex: 'status',
render: (text, record) => {
return text !== 2 ? paperComplainStatus[text] : <React.Fragment>
<Button className="mr5 font-12" type="primary" size="small" onClick={() => { checkPaperItem(record.id, 1) }}>通过</Button>
<Button className="mr5 font-12" type="info" size="small" onClick={() => { setActiveId(record.id); setVisible(true) }}>不通过</Button>
</React.Fragment>
}
},
{
title: '操作',
key: 'action',
render: (text, record) => (
<Link className="line_1 color-grey3" to={`/task/taskDetail/${record.taskId}`}>查看详情</Link>
),
},
]
}, []);
function checkPaperItem(paperId, pass, message) {
setLoading(true);
checkComplain({
paperId,
auditingVo: {
pass,
message,
}
}).then(res => {
if (res && res.message === 'success') {
setReload(Math.random());
setVisible(false);
}
setLoading(false);
});
}
function refuse() {
validateFields((err, values) => {
if (!err) {
checkPaperItem(activeId, 0, values.message);
}
});
}
const changeOptionId = useCallback((option) => {
setStatus(option.dicItemCode.toString() || '0,1');
setCurPage(1);
}, []);
return (
<div className="centerbox task-manage">
<div className="center-screen" >
<div className="center-left-but">
<Button className="circle-button" type={approve === 1 ? 'primary' : ''} onClick={() => { changeApprove(1) }}>待审批</Button>
<Button className="circle-button" type={approve === 2 ? 'primary' : ''} onClick={() => { changeApprove(2) }}>已审批</Button>
</div>
{/* <div className="center-right-but">
{helper(
"numberInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入任务编号进行检索"
/>
)}
<Button className="mr10" type="primary" onClick={onSearch}>搜索</Button>
<Button className="mr10" type="" onClick={clearSearch}>清除</Button>
</div> */}
</div>
<div className="center-content">
{
approve === 2 && <StatusNav
key={'status'}
type={'status'}
options={paperComplain}
changeOptionId={changeOptionId}
/>
}
<Table
loading={loading}
rowKey={(row) => row.id}
dataSource={taskList}
columns={columns}
pagination={false}
className="mt10"
/>
{total > 10 &&
<Pagination
onChange={(page) => { setCurPage(page) }}
current={curPage}
total={total}
/>}
</div>
<Modal
title="不通过的原因"
visible={visible}
onOk={refuse}
onCancel={() => { setVisible(false) }}
className="form-edit-modal"
>
{helper(
"message",
[{ required: visible, message: '请给出不通过的原因' }],
<TextArea
placeholder="(必填)我想给点什么意见呢200字以内"
autoSize={{ minRows: 6 }}
className="applyText"
maxLength={200}
/>
)}
</Modal>
</div>
)
}
)

View File

@ -0,0 +1,331 @@
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import { Input, Radio, Select, Button, Form, DatePicker, Table, Pagination, Modal } from 'antd';
import { Link } from "react-router-dom";
import { paperCheckStatusArr } from '../static';
import { readyCheckPapers, checkPaper, } from '../api';
import '../index.scss';
const format = "YYYY-MM-DD HH:mm:ss";
const Option = Select.Option;
const TextArea = Input.TextArea;
const checkStatusArr = [];
for (const item of paperCheckStatusArr) {
checkStatusArr[item.dicItemCode] = item.dicItemName;
}
export default Form.create()(({ current_user, form, showNotification, match, history }) => {
const { getFieldDecorator, validateFields, setFieldsValue, getFieldsValue } = form;
const [loading, setLoading] = useState(false);
const [searchObj, setSearchObj] = useState({
checkStatus: '0',
parentId: 0,
});
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [taskList, setTaskList] = useState([]);
const [reload, setReload] = useState(0);
const [visible, setVisible] = useState(false);
const [activeId, setActiveId] = useState('');
useEffect(() => {
const params = {
curPage,
pageSize: 10,
...searchObj,
};
setLoading(true);
readyCheckPapers(params).then(data => {
if (data) {
setTaskList(data.rows);
setTotal(data.total);
}
setLoading(false);
})
}, [reload, curPage, searchObj]);
const helper = useCallback(
(label, name, rules, widget, initialValue) => (
<Form.Item label={label}>
{getFieldDecorator(name, { rules, initialValue, validateFirst: true, })(widget)}
</Form.Item>
), []);
function onSearch() {
let values = getFieldsValue(['checkStatus', 'endCreatedAt', 'startCreatedAt', 'parentId']);
if (values.startCreatedAt) values.startCreatedAt = values.startCreatedAt.format(format);
if (values.endCreatedAt) values.endCreatedAt = values.endCreatedAt.format(format);
if (values.checkStatus === '0,1,2') values.checkStatus = '';
setSearchObj(values);
}
function clearSearch() {
setFieldsValue({
startCreatedAt: '',
endCreatedAt: '',
checkStatus: '0,1,2'
});
setSearchObj({});
}
function checkPaperItem(paperId, passStatus) {
validateFields((err, values) => {
if (!err) {
setLoading(true);
checkPaper({
paperId,
auditingVo: {
pass: passStatus ? 1 : values.pass,
message: values.message,
}
}).then(res => {
setLoading(false);
if (res && res.message === 'success') {
setReload(Math.random());
setVisible(false);
setFieldsValue({
pass: '',
message: '',
})
}
});
}
});
}
const columns = useMemo(() => {
return [
{
title: '序号',
dataIndex: 'index',
render: (text, record, index) => {
return <div style={{ textAlign: 'center' }}>{index + 1}</div>
}
},
{
title: '来源任务',
dataIndex: 'name',
width: "20%",
render: (text, record) => (
<span>
<Link className="line_1 color-grey3" to={`/task/taskDetail/${record.taskId}`}>{record.task && record.task.name}</Link>
</span >
),
},
{
title: '评论/成果内容',
dataIndex: 'content',
width: "30%",
render: (text, record) => {
return record.paperDetail ? <div dangerouslySetInnerHTML={{ __html: record.paperDetail.content }}></div> : ''
}
},
{
title: '提交者',
dataIndex: 'user',
render: (text, record) => {
return record.user.nickname || record.user.login
}
},
{
title: '提交时间',
dataIndex: 'createdAt',
},
{
title: '审核状态',
dataIndex: 'checkStatus',
render: (text, record) => {
return text === 0 ? <React.Fragment>
<Button className="mr5 font-12" type="primary" size="small" onClick={() => { checkPaperItem(record.id, true) }}>通过</Button>
<Button className="mr5 font-12" type="info" size="small" onClick={() => { disagree(record) }}>不通过</Button>
</React.Fragment> : checkStatusArr[text]
}
},
{
title: '操作',
key: 'action',
render: (text, record) => (
<React.Fragment>
<Link className="line_1 color-grey3" to={`/task/taskDetail/${record.taskId}`}>查看</Link>
</React.Fragment>
),
},
]
}, []);
//
function disagree(item) {
if (!item.parentId) {
setActiveId(item.id); setVisible(true);
} else {
Modal.confirm({
title: '此条评论不通过?',
onOk() {
setLoading(true);
checkPaper({
paperId: item.id,
auditingVo: {
pass: 0,
message: '不通过',
}
}).then(res => {
setLoading(false);
if (res && res.message === 'success') {
setReload(Math.random());
}
});
}
});
}
}
function changeStatus(e) {
setSearchObj({
...searchObj,
checkStatus: e,
});
setCurPage(1);
}
function changeType(e) {
setSearchObj({
...searchObj,
parentId: e,
});
setCurPage(1);
}
return (
<div className="centerbox task-manage">
<div className="center-screen" >
<div className="center-left-but">
{helper(
"",
"checkStatus",
[],
<Select
showArrow
placeholder="请选择审核状态"
onChange={changeStatus}
>
<Option key={'0,1,2'}>{'所有状态'}</Option>
{
paperCheckStatusArr.map(item => {
return <Option key={item.dicItemCode}>{item.dicItemName}</Option>
})
}
</Select>,
'0'
)}
{helper(
"",
"parentId",
[],
<Select
showArrow
placeholder="请选择需要审核的类型"
onChange={changeType}
>
<Option key={0} value={0}>成果</Option>
<Option key={1} value={1}>评论</Option>
</Select>,
0
)}
</div>
<div className="center-right-but">
{helper(
"发布时间:",
"startCreatedAt",
[],
<DatePicker
showTime
format={format}
placeholder="请选择开始时间"
/>
)}
{helper(
"",
"endCreatedAt",
[],
<DatePicker
showTime
format={format}
placeholder="请选择结束时间"
/>
)}
<Button className="mr10" type="primary" onClick={onSearch}>搜索</Button>
<Button className="mr10" type="" onClick={clearSearch}>清除</Button>
</div>
</div>
<div className="center-content">
<Table
loading={loading}
rowKey={(row) => row.id}
dataSource={taskList}
columns={columns}
pagination={false}
className="mt10"
/>
{total > 10 &&
<Pagination
onChange={(page) => { setCurPage(page) }}
current={curPage}
total={total}
/>}
</div>
<Modal
title="不通过的原因"
visible={visible}
onOk={() => { checkPaperItem(activeId, false) }}
onCancel={() => { setVisible(false); setFieldsValue({ pass: 2, message: '' }); }}
className="form-edit-modal"
>
{helper(
"",
"pass",
[{ required: visible, message: '请选择是否允许申诉' }],
<Radio.Group
className="mb10"
>
<Radio value={2}>允许申诉</Radio>
<Radio value={0}>不允许申诉</Radio>
</Radio.Group>,
2
)
}
{helper(
"",
"message",
[{ required: visible, message: '请输入拒绝的原因' }],
<TextArea
placeholder="(必填)我想说点什么呢,200字以内"
autoSize={{ minRows: 6 }}
className="applyText"
maxLength={200}
/>
)
}
</Modal>
</div>
)
}
)

View File

@ -0,0 +1,138 @@
import React, { useCallback, forwardRef, useEffect, useState } from 'react';
import { Input, Button, Form } from 'antd';
import ItemPayProof from '../components/itemPayProof';
// import StatusNav from '../../components/statusNav';
import { agreementArr } from '../static';
import { uploadPayProofList } from '../api';
import '../index.scss';
agreementArr.splice(1,1);
export default Form.create()(({ current_user, form, showNotification, match, history }) => {
const { getFieldDecorator, validateFields, setFieldsValue, } = form;
const [approve, setApprove] = useState(1);
const [loading, setLoading] = useState(false);
const [searchObj, setSearchObj] = useState({});
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [taskList, setTaskList] = useState([]);
const [reload, setReload] = useState(0);
useEffect(() => {
const params = {
...searchObj,
status:approve,
type:'',
currentPage:curPage,
pageSize: 10,
};
setLoading(true);
uploadPayProofList(params).then(data => {
if (data) {
setTaskList(data.rows);
setTotal(data.total);
}
setLoading(false);
})
}, [reload, approve, curPage, searchObj]);
const helper = useCallback(
(name, rules, widget) => (
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true, })(widget)}
</Form.Item>
), []);
function onSearch() {
validateFields((err, values) => {
if (!err) {
setSearchObj(values);
}
});
}
function changeApprove(approve) {
setApprove(approve);
setCurPage(1);
}
function clearSearch() {
setFieldsValue({
numberInput: '',
nameInput: '',
enterpriseNameInput: ''
});
setSearchObj({});
}
const reloadList = useCallback(() => {
setReload(Math.random());
}, [])
return (
<div className="centerbox task-manage">
<div className="center-screen" >
<div className="center-left-but">
<Button className="circle-button" type={approve === 1 ? 'primary' : ''} onClick={() => { changeApprove(1) }}>待上传</Button>
<Button className="circle-button" type={approve === 2 ? 'primary' : ''} onClick={() => { changeApprove(2) }}>已上传</Button>
</div>
<div className="center-right-but">
{helper(
"numberInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入任务编号进行检索"
/>
)}
{helper(
"nameInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入任务名称进行检索"
/>
)}
{helper(
"enterpriseNameInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入发布主体名称进行检索"
/>
)}
<Button className="mr10" type="primary" onClick={onSearch}>搜索</Button>
<Button className="mr10" type="" onClick={clearSearch}>清除</Button>
</div>
</div>
<div className="center-content">
<ItemPayProof
list={taskList}
curPage={curPage}
total={total}
changePage={(page) => { setCurPage(page) }}
loading={loading}
showNotification={showNotification}
reloadList={reloadList}
/>
</div>
</div>
)
}
)

View File

@ -0,0 +1,153 @@
import React, { useCallback, forwardRef, useEffect, useState } from 'react';
import { Input, Button, Form } from 'antd';
import ItemProofManage from '../components/itemProofManage';
import StatusNav from '../../components/statusNav';
import { proofArr } from '../static';
import { proofList, } from '../api';
import '../index.scss';
const proofArrCheck = proofArr.slice(0, 2);
export default Form.create()(({ current_user, form, showNotification, match, history }) => {
const { getFieldDecorator, validateFields, setFieldsValue } = form;
const [approve, setApprove] = useState(1);
const [loading, setLoading] = useState(false);
const [searchObj, setSearchObj] = useState({});
const [proofStatusString, setProofStatusString] = useState('2');
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [taskList, setTaskList] = useState([]);
const [reload, setReload] = useState(0);
useEffect(() => {
const params = {
...searchObj,
proofStatusString,
orderBy: 'createdAtDesc',
curPage,
pageSize: 10,
};
setLoading(true);
proofList(params).then(data => {
if (data) {
setTaskList(data.rows);
setTotal(data.total);
}
setLoading(false);
})
}, [reload, proofStatusString, curPage, searchObj]);
const helper = useCallback(
(name, rules, widget) => (
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true, })(widget)}
</Form.Item>
), []);
function onSearch() {
validateFields((err, values) => {
if (!err) {
setSearchObj(values);
}
});
}
const changeOptionId = useCallback((option) => {
setProofStatusString(option.dicItemCode.toString() || '0,1');
setCurPage(1);
}, []);
function changeApprove(approve) {
setApprove(approve);
setCurPage(1);
if (approve === 1) {
setProofStatusString('2');
} else {
setProofStatusString('0,1');
}
}
function clearSearch() {
setFieldsValue({
numberInput: '',
nameInput: '',
enterpriseNameInput: ''
});
setSearchObj({});
}
const reloadList = useCallback(() => {
setReload(Math.random());
}, [])
return (
<div className="centerbox task-manage">
<div className="center-screen" >
<div className="center-left-but">
<Button className="circle-button" type={approve === 1 ? 'primary' : ''} onClick={() => { changeApprove(1) }}>待审批</Button>
<Button className="circle-button" type={approve === 2 ? 'primary' : ''} onClick={() => { changeApprove(2) }}>已审批</Button>
</div>
<div className="center-right-but">
{helper(
"numberInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入任务编号进行检索"
/>
)}
{helper(
"nameInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入任务名称进行检索"
/>
)}
{helper(
"enterpriseNameInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入发布主体名称进行检索"
/>
)}
<Button className="mr10" type="primary" onClick={onSearch}>搜索</Button>
<Button className="mr10" type="" onClick={clearSearch}>清除</Button>
</div>
</div>
<div className="center-content">
{
approve === 2 && <StatusNav
key={'approveStatus'}
type={'approveStatus'}
options={proofArrCheck}
changeOptionId={changeOptionId}
/>
}
<ItemProofManage
list={taskList}
curPage={curPage}
total={total}
changePage={(page) => { setCurPage(page) }}
loading={loading}
showNotification={showNotification}
reloadList={reloadList}
/>
</div>
</div>
)
}
)

View File

@ -0,0 +1,130 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Input, Button, Form } from 'antd';
import ItemPublicityComplain from '../components/itemPublicityComplain';
import StatusNav from '../../components/statusNav';
import { publicityArr } from '../static';
import { publicityComplainList, } from '../api';
import '../index.scss';
export default Form.create()(({ current_user, form, showNotification, match, history }) => {
const { getFieldDecorator, validateFields, setFieldsValue } = form;
const [approve, setApprove] = useState(1);
const [loading, setLoading] = useState(false);
const [searchObj, setSearchObj] = useState({});
const [status, setStatus] = useState('2');
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [taskList, setTaskList] = useState([]);
const [reload, setReload] = useState(0);
useEffect(() => {
const params = {
...searchObj,
status,
orderBy: 'createdAtDesc',
curPage,
pageSize: 10,
};
setLoading(true);
publicityComplainList(params).then(data => {
if (data) {
setTaskList(data.rows);
setTotal(data.total);
}
setLoading(false);
})
}, [reload, status, curPage, searchObj]);
const helper = useCallback(
(name, rules, widget) => (
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true, })(widget)}
</Form.Item>
), []);
function onSearch() {
validateFields((err, values) => {
if (!err) {
setSearchObj(values);
}
});
}
const changeOptionId = useCallback((option) => {
setStatus(option.dicItemCode.toString() || '0,1');
setCurPage(1);
}, []);
function changeApprove(approve) {
setApprove(approve);
setCurPage(1);
if (approve === 1) {
setStatus('2');
} else {
setStatus('0,1');
}
}
function clearSearch() {
setFieldsValue({
numberInput: '',
});
setSearchObj({});
}
const reloadList = useCallback(() => {
setReload(Math.random());
}, [])
return (
<div className="centerbox task-manage">
<div className="center-screen" >
<div className="center-left-but">
<Button className="circle-button" type={approve === 1 ? 'primary' : ''} onClick={() => { changeApprove(1) }}>待审批</Button>
<Button className="circle-button" type={approve === 2 ? 'primary' : ''} onClick={() => { changeApprove(2) }}>已审批</Button>
</div>
<div className="center-right-but">
{helper(
"numberInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入成果编号进行检索"
/>
)}
<Button className="mr10" type="primary" onClick={onSearch}>搜索</Button>
<Button className="mr10" type="" onClick={clearSearch}>清除</Button>
</div>
</div>
<div className="center-content">
{
approve === 2 && <StatusNav
key={'status'}
type={'status'}
options={publicityArr}
changeOptionId={changeOptionId}
/>
}
<ItemPublicityComplain
list={taskList}
curPage={curPage}
total={total}
changePage={(page) => { setCurPage(page) }}
loading={loading}
showNotification={showNotification}
reloadList={reloadList}
/>
</div>
</div>
)
}
)

170
src/military/task/static.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,339 @@
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import classNames from 'classnames';
import { Input, Select, Button, Form, DatePicker, Table, Pagination, } from 'antd';
import { Link } from "react-router-dom";
import { paperCheckStatusArr, publishModeArr, taskStatusAllArr, showUserModeArr ,main_web_site_url} from '../static';
import { getTaskAdminList, changeShowUserMode } from '../api';
import '../index.scss';
import './index.scss';
const format = "YYYY-MM-DD HH:mm:ss";
const Option = Select.Option;
const statusArr = [];
for (const item of taskStatusAllArr) {
statusArr[item.dicItemCode] = item.dicItemName;
}
const checkStatusArr = [];
for (const item of paperCheckStatusArr) {
checkStatusArr[item.dicItemCode] = item.dicItemName;
}
export default Form.create()(({ form, showNotification, match, history }) => {
const { getFieldDecorator, setFieldsValue, getFieldsValue } = form;
const [loading, setLoading] = useState(false);
const [statusString, setStatusString] = useState('');
const [publishMode, setPublishMode] = useState('');
const [showUserMode, setShowUserMode] = useState('');
const [sort, setSort] = useState('Desc');
const [order, setOrder] = useState('createdAt');
const [searchObj, setSearchObj] = useState({});
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [taskList, setTaskList] = useState([]);
useEffect(() => {
const params = {
...searchObj,
statusString,
publishMode: publishMode.length > 1 ? '' : publishMode,
showUserMode: showUserMode.length > 1 ? '' : showUserMode,
curPage,
pageSize: 10,
orderBy: order + sort,
};
setLoading(true);
getTaskAdminList(params).then(data => {
if (data) {
setTaskList(data.rows);
setTotal(data.total);
}
setLoading(false);
})
}, [statusString, order, sort, publishMode, showUserMode, curPage, searchObj]);
const helper = useCallback(
(label, name, rules, widget, initialValue) => (
<Form.Item label={label}>
{getFieldDecorator(name, { rules, initialValue, validateFirst: true, })(widget)}
</Form.Item>
), []);
function onSearch() {
let values = getFieldsValue(['nameInput', 'endTime', 'startTime', 'enterpriseNameInput']);
if (values.startTime) values.startTime = values.startTime.format(format);
if (values.endTime) values.endTime = values.endTime.format(format);
if (values.checkStatus === '0,1,2') values.checkStatus = '';
setSearchObj(values);
}
function clearSearch() {
setFieldsValue({
startTime: '',
endTime: '',
nameInput: '',
enterpriseNameInput: ''
});
setSearchObj({});
}
const columns = useMemo(() => {
return [
{
title: '序号',
dataIndex: 'index',
render: (text, record, index) => {
return <div style={{ textAlign: 'center' }}>{index + 1}</div>
}
},
{
title: '任务编号',
dataIndex: 'number',
},
{
title: '任务名称',
dataIndex: 'name',
width: "15%",
render: (text, record) => (
<Link className="line_1 color-grey3" to={`/task/taskDetail/${record.id}`}>{text}</Link>
),
},
{
title: <Select
className="column-select"
showArrow
defaultValue={'0,1'}
onChange={setPublishMode}
>
<Option key={'0,1'} >发布方式</Option>
<Option key={'0'} >自主提交</Option>
<Option key={'1'} >统筹任务</Option>
</Select>,
dataIndex: 'publishMode',
render: (text, record) => {
return publishModeArr[text]
}
},
{
title: <Select
className="column-select"
showArrow
defaultValue={'0,1,2,3,4,5,6,7,8,9'}
onChange={setStatusString}
>
<Option key={'0,1,2,3,4,5,6,7,8,9'} >任务状态</Option>
{
taskStatusAllArr.map(item => {
return <Option key={item.dicItemCode} >{item.dicItemName}</Option>
})
}
</Select>,
width: '10%',
dataIndex: 'status',
render: (text, record) => {
return statusArr[text]
}
},
{
title: '稿件数',
dataIndex: 'papersCount',
},
{
title: '发布主体',
dataIndex: 'enterpriseName',
},
{
title: '发布时间',
dataIndex: 'publishedAt',
render: (text, record) => {
return text || '--'
}
},
{
title: '截稿时间',
dataIndex: 'collectingEndTime',
render: (text, record) => {
return text || '--'
}
},
{
// title: '',
title: <Select
className="column-select"
showArrow
placeholder="请选择审核状态"
defaultValue={'0,1,2'}
onChange={setShowUserMode}
>
<Option key={'0,1,2'}>应征者名单公示</Option>
{
showUserModeArr.map(item => {
return <Option key={item.dicItemCode}>{item.dicItemName}</Option>
})
}
</Select>,
dataIndex: 'showUserMode',
render: (text, record) => {
return <Select
className="column-select"
showArrow
placeholder="请选择审核状态"
defaultValue={text}
onChange={(key) => { changeStatus(key, record.id) }}
>
{
showUserModeArr.map(item => {
return <Option key={item.dicItemCode} value={item.dicItemCode}>{item.dicItemName}</Option>
})
}
</Select>
}
},
// {
// title: '',
// key: 'action',
// render: (text, record) => (
// <React.Fragment>
// {/* <Button className="mr5 font-12" type="danger" size="small" onClick={() => { deletItem(record.id) }}></Button> */}
// <Link className="line_1 color-grey3" to={`/task/taskDetail/${record.taskId}`}></Link>
// </React.Fragment>
// ),
// },
]
}, []);
function changeStatus(showUserMode, taskId) {
changeShowUserMode({
taskId,
showUserMode
}).then(res => {
if (res && res.message === 'success') {
showNotification('修改成功!');
}
})
}
//
const changeSortName = useCallback((sortType) => {
setOrder(sortType);
setCurPage(1);
}, []);
//
const changeSort = useCallback((sort) => {
setSort(sort);
setCurPage(1);
}, []);
function downloadFile() {
window.open(main_web_site_url + '/admin/tasks.xlsx');
}
return (
<div className="centerbox task-manage-all ">
<div className="search-screen" >
{helper(
"任务名称",
"nameInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入任务名称进行检索"
/>
)}
{helper(
"发布主体名称",
"enterpriseNameInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入发布主体名称进行检索"
/>
)}
<div className="center-right-but">
{helper(
"发布时间:",
"startTime",
[],
<DatePicker
showTime
format={format}
placeholder="请选择开始时间"
/>
)}
{helper(
"",
"endTime",
[],
<DatePicker
showTime
format={format}
placeholder="请选择结束时间"
/>
)}
</div>
<div className="button-div">
{/* <a href="/admin/tasks.xlsx" class="fr edu-default-btn edu-blueback-btn plr30">导出</a> */}
<Button className="mr10" type="primary" onClick={onSearch}>搜索</Button>
<Button className="mr10" onClick={clearSearch}>清除</Button>
<Button className="mr10" type="primary" onClick={downloadFile}>导出</Button>
</div>
</div>
<Form.Item className="inline-form" label="排序">
<Select
style={{ width: '200px' }}
showArrow
onChange={changeSortName}
defaultValue='createdAt'
>
<Option key={'createdAt'} >创建时间</Option>
<Option key={'publishedAt'} >发布时间</Option>
</Select>
<span className={classNames({ "sort-active": sort === 'Desc', 'sort-icon': true, 'ml10': true })} onClick={() => { changeSort('Desc') }}>
<i className="fa fa-long-arrow-down font-16 "></i>
</span>
<span className={classNames({ "sort-active": sort === 'Asc', 'sort-icon': true })} onClick={() => { changeSort('Asc') }}>
<i className="fa fa-long-arrow-up font-16 "></i>
</span>
</Form.Item>
<div className="center-content">
<Table
loading={loading}
rowKey={(row) => row.id}
dataSource={taskList}
columns={columns}
pagination={false}
className="mt10"
/>
{total > 10 &&
<Pagination
onChange={(page) => { setCurPage(page) }}
current={curPage}
total={total}
/>}
</div>
</div>
)
}
)

View File

@ -0,0 +1,66 @@
.search-screen {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
margin: 1.25rem 0;
padding: 1.25rem;
background-color: #fff;
border-bottom: 1px solid #dedede;
> .ant-row {
min-width: 41%;
margin-right: 2rem;
.ant-form-item-label {
float: left;
}
.ant-form-item-control-wrapper {
overflow: hidden;
}
}
.center-right-but {
.ant-form-item {
margin: 0 1rem 0 0;
}
.ant-form-item-control-wrapper {
display: inline-block;
}
}
.button-div {
margin-right: 1.5rem;
}
}
.task-manage-all {
.column-select {
min-width: 100px;
.ant-select-selection {
background: #fafafa;
border: 0;
}
}
.ant-table-thead > tr > th,
.ant-table-tbody > tr > td {
padding: 1rem 0.2rem;
text-align: center;
}
}
.inline-form {
margin:-.35rem 0 .5rem 0;
text-align: right;
.ant-form-item-control-wrapper {
display: inline-block;
}
}
.sort-icon {
color: #ccc;
i {
padding: 0.1rem 0.25rem;
}
}
.sort-active {
color: #29bd8b;
}

View File

@ -0,0 +1,136 @@
import React from "react";
import { Route, Switch } from "react-router-dom";
import Loadable from "react-loadable";
import Loading from "../../Loading";
import AdminRouter from "./components/adminRouter";
const TaskManage = Loadable({
loader: () => import("./taskManage"),
loading: Loading,
});
const PaperManage = Loadable({
loader: () => import("./paperManage"),
loading: Loading,
});
const PaperComplain = Loadable({
loader: () => import("./paperComplain"),
loading: Loading,
});
const ProofManage = Loadable({
loader: () => import("./proofManage"),
loading: Loading,
});
const PublicityComplain = Loadable({
loader: () => import("./publicityComplain"),
loading: Loading,
});
const AgreementManage = Loadable({
loader: () => import("./agreementManage"),
loading: Loading,
});
const PayProof = Loadable({
loader: () => import("./payProof"),
loading: Loading,
});
const TaskAdmin = Loadable({
loader: () => import("./taskAdmin"),
loading: Loading,
});
const DelayManage = Loadable({
loader: () => import("./delayManage"),
loading: Loading,
});
const AdminPage = (propsF) => {
return (
<React.Fragment>
<AdminRouter {...propsF} />
<Switch {...propsF}>
{/* 任务管理审核 */}
<Route
path="/task/taskManage"
render={(props) => (
<TaskManage {...propsF} {...props} />
)}
></Route>
{/* 成果管理审核 */}
<Route
path="/task/paperManage"
render={(props) => (
<PaperManage {...propsF} {...props} />
)}
></Route>
{/* 成果申诉管理审核 */}
<Route
path="/task/paperComplain"
render={(props) => (
<PaperComplain {...propsF} {...props} />
)}
></Route>
{/* 公示期成果申诉管理审核 */}
<Route
path="/task/publicityComplain"
render={(props) => (
<PublicityComplain {...propsF} {...props} />
)}
></Route>
{/* 佐证管理审核 */}
<Route
path="/task/proofManage"
render={(props) => (
<ProofManage {...propsF} {...props} />
)}
></Route>
{/* 管理员协议审核 */}
<Route
path="/task/agreementManage"
render={(props) => (
<AgreementManage {...propsF} {...props} />
)}
></Route>
{/* 管理员上传支付凭证 */}
<Route
path="/task/payProof"
render={(props) => (
<PayProof {...propsF} {...props} />
)}
></Route>
{/* 管理员任务列表 */}
<Route
path="/task/taskAdmin"
render={(props) => (
<TaskAdmin {...propsF} {...props} />
)}
></Route>
{/* 延期管理 */}
<Route
path="/task/delayManage"
render={(props) => (
<DelayManage {...propsF} {...props} />
)}
></Route>
</Switch>
</React.Fragment>
);
}
// }
export default AdminPage;

View File

@ -0,0 +1,501 @@
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 } from '../api';
import { taskModeIdArr, applyStatusArr, applyStatusAllArr, agreementContent, paperCheckTextArr } from '../static';
import { httpUrl } from '../../fetch';
import './index.scss';
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(({ match, current_user, form, history, showNotification }, ref) => {
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);
//
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 => {
setDetailData(data || {});
})
}, [id, reload]);
//
useEffect(() => {
current_user.user_id && id && checkAgreement(id).then(res => {
if (res && res.data && res.data.status === 1) {
setSignAgreement(true);
}
})
}, [current_user.user_id]);
//
useEffect(() => {
current_user.user_id && id && checkHavePaper(id).then(res => {
if (res && res.data && res.data.status === 1) {
setIsPaper(true);
}
})
}, [current_user.user_id]);
const taskLimit = useMemo(() => {
if (current_user.admin) {
return true;
}
if (detailData.user) {
return current_user.login === detailData.user.login
}
}, [detailData, current_user])
//
useEffect(() => {
applyModal && getAgreement().then(res => {
if (res && res.data) {
setApplyContent({
title: res.data.title,
content: res.data.content,
});
}
});
}, [applyModal]);
//
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]);
//
const process = useCallback((title, status, days) => {
let surplusTimetext = '';
let surplus;
if (detailData.status === status) {
switch (status) {
case 3:
surplus = detailData.collectingDays * 24 * 3600 - (new Date() - new Date(detailData.publishedAt || detailData.createdAt)) / 1000;
break;
case 4:
surplus = detailData.choosingDays * 24 * 3600 - (new Date() - new Date(detailData.collectingCompleteAt)) / 1000;
break;
case 5:
surplus = detailData.makePublicDays * 24 * 3600 - (new Date() - new Date(detailData.makePublicAt)) / 1000;
break;
case 6:
surplus = detailData.signingDays * 24 * 3600 - (new Date() - new Date(detailData.publicityCompleteAt)) / 1000;
break;
case 7:
surplus = detailData.payingDays * 24 * 3600 - (new Date() - new Date(detailData.signingCompleteAt)) / 1000;
break;
default:
surplus = 0;
}
surplusTimetext = formatDuring(surplus);
}
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">{surplus < 0 ? ('延期' + surplusTimetext) : ('剩余' + surplusTimetext)}</p> : ''}
</li>
)
}, [detailData]);
function downFile(item) {
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
}
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('操作失败');
}
})
}
})
}
}
const reloadList = useCallback(() => {
setRelaodChildList(Math.random());
});
const reloadDetail = useCallback(() => {
setReload(Math.random());
}, []);
const signContent = useCallback(() => {
if (signAgreement && isPaper) {
let checkStatustext = paperCheckTextArr[0];
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}
</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">
<Button className="mr20" type={"primary"} onClick={() => { setApplyModal(true) }}>我要应征投稿</Button>
</div>
}
}, [signAgreement, isPaper, current_user, detailData, dataList]);
function goUser(login) {
window.location.href = `/users/${login}`;
}
function goUserProfiles() {
window.open(`/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');
}
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">
<ul className="clearfix mb20">
<li className="font-18 mr20 font-bd task-hide lineh-20 fl" style={{ maxWidth: "800px" }}>{detailData.name}</li>
<span className="task_tag">{taskCategoryValueArr[detailData.categoryId]}</span>
</ul>
<div className="clearfix">
<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>
<ul className="fr edu-txt-right">
<li className="color-orange font-bd lineh-30"><span className="font-18"></span><span className="font-28">{detailData.bounty}</span></li>
</ul>
</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>
</div>
{!current_user.enterpriseCertification && <div className="edu-back-white padding30 mt20 font-16 text-center mb50">
<a onClick={goUserProfiles} className="color-blue_4C">请先完善主体信息</a>
</div>}
{current_user.enterpriseCertification && 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"></i>
</Tooltip>}
{detailData.status === 4 && dataList.length && (!detailData.isProofBoolean) && 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) && taskLimit && <a className="fr color-orange ml20" onClick={showUser}>应征者名单公示 &gt;&gt;</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}
/>
</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>
)
})
)

View File

@ -0,0 +1,164 @@
.centerbox {
position: relative;
margin-top: 40px;
.head-navigation {
span {
display: inline-flex;
align-items: center;
&:hover {
color: #409eff;
}
}
}
}
.head-navigation {
position: absolute;
top: -2.3em;
}
.task_tag {
padding: 0px 12px;
background: #f1f8ff;
border-radius: 4px;
float: left;
color: #459be6;
height: 1.25rem;
line-height: 1.25rem;
}
.task_tip {
padding: 0px 20px;
border-radius: 15px;
line-height: 30px;
background: #FFF8F5;
}
.tasks_status{
display: flex;
justify-content: space-around;
padding: 1rem;
}
.tasks_status li {
position: relative;
background: #ebebeb;
width: 12.4%;
height: 35px;
line-height: 35px;
color: #05101a;
text-align: center;
margin-bottom: 32px;
}
.tasks_status li:not(:last-child){
margin-right: 20px;
}
.tasks_status li.active {
background: #4cacff;
color: #fff;
}
.tasks_status li:not(:first-child):before {
width: 0;
height: 0;
border-width: 17px;
content: "";
position: absolute;
left: 0px;
border-style: solid;
top: 0px;
border-color: transparent transparent #fafafa transparent;
transform: rotate(90deg);
}
.tasks_status li:not(:last-child):after {
width: 0;
height: 0;
border-width: 17px;
content: "";
position: absolute;
right: -34px;
border-style: solid;
top: 0px;
border-color: transparent transparent #ebebeb transparent;
transform: rotate(90deg);
}
.tasks_status li.active:after {
border-color: transparent transparent #4cacff transparent;
transform: rotate(90deg);
}
.file-list-box {
padding: 0 1.25rem;
}
.content-text {
padding: 1.25rem;
}
.content-download {
color: #409eff;
}
.applyText {
margin-top: 0.5rem;
border-radius: 3px;
font-family: "微软雅黑", "宋体";
font-size: 14px;
line-height: 1.9;
border: 1px solid #eaeaea;
background: #ffffff;
color: #05101a;
resize: none;
}
.applyList {
table {
width: 100%;
border: 1px solid #eee;
border-bottom: none;
background: #fff;
color: #888;
cursor: default;
}
.ant-table-body tbody tr {
height: 90px;
}
.ant-table-thead
> tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
> td,
.ant-table-tbody
> tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
> td,
.ant-table-thead
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
> td,
.ant-table-tbody
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
> td {
background: #fff;
}
}
.task-detail{
.ant-btn-primary{
background-color:#409eff;
border-color: #409eff;
}
}
.agreement-content{
box-sizing: border-box;
padding: 0px 20px;
height: 50vh;
overflow-y: scroll;
}
li.except-close{
background: #fa6400;
color: #fff;
}

View File

@ -0,0 +1,543 @@
import React, { forwardRef, useCallback, useEffect, useState } from 'react';
import { Form, Radio, Input, InputNumber, Icon, Button, Modal } from 'antd';
import classNames from 'classnames';
import moment from 'moment';
import ReactWEditor from 'wangeditor-for-react';
import { Link } from "react-router-dom";
import Upload from '../../components/Upload';
import { httpUrl } from '../../fetch';
import { getTaskDetail, addTask, updateTask, getTaskCategory, getCompanyInfo } from '../api';
import { formItemLayout, formModalLayout } from '../static';
import { editorConfig } from '../../components/config';
import './index.scss';
const { info } = Modal;
function getSomeDayAfter(nDay) {
return moment(new Date().setDate(new Date().getDate() + nDay)).format('YYYY-MM-DD HH:mm');
}
export default Form.create()(forwardRef(({ current_user, form, showNotification, match, history }, ref) => {
console.log(current_user);
const [taskCategoryArr, setTaskCategoryArr] = useState([]);
const [fileList, setFileList] = useState(null);
const [publishMode, setPublishMode] = useState('0');
const [categoryId, setCategoryId] = useState('7');
const [descriptionHtml, setDescriptionHtml] = useState('');
const [enterpriseName, setEnterpriseName] = useState('');
const [displayTime, setDisplayTime] = useState(() => {
return {
collectingTime: getSomeDayAfter(30),
choosingTime: getSomeDayAfter(30 + 15),
makePublicTime: getSomeDayAfter(30 + 15 + 7),
signingTime: getSomeDayAfter(30 + 15 + 7 + 15),
payingTime: getSomeDayAfter(30 + 15 + 7 + 15 + 15)
}
});
const [visible, setVisible] = useState(false);
const [num, setNum] = useState(0) //
const [isSend, setIsSend] = useState(false) //
const id = match.params.taskId;
const { getFieldDecorator, validateFields, setFieldsValue, getFieldsValue } = form;
// id
useEffect(() => {
if (id) {
getTaskDetail(id).then(data => {
let formValue = {
name: data.name,
contactName: data.contactName,
contactPhone: data.contactPhone,
uploadFileNumbers: data.uploadFileNumbers,
bounty: data.bounty,
taskModeId: data.taskModeId,
collectionMode: data.collectionMode,
publishMode: data.publishMode + '',
description: data.description,
collectingDays: data.collectingDays,
choosingDays: data.choosingDays,
makePublicDays: data.makePublicDays,
signingDays: data.signingDays,
payingDays: data.payingDays,
};
if (data.tasksAttachments && data.tasksAttachments.length) {
for (const item of data.tasksAttachments) {
item.name = item.fileName;
item.size = item.fileSize;
item.uid = "rc-upload" + item.id
}
setFileList(data.tasksAttachments);
}
setFieldsValue(formValue);
setEnterpriseName(data.enterpriseName);
setCategoryId(data.categoryId);
setDescriptionHtml(data.description);
setPublishMode(data.publishMode + '');
let collectingTime = getSomeDayAfter(data.collectingDays);
let choosingTime = getSomeDayAfter(data.collectingDays + data.choosingDays);
let makePublicTime = getSomeDayAfter(data.collectingDays + data.choosingDays + data.makePublicDays);
let signingTime = getSomeDayAfter(data.collectingDays + data.choosingDays + data.makePublicDays + data.signingDays);
let payingTime = getSomeDayAfter(data.collectingDays + data.choosingDays + data.makePublicDays + data.signingDays + data.payingDays);
setDisplayTime({ collectingTime, choosingTime, makePublicTime, signingTime, payingTime });
});
} else {
let formValue = {
contactName: current_user.username || current_user.login,
contactPhone: current_user.contactPhone,
taskModeId: 1,
collectionMode: 1,
publishMode: '0',
collectingDays: 30,
choosingDays: 15,
makePublicDays: 7,
signingDays: 15,
payingDays: 15,
};
setFieldsValue(formValue);
setCategoryId('7');
}
}, [id]);
useEffect(() => {
getCompanyInfo().then(res => {
if (res && res.message === 'success') {
setEnterpriseName(res.data.enterpriseName);
setFieldsValue({
contactPhone:res.data.phone,
contactName:res.data.userName,
})
}else{
showNotification(res.message);
}
});
},[]);
useEffect(() => {
getTaskCategory().then(data => {
if (data) {
for (const item of data) {
item.dicItemCode = item.id;
item.dicItemName = item.name;
}
setTaskCategoryArr(data);
}
});
}, []);
/** 倒计时显示*/
useEffect(() => {
let timer = 0;
if (isSend && num !== 0) {
timer = setInterval(() => {
setNum(n => {
if (n === 1) {
setIsSend(false)
clearInterval(timer)
}
return n - 1;
});
}, 1000);
}
return () => {
//
clearInterval(timer)
};
}, [isSend]);
function getCode() {
setIsSend(true);
setNum(60);
}
//
function UploadFunc(fileList) {
setFileList(fileList);
let files = [];
for (const item of fileList) {
if (item) {
let itemId = (item.response && item.response.data && item.response.data.id) || item.id;
itemId && files.push(itemId);
}
}
setFieldsValue({
uploadFileNumbers: files.join()
});
}
const helper = useCallback(
(label, name, rules, widget, initialValue, rightComponent) => (
<Form.Item label={label}>
{getFieldDecorator(name, { rules, initialValue, validateFirst: true, })(widget)}
{rightComponent}
</Form.Item>
), []);
const helperNoLabel = useCallback(
(title, name, rules, widget, displayTime) => (
<div className="timing_task" key={name}>
<div className="mbt10 color-grey-9 lineh-35"><span className="inline-span">{title}</span></div>
<Form.Item className="no-label">
{getFieldDecorator(name, { rules, validateFirst: true, })(widget)}
</Form.Item>
<span className="days-word color-grey-9 "></span>
{publishMode == '0' && <p className="display-time">{displayTime}</p>}
</div>
), [publishMode]);
//
const publishDeal = useCallback((status, publishMode, res) => {
if (res && res.data) {
showNotification("任务保存成功!");
if (!status) {
history.push("/task/myTask?published=false")
} else if (publishMode == '1') {
info({
title: '提示',
content: <div>
<h4>发布申请已提交请等待管理员的审核</h4>
<p>我们将在1-2个工作日内完成审核</p>
</div>,
onOk() {
history.push("/task/myTask?published=false");
},
});
} else {
history.push("/task/myTask");
}
} else {
showNotification(res.message);
}
}, []);
//
function saveItem(status) {
validateFields((error, values) => {
if (!error) {
let params = {
...values,
status,
enterpriseName: enterpriseName,
categoryId,
};
let dateSum = params.collectingDays + params.choosingDays + params.makePublicDays + params.signingDays + params.payingDays;
if (dateSum > 180) {
showNotification("任务天数总和不得超过180天");
return;
}
if (id) {
//
params.id = id;
updateTask(params).then(res => {
publishDeal(status, publishMode, res);
});
} else {
//
addTask(params).then(res => {
publishDeal(status, publishMode, res);
});
}
}
})
}
function changeDate(v, field) {
if (typeof v !== 'number' || v < 0) {
return;
}
let values = getFieldsValue();
values[field] = v;
let collectingTime = getSomeDayAfter(values.collectingDays);
let choosingTime = getSomeDayAfter(values.collectingDays + values.choosingDays);
let makePublicTime = getSomeDayAfter(values.collectingDays + values.choosingDays + values.makePublicDays);
let signingTime = getSomeDayAfter(values.collectingDays + values.choosingDays + values.makePublicDays + values.signingDays);
let payingTime = getSomeDayAfter(values.collectingDays + values.choosingDays + values.makePublicDays + values.signingDays + values.payingDays);
setDisplayTime({ collectingTime, choosingTime, makePublicTime, signingTime, payingTime });
}
return (
<div className="centerbox">
<div className="head-navigation">
<Link to="/task">创客空间 &gt;</Link>
<Link to="/task">任务大厅 &gt;</Link>
<span >发布任务 </span>
</div>
<p className="font-18 font-bd mb15">任务提交</p>
<div className="edu-back-white mb30">
<div className="padding30 bor-bottom-greyE">
<p className="partTitle">联系方式<span className="color-red font-14">*必填</span></p>
<Form {...formItemLayout}>
<Form.Item label='主体信息:'>
{enterpriseName}
</Form.Item>
{helper(
"联系人:",
"contactName",
[{ required: true, message: "请输入联系人" }],
<Input
className="contact-input"
placeholder="请输入联系人"
/>
)}
{helper(
"联系电话:",
"contactPhone",
[{ required: true, message: "请输入联系电话" }],
<Input
className="contact-input"
placeholder="请输入联系电话"
disabled
/>, '', <Icon className="editPhone" type="edit" onClick={() => { setVisible(true) }} />
)}
</Form>
</div>
<div className="padding30 bor-bottom-greyE">
<p className="partTitle">任务内容<span className="color-red font-14">*必填</span>
<span>
<a href="http://117.50.100.12:8000/attachments/download/523/%E5%88%9B%E5%AE%A2%E4%BB%BB%E5%8A%A1%E5%88%97%E8%A1%A8_2019-07-26_20-53.xlsx" className="icon icon-attachment font-13 color-blue" length="32" target="_blank">创客任务列表_2019-07-26_20-53.xlsx</a>
</span><span className="color-grey-9 ml5 font-12 ">点击下载示例模版</span>
</p>
<div className="pl15 task-edit-content">
<p className="color-grey3 mb20">选择任务所在领域</p>
<div className="mb20 clearfix areaDiv" >
{
taskCategoryArr.map(item => {
return <button
className={classNames({ "choose-button": true, "active": item.dicItemCode == categoryId })}
key={item.dicItemCode}
onClick={() => { setCategoryId(item.dicItemCode) }}
>{item.dicItemName}</button>
})
}
</div>
{helper(
"",
"name",
[{ required: true, message: "请用一句话概括您要做什么比如开源项目网站开发最大限制60个字符" },
{ max: 60, message: '长度不能超过60个字符' }],
<Input
placeholder="请用一句话概括您要做什么比如开源项目网站开发最大限制60个字符"
/>
)}
<Form.Item >
{(!id || (id && descriptionHtml)) && <ReactWEditor
defaultValue={descriptionHtml}
config={{
...editorConfig,
placeholder: "把您的任务内容补充详细一些吧,越清晰具体,任务完成质量越高哟~",
}}
onChange={(html) => { setFieldsValue({ description: html }) }}
/>}
{/* 用一个隐藏的input实现上必填校验 */}
{getFieldDecorator('description', {
rules: [{ required: true, message: "把您的任务内容补充详细一些吧,越清晰具体,任务完成质量越高哟~" }],
validateFirst: true
})(<Input style={{ display: 'none' }} />)}
</Form.Item>
<p className="color-grey3 mb10 mt20">
<span className="color-orange mr2 ">*</span>
任务发布之后将会公开展示在交易中心不要把与项目客户相关等隐私信息以及QQ号微信号电话号码等联系方式填写在任务中
</p>
<Form.Item >
<Upload
className="commentStyle"
load={UploadFunc}
size={50}
showNotification={showNotification}
actionUrl={httpUrl}
fileList={fileList}
/>
{getFieldDecorator('uploadFileNumbers', {
validateFirst: true
})(<Input style={{ display: 'none' }} />)}
</Form.Item>
</div>
</div>
<div className="padding30 ">
<p className="partTitle">选择任务周期价格和发布模式<span className="color-red font-14">*必填</span></p>
<Form className="gray-form" {...formItemLayout}>
{helper(
"赏金:",
"bounty",
[{ required: true, message: "您打算支付多少赏金呢" }],
<InputNumber
min={1}
max={99999999}
className="number-input"
placeholder="您打算支付多少赏金呢"
formatter={value => `${value}¥`}
/>
)}
{helper(
"悬赏模式:",
"taskModeId",
[{ required: true, message: "请选择悬赏模式" }],
<Radio.Group>
<Radio value={1}>单人悬赏只设置一个中标者</Radio>
<Radio value={2}>多人悬赏设置多分中标分享赏金</Radio>
<Radio value={3}>计件悬赏合格一稿支付一稿稿件数量2</Radio>
</Radio.Group>
)}
{helper(
"征集方式:",
"collectionMode",
[{ required: true, message: "请选择征集方式" }],
<Radio.Group>
<Radio value={1}>创意征集应征者以开放讨论的形式参与</Radio>
<Radio value={0}>物化成果征集应征者以各自提交成果物的形式参与</Radio>
</Radio.Group>
)}
{helper(
"发布方式:",
"publishMode",
[{ required: true, message: "请选择发布方式" }],
<Radio.Group onChange={(e) => { setPublishMode(e.target.value) }}>
<Radio value={'0'}>自主提交方式由发布方自行支付赏金一键自助发布</Radio>
<Radio value={'1'}>统筹任务由平台支付赏金需经过平台遴选方能发布</Radio>
</Radio.Group>
)}
<div className="task-setting-days">
<div className="timing_task">
<div className="mbt10 color-grey-9 lineh-35"><span className="inline-span active">发布任务</span></div>
<div className="color-grey-9 ">自主提交立即发布</div>
<div className="color-grey-9 ">统筹任务遴选后发布</div>
</div>
{helperNoLabel(
"成果提交",
"collectingDays",
[{ required: true, message: "请输入天数" }],
<InputNumber
className="date-input"
onChange={(v) => { changeDate(v, 'collectingDays') }}
min={1}
max={180}
/>,
displayTime.collectingTime
)}
{/* <div className="color-grey-9 format-time-days-1 format-time-day-show">2021-06-16 07:10</div> */}
{/* <div className="days-error"><span className="color-red none">成果提交时间不能为空</span></div> */}
{helperNoLabel(
"成果评选",
"choosingDays",
[{ required: true, message: "请输入天数" }],
<InputNumber
className="date-input"
onChange={(v) => { changeDate(v, 'choosingDays') }}
min={1}
max={180}
/>,
displayTime.choosingTime
)}
{helperNoLabel(
"结果公示",
"makePublicDays",
[{ required: true, message: "请输入天数" }],
<InputNumber
className="date-input"
onChange={(v) => { changeDate(v, 'makePublicDays') }}
min={1}
max={180}
/>,
displayTime.makePublicTime
)}
{helperNoLabel(
"任务协议签订",
"signingDays",
[{ required: true, message: "请输入天数" }],
<InputNumber
className="date-input"
onChange={(v) => { changeDate(v, 'signingDays') }}
min={1}
max={180}
/>,
displayTime.signingTime
)}
{helperNoLabel(
"支付",
"payingDays",
[{ required: true, message: "请输入天数" }],
<InputNumber
className="date-input"
onChange={(v) => { changeDate(v, 'payingDays') }}
min={1}
max={180}
/>,
displayTime.payingTime
)}
<div className="timing_task">
<div className="mbt10 color-grey-9 lineh-35"><span className="inline-span">任务完成</span></div>
<div className="color-grey-9 ">支付确认后任务完成</div>
</div>
</div>
<p className="color-grey3 mb10 ml40 mt20">
<span className="color-orange mr2 ">*</span>
任务天数总和不得超过180天
</p>
</Form>
</div>
</div>
<Button className="mr20" type={"primary"} onClick={() => { saveItem(1) }}>发布</Button>
<Button className="mr20" type={"primary"} onClick={() => { saveItem(0) }}>保存</Button>
<Button onClick={() => { history.go(-1) }}>取消</Button>
<Modal
title="修改联系电话"
visible={visible}
// onOk={checkItem}
onCancel={() => { setVisible(false) }}
className="form-edit-modal"
>
<Form {...formModalLayout}>
<Form.Item label={"新手机号码:"} >
<Input
className="tel-input"
placeholder="请输入11位手机号"
/>
</Form.Item>
<Form.Item label={"验证码:"} >
<Input
className="code-input"
placeholder="请输入验证码"
/>
<Button className="ml10" type="primary" disabled={num !== 0} onClick={getCode}>{num || '获取验证码'}</Button>
</Form.Item>
</Form>
</Modal>
</div>
)
})
)

View File

@ -0,0 +1,154 @@
.achieve-form {
padding: 24px 40px 0px 40px;
}
.partTitle {
color: #05101a;
font-size: 16px;
position: relative;
height: 20px;
line-height: 20px;
margin-bottom: 20px !important;
}
.partTitle:before {
position: absolute;
left: -10px;
top: 3px;
width: 2px;
height: 16px;
background: #459be6;
content: "";
}
.color-red {
color: red;
}
.areaDiv {
overflow-y: hidden;
}
.choose-button {
float: left;
border: 1px solid #eee;
border-radius: 0px;
height: 28px;
line-height: 28px;
padding: 0px 12px;
color: #656565;
margin: 0px 10px 10px;
cursor: pointer;
}
.choose-button.active {
color: #4cacff !important;
border: 1px solid #4cacff;
}
.gray-form {
.ant-form-item-required {
color: #999;
}
}
.contact-input {
max-width: 300px;
}
.editPhone {
margin-left: 1rem;
padding: 0.25em;
font-size: 1rem;
color: #fff;
background: #1484ef;
border-radius: 50%;
cursor: pointer;
}
.number-input {
width: 200px;
.ant-input-number-input {
padding-right: 1em;
text-align: right;
}
}
.task-setting-days {
display: flex;
flex-flow: row wrap;
text-align: center;
}
.timing_task {
flex: 1px;
position: relative;
line-height: 2;
&:before {
position: absolute;
content: "";
width: 100%;
height: 2px;
background: #efefef;
top: 0px;
left: 0px;
}
}
.inline-span:before {
position: absolute;
content: "";
width: 10px;
height: 10px;
border-radius: 50%;
top: -4px;
left: 50%;
margin-left: -6px;
background: #bfbfbf;
z-index: 1;
}
.inline-span.active {
font-size: 16px;
color: #333;
&:before {
background: #4cacff;
}
}
.date-input {
margin-right: 0.4rem;
}
.tel-input{
width: 290px;
}
.code-input{
width: 180px;
}
.no-label {
display: inline-block;
margin-bottom: 0;
.ant-form-explain {
position: absolute;
white-space: nowrap;
bottom: -40px;
}
}
.display-time{
position: relative;
top:-6px;
}
.days-word {
line-height: 40px;
}
.task-edit-content{
.ant-form-item-control-wrapper {
display: block;
}
}

View File

@ -0,0 +1,245 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Input, Button, Modal } from 'antd';
import moment from 'moment';
import { formatDuring } from 'educoder';
import ChooseNav from '../../components/chooseNav';
import SortBox from '../../components/sortBox';
import ItemListTask from '../components/itemListTask';
import { taskTimeArr, taskStatusArr, sortArr, taskModeIdArr } from '../static';
import { getTaskList, getTaskCategory, getCompanyInfo } from '../api';
import '../index.scss';
const Search = Input.Search;
export default ({ history, current_user, showLoginDialog }) => {
console.log(current_user);
const [loading, setLoading] = useState(false);
const [taskCategoryArr, setTaskCategoryArr] = useState([]);
const [categoryId, setCategoryId] = useState('');
const [taskModeId, setTaskModeId] = useState('');
const [expiredStartTime, setExpiredStartTime] = useState('');
const [expiredEndTime, setExpiredEndTime] = useState('');
const [statusString, setStatusString] = useState('3,4,5,6,7,8');
const [searchInput, setSearchInput] = useState('');
const [orderBy, setOrderBy] = useState('');
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [taskList, setTaskList] = useState([]);
//
useEffect(() => {
getTaskCategory().then(data => {
if (data) {
for (const item of data) {
item.dicItemCode = item.id;
item.dicItemName = item.name;
}
setTaskCategoryArr(data);
}
});
}, []);
//
useEffect(() => {
const params = {
categoryId,
taskModeId,
expiredStartTime,
expiredEndTime,
statusString,
searchInput,
orderBy,
curPage,
pageSize: 10,
};
setLoading(true);
getTaskList(params).then(data => {
if (data) {
for (const item of data.rows) {
item.delayTime = surplusTime(item);
}
setTaskList(data.rows);
setTotal(data.total);
}
setLoading(false);
})
}, [categoryId, taskModeId, expiredStartTime, expiredEndTime, statusString, searchInput, orderBy, curPage]);
//
const changeOptionId = useCallback((option, type) => {
if (type === 'taskCategory') {
setCategoryId(option.dicItemCode);
} else if (type === 'taskModeId') {
setTaskModeId(option.dicItemCode);
} else if (type === 'taskTime') {
if (option.dicItemCode) {
let nowTime = moment(new Date()).format('YYYY-MM-DD HH:mm:ss');
let nextTime = moment(new Date().setDate(new Date().getDate() + option.dicItemCode)).format('YYYY-MM-DD HH:mm:ss');
setExpiredStartTime(nowTime);
setExpiredEndTime(nextTime);
} else {
setExpiredStartTime('');
setExpiredEndTime('');
}
} else if (type === 'taskStatus') {
setStatusString(option.dicItemCode || '3,4,5,6,7,8');
}
}, []);
function surplusTime(item) {
let surplus;
switch (item.status) {
case 3:
surplus = item.collectingDays * 24 * 3600 - (new Date() - new Date(item.publishedAt || item.createdAt)) / 1000;
break;
case 4:
surplus = item.choosingDays * 24 * 3600 - (new Date() - new Date(item.collectingCompleteAt)) / 1000;
break;
case 5:
surplus = item.makePublicDays * 24 * 3600 - (new Date() - new Date(item.makePublicAt)) / 1000;
break;
case 6:
surplus = item.signingDays * 24 * 3600 - (new Date() - new Date(item.publicityCompleteAt)) / 1000;
break;
case 7:
surplus = item.payingDays * 24 * 3600 - (new Date() - new Date(item.signingCompleteAt)) / 1000;
break;
default:
surplus = 0;
}
let surplusTimetext = formatDuring(surplus);
return surplus > 0 ? '剩余' + surplusTimetext : '延期' + surplusTimetext;
}
//
const changeSort = useCallback((sortType) => {
let sortValue = '';
if (sortType.type !== 'default') {
if (sortType.desc) {
sortValue = sortType.type + 'Desc';
} else {
sortValue = sortType.type + 'Asc';
}
}
setOrderBy(sortValue);
setCurPage(1);
}, []);
function taskClick(id) {
if (!current_user.user_id) {
showLoginDialog();
return;
}
history.push(`/task/taskDetail/${id}`);
}
//
function goAdd() {
if (!current_user.user_id) {
showLoginDialog();
return;
}
getCompanyInfo().then(res => {
if (res) {
if (res.message === 'success') {
history.push("/task/taskAdd");
} else if (res.message === '主体信息未认证') {
Modal.info({
title: '因为以下原因,您暂时不能进行本操作',
content: <div className="mt10">
<p>完成条件后重新点击查看您的最新参与资格</p>
<div className="inline mt10">
<span className="mr30"> <i className="iconfont icon-mingpian"></i> <span className="color-red">主体信息未认证</span> </span>
</div>
</div>,
onOk: () => {
window.location.href = `/users/${current_user.login}/profiles`;
},
okText: '立即完善',
});
} else {
Modal.info({
content: '您没有权限发布任务'
});
}
}
});
}
return (
<div className="centerbox" style={{ marginTop: '20px' }}>
<div className="nav-content">
<ChooseNav
key={'taskCategory'}
type={'taskCategory'}
title={'任务领域:'}
options={taskCategoryArr}
changeOptionId={changeOptionId}
size='big'
/>
<ChooseNav
key={'taskModeId'}
type={'taskModeId'}
title={'任务模式:'}
options={taskModeIdArr}
changeOptionId={changeOptionId}
size='big'
/>
<ChooseNav
key={'taskTime'}
type={'taskTime'}
title={'任务时限:'}
options={taskTimeArr}
changeOptionId={changeOptionId}
size='big'
/>
<ChooseNav
key={'taskStatus'}
type={'taskStatus'}
title={'任务状态:'}
options={taskStatusArr}
changeOptionId={changeOptionId}
size='big'
/>
</div>
<div className="center-content">
<div className="centerScreen" >
<SortBox
options={sortArr}
changeOptionId={changeSort}
/>
<div className="center-right-but">
<Search
maxLength={20}
style={{ width: "300px" }}
placeholder="请输入任务编号/任务名称关键字"
onSearch={(value) => { setSearchInput(value); setCurPage(1); }}
/>
<Button className="mr20 font-12" type="primary" onClick={goAdd}><i className="iconfont icon-zaibianji font-12 mr3"></i>发布任务</Button>
</div>
</div>
<ItemListTask
list={taskList}
itemClick={taskClick}
curPage={curPage}
total={total}
changePage={(page) => { setCurPage(page) }}
loading={loading}
/>
</div>
</div>
)
}

View File

@ -0,0 +1,166 @@
import React, { useCallback, forwardRef, useEffect, useState } from 'react';
import { Input, Button, Form } from 'antd';
import ItemTaskManage from '../components/itemTaskManage';
import StatusNav from '../../components/statusNav';
import { approveArr, main_web_site_url } from '../static';
import { getTaskAdminList, getTaskCategory } from '../api';
import '../index.scss';
export default Form.create()(({ current_user, form, showNotification, match, history }) => {
const { getFieldDecorator, validateFields, setFieldsValue, getFieldsValue } = form;
const [categoryArr, setCategoryArr] = useState([]);
const [approve, setApprove] = useState(1);
const [loading, setLoading] = useState(false);
const [searchObj, setSearchObj] = useState({});
const [statusString, setStatusString] = useState('1');
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [taskList, setTaskList] = useState([]);
const [reload, setReload] = useState(0);
useEffect(() => {
getTaskCategory().then(data => {
if (data) {
let categoryArr = [];
for (const item of data) {
categoryArr[item.id] = item.name;
}
setCategoryArr(categoryArr);
}
});
}, []);
useEffect(() => {
const params = {
...searchObj,
statusString,
orderBy: 'createdAtDesc',
curPage,
pageSize: 10,
publishMode: 1
};
setLoading(true);
getTaskAdminList(params).then(data => {
if (data) {
setTaskList(data.rows);
setTotal(data.total);
}
setLoading(false);
})
}, [reload, statusString, curPage, searchObj]);
const helper = useCallback(
(name, rules, widget) => (
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true, })(widget)}
</Form.Item>
), []);
function onSearch() {
validateFields((err, values) => {
if (!err) {
setSearchObj(values);
}
});
}
const changeOptionId = useCallback((option) => {
setStatusString(option.dicItemCode.toString() || '2,3,4,5,6,7,8');
setCurPage(1);
}, []);
function changeApprove(approve) {
setApprove(approve);
setCurPage(1);
if (approve === 1) {
setStatusString('1');
} else {
setStatusString('2,3,4,5,6,7,8');
}
}
function clearSearch() {
setFieldsValue({
numberInput: '',
nameInput: '',
enterpriseNameInput: ''
});
setSearchObj({});
}
const reloadList = useCallback(() => {
setReload(Math.random());
}, [])
return (
<div className="centerbox task-manage">
<div className="center-screen" >
<div className="center-left-but">
<Button className="circle-button" type={approve === 1 ? 'primary' : ''} onClick={() => { changeApprove(1) }}>待审批</Button>
<Button className="circle-button" type={approve === 2 ? 'primary' : ''} onClick={() => { changeApprove(2) }}>已审批</Button>
</div>
<div className="center-right-but">
{helper(
"numberInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入任务编号进行检索"
/>
)}
{helper(
"nameInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入任务名称进行检索"
/>
)}
{helper(
"enterpriseNameInput",
[{ max: 20, message: '长度不能超过20个字符' }],
<Input
placeholder="输入发布主体名称进行检索"
/>
)}
<Button className="mr10" type="primary" onClick={onSearch}>搜索</Button>
<Button className="mr10" type="" onClick={clearSearch}>清除</Button>
<Button className="mr10" type="primary" onClick={() => { window.open(`${main_web_site_url}/admin/tasks/activity_managed_tasks.zip?review_status=reviewing&amp;status_value=0`) }}>导出</Button>
</div>
</div>
<div className="center-content">
{
approve === 2 && <StatusNav
key={'approveStatus'}
type={'approveStatus'}
options={approveArr}
changeOptionId={changeOptionId}
/>
}
<ItemTaskManage
list={taskList}
curPage={curPage}
total={total}
changePage={(page) => { setCurPage(page) }}
loading={loading}
categoryArr={categoryArr}
showNotification={showNotification}
reloadList={reloadList}
/>
</div>
</div>
)
}
)

View File

@ -842,6 +842,9 @@ class NewHeader extends Component {
<li>
<Link to={`/users/${this.props.current_user.login}`}>个人中心</Link>
</li>
<li>
<Link to={`/task/myTask`}>创客管理</Link>
</li>
{
mygetHelmetapi2 && mygetHelmetapi2.new_course && mygetHelmetapi2.new_course.my_courses &&
<li><a href={`${mygetHelmetapi2.new_course.my_courses}`} target="_blank">我的课程</a></li>