diff --git a/package-lock.json b/package-lock.json index bfe90fc0f..9971f9667 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8340,9 +8340,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001287", - "resolved": "https://registry.npmmirror.com/caniuse-lite/download/caniuse-lite-1.0.30001287.tgz", - "integrity": "sha512-4udbs9bc0hfNrcje++AxBuc6PfLNHwh3PO9kbwnfCQWyqtlzg3py0YgFu8jyRTTo85VAz4U+VLxSlID09vNtWA==" + "version": "1.0.30001512", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz", + "integrity": "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==" }, "node_modules/canvg": { "version": "3.0.9", @@ -34122,9 +34122,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001287", - "resolved": "https://registry.npmmirror.com/caniuse-lite/download/caniuse-lite-1.0.30001287.tgz", - "integrity": "sha512-4udbs9bc0hfNrcje++AxBuc6PfLNHwh3PO9kbwnfCQWyqtlzg3py0YgFu8jyRTTo85VAz4U+VLxSlID09vNtWA==" + "version": "1.0.30001512", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz", + "integrity": "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==" }, "canvg": { "version": "3.0.9", diff --git a/src/components/AsyncButton/index.tsx b/src/components/AsyncButton/index.tsx new file mode 100644 index 000000000..4033b42c9 --- /dev/null +++ b/src/components/AsyncButton/index.tsx @@ -0,0 +1,22 @@ +import { Button } from 'antd' +import React, { useState } from 'react' +import { ButtonProps } from 'antd/es/button' + + +interface AsyncButtonProps extends ButtonProps { + 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/components/User/PopLoginRegister/components/OauthPanel/index.tsx b/src/components/User/PopLoginRegister/components/OauthPanel/index.tsx index 343c28ce1..8013079ea 100644 --- a/src/components/User/PopLoginRegister/components/OauthPanel/index.tsx +++ b/src/components/User/PopLoginRegister/components/OauthPanel/index.tsx @@ -4,7 +4,7 @@ import qqUrl from './qq.svg' import { isPc, openNewWindow } from '@/utils/util' import styles from './index.less' import ENV from '@/utils/env'; -const HOST = window.location.host +const HOST = window.location.host; const redirect_uri = ENV.QQLoginCB export default ({ onWechatLogin, setType, loginType }: { onWechatLogin: any, setType: any, loginType: number }) => { diff --git a/src/components/User/PopLoginRegister/components/OauthPanel/qq.png b/src/components/User/PopLoginRegister/components/OauthPanel/qq.png new file mode 100644 index 000000000..f6937c2ba Binary files /dev/null and b/src/components/User/PopLoginRegister/components/OauthPanel/qq.png differ diff --git a/src/components/User/PopLoginRegister/components/OauthPanel/qq.svg b/src/components/User/PopLoginRegister/components/OauthPanel/qq.svg new file mode 100644 index 000000000..b0d3eaa1e --- /dev/null +++ b/src/components/User/PopLoginRegister/components/OauthPanel/qq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/User/PopLoginRegister/components/OauthPanel/qq.svg~merged b/src/components/User/PopLoginRegister/components/OauthPanel/qq.svg~merged deleted file mode 100644 index 495690e9c..000000000 --- a/src/components/User/PopLoginRegister/components/OauthPanel/qq.svg~merged +++ /dev/null @@ -1,33 +0,0 @@ -<<<<<<< HEAD - - - - -Created with Sketch. - - - - - - - - - - - - - - - -======= - ->>>>>>> dev diff --git a/src/components/User/PopLoginRegister/components/OauthPanel/wechat.png b/src/components/User/PopLoginRegister/components/OauthPanel/wechat.png new file mode 100644 index 000000000..2cca81b42 Binary files /dev/null and b/src/components/User/PopLoginRegister/components/OauthPanel/wechat.png differ diff --git a/src/components/User/PopLoginRegister/components/OauthPanel/wechat.svg b/src/components/User/PopLoginRegister/components/OauthPanel/wechat.svg index 6e1940a45..ccd9347bb 100644 --- a/src/components/User/PopLoginRegister/components/OauthPanel/wechat.svg +++ b/src/components/User/PopLoginRegister/components/OauthPanel/wechat.svg @@ -1,34 +1 @@ - - - - -Created with Sketch. - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/src/models/competitions/index.ts b/src/models/competitions/index.ts index 4b37b1673..12dcf0964 100755 --- a/src/models/competitions/index.ts +++ b/src/models/competitions/index.ts @@ -1,6 +1,5 @@ import { Effect, Reducer, Subscription } from 'umi'; -import { Action } from '@@/plugin-dva/connect' import { getCompetitionsList, addApplytojoincourse , competitionTeams, @@ -34,7 +33,8 @@ import { getCompetitionsList, getShixun, getCourse, Results, - TabResults + TabResults, + getWorkSubmitUpdateRes } from "@/service/competitions"; export interface CompetitionsModelState { name: string; @@ -88,6 +88,7 @@ export interface CompetitionsType { getCourse:Effect; Results:Effect; TabResults:Effect; + getWorkSubmitUpdateRes?:Effect; }; reducers: { save: Reducer; @@ -113,6 +114,11 @@ const CompetitionsModel: CompetitionsType = { }, effects: { + //获取头部信息 + *getWorkSubmitUpdateRes({ payload, callback }, { call, put }) { + const response = yield call(getWorkSubmitUpdateRes, payload); + return response; + }, // 获取页面数据 * getList({ payload }, { call, put }) { const listParams = payload; diff --git a/src/models/user/index.ts b/src/models/user/index.ts index dc14a5889..4f3209e6c 100755 --- a/src/models/user/index.ts +++ b/src/models/user/index.ts @@ -92,7 +92,7 @@ const UserModel: UserModelType = { }, *getUserInfo({ payload }, { call, put }) { // location.search,,debug:"admin" - const response = yield call(getUserInfo, { ...payload}) + const response = yield call(getUserInfo, { ...payload,debug:"student"}) localStorage.userInfo = JSON.stringify(response); yield put({ 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..7a43a1c1f --- /dev/null +++ b/src/pages/Competitions/Detail/components/WorkSubmit/index.less @@ -0,0 +1,43 @@ +[class^='ant-upload-list-item-info']{ + padding:10px; + height: 26px; + background: #F6F7F9; + border-radius: 15px; +} +[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..f81e095bd --- /dev/null +++ b/src/pages/Competitions/Detail/components/WorkSubmit/index.tsx @@ -0,0 +1,479 @@ + +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 +} 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'; + +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 [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 [rowTable,setRowTable] = useState({ + name:"", + url:"", + id:null, + fileList:[], + }); + 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(()=>{ + if(ItemData?.only_file)setModelType(2); + },[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(); + },[gameItem?.id,urlData.page]) + // 表格头部数据 + 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:"140px", + align: 'center', + render: (text: any,record:any) => + {!identity&&} + downLoadLink(record.file_name,text)}>{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)=>{ + console.log(values,fileList); + 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, + container_id:HeaderDetail?.id, + result_id:rowTable?.id, + module_type:'worksubmit', + } + if(modelType==1){ + const res:any = await Fetch(`/api/competitions/${identifier}/check_result_url.json`, + { + method:"GET", + params:{ + url:values.url, + stage_id:gameItem?.id, + result_id:rowTable?.id, + } + } + ) + 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', + } + }) + }else{ + res=await uploadFile(fileList[0],Data) + } + if(res?.status == 0){ + message.success("提交成功"); + }else{ + message.info(res?.message||"提交失败"); + } + 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])}>提交 + + + : + } +
} + {identity&& + + } + onChange={(e:any)=>{ + urlData.keyword = e.target.value + setUrlData(urlData) + }} + style={{background:"#F6F7F9"}} + bordered={false} + onPressEnter={getResults}/> +
当前共计作品{tableList.total_count}
+
+ + {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")} + } + { + urlData.page = page + setUrlData(urlData) + }}/> + + + 参数作品提交} + open={isSubmitModel} + bodyStyle={{padding:" 0px 25px 10px 25px"}} + footer={null} + 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 e392c86a5..e2e292855 100644 --- a/src/pages/Competitions/Detail/index.tsx +++ b/src/pages/Competitions/Detail/index.tsx @@ -35,6 +35,7 @@ import PhoneModal from '../components/PhoneModal' import env from '@/utils/env'; import ClaModal from '../components/ClaModal'; import SubCompetition from './components/SubCompetition'; +import WorkSubmit from './components/WorkSubmit'; interface PageProps extends ConnectProps { globalSetting: GlobalSettingModelState; @@ -97,6 +98,8 @@ const competitionDetails: FC = ({ const [isopen,setisopen]=useState(false); const [datas,setdatas]=useState(''); const [visible,setVisible]=useState(false); + // 作品提交 + const [isWorkSubmit,setIsWorkSubmit] = useState(false); const loction = useLocation(); const see = useRef(null); @@ -180,9 +183,14 @@ const competitionDetails: FC = ({ setItemData(data); setshowmake(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(); @@ -644,10 +652,11 @@ async function JoinTeams(name: any) { {isAward ? : null} {isRanKing ? : null} {ISsee ? : null} - {!ISsee &&!showmake&&!entrance&& !isRanKing && !isAward && !MdTab ? : null} + {!ISsee &&!showmake&&!entrance&& !isRanKing && !isAward && !MdTab && !isWorkSubmit ? : null} {MdTab && } {showmake && } {entrance&& } + {isWorkSubmit&&} diff --git a/src/service/competitions.ts b/src/service/competitions.ts index 24c160f0a..544ea2fbc 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/yarn.lock b/yarn.lock index dac5c1eee..20292e0e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4307,9 +4307,9 @@ "lodash.uniq" "^4.5.0" "caniuse-lite@^1.0.0", "caniuse-lite@^1.0.30000981", "caniuse-lite@^1.0.30001109", "caniuse-lite@^1.0.30001286": - "integrity" "sha512-4udbs9bc0hfNrcje++AxBuc6PfLNHwh3PO9kbwnfCQWyqtlzg3py0YgFu8jyRTTo85VAz4U+VLxSlID09vNtWA==" - "resolved" "https://registry.npmmirror.com/caniuse-lite/download/caniuse-lite-1.0.30001287.tgz" - "version" "1.0.30001287" + "integrity" "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==" + "resolved" "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz" + "version" "1.0.30001512" "canvg@^3.0.6": "integrity" "sha1-m6CV8Vi5S5fKLJwcQHhbEdwI320="