Merge pull request 'glcc支持多个项目报名+首页时间配置从后台读取' (#534) from durian/forgeplus-react:feature_glcc into gitlink_server

This commit is contained in:
caishi 2023-05-08 11:03:25 +08:00
commit 1a9e3634e8
11 changed files with 593 additions and 276 deletions

46
package-lock.json generated
View File

@ -160,7 +160,7 @@
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.nlark.com/source-map/download/source-map-0.5.7.tgz",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
}
}
@ -1296,13 +1296,13 @@
"dependencies": {
"inherits": {
"version": "2.0.1",
"resolved": "https://registry.nlark.com/inherits/download/inherits-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
"integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
"dev": true
},
"util": {
"version": "0.10.3",
"resolved": "https://registry.nlark.com/util/download/util-0.10.3.tgz",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
"dev": true,
"requires": {
@ -2561,7 +2561,7 @@
"dependencies": {
"define-property": {
"version": "1.0.0",
"resolved": "https://registry.nlark.com/define-property/download/define-property-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
"requires": {
"is-descriptor": "^1.0.0"
@ -2869,7 +2869,7 @@
"dependencies": {
"resolve": {
"version": "1.1.7",
"resolved": "https://registry.npm.taobao.org/resolve/download/resolve-1.1.7.tgz",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
"integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs="
}
}
@ -3661,7 +3661,7 @@
"dependencies": {
"define-property": {
"version": "0.2.5",
"resolved": "https://registry.nlark.com/define-property/download/define-property-0.2.5.tgz",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
"requires": {
"is-descriptor": "^0.1.0"
@ -6386,7 +6386,7 @@
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.nlark.com/path-to-regexp/download/path-to-regexp-0.1.7.tgz?cache=0&sync_timestamp=1618847046445&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpath-to-regexp%2Fdownload%2Fpath-to-regexp-0.1.7.tgz",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
"dev": true
},
@ -6859,7 +6859,7 @@
"dependencies": {
"core-js": {
"version": "1.2.7",
"resolved": "https://registry.npmmirror.com/core-js/download/core-js-1.2.7.tgz",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
},
"promise": {
@ -8069,7 +8069,7 @@
"dependencies": {
"pify": {
"version": "3.0.0",
"resolved": "https://registry.nlark.com/pify/download/pify-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
}
}
@ -11630,7 +11630,7 @@
},
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.nlark.com/punycode/download/punycode-1.4.1.tgz",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"dev": true
}
@ -16594,7 +16594,7 @@
"dependencies": {
"jsesc": {
"version": "0.5.0",
"resolved": "https://registry.nlark.com/jsesc/download/jsesc-0.5.0.tgz",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
"dev": true
}
@ -17517,7 +17517,7 @@
"dependencies": {
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/ms/download/ms-2.0.0.tgz",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
@ -17629,7 +17629,7 @@
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.nlark.com/extend-shallow/download/extend-shallow-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"requires": {
"is-extendable": "^0.1.0"
@ -18110,7 +18110,7 @@
},
"define-property": {
"version": "0.2.5",
"resolved": "https://registry.nlark.com/define-property/download/define-property-0.2.5.tgz",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
"requires": {
"is-descriptor": "^0.1.0"
@ -18118,7 +18118,7 @@
},
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.nlark.com/extend-shallow/download/extend-shallow-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"requires": {
"is-extendable": "^0.1.0"
@ -18126,7 +18126,7 @@
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.nlark.com/source-map/download/source-map-0.5.7.tgz",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
}
}
@ -18143,7 +18143,7 @@
"dependencies": {
"define-property": {
"version": "1.0.0",
"resolved": "https://registry.nlark.com/define-property/download/define-property-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
"requires": {
"is-descriptor": "^1.0.0"
@ -20018,7 +20018,7 @@
"dependencies": {
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.nlark.com/inherits/download/inherits-2.0.3.tgz",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
}
@ -20394,7 +20394,7 @@
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.nlark.com/extend-shallow/download/extend-shallow-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
@ -20575,7 +20575,7 @@
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npm.taobao.org/fill-range/download/fill-range-4.0.0.tgz",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
"dev": true,
"requires": {
@ -20587,7 +20587,7 @@
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.nlark.com/extend-shallow/download/extend-shallow-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
@ -20653,7 +20653,7 @@
},
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.nlark.com/is-number/download/is-number-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"dev": true,
"requires": {
@ -20662,7 +20662,7 @@
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {

View File

@ -212,4 +212,13 @@ export function getGlccSettings(params) {
method: 'get',
params,
});
}
// 删除项目报名信息
export function deleteApplyInfo(data) {
return fetch({
url: `/api/applyInformation/delete`,
method: 'post',
data
});
}

291
src/glcc/apply/editInfo.jsx Normal file
View File

@ -0,0 +1,291 @@
import React , { useCallback, useEffect , useState } from 'react';
import { Button, Form, Input, Select, Upload, message, Popconfirm } from 'antd';
import {getUploadActionUrl} from 'educoder';
import {applyGlcc, updateApplyGlcc} from '../api';
import TextArea from 'antd/lib/input/TextArea';
import { main_site_url } from '../fetch';
import TaskApply from './taskApply';
import './index.scss';
import { FlexAJ } from '../../forge/Component/layout';
const Option = Select.Option;
//
function EditRepoApplyInfo(props) {
const {form, applyInfo, current_user, round, cateList, setReload, isAddRepo, setIsAddRepo, setIsEdit, setEditRepoCount} = props;
const phonereg = /(^|\s*\+?0?0?86|\D)(1\d{2})[-\s]{0,3}(\d{4})[-\s]{0,3}(\d{4})(?=\D|$)/
const emailreg = /^[a-zA-Z0-9]+([.\-_\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/
const {getFieldDecorator, validateFields, setFieldsValue, validateFieldsAndScroll } = form;
const [imageUrl, setImageUrl] = useState(undefined);
// loading
const [loading, setLoading] = useState(false);
//
const [taskCount, setTaskCount] = useState(undefined);
useEffect(()=>{
if(applyInfo){
const {projectName, projectType, gitlinkUrl, contactName, contactPhone, projectIntro, registrationTaskList, projectLogoId, contactMail} = applyInfo;
const taskInfo = {};
const taskCountArr = []
registrationTaskList.map((item, index)=>{
taskCountArr.push(index+1);
const{taskName, taskUrl, taskDifficulty, taskReward, tutorName, tutorMail, taskDesc, tutorPhone} = item;
taskInfo[`taskName${index+1}`] = taskName;
taskInfo[`taskUrl${index+1}`] = taskUrl;
taskInfo[`taskDifficulty${index+1}`] = taskDifficulty;
taskInfo[`taskReward${index+1}`] = taskReward;
taskInfo[`tutorName${index+1}`] = tutorName;
taskInfo[`tutorMail${index+1}`] = tutorMail;
taskInfo[`taskDesc${index+1}`] = taskDesc;
taskInfo[`tutorPhone${index+1}`] = tutorPhone;
})
setTaskCount(taskCountArr);
setTimeout(() => {
setFieldsValue({
projectName, projectType, gitlinkUrl, contactName, contactPhone, projectIntro, contactMail,
...taskInfo,
logo: {file:{response:{id: projectLogoId}}}
})
}, 100);
}else{
setTaskCount([1])
}
}, [])
const helper = useCallback(
(label, extra, name, rules, widget) => (
<Form.Item label={label} extra={extra}>
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
</Form.Item>
),
[]
);
//
function verify(dataIndex){
validateFields([dataIndex],(error, values)=>{
if(error && error[dataIndex]){
return;
}
})
}
//
function handleSubmit(e){
e.preventDefault();
validateFieldsAndScroll((err, values) => {
if (!err) {
const registrationTaskList = []
taskCount && taskCount.map(item=>{
registrationTaskList.push({
taskName: values[`taskName${item}`],
taskUrl: values[`taskUrl${item}`],
taskDifficulty: values[`taskDifficulty${item}`],
taskReward: values[`taskReward${item}`],
tutorName: values[`tutorName${item}`],
tutorMail: values[`tutorMail${item}`],
taskDesc: values[`taskDesc${item}`],
tutorPhone: values[`tutorPhone${item}`],
round
})
})
const {projectName, projectType, gitlinkUrl, contactName, contactPhone, projectIntro, contactMail} = values;
const params ={
projectName, projectType, gitlinkUrl, contactName, contactPhone, projectIntro, registrationTaskList, round, contactMail,
projectLogoId: values.logo.file.response.id+"",
userId: current_user.user_id,
userName: current_user.username
}
setLoading(true);
if(applyInfo){
//
params[`id`] = applyInfo.id;
updateApplyGlcc(params).then(response=>{
if(response && response.message === "success"){
message.success("修改成功");
setReload();
}
setLoading(false);
setIsEdit(false);
setEditRepoCount(0);
setTimeout(() => {
scrollToRepoInfo()
}, 100);
})
return
}
applyGlcc(params).then(response=>{
if(response && response.message === "success"){
message.success("报名成功");
isAddRepo && setIsAddRepo(false);
setReload();
}
setLoading(false);
window.scrollTo(0,950);
})
}
});
};
//
function beforeUpload(file){
const isLt100M = file.size / 1024 / 1024 < 20;
if (!isLt100M) {
message.error(`文件大小必须小于${20}M!`);
}
const isType = file.type === "image/png" || file.type === "image/jpg" || file.type === "image/jpeg";
if (!isType) {
message.error("只能上传png、jpg、jpeg格式文件");
}
return isLt100M && isType;
}
function getBase64(img, callback) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
reader.onload = function(e) {
setImageUrl(e.target.result); //
}
}
//
function handleChange(info){
if(info && info.file && info.file.status === "done"){
getBase64(info.file.originFileObj, imageUrl =>
setImageUrl(imageUrl)
);
}
}
//
function addTaskApplyForm(){
setTaskCount(taskCount.concat([taskCount.length+1]));
}
//
function deleteTaskApply(index){
const arr = [...taskCount];
arr.splice(index-1, 1)
setTaskCount(arr);
}
//
function cancelEdit(){
if(isAddRepo){
//
setIsAddRepo(false);
return
}
//
setIsEdit(false);
setEditRepoCount(0);
setTimeout(() => {
scrollToRepoInfo()
}, 100);
}
//
function scrollToRepoInfo(){
const element = document.getElementById(`repoShowId${applyInfo.id}`);
window.scrollTo(0, element.offsetTop-100);
}
return(
<div className='applyInfoBox'>
<div className='titleG font-18 mb10' id="editRepoInfoId">项目信息</div>
{taskCount && <Form className="pt20 glcc_info_form" onSubmit={handleSubmit}>
{helper('项目名称',
'',
'projectName',
[{ required: true, message: "请输入项目名称长度限制32个字符" },
{ max: 32, message: '超出限制长度32位字符请重新编辑' }],
<Input placeholder="请输入项目名称长度限制32个字符" onBlur={()=>{verify("name")}}/>
)}
{helper('项目分类',
'',
'projectType',
[{ required: true, message: "请选择项目分类" }],
<Select placeholder="请选择项目分类">
{cateList && cateList.map(item=> {return <Option value={item.name} key={item.id}>{item.name}</Option>})}
</Select>
)}
{helper('GitLink地址',
'',
'gitlinkUrl',
[{ required: true, message: "请输入GitLink项目地址" },
{ max: 252, message: '超出限制长度252位字符请重新编辑' }],
<Input placeholder="请输入GitLink项目地址" onBlur={()=>{verify("gitlink")}}/>
)}
{helper('联系人姓名',
'',
'contactName',
[{ required: true, message: "请输入项目联系人姓名" },
{ max: 32, message: '超出限制长度32位字符请重新编辑' }],
<Input placeholder="请输入项目联系人姓名" onBlur={()=>{verify("name")}}/>
)}
{helper('联系人电话',
'',
'contactPhone',
[{ required: true, message: "输入项目联系人手机号码" },
{ pattern: phonereg, message: "请输入正确的手机号码"},],
<Input placeholder="输入项目联系人手机号码" onBlur={()=>{verify("name")}}/>
)}
{helper('联系人邮箱',
'',
'contactMail',
[{ required: true, message: "输入项目联系人邮箱" },
{ pattern: emailreg, message: "请输入正确的邮箱地址"},],
<Input placeholder="输入项目联系人邮箱" onBlur={()=>{verify("name")}}/>
)}
<div className='introArea'>{helper('社区简介',
'',
'projectIntro',
[{ required: true, message: "请输入社区简介" },
{ max: 200, message: '超出限制长度200位字符请重新编辑' }],
<TextArea placeholder="请输入社区简介" onBlur={()=>{verify("intro")}} rows={4}/>
)}</div>
<div className='introArea'>{helper('项目logo',
'限制上传格式为png、jpg、jpeg限制20M。建议上传大小为382*228',
'logo',
[{ required: true, message: "请正确上传项目logo" }],
<Upload
listType="picture-card"
className="avatar-uploader"
showUploadList={false}
action={getUploadActionUrl()}
accept=".png,.jpg,.jpeg"
beforeUpload={beforeUpload}
onChange={handleChange}
>
{(imageUrl || applyInfo) ? <img src={imageUrl || `${main_site_url}/api/attachments/${applyInfo && applyInfo.projectLogoId}`} alt="avatar" style={{ maxWidth: '100px', maxHeight: '100px' }} /> : <div>
<i className='iconfont icon-tianjiadaohang font-30'></i>
<div className="ant-upload-text font-13">请上传logo图片</div>
</div>}
</Upload>
)}</div>
{taskCount.map((item, index)=>{
return <div className='taskApplyBox' key={item}>
<FlexAJ className='taskApplyTitleBox'>
<div className='taskApplyTitle font-16'>课题信息<span>{index+1}</span>/5)</div>
{index>0 && <Button type="link" onClick={()=>{deleteTaskApply(item)}}><i className='iconfont icon-shanchuicon3 mr5 font-15'></i>删除</Button>}
</FlexAJ>
<TaskApply helper={helper} taskKey={item} phonereg={phonereg} emailreg={emailreg}/>
</div>
})}
{taskCount && taskCount.length<5 && <div>
<Button type="primary" className='taskApplyBox' style={{width: "100px", height: "36px"}} onClick={addTaskApplyForm}>添加课题</Button>
</div>}
<Form.Item className='subInfo introArea'>
{(isAddRepo || applyInfo) && <Popconfirm
title={<span>取消编辑将清除已填写的所有项目信息请谨慎操作</span>}
onConfirm={cancelEdit}
okText="确定"
cancelText="取消"
>
<Button className='mr20' style={{height: '36px', width: '110px'}}>取消</Button>
</Popconfirm>}
<Button type="primary" htmlType="submit" className='sub' loading={loading}>提交</Button>
</Form.Item>
</Form>}
</div>
)
}
export default Form.create()(EditRepoApplyInfo);

View File

@ -1,38 +1,23 @@
import React , { useCallback, useEffect , useState } from 'react';
import { Breadcrumb, Button, Form, Input, Select, Upload, Modal, message } from 'antd';
import {getUploadActionUrl} from 'educoder';
import {getUserApplyInfo, applyGlcc, updateApplyGlcc} from '../api';
import React , { useEffect , useState } from 'react';
import { Breadcrumb, message } from 'antd';
import {getUserApplyInfo} from '../api';
import axios from 'axios';
import TextArea from 'antd/lib/input/TextArea';
import { main_site_url } from '../fetch';
import TaskApply from './taskApply';
import RepoInfo from './repoInfo';
import EditInfo from './editInfo';
import banner from "../img/banner.png";
import apply1 from "../img/apply1.png";
import './index.scss';
import { FlexAJ } from '../../forge/Component/layout';
const Option = Select.Option;
//
function Apply(props) {
const phonereg = /(^|\s*\+?0?0?86|\D)(1\d{2})[-\s]{0,3}(\d{4})[-\s]{0,3}(\d{4})(?=\D|$)/
const emailreg = /^[a-zA-Z0-9]+([.\-_\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/
const {form, current_user, showNotification, round} = props;
const {getFieldDecorator, validateFields, setFieldsValue, validateFieldsAndScroll } = form;
const {current_user, round} = props;
const [reload, setReload] = useState(undefined);
const [imageUrl, setImageUrl] = useState(undefined);
// loading
const [loading, setLoading] = useState(false);
const [cateList, setCateList] = useState([]);
const [userApplyInfo, setUserApplyInfo] = useState(undefined);
//
const [taskCount, setTaskCount] = useState([1]);
const helper = useCallback(
(label, extra, name, rules, widget) => (
<Form.Item label={label} extra={extra}>
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
</Form.Item>
),
[]
);
const [userApplyInfo, setUserApplyInfo] = useState([]);
//
const [isAddRepo, setIsAddRepo] = useState(false);
//
const [editRepoCount, setEditRepoCount] = useState(0);
useEffect(()=>{
//
@ -55,145 +40,38 @@ function Apply(props) {
current_user && round && getUserApplyInfo({userId: current_user.user_id, round}).then(response=>{
if(response && response.message === "success"){
setUserApplyInfo(response.data);
if(!response.data){
return
}
const {projectName, projectType, gitlinkUrl, contactName, contactPhone, projectIntro, registrationTaskList, projectLogoId, contactMail} = response.data;
const taskInfo = {};
const taskCountArr = []
registrationTaskList.map((item, index)=>{
taskCountArr.push(index+1);
const{taskName, taskUrl, taskDifficulty, taskReward, tutorName, tutorMail, taskDesc, tutorPhone} = item;
taskInfo[`taskName${index+1}`] = taskName;
taskInfo[`taskUrl${index+1}`] = taskUrl;
taskInfo[`taskDifficulty${index+1}`] = taskDifficulty;
taskInfo[`taskReward${index+1}`] = taskReward;
taskInfo[`tutorName${index+1}`] = tutorName;
taskInfo[`tutorMail${index+1}`] = tutorMail;
taskInfo[`taskDesc${index+1}`] = taskDesc;
taskInfo[`tutorPhone${index+1}`] = tutorPhone;
})
setTaskCount(taskCountArr);
setFieldsValue({
projectName, projectType, gitlinkUrl, contactName, contactPhone, projectIntro, contactMail,
...taskInfo,
logo: {file:{response:{id: projectLogoId}}}
})
}
})
}, [reload])
//
function verify(dataIndex){
validateFields([dataIndex],(error, values)=>{
if(error && error[dataIndex]){
return;
}
})
}
//
function handleSubmit(e){
e.preventDefault();
validateFieldsAndScroll((err, values) => {
if (!err) {
const registrationTaskList = []
taskCount && taskCount.map(item=>{
registrationTaskList.push({
taskName: values[`taskName${item}`],
taskUrl: values[`taskUrl${item}`],
taskDifficulty: values[`taskDifficulty${item}`],
taskReward: values[`taskReward${item}`],
tutorName: values[`tutorName${item}`],
tutorMail: values[`tutorMail${item}`],
taskDesc: values[`taskDesc${item}`],
tutorPhone: values[`tutorPhone${item}`],
round
})
})
const {projectName, projectType, gitlinkUrl, contactName, contactPhone, projectIntro, contactMail} = values;
const params ={
projectName, projectType, gitlinkUrl, contactName, contactPhone, projectIntro, registrationTaskList, round, contactMail,
projectLogoId: values.logo.file.response.id+"",
userId: current_user.user_id,
userName: current_user.username
}
setLoading(true);
if(userApplyInfo){
//
params[`id`] = userApplyInfo.id;
updateApplyGlcc(params).then(response=>{
if(response && response.message === "success"){
message.success("修改成功");
setReload(Math.random());
}
setLoading(false);
window.scrollTo(0,0);
})
return
}
applyGlcc(params).then(response=>{
if(response && response.message === "success"){
message.success("报名成功");
setReload(Math.random());
}
setLoading(false);
window.scrollTo(0,0);
})
}
});
};
//
function beforeUpload(file){
const isLt100M = file.size / 1024 / 1024 < 20;
if (!isLt100M) {
showNotification(`文件大小必须小于${20}M!`);
}
const isType = file.type === "image/png" || file.type === "image/jpg" || file.type === "image/jpeg";
if (!isType) {
showNotification("只能上传png、jpg、jpeg格式文件");
}
return isLt100M && isType;
}
function getBase64(img, callback) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
reader.onload = function(e) {
setImageUrl(e.target.result); //
//
function addRepoBut(){
if(isAddRepo || editRepoCount){
scrollToEdit();
message.error("请先提交当前项目信息");
return;
}
setIsAddRepo(true);
setTimeout(() => {
scrollToEdit()
}, 100);
}
//
function handleChange(info){
if(info && info.file && info.file.status === "done"){
getBase64(info.file.originFileObj, imageUrl =>
setImageUrl(imageUrl)
);
}
//
function scrollToEdit(){
const element = document.getElementById("editRepoInfoId");
window.scrollTo(0, element.offsetTop-80);
}
//
function addTaskApplyForm(){
setTaskCount(taskCount.concat([taskCount.length+1]));
}
//
function deleteTaskApply(index){
const arr = [...taskCount];
arr.splice(index-1, 1)
setTaskCount(arr);
}
return(
<div className="glcc_apply">
<img className="glcc-banner" src={banner} alt=''></img>
{/* <img className="glcc-banner" src={banner} alt=''></img> */}
<div className='apply'>
<Breadcrumb className='pt20 font-16 breadcrumbGlcc'>
<Breadcrumb.Item><a href="/glcc">开源夏令营</a></Breadcrumb.Item>
<Breadcrumb.Item>项目报名</Breadcrumb.Item>
</Breadcrumb>
{userApplyInfo && <div className='applySuccess mt20 font-15'>您已报名成功可实时修改您的报名信息再次提交请关注夏令营首页新闻公告获得更多活动资讯!</div>}
{userApplyInfo.length > 0 && <div className='applySuccess mt20 font-15'>您已报名成功可实时修改您的报名信息再次提交请关注夏令营首页新闻公告获得更多活动资讯!</div>}
<div className={`explain`}>
<p className='font-15 c000'>申请说明</p>
<div>1项目报名时间为<span className='c000'>4月29日5月30日</span>请在报名截止时间北京时间<span className='c000'>2022年5月30日24点</span>前提交报名信息</div>
@ -201,98 +79,16 @@ function Apply(props) {
<div>3如果您的项目还未迁移到GitLink上迁移事项请查看<a href='https://forum.gitlink.org.cn/forums/7296/detail' target="_blank" className='link'>迁移说明文档</a>如在迁移过程中遇到问题请加qq群: 1071514693 联系qq群管理员</div>
<div>4提交社区和题目信息后欢迎与组委会联系沟通宣传推广和后续合作工作联系人微信: _TigerWang备注GitLink编程夏令营</div>
</div>
<div className='pt20 font-18'><img src={apply1} alt="" width="25px" className="mr10"/>项目报名</div>
<div className='applyInfoBox mt20'>
<div className='titleG font-18 mb10'>项目信息</div>
<Form className="pt20 glcc_info_form" onSubmit={handleSubmit}>
{helper('项目名称',
'',
'projectName',
[{ required: true, message: "请输入项目名称长度限制32个字符" },
{ max: 32, message: '超出限制长度32位字符请重新编辑' }],
<Input placeholder="请输入项目名称长度限制32个字符" onBlur={()=>{verify("name")}}/>
)}
{helper('项目分类',
'',
'projectType',
[{ required: true, message: "请选择项目分类" }],
<Select placeholder="请选择项目分类">
{cateList && cateList.map(item=> {return <Option value={item.name} key={item.id}>{item.name}</Option>})}
</Select>
)}
{helper('GitLink地址',
'',
'gitlinkUrl',
[{ required: true, message: "请输入GitLink项目地址" },
{ max: 252, message: '超出限制长度252位字符请重新编辑' }],
<Input placeholder="请输入GitLink项目地址" onBlur={()=>{verify("gitlink")}}/>
)}
{helper('联系人姓名',
'',
'contactName',
[{ required: true, message: "请输入项目联系人姓名" },
{ max: 32, message: '超出限制长度32位字符请重新编辑' }],
<Input placeholder="请输入项目联系人姓名" onBlur={()=>{verify("name")}}/>
)}
{helper('联系人电话',
'',
'contactPhone',
[{ required: true, message: "输入项目联系人手机号码" },
{ pattern: phonereg, message: "请输入正确的手机号码"},],
<Input placeholder="输入项目联系人手机号码" onBlur={()=>{verify("name")}}/>
)}
{helper('联系人邮箱',
'',
'contactMail',
[{ required: true, message: "输入项目联系人邮箱" },
{ pattern: emailreg, message: "请输入正确的邮箱地址"},],
<Input placeholder="输入项目联系人邮箱" onBlur={()=>{verify("name")}}/>
)}
<div className='introArea'>{helper('社区简介',
'',
'projectIntro',
[{ required: true, message: "请输入社区简介" },
{ max: 200, message: '超出限制长度200位字符请重新编辑' }],
<TextArea placeholder="请输入社区简介" onBlur={()=>{verify("intro")}} rows={4}/>
)}</div>
<div className='introArea'>{helper('项目logo',
'限制上传格式为png、jpg、jpeg限制20M。建议上传大小为382*228',
'logo',
[{ required: true, message: "请正确上传项目logo" }],
<Upload
listType="picture-card"
className="avatar-uploader"
showUploadList={false}
action={getUploadActionUrl()}
accept=".png,.jpg,.jpeg"
beforeUpload={beforeUpload}
onChange={handleChange}
>
{(imageUrl || userApplyInfo) ? <img src={imageUrl || `${main_site_url}/api/attachments/${userApplyInfo.projectLogoId}`} alt="avatar" style={{ maxWidth: '100px', maxHeight: '100px' }} /> : <div>
<i className='iconfont icon-tianjiadaohang font-30'></i>
<div className="ant-upload-text font-13">请上传logo图片</div>
</div>}
</Upload>
)}</div>
{taskCount.map((item, index)=>{
return <div className='taskApplyBox' key={item}>
<FlexAJ className='taskApplyTitleBox'>
<div className='taskApplyTitle font-16'>课题信息<span>{index+1}</span>/5)</div>
{index>0 && <Button type="link" onClick={()=>{deleteTaskApply(item)}}><i className='iconfont icon-shanchuicon3 mr5 font-15'></i>删除</Button>}
</FlexAJ>
<TaskApply helper={helper} taskKey={item} phonereg={phonereg} emailreg={emailreg}/>
</div>
})}
{taskCount && taskCount.length<5 && <div>
<Button type="primary" className='taskApplyBox' style={{width: "100px", height: "36px"}} onClick={addTaskApplyForm}>添加课题</Button>
</div>}
<Form.Item className='subInfo introArea'>
<Button type="primary" htmlType="submit" className='sub' loading={loading}>提交</Button>
</Form.Item>
</Form>
</div>
<FlexAJ className='pt20 font-18 pb20'>
<span><img src={apply1} alt="" width="25px" className="mr10"/>项目报名</span>
{userApplyInfo.length > 0 && <a className='font-16 addRepoBut' onClick={addRepoBut}><i className='iconfont icon-tianjia_tianchong mr5 font-22'></i>添加项目</a>}
</FlexAJ>
{userApplyInfo.length > 0 && <div className='repoListBox'>
{userApplyInfo.map((item, index)=><div className='repoList mb20' key={index}><RepoInfo cateList={cateList} applyInfo={item} current_user={current_user} round={round} setReload={()=>{setReload(Math.random())}} isAddRepo={isAddRepo} editRepoCount={editRepoCount} setEditRepoCount={setEditRepoCount} repoCount={userApplyInfo.length} scrollToEdit={scrollToEdit}/></div>)}
</div>}
{(!userApplyInfo.length || isAddRepo) && <EditInfo isAddRepo={isAddRepo} setIsAddRepo={setIsAddRepo} round={round} cateList={cateList} current_user={current_user} setReload={()=>{setReload(Math.random())}}/>}
</div>
</div>
)
}
export default Form.create()(Apply);
export default Apply;

View File

@ -76,7 +76,7 @@
text-align: center;
margin: 40px 0 70px -60px;
.sub{
width: 200px;
width: 110px;
height: 36px;
background-color: #466aff;
border-color: #466aff;
@ -169,4 +169,98 @@
position: relative;
left: -17px;
}
}
.repoListBox{
background-color:rgba(255, 255, 255, 0.27);
border:1px solid #ffffff;
padding: 15px 15px 0;
}
.repoList{
border-bottom: 1px dashed #bec5d5;
&:hover{
background-color:#ffffff;
border-radius:4px;
box-shadow:0px 0px 10px rgba(84, 124, 255, 0.28);
.taskApplyInfo{
background-color:rgba(236, 242, 255, 0.83);
}
}
}
.flexStartBox{
display: flex;
align-items: flex-start;
justify-content: flex-start;
.repoName{
font-weight:700;
color:#333333;
}
}
.repoInfo{
padding: 20px 10px 30px 10px;
.repoLogo{
width:36px;
height:36px;
border-radius: 50%;
object-fit: cover;
}
.repoType{
display: inline-block;
padding: 5px 10px;
line-height: normal;
color:#465474;
border:1px solid #6680bb;
border-radius:4px;
}
.repoOthersInfo{
color:#465474;
word-break: break-all;
}
.labelInfoBox{
width: 75px;
display: inline-block;
&.gitlinkUrl{
width: 95px;
}
}
}
.taskApplyInfo{
background-color:rgba(251, 252, 255, 0.8);
border:1px solid #ffffff;
padding: 20px;
.taskDifficulty{
display: inline-block;
width:27px;
height:24px;
border:1px solid #0ba840;
border-radius:6px;
color:#0ba840;
line-height: 24px;
text-align: center;
&.diff2{
color:#ffb300;
border-color:#ffb300;
}
&.diff3{
color:#ff0000;
border-color:#ff1d1d;
}
}
.taskRewardBox{
color: $primary-color;
}
}
.addRepoBut{
color: $primary-color;
display: inline-flex;
align-items: center;
}
.openTaskBox{
text-align: center;
margin-top: 20px;
.openTask{
color: $primary-color;
border:1px solid $primary-color;
border-radius:6px;
padding: 10px 20px;
}
}

100
src/glcc/apply/repoInfo.jsx Normal file
View File

@ -0,0 +1,100 @@
import { Button, Row, Col, Popconfirm, message } from "antd";
import React, { useState } from "react";
import EditInfo from "./editInfo";
import { FlexAJ } from "../../forge/Component/layout";
import { main_site_url } from "../fetch";
import './index.scss';
import { deleteApplyInfo } from "../api";
//
function RepoInfo(props) {
const {applyInfo:{id, projectLogoId, projectName, projectType, contactName, contactPhone, contactMail, gitlinkUrl, projectIntro, registrationTaskList}, setReload, editRepoCount, setEditRepoCount, repoCount, isAddRepo, scrollToEdit} = props;
//
const difficultyList = ['', '低', '中', '高'];
const [isEdit, setIsEdit] = useState(false);
const [showAllTask, setShowAllTask] = useState(false);
//
function editRepoBut(){
if(editRepoCount || isAddRepo){
scrollToEdit();
message.error("请先提交当前项目信息");
return;
}
setEditRepoCount(editRepoCount+1);
setIsEdit(true);
}
//
function deleteBut(){
deleteApplyInfo({ids: [id], round: 2}).then(res=>{
message.success("删除成功");
setReload();
})
}
//
const showTask = (task) => {
const {id, taskDifficulty, taskName, taskReward, tutorName, tutorPhone, tutorMail, taskDesc, taskUrl} = task;
return <div className="taskApplyInfo mt20" key={id}>
<FlexAJ>
<div>
<div className={`taskDifficulty diff${taskDifficulty} font-14 mr10`}>{difficultyList[taskDifficulty]}</div>
<span className="repoName font-16">{taskName}</span>
</div>
<span className="font-20 taskRewardBox"> {taskReward}</span>
</FlexAJ>
<div style={{flex: 1}} className="font-15 ml35 mt10">
<Row gutter={16}>
<Col span={12}><div className="repoOthersInfo"><span className="labelInfoBox">导师信息:</span>{tutorName}{tutorPhone} | {tutorMail}</div></Col>
<Col span={12}><div className="repoOthersInfo"><span className="labelInfoBox">课题链接:</span>{taskUrl}</div></Col>
</Row>
<div className="repoOthersInfo mt10"><span className="labelInfoBox">课题简介:</span>{taskDesc}</div>
</div>
</div>
}
return (
isEdit ? <EditInfo {...props} setIsEdit={setIsEdit}/> : <div className="flexStartBox repoInfo">
<img className="repoLogo mr10" src={`${main_site_url}/api/attachments/${projectLogoId}`} alt= { `${projectName}-glcc`} />
<div style={{flex: 1, width: 0}} className="font-15" id={`repoShowId${id}`}>
{/* 项目信息 */}
<FlexAJ>
<div>
<span className="repoName font-16">{projectName}</span>
<span className="repoType ml20">{projectType}</span>
</div>
<div>
<Button type="link" onClick={editRepoBut}><i className='iconfont icon-a-bianji12 mr5 font-12'></i>编辑</Button>
{repoCount > 1 && <Popconfirm
title={<span>删除项目将清除项目信息及所有课题信息<br/>请谨慎操作</span>}
onConfirm={deleteBut}
okText="确定"
cancelText="取消"
>
<Button type="link"><i className='iconfont icon-shanchuicon3 mr5 font-15'></i>删除</Button>
</Popconfirm>}
</div>
</FlexAJ>
{/* <Rows label={["官网地址", "GitLink地址"]} content={[]}/> */}
<Row gutter={16}>
<Col span={12}><div className="repoOthersInfo mt15"><span className="labelInfoBox">导师信息:</span>{contactName}{contactPhone} | {contactMail}</div></Col>
<Col span={12}><div className="repoOthersInfo mt15"><span className="labelInfoBox gitlinkUrl">GitLink地址:</span>{gitlinkUrl}</div></Col>
</Row>
<div className="repoOthersInfo mt10"><span className="labelInfoBox">社区简介:</span>{projectIntro}</div>
{/* 课题信息list */}
{/* 多个课题(仅展示一个课题) 单个课题 */}
{registrationTaskList && registrationTaskList.length > 1 && !showAllTask && <div>
{showTask(registrationTaskList[0])}
<div className="openTaskBox"><a onClick={()=>{setShowAllTask(!showAllTask)}} className="openTask">展开课题<i className="iconfont icon-xiala1 ml10 font-14"></i></a></div>
</div>}
{/* 多个课题同时展示 */}
{registrationTaskList && (registrationTaskList.length === 1 || showAllTask) && <React.Fragment>
{registrationTaskList.map(item=>showTask(item))}
{showAllTask && <div className="openTaskBox"><a onClick={()=>{setShowAllTask(!showAllTask)}} className="openTask">收起课题<i className="iconfont icon-xiala2 ml10 font-14"></i></a></div>}
</React.Fragment>}
</div>
</div>
)
}
export default RepoInfo;

View File

@ -1,7 +1,8 @@
import React , { useCallback, useEffect , useState } from 'react';
import { Breadcrumb, Button, Form, Input, Select, Upload, Modal, InputNumber } from 'antd';
import React from 'react';
import {Form, Input, Select } from 'antd';
import TextArea from 'antd/lib/input/TextArea';
const Option = Select.Option;
//
function TaskApply(props) {
const {helper, disabled, taskKey, phonereg, emailreg} = props;
//

View File

@ -21,7 +21,7 @@ import './index.scss';
// round20221 20232
// period: 0
export default (props) => {
const { current_user, isGlccApplyDate, showNotification, studentApplyStart, history, secondStudentApplyDate, isStudentApplyDate, checkedTaskId, checkTime1, checkTime2, checkTime3, round, match:{params:{id}}, period, showLoginDialog} = props;
const { current_user, isGlccApplyDate, showNotification, studentApplyStart, history, secondStudentApplyDate, isStudentApplyDate, checkedTaskId, checkTime1, checkTime2, checkTime3, round, match:{params:{id}}, period, showLoginDialog, glccSettings} = props;
function goToApply() {
if (isGlccApplyDate) {
if (current_user && current_user.login) {
@ -75,7 +75,7 @@ export default (props) => {
history.push(url);
return
}
showLoginDialog();
showLoginDialog(`/glcc/apply`);
}
return (
@ -198,7 +198,7 @@ export default (props) => {
</div>
</div>
<Lightspot period={period} history={history} current_user={current_user} round={round} showLoginDialog={showLoginDialog}/>
<TimerShaft round={round}/>
<TimerShaft round={round} glccSettings={glccSettings}/>
<Award />
<News />
<Partner round={round}/>

View File

@ -10,11 +10,12 @@ function Lightspot(props) {
const {current_user, period, history, round, showLoginDialog} = props;
function goToApply(){
const url = period === 0 ? `/glcc/apply` : `/glcc/subjects`;
if(current_user && !current_user.login){
showLoginDialog();
showLoginDialog(url);
return;
}
history.push(period === 0 ? `/glcc/apply` : `/glcc/subjects`);
history.push(url);
}
return (

View File

@ -62,7 +62,7 @@ const stepArr = [{
},
{
title: "Step2",
date: "5.30-6.3",
date: "5.31-6.3",
content: "课题审核及公布"
},
{
@ -104,9 +104,30 @@ const stepArr = [{
}
]
function TimerShaft({round}) {
function TimerShaft({round, glccSettings}) {
if(glccSettings && glccSettings.length > 12){
stepArr[1].date = getRangeTime(glccSettings[0].value);
stepArr[2].date = getRangeTime(glccSettings[1].value);
stepArr[3].date = getRangeTime(glccSettings[2].value);
stepArr[4].date = getTime(glccSettings[3].value);
stepArr[5].date = getRangeTime(glccSettings[11].value);
stepArr[6].date = getTime(glccSettings[7].value);
stepArr[7].date = getRangeTime(glccSettings[12].value);
stepArr[8].date = getTime(glccSettings[9].value);
stepArr[9].date = getTime(glccSettings[4].value);
}
const step = round === 2 ? stepArr : stepArr2022
// /
function getRangeTime(value){
return value.split(",").map(item=>`${new Date(item).getMonth()+1}.${new Date(item).getDate()}`).join("-");
}
function getTime(value){
const time = new Date(value.split(",")[0]);
return `${new Date(time).getMonth()+1}.${new Date(time).getDate()}`;
}
return (
<div className="timerShaft">
<div className="glcc-content">

View File

@ -80,7 +80,10 @@ const Glcc = (propsF) => {
const {current_user, showLoginDialog, match:{params:{id}}} = propsF;
// round20221 20232
const [round, setRound] = useState(2);
// glcc 0()
const [period, setPeriod] = useState(undefined);
// glcc
const [glccSettings, setGLccSetting] = useState(undefined);
useEffect(()=>{
if(id && id === "2022"){
@ -91,6 +94,7 @@ const Glcc = (propsF) => {
// glcc
getGlccSettings({round: 2}).then(res=>{
if(res && res.message === "success"){
setGLccSetting(res.data);
const nowTime = new Date().getTime();
setPeriod(res.data.findIndex(item=>judge(nowTime, item.value)));
}
@ -269,7 +273,7 @@ const Glcc = (propsF) => {
<Route
path="/glcc/:id"
render={(props) => (
<Home period={period} round={round} {...propsF} {...props} studentApplyStart={studentApplyStart} isStudentApplyDate={isStudentApplyDate} secondStudentApplyDate={secondStudentApplyDate} checkedTaskId={checkedTaskId} checkTime1={checkTime1} checkTime2={checkTime2} checkTime3={checkTime3}/>
<Home period={period} round={round} {...propsF} {...props} studentApplyStart={studentApplyStart} isStudentApplyDate={isStudentApplyDate} secondStudentApplyDate={secondStudentApplyDate} checkedTaskId={checkedTaskId} checkTime1={checkTime1} checkTime2={checkTime2} checkTime3={checkTime3} glccSettings={glccSettings}/>
)}
></Route>
@ -277,7 +281,7 @@ const Glcc = (propsF) => {
<Route
path="/glcc"
render={(props) => (
<Home period={period} round={round} {...propsF} {...props} studentApplyStart={studentApplyStart} isStudentApplyDate={isStudentApplyDate} secondStudentApplyDate={secondStudentApplyDate} checkedTaskId={checkedTaskId} checkTime1={checkTime1} checkTime2={checkTime2} checkTime3={checkTime3}/>
<Home period={period} round={round} {...propsF} {...props} studentApplyStart={studentApplyStart} isStudentApplyDate={isStudentApplyDate} secondStudentApplyDate={secondStudentApplyDate} checkedTaskId={checkedTaskId} checkTime1={checkTime1} checkTime2={checkTime2} checkTime3={checkTime3} glccSettings={glccSettings}/>
)}
></Route>