pull代码

This commit is contained in:
谢思 2023-06-01 18:38:19 +08:00
commit 4b5e155fbf
83 changed files with 7750 additions and 777 deletions

18
package-lock.json generated
View File

@ -4665,6 +4665,11 @@
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
},
"cssfilter": {
"version": "0.0.10",
"resolved": "https://registry.npmmirror.com/cssfilter/-/cssfilter-0.0.10.tgz",
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="
},
"cssnano": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz",
@ -7953,7 +7958,7 @@
},
"fsevents": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"optional": true,
"requires": {
@ -19262,7 +19267,7 @@
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
@ -20777,6 +20782,15 @@
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
},
"xss": {
"version": "1.0.14",
"resolved": "https://registry.npmmirror.com/xss/-/xss-1.0.14.tgz",
"integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==",
"requires": {
"commander": "^2.20.3",
"cssfilter": "0.0.10"
}
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@ -100,6 +100,7 @@
"styled-components": "^4.4.1",
"whatwg-fetch": "2.0.3",
"wrap-md-editor": "^0.2.20",
"xss": "^1.0.14",
"xterm": "4.8.1",
"xterm-addon-fit": "0.4.0"
},

File diff suppressed because one or more lines are too long

View File

@ -3268,6 +3268,7 @@ a.user_greybg_btn {
.markdown-body ul>li {
list-style-type: disc !important;
margin-bottom: 0 !important;
}
.new_li .markdown-body ol>li,

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2340181 */
src: url('iconfont.woff2?t=1682485448664') format('woff2'),
url('iconfont.woff?t=1682485448664') format('woff'),
url('iconfont.ttf?t=1682485448664') format('truetype');
src: url('iconfont.woff2?t=1682403222759') format('woff2'),
url('iconfont.woff?t=1682403222759') format('woff'),
url('iconfont.ttf?t=1682403222759') format('truetype');
}
.iconfont {
@ -13,10 +13,6 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-zanting:before {
content: "\e96a";
}
.icon-cangkuyuyanicon:before {
content: "\e962";
}

File diff suppressed because one or more lines are too long

View File

@ -5,13 +5,6 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "887857",
"name": "暂停",
"font_class": "zanting",
"unicode": "e96a",
"unicode_decimal": 59754
},
{
"icon_id": "34965902",
"name": "仓库语言icon",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -31,7 +31,7 @@
<link rel="canonical" href="https://gitlink.org.cn" data-turbo-transient="">
<link rel="fluid-icon" href="https://www.gitlink.org.cn/images/logo.png" title="GitLink">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link href="https://gw.alipayobjects.com/os/lib/alipay/alex/2.0.19/bundle/alex.all.global.min.css" rel="stylesheet"/>
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/alex/alex.all.global.min.css">
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/iconfont.css">
<!-- <link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/edu-purge.css"> -->
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/editormd.min.css">
@ -53,7 +53,7 @@
<script src="%PUBLIC_URL%js/codemirror/codemirror.js"></script>
<script src="%PUBLIC_URL%js/editormd/editormd.min.js"></script>
<script src="%PUBLIC_URL%js/codemirror/merge/merge.js"></script>
<script src="https://gw.alipayobjects.com/os/lib/moment/2.29.4/moment.js"></script>
<script src="%PUBLIC_URL%js/alex/moment.js"></script>
<script src="https://gw.alipayobjects.com/os/lib/alipay/alex/2.0.19/bundle/alex.all.global.min.js"></script>
<%= htmlWebpackPlugin.tags.bodyTags %>
<script>

5685
public/js/alex/moment.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -329,7 +329,7 @@ class App extends Component {
<MuiThemeProvider theme={theme}>
<LoginDialog {...this.props} {...this.state} Modifyloginvalue={() => this.Modifyloginvalue()}></LoginDialog>
{/* <GlccModal /> */}
{!pathName || (pathName && pathName.indexOf("glcc") === -1) ? <SiderBar {...this.props}/> : <SiderBarHelp/>}
{!pathName || (pathName && pathName.toLowerCase().indexOf("glcc") === -1) ? <SiderBar {...this.props}/> : <SiderBarHelp/>}
{/* <Router> */}
<Switch>
{/* wiki预览 */}

View File

@ -4,7 +4,7 @@ import { Spin } from 'antd';
class Loading extends Component {
componentDidUpdate(prevProps, prevState) {
if (!prevProps.error && this.props.error) {
console.log(this.props.error)
// console.log(this.props.error)
}
}

View File

@ -1,14 +1,16 @@
import React, { useEffect, useRef, useMemo } from 'react'
import 'katex/dist/katex.min.css'
import React, { useEffect, useRef, useMemo , useState } from 'react'
import 'katex/dist/katex.min.css';
import marked, { getTocContent, cleanToc, getMathExpressions, resetMathExpressions } from '../common/marked';
import 'code-prettify';
import dompurify from 'dompurify';
import { getEmoji } from '../forge/Main/emoji';
import axios from 'axios';
import { renderToString } from 'katex'
const preRegex = /<pre[^>]*>/g;
const strRegexSub = /:([a-zA-Z_]+):/g;
const quoteRegex = /\[[#][0-9]{0,}\]\(\/[A-Za-z0-9]{0,}\/[A-Za-z0-9]{0,}\/issues\/[0-9]{0,}\)/g;
function _unescape(str) {
let div = document.createElement('div')
div.innerHTML = str
@ -21,8 +23,36 @@ export default ({
value = '',
className,
style = {},
url
url,
owner=undefined,
projectsId=undefined
}) => {
const [ issues , setIssues ] = useState([]);
useEffect(()=>{
if(owner&&projectsId){
getIssueList();
}
},[owner,projectsId])
function getIssueList(){
let matchQuote = str.match(quoteRegex);
let array = [];
if(matchQuote && matchQuote.length>0){
for(var x=0;x<matchQuote.length;x++){
let getIndex = matchQuote[x].match(/#(\S*)\]/)[1];
getIndex && array.push(getIndex);
}
axios.get(`/${owner}/${projectsId}/issues/index_to_name`,{params:{
index:array
}}).then(result=>{
if(result){
setIssues(result.data);
}
})
}
}
let str = String(value);
const html = useMemo(() => {
let rs = marked(str);
@ -38,6 +68,13 @@ export default ({
rs = rs.replace(matchStr[i],getEmoji(matchStr[i]));
}
}
if(owner && projectsId && issues && issues.length>0){
for(var x=0;x<issues.length;x++){
let item = issues[x];
let content = item.id ? `<a href="`+`/${owner}/${projectsId}/issues/${item.project_issues_index}`+`">#${item.project_issues_index}:${item.subject}</a>` : `<span>#${item.project_issues_index}(已删除)</span>`;
rs = rs.replaceAll(`<a href="`+`/${owner}/${projectsId}/issues/${item.project_issues_index}`+`">#${item.project_issues_index}</a>`,content);
}
}
rs = rs.replace(/(__special_katext_id_\d+__)/g, (_match, capture) => {
const { type, expression } = math_expressions[capture];
@ -46,7 +83,7 @@ export default ({
rs = rs.replace(/▁/g, "▁▁▁")
resetMathExpressions()
return dompurify.sanitize(rs)
}, [str]);
}, [str,issues]);
// #id
useEffect(()=>{

View File

@ -263,15 +263,15 @@ class Activity extends Component{
<div class="normalBox-title">项目演化分析</div>
<div className="echartBox">
<span className="echartTitle" style={{marginTop:0}}>开源项目社群激发</span>
<span className="echartTitle" style={{marginTop:0}}>开源项目社群激发演化拓扑</span>
<BranchLine url={ai_shang_v1_url}/>
<p>基于信息熵围绕疑修任务的社群群智激发演化度量</p>
<span className="echartTitle">开源项目代码变更</span>
<span className="echartTitle">开源项目代码变更演化拓扑</span>
<IssueLine url={ai_shang_v3_url}/>
<p>基于信息熵围绕代码提交的软件代码变更演化度量</p>
<span className="echartTitle">开源项目社区演化</span>
<span className="echartTitle">开源项目社区演化拓扑</span>
<SmoothLineTwo url={ai_shang_v4_url}/>
<p>基于信息熵围绕项目社群的社区分裂缩减合并和扩大演化行为度量</p>

View File

@ -41,7 +41,7 @@ function Line({url}) {
yAxis: {
type: 'value',
scale: true,
name: '社\n群\n激\n发\n熵',
name: '社\n群\n激\n发\n演\n化\n拓\n扑\n熵',
min:0,
splitLine : {
show : true,
@ -49,7 +49,7 @@ function Line({url}) {
nameLocation:'end',
nameTextStyle:{
fontSize:14,
padding:[0,70,-80,0]
padding:[0,70,-120,0]
}
},
dataZoom: [

View File

@ -43,7 +43,7 @@ function Line({url}) {
yAxis: {
type: 'value',
scale: true,
name: '代\n码\n变\n更\n熵',
name: '代\n码\n变\n更\n演\n化\n拓\n扑\n熵',
min:0,
splitLine : {
show : true,
@ -51,7 +51,7 @@ function Line({url}) {
nameLocation:'end',
nameTextStyle:{
fontSize:14,
padding:[0,70,-80,0]
padding:[0,70,-120,0]
}
},
dataZoom: [

View File

@ -53,7 +53,7 @@ function SmoothLineTwo({url}) {
yAxis: {
type: 'value',
scale: true,
name: '社\n区\n演\n化\n熵',
name: '社\n区\n演\n化\n拓\n扑\n熵',
min:0,
splitLine : {
show : true,
@ -61,7 +61,7 @@ function SmoothLineTwo({url}) {
nameLocation:'end',
nameTextStyle:{
fontSize:14,
padding:[0,70,-80,0]
padding:[0,70,-120,0]
}
},
dataZoom: [

View File

@ -311,22 +311,7 @@ class NewHeader extends Component {
{
settings.navbar && settings.navbar.map((item, key) => {
var new_link = item.link;
// var user_login = current_user && current_user.login;
var is_hidden = item.hidden;
// if (new_link && (new_link.indexOf("courses") > -1 || new_link.indexOf("contests") > -1)) {
// if (user_login) {
// if (new_link.indexOf("courses") > -1) {
// new_link = new_link.replace(/courses/g, user_login + "/courses")
// } else if (new_link.indexOf("contests") > -1) {
// new_link = new_link.replace(/contests/g, user_login + "/contests")
// }
// } else {
// is_hidden = true
// }
// }
// if (user_login && (new_link && new_link.indexOf("homes") > -1)) {
// new_link = new_link.replace(/homes/g, user_login + "/user_activities")
// }
var waiLian = (new_link && str.filter(item=>new_link.indexOf(item)>-1) );
var wl = waiLian && waiLian.length>0;
return (

View File

@ -115,6 +115,7 @@ function EditComment(props){
initValue={content}
onChange={(value)=>{setQuillFlag(false);setContent(value);}}
isCanAtme = {true}
isQuoteIssue={true}
changeAtWhoLoginList = {(loginList)=>{setAtWhoLoginList(loginList); setAttachmentClean(true)}}
owner = {owner}
projectsId = {projectsId}

View File

@ -89,7 +89,7 @@ function IssueCommentList(props){
}, [reload, category, page])
function commentCtx(v){
return <RenderHtml className="break_word_comments imageLayerParent commentRenderHtml" value={v} url={location}/>;
return <RenderHtml owner={owner} projectsId={projectsId} className="break_word_comments imageLayerParent commentRenderHtml" value={v} url={location}/>;
};
//

View File

@ -6,7 +6,7 @@ import Attachments from "../../Upload/attachment";
import UploadImg from '../Img/UploadImg.png';
function NewPanel(props,ref){
const [ description , setDescription ] = useState("");
const [ description , setDescription ] = useState(undefined);
const [ fileList , setFileList] = useState(undefined);
const [ attachments , setAttachments ] = useState([]);
const [ save , setSave ] = useState([]);
@ -15,12 +15,11 @@ function NewPanel(props,ref){
const { createFunc , title , desc , files , onCancel , owner , projectsId } = props;
const { form: { getFieldDecorator, validateFields , setFieldsValue } } = props;
useState(()=>{
title && setTimeout(()=>{
setFieldsValue({subject:title});
desc && onContentChange(desc);
},100)
desc && setDescription(desc);
files && setAttachments(files);
files && setSave(files);
},[title , desc , files])
@ -84,11 +83,12 @@ function NewPanel(props,ref){
<MDEditor
placeholder={"请输入描述信息"}
height={392}
mdID={"order-new-description"}
mdID={"issue-edit-description"}
initValue={description}
onChange={onContentChange}
className="mt20"
isCanAtme = {true}
isQuoteIssue={true}
owner = {owner}
projectsId = {projectsId}
changeAtWhoLoginList = {changeAtWhoLoginList}

View File

@ -32,6 +32,7 @@ function Details(props){
const [ tag , setTag ] = useState(undefined);
const [ branchList , setBranchList ] = useState(undefined);
const [ branch , setBranch ] = useState(undefined);
const [ desc , setDesc ] = useState(undefined);
const [ orderId , setOrderId ] = useState(undefined);
@ -91,6 +92,8 @@ function Details(props){
let per = data && !data.user_permission;
setEditFlag(per);
setDesc(data && data.description);
const aut = current_user && data.author && (data.author.login === current_user.login);
const con = data.pull_fixed === false && aut;
setAmountEditFlag(con);
@ -277,10 +280,10 @@ function Details(props){
if(result){
window.scrollTo(0,0);
props.showNotification("疑修更新成功!");
//
setCommentReload(Math.random());
Init();
setEdit(false);
//
setCommentReload(Math.random());
}
}).catch(error=>{})
}else{
@ -318,6 +321,7 @@ function Details(props){
props.history.push(`/${owner}/${projectsId}/issues/${index}`);
}else{
setEdit(false);
window.scrollTo(0,0);
}
}
@ -367,7 +371,7 @@ function Details(props){
{...props}
onCancel={onCancel}
title={details.subject}
desc={details.description}
desc={edit ? desc : undefined}
files={copy ? undefined : details.attachments}
createFunc={createFunc}
owner = {owner} projectsId = {projectsId}
@ -405,8 +409,8 @@ function Details(props){
}
</div>
<div class="descPanel">
{details.description ?
<RenderHtml className="break_word_comments imageLayerParent" value={details.description} url={props.history.location} />
{desc ?
<RenderHtml owner={owner} projectsId={projectsId} className="break_word_comments imageLayerParent" value={desc} url={props.history.location} />
:
<span className="color-grey-9 ml3 mr3">暂无描述</span>
}

View File

@ -223,6 +223,10 @@ function CoderDepot(props){
type
}
}).then((result) => {
if(result.data.status === -2){
props.showNotification(result.data.message);
props.history.push(`/${owner}/${projectsId}/tree/${turnbar(ref || branchName)}`)
}
if (result) {
let en = result.data.entries;
if(en.type){

View File

@ -6,6 +6,8 @@ import Meditor from "../Newfile/m_editor";
import RenderHtml from "../../components/render-html";
import ReadmeCatelogue from "./sub/ReadmeCatelogue";
import { Base64 } from "js-base64";
import ReplaceModal from "./Modal/ReplaceModal";
import {returnbar , turnbar} from 'educoder';
const $ = window.$;
function bytesToSize(bytes) {
@ -23,7 +25,9 @@ class CoderRootFileDetail extends Component {
language: undefined,
languages: undefined,
description: props.detail.replace_content,
menuList:undefined
menuList:undefined,
replaceVisible:false,
loading:false
};
}
@ -212,6 +216,49 @@ class CoderRootFileDetail extends Component {
}
}
//------------- 替换原始文件相关
showReplaceBox=()=>{
this.setState({
replaceVisible:true
})
}
replaceSure=(e)=>{
this.setState({
loading:true
})
const { projectsId, owner , branchName } = this.props.match.params;
const { detail } = this.props;
const url = `/${owner}/${projectsId}/replace_file.json`;
const path = `/${detail.path && detail.path.replace(detail.name,"")}${e.fileName}`;
const msg = detail.commit && detail.commit.message;
const params={
filepath: returnbar(path.substr(1)),
base64_filepath:Base64.encode(returnbar(path.substr(1))),
branch: returnbar(branchName),
content:Base64.encode(e.fileContent),
message: msg,
delete_file:{
filepath: detail.path,
base64_filepath:Base64.encode(detail.path),
branch:branchName,
sha: detail.sha,
}
};
axios.post(url, {
...params
}).then(result=>{
if(result && result.status === 200){
this.setState({
replaceVisible:false,
loading:false
})
this.props.history.push(`/${owner}/${projectsId}/tree/${turnbar(branchName)}/${turnbar(path.substr(1))}`);
}
}).catch(error=>{})
}
render() {
const {
detail,
@ -221,15 +268,22 @@ class CoderRootFileDetail extends Component {
currentBranch,
platform,
md,
type
type,
} = this.props;
// 文件只读状态
const readOnly = this.props.history.location.search.indexOf('edit') === -1
const { language, languages, description } = this.state;
const { language, languages, description , replaceVisible , loading } = this.state;
let flag = current_user && current_user.login && (isManager || isDeveloper);
const Option = Select.Option;
return (
<React.Fragment>
<ReplaceModal
visible={replaceVisible}
onCancel={()=>{this.setState({replaceVisible:false})}}
onOk={this.replaceSure}
showNotification={this.props.showNotification}
loading={loading}
/>
<Anchor className="griditemAnchor" offsetTop={58}>
<div className="griditemCate">
{
@ -318,10 +372,15 @@ class CoderRootFileDetail extends Component {
<img alt="" src={detail.download_url} style={{ maxWidth: "80%" }} />
</div>
) : detail.direct_download ? (
<div className="mt20 text-center">
<div className="pt10 text-center pb10">
<a onClick={() => this.DownLoadFile(detail.download_url)} className="color-blue font-15">
下载原始文件
</a>
{
flag ?
<a onClick={this.showReplaceBox} className="color-blue font-15" style={{marginLeft:"120px"}}>替换原始文件</a>
:""
}
</div>
) : (
md && readOnly ?

View File

@ -519,7 +519,6 @@ class Detail extends Component {
}
textFunc = (forked_from_project_id, fork_info) => {
let type = fork_info && fork_info.fork_project_user_type;
return forked_from_project_id && fork_info ?
<div className="color-grey-9 df">
<span>复刻自</span>

View File

@ -547,4 +547,8 @@
line-height: 24px;
color: $primary-color;
}
}
.replaceStyle{
height: 140px!important;
}

View File

@ -0,0 +1,66 @@
import React , { useState , useRef } from 'react';
import { Button, message, Modal } from 'antd';
import Upload from "../../Upload/read";
import UploadImg from "../../Images/upload.png";
import '../Index.scss';
function ReplaceModal(props){
const { visible , onCancel , onOk , showNotification , loading } = props;
const [ content , setContent ] = useState(undefined);
const [ uploadTip , setUploadTip ] = useState(false);
const DelRef = useRef(null);
function onClose(){
setContent(undefined);
onCancel();
}
function UploadFunc(e){
setContent(e);
if(e){
setUploadTip(false);
}else{
setUploadTip(true);
}
}
function onSure() {
if(!uploadTip && content){
onOk && onOk(content);
}else{
setUploadTip(true);
}
}
return(
<Modal
title={"替换文件"}
width="500px"
visible={visible}
onCancel={onCancel}
footer={
<div>
<Button onClick={onClose}>取消</Button>
<Button onClick={onSure} type={"primary"} className="ml20" loading={loading}>确定</Button>
</div>
}
>
<Upload
className="replaceStyle"
load={UploadFunc}
ref={DelRef}
icon={
<img
src={UploadImg}
width="58"
alt=""
style={{ marginBottom: 15 }}
/>
}
size={5}
showNotification={showNotification}
/>
<p className="color-red">{uploadTip ? "请上传文件!":""}</p>
</Modal>
)
}
export default ReplaceModal;

View File

@ -66,6 +66,10 @@ function Files({ data,history,owner,projectsId , parentsSha }){
</div>
)
function subStrContent(content){
return content ? content.slice(1) :"";
}
return(
<div onClick={(e)=>{e.nativeEvent.stopImmediatePropagation()}}>
<AlignCenter className="color-grey-9" style={{position:'relative'}}>
@ -127,7 +131,10 @@ function Files({ data,history,owner,projectsId , parentsSha }){
<span>{item.leftIdx && item.leftIdx !=="0" ? item.leftIdx :"" }</span>
<span>{item.rightIdx && item.rightIdx !=="0" ? item.rightIdx :"" }</span>
</span>
<p>{item.content}</p>
<p style={{display:"flex"}}>
<span className="linetype">{item.type===2 ? "+" : item.type===3 ? "-" :""}</span>
{ (item.type===3 || item.type===2) ? subStrContent(item.content) : item.content}
</p>
</div>
)
})

View File

@ -112,4 +112,9 @@
.mergeBox{
margin:0px auto;
width: 1200px;
}
.linetype{
width:30px;
text-align: center;
display: inline-block;
}

View File

@ -193,7 +193,6 @@ form .ant-cascader-picker, form .ant-select {
width: 100px;
padding: 0px 8px;
box-sizing: border-box;
margin-right: 8px;
}
.linesContent .lines > span:first-child{
margin-right: 0px;

View File

@ -398,7 +398,7 @@ class Index extends Component {
<Input placeholder="请输入需要导入到本项目的仓库地址" onBlur={this.ChangeAddr} />
)}
</Form.Item>
<p className="formTip color-orange">示例https://github.com/facebook/reack.git</p>
<p className="formTip color-orange">示例https://github.com/Stability-AI/StableStudio.git</p>
</React.Fragment>
}
{

View File

@ -143,7 +143,7 @@ class UserSubmitComponent extends Component {
if (result.data && result.data.status === 1) {
let b = currentBranch || branch;
window.scrollTo(0,0);
let url = `/${owner}/${projectsId}${(values.branchname ? `/tree/${turnbar(values.branchname)}` : (b ? `/tree/${turnbar(b)}`:""))}`;
let url = `/${owner}/${projectsId}${(values.branchname ? `/tree/${turnbar(values.branchname)}` : (b ? `/tree/${turnbar(b)}`:""))}${detail.path ? `/${returnbar(detail.path)}` : ""}`;
this.props.history.push(url);
this.props.showNotification("文件修改成功!");
}

View File

@ -16,7 +16,7 @@ import IDEPlugin, { ExtensionCommand } from './plugins/idePlugin';
import { repoService } from './codeReview/mock/repo.service';
function CloudIDE({ download_url, params: { owner, projectsId, branchName }, filepath }) {
function CloudIDE({ download_url, params: { owner, projectsId, branchName }, filepath , value }) {
function FileContentStore() {
const [loading, setLoading] = useState(true);
const [showIde, setShowIde] = useState(false);
@ -35,8 +35,7 @@ function CloudIDE({ download_url, params: { owner, projectsId, branchName }, fil
};
}
const { fileSize, fileName } =
FileContentStore();
const { fileSize, fileName } = FileContentStore();
const fileCache = new Map();
const copyFile = () => {
@ -92,12 +91,11 @@ function CloudIDE({ download_url, params: { owner, projectsId, branchName }, fil
// blame
const onBlame = async (e) => {
setBlameChecked(e.target.checked);
console.log('onBlame:', e.target.checked);
if (!e.target.checked) {
plugin.current.commands.executeCommand(ExtensionCommand.toggleBlame);
if (!e.target.checked ) {
plugin.current.commands && plugin.current.commands.executeCommand(ExtensionCommand.toggleBlame);
} else {
const res = await repoService.getCodeBlame(projectsId, owner, { sha:decodeURIComponent(branchName), filepath })
plugin.current.commands.executeCommand(ExtensionCommand.toggleBlame, res);
plugin.current.commands && plugin.current.commands.executeCommand(ExtensionCommand.toggleBlame, res);
}
};
const [wordWrap, setWordWrap] = useState(false);
@ -194,89 +192,92 @@ function CloudIDE({ download_url, params: { owner, projectsId, branchName }, fil
wordWrap={wordWrap}
setWordWrap={onWordWrapChange}
/> */}
<EditorRenderer
appConfig={{
//
plugins: [changeThemePlugin, plugin.current],
workspaceDir: `${owner}/${projectsId}`,
defaultPreferences: {
// opensumi-dark
// 'general.theme': 'opensumi-light',
'general.theme': 'opensumi-light',
//
'editor.forceReadOnly': true,
//
'editor.scrollBeyondLastLine': false,
},
extensionMetadata: [
html,
css,
json,
typescript,
markdown,
ideTheme,
CodeBlame],
}}
runtimeConfig={{
//
biz: 'gitlink',
// editor null
scenario: null,
// editor none
startupEditor: 'none',
// editor tab
hideEditorTab: true,
}}
editorConfig={{
stretchHeight: true,
disableEditorSearch: true,
}}
//
documentModel={{
// code code
type: 'code',
// tag
ref: branchName,
//
owner: owner,
//
name: projectsId,
//
filepath: filepath,
//
readFile: async (filepath) => {
if (!filepath) {
return;
}
//
// const res = await fetch('https://gist.githubusercontent.com/gaearon/0b180827c190fe4fd98b4c7f570ea4a8/raw/b9157ce933c79a4559d2aa9ff3372668cce48de7/LikeButton.js');
const res = await fetch(`${getUrl()}/attachments/entries/get_file?download_url=${download_url}`);
setTimeout(()=>{
onWordWrapChange(localStorage.getItem('code.file.viewer.worlWrap') === 'on');
},5000)
return res.arrayBuffer();
},
// utf8 gbk
// encoding: 'utf8',
encoding,
onFilepathChange: (changedFilepath) => {
if (changedFilepath) {
console.log(`路由跳转到 ${changedFilepath}`)
}
},
//
// lineNumber: [0, 1],
onLineNumberChange: line => {
if (typeof line === 'number') {
console.log(`#L${line}`)
} else {
console.log(`#L${line[0]}-${line[1]}`)
}
},
}}
/>
{
value ?
<EditorRenderer
appConfig={{
//
plugins: [changeThemePlugin, plugin.current],
workspaceDir: `${owner}/${projectsId}`,
defaultPreferences: {
// opensumi-dark
// 'general.theme': 'opensumi-light',
'general.theme': 'opensumi-light',
//
'editor.forceReadOnly': true,
//
'editor.scrollBeyondLastLine': false,
},
extensionMetadata: [
html,
css,
json,
typescript,
markdown,
ideTheme,
CodeBlame],
}}
runtimeConfig={{
//
biz: 'gitlink',
// editor null
scenario: null,
// editor none
startupEditor: 'none',
// editor tab
hideEditorTab: true,
}}
editorConfig={{
stretchHeight: true,
disableEditorSearch: true,
}}
//
documentModel={{
// code code
type: 'code',
// tag
ref: branchName,
//
owner: owner,
//
name: projectsId,
//
filepath: filepath,
//
readFile: async (filepath) => {
if (!filepath) {
return;
}
//
// const res = await fetch('https://gist.githubusercontent.com/gaearon/0b180827c190fe4fd98b4c7f570ea4a8/raw/b9157ce933c79a4559d2aa9ff3372668cce48de7/LikeButton.js');
const res = await fetch(`${getUrl()}/attachments/entries/get_file?download_url=${download_url}`);
setTimeout(()=>{
onWordWrapChange(localStorage.getItem('code.file.viewer.worlWrap') === 'on');
},5000)
return res.arrayBuffer();
},
// utf8 gbk
// encoding: 'utf8',
encoding,
onFilepathChange: (changedFilepath) => {
if (changedFilepath) {
console.log(`路由跳转到 ${changedFilepath}`)
}
},
//
// lineNumber: [0, 1],
onLineNumberChange: line => {
if (typeof line === 'number') {
console.log(`#L${line}`)
} else {
console.log(`#L${line[0]}-${line[1]}`)
}
},
}}
/>:
<div className="pl20">空文件</div>
}
</Fragment>
)
}

View File

@ -34,6 +34,7 @@ class m_editor extends Component {
render() {
const { editorValue, changeValue } = this.state;
console.log("editorValue:",editorValue)
const { readOnly, editorType,onEmpty,filename,empty, currentBranch, descName, checkName, detail, match: { params },filepath,ideTheme } = this.props;
const editor_options = {
lineNumbers: "on",
@ -68,7 +69,7 @@ class m_editor extends Component {
options={editor_options}
onChange={this.changeEditor}
/> :
<CloudIDE download_url={download_url} params={params} filepath={filepath&&filepath.startsWith('/')?filepath.slice(1):filepath}/>
<CloudIDE download_url={download_url} value={editorValue} params={params} filepath={filepath&&filepath.startsWith('/')?filepath.slice(1):filepath}/>
}
</div>
{!readOnly && (

View File

@ -29,6 +29,7 @@ class Setting extends Component {
CategoryList: undefined,
LanguageList: undefined,
private_check: undefined,
prview_check:undefined,
loading:true,
project_units:['home',"activity","code"],
divertVisible:false,
@ -93,6 +94,7 @@ class Setting extends Component {
this.setState({
projectName:result.data.project_name,
private_check: result.data.private,
prview_check:result.data.pr_view_admin,
loading:false,
project_units:units,
transfer:result.data.transfer,
@ -224,6 +226,11 @@ class Setting extends Component {
});
};
changePrview=(e) => {
this.setState({
prview_check: e.target.checked,
});
};
// 转移仓库
DivertProject=()=>{
this.setState({
@ -266,7 +273,7 @@ class Setting extends Component {
const { projectsId , owner } = this.props.match.params;
const { projectDetail } = this.props;
const { CategoryList, LanguageList, private_check ,loading , divertVisible , is_transfering, transfer } = this.state;
const { CategoryList, LanguageList, private_check , prview_check ,loading , divertVisible , is_transfering, transfer } = this.state;
let mirror = projectDetail && projectDetail.mirror;
let type = projectDetail && projectDetail.type;
const forked_from_project_id = this.props && this.props.projectDetail && this.props.projectDetail.forked_from_project_id;
@ -361,6 +368,26 @@ class Setting extends Component {
</Select>
)}
</Form.Item>
<span className="mb15">访问权限:</span>
<div className="df" style={{ alignItems: "center" }}>
<span className="mr20 mb15 font-16">合并请求(PR)</span>
<Form.Item label="">
{getFieldDecorator("pr_view_admin", {
rules: [],
})(
<Checkbox
checked={prview_check}
onChange={this.changePrview}
disabled={forked_from_project_id}
>
<span className="color-grey-3">仅管理员可见</span>
{/* <span className="color-grey-6">
合并请求(PR)
</span> */}
</Checkbox>
)}
</Form.Item>
</div>
<Form.Item label="项目导航">
{getFieldDecorator("project_units", {
rules: [],

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { Button, Collapse , Tooltip , Spin } from 'antd';
import { Button, Collapse , Tooltip , Spin , Pagination } from 'antd';
import axios from 'axios';
import Content from './historyContent';
import Fault from '../images/fault.png';
@ -8,21 +8,30 @@ const { Panel } = Collapse;
function PushHistory({id,owner,projectsId,showNotification}) {
const [ list , setList ] = useState(undefined);
const [ isSpin , setIsSpin ] = useState(false);
const [ page , setPage ] = useState(1);
const [ total , setTotal ] = useState(0);
const pageSize = 10;
useEffect(()=>{
if(id && owner && projectsId){
Init();
Init(1);
}
},[id,owner,projectsId])
function Init() {
useEffect(()=>{
setList(undefined)
Init(page);
},[page])
function Init(page) {
const url = `/${owner}/${projectsId}/webhooks/${id}/tasks.json`;
axios.get(url,{
params:{page:1,limit:10}
params:{page:page,limit:pageSize}
}).then(result=>{
if(result && result.data){
setList(result.data.tasks);
setIsSpin(false);
setTotal(result.data.total_count);
}
}).catch(error=>{})
}
@ -49,7 +58,7 @@ function PushHistory({id,owner,projectsId,showNotification}) {
</span>
</div>
{
list && list.length>0 &&
list && list.length>0 ?
<Collapse accordion bordered={false} className="historyColl">
{
list.map((i,k)=>{
@ -75,6 +84,15 @@ function PushHistory({id,owner,projectsId,showNotification}) {
})
}
</Collapse>
:
<div style={{height:"470px",display:"flex",alignItems:"center",justifyContent:"center"}}>
<Spin />
</div>
}
{
total > pageSize ?
<div style={{padding:"15px 0px",textAlign:"right"}}><Pagination size="small" showQuickJumper pageSize={pageSize} current={page} total={total} onChange={(p)=>setPage(p)}/></div>
:""
}
</div>
)

View File

@ -1,12 +1,12 @@
import React from 'react';
import { Link } from 'react-router-dom';
export default (({ name , count , bottom , children , url })=>{
export default (({ name , icon , count , bottom , children , url })=>{
return(
<div className="box">
<div className="head">
<span className="font-16">{name}</span>
<Link to={url}>{count}<i className="iconfont icon-youjiantou font-12 ml3"></i></Link>
<span className="font-17">{icon}{name}{count && `(${count})`}</span>
{ url && <Link to={url} style={{color:"rgba(31, 35, 41, 1)"}}>更多<i className="iconfont icon-youjiantou font-12 ml3"></i></Link>}
</div>
<div className="content">
{children}

View File

@ -5,9 +5,7 @@ import { getUploadActionUrl } from 'educoder';
function UploadImage({ getImage , url, getImageId }){
const [ imageUrl , setImageUrl ] = useState(undefined);
useEffect(()=>{
if(url){
setImageUrl(url);
}
setImageUrl(url);
},[url])
function getBase64(img, callback) {
@ -33,7 +31,6 @@ function UploadImage({ getImage , url, getImageId }){
//
function handleChange(info){
if(info && info.file && info.file.status === "done"){
console.log('info', info);
getImageId && getImageId(info.file.response.id);
getBase64(info.file.originFileObj, imageUrl =>
setImageUrl(imageUrl)
@ -49,6 +46,7 @@ function UploadImage({ getImage , url, getImageId }){
action={getUploadActionUrl()}
beforeUpload={beforeUpload}
onChange={handleChange}
accept="image/*"
>
{
imageUrl ?

View File

@ -19,6 +19,10 @@ const New = Loadable({
loading: Loading,
});
const DetailIndex = Loadable({
loader: () => import("./List"),
loading: Loading,
});
const TeamDetailIndex = Loadable({
loader: () => import("./Sub/Detail"),
loading: Loading,
});
@ -61,14 +65,14 @@ export default withRouter(CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(
<Route
path="/:OIdentifier/teams"
render={(p) => {
return <DetailIndex {...props} {...p}/>
return <TeamDetailIndex {...props} {...p}/>
}}
></Route>
{/* 组织成员 */}
<Route
path="/:OIdentifier/members"
render={(p) => {
return <DetailIndex {...props} {...p}/>
return <TeamDetailIndex {...props} {...p}/>
}}
></Route>
@ -77,17 +81,17 @@ export default withRouter(CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(
<Route
path="/:OIdentifier/setting"
render={(p) => (
<DetailIndex {...props} {...p}/>
<TeamDetailIndex {...props} {...p}/>
)}
></Route>
{/* 组织下的项目详情 */}
<Route
path="/:owner/:projectsId"
render={(p) => (
<ProjectDetail {...props} {...p} />
)}
></Route>
path="/:owner/:projectsId"
render={(p) => (
<ProjectDetail {...props} {...p} />
)}
></Route>
{/* 组织详情(包含组织设置) */}
<Route

View File

@ -1,3 +1,47 @@
.top-orz{
background-color:#f9fbfe;
box-shadow:0px 8px 20px rgba(17, 35, 146, 0.06);
.box{
width: 1200px;
display: flex;
margin:0px auto;
align-items: center;
padding:25px 0px 35px;
&>img{
margin-right: 27px;
// max-width: 140px;
height: 112px;
border-radius:4px;
}
.df{
display: flex;
align-items: center;
}
.info-orz{
flex: 1;
.orz-main{
display: flex;
justify-content: space-between;
width: 100%;
align-items: center;
margin-bottom: 14px!important;
height:30px;
}
.orz-name{
font-weight:700;
color:#333333;
font-size:22px;
}
.orz-desc{
color:#4c5b76;
font-size:15px;
line-height:26px;
height: 52px;
word-break: break-all;
}
}
}
}
.teamBox{
margin:10px 0px;
border:1px solid #eee;
@ -40,17 +84,79 @@
margin:0px auto;
padding-top:18px;
}
.list{
.content-orz{
background-color:#f3f5f8;
}
.content-orz .list{
display: flex;
align-items: flex-start;
width: 1200px;
margin:0px auto;
padding-top: 40px;
.list-l{
background-color: #fff;
// background-image: url('./images/left-bc.png');
background-size: 100%;
max-width: 860px;
width: 72%;
margin-bottom: 30px;
border:1px solid #eee;
padding:30px;
.teamnewstatus{
padding-bottom: 20px;
border-bottom: 1px dashed rgba(170, 175, 190, 1);
.teamdesc{
color:#4c5876;
font-size:14px;
line-height:26px;
}
.teamtitle{
height:22px;
line-height:22px;
color:#1f2329;
font-size:16px;
margin:20px auto 14px!important;
}
.teamname{
height:24px;
span{
font-weight:700;
color:#1f2329;
font-size:17px;
}
i{
margin-right: 4px;
color:rgba(31, 35, 41, 1);
}
}
}
.sepment{
margin:30px auto 20px;
.ant-radio-group{
.ant-radio-button-wrapper{
height: 44px;
line-height: 42px;
border:none;
border:1px solid #cccfd2;
text-align: center;
&:first-child{
border-right: none;
}
&:last-child{
border-left: none;
}
span{
display: block;
}
}
.ant-radio-button-wrapper-checked{
background-color:#466aff;
border-color:#466aff;
color: #fff;
}
}
}
.head{
padding:16px 32px;
padding:16px 0px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
@ -70,7 +176,6 @@
}
}
.team{
padding:0px 32px;
min-height: 450px;
.team_project{
padding:22px 0px;
@ -97,6 +202,8 @@
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
word-break: break-all;
line-height: 22px;
}
&:last-child{
border-bottom: none;;
@ -106,31 +213,53 @@
}
.list-r{
width: 28%;
max-width: 340px;
padding-left: 20px;
max-width: 338px;
padding-left:30px;
padding-bottom: 30px;
box-sizing: border-box;
margin-bottom: 30px;
& > div{
border:1px solid #eee;
}
}
.box{
background:rgba(255,255,255,1);
border-radius:5px;
margin-bottom: 20px;
.head{
padding:25px 20px;
padding:25px 0px 15px;
display: flex;
border-bottom: 1px solid #eee;
border-bottom: 1px dashed rgba(170, 175, 190, 1);
justify-content: space-between;
color: #333;
align-items: center;
margin:0px 20px;
a:hover{
color: #466aff!important;
}
}
.content{
padding:13px 0px;
padding:5px 0px;
.progress{
display: flex;
padding:10px 35px 10px 20px;
align-items: center;
&:hover{
background-color: rgba(175, 183, 194, 0.13);
}
}
.teammembers{
display: flex;
align-items: center;
padding:13px 20px;
align-items: flex-start;
padding:10px 20px 7px 20px;
&:hover{
background-color: rgba(175, 183, 194, 0.13);
}
.memberIndex{
display: block;
width:22px;
height:22px;
line-height:22px;
background-color:#466aff;
border-radius:2px;
margin-right: 10px;
text-align: center;
color: #fff;
}
.m-img{
border-radius: 50%;
width:45px;
@ -141,7 +270,14 @@
}
.foot{
padding:15px 20px;
border-top: 1px solid #eee;
text-align: center;
.newBtn{
border-color:#466aff;
border-radius:8px;
color: #466aff;
display: inline-flex;
align-items: center;
}
}
}
}
@ -413,4 +549,16 @@
}
}
}
}
.overviewcontent{
.concentrateUl li{
border-color:#cddbeb!important;
border-radius:4px;
}
.ConcentrateTipImg{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}

View File

@ -7,7 +7,11 @@ import Item from './ListItem';
import Right from './RightBox';
import NoData from '../Nodata';
import CheckProfile from '../Component/ProfileModal/Profile';
import { Menu , Pagination , Dropdown , Spin } from 'antd';
import { Menu , Pagination , Dropdown , Spin , Tabs , Radio } from 'antd';
import ConcentrateProject from '../users/GeneralView/ConcentrateProject';
import { getImageUrl } from 'educoder';
import RenderHtml from "../../components/render-html";
import EmptyImg from '../Images/nodata.png';
import axios from 'axios';
const limit = 15;
@ -18,9 +22,27 @@ function List(props){
const [ search , setSearch ] = useState(undefined);
const [ page , setPage ] = useState(1);
const [ sortBy , setSortBy ] = useState("updated_on");
const [ organizeDetail , setOrganizeDetail ] = useState(undefined);
const [ mode , setMode ] = useState("overview");
const OIdentifier = props.match.params.OIdentifier;
const organizeDetail = props.organizeDetail;
const { current_user } = props;
console.log(props);
useEffect(()=>{
if(OIdentifier){
getDetail(OIdentifier);
}
},[OIdentifier]);
function getDetail(id) {
const url = `/organizations/${id}.json`;
axios.get(url).then(result=>{
if(result && result.data){
setOrganizeDetail(result.data);
}
}).catch(error=>{})
}
useEffect(()=>{
if(organizeDetail){
@ -31,11 +53,11 @@ function List(props){
},[organizeDetail])
useEffect(()=>{
if(OIdentifier){
if(OIdentifier && mode==="subject"){
setIsSpin(true);
getProject();
}
},[OIdentifier,sortBy,page,search])
},[OIdentifier,sortBy,page,search,mode])
function getProject(){
const url = `/organizations/${OIdentifier}/projects.json`;
@ -76,55 +98,136 @@ function List(props){
</li>
</ul>
)
function changeRadio(e){
setMode(e.target.value);
}
return(
<div className="list">
<div className="list-l">
<div>
<div className="head">
<div style={{width:"370px"}}>
<Search placeholder="输入仓库名称进行搜索" onSearch={onSearch}/>
</div>
<p>
{ organizeDetail && organizeDetail.can_create_project ?
<Sort menu={menu_new} overlayClassName={"newPopUl"}>
<a className="addBtn mr30">+&nbsp;新建项目</a>
</Sort>
:""}
<Dropdown overlay={menu}>
<a className="color-blue">排序<i className="iconfont icon-sanjiaoxing-down ml3 font-14"></i></a>
</Dropdown>
</p>
</div>
<Spin spinning={isSpin}>
<div className="team">
{
list && list.length>0 ? list.map((item,key)=>{
return(
<Item item={item} keu={key} OIdentifier={OIdentifier}/>
)
})
:
<NoData _html="暂无数据"/>
}
</div>
</Spin>
organizeDetail ?
<div className="content-orz">
<div className="top-orz">
<div className="box">
<img src={getImageUrl(organizeDetail && organizeDetail.avatar_url)} alt=""/>
<div className="info-orz">
<p className="orz-main">
<span className="orz-name">{organizeDetail && organizeDetail.nickname}</span>
{organizeDetail && organizeDetail.is_admin ?
<Link to={`/${OIdentifier}/setting`} className="color-blue ml10 font-14 df"><i className="iconfont icon-shezhi2"></i>设置</Link>
:""}
</p>
<div className="orz-desc task-hide-2">{organizeDetail && organizeDetail.description}</div>
</div>
</div>
</div>
<div className="list">
<div className="list-l">
{
totalCount > limit &&
<div className="mb20 mt20" style={{textAlign:"center"}}>
<Pagination simple current={page} total={totalCount} pageSize={limit} onChange={(page)=>setPage(page)}/>
organizeDetail.news_title &&
<div className="teamnewstatus">
<p className="teamname"><i className="iconfont icon-dongtaiicon1" /><span>新闻动态</span></p>
<p className="teamtitle">
{organizeDetail.news_title}
</p>
<div className="teamdesc">
{organizeDetail.news_content}
</div>
</div>
}
<div className="sepment" onChange={changeRadio} >
<Radio.Group size="large" value={mode}>
<Radio.Button value="overview" style={{width:"130px"}}>概览</Radio.Button>
<Radio.Button value="subject" style={{width:"130px"}}>仓库</Radio.Button>
</Radio.Group>
</div>
{
mode === "overview" &&
<div className="overviewcontent">
<ConcentrateProject
{...props}
title={
<p className="names">
<i className="iconfont icon-jingxuanxiangmu font-17 mr8"></i>
<span className="font-17">精选项目</span>
</p>
}
btn={
<span><i className="iconfont icon-zidingyi font-14 mr5"></i>自定义</span>
}
emptyContent={
<img src={EmptyImg} width="325px" alt=""/>
}
typeFlag={true}
userLogin={OIdentifier}
type="organizations"
current={organizeDetail.is_admin}
/>
{
organizeDetail.memo &&
<div>
<p className="mt30 mb10">
<i className="iconfont icon-zuzhijieshao mr4"></i>
<span className="font-17">组织介绍</span>
</p>
<RenderHtml className="break_word_comments imageLayerParent" value={organizeDetail.memo} url={props.history.location}/>
</div>
}
</div>
}
{
mode === "subject" &&
<div>
<div>
<div className="head">
<div style={{width:"370px"}}>
<Search placeholder="输入仓库名称进行搜索" onSearch={onSearch}/>
</div>
<p>
{ organizeDetail && organizeDetail.can_create_project ?
<Sort menu={menu_new} overlayClassName={"newPopUl"}>
<a className="addBtn mr30">+&nbsp;新建项目</a>
</Sort>
:""}
<Dropdown overlay={menu}>
<a className="color-blue">排序<i className="iconfont icon-sanjiaoxing-down ml3 font-14"></i></a>
</Dropdown>
</p>
</div>
<Spin spinning={isSpin}>
<div className="team">
{
list && list.length>0 ? list.map((item,key)=>{
return(
<Item item={item} keu={key} OIdentifier={OIdentifier}/>
)
})
:
<NoData _html="暂无数据"/>
}
</div>
</Spin>
</div>
{
totalCount > limit &&
<div className="mb20 mt20" style={{textAlign:"center"}}>
<Pagination simple current={page} total={totalCount} pageSize={limit} onChange={(page)=>setPage(page)}/>
</div>
}
</div>
}
</div>
<Right
admin={organizeDetail && organizeDetail.is_admin}
OIdentifier={OIdentifier}
showCompeleteDialog={props.showCompeleteDialog}
completeProfile={props.completeProfile}
history={props.history}
/>
<Right
admin={organizeDetail && organizeDetail.is_admin}
OIdentifier={OIdentifier}
showCompeleteDialog={props.showCompeleteDialog}
completeProfile={props.completeProfile}
history={props.history}
/>
</div>
</div>
:""
)
}
export default List;

View File

@ -5,28 +5,32 @@ import axios from 'axios';
import { getImageUrl } from 'educoder';
import { Link } from 'react-router-dom';
import Nodata from '../Nodata';
import { Progress } from 'antd';
import Right from './images/right.png';
import CheckProfile from '../Component/ProfileModal/Profile';
const Span = styled.span`{
color:#888;
font-size:12px;
font-size:14px;
margin-right:10px;
height:20px;
line-height:20px;
}`
const Align = styled.div`{
display:flex;
aligin:center;
}`
const ListName = styled.div`{
font-size:14px;
font-size:15px;
color:#333;
margin-bottom:8px;
height:18px;
line-height:18px;
}`;
const ColorListName = styled.div`{
color:#5091FF;
font-size:14px;
color:rgba(31, 35, 41, 1);
font-size:15px;
margin-bottom:8px;
height:18px;
line-height:18px;
@ -38,6 +42,7 @@ const Img = styled.img`{
margin-right:12px;
}`
function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,completeProfile }) {
const [ languageData, setLanguageData ] = useState(undefined);
const [ memberData, setMemberData ] = useState(undefined);
const [ groupData, setGroupData ] = useState(undefined);
@ -45,8 +50,32 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet
if(OIdentifier){
getMember(OIdentifier);
getGroup(OIdentifier);
getLanguage(OIdentifier);
}
},[OIdentifier])
function getLanguage(iden){
const url = `/organizations/${iden}/languages.json`;
axios.get(url,{params:{limit:10}}).then(result=>{
if(result && result.data){
let languages = result.data;
let arr = [];
Object.keys(languages).map((item,key)=>{
arr.push({name:item,percent:languages[item].replace("%",""),color:getColor()});
})
setLanguageData(arr);
console.log(arr);
}
}).catch(error=>{})
}
  function getColor(){
let str = "#";
let arr = ["1","2","3","4","4","5","6","7","8","9","a","b","c","d","e","f"];
for(var i=0;i<6;i++){
let num = parseInt(Math.random() * 16);
str+=arr[num];
}
return str;
}
function getMember(iden){
const url = `/organizations/${iden}/organization_users.json`;
@ -66,17 +95,43 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet
}
return(
<div className="list-r">
{
languageData && languageData.length>0 ?
<Box
name="仓库语言"
// count={languageData && languageData.length}
icon={<i className="iconfont icon-cangkuyuyanicon font-17 mr8"></i>}
// url={`/${OIdentifier}/members`}
>
{
languageData.map((item,key)=>{
return(
<div className="progress">
<span className="mr10">{item.name}</span>
<Progress percent={item.percent} strokeColor={item.color}/>
</div>
)
})
}
</Box>
:""
}
{
memberData && memberData.organization_users && memberData.organization_users.length>0 ?
<Box name="组织成员" count={memberData && memberData.total_count} url={`/${OIdentifier}/members`}>
<Box
name="组织成员"
count={memberData && memberData.total_count}
icon={<i className="iconfont icon-zuzhichengyuan1 font-16 mr8"></i>}
url={`/${OIdentifier}/members`}>
{
memberData.organization_users.map((item,key)=>{
return(
<div className="teammembers" key={key}>
key<5 && <div className="teammembers" key={key}>
<Link to={`/${item.user && item.user.login}`}><Img src={getImageUrl(`/${item.user && item.user.image_url}`)} alt="" className="m-img"/></Link>
<div>
<Link to={`/${item.user && item.user.login}`}><ListName>{item.user && item.user.name}</ListName></Link>
<Align><i className="iconfont icon-shijian color-green mr3 font-13"></i><Span>加入时间{item.created_at}</Span></Align>
<Align><Span>加入时间{item.created_at}</Span></Align>
{/* <i className="iconfont icon-shijian color-green mr3 font-13"></i> */}
</div>
</div>
)
@ -88,14 +143,15 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet
<Box
name="组织团队"
count={groupData && groupData.total_count}
icon={<i className="iconfont icon-zuzhituandui font-17 mr8"></i>}
bottom={
admin &&
<CheckProfile
showCompeleteDialog={showCompeleteDialog}
completeProfile={completeProfile}
sureFunc={()=>history.push(`/${OIdentifier}/teams/new`)}
className={"ant-btn ant-btn-primary"}
>新建团队</CheckProfile>
className={"ant-btn newBtn"}
>新建团队<img src={Right} alt="" width="9" className="ml5" /></CheckProfile>
}
url={`/${OIdentifier}/teams`}
>
@ -104,7 +160,8 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet
<React.Fragment>
{groupData.teams.map((item,key)=>{
return(
<div className="teammembers" key={key}>
key < 5 &&<div className="teammembers" key={key}>
<span className="memberIndex">{key+1}</span>
<div>
{
(item.is_admin || item.is_member) ?
@ -113,8 +170,8 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet
<ColorListName>{item.name}</ColorListName>
}
<Align>
<Span>{item.num_users}名成员</Span>
<Span>{item.num_projects}个仓库</Span>
<Span><span style={{color:"rgba(31, 35, 41, 1)"}}>{item.num_users}</span>名成员</Span>
<Span><span style={{color:"rgba(31, 35, 41, 1)"}}>{item.num_projects}</span>个仓库</Span>
</Align>
</div>
</div>

View File

@ -16,6 +16,10 @@ const Member = Loadable({
loader: () => import("./TeamSettingMember"),
loading: Loading,
});
const Index = Loadable({
loader: () => import("./TeamSettingPage"),
loading: Loading,
});
const Common = Loadable({
loader: () => import("./TeamSettingCommon"),
loading: Loading,
@ -39,17 +43,20 @@ export default (( props )=>{
function returnActive (pathname){
let a = 0;
if(pathname === `/${OIdentifier}/setting/member`){
a = 2;
}else if(pathname === `/${OIdentifier}/setting/index`){
a = 1;
}else if(pathname === `/${OIdentifier}/setting/group`){
a = 2;
}else if(pathname === `/${OIdentifier}/setting/hooks`){
a = 3;
}else if(pathname === `/${OIdentifier}/setting/hooks`){
a = 4;
}
return a;
}
const active = returnActive(pathname);
const array = {list:[
{name:'基本设置',icon:"icon-base",href:`/${OIdentifier}/setting`},
{name:'组织首页管理',icon:"icon-huabanfuben",href:`/${OIdentifier}/setting/index`},
{name:'组织成员管理',icon:"icon-zuzhichengyuan",href:`/${OIdentifier}/setting/member`},
{name:'组织团队管理',icon:"icon-zuzhixiangmu",href:`/${OIdentifier}/setting/group`},
// {name:'web',icon:"icon-zhongqingdianxinicon10",href:`/${OIdentifier}/setting/hooks`}
@ -71,6 +78,12 @@ export default (( props )=>{
<Hooks {...props} />
)}
></Route>
<Route
path="/:OIdentifier/setting/index"
render={() => (
<Index {...props} />
)}
></Route>
<Route
path="/:OIdentifier/setting/group"
render={() => (

View File

@ -0,0 +1,97 @@
import React, { useEffect, useState , forwardRef } from 'react';
import { Button, Form , Input } from 'antd';
import Title from '../../Component/Title';
import MDEditor from "../../../modules/tpm/challengesnew/tpm-md-editor";
import UploadImage from '../Component/UploadImage';
import {getImageUrl } from 'educoder';
import axios from 'axios';
export default Form.create()(forwardRef((props)=>{
const { form , organizeDetail } = props;
const OIdentifier = props.match.params.OIdentifier;
const { getFieldDecorator , validateFields , setFieldsValue , getFieldsValue} = form;
const [ content , setContent ] = useState("");
const [ imageUrl , setImageUrl ] = useState("");
const [ imageId , setImageId ] = useState(undefined);
// useEffect(()=>{
// let url = imageId ? getImageUrl(`/api/attachments/${imageId}`) :"";
// setImageUrl(url);
// },[imageId])
useEffect(()=>{
if(organizeDetail){
setContent(organizeDetail.memo);
setFieldsValue({
...organizeDetail
})
setImageId(organizeDetail.news_banner_id);
}
},[organizeDetail])
function onChangeDesc(v){
setContent(v);
}
function save(){
validateFields((error,values)=>{
if(!error){
const url = `/organizations/${OIdentifier}/update_other.json`;
axios.post(url,{
...values,
memo:content,
// news_banner_id:imageId
}).then(result=>{
if(result){
props.showNotification("组织首页更新成功!");
}
}).catch(error=>{})
}
})
}
return(
<div>
<Title>
<span>组织首页管理</span>
</Title>
<Form style={{padding:"20px 30px 30px 30px"}}>
{/* <p></p>
<UploadImage url={imageUrl} getImageId={(id)=>{setImageId(id);}}/>
<p><a className="color-blue" onClick={()=>setImageId("")}>删除</a></p> */}
<Form.Item label="新闻动态原文链接">
{getFieldDecorator("news_url",{
rules:[]
})(
<Input placeholder="请输入新闻动态原文链接" maxLength={50}/>
)}
</Form.Item>
<Form.Item label="新闻动态标题">
{getFieldDecorator("news_title",{
rules:[]
})(
<Input placeholder="请输入新闻动态标题" maxLength={45}/>
)}
</Form.Item>
<Form.Item label="新闻动态内容">
{getFieldDecorator("news_content",{
rules:[]
})(
<Input placeholder="请输入新闻动态内容" maxLength={200}/>
)}
</Form.Item>
<p class="mt30">组织介绍</p>
<MDEditor
placeholder={"请输入组织介绍"}
height={300}
mdID={"teamdescription"}
initValue={content}
onChange={onChangeDesc}
></MDEditor>
<Button type="primary" onClick={save}>确定</Button>
</Form>
</div>
)
})
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

View File

@ -89,6 +89,8 @@ class Index extends Component {
return item.response && item.status==="done" && array.push((item.response.id || (item.response.data && item.response.data.id)));
})
array && array.length>0 && this.props.load(array);
}else{
this.props.load([]);
}
}

View File

@ -17,6 +17,8 @@ class Read extends Component {
fileContent: undefined,
fileName: undefined,
});
const { load } = this.props;
load && load();
};
beforeUpload = (file) => {

View File

@ -5,7 +5,7 @@ import CheckProfile from '../../Component/ProfileModal/Profile';
const { Search } = Input;
const limit = 20;
function ConcentrateBox({ visible , onCancel , onSure , username , choosed , history , showCompeleteDialog , completeProfile }) {
function ConcentrateBox({ visible , onCancel , onSure , type , username , choosed , history , showCompeleteDialog , completeProfile }) {
const [ page , setPage ]= useState(1);
const [ total , setTotal ]= useState(0);
const [ pageSize , setPageSize ] = useState(false);
@ -54,7 +54,7 @@ function ConcentrateBox({ visible , onCancel , onSure , username , choosed , his
},[value])
function getProjectList(p,s) {
const url = `/users/${username}/projects.json`;
const url = `/${type || `users`}/${username}/projects.json`;
Axios.get(url,{
params:{
page:p,limit,is_public: "public",search:s,choosed
@ -147,12 +147,12 @@ function ConcentrateBox({ visible , onCancel , onSure , username , choosed , his
onChange={(e)=>setSearch(e.target.value)}
/>
</div>
<div className="listbox">
<Checkbox.Group value={value} onChange={chooseProject} style={{width:"100%"}}>
<div className="conbox">
<Checkbox.Group value={value} onChange={chooseProject}>
{
copyList && copyList.length >0 && copyList.map((i,k)=>{
return(
<Checkbox value={i.id} disabled={disable && (value.filter(j=>j === i.id).length===0)}>{i.author && i.author.name}/{i.name}</Checkbox>
<Checkbox value={i.id} disabled={disable && (value.filter(j=>j === i.id).length===0)}>{i.author && `${i.author.name}/`}{i.name}</Checkbox>
)
})
}
@ -160,7 +160,7 @@ function ConcentrateBox({ visible , onCancel , onSure , username , choosed , his
list && list.length > 0 && list.map((i,k)=>{
let c = copyList && copyList.length >0 && copyList.filter(j=>j.id === i.id).length !== 0;
return(
!c && <Checkbox value={i.id} disabled={disable && (value.filter(j=>j === i.id).length===0)}>{i.author && i.author.name}/{i.name}</Checkbox>
!c && <Checkbox value={i.id} disabled={disable && (value.filter(j=>j === i.id).length===0)}>{i.author && `${i.author.name}/`}{i.name}</Checkbox>
)
})
}
@ -173,7 +173,7 @@ function ConcentrateBox({ visible , onCancel , onSure , username , choosed , his
<CheckProfile
showCompeleteDialog={showCompeleteDialog}
completeProfile={completeProfile}
sureFunc={()=>{history.push(`/projects/deposit/new`)}}
sureFunc={()=>{history.push(`/projects/deposit/new${type ? `/${username}`:""}`)}}
className="color-blue">新建项目</CheckProfile>
</div>
}

View File

@ -3,8 +3,20 @@ import { FlexAJ , AlignCenter } from '../../Component/layout';
import { Link } from 'react-router-dom';
import axios from 'axios';
import Box from './ConcentrateBox';
import './Index.scss';
function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfile,history}) {
function ConcentrateProject({
userLogin,
current,
showCompeleteDialog,
completeProfile,
history,
type,
title,
typeFlag,
btn,
emptyContent
}) {
const [ list , setList ] = useState(undefined);
const [ visible , setVisible ] = useState(false);
const [ value , setValue ] = useState([]);
@ -14,7 +26,7 @@ function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfi
},[])
function getList() {
const url = `/users/${userLogin}/is_pinned_projects.json`;
const url = `/${type || `users`}/${userLogin}/is_pinned_projects.json`;
axios.get(url).then(result=>{
if(result && result.data){
let p = result.data.projects;
@ -33,7 +45,7 @@ function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfi
if(is_pinned_project_ids && is_pinned_project_ids.length===0){
setValue([]);
}
const url = `/users/${userLogin}/is_pinned_projects/pin.json`;
const url = `/${type || `users`}/${userLogin}/is_pinned_projects/pin.json`;
axios.post(url,{
is_pinned_project_ids
}).then(result=>{
@ -54,29 +66,36 @@ function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfi
completeProfile={completeProfile}
showCompeleteDialog={showCompeleteDialog}
history={history}
type={type}
/>
{
list && list.length>0 &&
<div className="concentrate">
<FlexAJ>
<span className="font-18">精选项目</span>
{ current && <a className="color-blue" onClick={()=>setVisible(true)}>自定义精选项目</a> }
{
title || <span className="font-18">精选项目</span>
}
{ (current && ((btn && list && list.length > 0) || !btn)) &&
<a className="color-blue" onClick={()=>setVisible(true)}>
{btn || "自定义精选项目"}
</a>
}
</FlexAJ>
{
list && list.length>0 &&
<div>
<ul className="concentrateUl">
{
list.map((i,k)=>{
return(
<li key={i.id}>
<Link to={`/${i.author && i.author.login}/${i.identifier}`} className="name">{i.name}</Link>
<Link to={`/${i.author && i.author.login}/${i.identifier}`} className="name task-hide">{i.name}</Link>
<p className="task-hide desc">{i.description}</p>
{/* 项目标签 */}
{i.topics && <div className='viewProListTopics'>
{i.topics && <div className='viewProListTopics mt10'>
{i.topics.map(item=><Link to={`/explore/topic/${item.id}/${encodeURIComponent(item.name)}`} className='viewProListTopic mr15 font-13 task-hide'>{item.name}</Link>)}
</div>}
<AlignCenter>
{ i.category && <span className="tagName">{i.category.name}</span> }
<span className="pariseCount"><i className="iconfont icon-xingzhuang"></i>{i.watchers_count}</span>
{ i.category && !typeFlag && <span className="tagName">{i.category.name}</span> }
<span className="pariseCount ml2"><i className="iconfont icon-xingzhuang"></i>{i.watchers_count}</span>
<span className="forkCount"><i className="iconfont icon-morenfuke_icon1"></i>{i.forked_count}</span>
</AlignCenter>
</li>
@ -85,10 +104,22 @@ function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfi
}
</ul>
</div>
}
</div>
{
(!emptyContent && list && list.length === 0 && current) &&
<div className="ConcentrateTip">
<i className="iconfont icon-tishi2"></i>
你还没有设置精选项目<a onClick={()=>setVisible(true)}>点击设置</a>
</div>
}
{
list && list.length === 0 && current && <div className="ConcentrateTip"><i className="iconfont icon-tishi2"></i>你还没有设置精选项目<a onClick={()=>setVisible(true)}>点击设置</a></div>
emptyContent && list && list.length === 0 ?
<div className="ConcentrateTipImg">
{emptyContent}
<span className="font-15">暂无数据{current && <span><a className="color-blue" onClick={()=>setVisible(true)}>点击设置</a></span>}</span>
</div>
:""
}
</React.Fragment>
)

View File

@ -18,14 +18,16 @@
}
.name{
font-size: 16px;
// color: #4CACFF;
color: $primary-color;
height: 20px;
line-height: 20px;
&:hover{
color: $primary-color-hover;
}
}
.desc{
color: #999;
line-height: 21px;
}
.tagName{
display: block;
@ -36,6 +38,7 @@
height: 22px;
line-height: 22px;
font-size: 13px;
margin-right: 20px;
}
.pariseCount,.forkCount{
i{
@ -43,7 +46,7 @@
margin-right: 4px;
}
color: #999;
margin-left: 20px;
margin-right: 20px;
}
}
}
@ -71,10 +74,27 @@
padding:0px;
padding-bottom: 15px;
}
.listbox{
.conbox{
max-height: 210px;
overflow-y: auto;
padding-left: 30px;
.ant-checkbox-group{
display: flex;
flex-wrap: wrap;
width: 100%;
.ant-checkbox-wrapper{
width: 48%;
margin-left: 0px!important;
display: flex;
align-items: center;
span:last-child{
flex:1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
}
.operateDiv{
padding:20px 30px
@ -84,24 +104,6 @@
padding-top:15px;
cursor: pointer;
}
.ant-checkbox-group{
display: flex;
flex-wrap: wrap;
.ant-checkbox-wrapper{
width: 50%;
margin-left: 0px!important;
display: flex;
.ant-checkbox{
padding-top: 3px;
}
span:last-child{
flex:1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
}
.recentStatic{
padding: 30px 0px;
@ -152,13 +154,13 @@
display: flex;
flex-wrap: wrap;
.viewProListTopic{
background-color:#f3f8ff;
background-color:rgba(70, 106, 255, 0.12);
border-radius: 4px;
max-width: 158px;
padding: 0 10px;
height: 24px;
line-height: 24px;
color: #4c5b76;
color:#466aff;
margin-bottom: 5px !important;
}
}

View File

@ -65,10 +65,11 @@ export function taskList(params) {
}
// 项目id查询项目详情
export function getProjectById(id) {
export function getProjectById(id, params) {
return fetch({
url: `/api/applyInformation/${id}`,
method: 'get'
method: 'get',
params
});
}
@ -212,4 +213,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,244 +40,56 @@ 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>
<div>1项目报名时间为<span className='c000'>4月29日5月30日</span>请在报名截止时间北京时间<span className='c000'>2023年5月30日24点</span>前提交报名信息</div>
<div>2本次GLCC夏令营建议使用GitLink为代码托管平台学生基于GitLink上项目完成编程任务同时也欢迎使用其他代码托管平台的项目参与活动目前GLCC只对夏令营期间使用GitLink托管的项目提供部分资金支持</div>
<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><span className='c000'>5奖金默认由项目方支持请根据课题难度设定金额若您可能无法支撑本课题奖金GLCC组委会将进行资助评审择优资助如未获得资助将下线该课题</span></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;
//
@ -10,6 +11,17 @@ function TaskApply(props) {
{id: 2, name: "中"},
{id: 3, name: "高"}
]
//
const rewardList = [
{id: 12000},
{id: 11000},
{id: 10000},
{id: 9000},
{id: 8000},
{id: 7000},
{id: 6000},
{id: 0},
]
return(
<div className="taskApplyForm">
@ -39,16 +51,19 @@ function TaskApply(props) {
{helper('课题奖金',
'',
`taskReward${taskKey}`,
[{ required: true, message: "请输入课题奖金" },
{pattern: /^[1-9]+[0-9]*]*$/, message: "请输入正整数" },
{ validator: (rule,val,callback) =>{
if(val>=6000 && val<=12000){
callback();
}else{
callback("区间为6000-12000元");
}
}}],
<Input placeholder="请输入课题奖金" disabled = {disabled} className={disabled? "disabledInput":""}/>
[{ required: true, message: "请选择课题奖金" },
// {pattern: /^[1-9]+[0-9]*]*$/, message: "" },
// { validator: (rule,val,callback) =>{
// if(val>=6000 && val<=12000){
// callback();
// }else{
// callback("6000-12000");
// }
// }}
],
<Select placeholder="请根据课题难度选择奖金" disabled = {disabled} className={disabled? "disabledInput":""}>
{rewardList && rewardList.map(item=> {return <Option value={item.id} key={item.id}>{item.id}</Option>})}
</Select>
)}
{helper('导师姓名',
'',

View File

@ -21,7 +21,7 @@ function Help(props) {
projectId: locationStatus ? '1403908' : '546103'
}).then(res => {
if (res && res.message === "200" && Array.isArray(res.data)) {
setFileArrInit(res.data);
setFileArrInit(res.data.filter(item=>item.name !== "_Sidebar"));
if (res.data.length) {
setCheckItem(res.data[0]);
};

View File

@ -16,6 +16,7 @@
border:1px solid;
border-color:#ffffff;
margin-right: 40px;
min-height: 450px;
}
.wiki_title{
cursor: pointer;

View File

@ -19,9 +19,10 @@ import { hasAuditRole } from '../api';
import './index.scss';
// round20221 20232
// period: 0
// period: "repoApply"
// match:{params:{id=2023}}
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=2023}}, period, showLoginDialog, glccSettings, repoPublic} = props;
function goToApply() {
if (isGlccApplyDate) {
if (current_user && current_user.login) {
@ -75,7 +76,7 @@ export default (props) => {
history.push(url);
return
}
showLoginDialog();
showLoginDialog(url);
}
return (
@ -108,7 +109,7 @@ export default (props) => {
{/* 当前glcc */}
{round === 2 && <div className="applyBox">
{/* 项目报名入口 */}
{period === 0 && <a onClick={()=>{gotoCheckLogin(`/glcc/apply`)}} className="apply project" >
{period === "repoApply" && <a onClick={()=>{gotoCheckLogin(`/glcc/apply`)}} className="apply project" >
<div>
<img src={apply1} alt="" className="applyIcon" />
<span className="hover-none"><span className="til">项目报名</span> </span>
@ -116,6 +117,24 @@ export default (props) => {
</div>
<div className="pt6">项目报名开启导师席位虚位以待</div>
</a>}
{/* 查看项目、课题 */}
{repoPublic && <Link to={`/glcc/${id}/projects`} className="apply project" >
<div>
<img src={apply1} alt="" className="applyIcon" />
<span className="hover-none"><span className="til">查看项目</span> </span>
</div>
<div className="pt6">掌握项目课题详细信息</div>
</Link>}
{/* 学生报名6.24结束第一次报名6.29 1:00 - 6.30 24:00第二次报名 */}
{(period === "stuApply" || period === "stuApply1") && <div className="apply" onClick={()=>{gotoCheckLogin(`/glcc/${id}/subjects`)}}>
<div>
<img src={apply2} alt="" className="applyIcon" />
<span className="til">学生报名</span>
</div>
<div className="pt6">选择课题开启您的开源之旅</div>
</div>}
{/* 6.28.-7.1 审核结果仅对导师可见7.1之后对所有用户可见*/}
{/* {resultTime2 && <div className="apply" onClick={goToCheckResult}>
<div>
@ -150,23 +169,6 @@ export default (props) => {
<div className="pt6">结项课题考核结果公示</div>
</Link>} */}
{/* 项目报名 */}
{/* <Link to="/glcc/projects" className="apply project" >
<div>
<img src={apply1} alt="" className="applyIcon" />
<span className="hover-none"><span className="til">查看项目</span> </span>
</div>
<div className="pt6">掌握项目课题详细信息</div>
</Link> */}
{/* 学生报名6.24结束第一次报名6.29 1:00 - 6.30 24:00第二次报名 */}
{/* <div className="apply" onClick={goToStudent}>
<div>
<img src={apply2} alt="" className="applyIcon" />
<span className="til">学生报名</span>
</div>
<div className="pt6">选择课题开启您的开源之旅</div>
</div> */}
{/* 导师审核 */}
{/* {hasRole && new Date().getTime() < new Date('2022/07/01 0:0').getTime() && <div className="apply" onClick={goToCheck}>
<div>
@ -197,8 +199,8 @@ export default (props) => {
</div>}
</div>
</div>
<Lightspot period={period} history={history} current_user={current_user} round={round} showLoginDialog={showLoginDialog}/>
<TimerShaft round={round}/>
<Lightspot id={id} period={period} history={history} current_user={current_user} round={round} showLoginDialog={showLoginDialog}/>
<TimerShaft round={round} glccSettings={glccSettings}/>
<Award />
<News />
<Partner round={round}/>

View File

@ -185,6 +185,7 @@
}
.introduceBox{
display: flex;
align-items: center;
.introduceImg{
width: 32vw;
margin-left: 3vw;

View File

@ -7,14 +7,15 @@ import './index.scss';
function Lightspot(props) {
const {current_user, period, history, round, showLoginDialog} = props;
const {current_user, period, history, round, showLoginDialog, id} = props;
function goToApply(){
const url = period === "repoApply" ? `/glcc/apply` : `/glcc/${id}/subjects`;
if(current_user && !current_user.login){
showLoginDialog();
showLoginDialog(url);
return;
}
history.push(period === 0 ? `/glcc/apply` : `/glcc/subjects`);
history.push(url);
}
return (
@ -26,12 +27,12 @@ function Lightspot(props) {
<div className="teacher-text">
<h3 className="teacher-invite">有兴趣成为GLCC的导师吗</h3>
<div className="teacher-content">想要扩大项目知名度和影响力为开源项目吸引新鲜血液培养长期开发者通过GitLink平台与高校建立连接指导开源新人传授他们的开源文化享受开源的乐趣</div>
{period === 0 && round === 2 ? <div className='applyBut' onClick={goToApply}>立即报名</div> : <div className='applyBut project'><span className='hover-none'>项目报名</span><span className='hover-show'>{period === -1 ? "敬请期待" : "已结束"}</span></div>}
{period === "repoApply" && round === 2 ? <div className='applyBut' onClick={goToApply}>立即报名</div> : <div className='applyBut project'><span className='hover-none'>项目报名</span><span className='hover-show'>已结束</span></div>}
</div>
<div className="teacher-text">
<h3 className="teacher-invite">你是开源新手有兴趣参加GLCC吗</h3>
<div className="teacher-content">想要参与一线开源项目开发熟悉开源社区运作流程接受资深开源软件专家指导获得丰厚奖金和实习机会参加GLCC开启全新的开源之旅</div>
{period === 2 && round === 2 ? <div className='applyBut' onClick={goToApply}>学生报名</div> : <div className='applyBut project'><span className='hover-none'>学生报名</span><span className='hover-show'>{period<2 && round === 2 ? "敬请期待" : "已结束"}</span></div>}
{(period === "stuApply" || period === "stuApply1") && round === 2 ? <div className='applyBut' onClick={goToApply}>学生报名</div> : <div className='applyBut project'><span className='hover-none'>学生报名</span><span className='hover-show'>{period === "repoApply" && round === 2 ? "敬请期待" : "已结束"}</span></div>}
</div>
</div>

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">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

View File

@ -80,7 +80,12 @@ const Glcc = (propsF) => {
const {current_user, showLoginDialog, match:{params:{id}}} = propsF;
// round20221 20232
const [round, setRound] = useState(2);
// glcc repoApply() stuApply()
const [period, setPeriod] = useState(undefined);
// glcc
const [glccSettings, setGLccSetting] = useState(undefined);
//
const [repoPublic, setRepoPublic] = useState(false);
useEffect(()=>{
if(id && id === "2022"){
@ -91,16 +96,26 @@ 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)));
//
const publicRepoTask = res.data.filter(item=>item.name === "repoPublic");
publicRepoTask[0] && setRepoPublic(judge(nowTime, publicRepoTask[0].value, "range"))
//
const filterRepoPublic = res.data.filter(item=>item.name !== "repoPublic");
const periodIndex = filterRepoPublic.findIndex(item=>judge(nowTime, item.value, "range"));
periodIndex !== -1 && setPeriod(filterRepoPublic[periodIndex].name);
}
})
}, [])
//
function judge(nowTime, timeRnge){
const timeArr = timeRnge.split(",").map(item=>{return new Date(item).getTime()});
return nowTime > timeArr[0] && nowTime < timeArr[1]
function judge(nowTime, timeRnge, type){
const timeArr = timeRnge.split(",").map(item=>{
const data = item && item.replaceAll('-', '/');
return new Date(data).getTime()
});
return type === "range" ? nowTime > timeArr[0] && nowTime < timeArr[1] : nowTime > timeArr[0]
}
// 415~520
@ -134,7 +149,7 @@ const Glcc = (propsF) => {
useEffect(()=>{
// (todo)
false && current_user && current_user.login && getStudentApplyInfo({userId: current_user.user_id}).then(response=>{
(period === "stuApply" || period === "stuApply1") && current_user && current_user.login && getStudentApplyInfo({userId: current_user.user_id, round: round}).then(response=>{
if(response && response.message === "success"){
// setData(response.data.rows);
const data = {};
@ -145,10 +160,10 @@ const Glcc = (propsF) => {
item.passStatus && item.mediumExaminationPass && setStudentRegId(item.studentRegId);
})
setApplyTaskId(data);
response.data&&setCancelCount(Number(response.data.cancelCount));
response.data && setCancelCount(Number(response.data.cancelCount));
}
})
},[studentInfoReset, current_user])
},[studentInfoReset, current_user, period])
//
function applyTask(id){
@ -176,11 +191,11 @@ const Glcc = (propsF) => {
<Route
path="/glcc/student/apply/:taskId"
render={(props) => (
<Student {...propsF} {...props} isGlccApplyDate={isGlccApplyDate} setStudentInfoReset={setStudentInfoReset}/>
<Student {...propsF} {...props} round={round} period={period} isGlccApplyDate={isGlccApplyDate} setStudentInfoReset={setStudentInfoReset}/>
)}
></Route>
{/* 开源夏令营报名页面 */}
{period === 0 && <Route
{period === "repoApply" && <Route
path="/glcc/apply"
render={(props) => (
<Apply round={round} {...propsF} {...props}/>
@ -203,14 +218,14 @@ const Glcc = (propsF) => {
<Route
path="/glcc/:id/subjects/detail/:taskId"
render={(props) => (
<TaskDetail {...propsF} {...props} isStudentApplyDate={isStudentApplyDate} secondStudentApplyDate={secondStudentApplyDate} applyTaskId={applyTaskId} applyTask={applyTask} />
<TaskDetail {...propsF} {...props} applyTaskId={applyTaskId} applyTask={applyTask} period={period}/>
)}
></Route>
{/* 项目/课题列表 */}
<Route
path="/glcc/:id/projects"
render={(props) => (
<Project round={round} {...propsF} {...props} isStudentApplyDate={isStudentApplyDate} secondStudentApplyDate={secondStudentApplyDate} applyTaskId={applyTaskId} setStudentInfoReset={setStudentInfoReset} applyTask={applyTask}/>
<Project period={period} round={round} {...propsF} {...props} isStudentApplyDate={isStudentApplyDate} secondStudentApplyDate={secondStudentApplyDate} applyTaskId={applyTaskId} setStudentInfoReset={setStudentInfoReset} applyTask={applyTask}/>
)}
></Route>
@ -218,7 +233,7 @@ const Glcc = (propsF) => {
<Route
path="/glcc/:id/subjects"
render={(props) => (
<Project {...propsF} {...props} isStudentApplyDate={isStudentApplyDate} secondStudentApplyDate={secondStudentApplyDate} applyTaskId={applyTaskId} setStudentInfoReset={setStudentInfoReset} cancelCount={cancelCount} applyTask={applyTask}/>
<Project period={period} round={round} {...propsF} {...props} isStudentApplyDate={isStudentApplyDate} secondStudentApplyDate={secondStudentApplyDate} applyTaskId={applyTaskId} setStudentInfoReset={setStudentInfoReset} cancelCount={cancelCount} applyTask={applyTask}/>
)}
></Route>
@ -266,7 +281,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 repoPublic={repoPublic} period={period} round={round} {...propsF} {...props} studentApplyStart={studentApplyStart} isStudentApplyDate={isStudentApplyDate} secondStudentApplyDate={secondStudentApplyDate} checkedTaskId={checkedTaskId} checkTime1={checkTime1} checkTime2={checkTime2} checkTime3={checkTime3} glccSettings={glccSettings}/>
)}
></Route>
@ -274,7 +289,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 repoPublic={repoPublic} period={period} round={round} {...propsF} {...props} studentApplyStart={studentApplyStart} isStudentApplyDate={isStudentApplyDate} secondStudentApplyDate={secondStudentApplyDate} checkedTaskId={checkedTaskId} checkTime1={checkTime1} checkTime2={checkTime2} checkTime3={checkTime3} glccSettings={glccSettings}/>
)}
></Route>

View File

@ -26,7 +26,7 @@ export default ({ detail, projectId, applyTaskId, isStudentApplyDate, secondStud
{info.registrationTaskList && info.registrationTaskList.length > 0 ? info.registrationTaskList.map((item, index)=>{
return <div className='taskItem mt15' key={index}>
<div className="left">
<div className="taskTitle" onClick={()=>{window.location.href=`/glcc/subjects/detail/${item.id}`}}><Tooltip title={item.taskName}>{item.taskName}</Tooltip></div>
<div className="taskTitle" onClick={()=>{window.location.href=`/glcc/2022/subjects/detail/${item.id}`}}><Tooltip title={item.taskName}>{item.taskName}</Tooltip></div>
<div className='mt15 oneLine leftWidth'>导师姓名: &nbsp;&nbsp;{item.tutorName}</div>
{item.tutorMail && <div className='mb15 email oneLine leftWidth'>邮箱地址: &nbsp;&nbsp;<span><Tooltip title={item.tutorMail}>{item.tutorMail}</Tooltip></span></div>}
</div>

View File

@ -25,7 +25,7 @@ function SliderLeft({list,history}) {
// pathname:'/glcc/projects',
// state:{projectName:projectName.replace(/ /g,'-')}
// })
window.open(`/glcc/projects?projectName=${projectName.replace(/ /g,'')}`);
window.open(`/glcc/2022/projects?projectName=${projectName.replace(/ /g,'')}`);
}
return (

View File

@ -4,13 +4,13 @@ import Nodata from '../../../forge/Nodata';
import { useEffect } from 'react';
import { getProjectById } from '../../api';
export default ({ detail, projectId, applyTaskId, isStudentApplyDate, secondStudentApplyDate, showTask=true, applyTask, id }) => {
export default ({ detail, projectId, applyTaskId, period, showTask=true, applyTask, id, round }) => {
const [info, setInfo] = useState(detail);
useEffect(()=>{
if(!detail && projectId){
// Id
getProjectById(projectId).then(response=>{
getProjectById(projectId, {round}).then(response=>{
if(response && response.message === 'success'){
setInfo(response.data)
}
@ -43,9 +43,9 @@ export default ({ detail, projectId, applyTaskId, isStudentApplyDate, secondStud
{/* 课题申请时间范围内: 申请课题 */}
{/* 第一次报名 */}
{isStudentApplyDate && !(applyTaskId && Object.keys(applyTaskId).includes(item.id.toString())) && <Button type='primary' className='applyBut mr10' onClick={()=>{applyTask(item.id)}}>申请课题</Button>}
{period === "stuApply" && !(applyTaskId && Object.keys(applyTaskId).includes(item.id.toString())) && <Button type='primary' className='applyBut mr10' onClick={()=>{applyTask(item.id)}}>申请课题</Button>}
{/* 第二次报名 锁定状态 */}
{secondStudentApplyDate && !(applyTaskId && Object.keys(applyTaskId).includes(item.id.toString())) && (item.locked ? <Tooltip title={"该课题已有学生入选"}><Button type='primary' className='mr10' disabled>申请课题</Button></Tooltip> : <Button type='primary' className='applyBut mr10' onClick={()=>{applyTask(item.id)}}>申请课题</Button>)}
{period === "stuApply1" && !(applyTaskId && Object.keys(applyTaskId).includes(item.id.toString())) && (item.locked ? <Tooltip title={"该课题已有学生入选"}><Button type='primary' className='mr10' disabled>申请课题</Button></Tooltip> : <Button type='primary' className='applyBut mr10' onClick={()=>{applyTask(item.id)}}>申请课题</Button>)}
{/* 查看课题详情按钮 */}
<Button onClick={()=>{window.open(`/glcc/${id}/subjects/detail/${item.id}`)}} className='lookDetail'>课题详情</Button>

View File

@ -28,7 +28,10 @@ function Project(propsF) {
return(
<div className="glcc_project">
<img className="glcc-banner" src={banner} alt=''></img>
<div className='projectBannerBox'>
<img className="glcc-banner" src={banner} alt=''></img>
<div className='stuApplytimeBox'>学生报名时间: {id === '2023' ? '2023年6月4日-6月21日' : '2022年5月26日-6月24日'}</div>
</div>
<div className='head'>
<Link to={`/glcc/${id}/subjects`} className={!pathname.endsWith('projects') ? 'active' : ''}>课题列表</Link>
<Link to={`/glcc/${id}/projects`} className={pathname.endsWith('projects') ? 'active' : ''}>项目列表</Link>
@ -54,13 +57,13 @@ function Project(propsF) {
<Route
path="/glcc/:id/subjects"
render={(props) => (
<TaskList {...propsF} {...props}/>
<TaskList {...propsF} {...props}/>
)}
></Route>
<Route
path="/glcc/:id/student"
render={(props) => (
<TaskList {...propsF} {...props}/>
<TaskList {...propsF} {...props}/>
)}
></Route>
</Switch>

View File

@ -226,4 +226,21 @@
border-color: #1140ff;
color: #1140ff;
}
}
.projectBannerBox{
position: relative;
.stuApplytimeBox{
background-image: url('../img/studentProject1.png');
position: absolute;
color: #FFDC95;
font-size: 20px;
bottom: 28%;
left: 50%;
margin-left: -250px;
width: 500px;
height: 60px;
line-height: 60px;
text-align: center;
background-size: 100% 100%;
}
}

View File

@ -7,10 +7,11 @@ import ProjectDetail from '../component/projectDetail';
import bgPng from "../../img/bgPng.png";
import logo from "../../img/logo.png";
import star from "../../img/star.png";
import Nodata from '../../../forge/Nodata';
const { Search } = Input;
//
function ProjectList({ applyTaskId, isStudentApplyDate, location, match, secondStudentApplyDate, applyTask, round, match:{params:{id}}}) {
function ProjectList({ applyTaskId, location, match, applyTask, round, period, match:{params:{id}}}) {
let gitlinkLastUrl = match.params.gitlinkLastUrl && match.params.gitlinkLastUrl.replace(/\./g, '');
let openmmlab = location.search && location.search.split('=')[1];
//
@ -62,7 +63,7 @@ function ProjectList({ applyTaskId, isStudentApplyDate, location, match, secondS
}
setLoading(false);
})
}, [keyword])
}, [keyword, round])
function changeVisible(visible, item) {
if (visible) {
@ -84,7 +85,7 @@ function ProjectList({ applyTaskId, isStudentApplyDate, location, match, secondS
onVisibleChange={(visible) => { changeVisible(visible, item) }}
key={index}
placement={(index + 1) % 3 === 0 ? 'bottomRight' : (index + 1) % 3 % 2 === 0 ? 'bottom' : 'bottomLeft'}
content={<ProjectDetail id={id} detail={item} applyTaskId={applyTaskId} isStudentApplyDate={isStudentApplyDate} secondStudentApplyDate={secondStudentApplyDate} applyTask={applyTask}/>}
content={<ProjectDetail id={id} detail={item} applyTaskId={applyTaskId} applyTask={applyTask} period={period} round={round}/>}
trigger='click'
overlayClassName='projectItemPopover'
autoAdjustOverflow={false}
@ -101,6 +102,7 @@ function ProjectList({ applyTaskId, isStudentApplyDate, location, match, secondS
</Popover>
})}
</div>
{data && !data.length && <div style={{paddingBottom: '50px'}}><Nodata _html="暂无数据"/></div>}
</Spin>
</div>
<img src={bgPng} alt='' className='bgPng3' />

View File

@ -7,7 +7,7 @@ import './index.scss';
//
function TaskDetail(props) {
const {match, location, isStudentApplyDate, secondStudentApplyDate, applyTaskId, applyTask, match:{params:{id}}} = props
const {match, location, period, applyTaskId, applyTask, match:{params:{id}}} = props
const taskId = Number(match.params.taskId);
const [detail, setDetail] = useState(undefined);
@ -52,9 +52,9 @@ function TaskDetail(props) {
{/* 课题申请时间范围内: 申请课题 */}
{/* 第一次报名 */}
{isStudentApplyDate && !(applyTaskId && Object.keys(applyTaskId).includes(taskId.toString())) && <Button type='primary' className='applyBut detailBut' onClick={()=>{applyTask(taskId)}}>申请课题</Button>}
{period==="stuApply" && !(applyTaskId && Object.keys(applyTaskId).includes(taskId.toString())) && <Button type='primary' className='applyBut detailBut' onClick={()=>{applyTask(taskId)}}>申请课题</Button>}
{/* 第二次报名 锁定状态 */}
{secondStudentApplyDate && !(applyTaskId && Object.keys(applyTaskId).includes(taskId.toString())) && (detail.locked ? <Tooltip title={"该课题已有学生入选"}><Button type='primary' className='mr10 detailBut' disabled>申请课题</Button></Tooltip> : <Button type='primary' className='applyBut mr10 detailBut' onClick={()=>{applyTask(taskId)}}>申请课题</Button>)}
{period === "stuApply1" && !(applyTaskId && Object.keys(applyTaskId).includes(taskId.toString())) && (detail.locked ? <Tooltip title={"该课题已有学生入选"}><Button type='primary' className='mr10 detailBut' disabled>申请课题</Button></Tooltip> : <Button type='primary' className='applyBut mr10 detailBut' onClick={()=>{applyTask(taskId)}}>申请课题</Button>)}
</div>
<img src={bgPng} alt='' className='bgPng1'/>
<img src={bgPng} alt='' className='bgPng2'/>

View File

@ -7,7 +7,7 @@ import bgPng from "../../img/bgPng.png";
const { Search } = Input;
//
function TaskList({applyTaskId, setStudentInfoReset, current_user, showLoginDialog, isStudentApplyDate, secondStudentApplyDate,cancelCount,showNotification, applyTask, round, match:{params:{id}}}) {
function TaskList({applyTaskId, setStudentInfoReset, current_user, showLoginDialog,cancelCount,showNotification, applyTask, round, match:{params:{id}}, period }) {
const [visible, setVisible] = useState(false);
const [deleteTaskId, setDeleteTaskId] = useState(undefined);
//
@ -36,13 +36,13 @@ function TaskList({applyTaskId, setStudentInfoReset, current_user, showLoginDial
{applyTaskId && Object.keys(applyTaskId).includes(item.id.toString()) && <span onClick={()=>{window.location.href=`/glcc/student/apply/${item.id}`}}><i className='iconfont icon-baomingxiangqingicon mr5'></i>报名详情</span>}
{/* 取消按钮5.26-6.25 */}
{applyTaskId && Object.keys(applyTaskId).includes(item.id.toString()) && isStudentApplyDate && <Tooltip title={"取消申请"}><i className='iconfont icon-shanchuicon3 ml20 cancelApply' onClick={()=>{deleteItem(item.id)}}></i></Tooltip>}
{(period === "stuApply" || period === "stuApply1") && applyTaskId && Object.keys(applyTaskId).includes(item.id.toString()) && <Tooltip title={"取消申请"}><i className='iconfont icon-shanchuicon3 ml20 cancelApply' onClick={()=>{deleteItem(item.id)}}></i></Tooltip>}
{/* 课题申请时间范围内: 申请课题 */}
{/* 第一次报名 */}
{isStudentApplyDate && !(applyTaskId && Object.keys(applyTaskId).includes(item.id.toString())) && <span onClick={()=>{applyTask(item.id)}}><i className='iconfont icon-shenqingketiicon applyTask mr5'></i><span className='applyTask'>申请课题</span></span>}
{period === "stuApply" && !(applyTaskId && Object.keys(applyTaskId).includes(item.id.toString())) && <span onClick={()=>{applyTask(item.id)}}><i className='iconfont icon-shenqingketiicon applyTask mr5'></i><span className='applyTask'>申请课题</span></span>}
{/* 第二次报名 锁定状态 */}
{secondStudentApplyDate && !(applyTaskId && Object.keys(applyTaskId).includes(item.id.toString())) && (item.locked ? <Tooltip title={"该课题已有学生入选"}><span className='disabled'><i className='iconfont icon-shenqingketiicon mr5 disabled'></i><span className='disabled'>申请课题</span></span></Tooltip> : <span onClick={()=>{applyTask(item.id, item.locked)}}><i className='iconfont icon-shenqingketiicon applyTask mr5'></i><span className='applyTask'>申请课题</span></span>)}
{period === "stuApply1" && !(applyTaskId && Object.keys(applyTaskId).includes(item.id.toString())) && (item.locked ? <Tooltip title={"该课题已有学生入选"}><span className='disabled'><i className='iconfont icon-shenqingketiicon mr5 disabled'></i><span className='disabled'>申请课题</span></span></Tooltip> : <span onClick={()=>{applyTask(item.id, item.locked)}}><i className='iconfont icon-shenqingketiicon applyTask mr5'></i><span className='applyTask'>申请课题</span></span>)}
</div>
)
})
@ -71,7 +71,7 @@ function TaskList({applyTaskId, setStudentInfoReset, current_user, showLoginDial
}
const expandRow = (record) => {
return <ProjectDetail id={id} detail={null} projectId={record.regId} applyTaskId={applyTaskId} current_user={current_user} showLoginDialog={showLoginDialog} isStudentApplyDate={isStudentApplyDate} showNotification={showNotification} secondStudentApplyDate={secondStudentApplyDate} applyTask={applyTask}/>
return <ProjectDetail id={id} detail={null} projectId={record.regId} applyTaskId={applyTaskId} current_user={current_user} showLoginDialog={showLoginDialog} period={period} showNotification={showNotification} applyTask={applyTask} round={round}/>
}
//
@ -90,7 +90,8 @@ function TaskList({applyTaskId, setStudentInfoReset, current_user, showLoginDial
const id = [];
id.push(applyTaskId[deleteTaskId]);
const params = {
ids: id
ids: id,
round
}
cancelTaskApply(params).then(response=>{
if(response && response.message === "success"){
@ -144,7 +145,7 @@ function TaskList({applyTaskId, setStudentInfoReset, current_user, showLoginDial
}
setLoading(false);
})
}, [keyword, current, pageSize, applyTaskId, apply])
}, [keyword, current, pageSize, applyTaskId, apply, round])
return (
<div className="taskList listBox">

View File

@ -24,25 +24,21 @@ const gradeList = [
{ id: '博士及以上', name: '博士及以上' }
];
function Apply(props) {
const { form, current_user, showNotification, match, history, setStudentInfoReset } = props;
const { form, current_user, showNotification, match, history, setStudentInfoReset, period, round } = props;
const taskId = Number(match.params.taskId);
//
// current_user && (current_user.user_id = 6)
// current_user && (current_user.userName = "使")
const isStudentApplyDate = new Date().getTime() > new Date('2022/06/29 01:00:00').getTime() && new Date().getTime() < new Date('2022/07/01 01:00:00').getTime();
const studentApplyEnd = new Date().getTime() > new Date('2022/07/01 01:00:00').getTime();
const { getFieldDecorator, validateFields, setFieldsValue, validateFieldsAndScroll } = form;
const [imageUrl, setImageUrl] = useState(undefined);
const [loading, setLoading] = useState(false);
const [reload, setReload] = useState();
const [locked, setLocked] = useState(true);
const [userApplyInfo, setUserApplyInfo] = useState(undefined);
const [editable, setEditable] = useState(isStudentApplyDate);
const [editable, setEditable] = useState(false);
const [files, setFiles] = useState([]);
const [files1, setFiles1] = useState([]);
const [files2, setFiles2] = useState([]);
// console.log( new Date());
// console.log('isStudentApplyDate:' + isStudentApplyDate)
const initTask = {
taskId,
memo: '',
@ -51,8 +47,9 @@ function Apply(props) {
const [myTaskList, setMyTaskList] = useState([]);
const [allTaskList, setAllTaskList] = useState([]);
//
//
useEffect(()=>{
setEditable(period === "stuApply" || period === "stuApply1")
}, [period])
useEffect(() => {
//
@ -77,19 +74,20 @@ function Apply(props) {
useEffect(() => {
const params = {
curPage: 1,
pageSize: 10000
pageSize: 10000,
round
}
taskList(params).then(response => {
if (response && response.message === "success") {
setAllTaskList(response.data.rows);
}
})
}, [])
}, [round])
useEffect(() => {
//
current_user && getStudentApplyInfo({ userId: current_user.user_id }).then(response => {
current_user && getStudentApplyInfo({ userId: current_user.user_id, round }).then(response => {
if (response && response.message === "success" && response.data) {
let data = response.data;
let applyInfo = {
@ -135,14 +133,14 @@ function Apply(props) {
}]);
}
// if (item.enrollFirst) {
// applyInfo.enrollFirst = i;
// }
if (item.status == 2) {
item.status == 2
if (item.enrollFirst) {
applyInfo.enrollFirst = i;
}
// if (item.status == 2) {
// item.status == 2
// }
initTaskList.push(item);
// initTaskList.push({
// id: item.id,
@ -185,7 +183,7 @@ function Apply(props) {
}).then(res => setFieldsValue({ 'taskId0': taskId }))
}
})
}, [current_user, reload])
}, [current_user, reload, round])
//
function verify(dataIndex) {
@ -202,20 +200,20 @@ function Apply(props) {
e.preventDefault();
Confirm({
title:'提示',
content:'提交后将不允许修改报名信息,确认提交?',
content:'提交后,您的课题报名信息将无法更改,确认提交?',
onOk:()=>{
validateFieldsAndScroll((err, values) => {
if (!err) {
setLoading(true);
// if (myTaskList.length === 2) {
// if (values.enrollFirst == 1) {
// myTaskList[1].enrollFirst = true;
// myTaskList[0].enrollFirst = false;
// } else {
// myTaskList[1].enrollFirst = false;
// myTaskList[0].enrollFirst = true;
// }
// }
if(myTaskList && myTaskList.length){
const {enrollFirst=0} = values;
myTaskList.map((item, index)=>{
// registrationStudentTaskListround
item.round = round;
//
item.enrollFirst = index === enrollFirst
})
}
const params = {
grade: values.grade,
location: Array.isArray(values.location) && values.location.join(),
@ -226,10 +224,9 @@ function Apply(props) {
profession: values.profession,
studentName: values.studentName,
userId: current_user.user_id,
registrationStudentTaskList: myTaskList
registrationStudentTaskList: myTaskList,
round
}
if (userApplyInfo) {
params.id = userApplyInfo.id;
studentApplyEdit(params).then(response => {
@ -238,7 +235,8 @@ function Apply(props) {
setStudentInfoReset(Math.random());
setReload(Math.random());
setLoading(false);
history.push(`/glcc/subjects`)
window.scrollTo(0,0);
// history.push(`/glcc/subjects`)
}
});
} else {
@ -248,7 +246,8 @@ function Apply(props) {
setStudentInfoReset(Math.random());
setReload(Math.random());
setLoading(false);
history.push(`/glcc/subjects`)
window.scrollTo(0,0);
// history.push(`/glcc/subjects`)
}
});
}
@ -256,7 +255,6 @@ function Apply(props) {
});
}
});
};
const helper = useCallback(
@ -320,7 +318,6 @@ function Apply(props) {
if (info.file.status === 'removed') {
changeTaskItem('memoAttachmentId', '', i);
}
console.log(info.fileList);
if (i==1) {
setFiles1(appendFileSizeToUploadFileAll(info.fileList).slice(-1));
}else if (i==2) {
@ -359,33 +356,33 @@ function Apply(props) {
}
}
// function deleteTask(i) {
// let taskListNew = myTaskList.slice();
// let reWriteInfo;
// if (i) {
// reWriteInfo = {
// taskId1: '',
// memo1: '',
// memoAttachmentId1: ''
// }
// setFiles1([]);
// } else {
// reWriteInfo = {
// taskId0: taskListNew[1].taskId,
// memo0: taskListNew[1].memo,
// memoAttachmentId0: taskListNew[1].memoAttachmentId,
// taskId1: '',
// memo1: '',
// memoAttachmentId1: ''
// }
// setFiles(files1);
// setFiles1([]);
// }
function deleteTask(i) {
let taskListNew = myTaskList.slice();
let reWriteInfo;
if (i) {
reWriteInfo = {
taskId1: '',
memo1: '',
memoAttachmentId1: ''
}
setFiles1([]);
} else {
reWriteInfo = {
taskId0: taskListNew[1].taskId,
memo0: taskListNew[1].memo,
memoAttachmentId0: taskListNew[1].memoAttachmentId,
taskId1: '',
memo1: '',
memoAttachmentId1: ''
}
setFiles(files1);
setFiles1([]);
}
// taskListNew.splice(i, 1);
// setMyTaskList(taskListNew);
// setFieldsValue(reWriteInfo);
// }
taskListNew.splice(i, 1);
setMyTaskList(taskListNew);
setFieldsValue(reWriteInfo);
}
function changeTaskItem(field, val, i) {
let taskListNew = [...myTaskList];
@ -402,17 +399,17 @@ function Apply(props) {
<div className='apply'>
<Breadcrumb className='glcc_breadcrumb font-16'>
<Breadcrumb.Item><Link to="/glcc">开源夏令营</Link></Breadcrumb.Item>
<Breadcrumb.Item><Link to="/glcc/subjects">课题及项目列表</Link></Breadcrumb.Item>
<Breadcrumb.Item><Link to="/glcc/2023/subjects">课题及项目列表</Link></Breadcrumb.Item>
<Breadcrumb.Item style={{ color: '#202D40' }}>学生报名</Breadcrumb.Item>
</Breadcrumb>
<div className="head_introduce">
<h4 className="head_tit">申请说明</h4>
<div className="head_content">1学生报名时间为<span className="head_bold">2022年5月26日-6月24</span>请在报名截止时间北京时间<span className="head_bold">2022年6月24日24点</span>前提交报名信息</div>
<div className="head_content">1学生报名时间为<span className="head_bold">2023年6月4日-6月21</span>请在报名截止时间北京时间<span className="head_bold">2023年6月21日24点</span>前提交报名信息</div>
<div className="head_content">2本次GLCC夏令营基于GitLink代码托管平台完成编程任务若您没有平台账户请先注册后完成课题申请</div>
<div className="head_content">3每位学员最多可申请2个课题至多有且仅有1个课题可以入选</div>
<div className="head_content">4提交申请后每位学员可实时修改自己的报名信息但仅具有两次取消申请课题的机会请谨慎选择课题或取消课题</div>
<div className="head_content">3每位学员最多可申请2个课题有且仅有1个课题可以入选</div>
<div className="head_content">4每个学生有且仅有两次取消申请课题的机会请谨慎选择课题或取消课题</div>
<div className="head_content">5建议在提交课题申请与课题导师邮件沟通详细了解课题描述编码任务技能要求&编程语言预期产出结果等信息以便您能够选择最适合的课题</div>
<div className="head_content">6提交申请后请耐心等待我们会在2022年7月1日公布入围结果如有任何问题请添加开源夏令营QQ群210174286进行咨询</div>
<div className="head_content">6提交申请后请耐心等待我们会在2023年7月1日公布入围结果如有任何问题请添加开源夏令营QQ群210174286进行咨询</div>
</div>
<div className="main-tit">
<img src={studentSvg} alt='' /><span className="tit-text">学生报名</span>
@ -427,33 +424,33 @@ function Apply(props) {
'studentName',
[{ required: true, message: "请正确输入学生姓名" },
{ max: 32, message: '超出限制长度32位字符请重新编辑' }],
<Input placeholder="请输入学生姓名" onBlur={() => { verify("studentName") }} className={"disabledInput"} disabled />
<Input placeholder="请输入学生姓名" onBlur={() => { verify("studentName") }} className={editable ? "" : "disabledInput"} disabled={editable ? false : true} />
)}
{helper('所在高校',
'',
'school',
[{ required: true, message: "请正确输入所在高校" },
{ max: 32, message: '超出限制长度32位字符请重新编辑' }],
<Input placeholder="请输入所在高校" onBlur={() => { verify("school") }} className={"disabledInput"} disabled />
<Input placeholder="请输入所在高校" onBlur={() => { verify("school") }} className={editable ? "" : "disabledInput"} disabled={editable ? false : true}/>
)}
{helper('所学专业',
'',
'profession',
[{ max: 32, message: '超出限制长度32位字符请重新编辑' }],
<Input placeholder="请输入所学专业" onBlur={() => { verify("profession") }} className={"disabledInput"} disabled />
<Input placeholder="请输入所学专业" onBlur={() => { verify("profession") }} className={editable ? "" : "disabledInput"} disabled={editable ? false : true}/>
)}
<Form.Item label="地区">
{getFieldDecorator("location", {
rules: []
})(
<Cascader expandTrigger="hover" popupClassName="glcc_cascader" placeholder="请选择省份城市" options={locData} className={"disabledInput"} disabled />
<Cascader expandTrigger="hover" popupClassName="glcc_cascader" placeholder="请选择省份城市" options={locData} className={editable ? "" : "disabledInput"} disabled={editable ? false : true}/>
)}
</Form.Item>
{helper('所在年级',
'',
'grade',
[],
<Select placeholder="请选择所在年级" className={"disabledInput"} disabled dropdownClassName="glcc_select">
<Select placeholder="请选择所在年级" className={editable ? "" : "disabledInput"} disabled={editable ? false : true} dropdownClassName="glcc_select">
{gradeList.map(item => { return <Option value={item.name} key={item.id}>{item.name}</Option> })}
</Select>
)}
@ -463,7 +460,7 @@ function Apply(props) {
[{ required: true, message: "请正确输入联系电话" },
{ max: 14, message: '超出限制长度14位字符请重新编辑' },
{ pattern: /(^(\d{3,4}-)?\d{7,8})$|([1][3,4,5,6,7,8,9][0-9]{9})/, message: '请正确输入联系电话' }],
<Input placeholder="请输入联系电话" onBlur={() => { verify("phone") }} className={"disabledInput"} disabled />
<Input placeholder="请输入联系电话" onBlur={() => { verify("phone") }} className={editable ? "" : "disabledInput"} disabled={editable ? false : true} />
)}
{helper('邮箱地址',
'',
@ -473,7 +470,7 @@ function Apply(props) {
{ required: true, message: "请输入邮箱地址" },
{ max: 50, message: '超出限制长度50位字符请重新编辑' },
],
<Input placeholder="请输入邮箱地址" onBlur={() => { verify("mail") }} className={"disabledInput"} disabled />
<Input placeholder="请输入邮箱地址" onBlur={() => { verify("mail") }} className={editable ? "" : "disabledInput"} disabled={editable ? false : true} />
)}
<div className='introArea update_item'>{helper('学生证明',
@ -488,7 +485,6 @@ function Apply(props) {
accept=".png,.jpg,.jpeg"
beforeUpload={beforeUpload}
onChange={handleChange}
disabled
>
{imageUrl ? <img src={imageUrl} alt="avatar" style={{ maxWidth: '100px', maxHeight: '100px' }} /> : userApplyInfo && userApplyInfo.proveAttachmentId ?
<div><img src={`${getUrl()}/api/attachments/${userApplyInfo.proveAttachmentId}`} alt='' style={{ maxWidth: '100px' }} /></div> : <div>
@ -505,7 +501,7 @@ function Apply(props) {
<h4 className="item-tit">课题信息<span className="item-tit-num">{i + 1}</span>)
{/* {myTaskList.length > 1 && <span className="delete" onClick={() => { deleteTask(i) }}><i className="iconfont icon-shanchu mr5"></i>删除</span>} */}
</h4>
{item.status == 1 && item.auditRemark && <div className="reject-reason">
{item.status === 1 && item.auditRemark && <div className="reject-reason">
<div className="mb5"><i className="iconfont icon-shanchudiao"></i>欢迎您报名CCF GitLink开源编程夏令营感谢您对GLCC项目及课题的关注和支持本次您的报名信息未通过初审原因如下</div>
<div className="mb5 ml20" dangerouslySetInnerHTML={{ __html: item.auditRemark }}></div>
<div className="mb5 ml20">希望您尽快修改并重新提交报名信息我们将再次审核万分感谢</div>
@ -518,8 +514,10 @@ function Apply(props) {
placeholder="请选择课题名称"
dropdownClassName="glcc_select"
showSearch
disabled
className={"disabledInput"}
className={!editable || (item.id && item.status !== 1) ? "disabledInput" : ""}
disabled={!editable || (item.id && item.status !== 1)}
// disabled
// className={"disabledInput"}
filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) > -1}
onBlur={() => { verify("taskId" + i) }}
onChange={(val) => { changeTaskItem("taskId", val, i) }}
@ -542,8 +540,9 @@ function Apply(props) {
6提示文本框表达内容有限建议您提供内容的pdf或url链接方式展示更精彩的陈述"
onBlur={(e) => { verify("memo" + i); changeTaskItem("memo", e.currentTarget.value, i) }}
rows={7}
className={!(isStudentApplyDate && !item.id) ? "memoText disabledInput" : "memoText"}
disabled={!(isStudentApplyDate && !item.id)} />
className={!editable || (item.id && item.status !== 1) ? "memoText disabledInput" : "memoText"}
disabled={!editable || (item.id && item.status !== 1)}
/>
)}</div>
<div className='introArea'>{helper('附件',
@ -562,7 +561,7 @@ function Apply(props) {
downloadIcon: <i className="iconfont icon-xiazai-icon"></i>,
showRemoveIcon: true
}}
disabled={!(isStudentApplyDate && !item.id)}
disabled={!editable || (item.id && item.status !== 1)}
>
<div className="glcc_btn" >上传自荐书</div>
</Upload>
@ -573,27 +572,27 @@ function Apply(props) {
}
{
// myTaskList.length < 2 && <Button className="add_task" type="primary" onClick={() => { addTask() }}></Button>
myTaskList.length < 2 && <Button className="add_task mb20" type="primary" onClick={() => { addTask() }}>添加课题</Button>
}
{
// myTaskList.length >= 2 && <Form.Item className="priority"
// label={<Fragment><span className="note">2</span></Fragment>}
// >
// {getFieldDecorator("enrollFirst", {
// rules: [{ required: true, message: "" },]
// })(
// <Radio.Group disabled={studentApplyEnd}>
// <Radio value={0}></Radio>
// <Radio value={1}></Radio>
// <Radio value={2}></Radio>
// </Radio.Group>
// )}
// </Form.Item>
myTaskList.length >= 2 && <Form.Item className="priority"
label={<Fragment>对您而言哪个课题的优先级更高<span className="note">优先级将决定您申请课题的入选顺序若申请的课题同时通过审核将以高优先级的课题作为入选标准</span></Fragment>}
>
{getFieldDecorator("enrollFirst", {
rules: [{ required: true, message: "请选择课题优先级" },]
})(
<Radio.Group disabled={editable ? false : true}>
<Radio value={0}>课题一</Radio>
<Radio value={1}>课题二</Radio>
{myTaskList.length === 3 && <Radio value={2}>课题三</Radio>}
</Radio.Group>
)}
</Form.Item>
}
{!studentApplyEnd && <Form.Item className='subInfo introArea'>
<Button type="primary" htmlType="submit" className='sub' disabled={!(editable && !locked)}>提交</Button>
{(period === "stuApply" || period === "stuApply1") && <Form.Item className='subInfo introArea'>
<Button type="primary" htmlType="submit" className='sub' disabled={editable ? false : true}>提交</Button>
</Form.Item>}
</Form>
</div>

View File

@ -2139,9 +2139,10 @@ input.ant-input-number-input:focus {
}
/* md代码类样式渲染 */
.new_li .markdown-body ul>li {
.new_li .markdown-body ul>li ,.markdown-body ul>li {
list-style-type: disc !important;
margin-bottom: 0 !important;
white-space: pre-wrap;
}
.new_li .markdown-body ol>li {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -14,14 +14,21 @@ function LoginRegisterPage(props){
return(
<div className="loginRegister">
<div className="login_register_left">
<img src={logo} className="logo" onClick={()=>{window.location.href='/'}}></img>
<img src={ball} className="ball"></img>
<img src={banner} className="banner"></img>
<img src={logo} className="logo" alt="" onClick={()=>{window.location.href='/'}} />
<img src={ball} className="ball" alt="" />
<img src={banner} className="banner" alt="" />
</div>
<div className="login_register_right">
{props.location.pathname === "/login" ? <Login {...props}/> : props.location.pathname === "/register" ? <Register mygetHelmetapi={mygetHelmetapi}/> : <ResetPassword mygetHelmetapi={mygetHelmetapi}/>}
<img src={img1} className="img1"></img>
<img src={img2} className="img2"></img>
{props.location.pathname === "/login" ?
<Login {...props}/>
:
props.location.pathname === "/register" ?
<Register mygetHelmetapi={mygetHelmetapi}/>
:
<ResetPassword mygetHelmetapi={mygetHelmetapi}/>
}
<img src={img1} className="img1" alt="" />
<img src={img2} className="img2" alt="" />
</div>
<div className="clear"></div>
</div>

View File

@ -5,11 +5,43 @@
color:#5091FF !important;
border-color: #5091FF !important;
}
.quoteDiv{
width: 40%!important;
max-height: 280px!important;
}
.quoteDiv > li{
display: flex;
align-items: center;
height: 32px;
padding:0px 10px;
border-bottom: 1px solid rgba(212, 212, 212, 0.5);
}
.quoteDiv > li.active{
background-color: #F3F4F6;
}
.quoteDiv > li:last-child{
border-bottom: none;
}
.issueIndex{
background-color: rgba(213, 220, 246, 0.36);
border-radius: 4px;
height: 19px;
line-height: 19px;
color: #666666;
text-align: center;
float: left;
padding: 0px 2px;
cursor: pointer;
font-size: 12px;
margin-right: 7px;
}
.issueName{
flex:1
}
/*md编辑器中输入@弹出可选人列表样式*/
.at_who_list{
.at_who_list,.quoteDiv{
position: absolute;
z-index: 100;
z-index: 10000;
width: 180px;
max-height: 160px;
background: #FFFFFF;

View File

@ -74,8 +74,8 @@ function md_elocalStorage(editor, mdu, id) {
return tid
}
// isFocus是否聚焦到md编译器
export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, className = '', noStorage = false, imageExpand = true, placeholder = '', width = '100%', height = 400, initValue = '', emoji, watch, showNullButton = false, showResizeBar = false, startInit = true , forMember = true , isCanAtme = false , changeAtWhoLoginList, owner, projectsId, isFocus = true }) => {
export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, className = '', noStorage = false, imageExpand = true, placeholder = '', width = '100%', height = 400, initValue = '', emoji, watch=true, showNullButton = false, showResizeBar = false, startInit = true , forMember = true , isCanAtme = false , isQuoteIssue = false , changeAtWhoLoginList, owner, projectsId , isFocus = true}) => {
const editorEl = useRef();
const resizeBarEl = useRef();
const [editorInstance, setEditorInstance] = useState();
@ -91,6 +91,13 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
const editorBodyId = `mdEditors_${mdID}`;
const tipId = `e_tips_mdEditor_${mdID}`;
// quote相关
const [issues, setIssues ] = useState([]);
const [quoteVisible, setQuoteVisible] = useState(false);
const [screenIssues, setScreenIssues ] = useState([]);
const quoteVisibleRef = useRef(false);
const chooseIssuesList = useRef([]);
useEffect(()=>{
//请求members接口获取全部可@列表
isCanAtme && axios.get(`/${owner}/${projectsId}/members.json`).then(response=>{
@ -103,9 +110,108 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
document.addEventListener('click',()=>{
atWhoVisibleRef.current = false;
setAtWhoVisible(false);
quoteVisibleRef.current = false;
setQuoteVisible(false);
})
isQuoteIssue && getIssueList();
},[])
// 请求issues接口获取全部可quote的issue列表
function getIssueList(keyword){
axios.get(`/v1/${owner}/${projectsId}/issues`,{params:{
only_name:true,keyword,sort_direction:"desc",sort_by:"issues.created_on"
}}).then(result=>{
if(result){
!keyword && setIssues(result.data.issues);
let data = result.data.issues;
setScreenIssues(data && data.length > 0 ? data:undefined);
}
})
}
// # 引用的弹框div
const QuoteDiv = (
<div id="quoteDiv" className="quoteDiv">
{
screenIssues && screenIssues.map((i,k)=>{
return(
<li className={`quote ${k === 0 && "active"}`} onClick={()=>selectQuoteIssue(i)} onMouseOver={()=>onMouseOverQuote(k)}>
<span style={{minWidth:"40px"}}><span className="issueIndex" title={i.project_issues_index}>#{i.project_issues_index}</span></span>
<span className="issueName task-hide">{i.subject}</span>
</li>
)
})
}
</div>
)
// 点击选择要引用的issue
function selectQuoteIssue(i){
quoteVisibleRef.current = false;
setQuoteVisible(false);
const cm = editorInstance.cm;
//获取鼠标所在行的行数和ch
const cursor = cm.doc.getCursor();
const line = cursor.line;//行
const ch = cursor.ch;//列
const startIndex = cm.getRange({line,ch:0},{line,ch}).lastIndexOf("#");
//替换内容
cm.replaceRange(`[#${i.project_issues_index}](/${owner}/${projectsId}/issues/${i.project_issues_index}) `,{line,ch:startIndex},{line,ch});
//鼠标聚焦
cm.focus();
// 保存引用的issue
const list = new Set(chooseIssuesList.current);
list.add(i.project_issues_index);
chooseIssuesList.current = Array.from(list);
}
function onMouseOverQuote(key){
document.getElementsByClassName("quote active")[0] && (document.getElementsByClassName("quote active")[0].className="quote");
document.getElementsByClassName("quote")[key] && (document.getElementsByClassName("quote")[key].className="quote active");
}
useEffect(()=>{
if(cmEl){
if(quoteVisibleRef.current){
const atWhoListDiv = document.getElementById("quoteDiv");
const atWhoDivs = document.getElementsByClassName("quote");
cmEl.addKeyMap({
'Up':()=>{
let index;
for(let i = 0; i<atWhoDivs.length;i++){
atWhoDivs[i].className === "quote active" && (index = i);
}
if(index>0){
index <=atWhoDivs.length-4 && (atWhoListDiv.scrollTop -=40)
atWhoDivs[index].className = "quote";
atWhoDivs[index-1].className = "quote active";
}
},
'Down':()=>{
let index;
for(let i = 0; i<atWhoDivs.length;i++){
atWhoDivs[i].className === "quote active" && (index = i);
}
if(index<atWhoDivs.length-1){
index >=3 && (atWhoListDiv.scrollTop +=40)
atWhoDivs[index].className = "quote";
atWhoDivs[index+1].className = "quote active";
}
},
'Enter':()=>{
//找到classname为quote active的div执行click事件
if(document.getElementsByClassName("quote active")[0]){
document.getElementsByClassName("quote active")[0].click();
}
}
})
} else {
//移除上下、enter键监听
cmEl.removeKeyMap();
}
}
},[quoteVisible])
function onLayout() {
let ro;
if (editorEl.current) {
@ -153,23 +259,22 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
}
//markdown编辑器中输入的键盘监听事件
function mdKeyDown(e){
function mdKeyDown(e){ //获取光标位置
const cssStyle = document.getElementsByClassName("CodeMirror cm-s-default CodeMirror-wrap")[0].firstChild.style;
//设置弹框位置
const newTop = 62
const newLeft = 20;
const codemirror = editorInstance.cm;
let value = codemirror.getValue();
if (e.shiftKey && e.code === "Digit2") {
// 输入@键后在对应的位置显示可选的项目成员
atWhoVisibleRef.current = true;
setAtWhoVisible(true);
//获取光标位置
const cssStyle = document.getElementsByClassName("CodeMirror cm-s-default CodeMirror-wrap")[0].firstChild.style;
//设置弹框位置
const newTop = 62
const newLeft = 20;
document.getElementById("at_who_list").style.top = parseInt(cssStyle.getPropertyValue("top").replace("px","")) + newTop +"px";
document.getElementById("at_who_list").style.left = parseInt(cssStyle.getPropertyValue("left").replace("px",""))+newLeft+"px";
}
}
//处理本来@了某人 -> 删掉 -> 撤回 的情况
if(e.ctrlKey && e.code === "KeyZ" && allUsers.length != 0){
const codemirror = editorInstance.cm;
let value = codemirror.getValue();
if(e.ctrlKey && e.code === "KeyZ" && allUsers.length !== 0){
//处理初始内容就自带@谁的情况
if(initValue){
const del = [];
@ -194,6 +299,13 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
}
})
}
if(e.shiftKey && e.code === "Digit3" && isQuoteIssue){
// 输入#键后在对应的位置显示可选的项目issue
quoteVisibleRef.current = true;
setQuoteVisible(true);
document.getElementById("quoteDiv").style.top = parseInt(cssStyle.getPropertyValue("top").replace("px","")) + newTop +"px";
document.getElementById("quoteDiv").style.left = parseInt(cssStyle.getPropertyValue("left").replace("px",""))+newLeft+"px";
}
}
useEffect(()=>{
@ -233,8 +345,7 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
htmlDecode: "style,script,iframe",
sequenceDiagram: true,
autoFocus: false,
watch: watch === undefined ? true : watch,
watch: watch,
saveHTMLToTextarea: true,
dialogMaskOpacity: 0.6,
placeholder: placeholder,
@ -265,7 +376,6 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
},
"fullScreen":function(cm,icon,cursor,selection){
icon.addClass("none");
console.log(cm,icon)
},
"inline-latex": function (cm, icon, cursor, selection) {
cm.replaceSelection("$$" + selection + "$$");
@ -298,7 +408,7 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
if(atWhoVisibleRef.current){
// 添加上下键、enter键监听事件
cmEl.addKeyMap({
'Up':()=>{
'Up':()=>{
const atWhoListDiv = document.getElementById("at_who_list");
const atWhoDivs = document.getElementsByClassName("at_who");
let index;
@ -357,10 +467,11 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
}
},[users])
useEffect(() => {
if (cmEl) {
let tid = null
let ro
let ro;
if (onCMBlur) {
editorInstance.cm.on('blur', () => { onCMBlur(editorInstance.getValue()) })
}
@ -374,15 +485,44 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
}
//isCanAtme:只有issue和合并请求以及评论部分可以@他人操作
//绑定@事件
isCanAtme && editorInstance.cm.on("focus", () => {
(isCanAtme || isQuoteIssue) && editorInstance.cm.on("focus", () => {
document.addEventListener("keydown", mdKeyDown);
});
isCanAtme && editorInstance.cm.on("blur", () => {
(isCanAtme || isQuoteIssue) && editorInstance.cm.on("blur", () => {
document.removeEventListener("keydown",mdKeyDown);
});
editorInstance.cm.on("change", (cm) => {
//调用父组件的onchange方法将输入内容传入父级组件
onChange && onChange(cm.getValue());
let reg = /alert\((.*?)\)/g;
let v = cm.getValue();
if(v){
let matchvalue = v.match(reg);
if(matchvalue && matchvalue.length>0){
for(var x=0;x<matchvalue.length;x++){
v = v.replace(matchvalue[x],"");
}
}
}
onChange && onChange(v);
if(quoteVisibleRef.current){
const cur = cm.doc.getCursor();
const line = cur.line;
const ch = cur.ch;
let rangeCont = cmEl.getRange({line,ch:0},{line,ch});
// #后第一个字符为空时隐藏列表,否则不隐藏
if(rangeCont.indexOf("#")===-1){
setQuoteVisible(false);
quoteVisibleRef.current = false;
}else{
rangeCont = rangeCont.substring(rangeCont.lastIndexOf("#")+1);
if(rangeCont === " "){
setQuoteVisible(false);
quoteVisibleRef.current = false;
}else{
getIssueList(rangeCont);
}
}
}
if(atWhoVisibleRef.current){
//搜索用户(弹框之后用户输入用户名信息)
const cur = cm.doc.getCursor();
@ -409,11 +549,12 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
}
}
changeValueTime("#",issues,chooseIssuesList.current);
//当内容发生改变并且有已@列表时
if(atWhoLoginList.current.length != 0){
const codemirror = editorInstance.cm;
//startValue触发change方法时的内容value处理了初始内容带@用户的情况
let startValue = codemirror.getValue();
let value = codemirror.getValue();
//处理初始内容就自带@谁的情况
if(initValue){
@ -442,13 +583,6 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
const ch = cursor.ch;
//处理全部内容中不包含“@”的情况
if(value.indexOf("@") === -1){
//markdown嵌套的链接删掉
// Array.from(atWhoMap.keys()).map(username=>{
// startValue = startValue.replaceAll(`[${username}](/${atWhoMap.get(username)}) `,username);
// })
//替换全部内容
// codemirror.setValue(startValue);
//全部内容已经有要@的列表,但是没有@符号 -> 清空@集合
atWhoLoginList.current = [];
setAtWhoLoginListState([]);
}
@ -467,21 +601,6 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
}
//处理已经有@列表但是value中不包含完整[@用户名](/login)的情况
if(value.indexOf(userCont)===-1){
// //markdown嵌套的链接删掉,删[]、()的情况不用处理markdown会自动认为不是链接
// //找到[和)的index将区域内容替换成[]包裹的内容
// //光标之后的内容
// const curLeterCont = codemirror.getRange({line,ch},{line,ch:content.length});
// console.log('光标之后的内容curLeterCont',curLeterCont);
// //删除用户名 -> ]在curLeterCont中
// //删除login -> ]在curAfterCont中
// const a = curAfterCont.lastIndexOf('[');
// const b = curLeterCont.indexOf(')')
// const c = curLeterCont.indexOf(']') === -1 ? curAfterCont.lastIndexOf(']') : curLeterCont.indexOf(']')+curAfterCont.length;
// console.log('[',a,')',b,']',c);
// const newCont = codemirror.getRange({line,ch:a+1},{line,ch:c});
// console.log('newCont',newCont);
// codemirror.replaceRange(newCont,{line,ch:a-1},{line,ch:b+curAfterCont.length+1})
//符合情况->踢掉这个人 不给他发消息
const list = new Set(atWhoLoginList.current);
list.delete(atWhoMap.get(username));
@ -516,6 +635,86 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
}
}, [cmEl])
function changeValueTime(str,alllist,chooselsit){
if(chooselsit.length != 0){
const codemirror = editorInstance.cm;
//startValue触发change方法时的内容value处理了初始内容带@用户的情况
let value = codemirror.getValue();
//处理初始内容就自带@谁的情况
if(initValue){
const del = [];
alllist.map(item=>{
let strings = item.username;
if(str==="#"){
strings = item.project_issues_index;
}
if(initValue.indexOf(strings)!=-1 && initValue.charAt(initValue.indexOf(strings)-1) === str && initValue.indexOf(`${str}${strings}`)===value.indexOf(`${str}${strings}`)){
//初始内容中有符合@+名字的格式并且当前内容未删除初始内容
del[del.length] = `[${str}${strings}](/${strings})`;
if(str==="#"){
del[del.length] = `[${str}${strings}](${owner}/${projectsId}/issues/${strings})`;
}
}
})
del.length!=0 && del.map(str=>{
value = value.replace(str,"");
})
}
//以project_issues_index为主键和value的map集合
let atWhoMap = new Map();
Array.from(chooselsit).map(item=>{
alllist.map(i=>{
if(i.project_issues_index === item){
atWhoMap.set(i.project_issues_index,i.project_issues_index);
}
})
});
const cursor = codemirror.doc.getCursor();
const line = cursor.line;
const ch = cursor.ch;
//处理全部内容中不包含“@”的情况
if(value.indexOf(str) === -1){
if(str==="#"){
chooseIssuesList.current = [];
}
}
//截取第一个字符到光标的内容
const curAfterCont = codemirror.getRange({line,ch:0},{line,ch});
const content = codemirror.getLine(line);
//处理光标所在行 有“@”的情况
if(content && content.indexOf(str) !== -1){
Array.from(atWhoMap.keys()).map(index=>{
//判断content是不是以列表中的某个username结尾
const userCont = `[${str}${index}](/${owner}/${projectsId}/issues/${atWhoMap.get(index)})`;
//删除空格->选中@用户区域
if(curAfterCont.endsWith(userCont)){
codemirror.setSelection({line,ch:curAfterCont.lastIndexOf(str)-1},{line,ch});
}
if(value.indexOf(userCont)===-1){
//符合情况->从数组中删除
if(str==="#"){
const list = new Set(chooseIssuesList.current);
list.delete(atWhoMap.get(index));
chooseIssuesList.current = Array.from(list);
}
}
})
}else{
//处理所在行没有“@”的情况
Array.from(atWhoMap.keys()).map(index=>{
const userCont = `[${str}${index}](/${owner}/${projectsId}/issues/${atWhoMap.get(index)})`;
if(value.indexOf(userCont)===-1){
if(str==="#"){
const list = new Set(chooseIssuesList.current);
list.delete(atWhoMap.get(index));
chooseIssuesList.current = Array.from(list);
}
}
})
}
}
}
useEffect(() => {
if (editorInstance && initValue !== undefined) {
if (initValue !== null && initValue !== editorInstance.getValue()) {
@ -558,15 +757,14 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
document.removeEventListener('mouseup', onMouseUp)
}
}
}, [
editorInstance, resizeBarEl
])
}, [ editorInstance, resizeBarEl ])
return (
<Fragment>
{atWhoVisible && atWhoList}
<div ref={editorEl} className={`df editormd-editing ${className} ${imageExpand && 'editormd-image-click-expand'} `}>
<div className={`edu-back-greyf5 radius4 editormd ${error ? 'error' : ''}`} id={containerId} >
{quoteVisible && QuoteDiv }
{atWhoVisible && atWhoList}
<textarea style={{ display: 'none' }} id={editorBodyId} name="content"></textarea>
<div className="CodeMirror cm-s-defualt"></div>
</div>