Merge remote-tracking branch 'fork/dev_edu_local' into dev_edu_local

This commit is contained in:
向明星 2025-04-14 09:42:54 +08:00
commit 55e5abf48c
12 changed files with 865 additions and 145 deletions

View File

@ -32,7 +32,8 @@ export function initAxiosInterceptors(props) {
// 判断网络是否连接
initOnlineOfflineListener();
var proxy = "http://172.16.100.220:81";
// var proxy = "http://172.16.100.220:81";
var proxy = 'http://172.16.100.130:81'
//响应前的设置
axios.interceptors.request.use(
config => {

View File

@ -25,6 +25,7 @@ function EducoderAccount({form , visible , onOk , email}){
return(
<Modal
visible={visible}
// visible={false}
title="提示"
width="500px"
closable={false}

View File

@ -1,84 +1,270 @@
import React, { useState , forwardRef, useEffect } from 'react';
import { Form , Modal , Input , Radio } from 'antd';
import React, { useState, forwardRef, useEffect } from 'react';
import { Form, Modal, Input, Radio, Upload, Button, Icon, message } from 'antd';
import Axios from 'axios';
import { getUploadActionUrl, appendFileSizeToUploadFileAll } from 'educoder';
export default Form.create()(
forwardRef((props)=>{
const { getFieldDecorator, validateFields , setFieldsValue } = props && props.form;
const [ visible , setVisible ] = useState(false);
const JoinProjectForm = Form.create({
name: 'join_project_form',
})(
forwardRef((props) => {
const { getFieldDecorator, validateFields } = props.form;
const { onOk, onCancel } = props;
useEffect(()=>{
if(!visible){
setFieldsValue({
code:undefined,
role:"developer"
})
useEffect(() => {
if (props.visible) {
props.form.setFieldsValue({
code: '',
role: 'developer',
});
}
},[visible])
}, [props.visible])
function onOk() {
validateFields((error,values)=>{
if(!error){
const url = `/applied_projects.json`;
Axios.post(url,{
applied_project:{
...values
}
}).then(result=>{
if(result && result.data){
setVisible(false);
props.showNotification("申请加入项目成功,等待审核!");
}
}).catch(error=>{})
}
})
}
function checkValue(rule, value, callback){
if(!value){
function checkValue(rule, value, callback) {
if (!value) {
callback();
}
if(value.length < 6 || value.length > 6){
callback("请输入6位数的邀请码");
if (value.length < 6 || value.length > 6) {
callback('请输入6位数的邀请码');
}
callback();
}
}
return(
<React.Fragment>
<Modal
title="加入项目"
width="480px"
visible={visible}
centered={true}
onOk={onOk}
onCancel={()=>setVisible(false)}
>
<Form layout={'inline'} className="inviteForm">
<Form.Item label="项目邀请码">
{getFieldDecorator("code",{
rules:[
{required:true,message:"请输入6位项目邀请码"},
{validator:checkValue}
]
})(
<Input placeholder="请输入6位项目邀请码" autoComplete={"off"} maxLength="6" style={{width:"300px"}}/>
)}
</Form.Item>
<Form.Item label="选择角色">
{getFieldDecorator("role",{
rules:[{required:true,message:"请选择角色"}]
})(
<Radio.Group defaultValue={"developer"}>
<Radio value="manager">管理员</Radio>
<Radio value="developer">开发者</Radio>
<Radio value="reporter">报告者</Radio>
</Radio.Group>
)}
</Form.Item>
</Form>
</Modal>
<a onClick={()=>setVisible(true)}>加入项目</a>
</React.Fragment>
)
const handleOk = () => {
validateFields((error, values) => {
if (!error) {
onOk(values);
}
});
};
return (
<Modal
title="加入项目"
width="480px"
visible={props.visible}
centered={true}
onOk={handleOk}
onCancel={onCancel}
>
<Form layout="inline" className="inviteForm">
<Form.Item label="项目邀请码">
{getFieldDecorator('code', {
rules: [
{ required: true, message: '请输入6位项目邀请码' },
{ validator: checkValue },
],
})(
<Input
placeholder="请输入6位项目邀请码"
autoComplete="off"
maxLength="6"
style={{ width: '300px' }}
/>
)}
</Form.Item>
<Form.Item label="选择角色">
{getFieldDecorator('role', {
rules: [{ required: true, message: '请选择角色' }],
})(
<Radio.Group>
<Radio value="manager">管理员</Radio>
<Radio value="developer">开发者</Radio>
<Radio value="reporter">报告者</Radio>
</Radio.Group>
)}
</Form.Item>
</Form>
</Modal>
);
})
)
);
const PersonalInfoForm = Form.create({
name: 'personal_info_form',
})(
forwardRef((props) => {
const { getFieldDecorator, validateFields } = props.form;
const { onOk, onCancel, contentFileList, uploadProps } = props;
useEffect(() => {
if (props.visible) {
props.form.setFieldsValue({
remarks: '',
attachment_id: '',
});
}
}, [props.visible])
const handleOk = () => {
validateFields((error, values) => {
if (!error) {
const attachmentId = contentFileList.map((item) => {
return item.response ? item.response.id : item.id;
});
onOk({ ...values, attachment_id: attachmentId.join(',') });
}
});
};
return (
<Modal
title="填写个人信息"
width="480px"
visible={props.visible}
centered={true}
onOk={handleOk}
onCancel={onCancel}
>
<Form layout="inline" className="inviteForm2">
<Form.Item label="个人情况">
{getFieldDecorator('remarks', {
rules: [{ required: true, message: '请输入个人情况' }],
})(
<Input
placeholder="请输入个人情况"
// autoComplete="off"
maxLength={200}
style={{ width: '300px', pointerEvents: 'auto' }} // pointer-events auto
/>
)}
</Form.Item>
<Form.Item label="个人资料" style={{ flexWrap: 'nowrap' }}>
{getFieldDecorator('attachment_id', {
rules: [],
})(
<Upload {...uploadProps}>
<Button type="default" className='mr5'>
<Icon type="upload" /> 上传附件
</Button>
(单个文件最大20MB)
</Upload>
)}
</Form.Item>
</Form>
</Modal>
);
})
);
export default forwardRef((props) => {
const [visible, setVisible] = useState(false);
const [infoVis, setInfoVis] = useState(false);
const [contentFileList, setContentFileList] = useState([]);
//
const [joinProjectValues, setJoinProjectValues] = useState({});
const uploadProps = {
fileList: contentFileList,
multiple: false,
action: `${getUploadActionUrl()}`,
onChange: (info) => {
let content = info.fileList;
setContentFileList(appendFileSizeToUploadFileAll(content));
},
onRemove: (file) => {
if (file.response != undefined) {
Modal.confirm({
content: '是否确认删除?',
onOk: () => {
deleteAttachment(file);
},
onCancel() {
console.log('Cancel');
},
});
return false;
}
},
beforeUpload: (file) => {
console.log('beforeUpload', file.name);
const isLt150M = file.size / 1024 / 1024 <= 20;
if (!isLt150M) {
message.error('文件大小必须不超过20MB!');
}
return isLt150M;
},
};
const deleteAttachment = (file) => {
const url = `/attachments/${file.response ? file.response.id : file.uid}.json`;
Axios.delete(url, {})
.then((response) => {
if (response.data) {
const { status } = response.data;
if (status === 0) {
setContentFileList([]);
}
}
})
.catch((error) => {
console.log(error);
});
};
const handleJoinProjectOk = (values) => {
const url = `/applied_projects.json`;
setJoinProjectValues({ ...values })
Axios.post(url, {
applied_project: {
alert_remarks: false,
...values,
},
})
.then((result) => {
if (result) {
setVisible(false);
if (result.data.data.need_verify) {
setInfoVis(true);
} else {
props.showNotification('申请已提交,请等待项目管理者审核!');
}
}
})
.catch((error) => {
console.log(error);
});
};
const handlePersonalInfoOk = (values) => {
const url = `/applied_projects.json`;
let attachmentId = contentFileList.map(item => {
return item.response ? item.response.id : item.id
})
Axios.post(url, {
applied_project: {
...joinProjectValues,
alert_remarks: true,
...values,
attachment_id: attachmentId.join(','),
},
})
.then((result) => {
if (result && result.data) {
setInfoVis(false);
setContentFileList([]);
props.showNotification('申请已提交,请等待项目管理者审核!');
}
})
.catch((error) => {
console.log(error);
});
};
return (
<React.Fragment>
<JoinProjectForm
visible={visible}
onOk={handleJoinProjectOk}
onCancel={() => setVisible(false)}
/>
<PersonalInfoForm
visible={infoVis}
onOk={handlePersonalInfoOk}
onCancel={() => setInfoVis(false)}
contentFileList={contentFileList}
uploadProps={uploadProps}
/>
<a onClick={() => setVisible(true)}>加入项目</a>
</React.Fragment>
);
});

View File

@ -152,4 +152,17 @@
width: 110px;
text-align: right;
}
}
.inviteForm2{
.ant-form-item{
margin-right: 0px;
display:flex;
flex-wrap: nowrap;
}
.ant-form-item-label{
width: 92px;
text-align: right;
}
}

View File

@ -35,6 +35,7 @@ class Index extends Component {
})
}
render() {
return (
<div className="newMain clearfix">

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { Menu, Input , Spin, Pagination , Popover , Select , Button } from 'antd';
import { Menu, Input , Spin, Pagination , Popover , Select , Button,Tabs } from 'antd';
import { getImageUrl } from 'educoder';
import '../css/index.scss'
import './list.css';
@ -32,14 +32,15 @@ class Index extends Component {
recommendList:undefined,
languageList:undefined,
languageId:undefined
languageId:undefined,
filter:undefined,
}
}
componentDidMount = () => {
const { page, limit, search, sort, project_type, category_id , languageId } = this.state;
this.getListData(page, limit, search, sort, project_type, category_id , languageId);
const { page, limit, search, sort, project_type, category_id , languageId,filter } = this.state;
this.getListData(page, limit, search, sort, project_type, category_id , languageId,filter);
this.getType();
@ -74,7 +75,7 @@ class Index extends Component {
}
// 获取列表
getListData = (page, limit, search, sort, project_type, category_id,languageId) => {
getListData = (page, limit, search, sort, project_type, category_id,languageId,filter) => {
const { current_user } = this.props;
const url = `/projects.json`;
axios.get(url, {
@ -86,7 +87,8 @@ class Index extends Component {
sort_by: sort,
project_type,
category_id,
language_id:languageId
language_id:languageId,
filter
}
}).then((result) => {
if (result) {
@ -132,8 +134,8 @@ class Index extends Component {
search: undefined
})
this.setTypeList(list, type)
const { page, limit, sort, category_id , languageId } = this.state;
this.getListData(page, limit, undefined, sort, type, category_id , languageId);
const { page, limit, sort, category_id , languageId,filter } = this.state;
this.getListData(page, limit, undefined, sort, type, category_id , languageId,filter);
}
// 获取类型
@ -168,8 +170,8 @@ class Index extends Component {
page: 1
});
this.setCategoryList(list, id)
const { limit, sort, project_type , languageId } = this.state;
this.getListData(1, limit, undefined, sort, project_type, id , languageId);
const { limit, sort, project_type , languageId,filter } = this.state;
this.getListData(1, limit, undefined, sort, project_type, id , languageId,filter);
}
// 排序
@ -180,8 +182,8 @@ class Index extends Component {
search: undefined,
isSpin: true
})
const { limit, project_type, category_id , languageId } = this.state;
this.getListData(1, limit, undefined, e.key, project_type, category_id , languageId);
const { limit, project_type, category_id , languageId,filter } = this.state;
this.getListData(1, limit, undefined, e.key, project_type, category_id , languageId,filter);
}
// 搜索
@ -193,8 +195,8 @@ class Index extends Component {
project_type: undefined,
sort: "updated_on"
})
const { limit, sort, category_id , languageId } = this.state;
this.getListData(1, limit, value, sort, undefined, category_id , languageId);
const { limit, sort, category_id , languageId ,filter} = this.state;
this.getListData(1, limit, value, sort, undefined, category_id , languageId,filter);
}
changeSearchValue = (e) => {
this.setState({
@ -206,8 +208,8 @@ class Index extends Component {
this.setState({
page
})
const { limit, search, sort, project_type, category_id , languageId } = this.state;
this.getListData(page, limit, search, sort, project_type, category_id , languageId);
const { limit, search, sort, project_type, category_id , languageId,filter } = this.state;
this.getListData(page, limit, search, sort, project_type, category_id , languageId,filter);
}
getoDetail=(login,identifier)=>{
@ -220,8 +222,17 @@ class Index extends Component {
isSpin:true,
languageId:e === 0 ?undefined:e
})
const { page, limit, sort , project_type , category_id } = this.state;
this.getListData(page, limit, undefined, sort, project_type, category_id ,e === 0 ?undefined:e);
const { page, limit, sort , project_type , category_id,filter } = this.state;
this.getListData(page, limit, undefined, sort, project_type, category_id ,e === 0 ?undefined:e,filter);
}
changeFilter=(e)=>{
this.setState({
isSpin:true,
filter:e
})
const { page,limit, search, sort, project_type, category_id , languageId } = this.state;
this.getListData(page, limit, search, sort, project_type, category_id , languageId,e);
}
menu =()=> {
@ -282,7 +293,7 @@ class Index extends Component {
{
recommendList && recommendList.length>0 &&
<div className="recommandProjects">
{
{/* {
recommendList.map((item,key)=>{
return(
<div onClick={()=>this.getoDetail(item.author && item.author.login,item.identifier)}>
@ -298,7 +309,7 @@ class Index extends Component {
</div>
)
})
}
} */}
</div>
}
@ -315,7 +326,19 @@ class Index extends Component {
</div>
<div className="list-right boxShandow radius-2" style={{padding:0}}>
<Spin spinning={isSpin}>
<div className="list-r-operation">
<div style={{padding:'20px 30px 0px 30px'}}>
<Tabs size='large' type="card" onChange={(key)=>{
this.changeFilter(key)
}}>
<Tabs.TabPane tab="全部" key="undefined" />
<Tabs.TabPane tab="我管理的" key="manage" />
<Tabs.TabPane tab="我学习的" key="study" />
</Tabs>
{/* <Button size='large' onClick={()=>{this.changeFilter(undefined)}} type={this.state.filter == undefined ? 'primary':'default'}></Button>
<Button size='large' onClick={()=>{this.changeFilter('manage')}} type={this.state.filter == 'manage' ? 'primary':'default'} className='ml10 mr10'>我管理的</Button>
<Button size='large' onClick={()=>{this.changeFilter('study')}} type={this.state.filter == 'study' ? 'primary':'default'}>我学习的</Button> */}
</div>
<div className="list-r-operation" style={{padding:'5px 30px 20px 30px'}}>
<div>
<Select
showSearch

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { Tooltip } from 'antd';
import { Tooltip, Badge, Modal, Form, Input, Upload, Button, Icon, Radio, message } from 'antd';
import { getImageUrl } from 'educoder';
import { AlignCenter } from '../Component/layout';
import { Link } from 'react-router-dom';
@ -7,87 +7,279 @@ import '../css/index.scss';
import Nodata from '../Nodata';
import './list.css';
import img_parise from '../Images/parise.png';
import Axios from 'axios'; // 引入 Axios
import { getUploadActionUrl,appendFileSizeToUploadFileAll } from 'educoder';
class PrivateAccessModal extends Component {
constructor(props) {
super(props);
this.state = {
contentFileList: []
};
}
handleOk = () => {
this.props.form.validateFields((err, values) => {
if (!err) {
const url = `/applied_projects.json`;
let attachmentId = this.state.contentFileList.map(item => {
return item.response ? item.response.id : item.id
})
Axios.post(url, {
applied_project: {
alert_remarks: true,
...values,
attachment_id: attachmentId.join(','),
code: this.props.projectInfo.invite_code
},
})
.then((result) => {
if (result && result.data) {
this.setState({
contentFileList: []
});
message.info('申请已提交,请等待项目管理者审核!');
}
})
.catch((error) => {
console.log(error);
});
this.props.onClose();
this.props.form.resetFields();
}
});
}
handleCancel = () => {
this.props.onClose();
this.props.form.resetFields();
}
uploadProps = ()=>{
return {
fileList: this.state.contentFileList,
multiple: false,
action: `${getUploadActionUrl()}`,
onChange: (info) => {
let content = info.fileList;
this.setState({
contentFileList: appendFileSizeToUploadFileAll(content)
});
},
onRemove: (file) => {
if (file.response != undefined) {
Modal.confirm({
content: '是否确认删除?',
onOk: () => {
this.deleteAttachment(file);
},
onCancel() {
console.log('Cancel');
},
});
return false;
}
},
beforeUpload: (file) => {
console.log('beforeUpload', file.name);
const isLt150M = file.size / 1024 / 1024 <= 20;
if (!isLt150M) {
message.error('文件大小必须不超过20MB!');
}
return isLt150M;
},
};
}
deleteAttachment = (file) => {
const url = `/attachments/${file.response ? file.response.id : file.uid}.json`;
Axios.delete(url, {})
.then((response) => {
if (response.data) {
const { status } = response.data;
if (status === 0) {
this.setState({
contentFileList: []
});
}
}
})
.catch((error) => {
console.log(error);
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<Modal
title="加入项目"
visible={this.props.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
centered={true}
width={500}
>
<Form layout="inline" className="inviteForm2">
<Form.Item label="选择角色">
{getFieldDecorator('role', {
rules: [{ required: true, message: '请选择角色' }],
initialValue: 'developer',
})(
<Radio.Group defaultValue={'developer'}>
<Radio value="manager">管理员</Radio>
<Radio value="developer">开发者</Radio>
<Radio value="reporter">报告者</Radio>
</Radio.Group>
)}
</Form.Item>
<Form.Item label="个人情况">
{getFieldDecorator('remarks', {
rules: [{ required: true, message: '请输入个人情况' }],
})(
<Input
placeholder="请输入个人情况"
// autoComplete="off"
maxLength={200}
style={{ width: '300px', pointerEvents: 'auto' }} // 确保 pointer-events 为 auto
/>
)}
</Form.Item>
<Form.Item label="个人资料" style={{ flexWrap: 'nowrap' }}>
{getFieldDecorator('attachment_id', {
rules: [],
})(
<Upload {...this.uploadProps()}>
<Button type="default" className='mr5'>
<Icon type="upload" /> 上传附件
</Button>
(单个文件最大20MB)
</Upload>
)}
</Form.Item>
</Form>
</Modal>
);
}
}
const WrappedPrivateAccessModal = Form.create()(PrivateAccessModal);
class IndexItem extends Component {
TurnToDetail = (login, url) => {
this.props.history.push({
pathname: url,
state: login
})
});
}
state = {
showModal: false,
projectInfo: null
};
openModal = (projectInfo) => {
this.setState({ showModal: true, projectInfo });
};
closeModal = () => {
this.setState({ showModal: false, projectInfo: null });
};
render() {
const { projects } = this.props;
return (
<div className="project-list minH-670" style={{padding:"0px 20px"}}>
{ projects && projects.length > 0 ? projects.map((item, key) => {
<div className="project-list minH-670" style={{ padding: "0px 20px" }}>
{projects && projects.length > 0 ? projects.map((item, key) => {
return (
<div className="p-r-Item" key={key}>
{
item.platform === "educoder" ?
<a href="javascript:void(0)" style={{cursor:"default"}} className="show-user-link">
<img className="p-r-photo" alt="" src={item.author && item.author.image_url} ></img>
</a>
:
<Link to={item.author && (item.author.type === "Organization" ? `/organize/${item.author.login}`:`/users/${item.author.login}`)} className="show-user-link">
<img className="p-r-photo" alt="" src={getImageUrl(`/${item.author && item.author.image_url}`)} ></img>
</Link>
item.platform === "educoder" ? (
<a href="javascript:void(0)" style={{ cursor: "default" }} className="show-user-link">
<img className="p-r-photo" alt="" src={item.author && item.author.image_url} />
</a>
) : (
<Link to={item.author && (item.author.type === "Organization" ? `/organize/${item.author.login}` : `/users/${item.author.login}`)} className="show-user-link">
<img className="p-r-photo" alt="" src={getImageUrl(`/${item.author && item.author.image_url}`)} />
</Link>
)
}
<div className="p-r-Infos">
<div className="p-r-name">
<AlignCenter>
<Link to={`/projects/${item.author.login}/${item.identifier}`} title={`${item.author.name}/${item.name}`} className="color-grey-3 font-18 task-hide " style={{maxWidth: 470 }}>
<Link onClick={(e) => {
if (item.is_member || item.is_manager || !item.join_project_need_verify) {
return true;
} else {
e.preventDefault();
this.openModal(item);
return false;
}
}} to={`/projects/${item.author.login}/${item.identifier}`} title={`${item.author.name}/${item.name}`} className="color-grey-3 font-18 task-hide " style={{ maxWidth: 470 }}>
{item.author.name}/{item.name}
</Link>
{ !item.is_public && <span className="privateTag">私有</span> }
{!item.is_public && <span className="privateTag">私有</span>}
{
item.forked_from_project_id ?
item.forked_from_project_id ? (
<span className="ml5">
<i className="iconfont icon-fork font-18 color-orange" />
</span>
: ""
) : null
}
{
item.type && item.type !== 0 ?
item.type === 2 ?
<Tooltip title="该项目是一个镜像" className="ml5">
<i className="iconfont icon-banbenku font-18 color-green" />
</Tooltip>:
<span className="ml5">
<i className="iconfont icon-jingxiang font-18 color-green" />
</span>:""
item.type && item.type !== 0 ?
item.type === 2 ?
<Tooltip title="该项目是一个镜像" className="ml5">
<i className="iconfont icon-banbenku font-18 color-green" />
</Tooltip> :
<span className="ml5">
<i className="iconfont icon-jingxiang font-18 color-green" />
</span> : null
}
</AlignCenter>
<AlignCenter>
{
(item.is_manager && item.join_project_need_verify && !!item.pending_verify_count) && <span style={{position:'relative',display:'inline-block',width:'120px',textAlign:'center',background:'rgba(250,100,0,0.1)',borderRadius:'2px',border:'1px solid #FFDCC5'}}>
<Badge dot offset={[-90,7]} style={{ backgroundColor: '#FA6400' }}><span style={{fontSize:'12px',color:'#FA6400'}}>待审核申请{item.pending_verify_count}</span></Badge>
</span>}
<span className="p-r-tags">
{
item.praises_count && item.praises_count>0 ?
<span className="pariseTag">
<img src={img_parise} alt="" className="pariseImg" /> {item.praises_count}
</span>:""
item.praises_count && item.praises_count > 0 ?
<span className="pariseTag">
<img src={img_parise} alt="" className="pariseImg" /> {item.praises_count}
</span> : null
}
{
item.forked_count && item.forked_count>0 ?
<span>
<i className="iconfont icon-fork mr3 font-16" style={{ color: "#1B8FFF" }} />fork {item.forked_count}
</span>:""
item.forked_count && item.forked_count > 0 ?
<span>
<i className="iconfont icon-fork mr3 font-16" style={{ color: "#1B8FFF" }} />fork {item.forked_count}
</span> : null
}
</span>
</AlignCenter>
</div>
<p className="break_word task-hide-2 mt10" style={{ maxHeight: "44px",lineHeight:"22px" }}>{item.description}</p>
<p className="break_word task-hide-2 mt10" style={{ maxHeight: "44px", lineHeight: "22px" }}>{item.description}</p>
<div className="p-r-about">
<span className="p-r-detail">
{/* <span><label>浏览量:</label>{item.visits}</span> */}
{/* {item.category && item.category.id && <span>{item.category.name}</span>} */}
{item.last_update_time ? <span><label>更新于</label>{item.time_ago}</span> : ""}
{item.language && item.language.id ? <span className="color-grey-3">{item.language.name}</span> : ""}
{item.last_update_time ? <span><label>更新于</label>{item.time_ago}</span> : null}
{item.language && item.language.id ? <span className="color-grey-3">{item.language.name}</span> : null}
</span>
</div>
</div>
</div>
)
}) : <Nodata _html="暂无数据~"></Nodata>}
);
}) : <Nodata _html="暂无数据~" />}
<WrappedPrivateAccessModal
visible={this.state.showModal}
onClose={this.closeModal}
projectInfo={this.state.projectInfo}
/>
</div>
)
);
}
}
export default IndexItem;

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { Skeleton , Tooltip } from 'antd';
import { Skeleton , Tooltip,Badge } from 'antd';
import { Link } from 'react-router-dom';
function DetailBanner({ history,list , owner , projectsId , isManager , url , pathname , state , urlFlag , projectDetail , platform ,open_devops }){
@ -109,7 +109,7 @@ function DetailBanner({ history,list , owner , projectsId , isManager , url , pa
<li className={pathname === "setting" ? "active" : ""}>
<Link to={`/projects/${owner}/${projectsId}/setting`}>
<i className={url && url.indexOf("/setting") > 0 ? "iconfont icon-cangku color-grey-3 mr5 font-14":"iconfont icon-cangku color-grey-6 font-14 mr5"}></i>
<span>设置</span>
{projectDetail && projectDetail.pending_verify_count ? <Badge dot offset={[6,-2]}><span>设置</span></Badge> :'设置'}
</Link>
</li>
}

View File

@ -44,7 +44,8 @@ class Index extends Component {
project_category_name: undefined,
license_name: undefined,
ignore_name: undefined,
descNum:0
descNum:0,
join_project_need_verify:true
}
}
componentDidMount = () => {
@ -169,7 +170,7 @@ class Index extends Component {
isSpin: true
})
const { projectsType } = this.props.match.params;
const { project_language_id, project_category_id, license_id, ignore_id , owners_id , owners_name } = this.state;
const { project_language_id, project_category_id, license_id, ignore_id , owners_id , owners_name,join_project_need_verify } = this.state;
const decoderPass = Base64.encode(values.password);
const url = (projectsType && projectsType === "mirror") ? "/projects/migrate.json" : "/projects.json";
axios.post(url, {
@ -179,7 +180,8 @@ class Index extends Component {
project_category_id,
license_id,
ignore_id,
user_id:owners_id
user_id:owners_id,
join_project_need_verify
}).then((result) => {
if (result) {
if (result.data.id) {
@ -288,6 +290,7 @@ class Index extends Component {
ignore_list,
mirrorCheck,
join_project_need_verify,
descNum
} = this.state;
@ -511,6 +514,14 @@ class Index extends Component {
)}
</Form.Item >
}
<Form.Item
label="加入项目设置"
colon={false}
>
{getFieldDecorator('join_project_need_verify')(
<Checkbox checked={join_project_need_verify} onChange={(e)=>this.setState({join_project_need_verify:e.target.checked})}>用户加入项目时需要审核<span className="font-13 color-grey-9">使用邀请码加入也需要审核由项目所有人或管理员审核</span></Checkbox>
)}
</Form.Item >
<div>
<span className="ant-form-item-required"></span>
</div>

248
src/forge/Settings/Audit.js Normal file
View File

@ -0,0 +1,248 @@
import React, { useEffect, useState } from 'react';
import { WhiteBack, FlexAJ } from '../Component/layout';
import { Table, Pagination, Popconfirm, Button, Modal, message, Input } from 'antd';
import Title from '../Component/Title';
import Search from '../Component/Search';
import styled from 'styled-components';
import { getImageUrl } from 'educoder';
import axios from 'axios';
import { Link } from 'react-router-dom';
const Img = styled.img`{
width:30px;
height:30px;
border-radius:50%;
}`
export default ((props) => {
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(15);
const [total, setTotal] = useState(0);
const [search, setSearch] = useState(undefined);
const [data, setData] = useState([]);
const [status, setStatus] = useState(1);
useEffect(() => {
getData();
}, [])
function getData(status, search, page) {
const url = `/applied_projects.json`;
axios.get(url, {
params: {
page, limit,
keyword: search,
status,
project_id: (props.projectDetail) ? props.projectDetail.project_id :''
}
}).then(result => {
if (result && result.data) {
setData(result.data.data.list);
setTotal(result.data.data.count);
}
}).catch(error => { })
}
// 切换分页
function ChangePage(page) {
setPage(page);
getData(status, search, page);
}
function verify(id, verifyStatus, reason) {
const url = `/applied_projects/${id}/verify.json?status=${status}`;
axios.post(url, {
verify_status: verifyStatus,
status,
reason
}).then(result => {
if (result && result.data) {
message.success("操作成功!");
getData(status, search, page);
}
}).catch(error => { })
}
const columns = [
{
title: '用户名',
dataIndex: 'user_name',
ellipsis: true,
width: "12%",
align:'left',
render: (item, record) => <span>{item || '--'}</span>
},
{
title: '申请角色',
dataIndex: 'applied_project',
ellipsis: true,
width: "10%",
align:'left',
render: (value, item) => {
return value || "--"
}
},
{
title: '学号',
dataIndex: 'student_id',
ellipsis: true,
width: "10%",
align: "left",
render: (value) => {
return value || "--"
}
},
{
title: '邮箱',
dataIndex: 'mail',
ellipsis: true,
width: "10%",
align:'left',
render: (value) => value || "--"
},
{
title: '个人情况',
dataIndex: 'remarks',
ellipsis: true,
width: "15%",
align:'left',
render: (value) => value || "--"
},
{
title: '上传附件',
dataIndex: 'attachment_info',
width: "10%",
align:'left',
render: (value, record) => {
if (JSON.stringify(record.attachment_info)!="{}") {
return <Button type='link' style={{padding:'0'}} onClick={() => {
// 构建下载链接
const downloadUrl = `${record.attachment_info.url}`;
// 创建一个 a 元素
const a = document.createElement('a');
// 设置 a 元素的 href 属性为下载链接
a.href = downloadUrl;
// 设置 a 元素的 download 属性,可根据实际情况设置文件名
a.download = record.attachment_info.title || 'attachment';
// 模拟点击 a 元素
document.body.appendChild(a)
a.click();
// 移除 a 元素
document.body.removeChild(a);
}}>下载</Button>
} else {
return '--'
}
}
},
{
title: '拒绝原因',
dataIndex: 'reason',
ellipsis: true,
width: "15%",
align:'left',
render: (value) => value || "--"
},
{
title: '操作',
dataIndex: 'operation1',
width: "15%",
align:'right',
flex:'right',
render: (value, item) => {
return (
<div>
<Button type='link' style={{padding:'0 5px'}} onClick={() => {
Modal.confirm({
title: '是否确认同意TA的加入',
okText: '确认',
cancelText: '取消',
centered: true,
onOk: () => {
verify(item.id, 'agree', '')
},
});
}}>同意</Button>
<Button type='link' style={{padding:'0 5px',color:'#FF0000'}} onClick={() => {
let rejectReason = '';
Modal.confirm({
title: '拒绝申请',
icon: null,
okText: '确认',
centered: true,
width: 500,
content: <div>
<div className='mb10 mt15'><span style={{ color: "#ff4d4f" }}>*</span><span>:</span></div>
<div><Input.TextArea placeholder='请输入拒绝原因' rows={3} maxLength={200} onChange={(event) => {
rejectReason = event.target.value;
}} /></div>
</div>,
cancelText: '取消',
onOk: () => {
verify(item.id, 'reject', rejectReason)
},
})
}}>拒绝</Button>
</div>
)
}
},
{
title: '操作',
dataIndex: 'operation2',
width: "15%",
align:'right',
flex:'right',
render: (value, record) => <span style={{color:'#1991FF'}}>{record.status == 'accepted'? '已同意':'已拒绝'}</span>
}
].filter(item => {
if (status == 1) {
return item.dataIndex != 'operation2' && item.dataIndex!= 'reason'
}else{
return item.dataIndex != 'operation1'
}
})
return (
<WhiteBack>
<Title>
<span>审核管理</span>
</Title>
<FlexAJ className="padding20-30">
<div>
<Button size='default' type={status == 1 ? 'primary' : 'default'} onClick={() => {
setStatus(1);
getData(1, search)
}}>待审核</Button>
<Button size='default' type={status == 2 ? 'primary' : 'default'} className='ml10' onClick={() => {
setStatus(2);
getData(2, search)
}}>已审核</Button>
</div>
<div style={{ width: "300px" }}>
<Search placeholder="搜索用户信息" onSearch={(value) => {
setSearch(value)
getData(status, value)
}} />
</div>
</FlexAJ>
<div className="pl30 pr30 pb30" style={{ minHeight: "400px" }}>
<Table
size="small"
columns={columns}
dataSource={data}
pagination={false}
className="teamMemberTable"
></Table>
{
total > limit ?
<div className="edu-txt-center mt30 mb20">
<Pagination simple current={page} total={total} pageSize={limit} onChange={ChangePage}></Pagination>
</div>
: ""
}
</div>
</WhiteBack>
)
})

View File

@ -7,6 +7,7 @@ import "./setting.scss";
import Loadable from "react-loadable";
import Loading from "../../Loading";
import { Box, Long, Short, Gap } from '../Component/layout';
import {Badge} from 'antd'
const BranchNew = Loadable({
loader: () => import("./BranchRule"),
@ -16,6 +17,10 @@ const Branch = Loadable({
loader: () => import("./Branch"),
loading: Loading,
});
const Audit = Loadable({
loader: () => import("./Audit"),
loading: Loading,
});
const Setting = Loadable({
loader: () => import("./Setting"),
loading: Loading,
@ -65,6 +70,18 @@ class Index extends Component {
</Link>
</p>
</li>
<li
className={
pathname.indexOf("setting/audit") > -1 ? "active" : ""
}
>
<p>
<Link to={`/projects/${owner}/${projectsId}/setting/audit`} className="w-100">
<i className="iconfont icon-zhongqingdianxinicon10 font-18 mr10"></i>
{(this.props.projectDetail && this.props.projectDetail.pending_verify_count) ? <Badge dot offset={[6,-2]}><span style={{color:'#05101a',fontSize:'16px'}}>审核管理</span></Badge> : ''}
</Link>
</p>
</li>
<li
className={
pathname.indexOf("setting/branch") > -1 ? "active" : ""
@ -125,6 +142,12 @@ class Index extends Component {
<BranchNew {...this.props} {...props} {...this.state} />
)}
></Route>
<Route
path="/projects/:owner/:projectsId/setting/audit"
render={(props) => (
<Audit {...this.props} {...props} {...this.state} />
)}
></Route>
<Route
path="/projects/:owner/:projectsId/setting/branch"
render={(props) => (

View File

@ -28,7 +28,8 @@ class Setting extends Component {
project_units:['home',"activity","code"],
divertVisible:false,
is_transfering:undefined,
projectName:undefined
projectName:undefined,
join_project_need_verify:false
};
}
@ -77,6 +78,7 @@ class Setting extends Component {
this.setState({
projectName:result.data.project_name,
private_check: result.data.private,
join_project_need_verify:result.data.join_project_need_verify,
loading:false,
project_units:units
});
@ -142,12 +144,13 @@ class Setting extends Component {
update=(values)=>{
const { projectsId , owner } = this.props.match.params;
const { private_check } = this.state;
const { private_check ,join_project_need_verify} = this.state;
const url = `/${owner}/${projectsId}.json`;
axios.put(url, {
name: values.project_name,
description: values.project_description,
private: private_check,
join_project_need_verify:join_project_need_verify,
...values,
}).then((result) => {
if (result) {
@ -196,7 +199,7 @@ class Setting extends Component {
const { getFieldDecorator } = this.props.form;
const { projectDetail } = this.props;
const { CategoryList, LanguageList, private_check ,loading } = this.state;
const { CategoryList, LanguageList, private_check ,loading,join_project_need_verify } = 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;
@ -216,8 +219,8 @@ class Setting extends Component {
],
})(<Input placeholder="请输入项目名称" />)}
</Form.Item>
<div className="df" style={{ alignItems: "center" }}>
<span className="mr20 mb15 font-16">可见性</span>
<div className="df">
<span className="mr20 mb15 font-14" style={{marginTop:'8px'}}>可见性</span>
<Form.Item label="">
{getFieldDecorator("private", {
rules: [],
@ -227,8 +230,8 @@ class Setting extends Component {
onChange={this.changePrivate}
disabled={forked_from_project_id}
>
<span className="color-grey-9">将仓库设为私有</span>
<span className="color-grey-6">
<span className="color-grey-6">将仓库设为私有</span>
<span className="color-grey-9">
{ forked_from_project_id ?`Fork仓库的可见性实时同步自源仓库不支持直接修改`:`修改仓库的可见性将会影响到该仓库下所有Fork仓库的可见性`}
</span>
</Checkbox>
@ -284,6 +287,24 @@ class Setting extends Component {
</Checkbox.Group>
)}
</Form.Item>
<div className="df">
<span className="mr20 mb15 font-14" style={{marginTop:'8px'}}>加入项目设置</span>
<Form.Item label="">
{getFieldDecorator("join_project_need_verify", {
rules: [],
})(
<Checkbox
checked={join_project_need_verify}
onChange={(e)=>this.setState({join_project_need_verify:e.target.checked})}
>
<span className="color-grey-6">用户加入项目时需要审核</span>
<span className="color-grey-9">
使用邀请码加入也需要审核由项目所有人或管理员审核
</span>
</Checkbox>
)}
</Form.Item>
</div>
<p className="clearfix">
<a className="submitBtn" onClick={this.resetSetting}>
更新仓库设置