diff --git a/README.md b/README.md index 212797d30..34bfe773b 100755 --- a/README.md +++ b/README.md @@ -16,4 +16,7 @@ $ npm run start ``` install过程中报错,可先安装node-sass npm install node-sass --save-dev -yarn install --force \ No newline at end of file +yarn install --force + + + diff --git a/src/components/AsyncButton/index.tsx b/src/components/AsyncButton/index.tsx new file mode 100644 index 000000000..1b247f6ec --- /dev/null +++ b/src/components/AsyncButton/index.tsx @@ -0,0 +1,20 @@ +import { Button } from 'antd' +import React, { useState } from 'react' + +type AsyncButtonProps = React.ComponentProps & { + onClick: (e?: React.MouseEvent) => Promise +} + +export const AsyncButton = ({ children, ...props }: AsyncButtonProps) => { + const [btnLoading, setBtnLoading] = useState(false) + return +} \ No newline at end of file diff --git a/src/models/competitions/index.ts b/src/models/competitions/index.ts index 8fa0c70ed..07fd55789 100755 --- a/src/models/competitions/index.ts +++ b/src/models/competitions/index.ts @@ -35,7 +35,8 @@ import { getCompetitionsList, getCourse, Results, TabResults, - getHSCompetitionInfo + getHSCompetitionInfo, + getWorkSubmitUpdateRes, } from "@/service/competitions"; export interface CompetitionsModelState { name: string; @@ -91,6 +92,7 @@ export interface CompetitionsType { Results:Effect; TabResults:Effect; getHSCompetitionInfo:Effect + getWorkSubmitUpdateRes?: Effect; }; reducers: { save: Reducer; @@ -127,6 +129,10 @@ const CompetitionsModel: CompetitionsType = { }) return response }, + *getWorkSubmitUpdateRes({ payload, callback }, { call, put }) { + const response = yield call(getWorkSubmitUpdateRes, payload); + return response; + }, //提交学生数据 *addApplytojoincourse({ payload, callback }, { call, put }) { const response = yield call(addApplytojoincourse, payload); diff --git a/src/pages/Competitions/Detail/AddSubmitModel.tsx b/src/pages/Competitions/Detail/AddSubmitModel.tsx index 16f366b79..24d4c4eae 100644 --- a/src/pages/Competitions/Detail/AddSubmitModel.tsx +++ b/src/pages/Competitions/Detail/AddSubmitModel.tsx @@ -59,7 +59,7 @@ const ShixunsListPage: FC = ({ return ( { if(fileList?.length<=0){ @@ -68,7 +68,7 @@ const ShixunsListPage: FC = ({ } setConfirmLoading(true); const resulr=await uploadFile(fileList[0],{ - login:user.eduUserInfo?.login, + login:user.userInfo?.login, container_type:"Competition", container_id: shixunsDetail.actionTabs.params.id, stage_type:shixunsDetail.actionTabs.params.value, diff --git a/src/pages/Competitions/Detail/SubmitResult.tsx b/src/pages/Competitions/Detail/SubmitResult.tsx index a43ca3e07..52c606a37 100644 --- a/src/pages/Competitions/Detail/SubmitResult.tsx +++ b/src/pages/Competitions/Detail/SubmitResult.tsx @@ -291,13 +291,14 @@ function Ranking({ChartRules,ItemData,getCharts,Selectkey,HeaderDetail,getChartR
{ChartRules?.stages?.length>0?null:} - {ChartRules?.stages?.length>0&&{ - setIsupdate(true) - - }}> + {ChartRules?.stages?.length>0&& + {setIsupdate(true)}}> 编辑 - :(StaffDetail.enrolled&&item?.start_time&&item?.end_time)&&提交时间:{moment(item?.start_time).format("YYYY-MM-DD HH:mm:ss")}至{moment(item?.end_time).format("YYYY-MM-DD HH:mm:ss")} - : + (StaffDetail.enrolled&&item?.start_time&&item?.end_time)&&提交时间:{moment(item?.start_time).format("YYYY-MM-DD HH:mm:ss")}至{moment(item?.end_time).format("YYYY-MM-DD HH:mm:ss")} + } onChange={(e)=>{ + }}>提交结果} onChange={(e)=>{ setIsupdate(false) let data=ChartRules?.stages?.filter(item=>parseInt(e)===parseInt(item?.id))[0]; let datas=data?.children?.[0]; diff --git a/src/pages/Competitions/Detail/components/WorkSubmit/index.less b/src/pages/Competitions/Detail/components/WorkSubmit/index.less new file mode 100644 index 000000000..c10661e6a --- /dev/null +++ b/src/pages/Competitions/Detail/components/WorkSubmit/index.less @@ -0,0 +1,45 @@ +[class^='ant-upload-list-item-info']{ + height: 26px; + background: #F6F7F9; + border-radius: 15px; +} +[class^='ant-upload-list-item-info'] [class^="anticon"]{ + top:7px!important; +} +[class^="ant-table-cell"]::before{ + background: rgba(255,255,255,0); +} +.WorkSubmit{ + &Desc{ + width: 100%; + padding: 8px 14px; + background: #f5f5f5; + color:#9096a3; + font-size: 12px; + border-radius: 0 0 8px 8px; + margin: 20px 0 30px 0; + } + .span{ + font-size: 12px; + font-weight: 400; + color: #C5C5C5; + span{ + color: #717171; + } + } + .search{ + width: 292px; + border-radius: 19px; + height: 38px; + padding: 5px 14px; + font-size: 14px; + } + .downBut{ + border-radius: 16px; + color: #3061D0; + padding: 0 20px; + height: 32px; + border: 1px solid #BACFFE; + box-shadow: 0px 2px 4px 0px #E0DFE1, inset 0px 1px 3px 0px rgba(255,255,255,0.5); + } +} \ No newline at end of file diff --git a/src/pages/Competitions/Detail/components/WorkSubmit/index.tsx b/src/pages/Competitions/Detail/components/WorkSubmit/index.tsx new file mode 100644 index 000000000..054ebb8ce --- /dev/null +++ b/src/pages/Competitions/Detail/components/WorkSubmit/index.tsx @@ -0,0 +1,567 @@ + +import React, { useEffect, useRef, useState } from 'react' +import type { FC } from 'react'; +import { + connect, ConnectProps, Dispatch, useParams +} from 'umi'; +import { + Tabs, + Row, + Pagination, + Table, + Button, + Modal, + Form, + message, + Upload, + Tooltip, + Input, + Select +} from 'antd'; +import { UploadOutlined } from '@ant-design/icons'; +import styles from './index.less'; +import NoData from '@/components/NoData'; +import { AsyncButton } from '@/components/AsyncButton'; +import MarkdownEditor from '@/components/markdown-editor'; +import RenderHtml from '@/components/RenderHtml'; +import moment from 'moment'; +import { uploadFile } from '@/components/UploadFile'; +import JSZip from 'jszip'; +import { downLoadLink } from "@/utils/util"; +import Fetch from '@/utils/fetch'; +import ENV from "@/utils/env"; + +interface PageProps extends Partial { + dispatch?: Dispatch, + userinfo?: any, + Editable?: boolean, + ItemData?: any, + TabResults?: any, + HeaderDetail?: any, + StaffDetail?: any, + getTabResults?: Function,//获取Tab和md数据 +} + +const WorkSubmit: FC = ({ + dispatch, + userinfo, + Editable, + ItemData, + TabResults, + HeaderDetail, + StaffDetail, + getTabResults = () => { } +}) => { + + const [form] = Form.useForm(); + const [formButLoading, setFormButLoading] = useState(false); + // 开启/关闭 编辑 + const [isEdit, setIsEdit] = useState(false) + const { identifier } = useParams(); + // 提交作品:1, 仅提交文件:2 + const [modelType, setModelType] = useState(1); + // 竞赛管理员、超管、运营 + const [identity, setIdentity] = useState(false); + const [isSubmitModel, setIsSubmitModel] = useState(false); + // 上传列表数据 + const [fileList, setFileList] = useState([]); + // 表格数据 + const [tableLoading, setTableLoading] = useState(false); + //战队信息 + const [teamList, setTeamlist] = useState([]); + //loading状态 + const [isloading, setisloading] = useState(false) + //获取id + const [rowTable, setRowTable] = useState({ + name: "", + url: "", + id: null, + fileList: [], + competition_team_id: '' + }); + const [tableList, setTableList] = useState({ + total_count: 0, + results: [], + }); + // 当前赛事 + const [gameItem, setGameItem] = useState(null); + // 分页,搜索 + const [urlData, setUrlData] = useState({ + page: 1, + per_page: 20, + keyword: "", + }); + // MD内容 + const [mdContent, setMdContent] = useState(""); + // 下载所有文件时打包成zip文件 + const [zipAll, setZipAll] = useState(new JSZip()); + useEffect(() => { + setIdentity(userinfo?.admin || userinfo?.business || Editable) + }, [userinfo?.admin, userinfo?.business, Editable]) + useEffect(() => { + ItemData?.only_file ? setModelType(2) : setModelType(1); + }, [ItemData]) + useEffect(() => { + if (TabResults.stages && TabResults.stages?.length > 0) { + TabResults.stages?.[0]?.children?.length > 0 ? + setGameItem(TabResults.stages?.[0]?.children?.[0] || null) : + setGameItem(TabResults.stages?.[0] || null); + } + }, [TabResults.stages]) + useEffect(() => { + getResults(); + // getTeamList() + getTeam() + }, [gameItem?.id, urlData.page]) + async function getTeamList() { + setisloading(true) + let res = await Fetch(`${ENV.API_SERVER}/api/competitions/${identifier}/my_teams`, { + method: 'get' + }) + setisloading(false) + setIsSubmitModel(true) + if (res?.status === 0) { + setTeamlist(res?.data) + form.setFieldsValue({ + name: '', + url: '', + id: '', + competition_team_id: res?.data?.[0]?.id, + }) + } + } + async function getTeam() { + + let res = await Fetch(`${ENV.API_SERVER}/api/competitions/${identifier}/my_teams`, { + method: 'get' + }) + + if (res?.status === 0) { + + + setTeamlist(res?.data) + + } + } + + // 表格头部数据 + const columns: any[] = [ + { + title: 队伍名称, + dataIndex: 'team_name', + width: "120px", + ellipsis: true, + render: (text: any) => {text || "- -"}, + isShow: [1, 2], + }, + { + title: 提交人, + dataIndex: 'user_name', + width: "120px", + ellipsis: true, + render: (text: any) => {text || "- -"}, + isShow: [1, 2], + }, + { + title: 作品名称, + dataIndex: 'name', + ellipsis: true, + render: (text: any, record: any) => text ? {text} : "--", + isShow: [1], + }, + { + title: 作品文件, + dataIndex: 'file_name', + width: modelType == 1 ? "180px" : "", + ellipsis: true, + render: (text: any) => {text || "- -"}, + isShow: [1, 2], + }, + { + title: 更新时间, + dataIndex: 'updated_at', + width: "180px", + render: (text: any) => text || '- -', + isShow: [1, 2], + }, + { + title: 操作, + dataIndex: 'result_url', + ellipsis: true, + width: "200px", + align: 'center', + render: (text: any, record: any) => + {!identity && } + downLoadLink(record.file_name, text)}>{identity ? "下载文件" : "下载"} + {!identity && } + + , + isShow: [1, 2], + }, + ].filter((item: any) => item.isShow.includes(modelType)) + // 获取列表数据 + const getResults = async () => { + setTableLoading(true) + const data: any = await dispatch({ + type: 'competitions/Results', + payload: { + identifier: identifier, + stage_id: gameItem?.id, + module_type: 'worksubmit', + ...urlData, + } + }) + setTableList(data) + setTableLoading(false) + } + // 更新MD内容 + const getUpMDContent = async (data: any) => { + const res: any = await dispatch({ + type: 'competitions/updateMdContent', + payload: { + identifier: identifier, + stage_id: gameItem?.id, + competition_module_id: ItemData.id, + content: mdContent, + md_content_id: data?.id, + } + }) + res.status == 0 && message.success(res.message); + setIsEdit(false); + await getTabResults(); + } + // 作品提交或者编辑 + const handleFormFinish = async (values: any) => { + + setFormButLoading(true); + let res: any; + if (modelType == 2 && fileList.length == 0) { + message.error("请选择文件"); + return; + } + const Data: any = { + login: userinfo?.login, + container_type: "Competition", + file_name: fileList?.[0]?.name, + stage_type: gameItem?.id || null, + container_id: HeaderDetail?.id, + result_id: rowTable?.id, + module_type: 'worksubmit', + competition_team_id: values.competition_team_id + } + if (modelType == 1) { + const res: any = await Fetch(`${ENV.API_SERVER}/api/competitions/${identifier}/check_result_url.json`, + { + method: "GET", + params: { + url: values.url, + stage_id: gameItem?.id, + result_id: rowTable?.id, + competition_team_id: values.competition_team_id + } + } + ) + setFormButLoading(false); + + if (res?.status != 0) return + Data["name"] = values.name; + Data["url"] = values.url; + } + if (modelType == 1 && fileList.length == 0) { + res = await dispatch({ + type: 'competitions/getWorkSubmitUpdateRes', + payload: { + identifier: identifier, + result_id: rowTable?.id, + name: values.name, + url: values.url, + stage_id: gameItem?.id, + result_url: rowTable?.result_url, + module_type: 'worksubmit', + competition_team_id: values.competition_team_id + } + }) + } else { + res = await uploadFile(fileList[0], Data) + } + if (res?.status == 0) { + message.success("提交成功"); + } else { + message.info(res?.message || "提交失败"); + } + setFormButLoading(false); + setIsSubmitModel(false); + setFileList([]); + getResults(); + } + // 下载所有作品 + const handleAllDownload = async () => { + const res: any = await dispatch({ + type: 'competitions/Results', + payload: { + identifier: identifier, + stage_id: gameItem?.id, + page: 1, + per_page: 9999999, // TODO :查询所有列表数据 + keyword: "", + module_type: 'worksubmit', + + } + }) + const DownloadList = res?.results?.filter((item: any) => item?.result_url && item.file_name) + const modal = Modal.info({ + width: 460, + title:
正在导出参赛文件,请勿关闭或操作当前浏览器窗口
, + content:
当前进度: {0}/{DownloadList.length}
, + maskClosable: false, + className: styles.modal, + onOk: () => { + } + }); + const nameLsit: string[] = []; + const addzip = async (data: any, progress: number) => { + try { + const zip = new JSZip(); + const response = await fetch(data.result_url, { method: "get" }); + const blob = await response.blob(); + zip.file(data.file_name, blob); + await zip.generateAsync({ type: "blob" }).then((blob) => { + const getNameFile = (name: string, index: number = 0) => { + if ((index == 0 && nameLsit.includes(name)) || nameLsit.includes(`${name}(${index})`)) { + getNameFile(name, index + 1); + } else { + nameLsit.push(index == 0 ? name : `${name}(${index})`) + zipAll.file(index == 0 ? name + ".zip" : `${name}(${index})` + ".zip", blob); + } + } + getNameFile(`${data?.team_name}-${data?.user_name}`); + }) + modal.update({ + content:
当前进度: {progress}/{DownloadList.length}
, + }) + } catch (error) { + message.error(`${data.file_name}下载失败`); + } + } + if (DownloadList.length > 0) { + let i = 0; + for (let item of DownloadList) { + await addzip(item, ++i); + } + zipAll.generateAsync({ type: "blob" }).then((blob) => { + downLoadLink(HeaderDetail.name, window.URL.createObjectURL(blob)); + }) + } + modal.destroy() + } + const uploadProps = { + maxCount: 1, + withCredentials: true, + fileList: fileList, + onRemove: () => { + setFileList([]); + }, + beforeUpload: (file: any) => { + const fileSize = file.size / 1024 / 1024; + const fileType = file.name.split(".").slice(-1)[0].toLowerCase(); + if ((modelType == 1 && fileSize > 150 || modelType == 2 && fileSize / 1024 > 1) || fileSize == 0) { + message.error(`${file.name} 文件无法上传。${fileSize == 0 ? "文件内容不能为空" : `超过文件大小限制(${modelType == 1 ? '150MB' : '1G'})`}`) + return Promise.reject() + } + setFileList([file]) + return false + } + }; + + + + + return (
+ {gameItem ? + + {!identity && StaffDetail.enrolled && } + {identity && !isEdit && } + } + onChange={(activeKey) => { + const item = TabResults.stages.find((item: any) => item.id == activeKey) + item.children.length > 0 ? setGameItem(item.children[0]) : setGameItem(item) + }}> + {TabResults.stages?.map((item: any, index: number) => {item.name}
} key={item.id}> + { + isEdit ? + <> + setMdContent(e)} /> + + getUpMDContent(TabResults.rule_contents[index])}>提交 + + + : + } + {item.children?.length > 0 && { setGameItem(item.children?.find((item: any) => item.id == activeKey)) }}> + {item.children?.map((ChildItem: any) => )} + } + )} +
: +
+ {(StaffDetail.enrolled || identity) && + {!identity && StaffDetail.enrolled && } + {identity && !isEdit && } + } + { + isEdit ? + <> + setMdContent(e)} /> + + getUpMDContent(TabResults.rule_contents?.[0])}>提交 + + + : + } +
} + + + } + onChange={(e: any) => { + urlData.keyword = e.target.value + urlData.search = e.target.value + setUrlData(urlData) + }} + style={{ background: "#F6F7F9" }} + bordered={false} + onPressEnter={getResults} /> +
当前共计作品{tableList.total_count}
+
+ {identity && + {modelType == 1 && } + } className={styles.downBut} style={{ marginLeft: "20px" }} onClick={handleAllDownload}>下载所有文件 + } +
+ }} pagination={false} /> + + { + (gameItem?.end_time && gameItem?.start_time || HeaderDetail.start_time && HeaderDetail.end_time) && + 提交时间:{moment(gameItem?.start_time || HeaderDetail.start_time).format("YYYY-MM-DD HH:mm:ss")}{moment(gameItem?.end_time || HeaderDetail.end_time).format("YYYY-MM-DD HH:mm:ss")} + } + setUrlData({ ...urlData, page })} + onShowSizeChange={(page, per_page) => setUrlData({ ...urlData, page: 1, per_page })} + total={tableList.total_count || 0} /> + + 作品提交} + visible={isSubmitModel} + bodyStyle={{ padding: " 0px 25px 10px 25px" }} + onOk={() => form.submit()} confirmLoading={formButLoading} + onCancel={() => { + setIsSubmitModel(false); + setFileList([]); + + setRowTable(null); + }}> +
说明:大赛进行中上传后的作品如需修改,可在【作品提交】列表点击“编辑”进行修改。
+
+ {modelType == 1 && <> + + + + + + } + + + + + { + if (Array.isArray(e)) return e; + return e?.fileList; + }}> + + + {modelType == 1 && + + + { e.preventDefault(); e.stopPropagation(); }}> +

提示:

+

1. 一次最多上传1个文件。

+

2. 单个文件大小请勿超过150M。

+ }> + { e.preventDefault(); e.stopPropagation(); }} className='iconfont icon-tishixiaowenhao ml5' style={{ cursor: 'pointer', color: '#C5C5C5' }} /> +
+
+
} + {modelType == 2 && +

+

拖拽文件或者点击上传

+
} +
+ +
+ ) +} + + +export default connect(( + { + }: { + }) => ({ + }))(WorkSubmit) diff --git a/src/pages/Competitions/Detail/index.tsx b/src/pages/Competitions/Detail/index.tsx index e3ec46c3f..3b674b05a 100644 --- a/src/pages/Competitions/Detail/index.tsx +++ b/src/pages/Competitions/Detail/index.tsx @@ -37,6 +37,7 @@ import env from '@/utils/env'; import ClaModal from '../components/ClaModal'; import SubCompetition from './components/SubCompetition'; import { hongshanId , getHSCount } from '@/utils/hongshan'; +import WorkSubmit from './components/WorkSubmit'; interface PageProps extends ConnectProps { competitions: CompetitionsModelState; @@ -102,6 +103,8 @@ const competitionDetails: FC = ({ const [datas,setdatas]=useState(''); const [visible,setVisible]=useState(false); const [hsVisible,setHsVisible]=useState(false); + // 作品提交 + const [isWorkSubmit, setIsWorkSubmit] = useState(false); const { hongshanInfos } = competitions; useEffect(()=>{ @@ -130,18 +133,18 @@ const competitionDetails: FC = ({ }, [identifier]) async function init() { + const res = await dispatch({ + type: 'competitions/getHeader', + payload: { + identifier: identifier + } + }) setStaffDetail(await dispatch({ type: 'competitions/getStaff', payload: { identifier: identifier } })) - const res = await dispatch({ - type: 'competitions/getHeader', - payload: { - identifier: identifier - } - }) setHeaderDetail(res) setDocumentTitle(res?.name || '竞赛') } @@ -168,6 +171,7 @@ const competitionDetails: FC = ({ async function getrightdatas(item: any) { setSeleckjey(item.id); Selectkey = item.id; + setMenuItem(item); let data ; if(item.module_type==='entrance'){ @@ -193,10 +197,15 @@ const competitionDetails: FC = ({ setMdTab(false) setItemData(data); setshowmake(false); - setentrance(false) + setentrance(false); + setIsWorkSubmit(false); setModelType(item.module_type); - if (item.module_type === "chart") { + if (item.module_type === "worksubmit") { + setIssee(false); + setIsWorkSubmit(true); + getTabResults(); + } else if (item.module_type === "chart") { setIsRanKing(true) setIssee(false); getChartRules(); @@ -435,7 +444,6 @@ const competitionDetails: FC = ({ async function getTabResults() { - setTabResults( await dispatch({ type: 'competitions/TabResults', @@ -666,6 +674,7 @@ async function JoinTeams(name: any) { message.info('加入战队才能查看'); return } + // if(item?.item?.has_url){ // window.visible(item?.module_url) // return @@ -679,13 +688,15 @@ async function JoinTeams(name: any) {
- {isAward ? : null} - {isRanKing ? : null} - {ISsee ? : null} - {!ISsee &&!showmake&&!entrance&& !isRanKing && !isAward && !MdTab ? : null} + {isAward ? : null} + {isRanKing ? : null} + {ISsee ? : null} + {!ISsee &&!showmake&&!entrance&& !isRanKing && !isAward && !MdTab && !isWorkSubmit ? : null} {MdTab && } {showmake && } {entrance&& } + {/* isWorkSubmit */} + {isWorkSubmit && }
diff --git a/src/service/competitions.ts b/src/service/competitions.ts index c0d6daa1c..61ab89b40 100755 --- a/src/service/competitions.ts +++ b/src/service/competitions.ts @@ -1,5 +1,12 @@ import Fetch from '@/utils/fetch'; import ENV from '@/utils/env'; +// 作品提交-作品编辑 /api/competitions/test22/update_result.json +export async function getWorkSubmitUpdateRes(params: any) { + return Fetch(`/api/competitions/${params.identifier}/update_result.json`, { + method: 'post', + body: params, + }); +} //获取在线竞赛列表 export async function getCompetitionsList(params: any) { return Fetch('/api/competitions.json', { diff --git a/src/utils/env/dev.ts b/src/utils/env/dev.ts index 2d54f96c1..13a0798a1 100644 --- a/src/utils/env/dev.ts +++ b/src/utils/env/dev.ts @@ -1,6 +1,6 @@ export const DEV = { PROXY_SERVER: "https://data.educoder.net", - API_SERVER: "https://pre-data.educoder.net", + API_SERVER: "https://data.educoder.net", HS_SERVER: "https://www.osredm.com", REPORT_SERVER: "http://192.168.1.57:3001", IMG_SERVER: 'https://data.educoder.net', diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts index 7a2115312..07cc5ad51 100755 --- a/src/utils/fetch.ts +++ b/src/utils/fetch.ts @@ -2,8 +2,7 @@ import { fetch } from 'dva'; import ENV from './env' import { notification, message, Modal } from 'antd' import hash from 'hash.js' -import { useDispatch, getDvaApp, history } from 'umi' -import { reportData } from 'monitor-error-ll' +import { getDvaApp, history } from 'umi' import { getCookie } from './util'; let modalConfirm: any; const codeMessage: any = { @@ -127,8 +126,8 @@ export default function request(url: string, option: any, flag?: boolean) { let newOptions = { ...defaultOptions, ...options } let eduHeader = { - 'X-Edu-Signature':'d81315a984e044dd57456b9e675727b6', - 'X-Edu-Timestamp':'1722216586532', + 'X-Edu-Signature':'ca213221bc2ffc422ce4378296f34eec', + 'X-Edu-Timestamp':'1724377167316', 'X-Edu-Type':'pc', }