forked from Gitlink/forgeplus-react
Merge branch 'develop' into dev_m_copy
# Conflicts: # src/AppConfig.js # src/forge/DevOps/Index.jsx # src/forge/Index.js # src/forge/Main/Detail.js # src/forge/Main/IndexItem.js # src/forge/Merge/MessageCount.js # src/forge/Settings/Collaborator.js # src/forge/users/watch_users.js # src/modules/tpm/NewHeader.js
This commit is contained in:
commit
fdab967b6a
|
@ -0,0 +1,16 @@
|
|||
<h3>前端react环境安装:</h3>
|
||||
<p>1、 安装node v6.9.x;此安装包含了node和npm。</p>
|
||||
<p>2、 安装cnpm(命令行): npm install -g cnpm --registry=https://registry.npm.taobao.org</p>
|
||||
<p>3、 安装依赖的js库(public/react目录下<即项目package.json所在目录>,开启命令行): cnpm install</p>
|
||||
<p>4、 如果你的ruby服务使用的是3000端口,则需要在package.json中修改"port"参数的值</p>
|
||||
<p>5、 启动服务(命令行-目录同3): npm start</p>
|
||||
<p>6、 build初始化 npm run build</p>
|
||||
|
||||
|
||||
<h3>分支信息:</h3>
|
||||
<p>相关代码提交到对应分支,能上线的代码先提交到develop分支上测试版,测试通过后合并提交到master分支上线正式版</p>
|
||||
<p>master:开发环境(正式环境)</p>
|
||||
<p>develop:测试环境</p>
|
||||
<p>dev_local:本地版本</p>
|
||||
<p>dev_chain:含有区块链相关内容的分支</p>
|
||||
<p>PS:新增加的需求功能先新建新分支开发,在测试版测试没问题后再分别合并到develop和master分支</p>
|
|
@ -17,16 +17,7 @@ const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
|||
const paths = require("./paths");
|
||||
const getClientEnvironment = require("./env");
|
||||
|
||||
// Some apps do not use client-side routing with pushState.
|
||||
// For these, "homepage" can be set to "." to enable relative asset paths.
|
||||
let publicPath = "/react/build/";
|
||||
// let nodeEnv = process.env.NODE_ENV
|
||||
// if (nodeEnv === 'testBuild') {
|
||||
// publicPath = 'https://testforgeplus.trustie.net/react/build/';
|
||||
// }
|
||||
// if (nodeEnv === 'production') {
|
||||
// publicPath = 'https://forgeplus.trustie.net/react/build/';
|
||||
// }
|
||||
const publicUrl = publicPath.slice(0, -1);
|
||||
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
|
||||
const env = getClientEnvironment(publicPath);
|
||||
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 584 KiB After Width: | Height: | Size: 724 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -236,7 +236,6 @@ class App extends Component {
|
|||
path="/register"
|
||||
render={
|
||||
(props) => {
|
||||
|
||||
return (<EducoderLogin {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
}
|
||||
|
@ -248,7 +247,7 @@ class App extends Component {
|
|||
<Route path={"/organize"}
|
||||
render={
|
||||
(props) => {
|
||||
return (<OrganizeIndex {...this.props} {...props} {...this.state} />)
|
||||
return (<OrganizeIndex {...props} {...this.props} {...this.state} />)
|
||||
}
|
||||
}>
|
||||
</Route>
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { AutoComplete , Button , Icon } from 'antd';
|
||||
import axios from 'axios';
|
||||
|
||||
const { Option } = AutoComplete;
|
||||
function AddGroup({organizeId,getGroupID}){
|
||||
const [ id , setID ] = useState(undefined);
|
||||
const [ source , setSource ] = useState(undefined);
|
||||
const [ searchKey , setSearchKey ] = useState("");
|
||||
|
||||
useEffect(()=>{
|
||||
getUserList();
|
||||
},[searchKey])
|
||||
|
||||
function getUserList(e){
|
||||
const url = `/organizations/${organizeId}/teams/search.json`;
|
||||
axios.get(url, {
|
||||
params: {
|
||||
search: searchKey,
|
||||
},
|
||||
}).then((result) => {
|
||||
if (result) {
|
||||
sourceOptions(result.data.teams);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
function sourceOptions(userDataSource){
|
||||
const s = userDataSource && userDataSource.map((item, key) => {
|
||||
return (
|
||||
<Option
|
||||
key={key}
|
||||
value={`${item.id}`}
|
||||
name={item.name}
|
||||
>
|
||||
{item.name}
|
||||
</Option>
|
||||
);
|
||||
});
|
||||
setSource(s);
|
||||
}
|
||||
|
||||
function changeInputUser(e){
|
||||
setSearchKey(e || "");
|
||||
};
|
||||
|
||||
// 选择用户
|
||||
function selectInputUser(e, option){
|
||||
setID(e);
|
||||
setSearchKey(option.props.name);
|
||||
};
|
||||
|
||||
function addCollaborator(){
|
||||
getGroupID && getGroupID(id);
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="addPanel">
|
||||
<AutoComplete
|
||||
dataSource={source}
|
||||
value={searchKey}
|
||||
style={{ width: 300 }}
|
||||
onChange={changeInputUser}
|
||||
onSelect={selectInputUser}
|
||||
placeholder="搜索需要添加的团队..."
|
||||
allowClear
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
ghost
|
||||
onClick={addCollaborator}
|
||||
className="ml15"
|
||||
>
|
||||
<Icon type="plus" size="16"></Icon>
|
||||
添加团队
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default AddGroup;
|
|
@ -0,0 +1,95 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { AutoComplete , Button , Icon } from 'antd';
|
||||
import axios from 'axios';
|
||||
import { getImageUrl } from 'educoder';
|
||||
|
||||
const { Option } = AutoComplete;
|
||||
function AddMember({getID,login}){
|
||||
const [ id , setID ] = useState(undefined);
|
||||
const [ source , setSource ] = useState(undefined);
|
||||
const [ searchKey , setSearchKey ] = useState(undefined);
|
||||
|
||||
useEffect(()=>{
|
||||
getUserList();
|
||||
},[searchKey])
|
||||
|
||||
function getUserList(e){
|
||||
const url = `/users/list.json`;
|
||||
axios.get(url, {
|
||||
params: {
|
||||
search: searchKey,
|
||||
},
|
||||
}).then((result) => {
|
||||
if (result) {
|
||||
sourceOptions(result.data.users);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
function sourceOptions(userDataSource){
|
||||
const s = userDataSource && userDataSource.map((item, key) => {
|
||||
return (
|
||||
<Option
|
||||
key={key}
|
||||
value={`${item.user_id}`}
|
||||
login={`${item.login}`}
|
||||
name={item.username}
|
||||
>
|
||||
<img
|
||||
className="user_img radius"
|
||||
width="28"
|
||||
height="28"
|
||||
src={getImageUrl(`images/${item && item.image_url}`)}
|
||||
alt=""
|
||||
/>
|
||||
<span className="ml10" style={{ "vertical-align": "middle" }}>
|
||||
{item.username}
|
||||
<span className="color-grey ml10">({item.login})</span>
|
||||
</span>
|
||||
</Option>
|
||||
);
|
||||
});
|
||||
setSource(s);
|
||||
}
|
||||
|
||||
function changeInputUser(e){
|
||||
setSearchKey(e);
|
||||
};
|
||||
|
||||
// 选择用户
|
||||
function selectInputUser(e, option){
|
||||
setID(login ? e : option.props.login);
|
||||
setSearchKey(option.props.name);
|
||||
};
|
||||
|
||||
function addCollaborator(){
|
||||
getID && getID(id);
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="addPanel">
|
||||
<AutoComplete
|
||||
dataSource={source}
|
||||
value={searchKey}
|
||||
style={{ width: 300 }}
|
||||
onChange={changeInputUser}
|
||||
onSelect={selectInputUser}
|
||||
placeholder="搜索需要添加的用户..."
|
||||
allowClear
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
ghost
|
||||
onClick={addCollaborator}
|
||||
className="ml15"
|
||||
>
|
||||
<Icon type="plus" size="16"></Icon>
|
||||
添加成员
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default AddMember;
|
|
@ -1,13 +1,15 @@
|
|||
import React from 'react';
|
||||
import { getImageUrl } from 'educoder';
|
||||
import { Link } from 'react-router-dom';
|
||||
import './Component.scss';
|
||||
|
||||
export default (({img , title, desc , rightBtn})=>{
|
||||
function Cards({img , title, desc , rightBtn , src}){
|
||||
return(
|
||||
<div className="cards">
|
||||
<div className="img"><img src={img} alt=""/></div>
|
||||
{img &&<div className="img"><img src={getImageUrl(`images/${img}`)} alt=""/></div>}
|
||||
<div className="content">
|
||||
<p className="titles">
|
||||
<span>{title}</span>
|
||||
<Link to={src}>{title}</Link>
|
||||
{rightBtn}
|
||||
</p>
|
||||
<div className="desc">
|
||||
|
@ -16,4 +18,5 @@ export default (({img , title, desc , rightBtn})=>{
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
export default Cards;
|
|
@ -19,6 +19,7 @@ li.ant-menu-item{
|
|||
padding:20px 34px;
|
||||
background-color: #fff;
|
||||
margin-bottom:18px;
|
||||
min-height: 130px;
|
||||
.img{
|
||||
margin-right: 20px;
|
||||
width: 190px;
|
||||
|
@ -27,8 +28,10 @@ li.ant-menu-item{
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
img{
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
|
@ -39,7 +42,9 @@ li.ant-menu-item{
|
|||
justify-content: space-between;
|
||||
margin-bottom: 10px!important;
|
||||
align-items: center;
|
||||
&>span{
|
||||
height: 22px;
|
||||
line-height: 22px;;
|
||||
&>a{
|
||||
font-size:18px ;
|
||||
color: #333;
|
||||
}
|
||||
|
@ -50,6 +55,7 @@ li.ant-menu-item{
|
|||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import './Component.scss';
|
|||
export default (()=>{
|
||||
return(
|
||||
<div className="handleBox">
|
||||
<a href="https://www.trustie.net/forums/82/memos/3075" target="_blank" >
|
||||
<a href="https://forum.trustie.net/forums/3075/detail" target="_blank" >
|
||||
<img src={Handbook} alt=""/>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,7 @@ export default (({fork,parise})=>{
|
|||
}`;
|
||||
const SpanStyleparise = styled.span`{
|
||||
display:flex;
|
||||
align-item:center;
|
||||
align-items:center;
|
||||
margin-left:30px;
|
||||
padding:0px 12px;
|
||||
border-radius:13px;
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import React from 'react';
|
||||
import './Component.scss';
|
||||
import { Button } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import FocusButton from "../UsersList/focus_button";
|
||||
import { getImageUrl } from 'educoder';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const Img = styled.img`{
|
||||
border-radius:50%;
|
||||
|
@ -28,26 +32,8 @@ const I = styled.i`{
|
|||
font-size:13px!important;
|
||||
color:#60B25E;
|
||||
margin-right:2px;
|
||||
}`
|
||||
const FocusBtn = styled.a`{
|
||||
display:inline-block;
|
||||
height:30px;
|
||||
line-height:26px;
|
||||
padding:0px 12px;
|
||||
background-color:#fafafa;
|
||||
border:1px solid #eee;
|
||||
border-radius:2px;
|
||||
color:#888!important;
|
||||
}`
|
||||
const Ifocused = styled.i`{
|
||||
font-size:16px!important;
|
||||
color:#FFA802;
|
||||
margin-right:4px;
|
||||
}`
|
||||
const Ifocus = styled.i`{
|
||||
font-size:16px!important;
|
||||
color:#BBBBBB;
|
||||
margin-right:4px;
|
||||
height:17px;
|
||||
line-height:17px;
|
||||
}`
|
||||
const Div = styled.div`{
|
||||
margin-bottom: 18px;
|
||||
|
@ -56,17 +42,18 @@ const Div = styled.div`{
|
|||
align-items: center;
|
||||
border:1px solid #eee;
|
||||
}`
|
||||
export default (({img,name,time, focusStatus})=>{
|
||||
return(
|
||||
export default (({ user , img, name, time, focusStatus, is_current_user, login , successFunc }) => {
|
||||
return (
|
||||
<Div>
|
||||
<Img src={img}/>
|
||||
<Link to={`/users/${user && user.login}`}><Img src={getImageUrl(`images/${img}`)} /></Link>
|
||||
<div className="m-infos">
|
||||
<Name>{name}</Name>
|
||||
<Link to={`/users/${user && user.login}`}><Name>{name}</Name></Link>
|
||||
<Time><I className="iconfont icon-shijian"></I>加入时间:{time}</Time>
|
||||
{
|
||||
focusStatus ?
|
||||
<FocusBtn><Ifocused className="iconfont icon-shixing"></Ifocused>已关注</FocusBtn> :
|
||||
<FocusBtn><Ifocus className="iconfont icon-kongxing"></Ifocus>关注</FocusBtn>
|
||||
is_current_user ?
|
||||
<Button type="default">当前用户</Button>
|
||||
:
|
||||
<FocusButton is_watch={focusStatus} id={login} successFunc={successFunc}/>
|
||||
}
|
||||
</div>
|
||||
</Div>
|
||||
|
|
|
@ -2,13 +2,15 @@ import React from "react";
|
|||
import { Input } from "antd";
|
||||
|
||||
const { Search } = Input;
|
||||
export default ({ placeholder , onSearch }) => {
|
||||
export default ({ placeholder , onSearch , onChange }) => {
|
||||
return (
|
||||
<Search
|
||||
allowClear
|
||||
placeholder={placeholder}
|
||||
enterButton={'搜索'}
|
||||
onSearch={onSearch}
|
||||
width="300px"
|
||||
onChange={onChange}
|
||||
></Search>
|
||||
)
|
||||
};
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React , { useState , useEffect } from 'react';
|
||||
import React , { useState } from 'react';
|
||||
import { AutoComplete } from 'antd';
|
||||
import { getImageUrl } from "educoder";
|
||||
import axios from 'axios';
|
||||
|
||||
const Option = AutoComplete.Option;
|
||||
|
||||
export default ({ getUser })=>{
|
||||
|
@ -31,18 +32,14 @@ export default ({ getUser })=>{
|
|||
}
|
||||
|
||||
function selectInputUser(id, option){
|
||||
setSearchKey(option.props.searchValue);
|
||||
getUserList(option.props.searchValue);
|
||||
setSearchKey(option.props.value);
|
||||
getUserList(option.props.value);
|
||||
getUser && getUser(id);
|
||||
}
|
||||
const source =
|
||||
userDataSource && userDataSource.map((item, key) => {
|
||||
return (
|
||||
<Option
|
||||
key={key}
|
||||
value={`${item.user_id}`}
|
||||
searchValue={`${item.username}`}
|
||||
>
|
||||
<Option key={key} value={`${item.login}`}>
|
||||
<img
|
||||
className="user_img radius"
|
||||
width="28"
|
||||
|
|
|
@ -61,22 +61,24 @@ export const Redline = styled.a`{
|
|||
line-height:28px;
|
||||
border-radius:2px;
|
||||
border:1px solid #F73030;
|
||||
color:#F73030;
|
||||
color:${props => (props.bold ? "#fff" : "#F73030")} !important;
|
||||
padding:0px 12px;
|
||||
display:inline-block;
|
||||
min-width:80px;
|
||||
text-align:center;
|
||||
background:${props => (props.bold ? "#F73030" : "#fff")};
|
||||
}`
|
||||
export const Greenline = styled.a`{
|
||||
height:30px;
|
||||
line-height:28px;
|
||||
border-radius:2px;
|
||||
border:1px solid #28BD6C;
|
||||
color:#28BD6C;
|
||||
color:${props => (props.bold ? "#fff" : "#28BD6C")} !important;
|
||||
padding:0px 12px;
|
||||
display:inline-block;
|
||||
min-width:80px;
|
||||
text-align:center;
|
||||
background:${props => (props.bold ? "#28BD6C" : "#fff")};
|
||||
}`
|
||||
export const Greenback = styled.a`{
|
||||
height:30px;
|
||||
|
@ -94,7 +96,7 @@ export const Blueback = styled.a`{
|
|||
line-height:30px;
|
||||
border-radius:2px;
|
||||
background-color:rgba(80,145,255,1);
|
||||
color:#fff;
|
||||
color:#fff!important;
|
||||
padding:0px 12px;
|
||||
display:inline-block;
|
||||
min-width:80px;
|
||||
|
@ -154,3 +156,9 @@ export const Content = styled.div`{
|
|||
background-color:#fff;
|
||||
justify-content: center;
|
||||
}`
|
||||
export const GroupProjectBackgroup = styled.div`{
|
||||
background:#fafafa;
|
||||
padding:20px 30px;
|
||||
width:100%;
|
||||
}`
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React, { forwardRef, useCallback, useState , useEffect } from "react";
|
||||
import activate from "../Images/activate.png";
|
||||
import { Blueback , AlignCenter } from "../Component/layout";
|
||||
import { AlignCenter, Blueback } from "../Component/layout";
|
||||
import PasswordAuthority from "../Component/PasswordAuthority";
|
||||
import styled from "styled-components";
|
||||
import { Form, Input , Spin , Modal } from "antd";
|
||||
import { Form, Input , Spin , Button } from "antd";
|
||||
import ServiceModal from './ServiceModal';
|
||||
import axios from "axios";
|
||||
|
||||
const P = styled.p`
|
||||
|
@ -20,15 +21,20 @@ const P = styled.p`
|
|||
function About(props, ref) {
|
||||
const { form: { getFieldDecorator, validateFields , setFieldsValue } } = props;
|
||||
const [isSpining, setIsSpining] = useState(true);
|
||||
const [ authorityValBox , setAuthorityValBox ] = useState(false);
|
||||
|
||||
// 认证密码
|
||||
const [ authorityVal , setAuthorityVal ] = useState(undefined);
|
||||
const [ authorityValFlag , setAuthorityValFlag ] = useState(false);
|
||||
|
||||
//0: 标识未开启devops
|
||||
//1: 标识用户已填写了云服务器相关信息,但并未开启认证
|
||||
const [step, setStep] = useState(undefined);
|
||||
// step大于1时:为true,不能再修改服务器信息
|
||||
const [step, setStep] = useState(0);
|
||||
|
||||
const owner = props.match.params.owner;
|
||||
const { user } = props;
|
||||
const projectsId = props.match.params.projectsId;
|
||||
const [ disabled, setDisabled ] = useState(false);
|
||||
const [ typeFlag, setTypeFlag] = useState(false);
|
||||
|
||||
|
||||
const AuthorLogin = props.author && props.author.login;
|
||||
|
@ -47,12 +53,17 @@ function About(props, ref) {
|
|||
method:`${type}`,
|
||||
url
|
||||
}).then(result=>{
|
||||
setIsSpining(false);
|
||||
if(result && result.data ){
|
||||
setIsSpining(false);
|
||||
setStep(result.data.step);
|
||||
setFieldsValue({...result.data.cloud_account});
|
||||
// setStep(0);
|
||||
// setFieldsValue({...result.data.cloud_account});
|
||||
// if(result.data.cloud_account){
|
||||
// setDisabled(true);
|
||||
// }
|
||||
}
|
||||
}).catch(error=>{
|
||||
setIsSpining(false);
|
||||
console.log(error);
|
||||
})
|
||||
}
|
||||
|
@ -72,8 +83,8 @@ function About(props, ref) {
|
|||
function goStep() {
|
||||
validateFields((error, values) => {
|
||||
if (!error) {
|
||||
if(step > 0){
|
||||
setAuthorityValBox(true);
|
||||
if(disabled){
|
||||
setStep(1);
|
||||
}else{
|
||||
setIsSpining(true);
|
||||
const url = `/${owner}/${projectsId}/cloud_accounts.json`;
|
||||
|
@ -82,7 +93,6 @@ function About(props, ref) {
|
|||
if (result && result.data.redirect_url) {
|
||||
props.showNotification("服务器信息配置完成!");
|
||||
setStep(1);
|
||||
setAuthorityValBox(true);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
@ -94,6 +104,50 @@ function About(props, ref) {
|
|||
});
|
||||
}
|
||||
|
||||
// 选择服务器后调用接口,点击下一步(1是自有服务器,2是trustie服务器)
|
||||
function sureModal(type){
|
||||
if(type === 2){
|
||||
setTypeFlag(false);
|
||||
setIsSpining(true);
|
||||
const url = `/users/ci/cloud_account/trustie_bind.json`;
|
||||
axios.post(url,{
|
||||
account:user && user.login
|
||||
}).then(result=>{
|
||||
setIsSpining(false);
|
||||
if(result && result.data){
|
||||
setStep(result.data.step);
|
||||
}
|
||||
}).catch(error=>{setIsSpining(false)})
|
||||
}else{
|
||||
setTypeFlag(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 上一步
|
||||
function goUpStep(step){
|
||||
setTypeFlag(false);
|
||||
setStep(step);
|
||||
}
|
||||
|
||||
// 输入用户密码后下一步
|
||||
function authStep(){
|
||||
if(!authorityVal){
|
||||
setAuthorityValFlag(true);
|
||||
return;
|
||||
}
|
||||
setAuthorityValFlag(false);
|
||||
setIsSpining(true);
|
||||
const url = `/users/ci/oauth_grant.json`;
|
||||
axios.get(url,{
|
||||
params:{password:authorityVal}
|
||||
}).then(result=>{
|
||||
setIsSpining(false);
|
||||
if(result){
|
||||
setStep(result.data.step);
|
||||
}
|
||||
}).catch(error=>{setIsSpining(false)});
|
||||
}
|
||||
|
||||
// 开始激活
|
||||
function startActive(){
|
||||
setIsSpining(true);
|
||||
|
@ -111,73 +165,89 @@ function About(props, ref) {
|
|||
setIsSpining(false);
|
||||
})
|
||||
}
|
||||
// 取消授权-登录密码的输入
|
||||
function cancelAuthority(){
|
||||
setAuthorityValBox(false);
|
||||
}
|
||||
// 确认授权
|
||||
function okAuthority(step){
|
||||
setAuthorityValBox(false);
|
||||
setStep(step);
|
||||
}
|
||||
|
||||
return (
|
||||
<Spin spinning={isSpining}>
|
||||
<PasswordAuthority authorityValBox={authorityValBox} successFunc={okAuthority} cancelFunc={cancelAuthority}></PasswordAuthority>
|
||||
{/* <PasswordAuthority authorityValBox={authorityValBox} successFunc={okAuthority} cancelFunc={cancelAuthority}></PasswordAuthority> */}
|
||||
<div className="activatePanel">
|
||||
<img src={activate} alt="" width="250px" />
|
||||
<P>定义DevOps工作流,帮助您检测bug、发布代码…</P>
|
||||
{
|
||||
CurrentLogin !== AuthorLogin && (step === undefined || (step && step < 1)) &&
|
||||
<div className="noOperation">DevOps开启功能暂未对项目创建者以外的角色开放,可以联系项目创建者进行开启,开启后便可查看构建信息。</div>
|
||||
CurrentLogin !== AuthorLogin ?
|
||||
<div className="noOperation">DevOps开启功能暂未对项目创建者以外的角色开放,可以联系项目创建者进行开启,开启后便可查看构建信息。</div>:""
|
||||
}
|
||||
<a href={"https://forum.trustie.net/forums/3080/detail"} target="_blank" style={{ color: "#5091FF"}}>
|
||||
<a href={"https://forum.trustie.net/forums/3110/detail"} target="_blank" style={{ color: "#5091FF"}}>
|
||||
了解什么是DevOps?
|
||||
</a>
|
||||
<a href={"https://forum.trustie.net/forums/3110/detail"} target="_blank" style={{ color: "#5091FF"}}>
|
||||
<a href={"https://forum.trustie.net/forums/3080/detail"} target="_blank" style={{ color: "#5091FF"}}>
|
||||
如何使用DevOps?
|
||||
</a>
|
||||
{
|
||||
AuthorLogin === CurrentLogin ?
|
||||
<React.Fragment>
|
||||
{ step <=1 ?
|
||||
<React.Fragment>
|
||||
<Input.Password style={{display:'none'}} size="large" />
|
||||
<Form style={{marginTop:"20px"}}>
|
||||
<p className="mb20" style={{width:"370px"}}>请仔细核对您的服务器信息,一旦确认提交将无法修改</p>
|
||||
{helper(
|
||||
"服务器IP地址:",
|
||||
"ip",
|
||||
[{ required: true, message: "请输入服务器IP地址" }],
|
||||
<Input
|
||||
placeholder="请输入服务器IP地址"
|
||||
style={{ width: "368px" }}
|
||||
size="large"
|
||||
disabled={step > 0}
|
||||
/>,
|
||||
true
|
||||
)}
|
||||
{helper(
|
||||
"服务器用户名:",
|
||||
"account",
|
||||
[{ required: true, message: "请输入服务器用户名" }],
|
||||
<Input placeholder="请输入服务器用户名" size="large" disabled={step > 0}/>,
|
||||
true
|
||||
)}
|
||||
{helper(
|
||||
"服务器密码:",
|
||||
"secret",
|
||||
[{ required: true, message: "请输入服务器密码" }],
|
||||
<Input.Password placeholder="请输入服务器密码" size="large" disabled={step > 0}/>,
|
||||
true
|
||||
)}
|
||||
</Form>
|
||||
<Blueback onClick={goStep}>下一步</Blueback>
|
||||
</React.Fragment>
|
||||
:""}
|
||||
{step > 1 &&
|
||||
{
|
||||
step === 0 && !typeFlag ?
|
||||
<ServiceModal sureModal={sureModal}></ServiceModal>
|
||||
:""
|
||||
}
|
||||
{ step === 0 && typeFlag ?
|
||||
<React.Fragment>
|
||||
<Input.Password style={{display:'none'}} size="large" />
|
||||
<Form style={{marginTop:"20px"}}>
|
||||
<p className="mb20" style={{width:"370px"}}>请仔细核对您的服务器信息,一旦确认提交将无法修改</p>
|
||||
{helper(
|
||||
"服务器IP地址:",
|
||||
"ip",
|
||||
[{ required: true, message: "请输入服务器IP地址" }],
|
||||
<Input
|
||||
placeholder="请输入服务器IP地址"
|
||||
style={{ width: "368px" }}
|
||||
size="large"
|
||||
disabled={disabled}
|
||||
/>,
|
||||
true
|
||||
)}
|
||||
{helper(
|
||||
"服务器用户名:",
|
||||
"account",
|
||||
[{ required: true, message: "请输入服务器用户名" }],
|
||||
<Input placeholder="请输入服务器用户名" size="large" disabled={disabled}/>,
|
||||
true
|
||||
)}
|
||||
{helper(
|
||||
"服务器密码:",
|
||||
"secret",
|
||||
[{ required: true, message: "请输入服务器密码" }],
|
||||
<Input.Password placeholder="请输入服务器密码" size="large" disabled={disabled}/>,
|
||||
true
|
||||
)}
|
||||
</Form>
|
||||
<AlignCenter>
|
||||
{ !disabled && <Button className="mr20" onClick={()=>goUpStep(0)}>上一步</Button>}
|
||||
<Blueback onClick={goStep}>下一步</Blueback>
|
||||
</AlignCenter>
|
||||
</React.Fragment>
|
||||
:""
|
||||
}
|
||||
{
|
||||
step === 1 ?
|
||||
<div>
|
||||
<AlignCenter style={{justifyContent:"center",marginTop:"20px"}}>
|
||||
<span style={{marginBottom:"42px"}}>密码:</span>
|
||||
<div>
|
||||
<Input.Password value={authorityVal} className={authorityValFlag===true && "flags"} onChange={(e)=>setAuthorityVal(e.target.value)} style={{width:"220px"}}></Input.Password>
|
||||
<p className="color-grey-9" style={{textAlign:"left",lineHeight:'21px'}}>您已保存相关服务器信息,请输入密码,<br/>确认授权DevOps应用</p>
|
||||
</div>
|
||||
</AlignCenter>
|
||||
<AlignCenter style={{justifyContent:'center'}}>
|
||||
<Blueback onClick={authStep} className="mt20">下一步</Blueback>
|
||||
</AlignCenter>
|
||||
</div>:""
|
||||
}
|
||||
{ step === 2 ?
|
||||
<div style={{textAlign:'center',marginTop:"20px"}}>
|
||||
<Blueback onClick={startActive} className="mt20">开始激活</Blueback>
|
||||
</div>
|
||||
</div>:""
|
||||
}
|
||||
</React.Fragment>
|
||||
:""
|
||||
|
|
|
@ -1,118 +1,162 @@
|
|||
import React , { useState , useEffect } from 'react';
|
||||
import { Spin } from 'antd';
|
||||
import { Spin , Pagination } from 'antd';
|
||||
import { Blueback } from '../Component/layout';
|
||||
import Editor from "react-monaco-editor";
|
||||
import Modals from './DisposeModal';
|
||||
import FileLanguage from '../Component/OpsFileLanguage';
|
||||
import List from './Dispose/List';
|
||||
import Head from './Dispose/head';
|
||||
import axios from 'axios';
|
||||
import PipelineName from './Dispose/PipelineName';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Div = styled.div`{
|
||||
padding:24px 30px;
|
||||
}`;
|
||||
const limit = 15;
|
||||
function Dispose(props){
|
||||
const [ spining , setSpining ] = useState(true);
|
||||
const [ info , setInfo ] = useState('.trustie-pipeline.yml');
|
||||
const [ spining , setSpining ] = useState(true);
|
||||
const [ updateInfo , setUpdateInfo ] = useState(undefined);
|
||||
const [ list , setList ] = useState(undefined);
|
||||
const [ permission , setPermission ] = useState(undefined);
|
||||
const [ visible , setVisible ] = useState(false);
|
||||
const [ ymlValue , setYmlValue ] = useState("");
|
||||
const [ sha , setSha ] = useState(undefined);
|
||||
const [ fileLanguage , setFileLanguage ] = useState(undefined);
|
||||
const [ first , setFirst ] = useState(false);
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ totalCount , setTotalCount ] = useState(0);
|
||||
const [ branchList , setBranchList ] =useState(undefined);
|
||||
|
||||
|
||||
const projectDetail = props.projectDetail;
|
||||
const current_user = props.current_user;
|
||||
let projectsId = props.match.params.projectsId;
|
||||
let owner = props.match.params.owner;
|
||||
|
||||
// 获取用户的身份角色
|
||||
useEffect(()=>{
|
||||
if(projectsId){
|
||||
const url = `/${owner}/${projectsId}/get_trustie_pipeline.json`;
|
||||
axios.get(url,{
|
||||
params:{
|
||||
project_id:projectsId
|
||||
}
|
||||
}).then(result=>{
|
||||
if(result && result.data.content){
|
||||
setInfo(result.data.name);
|
||||
setYmlValue(result.data.content);
|
||||
setFirst(true);
|
||||
setSha(result.data.sha);
|
||||
}else{
|
||||
setFirst(false);
|
||||
}
|
||||
if(projectDetail){
|
||||
setPermission(props.projectDetail.permission);
|
||||
}
|
||||
},[projectDetail])
|
||||
|
||||
function Init(){
|
||||
const url = `/ci/pipelines/list.json`;
|
||||
axios.get(url,{
|
||||
params:{
|
||||
identifier:projectsId,owner,
|
||||
page,limit
|
||||
}
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setList(result.data.pipelines);
|
||||
setSpining(false);
|
||||
}).catch(error=>{
|
||||
console.log(error);
|
||||
})
|
||||
}
|
||||
},[projectsId])
|
||||
|
||||
// 修改文件内容
|
||||
function changeEditor(value){
|
||||
setYmlValue(value);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
// 切换语言
|
||||
function select_language(value,array){
|
||||
setFileLanguage(value);
|
||||
setYmlValue( array && array.content);
|
||||
}
|
||||
useEffect(()=>{
|
||||
Init();
|
||||
},[page])
|
||||
|
||||
// 确定提交
|
||||
function submit(){
|
||||
let url = '';
|
||||
const { defaultBranch } = props;
|
||||
let params = {
|
||||
branch: defaultBranch,
|
||||
content:ymlValue,
|
||||
filepath:info,
|
||||
message:''
|
||||
}
|
||||
if(first){
|
||||
// 为true,则是编辑否则是新建
|
||||
url = `/${owner}/${projectsId}/update_trustie_pipeline.json`;
|
||||
axios.put(url,{
|
||||
...params,
|
||||
sha
|
||||
}).then(result=>{
|
||||
if(result){
|
||||
setVisible(true);
|
||||
useEffect(()=>{
|
||||
if(owner && projectsId){
|
||||
const url = `/${owner}/${projectsId}/branches.json`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
setBranchList(result.data);
|
||||
}
|
||||
}).catch(error=>{
|
||||
console.log(error);
|
||||
})
|
||||
}).catch(error=>{})
|
||||
}
|
||||
},[owner,projectsId])
|
||||
|
||||
// 新增/编辑流水线
|
||||
function addNew(pipeline_name,id,branch,event){
|
||||
setVisible(true);
|
||||
setUpdateInfo(undefined);
|
||||
if(pipeline_name){
|
||||
let eventA = event.split(",");
|
||||
let l = {pipeline_name,id,branch,event:eventA}
|
||||
setUpdateInfo(l);
|
||||
}else{
|
||||
url = `/${owner}/${projectsId}/create_file.json`;
|
||||
axios.post(url,params).then(result=>{
|
||||
if(result){
|
||||
setVisible(true);
|
||||
}
|
||||
}).catch(error=>{
|
||||
console.log(error);
|
||||
})
|
||||
setUpdateInfo(undefined);
|
||||
}
|
||||
}
|
||||
function suresubmit(){
|
||||
setVisible(false);
|
||||
props.history.push(`/projects/${owner}/${projectsId}/devops/list`);
|
||||
|
||||
function onOk(pipeline_name,updateId,branch,event){
|
||||
if(pipeline_name){
|
||||
let eventStr = "";
|
||||
for(var i = 0;i<event.length;i++){
|
||||
eventStr +=event[i]+",";
|
||||
}
|
||||
eventStr = eventStr.substring(0,eventStr.length-1);
|
||||
if(!updateId){
|
||||
// 新增
|
||||
const url = `/ci/pipelines.json`;
|
||||
axios.post(url,{
|
||||
pipeline_name,
|
||||
file_name:".trustie-pipeline.yml",
|
||||
repo:projectsId,branch,event:eventStr,owner
|
||||
}).then(result=>{
|
||||
setVisible(false);
|
||||
if(result && result.data){
|
||||
props.showNotification("流水线新增成功,请进行工作流配置!");
|
||||
props.history.push(`/projects/${owner}/${projectsId}/devops/dispose/${result.data.id}`);
|
||||
}else{
|
||||
props.showNotification("流水线新增失败,请稍后再试!");
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}else{
|
||||
// 修改
|
||||
const url = `/ci/pipelines/${updateId}.json`;
|
||||
axios.put(url,{
|
||||
pipeline_name,repo:projectsId,branch,event:eventStr,owner
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setVisible(false);
|
||||
Init();
|
||||
props.showNotification("流水线名称更新成功!");
|
||||
}else{
|
||||
props.showNotification("流水线名称更新失败,请稍后再试!");
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
}else{
|
||||
props.showNotification("请输入流水线名称!");
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
function deleteFunc(id){
|
||||
const url = `/ci/pipelines/${id}.json`;
|
||||
axios.delete(url).then(result=>{
|
||||
if(result && result.data){
|
||||
props.showNotification("流水线删除成功!");
|
||||
Init();
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
// 模板管理
|
||||
function toModalManage(){
|
||||
props.history.push(`/projects/${owner}/${projectsId}/devops/mould`);
|
||||
}
|
||||
|
||||
const operate = current_user && (permission && permission !== "Reporter");
|
||||
|
||||
return(
|
||||
<Spin spinning={spining}>
|
||||
<Modals visible={visible} closeFunc={(flag)=>setVisible(flag)} sureFunc={suresubmit}></Modals>
|
||||
<p>编程语言:</p>
|
||||
|
||||
<div className="mt20 mb20">
|
||||
<FileLanguage language={fileLanguage} select_language={select_language}/>
|
||||
<PipelineName branchList={branchList} visible={visible} value={updateInfo} onCancel={()=>setVisible(false)} onOk={onOk}/>
|
||||
<div className="disposePanel">
|
||||
<Head manager={ operate ? toModalManage : undefined} />
|
||||
<Div>
|
||||
{ operate && <Blueback onClick={()=>addNew(undefined,undefined)}>新增流水线</Blueback> }
|
||||
<div className="mt20 disposeList">
|
||||
<List list={list} operate={operate} projectsId={projectsId} owner={owner} showModal={addNew} deleteFunc={deleteFunc}/>
|
||||
{
|
||||
totalCount > limit &&
|
||||
<div className="mt20 pb20" style={{textAlign:'center'}}>
|
||||
<Pagination simple current={page} pageSize={limit} total={totalCount} onChange={(page)=>setPage(page)}/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Div>
|
||||
</div>
|
||||
<p>配置脚本:</p>
|
||||
<div className="editorBody">
|
||||
<p className="editorHead">{info}</p>
|
||||
<Editor
|
||||
height="300px"
|
||||
language={"yml"}
|
||||
theme={"vs-grey"}
|
||||
defaultValue="请输入内容"
|
||||
value={ymlValue}
|
||||
options={"editor_options"}
|
||||
onChange={changeEditor}
|
||||
></Editor>
|
||||
</div>
|
||||
<Blueback onClick={submit}>确定提交</Blueback>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
import React , { useEffect , useState } from 'react';
|
||||
|
||||
function Choosen({ chooseFunc, temp , templateId , category }){
|
||||
const [ tempId, setTemId ] = useState(undefined);
|
||||
const [ cate , setCate ]= useState(undefined);
|
||||
const [ templates , setTemplates ] = useState(undefined);
|
||||
const [ categories , setCategories ] = useState(undefined);
|
||||
|
||||
useEffect(()=>{
|
||||
if(templateId){
|
||||
setTemId(templateId);
|
||||
}
|
||||
},[templateId])
|
||||
|
||||
useEffect(()=>{
|
||||
if(category){
|
||||
setCate(category);
|
||||
}
|
||||
},[category])
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(temp && temp.length > 0){
|
||||
if(temp[0].category !== "初始化"){
|
||||
setCategories(temp);
|
||||
}else{
|
||||
setCategories(undefined);
|
||||
}
|
||||
if(category && temp[0].category !== "初始化" && category !== "初始化"){
|
||||
let c = temp.filter(item=>item.category === category);
|
||||
let t = c && c.length > 0 && c[0].templates;
|
||||
setTemplates(t);
|
||||
setCate(category);
|
||||
}else{
|
||||
setTemplates(temp[0].templates);
|
||||
setCate(temp[0].category);
|
||||
}
|
||||
}else{
|
||||
setTemplates(undefined);
|
||||
setCate(undefined);
|
||||
setCategories(undefined);
|
||||
}
|
||||
},[temp,category])
|
||||
|
||||
// 选择类别
|
||||
function changeCate(cate){
|
||||
setCate(cate);
|
||||
let c = categories && categories.filter(item=>item.category === cate);
|
||||
let t = c && c[0].templates;
|
||||
setTemplates(t);
|
||||
let m_t_id = t && t.length>0 && t[0].id;
|
||||
let m_t_content = t && t.length>0 && t[0].content;
|
||||
setTemId(m_t_id);
|
||||
chooseFunc && chooseFunc(m_t_content,m_t_id,cate);
|
||||
}
|
||||
|
||||
|
||||
// 选择模板
|
||||
function chooseOption(id){
|
||||
let item = templates.filter(item=>item.id === id);
|
||||
let content = item && item.length >0 && item[0].content;
|
||||
chooseFunc && chooseFunc(content,id,cate);
|
||||
setTemId(id);
|
||||
}
|
||||
|
||||
return(
|
||||
<React.Fragment>
|
||||
{
|
||||
categories && categories.length > 0 &&
|
||||
<div className="choosenList">
|
||||
<span>模板类别:</span>
|
||||
<ul>
|
||||
{
|
||||
categories.map((item,key)=>{
|
||||
return(
|
||||
<li className={cate === item.category ?"active":""} onClick={()=>changeCate(item.category)}>{item.category}</li>)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
templates && templates.length> 0 &&
|
||||
<div className="choosenList">
|
||||
<span>模板选择:</span>
|
||||
<ul>
|
||||
{
|
||||
templates.map((item,key)=>{
|
||||
return(
|
||||
<li className={tempId === item.id ? "active":""} onClick={()=>chooseOption(item.id)}>{item.template_name}</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
export default Choosen;
|
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
import Editor from 'react-monaco-editor';
|
||||
|
||||
function Editors({value,onChange,theme,height,visible,width="100%",Numbers="on"}){
|
||||
const editor_options = {
|
||||
lineNumbers: Numbers,
|
||||
wordWrap: true, //强制换行
|
||||
selectOnLineNumbers: true,
|
||||
lineHeight: 24,
|
||||
renderLineHighlight: "line",
|
||||
revealHorizontalRightPadding: 5,
|
||||
placeholder:"请输入内容",
|
||||
readOnly: visible,
|
||||
cursorStyle: visible ? "underline-thin" : "line",
|
||||
folding: true,
|
||||
foldingStrategy: "indentation", // 代码可分小段折叠
|
||||
automaticLayout: true, // 自适应布局
|
||||
minimap: {
|
||||
// 不要小地图
|
||||
enabled: false,
|
||||
},
|
||||
}
|
||||
return(
|
||||
<Editor
|
||||
height={height}
|
||||
width={width}
|
||||
language={"yaml"}
|
||||
theme={theme}
|
||||
placeholder="请输入内容"
|
||||
value={value}
|
||||
options={editor_options}
|
||||
onChange={(value)=>onChange(value)}
|
||||
disabled={true}
|
||||
></Editor>
|
||||
)
|
||||
}
|
||||
export default Editors;
|
|
@ -0,0 +1,66 @@
|
|||
import React , { useEffect , useState } from 'react';
|
||||
import { Button } from 'antd';
|
||||
import Choosen from './Choosen';
|
||||
import Editors from './Editors';
|
||||
|
||||
function Init({ datas , templates , saveFunc , saveDatas}){
|
||||
const [ templateId , setTemplateId ] = useState(undefined);
|
||||
const [ ymlValue , setYmlValue ] = useState(undefined);
|
||||
const [ temp , setTemp ] = useState(undefined);
|
||||
|
||||
useEffect(()=>{
|
||||
if(templates && templates.length > 0){
|
||||
setTemp(templates)
|
||||
}
|
||||
},[templates])
|
||||
|
||||
useEffect(()=>{
|
||||
if(datas && datas.length > 0){
|
||||
setTemplateId(datas[0].template_id);
|
||||
setYmlValue(datas[0].content);
|
||||
}
|
||||
},[datas])
|
||||
|
||||
// 选择模板
|
||||
function chooseFunc(content,id,cate){
|
||||
setTemplateId(id);
|
||||
setYmlValue(content);
|
||||
recieveData(id,content);
|
||||
}
|
||||
|
||||
function recieveData(id,content){
|
||||
let steps = datas;
|
||||
if(datas && datas.length>0){
|
||||
steps[0].content = content || ymlValue;
|
||||
steps[0].template_id = id || templateId ;
|
||||
}else{
|
||||
steps =
|
||||
[{
|
||||
step_name:"初始化",
|
||||
show_index:1,
|
||||
content:content || ymlValue,
|
||||
template_id:id || templateId
|
||||
}]
|
||||
}
|
||||
saveDatas(steps);
|
||||
}
|
||||
|
||||
// 点击下一步时
|
||||
function nextStep(){
|
||||
recieveData();
|
||||
saveFunc(undefined,undefined,undefined,undefined,"next");
|
||||
}
|
||||
|
||||
return(
|
||||
<div>
|
||||
<Choosen chooseFunc={chooseFunc} templateId={templateId} temp={temp}/>
|
||||
<div className="mt15">
|
||||
<Editors value={ymlValue} onChange={setYmlValue} theme={"vs-dark"} height={"400px"}/>
|
||||
</div>
|
||||
<div className="mt20">
|
||||
<Button type={"primary"} onClick={nextStep}>下一步</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Init;
|
|
@ -0,0 +1,97 @@
|
|||
import React from 'react';
|
||||
import { Table , Popconfirm } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const STATUS = {
|
||||
running:"运行中",
|
||||
failure:"未通过",
|
||||
error:"未通过",
|
||||
success:"已通过",
|
||||
killed:"已撤销",
|
||||
pending:"准备中"
|
||||
}
|
||||
function List({ list, operate , projectsId , owner , showModal , deleteFunc }){
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title:"流水线名称",
|
||||
dataIndex:"pipeline_name",
|
||||
key:1,
|
||||
ellipsis:true,
|
||||
render:(txt,item)=>{
|
||||
return(
|
||||
<span onDoubleClick={()=>showModal(txt,item.id,item.branch,item.event)} style={{display:"block",cursor:"pointer"}}>{txt}</span>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title:"文件名称",
|
||||
dataIndex:"file_name",
|
||||
key:1,
|
||||
width:"15%",
|
||||
ellipsis:true,
|
||||
render:(value,item)=>{
|
||||
return(
|
||||
<Link to={`/projects/${owner}/${projectsId}/branch/${item.branch}?url=${value}`} className="color-blue">{value}</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title:"触发分支",
|
||||
dataIndex:"branch",
|
||||
key:1,
|
||||
width:"10%",
|
||||
ellipsis:true
|
||||
},
|
||||
{
|
||||
title:"触发事件",
|
||||
dataIndex:"event",
|
||||
key:1,
|
||||
width:"10%",
|
||||
ellipsis:true
|
||||
},
|
||||
{
|
||||
title:"最近构建时间",
|
||||
dataIndex:"last_build_time",
|
||||
key:1,
|
||||
width:"15%",
|
||||
ellipsis:true
|
||||
},
|
||||
{
|
||||
title:"最近构建状态",
|
||||
dataIndex:"pipeline_status",
|
||||
key:1,
|
||||
width:"10%",
|
||||
ellipsis:true,
|
||||
render:(txt)=>{
|
||||
return(STATUS[txt])
|
||||
}
|
||||
},
|
||||
{
|
||||
title:"操作",
|
||||
dataIndex:"operation",
|
||||
key:1,
|
||||
width:"21%",
|
||||
render:(txt,item)=>{
|
||||
return(
|
||||
<span>
|
||||
{ operate ?
|
||||
<Link to={`/projects/${owner}/${projectsId}/devops/dispose/${item.id}`} className="mr10 color-grey-6">
|
||||
<i className="iconfont icon-zaibianji font-13 mr3"></i>编辑</Link> :""
|
||||
}
|
||||
{ operate ?
|
||||
<Popconfirm title={"确定要删除此流水线?"} onConfirm={()=>deleteFunc(item.id)} okText="确定" cancelText={"取消"}>
|
||||
<a className="mr10 color-grey-6"><i className="iconfont icon-lajitong font-13 mr3"></i>删除</a>
|
||||
</Popconfirm>:""
|
||||
}
|
||||
<Link to={`/projects/${owner}/${projectsId}/devops/list/${item.branch}`} className="color-grey-6"><i className="iconfont icon-yunhang font-13 mr3"></i>查看运行记录</Link>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
return(
|
||||
<Table size="small" columns={columns} dataSource={list} rowKey={(row)=>row.id} pagination={false}></Table>
|
||||
)
|
||||
}
|
||||
export default List;
|
|
@ -0,0 +1,67 @@
|
|||
import React , { useEffect , useState } from 'react';
|
||||
import { Modal , Input , Select } from 'antd';
|
||||
const { Option }= Select;
|
||||
|
||||
const EVENT = ["push","pull_request","tag","cron","custom","promote","rollback"]
|
||||
function PipelineName({visible,onCancel,onOk,value ,branchList}){
|
||||
const [ name , setName ] = useState(undefined);
|
||||
const [ branchValue , setBranchValue ] = useState(undefined);
|
||||
const [ eventValue , setEventValue ] = useState([EVENT[0]]);
|
||||
|
||||
useEffect(()=>{
|
||||
if(branchList && branchList.length>0){
|
||||
setBranchValue(branchList[0].name);
|
||||
}
|
||||
},[branchList])
|
||||
|
||||
useEffect(()=>{
|
||||
if(value){
|
||||
setName(value.pipeline_name);
|
||||
setBranchValue(value.branch);
|
||||
setEventValue(value.event);
|
||||
}else{
|
||||
setName(undefined);
|
||||
}
|
||||
},[value])
|
||||
|
||||
function onSure(){
|
||||
onOk(name,value && value.id,branchValue,eventValue);
|
||||
}
|
||||
return(
|
||||
<Modal
|
||||
visible={visible}
|
||||
title="流水线名称"
|
||||
width={"500px"}
|
||||
onCancel={onCancel}
|
||||
onOk={onSure}
|
||||
centered={true}
|
||||
>
|
||||
<div className="choosenList">
|
||||
<span>流水线名称:</span>
|
||||
<Input value={name} onChange={(e)=>setName(e.target.value)} placeholder="请输入名称" style={{width:"340px",margin:"6px 0px"}}/>
|
||||
</div>
|
||||
<div className="choosenList mt20">
|
||||
<span>触发条件:</span>
|
||||
<Select value={branchValue} style={{width:"150px"}} onChange={(e)=>setBranchValue(e)}>
|
||||
{
|
||||
branchList && branchList.length>0 && branchList.map((item,key)=>{
|
||||
return(
|
||||
<Option value={item.name} key={key}>{item.name}</Option>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
<Select mode="multiple" allowClear value={eventValue} style={{width:"180px",marginLeft:"10px"}} onChange={(e)=>{console.log(e);setEventValue(e)}}>
|
||||
{
|
||||
EVENT.map((item,key)=>{
|
||||
return(
|
||||
<Option value={item} key={key}>{item}</Option>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
export default PipelineName;
|
|
@ -0,0 +1,102 @@
|
|||
import React , { useEffect , useState } from 'react';
|
||||
import { Button , Popconfirm } from 'antd';
|
||||
import { Cancel } from '../../Component/layout';
|
||||
import Item from './StageItem';
|
||||
|
||||
function Stage({
|
||||
templates ,
|
||||
datas ,
|
||||
saveDatas ,
|
||||
saveFunc ,
|
||||
stepName ,
|
||||
deleteStep ,
|
||||
deleteFunc ,
|
||||
deleteFlag
|
||||
}){
|
||||
const [ stepList , setStepList ] = useState(undefined);
|
||||
const [ temp , setTemp ] = useState(undefined);
|
||||
|
||||
useEffect(()=>{
|
||||
if(templates && templates.length > 0){
|
||||
setTemp(templates);
|
||||
}
|
||||
},[templates])
|
||||
|
||||
useEffect(()=>{
|
||||
if(datas){
|
||||
if(datas.length > 0 && stepList !== datas){
|
||||
setStepList(datas);
|
||||
}else if(datas.length === 0){
|
||||
let list = [];
|
||||
setStepList(list);
|
||||
}
|
||||
}
|
||||
},[datas])
|
||||
|
||||
// 添加步骤
|
||||
function addFunc(){
|
||||
let list = stepList;
|
||||
let length = list ? list.length : 0;
|
||||
let pre = temp && temp.length > 0 && temp[0];
|
||||
let c = pre && pre.category;
|
||||
let child = pre && pre.templates && pre.templates.length > 0 && pre.templates[0];
|
||||
let step =
|
||||
{
|
||||
"category":c,
|
||||
"step_name": stepName+`${length + 1}`,
|
||||
"show_index": length + 1,
|
||||
"content":child.content,
|
||||
"template_id":child.id,
|
||||
"hide":false
|
||||
}
|
||||
list.push(step);
|
||||
saveDatas(list);
|
||||
}
|
||||
|
||||
// 将修改的对应项保存在数组中
|
||||
function saveItems(content,id,cate,key){
|
||||
let item = stepList;
|
||||
item[key].content = content;
|
||||
item[key].template_id = id;
|
||||
item[key].category = cate;
|
||||
saveDatas([...item]);
|
||||
}
|
||||
|
||||
function slideItems(key,hide){
|
||||
let item = stepList;
|
||||
item[key].hide = !hide;
|
||||
setStepList([...item]);
|
||||
saveDatas(item);
|
||||
}
|
||||
|
||||
function deleteItem(id,key){
|
||||
deleteStep(id,key);
|
||||
}
|
||||
// 点击下一步时
|
||||
function nextStep(btn){
|
||||
saveFunc(undefined,undefined,undefined,undefined,btn);
|
||||
}
|
||||
|
||||
return(
|
||||
<div>
|
||||
{
|
||||
stepList && stepList.length > 0 && stepList.map((item,key)=>{
|
||||
return(
|
||||
<Item item={item} templates={temp} k={key} saveItems={saveItems} slideItems={slideItems} deleteStep={deleteItem}/>
|
||||
)
|
||||
})
|
||||
}
|
||||
<a className="addStageBtn" onClick={addFunc}>+ 添加步骤</a>
|
||||
<div className="mt20">
|
||||
<Button type="primary" onClick={()=>nextStep("last")}>上一步</Button>
|
||||
<Button className="ml20" type="primary" onClick={()=>nextStep("next")}>下一步</Button>
|
||||
{!deleteFlag &&
|
||||
<Popconfirm title={'确定要删除当前阶段吗'} okText="是" cancelText="否" onConfirm={deleteFunc}>
|
||||
<Cancel className="ml20">删除</Cancel>
|
||||
</Popconfirm>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Stage;
|
|
@ -0,0 +1,41 @@
|
|||
import React from 'react';
|
||||
import { FlexAJ } from '../../Component/layout';
|
||||
import Editors from './Editors';
|
||||
import Choosen from './Choosen';
|
||||
import { Popconfirm } from 'antd';
|
||||
|
||||
|
||||
function StageItem({item, templates,saveItems,k, slideItems , deleteStep}){
|
||||
|
||||
function onChangevalue(value){
|
||||
saveItems(value,item.template_id,item.category,k);
|
||||
}
|
||||
// 选择模板
|
||||
function chooseFunc(content,id,cate){
|
||||
saveItems(content,id,cate,k);
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="stepsItem">
|
||||
<FlexAJ className="stepsHead">
|
||||
<span>{item.step_name}</span>
|
||||
<span className="color-grey-9">
|
||||
<Popconfirm
|
||||
title={"确定要删除这个步骤吗?"}
|
||||
okText="是"
|
||||
cancelText="否"
|
||||
onConfirm={() => deleteStep(item.id,k)}
|
||||
><a>
|
||||
<i className="iconfont icon-lajitong1 font-14"></i></a>
|
||||
</Popconfirm>
|
||||
<a onClick={()=>slideItems(k,item.hide)}><i className={ (!item.hide || item.hide === false) ? "iconfont icon-sanjiaoxing-down font-14" :"iconfont icon-triangle font-14"}></i></a>
|
||||
</span>
|
||||
</FlexAJ>
|
||||
<div className={(!item.hide || item.hide === false) ? "stepsBody active" : "stepsBody"}>
|
||||
<Choosen chooseFunc={chooseFunc} category={item.category} templateId={item.template_id} temp={templates}/>
|
||||
<Editors value={item.content} onChange={onChangevalue} theme="vs-dark" height="270px" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default StageItem;
|
|
@ -0,0 +1,32 @@
|
|||
import React ,{useEffect , useState} from 'react';
|
||||
import { Button } from 'antd';
|
||||
import Editors from './Editors';
|
||||
|
||||
function Sure({datas , name , saveFunc , sureSubmit , loading}){
|
||||
const [ value , setValue ] = useState(undefined);
|
||||
useEffect(()=>{
|
||||
if(datas && datas.content){
|
||||
setValue(datas.content)
|
||||
}
|
||||
},[datas]);
|
||||
|
||||
function sure(){
|
||||
sureSubmit();
|
||||
}
|
||||
|
||||
return(
|
||||
<div>
|
||||
<div style={{padding:"0px 15px 15px 15px"}}>
|
||||
工作流名称:{name}
|
||||
</div>
|
||||
<div className="editorBody" style={{marginTop:"0px"}}>
|
||||
<Editors value={value} theme={"vs-grey"} height={"600px"} visible/>
|
||||
</div>
|
||||
<div className="mt20">
|
||||
<Button type={"primary"} onClick={()=>saveFunc(undefined,undefined,undefined,undefined,"last")}>上一步</Button>
|
||||
{ value && <Button type={"primary"} loading={loading} className="ml20" onClick={sure}>确定提交</Button> }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Sure;
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import { AlignCenterBetween , Blueline , FlexAJ } from '../../Component/layout';
|
||||
|
||||
|
||||
function head({manager}){
|
||||
return(
|
||||
<AlignCenterBetween>
|
||||
<span className="font-20">工作流配置</span>
|
||||
<FlexAJ>
|
||||
<a href={`https://forum.trustie.net/forums/3111/detail`} target="_blank" className="color-grey-6"><i className="iconfont icon-tishi1 font-14 mr3"></i>模板使用说明</a>
|
||||
{
|
||||
manager && <Blueline style={{marginLeft:"20px"}} onClick={manager}>模板管理</Blueline>
|
||||
}
|
||||
</FlexAJ>
|
||||
</AlignCenterBetween>
|
||||
)
|
||||
}
|
||||
export default head;
|
|
@ -0,0 +1,40 @@
|
|||
import React from 'react';
|
||||
import MenusRename from './menusRename';
|
||||
import MenusAdd from './menusAdd';
|
||||
|
||||
const typeIcon = {
|
||||
init:"icon-initialize",build:"icon-structure",deploy:"icon-arrange",customize:"icon-newStage",confirm:'icon-sure'
|
||||
}
|
||||
|
||||
function Menus({step,changeStep, menuList , renameFunc , checkDatas , addFunc }){
|
||||
|
||||
function InitActive(key,stage_type,stage_id,stage_name){
|
||||
changeStep(key,stage_type,stage_id,stage_name);
|
||||
}
|
||||
|
||||
// 新增阶段
|
||||
function getName(name,index){
|
||||
addFunc && addFunc(name,index);
|
||||
}
|
||||
|
||||
return(
|
||||
<ul className="menus">
|
||||
{
|
||||
menuList && menuList.length > 0 && menuList.map((item,key)=>{
|
||||
return(
|
||||
<React.Fragment key={item.id} >
|
||||
<li onClick={()=>InitActive(item.show_index,item.stage_type,item.id,item.stage_name)} className={item.show_index === step ?"active":""}>
|
||||
<i className={`iconfont ${typeIcon[`${item.stage_type}`]}`}></i>
|
||||
<MenusRename renameFunc={renameFunc} id={item.id} name={item.stage_name} edit={item.stage_type !== "init" && item.stage_type !== "confirm"}/>
|
||||
</li>
|
||||
{ key !== (menuList.length-1) && menuList.length < 7 ?
|
||||
<MenusAdd checkDatas={checkDatas} k={key+2} getName={getName}/>:""
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
export default Menus;
|
|
@ -0,0 +1,55 @@
|
|||
import React , { useState , useEffect } from 'react';
|
||||
import { Input } from 'antd';
|
||||
|
||||
function menusAdd ({ getName , checkDatas , k }){
|
||||
const [ show, setShow ] = useState(false);
|
||||
const [ value , setValue ] = useState(undefined);
|
||||
const [ index , setIndex ] = useState(undefined);
|
||||
const [ref, setRef ] = useState(undefined);
|
||||
const [put, setPut ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (put && ref) {
|
||||
ref.focus();
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
if (k) {
|
||||
setIndex(k);
|
||||
}
|
||||
},[k])
|
||||
|
||||
|
||||
function blurInput(){
|
||||
if(value){
|
||||
getName(value , index);
|
||||
}
|
||||
setValue(undefined);
|
||||
setShow(false);
|
||||
setPut(false);
|
||||
}
|
||||
|
||||
function showInput(){
|
||||
let c = checkDatas();
|
||||
if(c || c === "" ){
|
||||
setShow(true);
|
||||
setPut(true);
|
||||
}
|
||||
}
|
||||
|
||||
return(
|
||||
<li className="menuAdd">
|
||||
{ !show && <i className="iconfont icon-tianjia" onClick={showInput}></i> }
|
||||
<Input
|
||||
ref={(el) => setRef(el)}
|
||||
size={"small"}
|
||||
maxLength={8}
|
||||
style={{width:"75px",display : `${show?"block":'none'}`}}
|
||||
placeholder="新阶段名称"
|
||||
value={value}
|
||||
onChange={(e)=>setValue(e.target.value)} onBlur={blurInput}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
export default menusAdd;
|
|
@ -0,0 +1,53 @@
|
|||
import React , { useEffect , useState } from 'react';
|
||||
import { Input } from 'antd';
|
||||
|
||||
function menusRename({ name , edit , id , renameFunc }){
|
||||
const [ n , setN ] = useState(undefined);
|
||||
const [ show , setShow ] = useState(false);
|
||||
const [ref, setRef ] = useState(undefined);
|
||||
const [put, setPut ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (edit && ref) {
|
||||
ref.focus();
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(()=>{
|
||||
if(name){
|
||||
setN(name)
|
||||
}
|
||||
},[name])
|
||||
|
||||
// 显示input框编辑
|
||||
function changeShow(e){
|
||||
e.stopPropagation();
|
||||
setShow(true);
|
||||
setPut(true);
|
||||
}
|
||||
|
||||
function blurInput(e){
|
||||
renameFunc(e.target.value,id);
|
||||
setPut(false);
|
||||
setShow(false);
|
||||
}
|
||||
return(
|
||||
<div className="aboutEdit">
|
||||
<span className="operateName">
|
||||
{ !show && n }
|
||||
<Input
|
||||
ref={(el) => setRef(el)}
|
||||
value={n}
|
||||
size="small"
|
||||
maxLength={8}
|
||||
onClick={(e)=>e.stopPropagation()}
|
||||
onBlur={blurInput}
|
||||
style={{width:"75px",display : `${show?"block":'none'}`}}
|
||||
onChange={(e)=>setN(e.target.value)}
|
||||
/>
|
||||
{ !show && edit && <i className="iconfont icon-editUnder font-16 color-grey-9" onClick={changeShow}></i> }
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default menusRename
|
|
@ -10,40 +10,55 @@ const About = Loadable({
|
|||
loader: () => import('./About'),
|
||||
loading: Loading,
|
||||
})
|
||||
const Infos = Loadable({
|
||||
loader: () => import('./Infos'),
|
||||
const New = Loadable({
|
||||
loader: () => import('./disposePipeline'),
|
||||
loading: Loading,
|
||||
})
|
||||
const Dispose = Loadable({
|
||||
loader: () => import('./Dispose'),
|
||||
loading: Loading,
|
||||
})
|
||||
const Stucture = Loadable({
|
||||
loader: () => import('./Structure'),
|
||||
loading: Loading,
|
||||
})
|
||||
const Mould = Loadable({
|
||||
loader: () => import('./Mould'),
|
||||
loading: Loading,
|
||||
})
|
||||
export default ((props)=>{
|
||||
const { projectsId , owner } = props.match.params;
|
||||
const open_devops = props.projectDetail && props.projectDetail.open_devops;
|
||||
|
||||
// 工作流:两种状态进入的链接不同
|
||||
useEffect(()=>{
|
||||
if(open_devops !== undefined){
|
||||
if(open_devops){
|
||||
props.history.replace(`/projects/${owner}/${projectsId}/devops/list`);
|
||||
}else{
|
||||
props.history.replace(`/projects/${owner}/${projectsId}/devops`);
|
||||
}
|
||||
}
|
||||
},[open_devops])
|
||||
return(
|
||||
<WhiteBack className="opsPanel">
|
||||
<Switch {...props}>
|
||||
<Route path="/projects/:owner/:projectsId/devops/dispose"
|
||||
<Route path="/projects/:owner/:projectsId/devops/dispose/:disposeId"
|
||||
render={
|
||||
() => (<Infos {...props} />)
|
||||
(p) => (<New {...props} {...p}/>)
|
||||
}
|
||||
></Route>
|
||||
<Route path="/projects/:owner/:projectsId/devops/list"
|
||||
<Route path="/projects/:owner/:projectsId/devops/mould"
|
||||
render={
|
||||
() => (<Infos {...props} />)
|
||||
(p) => (<Mould {...props} {...p}/>)
|
||||
}
|
||||
></Route>
|
||||
<Route path="/projects/:owner/:projectsId/devops/dispose/new"
|
||||
render={
|
||||
(p) => (<New {...props} {...p}/>)
|
||||
}
|
||||
></Route>
|
||||
<Route path="/projects/:owner/:projectsId/devops/dispose"
|
||||
render={
|
||||
(p) => (<Dispose {...props} {...p}/>)
|
||||
}
|
||||
></Route>
|
||||
<Route path="/projects/:owner/:projectsId/devops/list/:branch"
|
||||
render={
|
||||
(p) => (<Stucture {...props} {...p}/>)
|
||||
}
|
||||
></Route>
|
||||
<Route path="/projects/:owner/:projectsId/devops"
|
||||
render={
|
||||
() => (<About {...props} />)
|
||||
(p) => (<About {...props} {...p}/>)
|
||||
}
|
||||
></Route>
|
||||
</Switch>
|
||||
|
|
|
@ -44,13 +44,13 @@ export default ((props)=>{
|
|||
return(
|
||||
<div className="disposePanel">
|
||||
<Banner>
|
||||
{ permission !=="Reporter" && <Link to={`/projects/${owner}/${props.match.params.projectsId}/devops/dispose`} className={menu===false && "color-blue"} style={{ marginRight:"66px"}}>工作流配置</Link>}
|
||||
<Link to={`/projects/${owner}/${props.match.params.projectsId}/devops/list`}className={menu===true && "color-blue"}>构建列表</Link>
|
||||
{ menu===true && <a onClick={updateChildState} style={{float:"right",fontSize:"14px",color:"#FF6E21",marginTop:"5px"}}>刷新</a>}
|
||||
{ permission !=="Reporter" && <Link to={`/projects/${owner}/${props.match.params.projectsId}/devops/dispose`}>工作流配置</Link>}
|
||||
{/* <Link to={`/projects/${owner}/${props.match.params.projectsId}/devops/list`}className={menu===true && "color-blue"}>构建列表</Link> */}
|
||||
{/* { menu===true && <a onClick={updateChildState} style={{float:"right",fontSize:"14px",color:"#FF6E21",marginTop:"5px"}}>刷新</a>} */}
|
||||
</Banner>
|
||||
<Div>
|
||||
{ menu === true && <Structure {...props} wrappedComponentRef={(form) => childRef.current = form} ref={childRef}/> }
|
||||
{ menu === false && permission !=="Reporter" && <Dispost {...props}/> }
|
||||
{/* { menu === true && <Structure {...props} wrappedComponentRef={(form) => childRef.current = form} ref={childRef}/> } */}
|
||||
<Dispost {...props}/>
|
||||
</Div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
import React , { useEffect , useState , useRef } from 'react';
|
||||
import { Banner , Blueback , FlexAJ } from '../Component/layout';
|
||||
import { Input , Table ,Pagination , Select , Popconfirm } from 'antd';
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled from 'styled-components';
|
||||
import axios from 'axios';
|
||||
import New from './MouldNew';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const Div = styled.div`{
|
||||
padding:24px 30px;
|
||||
min-height:420px;
|
||||
}`;
|
||||
const STAGE = [
|
||||
{stage_name:"所有",stage_type:"all"},
|
||||
{stage_name:"初始化",stage_type:"init"},
|
||||
{stage_name:"编译构建",stage_type:"build"},
|
||||
{stage_name:"部署",stage_type:"deploy"},
|
||||
{stage_name:"其他",stage_type:"customize"}
|
||||
]
|
||||
const limit = 15;
|
||||
function Mould(props){
|
||||
const [ visible ,setVisible ] = useState(false);
|
||||
const [ list ,setList ] = useState(undefined);
|
||||
const [ page ,setPage ] = useState(1);
|
||||
const [ totalCount ,setTotalCount ] = useState(0);
|
||||
const [ stageType ,setStageType ] = useState("all");
|
||||
const [ search ,setSearch ] = useState(undefined);
|
||||
const childRef = useRef();
|
||||
let projectsId = props.match.params.projectsId;
|
||||
let owner = props.match.params.owner;
|
||||
useEffect(()=>{
|
||||
Init(page,stageType);
|
||||
},[page,stageType])
|
||||
|
||||
function Init(page,stageType,searchvalue){
|
||||
const url = `/ci/templates/list.json`;
|
||||
axios.get(url,{
|
||||
params:{
|
||||
page,limit,stage_type:stageType,name:searchvalue
|
||||
}
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setList(result.data.templates);
|
||||
setTotalCount(result.data.total_count);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
const columns=[
|
||||
{
|
||||
title:"名称",
|
||||
dataIndex:"template_name",
|
||||
key:1,
|
||||
ellipsis:true
|
||||
},
|
||||
{
|
||||
title:"所属阶段",
|
||||
dataIndex:"stage_type",
|
||||
key:2,
|
||||
ellipsis:true,
|
||||
render:(txt,item)=>{
|
||||
let i = STAGE.filter(item=>item.stage_type === txt);
|
||||
return i && i.length>0 && i[0].stage_name
|
||||
}
|
||||
},
|
||||
{
|
||||
title:"模板类型",
|
||||
dataIndex:"category",
|
||||
key:3,
|
||||
ellipsis:true
|
||||
},
|
||||
{
|
||||
title:"操作",
|
||||
dataIndex:"operation",
|
||||
key:4,
|
||||
ellipsis:true,
|
||||
render:(txt,item)=>{
|
||||
return(
|
||||
<span>
|
||||
<a className="mr10 color-grey-6" onClick={()=>editMouldFunc(item)}><i className="iconfont icon-zaibianji font-13 mr3"></i>编辑</a>
|
||||
<Popconfirm title={"确定要删除此模板?"} onConfirm={()=>deleteMouldFunc(item.id)} okText="确定" cancelText={"取消"}>
|
||||
<a className="mr10 color-grey-6"><i className="iconfont icon-lajitong font-13 mr3"></i>删除</a>
|
||||
</Popconfirm>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 编辑模板
|
||||
function editMouldFunc(item){
|
||||
if (childRef.current) {
|
||||
childRef.current.setEditInfo(item);
|
||||
}
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
// 删除模板
|
||||
function deleteMouldFunc(id){
|
||||
const url = `/ci/templates/${id}.json`;
|
||||
axios.delete(url).then(result=>{
|
||||
if(result && result.data){
|
||||
props.showNotification("模板删除成功!");
|
||||
Init(page,stageType,search);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function searchValue(){
|
||||
Init(page,stageType,search);
|
||||
}
|
||||
|
||||
function newMouldFunc(){
|
||||
if (childRef.current) {
|
||||
childRef.current.setEditInfo(undefined);
|
||||
}
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
function onOk(){
|
||||
Init(page,stageType);
|
||||
}
|
||||
return(
|
||||
<div>
|
||||
<New wrappedComponentRef={(f) => childRef.current = f} ref={childRef} visible={visible} onCancel={()=>setVisible(false)} onOk={onOk}></New>
|
||||
<Banner>
|
||||
<FlexAJ><span>工作流 - 模板管理</span><Link to={`/projects/${owner}/${projectsId}/devops/dispose`} className="font-14 color-grey-9">返回</Link></FlexAJ>
|
||||
</Banner>
|
||||
<Div className="disposeList">
|
||||
<FlexAJ>
|
||||
<Blueback onClick={newMouldFunc}>新建模板</Blueback>
|
||||
<FlexAJ>
|
||||
<span className="mr10">阶段:</span>
|
||||
<Select onChange={e=>setStageType(e)} value={stageType} style={{width:"180px"}}>
|
||||
{
|
||||
STAGE.map((item,key)=>{
|
||||
return(
|
||||
<Option value={item.stage_type}>{item.stage_name}</Option>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
<Input placeholder="请输入模板名称" value={search} onChange={(e)=>setSearch(e.target.value)} allowClear style={{width:"160px",marginLeft:"15px"}}/>
|
||||
<Blueback className="ml15" onClick={searchValue}>搜索</Blueback>
|
||||
</FlexAJ>
|
||||
</FlexAJ>
|
||||
<Table className="mt20" size="small" columns={columns} dataSource={list} rowKey={(row)=>row.id} pagination={false}></Table>
|
||||
{
|
||||
totalCount > limit &&
|
||||
<div className="mt20 pb20" style={{textAlign:'center'}}>
|
||||
<Pagination simple current={page} pageSize={limit} total={totalCount} onChange={(page)=>setPage(page)}/>
|
||||
</div>
|
||||
}
|
||||
</Div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Mould;
|
|
@ -0,0 +1,166 @@
|
|||
import React , { useImperativeHandle , useState , forwardRef , useCallback } from 'react';
|
||||
import { Form , Modal , Input , Select , Spin } from 'antd';
|
||||
import Editor from './Dispose/Editors';
|
||||
import axios from 'axios';
|
||||
|
||||
const { Option } = Select;
|
||||
const TYPE = ["Java","C","C++","Python","Go","Ruby","R","PHP",
|
||||
"Perl","Node","Docker","Rust","Swift","Erlang","Other"]
|
||||
|
||||
function MouldNew({ form , visible , onCancel , onOk }, ref){
|
||||
const [value , setValue ] = useState(undefined);
|
||||
const [isSpin , setIsSpin ] = useState(false);
|
||||
const [valueFlag , setValueFlag ] = useState(false);
|
||||
const [ id , setId ] = useState(false);
|
||||
const [ buildFlag , setBuildFlag ] = useState(false);
|
||||
|
||||
const { getFieldDecorator, validateFields , setFieldsValue } = form;
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
setEditInfo: (info) => {
|
||||
if(info){
|
||||
setFieldsValue({
|
||||
...info
|
||||
})
|
||||
if(info.stage_type === "build"){
|
||||
setBuildFlag(true);
|
||||
setFieldsValue({
|
||||
category:TYPE[0]
|
||||
})
|
||||
}else{
|
||||
removeCate();
|
||||
}
|
||||
setValue(info.content);
|
||||
setId(info.id);
|
||||
}else{
|
||||
removeCate();
|
||||
clear();
|
||||
setId(undefined);
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
const helper = useCallback(
|
||||
(label, name, rules, widget, className, isRequired,flag) => (
|
||||
<Form.Item label={label} className={className}>
|
||||
{getFieldDecorator(name, { rules, validateFirst: true , valuePropName:flag ? "checked":"value" })(widget)}
|
||||
</Form.Item>
|
||||
),
|
||||
[]
|
||||
);
|
||||
function changeContent(v){
|
||||
if(v){
|
||||
setValue(v);
|
||||
setValueFlag(false);
|
||||
}
|
||||
}
|
||||
function cancel(){
|
||||
clear();
|
||||
onCancel();
|
||||
}
|
||||
function sure(){
|
||||
if(!value){
|
||||
setValueFlag(true);
|
||||
return;
|
||||
}
|
||||
validateFields((error,values)=>{
|
||||
if(!error){
|
||||
setIsSpin(true);
|
||||
const url = `/ci/templates.json`;
|
||||
axios.post(url,{
|
||||
...values,id,content:value,category:buildFlag ? values.category:""
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setIsSpin(false);
|
||||
cancel();
|
||||
onOk();
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
})
|
||||
}
|
||||
function clear(){
|
||||
setFieldsValue({
|
||||
stage_type:"init",
|
||||
template_name:undefined,
|
||||
category:"Java",
|
||||
})
|
||||
setValue("");
|
||||
setValueFlag(false);
|
||||
}
|
||||
|
||||
function changeStage(e){
|
||||
if(e === "build"){
|
||||
setBuildFlag(true);
|
||||
setFieldsValue({
|
||||
category:TYPE[0]
|
||||
})
|
||||
}else{
|
||||
removeCate();
|
||||
}
|
||||
}
|
||||
|
||||
function removeCate(){
|
||||
setBuildFlag(false);
|
||||
setFieldsValue({
|
||||
category:""
|
||||
})
|
||||
}
|
||||
return(
|
||||
<Modal
|
||||
visible={visible}
|
||||
width="500px"
|
||||
title={"新建/编辑模板"}
|
||||
onCancel={cancel}
|
||||
onOk={sure}
|
||||
centered={true}
|
||||
>
|
||||
<Spin spinning={isSpin}>
|
||||
<Form layout={"inline"}>
|
||||
{helper(
|
||||
"所属阶段",
|
||||
"stage_type",
|
||||
[{required:true,message:"请选择所属阶段"}],
|
||||
<Select placeholder="请选择所属阶段" style={{width:"350px"}} onChange={(e)=>{changeStage(e)}}>
|
||||
<Option value="init">初始化</Option>
|
||||
<Option value="build">编译构建</Option>
|
||||
<Option value="deploy">部署</Option>
|
||||
<Option value="customize">其他</Option>
|
||||
</Select>
|
||||
)}
|
||||
{helper(
|
||||
"模板名称",
|
||||
"template_name",
|
||||
[{required:true,message:"请输入模板名称"}],
|
||||
<Input placeholder="请输入模板名称" style={{width:"350px"}}/>
|
||||
)}
|
||||
{helper(
|
||||
"模板分类",
|
||||
"category",
|
||||
[{required:buildFlag,message:"请选择模板分类"}],
|
||||
<Select placeholder="请选择模板分类" style={{width:"350px"}}>
|
||||
{
|
||||
TYPE.map((item,key)=>{
|
||||
return(
|
||||
<Option value={item}>{item}</Option>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Select>,buildFlag===true ? "" :"hide"
|
||||
)}
|
||||
<div style={{display:'flex',justifyContent:"flex-start"}}>
|
||||
<span><span className="color-red">* </span>模板内容:</span>
|
||||
<div>
|
||||
<div className="editorPanel">
|
||||
<Editor Numbers={"off"} width={"350px"} value={value} height="200px" theme="vs-grey" onChange={changeContent}/>
|
||||
</div>
|
||||
{ valueFlag && <span className="color-red">请输入模板内容</span>}
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</Spin>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
export default Form.create()(forwardRef(MouldNew));
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Radio , Button } from 'antd';
|
||||
|
||||
function ServiceModal({sureModal}){
|
||||
const [ type , setType ] = useState(1);
|
||||
|
||||
function changeType(e){
|
||||
setType(e.target.value);
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="mt30" style={{textAlign:"center"}}>
|
||||
<Radio.Group value={type} onChange={changeType}>
|
||||
<Radio value={1}>自有服务器</Radio>
|
||||
<Radio value={2}>Trustie服务器</Radio>
|
||||
</Radio.Group>
|
||||
<p className="mt30"><Button type="primary" onClick={()=>sureModal(type)}>下一步</Button></p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default ServiceModal;
|
|
@ -1,10 +1,16 @@
|
|||
import React, { useState, useEffect , useImperativeHandle ,forwardRef } from "react";
|
||||
import { FlexAJ, AlignCenter } from "../Component/layout";
|
||||
import { FlexAJ, AlignCenter , Banner } from "../Component/layout";
|
||||
import { Table, Pagination, Popconfirm } from "antd";
|
||||
import { truncateCommitId } from "../common/util";
|
||||
import {getUrl} from 'educoder';
|
||||
import axios from "axios";
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Div = styled.div`{
|
||||
padding:24px 30px;
|
||||
}`;
|
||||
const STATUS = [
|
||||
{ name: "所有"},
|
||||
{ name: "运行中", value: "running" },
|
||||
|
@ -22,6 +28,7 @@ function Structure(props,ref){
|
|||
|
||||
let projectsId = props.match.params.projectsId;
|
||||
let owner = props.match.params.owner;
|
||||
let branch = props.match.params.branch;
|
||||
const permission = props.projectDetail && props.projectDetail.permission;
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
|
@ -43,7 +50,7 @@ function Structure(props,ref){
|
|||
axios.get(url,{
|
||||
params:{
|
||||
search:status,
|
||||
page,limit:LIMIT
|
||||
page,limit:LIMIT,branch
|
||||
}
|
||||
}).then((result) => {
|
||||
if (result && result.data) {
|
||||
|
@ -278,42 +285,45 @@ function Structure(props,ref){
|
|||
},
|
||||
];
|
||||
return (
|
||||
<div className="listPart">
|
||||
<FlexAJ>
|
||||
{renderStatus()}
|
||||
{/* <Blueback>手动创建</Blueback> */}
|
||||
{/* <span className="mr30">
|
||||
<i className="iconfont icon-fenzhi1 font-16 mr5 color-blue"></i>分支
|
||||
</span>
|
||||
<span>
|
||||
<i className="iconfont icon-biaoqian3 font-16 mr5 color-blue"></i>
|
||||
标签
|
||||
</span> */}
|
||||
</FlexAJ>
|
||||
<Table
|
||||
onRow={(record,index)=>{
|
||||
return{
|
||||
onClick:(event)=>clickRows(event,record)
|
||||
}
|
||||
}}
|
||||
columns={column}
|
||||
className="normalTable"
|
||||
dataSource={data}
|
||||
pagination={false}
|
||||
loading={tableLoading}
|
||||
></Table>
|
||||
{total > LIMIT ?
|
||||
<div style={{ textAlign: "center", margin: "30px 50px" }}>
|
||||
<Pagination
|
||||
showQuickJumper
|
||||
defaultCurrent={page}
|
||||
total={total}
|
||||
pageSize={LIMIT}
|
||||
onChange={ChangePage}
|
||||
></Pagination>
|
||||
</div>
|
||||
:
|
||||
"" }
|
||||
<div className="disposePanel">
|
||||
<Banner>
|
||||
<FlexAJ>
|
||||
<span>构建列表</span>
|
||||
<Link to={`/projects/${owner}/${projectsId}/devops/dispose`} className="font-15 color-grey-9">返回</Link>
|
||||
</FlexAJ>
|
||||
</Banner>
|
||||
<Div>
|
||||
<div className="listPart">
|
||||
<FlexAJ>
|
||||
{renderStatus()}
|
||||
<a onClick={()=>Init(status)} className="color-red font-16">刷新</a>
|
||||
</FlexAJ>
|
||||
<Table
|
||||
onRow={(record,index)=>{
|
||||
return{
|
||||
onClick:(event)=>clickRows(event,record)
|
||||
}
|
||||
}}
|
||||
columns={column}
|
||||
className="normalTable"
|
||||
dataSource={data}
|
||||
pagination={false}
|
||||
loading={tableLoading}
|
||||
></Table>
|
||||
{total > LIMIT ?
|
||||
<div style={{ textAlign: "center", margin: "30px 50px" }}>
|
||||
<Pagination
|
||||
showQuickJumper
|
||||
defaultCurrent={page}
|
||||
total={total}
|
||||
pageSize={LIMIT}
|
||||
onChange={ChangePage}
|
||||
></Pagination>
|
||||
</div>
|
||||
:
|
||||
"" }
|
||||
</div>
|
||||
</Div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,317 @@
|
|||
import React , { useEffect , useState } from 'react';
|
||||
import { WhiteBack } from '../Component/layout';
|
||||
import { Spin } from 'antd';
|
||||
import Head from './Dispose/head';
|
||||
import Menus from './Dispose/menus';
|
||||
import Init from './Dispose/Init';
|
||||
import Sure from './Dispose/Sure';
|
||||
import Stage from './Dispose/Stage';
|
||||
import axios from 'axios';
|
||||
|
||||
function disposePipeline(props){
|
||||
const [ spining , setSpining ] = useState(true);
|
||||
const [ stage , setStage ] = useState(1);
|
||||
const [ pipeLineName , setPipeLineName ] = useState(undefined);
|
||||
const [ stepName , setStepName ] = useState(undefined);
|
||||
const [ stageId , setStageId ] =useState(undefined);
|
||||
const [ menuList , setMenuList ] = useState(undefined);
|
||||
const [ stageType , setStageType ] = useState("init");
|
||||
const [ datas , setDatas ] = useState(undefined);
|
||||
const [ templates , setTemplates] = useState(undefined);
|
||||
const [ datasUpdataFlag , setDatasUpdataFlag ] = useState(false);
|
||||
const [ loading ,setLoading ] = useState(false);
|
||||
|
||||
const { disposeId } = props.match.params;
|
||||
let projectsId = props.match.params.projectsId;
|
||||
let owner = props.match.params.owner;
|
||||
useEffect(()=>{
|
||||
if(stageType && stageType !=="confirm"){
|
||||
InitTemplates();
|
||||
}
|
||||
},[stageType])
|
||||
|
||||
// 获取模板
|
||||
function InitTemplates(){
|
||||
const url = `/ci/templates/templates_by_stage.json`;
|
||||
axios.get(url,{
|
||||
params:{ stage_type:stageType, id:disposeId }
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setTemplates(result.data);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
if(disposeId && (stageType && stageType !=="confirm")){
|
||||
getData(0);
|
||||
}
|
||||
},[disposeId])
|
||||
// 获取所有阶段默认第一个阶段的数据
|
||||
function getData(index){
|
||||
const url = `/ci/pipelines/${disposeId}/stages.json`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
setMenuList(result.data.stages);
|
||||
if(index || index === 0){
|
||||
let first = result.data.stages[index];
|
||||
setStage(first.show_index);
|
||||
setStageId(first.id);
|
||||
setPipeLineName(`${first.pipeline_name}`);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
if(stageId){
|
||||
getStageStep(stageId);
|
||||
}
|
||||
},[stageId])
|
||||
// 切换阶段时获取阶段步骤数据(第一次默认查询”初始化“阶段的数据)
|
||||
function getStageStep(stageId){
|
||||
let url = "";
|
||||
if(stageType && stageType === "confirm"){
|
||||
url = `/ci/pipelines/${disposeId}/content.json`;
|
||||
axios.get(url,{
|
||||
params:{
|
||||
owner,repo:projectsId
|
||||
}
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setDatas(result.data);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}else{
|
||||
url = `/ci/pipelines/${disposeId}/${stageId}/steps.json`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
let step = result.data.steps;
|
||||
setDatas(step);
|
||||
let flag = !step || (step && step.length === 0);
|
||||
setDatasUpdataFlag(flag);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
setSpining(false);
|
||||
}
|
||||
|
||||
// 切换阶段:s:show_index,stage_type:stage_type
|
||||
function changestage(show_index,stage_type,stage_id,stage_name){
|
||||
if(show_index !== stage){
|
||||
saveFunc(show_index,stage_type,stage_id,stage_name);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取子组件传过来的数据
|
||||
function saveDatas(steps){
|
||||
setDatas([...steps]);
|
||||
setDatasUpdataFlag(true);
|
||||
}
|
||||
|
||||
// 检查数组里面是否有空数据
|
||||
function checkDatas(){
|
||||
if(datas && datas.length > 0){
|
||||
for(let i= 0;i< datas.length;i++){
|
||||
if(datas[i] && (!datas[i].content || !datas[i].template_id)){
|
||||
props.showNotification("请先选择模板!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(stageType === "init" ){
|
||||
props.showNotification("请先选择模板!");
|
||||
return false;
|
||||
}else if(stageType === "confirm" ){
|
||||
return true;
|
||||
}else{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 保存步骤
|
||||
function saveFunc(show_index,stage_type,stage_id,stageName,btn){
|
||||
setSpining(true);
|
||||
// 判断数据是否有过更新
|
||||
if(datasUpdataFlag && stageType !== "confirm"){
|
||||
// 先判断子组件传过来的数据是否有undefined --datas
|
||||
let f = checkDatas();
|
||||
if(f && (datas && datas.length !== 0)){
|
||||
// 数据没问题后先保存数据再切换阶段
|
||||
saveDataFunc(btn,show_index,stage_type,stage_id,stageName);
|
||||
}
|
||||
else{
|
||||
setSpining(false);
|
||||
f === "" && elseFunc(btn,show_index,stage_type,stage_id,stageName);
|
||||
}
|
||||
}else{
|
||||
elseFunc(btn,show_index,stage_type,stage_id,stageName);
|
||||
}
|
||||
}
|
||||
|
||||
function saveDataFunc(btn,show_index,stage_type,stage_id,stageName){
|
||||
const url = `/ci/pipelines/${disposeId}/${stageId}/stage_step.json`;
|
||||
axios.post(url,{
|
||||
steps:datas
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setDatasUpdataFlag(false);
|
||||
if(!btn){
|
||||
setStage(show_index);
|
||||
setStageType(stage_type);
|
||||
setStageId(stage_id);
|
||||
setStepName(pipeLineName+`-`+stageName);
|
||||
}else{
|
||||
enters(btn);
|
||||
}
|
||||
}else{
|
||||
props.showNotification("阶段更新失败,请稍微重试!");
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
function elseFunc(btn,show_index,stage_type,stage_id,stageName){
|
||||
if(btn){
|
||||
enters(btn);
|
||||
}else{
|
||||
setStage(show_index);
|
||||
setStageType(stage_type);
|
||||
setStageId(stage_id);
|
||||
setStepName(pipeLineName+`-`+stageName);
|
||||
}
|
||||
}
|
||||
|
||||
// 上一步、下一步
|
||||
function enters(btn){
|
||||
let s = stage;
|
||||
if(btn === "next"){
|
||||
// 点击阶段里面的下一步
|
||||
s = s+1;
|
||||
}else{
|
||||
// 上一步
|
||||
s = s-1;
|
||||
}
|
||||
let item = menuList && menuList.filter(i=>i.show_index === (s));
|
||||
setStage(s);
|
||||
setStageType(item[0].stage_type);
|
||||
setStageId(item[0].id);
|
||||
setStepName(pipeLineName+`-`+item[0].stage_name);
|
||||
}
|
||||
// 删除步骤
|
||||
function deleteStep(id,index){
|
||||
if(id){
|
||||
const url = `/ci/pipelines/${disposeId}/${stageId}/${id}/delete_step.json`;
|
||||
axios.delete(url).then(result=>{
|
||||
if(result && result.data){
|
||||
getStageStep(stageId);
|
||||
props.showNotification("阶段步骤删除成功!");
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}else{
|
||||
let d = datas.filter(s=>s.show_index !== (index+1));
|
||||
setDatas(d);
|
||||
}
|
||||
}
|
||||
function renameFunc(value,id){
|
||||
const url = `/ci/pipelines/${disposeId}/${id}/update_stage.json`;
|
||||
axios.put(url,{
|
||||
stage_name:value
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
getData();
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
// 新增阶段
|
||||
function addMenuFunc(name,index){
|
||||
const url = `/ci/pipelines/${disposeId}/create_stage.json`;
|
||||
axios.post(url,{
|
||||
show_index:index,
|
||||
stage_name:name
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
getData(index-1);
|
||||
setStageType("customize");
|
||||
}else{
|
||||
props.showNotification("阶段新增失败!");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除阶段
|
||||
function deleteStageFunc(){
|
||||
let next = menuList && menuList.filter(item=>item.show_index === (stage+1));
|
||||
let nextStageType = next && next.length>0 && next[0].stage_type;
|
||||
const url = `/ci/pipelines/${disposeId}/${stageId}/delete_stage.json`;
|
||||
axios.delete(url,{
|
||||
params:{show_index:stage}
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
getData(stage-1);
|
||||
setStageType(nextStageType);
|
||||
}else{
|
||||
props.showNotification("阶段删除失败!");
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
// 确认提交
|
||||
function sureSubmit(){
|
||||
setLoading(true);
|
||||
let params = {
|
||||
branch: datas.branch,
|
||||
content:datas.content,
|
||||
filepath:'.trustie-pipeline.yml',
|
||||
message:'',
|
||||
sha:datas.sha || undefined,
|
||||
owner:owner,
|
||||
repo:projectsId
|
||||
}
|
||||
let url = `/${owner}/${projectsId}/update_trustie_pipeline.json`;
|
||||
axios.put(url,{
|
||||
...params
|
||||
}).then(result=>{
|
||||
if(result){
|
||||
props.history.push(`/projects/${owner}/${projectsId}/devops/dispose`);
|
||||
}
|
||||
setLoading(false);
|
||||
}).catch(error=>{
|
||||
console.log(error);
|
||||
setLoading(false);
|
||||
})
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="disposePanel">
|
||||
<Head />
|
||||
<WhiteBack style={{padding:"24px 30px"}}>
|
||||
<Spin spinning={spining}>
|
||||
<div style={{minHeight:"450px"}}>
|
||||
<Menus step={stage} checkDatas={checkDatas} changeStep={changestage} menuList={menuList} renameFunc={renameFunc} addFunc={addMenuFunc}/>
|
||||
{
|
||||
stageType === "init" ? <Init stage_type={stageType} templates={templates} datas={datas} saveDatas={saveDatas} saveFunc={saveFunc}/>
|
||||
:
|
||||
stageType === "confirm" ? <Sure sureSubmit={sureSubmit} name={pipeLineName} datas={datas} saveFunc={saveFunc} loading={loading}/>
|
||||
:
|
||||
<Stage {...props}
|
||||
stepName={stepName}
|
||||
deleteStep={deleteStep}
|
||||
stage_type={stageType}
|
||||
templates={templates}
|
||||
datas={datas}
|
||||
deleteFunc={deleteStageFunc}
|
||||
saveDatas={saveDatas}
|
||||
saveFunc={saveFunc}
|
||||
deleteFlag={menuList && menuList.length === 3}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</Spin>
|
||||
</WhiteBack>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default disposePipeline;
|
|
@ -313,4 +313,200 @@
|
|||
text-align:center;
|
||||
line-height:22px;
|
||||
color:#666;
|
||||
}
|
||||
|
||||
.disposeList{
|
||||
min-height: 450px;
|
||||
.ant-table-body{
|
||||
margin:0px!important;
|
||||
.ant-table-thead{
|
||||
background-color: #fafafa;
|
||||
}
|
||||
}
|
||||
}
|
||||
.txtright{
|
||||
text-align: right;
|
||||
}
|
||||
.menus{
|
||||
display: flex;
|
||||
box-shadow: 0px 0px 6px rgba(0,3,8,0.1);
|
||||
padding:20px 0px 10px 0px;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
& > li{
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items:center;
|
||||
justify-content: flex-start;
|
||||
text-align: center;
|
||||
color: #787878;
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
max-width: 122px;
|
||||
flex:2;
|
||||
&>i{
|
||||
font-size: 30px!important;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
&:hover,&.active{
|
||||
& > i{
|
||||
color: #1890FF;
|
||||
}
|
||||
}
|
||||
&.active::after{
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-left: -25px;
|
||||
bottom:-10px;
|
||||
width: 50px;
|
||||
height: 2px;
|
||||
background-color: #1890FF;
|
||||
content: "";
|
||||
}
|
||||
.aboutEdit{
|
||||
width: 100%;
|
||||
.operateName{
|
||||
position: relative;
|
||||
line-height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0px 15px;
|
||||
min-height: 40px;
|
||||
& > i{
|
||||
position: absolute;
|
||||
right:0px;
|
||||
top:10px;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&:hover i{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
& > li.menuAdd{
|
||||
position: relative;
|
||||
flex: 1;
|
||||
&:hover{
|
||||
& > i{
|
||||
color: #787878;
|
||||
}
|
||||
}
|
||||
& > i{
|
||||
font-size: 18px!important;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top:0px;
|
||||
}
|
||||
& > input{
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top:3px;
|
||||
}
|
||||
&::before{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
border-bottom: 1px dashed #eee;
|
||||
content: "";
|
||||
top:15px;
|
||||
margin-top: -1px;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.choosenList{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
line-height: 35px;
|
||||
align-items: center;
|
||||
& > span{
|
||||
display: block;
|
||||
min-width: 75px;
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
height: 35px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
& > ul{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
li{
|
||||
padding:0px 12px;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
border:1px solid #e1e4e8;
|
||||
background-color: #fff;
|
||||
margin:6px 0px;
|
||||
margin-right: 12px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
color: #333;
|
||||
}
|
||||
li.active{
|
||||
color: #fff;
|
||||
background-color: #1890FF;
|
||||
}
|
||||
}
|
||||
.ant-select-selection.ant-select-selection--multiple{
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
height: 35px;
|
||||
}
|
||||
}
|
||||
.addStageBtn{
|
||||
display: block;
|
||||
width: 100%;
|
||||
border:1px solid #e1e4e8;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
color: #1890FF;
|
||||
font-size: 16px;
|
||||
border-radius: 3px;
|
||||
padding: 0px 20px;
|
||||
}
|
||||
.stepsItem{
|
||||
border:1px solid #e1e4e8;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
.stepsHead{
|
||||
padding:8px 15px;
|
||||
span > a > i{
|
||||
margin-left: 8px;
|
||||
color: #666!important;
|
||||
}
|
||||
}
|
||||
.stepsBody{
|
||||
padding:10px 15px;
|
||||
border-top: 1px solid #e1e4e8;
|
||||
background-color: rgb(251, 251, 251);
|
||||
display: none;
|
||||
transition: 0.3s;
|
||||
}
|
||||
.stepsBody.active{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.editorPanel{
|
||||
border:1px solid #eee;
|
||||
.margin{
|
||||
width: 0px;
|
||||
}
|
||||
.monaco-scrollable-element.editor-scrollable{
|
||||
left: 0px!important;
|
||||
width: 100%!important;
|
||||
.view-lines{
|
||||
width: 336px!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.hide{
|
||||
display: none!important;
|
||||
}
|
|
@ -87,7 +87,7 @@ export default (props) => {
|
|||
</AlignCenter>
|
||||
<Link
|
||||
style={{ color: "#ddd" }}
|
||||
to={`/projects/${owner}/${projectId}/devops/list`}
|
||||
to={`/projects/${owner}/${projectId}/devops/dispose`}
|
||||
>
|
||||
<i className="iconfont icon-yiguanbi font-15 mr5"></i>退出
|
||||
</Link>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { withRouter } from "react-router";
|
|||
import { SnackbarHOC } from "educoder";
|
||||
import { CNotificationHOC } from "../modules/courses/common/CNotificationHOC";
|
||||
import { TPMIndexHOC } from "../modules/tpm/TPMIndexHOC";
|
||||
import Handbook from './Component/Handbook';
|
||||
import "./css/index.scss";
|
||||
|
||||
import Loadable from "react-loadable";
|
||||
|
@ -13,7 +14,7 @@ import Loading from "../Loading";
|
|||
import { ImageLayerOfCommentHOC } from "../modules/page/layers/ImageLayerOfCommentHOC";
|
||||
|
||||
const ProjectNew = Loadable({
|
||||
loader: () => import("./New/Index"),
|
||||
loader: () => import("./New/Index"),
|
||||
loading: Loading,
|
||||
});
|
||||
const ProjectIndex = Loadable({
|
||||
|
@ -30,13 +31,18 @@ const Infos = Loadable({
|
|||
loader: () => import("./users/Infos"),
|
||||
loading: Loading,
|
||||
});
|
||||
|
||||
class Index extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="newMain clearfix">
|
||||
<Handbook />
|
||||
<Switch {...this.props}>
|
||||
<Route
|
||||
path="/projects/:projectsType/new/:OIdentifier"
|
||||
render={(props) => (
|
||||
<ProjectNew {...this.props} {...props} />
|
||||
)}
|
||||
></Route>
|
||||
<Route
|
||||
path="/projects/:projectsType/new"
|
||||
render={(props) => (
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
|
|||
import { Dropdown , Menu , Icon , Tooltip , Spin } from 'antd';
|
||||
import { truncateCommitId } from '../common/util';
|
||||
import { getBranch } from '../GetData/getData';
|
||||
|
||||
import Nodata from '../Nodata';
|
||||
import './list.css';
|
||||
|
||||
export default ((props)=>{
|
||||
|
@ -54,6 +54,8 @@ export default ((props)=>{
|
|||
</ul>
|
||||
</React.Fragment>
|
||||
)
|
||||
}else if(data && data.length === 0){
|
||||
return ( <Nodata _html="暂无数据"/>)
|
||||
}
|
||||
}
|
||||
const menu =(zip_url,tar_url)=> (
|
||||
|
@ -68,7 +70,7 @@ export default ((props)=>{
|
|||
<Spin spinning={isSpin}>
|
||||
<div className="branchTable">
|
||||
<p className="branchTitle bor-bottom-greyE">分支列表</p>
|
||||
{list()}
|
||||
<div style={{minHeight:"400px"}}>{list()}</div>
|
||||
</div>
|
||||
</Spin>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,6 @@ import { getImageUrl } from 'educoder';
|
|||
import { truncateCommitId } from '../common/util';
|
||||
import SelectBranch from '../Branch/Select';
|
||||
import Nodata from '../Nodata';
|
||||
import { getBranch } from '../GetData/getData';
|
||||
|
||||
import axios from 'axios';
|
||||
import {Link} from "react-router-dom";
|
||||
|
@ -129,7 +128,7 @@ class CoderRootCommit extends Component{
|
|||
</div>
|
||||
<div className="commitList">
|
||||
{
|
||||
commitDatas && commitDatas.length > 0 ? commitDatas.map((item,k)=>{
|
||||
commitDatas && commitDatas.length > 0 && commitDatas.map((item,k)=>{
|
||||
return(
|
||||
<div key={k}>
|
||||
<p className="f-wrap-alignCenter">
|
||||
|
@ -144,8 +143,9 @@ class CoderRootCommit extends Component{
|
|||
</p>
|
||||
</div>
|
||||
)
|
||||
}):<Nodata _html="暂无数据"/>
|
||||
})
|
||||
}
|
||||
{commitDatas && commitDatas.length > 0 && <Nodata _html="暂无数据"/>}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
|
|
|
@ -123,7 +123,7 @@ class CoderRootDirectory extends Component {
|
|||
let entries = result.data && result.data.entries;
|
||||
this.setState({
|
||||
filePath: undefined,
|
||||
fileDetail: undefined,
|
||||
fileDetail: [],
|
||||
isSpin: false,
|
||||
branchLastCommit: last_commit && last_commit.commit,
|
||||
lastCommitAuthor:
|
||||
|
@ -209,12 +209,12 @@ class CoderRootDirectory extends Component {
|
|||
if(entries.type){
|
||||
this.setState({
|
||||
fileDetail:[entries],
|
||||
rootList:undefined,
|
||||
rootList:[],
|
||||
subFileType:false
|
||||
})
|
||||
}else{
|
||||
this.setState({
|
||||
fileDetail:undefined,
|
||||
fileDetail:[],
|
||||
rootList:entries,
|
||||
branchLastCommit:result.data.last_commit && result.data.last_commit.commit,
|
||||
lastCommitAuthor:result.data.last_commit && (result.data.last_commit.author || (result.data.last_commit.commit && result.data.last_commit.commit.author))
|
||||
|
@ -223,8 +223,8 @@ class CoderRootDirectory extends Component {
|
|||
}
|
||||
}else{
|
||||
this.setState({
|
||||
fileDetail:undefined,
|
||||
rootList:undefined,
|
||||
fileDetail:[],
|
||||
rootList:[],
|
||||
isSpin:false,
|
||||
subFileType:false
|
||||
})
|
||||
|
@ -409,12 +409,14 @@ class CoderRootDirectory extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
render(){
|
||||
const { branchLastCommit , lastCommitAuthor , rootList ,filePath , fileDetail , subFileType , readMeContent, isSpin , zip_url , tar_url , branchList} = this.state;
|
||||
const { isManager , isDeveloper , projectDetail , platform , defaultBranch } = this.props;
|
||||
|
||||
const { projectsId , owner , branchName } = this.props.match.params;
|
||||
let branch = branchName || defaultBranch;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key:"name",
|
||||
|
@ -450,12 +452,11 @@ class CoderRootDirectory extends Component {
|
|||
:""
|
||||
},
|
||||
];
|
||||
|
||||
const urlRoot = filePath === undefined ? "" : `/${filePath}`;
|
||||
let array = filePath && filePath.split("/");
|
||||
return (
|
||||
<Spin spinning={isSpin}>
|
||||
<div className="main">
|
||||
<div className="main" style={{minHeight:'400px'}}>
|
||||
<div className="f-wrap-between mb20">
|
||||
<div className="f-wrap-alignCenter">
|
||||
{
|
||||
|
@ -472,7 +473,6 @@ class CoderRootDirectory extends Component {
|
|||
:
|
||||
<span>分支:<span className="color-grey-6">master</span></span>
|
||||
}
|
||||
|
||||
|
||||
{filePath && (
|
||||
<span className="ml20 font-16">
|
||||
|
@ -525,7 +525,7 @@ class CoderRootDirectory extends Component {
|
|||
</div>
|
||||
</div>
|
||||
{/* 主目录列表 */}
|
||||
{rootList && (
|
||||
{rootList && rootList.length > 0 && (
|
||||
<RootTable
|
||||
columns={columns}
|
||||
data={rootList}
|
||||
|
@ -544,11 +544,11 @@ class CoderRootDirectory extends Component {
|
|||
></CoderRootFileDetail>
|
||||
)}
|
||||
{
|
||||
!rootList && !fileDetail && <Nodata _html="暂未发现当前文件!"/>
|
||||
(rootList && rootList.length === 0) && (fileDetail && fileDetail.length === 0) && <Nodata _html="暂未发现文件!"/>
|
||||
}
|
||||
{ rootList && this.renderReadMeContent(readMeContent, isManager || isDeveloper)}
|
||||
</div>
|
||||
</Spin>
|
||||
</div>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,9 @@ export default (( props, { projectDetail }) => {
|
|||
return (
|
||||
<div className="main">
|
||||
<Spin spinning={isSpin}>
|
||||
{
|
||||
data && data.length > 0 ?
|
||||
<div style={{minHeight:"400px"}}>
|
||||
{
|
||||
data && data.length > 0 &&
|
||||
<div className="div_table">
|
||||
<ul className="ul_thead">
|
||||
<li>
|
||||
|
@ -40,7 +41,7 @@ export default (( props, { projectDetail }) => {
|
|||
</ul>
|
||||
<ul className="ul_tbody">
|
||||
{
|
||||
data && data.length > 0 && data.map((item, key) => {
|
||||
data.map((item, key) => {
|
||||
return (
|
||||
<li>
|
||||
<span className="flex1">
|
||||
|
@ -60,9 +61,9 @@ export default (( props, { projectDetail }) => {
|
|||
}
|
||||
</ul>
|
||||
</div>
|
||||
:
|
||||
<Nodata _html={`暂无标签!`}/>
|
||||
}
|
||||
}
|
||||
{ data && data.length === 0 && <Nodata _html={`暂无标签!`}/> }
|
||||
</div>
|
||||
</Spin>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -4,7 +4,6 @@ import { Link, Route, Switch } from 'react-router-dom';
|
|||
import { Content } from '../Component/layout';
|
||||
import '../css/index.scss'
|
||||
import './list.css';
|
||||
import SpecialModal from './SpecialModal';
|
||||
|
||||
import Loadable from 'react-loadable';
|
||||
import Loading from '../../Loading';
|
||||
|
@ -115,9 +114,9 @@ const DevIndex = Loadable({
|
|||
/**
|
||||
* permission:Manager:管理员,Reporter:报告人员(只有读取权限),Developer:开发人员(除不能设置仓库信息外)
|
||||
*/
|
||||
function checkPathname(pathname){
|
||||
function checkPathname(projectsId,owner,pathname){
|
||||
let name = "";
|
||||
if(pathname){
|
||||
if(pathname && pathname !== `/projects/${owner}/${projectsId}`){
|
||||
if(pathname.indexOf("/about")>-1){
|
||||
name="about"
|
||||
}else if(pathname.indexOf("/issues")>-1 ||pathname.indexOf("Milepost") > 0){
|
||||
|
@ -130,7 +129,7 @@ function checkPathname(pathname){
|
|||
name="activity"
|
||||
}else if(pathname.indexOf("/setting")>-1){
|
||||
name="setting"
|
||||
}else if(pathname.indexOf("/devops")>-1){
|
||||
}else if(pathname.indexOf(`/devops`)>-1){
|
||||
name="devops"
|
||||
}
|
||||
}
|
||||
|
@ -162,9 +161,7 @@ class Detail extends Component {
|
|||
defaultBranch:undefined,
|
||||
|
||||
// 非本平台项目
|
||||
platform:false,
|
||||
visible:false,
|
||||
user_apply_signatures:[]
|
||||
platform:false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,7 +178,6 @@ class Detail extends Component {
|
|||
}
|
||||
|
||||
getProject = (num) => {
|
||||
const {user} = this.props;
|
||||
const { projectsId , owner } = this.props.match.params;
|
||||
const url = `/${owner}/${projectsId}/simple.json`;
|
||||
axios.get(url).then((result) => {
|
||||
|
@ -191,24 +187,6 @@ class Detail extends Component {
|
|||
open_devops:result.data.open_devops,
|
||||
platform:result.data.platform && result.data.platform !== 'educoder'
|
||||
})
|
||||
let signa = result.data.user_apply_signatures && result.data.user_apply_signatures[0];
|
||||
if(result.data.is_secret && !result.data.is_member && (!signa || (signa && signa.status !== "passed")) && user.login !== owner){
|
||||
this.setState({
|
||||
visible:true,
|
||||
is_secret:result.data.is_secret,
|
||||
user_apply_signatures:signa
|
||||
})
|
||||
}
|
||||
|
||||
// 工作流:两种状态进入的链接不同
|
||||
const pathname = this.props.history.location.pathname;
|
||||
if(pathname===`/projects/${owner}/${projectsId}/devops`){
|
||||
if(result.data.open_devops && pathname === `/projects/${owner}/${projectsId}/devops`){
|
||||
this.props.history.push(`/projects/${owner}/${projectsId}/devops/list`);
|
||||
}else if(result.data.open_devops===false && pathname !== `/projects/${owner}/${projectsId}/devops`){
|
||||
this.props.history.push(`/projects/${owner}/${projectsId}/devops`);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.data.type !== 0 && result.data.mirror_status === 1) {
|
||||
console.log("--------start channel --------");
|
||||
|
@ -378,30 +356,19 @@ class Detail extends Component {
|
|||
})
|
||||
}
|
||||
|
||||
hideModal=()=>{
|
||||
this.setState({
|
||||
visible:false
|
||||
})
|
||||
}
|
||||
|
||||
sureModal=()=>{
|
||||
this.hideModal();
|
||||
this.props.history.push('/projects');
|
||||
}
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
const { projectDetail, watchers_count, praises_count,
|
||||
forked_count, firstSync , secondSync ,
|
||||
isManager, watched, praised,
|
||||
project , open_devops , platform , defaultBranch , project_id , user_apply_signatures , visible } = this.state;
|
||||
project , open_devops , platform , defaultBranch } = this.state;
|
||||
const url = this.props.history.location.pathname;
|
||||
const urlArr = url.split("/");
|
||||
const urlFlag = (urlArr.length === 3);
|
||||
let pathname = checkPathname(url);
|
||||
|
||||
const { projectsId , owner } = this.props.match.params;
|
||||
let pathname = checkPathname(projectsId,owner,url);
|
||||
|
||||
const { state } = this.props.history.location;
|
||||
|
||||
|
@ -422,19 +389,18 @@ class Detail extends Component {
|
|||
}
|
||||
return (
|
||||
<div>
|
||||
<SpecialModal {...this.props} visible={visible} hideModal={this.sureModal} user_apply_signatures={user_apply_signatures} project_id={project_id} sureModal={this.sureModal}></SpecialModal>
|
||||
<div className="detailHeader-wrapper">
|
||||
<div className="normal">
|
||||
<div className="f-wrap-between pb15" style={{ position: "relative" }}>
|
||||
<p className="color-white font-22 df flex-1 lineH2 mt15" style={{ alignItems: "center" }}>
|
||||
<p className="font-22 df flex-1 lineH2 mt15" style={{ alignItems: "center" }}>
|
||||
{project && project.author &&
|
||||
<Link to={`/users/${project.author.login}`} className="show-user-link color-white">
|
||||
<Link to={`${project.author.type ==="Organization" ? "/organize":'/users'}/${project.author.login}`} className="show-user-link">
|
||||
{project.author.name}
|
||||
</Link>
|
||||
}
|
||||
<span className="ml5 mr5">/</span>
|
||||
<span className="hide-1 flex-1 df">
|
||||
<Link to={`/projects/${owner}/${projectsId}`} className="color-white font-22">{project && project.name}</Link>
|
||||
<Link to={`/projects/${owner}/${projectsId}`} className="font-22">{project && project.name}</Link>
|
||||
{
|
||||
projectDetail && projectDetail.forked_from_project_id && projectDetail.fork_info ?
|
||||
<Tooltip placement={'right'} title={text}>
|
||||
|
@ -471,14 +437,15 @@ class Detail extends Component {
|
|||
<span>{watched ? '取消关注' : '关注'}</span>
|
||||
</a>
|
||||
{
|
||||
watchers_count > 0 ?
|
||||
platform ?
|
||||
<Link className="detail_tag_btn_count" style={{color:`${watched?"#2878FF":"#666"}`}} to={platform?{ pathname: `/projects/${owner}/${projectsId}/watchers`, state }:""}>
|
||||
{watchers_count}
|
||||
</Link>
|
||||
:
|
||||
<span className="detail_tag_btn_count">{watchers_count}</span>
|
||||
:""
|
||||
}
|
||||
|
||||
</span>
|
||||
<span className="detail_tag_btn">
|
||||
<a className="detail_tag_btn_name" style={{cursor:platform?"pointer":"default"}} onClick={() => this.pariseFunc(praised)}>
|
||||
|
@ -486,11 +453,13 @@ class Detail extends Component {
|
|||
<span>{praised ? '取消点赞' : '点赞'}</span>
|
||||
</a>
|
||||
{
|
||||
praises_count > 0 ?
|
||||
platform ?
|
||||
<Link className="detail_tag_btn_count" style={{color:`${praised?"#2878FF":"#666"}`}} to={{ pathname: `/projects/${owner}/${projectsId}/stargazers`, state }}>
|
||||
{praises_count}
|
||||
</Link>:
|
||||
<span className="detail_tag_btn_count">{praises_count}</span>
|
||||
:""
|
||||
}
|
||||
</span>
|
||||
<span className="detail_tag_btn">
|
||||
|
@ -498,12 +467,14 @@ class Detail extends Component {
|
|||
<i className="iconfont icon-fork color-grey-9 mr3"></i>复刻 (Fork)
|
||||
</a>
|
||||
{
|
||||
forked_count > 0 ?
|
||||
platform ?
|
||||
<Link className="detail_tag_btn_count" to={{ pathname: `/projects/${owner}/${projectsId}/fork_users`, state }}>
|
||||
{forked_count}
|
||||
</Link>
|
||||
:
|
||||
<span className="detail_tag_btn_count">{praises_count}</span>
|
||||
<span className="detail_tag_btn_count">{forked_count}</span>
|
||||
:""
|
||||
}
|
||||
</span>
|
||||
</span>
|
||||
|
@ -511,23 +482,23 @@ class Detail extends Component {
|
|||
</div>
|
||||
{
|
||||
firstSync ? "" :
|
||||
<div className="f-wrap-between pb20">
|
||||
<div className="f-wrap-between mt15">
|
||||
<ul className="headerMenu-wrapper">
|
||||
<li className={pathname==="about" ? "active" : ""}>
|
||||
<Link to={{ pathname: `/projects/${owner}/${projectsId}/about`, state }}>
|
||||
<i className={ pathname === "about" ? "iconfont icon-zhuye1 color-blue mr5 font-14":"iconfont icon-zhuye1 color-white font-14 mr5"}></i>
|
||||
<i className={(pathname==="" || urlFlag) ? "iconfont icon-zhuye1 color-grey-3 mr5 font-14":"iconfont icon-zhuye1 color-grey-6 font-14 mr5"}></i>
|
||||
<span>主页</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li className={(pathname==="" || urlFlag) ? "active" : ""}>
|
||||
<Link to={{ pathname: `/projects/${owner}/${projectsId}`, state }}>
|
||||
<i className={(pathname==="" || urlFlag) ? "iconfont icon-daimaku color-blue mr5 font-14":"iconfont icon-daimaku color-white font-14 mr5"}></i>
|
||||
<i className={(pathname==="" || urlFlag) ? "iconfont icon-daimaku color-grey-3 mr5 font-14":"iconfont icon-daimaku color-grey-6 font-14 mr5"}></i>
|
||||
<span>代码库</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li className={pathname==="issues" ? "active" : ""}>
|
||||
<Link to={{ pathname: `/projects/${owner}/${projectsId}/issues`, state }}>
|
||||
<i className={pathname==="issues" ? "iconfont icon-renwu color-blue mr5 font-14":"iconfont icon-renwu color-white font-14 mr5"}></i>
|
||||
<i className={pathname==="issues" ? "iconfont icon-renwu color-grey-3 mr5 font-14":"iconfont icon-renwu color-grey-6 font-14 mr5"}></i>
|
||||
<span>易修 (Issue)</span>
|
||||
{projectDetail && projectDetail.issues_count ? <span className="num">{projectDetail.issues_count}</span> : ""}
|
||||
</Link>
|
||||
|
@ -536,32 +507,32 @@ class Detail extends Component {
|
|||
projectDetail && parseInt(projectDetail.type) !== 2 && platform &&
|
||||
<li className={pathname==="pulls" ? "active" : ""}>
|
||||
<Link to={{ pathname: `/projects/${owner}/${projectsId}/pulls`, state }}>
|
||||
<i className={pathname==="pulls" ? "iconfont icon-hebingqingqiu1 color-blue mr5 font-14":"iconfont icon-hebingqingqiu1 color-white font-14 mr5"}></i>
|
||||
<i className={pathname==="pulls" ? "iconfont icon-hebingqingqiu1 color-grey-3 mr5 font-14":"iconfont icon-hebingqingqiu1 color-grey-6 font-14 mr5"}></i>
|
||||
<span>合并请求</span>
|
||||
{projectDetail && projectDetail.pull_requests_count ? <span className="num">{projectDetail.pull_requests_count}</span> : ""}
|
||||
</Link>
|
||||
</li>
|
||||
}
|
||||
{/* {
|
||||
{
|
||||
platform &&
|
||||
<li className={pathname==="devops" ? "active" : ""}>
|
||||
<Link to={{ pathname: `/projects/${owner}/${projectsId}/devops${open_devops ? `/list`:""}`, state }}>
|
||||
<Link to={{ pathname: `/projects/${owner}/${projectsId}/devops${open_devops ? `/dispose`:""}`, state }}>
|
||||
<i className="iconfont icon-gongzuoliu font-13 mr8"></i>工作流(beta版)
|
||||
{projectDetail && projectDetail.ops_count ? <span>{projectDetail.ops_count}</span> : ""}
|
||||
</Link>
|
||||
</li>
|
||||
} */}
|
||||
}
|
||||
|
||||
<li className={pathname==="milestones" ? "active" : ""}>
|
||||
<Link to={{ pathname: `/projects/${owner}/${projectsId}/milestones`, state }}>
|
||||
<i className={pathname==="milestones" ? "iconfont icon-lichengbei color-blue mr5 font-14":"iconfont icon-lichengbei color-white font-14 mr5"}></i>
|
||||
<i className={pathname==="milestones" ? "iconfont icon-lichengbei color-grey-3 mr5 font-14":"iconfont icon-lichengbei color-grey-6 font-14 mr5"}></i>
|
||||
<span>里程碑</span>
|
||||
{projectDetail && projectDetail.versions_count ? <span className="num">{projectDetail.versions_count}</span> :""}
|
||||
</Link>
|
||||
</li>
|
||||
<li className={pathname==="activity" ? "active" : ""}>
|
||||
<Link to={{ pathname: `/projects/${owner}/${projectsId}/activity`, state }}>
|
||||
<i className={pathname==="activity" ? "iconfont icon-tongzhi color-blue mr5 font-14":"iconfont icon-tongzhi color-white font-14 mr5"}></i>
|
||||
<i className={pathname==="activity" ? "iconfont icon-tongzhi color-grey-3 mr5 font-14":"iconfont icon-tongzhi color-grey-6 font-14 mr5"}></i>
|
||||
<span>动态</span>
|
||||
</Link>
|
||||
</li>
|
||||
|
@ -569,7 +540,7 @@ class Detail extends Component {
|
|||
isManager && platform &&
|
||||
<li className={url.indexOf("/setting") > 0 ? "active" : ""}>
|
||||
<Link to={`/projects/${owner}/${projectsId}/setting`}>
|
||||
<i className={url.indexOf("/setting") > 0 ? "iconfont icon-cangku color-blue mr5 font-14":"iconfont icon-cangku color-white font-14 mr5"}></i>
|
||||
<i className={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>
|
||||
</Link>
|
||||
</li>
|
||||
|
|
|
@ -6,123 +6,72 @@ import '../css/index.scss';
|
|||
import Nodata from '../Nodata';
|
||||
import './list.css';
|
||||
import img_parise from '../Images/parise.png';
|
||||
import SpecialModal from './SpecialModal';
|
||||
|
||||
class IndexItem extends Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state={
|
||||
visible:false,
|
||||
user_apply_signatures:[],
|
||||
project_id:undefined
|
||||
}
|
||||
}
|
||||
TurnToDetail = (login, url) => {
|
||||
this.props.history.push({
|
||||
pathname: url,
|
||||
state: login
|
||||
})
|
||||
}
|
||||
/**
|
||||
* link:跳转到详情的地址
|
||||
* user_apply_signatures:是否已经发送访问特殊开源项目的文件
|
||||
* project_id:项目id
|
||||
* is_secret:是否是特殊开源许可证项目
|
||||
* id:创建者login
|
||||
* is_member:是否是项目成员(如果是项目成员可以直接进入项目)
|
||||
* */
|
||||
projectHref=(link , user_apply_signatures,project_id,is_secret , id,is_member)=>{
|
||||
const { user , showLoginDialog } = this.props;
|
||||
if(is_secret && (!user || (user && !user.login))){
|
||||
showLoginDialog();
|
||||
return;
|
||||
}
|
||||
let signa = user_apply_signatures && user_apply_signatures[0];
|
||||
if((is_secret && !is_member && (!signa || (signa && signa.status !== "passed"))) && user.login !== id ){
|
||||
this.setState({
|
||||
visible:true,
|
||||
user_apply_signatures:user_apply_signatures.length>0 ? user_apply_signatures[0] : undefined,
|
||||
project_id
|
||||
})
|
||||
}else{
|
||||
this.props.history.push(link);
|
||||
}
|
||||
}
|
||||
hideModal=()=>{
|
||||
this.setState({
|
||||
visible:false
|
||||
})
|
||||
}
|
||||
|
||||
sureModal=()=>{
|
||||
this.hideModal();
|
||||
const { getListData } = this.props;
|
||||
getListData && getListData(1);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { projects } = this.props;
|
||||
const { visible , user_apply_signatures , project_id } = this.state;
|
||||
const renderList = (
|
||||
projects && projects.length > 0 ? projects.map((item, key) => {
|
||||
return (
|
||||
<div className="p-r-Item" key={key}>
|
||||
{
|
||||
item.platform === "educoder" ?
|
||||
<a style={{cursor:"default"}} className="show-user-link">
|
||||
<img className="p-r-photo" alt="" src={item.author && item.author.image_url} ></img>
|
||||
</a>
|
||||
:
|
||||
<Link to={`/users/${item.author.login}`} className="show-user-link">
|
||||
<img className="p-r-photo" alt="" src={getImageUrl(`${item.author && item.author.image_url}`)} ></img>
|
||||
</Link>
|
||||
}
|
||||
<div className="p-r-Infos">
|
||||
<div className="p-r-name">
|
||||
<a onClick={()=>this.projectHref(`/projects/${item.author.login}/${item.identifier}`,item.user_apply_signatures, item.id,item.is_secret,item.author.login,item.is_member)} className="hide-1 color-grey-3 font-18 task-hide fwt-500 " style={{ whiteSpace: "wrap", display: 'flex', width: 400 }}>
|
||||
{item.author.name}/{item.name}
|
||||
{
|
||||
item.forked_from_project_id ?
|
||||
<span className="ml5">
|
||||
<i className="iconfont icon-fork font-18 color-orange" />
|
||||
</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>:""
|
||||
}
|
||||
</a>
|
||||
<span className="p-r-tags">
|
||||
<span className="pariseTag"><img src={img_parise} alt="" className="pariseImg" />赞 {item.praises_count}</span>
|
||||
<span><i className="iconfont icon-fork mr3 font-16" style={{ color: "#1B8FFF" }} />fork {item.forked_count}</span>
|
||||
</span>
|
||||
</div>
|
||||
<p className="break_word task-hide-2 mt8 color-grey-3 " 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> : ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}) : <Nodata _html="暂无数据~"></Nodata>
|
||||
)
|
||||
return (
|
||||
<div className="project-list minH-670">
|
||||
<SpecialModal {...this.props} visible={visible} hideModal={this.hideModal} user_apply_signatures={user_apply_signatures} project_id={project_id} sureModal={this.sureModal}></SpecialModal>
|
||||
{renderList}
|
||||
{ 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={`/users/${item.author.login}`} className="show-user-link">
|
||||
<img className="p-r-photo" alt="" src={getImageUrl(`${item.author && item.author.image_url}`)} ></img>
|
||||
</Link>
|
||||
}
|
||||
<div className="p-r-Infos">
|
||||
<div className="p-r-name">
|
||||
<Link to={`/projects/${item.author.login}/${item.identifier}`} className="hide-1 color-grey-3 font-18 task-hide " style={{ whiteSpace: "wrap", display: 'flex', width: 400 }}>
|
||||
{item.author.name}/{item.name}
|
||||
{
|
||||
item.forked_from_project_id ?
|
||||
<span className="ml5">
|
||||
<i className="iconfont icon-fork font-18 color-orange" />
|
||||
</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>:""
|
||||
}
|
||||
</Link>
|
||||
<span className="p-r-tags">
|
||||
<span className="pariseTag"><img src={img_parise} alt="" className="pariseImg" />赞 {item.praises_count}</span>
|
||||
<span><i className="iconfont icon-fork mr3 font-16" style={{ color: "#1B8FFF" }} />fork {item.forked_count}</span>
|
||||
</span>
|
||||
</div>
|
||||
<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> : ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}) : <Nodata _html="暂无数据~"></Nodata>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -676,6 +676,7 @@ a.color-grey-ccc:hover{
|
|||
|
||||
.commitList{
|
||||
padding:0px 30px;
|
||||
min-height: 450px;
|
||||
}
|
||||
.commitList > div{
|
||||
border-bottom: 1px solid #EEEEEE;
|
||||
|
|
|
@ -122,12 +122,12 @@ class MessageCount extends Component {
|
|||
SpinMerge: true,
|
||||
});
|
||||
const { projectsId , owner } = this.props.match.params;
|
||||
const { title, body, mergekey, pull_request } = this.state;
|
||||
const url = `/${owner}/${projectsId}/pulls/${pull_request.id}/pr_merge.json`;
|
||||
const { data, title, body, mergekey, pr_status } = this.state;
|
||||
const url = `/${owner}/${projectsId}/pulls/${data.pull_request.id}/pr_merge.json`;
|
||||
axios
|
||||
.post(url, {
|
||||
project_id: projectsId,
|
||||
id: pull_request.id,
|
||||
id: data.pull_request.id,
|
||||
do: mergekey,
|
||||
body: body,
|
||||
title: title,
|
||||
|
@ -255,11 +255,6 @@ class MessageCount extends Component {
|
|||
pull_request
|
||||
} = this.state;
|
||||
const { current_user, projectDetail } = this.props;
|
||||
|
||||
const permission = projectDetail && (projectDetail.permission === "Admin" || projectDetail.permission === "Owner" || projectDetail.permission === "Manager");
|
||||
const userLogin = current_user && current_user.login;
|
||||
const operate = userLogin && projectDetail && pr_status === 0 && permission;
|
||||
|
||||
const menu = (
|
||||
<Menu onClick={(e) => this.getOption(e)}>
|
||||
<Menu.Item key={"merge"} value="合并请求">
|
||||
|
@ -276,9 +271,11 @@ class MessageCount extends Component {
|
|||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const permission = projectDetail && (projectDetail.permission === "Admin" || projectDetail.permission === "Owner" || projectDetail.permission === "Manager");
|
||||
const userLogin = current_user && current_user.login;
|
||||
const operate = userLogin && projectDetail && pr_status === 0 && permission;
|
||||
return (
|
||||
<div className="">
|
||||
<div>
|
||||
{data ? (
|
||||
<div>
|
||||
<div className="main">
|
||||
|
@ -288,13 +285,13 @@ class MessageCount extends Component {
|
|||
<div className="ver-middle">
|
||||
<span className="mr10 ver-middle">
|
||||
<span className="font-18 fwb">
|
||||
{ data.issue && data.issue.subject}
|
||||
{data.issue.subject}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
{pull_request && (
|
||||
{data.pull_request && (
|
||||
<Tag
|
||||
className={`pr_tags_${pull_request.pull_request_staus}`}
|
||||
className={`pr_tags_${data.pull_request.pull_request_staus}`}
|
||||
>
|
||||
{pr_status === 1
|
||||
? "已合并"
|
||||
|
@ -308,10 +305,10 @@ class MessageCount extends Component {
|
|||
<div className="mt15">
|
||||
<Tag className="pr-branch-tag">
|
||||
<Link
|
||||
to={`/projects/${owner}/${pull_request.is_original?data.project_identifier:projectsId}/branch/${pull_request.head}`}
|
||||
to={`/projects/${owner}/${data.pull_request.is_original?data.project_identifier:projectsId}/branch/${data.pull_request.head}`}
|
||||
className="ver-middle"
|
||||
>
|
||||
{pull_request.is_original ? pull_request.fork_project_user : data.issue.project_author_name}:{pull_request.head}
|
||||
{data.pull_request.is_original ? data.pull_request.fork_project_user : data.issue.project_author_name}:{data.pull_request.head}
|
||||
</Link>
|
||||
</Tag>
|
||||
<span className="mr8 ver-middle">
|
||||
|
@ -323,11 +320,11 @@ class MessageCount extends Component {
|
|||
</span>
|
||||
<Tag className="pr-branch-tag">
|
||||
<Link
|
||||
to={`/projects/${owner}/${projectsId}/branch/${pull_request.base}`}
|
||||
to={`/projects/${owner}/${projectsId}/branch/${data.pull_request.base}`}
|
||||
className="ver-middle"
|
||||
>
|
||||
{/* {data.pull_request.is_fork ? data.pull_request.base : `${data.pull_request.pull_request_user}:${data.pull_request.base}`} */}
|
||||
{data.issue.project_author_name}:{pull_request.base}
|
||||
{data.issue.project_author_name}:{data.pull_request.base}
|
||||
</Link>
|
||||
</Tag>
|
||||
</div>
|
||||
|
|
|
@ -273,7 +273,7 @@ class merge extends Component {
|
|||
);
|
||||
return (
|
||||
<div className="main">
|
||||
<div className="topWrapper" style={{borderBottom:"1px solid #eee"}}>
|
||||
<div className="topWrapper" style={{borderBottom:"none"}}>
|
||||
<div className="target-detail-search">
|
||||
<Search
|
||||
placeholder="输入关键字搜索合并请求"
|
||||
|
@ -405,26 +405,26 @@ class merge extends Component {
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{data && data.search_count && data.search_count > 0 ? (
|
||||
<div style={{minHeight:"470px"}}>
|
||||
<Spin spinning={isSpin}>
|
||||
<OrderItem
|
||||
issues={issues}
|
||||
search_count={search_count}
|
||||
page={select_params.page}
|
||||
limit={select_params.limit}
|
||||
project_name={data.project_name}
|
||||
project_author_name={data.project_author_name}
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
></OrderItem>
|
||||
{Paginations}
|
||||
</Spin>
|
||||
</div>
|
||||
) : (
|
||||
<NoneData _html="暂时还没有相关数据!" projectsId={projectsId} owner={owner} />
|
||||
)}
|
||||
<div style={{minHeight:"470px"}}>
|
||||
<Spin spinning={isSpin}>
|
||||
{data && data.search_count && data.search_count > 0 ? (
|
||||
<div>
|
||||
<OrderItem
|
||||
issues={issues}
|
||||
search_count={search_count}
|
||||
page={select_params.page}
|
||||
limit={select_params.limit}
|
||||
project_name={data.project_name}
|
||||
project_author_name={data.project_author_name}
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
></OrderItem>
|
||||
{Paginations}
|
||||
</div>
|
||||
):""}
|
||||
{ data && data.issues && data.issues.length === 0 ? <NoneData _html="暂时还没有相关数据!" projectsId={projectsId} owner={owner} /> :""}
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ class MergeFooter extends Component {
|
|||
}
|
||||
{
|
||||
filesData && filesData.files && filesData.files.length>0 &&
|
||||
<TabPane tab={
|
||||
<TabPane tab={
|
||||
<span><span className="font-16">文件</span>
|
||||
{filesCount > 0 && <span className="tabNum">{filesCount}</span>}
|
||||
</span>
|
||||
|
|
|
@ -23,18 +23,23 @@ class Index extends Component {
|
|||
LanguageList: undefined,
|
||||
GitignoreList: undefined,
|
||||
LicensesList: undefined,
|
||||
OwnerList: undefined,
|
||||
isSpin: false,
|
||||
|
||||
project_language_id: undefined,
|
||||
project_category_id: undefined,
|
||||
license_id: undefined,
|
||||
ignore_id: undefined,
|
||||
owners_id:undefined,
|
||||
owners_name:undefined,
|
||||
|
||||
project_language_list: undefined,
|
||||
project_category_list: undefined,
|
||||
license_list: undefined,
|
||||
ignore_list: undefined,
|
||||
|
||||
owners_list:undefined,
|
||||
|
||||
project_language_name: undefined,
|
||||
project_category_name: undefined,
|
||||
license_name: undefined,
|
||||
|
@ -44,6 +49,8 @@ class Index extends Component {
|
|||
}
|
||||
}
|
||||
componentDidMount = () => {
|
||||
// 获取拥有者列表
|
||||
this.getOwner();
|
||||
// 获取项目类别
|
||||
this.getCategory();
|
||||
// 获取项目语言
|
||||
|
@ -52,7 +59,6 @@ class Index extends Component {
|
|||
this.getGitignore();
|
||||
// 获取开源许可证
|
||||
this.getLicenses();
|
||||
|
||||
}
|
||||
componentDidUpdate=(prevPros)=>{
|
||||
if(prevPros && this.props && !this.props.checkIfLogin()){
|
||||
|
@ -60,6 +66,31 @@ class Index extends Component {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
getOwner=()=>{
|
||||
const { OIdentifier } = this.props.match.params;
|
||||
const url = `/owners.json`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
let owner = result.data.owners;
|
||||
if(OIdentifier){
|
||||
owner = owner.filter(item=>item.name === OIdentifier);
|
||||
this.props.form.setFieldsValue({
|
||||
user_id:OIdentifier
|
||||
})
|
||||
owner && this.setState({
|
||||
owners_id:owner[0].id,
|
||||
owners_name:owner[0].name
|
||||
})
|
||||
}
|
||||
this.setOptionsList(owner, 'owners');
|
||||
this.setState({
|
||||
OwnerList: owner,
|
||||
})
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
getCategory = () => {
|
||||
const url = `/project_categories.json`
|
||||
axios.get(url).then((result) => {
|
||||
|
@ -143,9 +174,8 @@ class Index extends Component {
|
|||
this.setState({
|
||||
isSpin: true
|
||||
})
|
||||
const { current_user } = this.props;
|
||||
const { projectsType } = this.props.match.params;
|
||||
const { project_language_id, project_category_id, license_id, ignore_id } = this.state;
|
||||
const { project_language_id, project_category_id, license_id, ignore_id , owners_id , owners_name } = this.state;
|
||||
const decoderPass = Base64.encode(values.password);
|
||||
const url = projectsType === "deposit" ? "/projects.json" : "/projects/migrate.json";
|
||||
axios.post(url, {
|
||||
|
@ -155,7 +185,7 @@ class Index extends Component {
|
|||
project_category_id,
|
||||
license_id,
|
||||
ignore_id,
|
||||
user_id: current_user.user_id
|
||||
user_id:owners_id
|
||||
}).then((result) => {
|
||||
if (result) {
|
||||
if (result.data.id) {
|
||||
|
@ -163,7 +193,7 @@ class Index extends Component {
|
|||
isSpin: false
|
||||
})
|
||||
this.props.showNotification(`${projectsType === "deposit" ? "托管" : "镜像"}项目创建成功!`);
|
||||
this.props.history.push(`/projects/${current_user && current_user.login}/${result.data.identifier}`);
|
||||
this.props.history.push(`/projects/${owners_name}/${result.data.identifier}`);
|
||||
}
|
||||
}
|
||||
}).catch((error) => {
|
||||
|
@ -244,20 +274,13 @@ class Index extends Component {
|
|||
const { projectsType } = this.props.match.params;
|
||||
|
||||
const {
|
||||
preType,
|
||||
languageValue,
|
||||
gitignoreType,
|
||||
LicensesType,
|
||||
|
||||
CategoryList,
|
||||
LanguageList,
|
||||
GitignoreList,
|
||||
LicensesList,
|
||||
isSpin,
|
||||
project_language_name,
|
||||
project_category_name,
|
||||
license_name,
|
||||
ignore_name,
|
||||
owners_list,
|
||||
OwnerList,
|
||||
|
||||
project_language_list,
|
||||
project_category_list,
|
||||
|
@ -274,6 +297,26 @@ class Index extends Component {
|
|||
<Spin spinning={isSpin}>
|
||||
<Form>
|
||||
<div className="newPanel_content">
|
||||
<Form.Item
|
||||
label="拥有者"
|
||||
>
|
||||
{getFieldDecorator('user_id', {
|
||||
rules: [{
|
||||
required: true, message: '请选择拥有者'
|
||||
},{
|
||||
validator:(rule, value, callback) => this.checkId(rule, value, callback, OwnerList, '拥有者')
|
||||
}],
|
||||
})(
|
||||
<AutoComplete
|
||||
placeholder="请选择拥有者"
|
||||
onChange={(value, e) => this.ChangePlatform(value, e, 'owners', OwnerList)}
|
||||
className="plateAutoComplete"
|
||||
onBlur={(value) => this.blurCategory(value, OwnerList, "owners")}
|
||||
>
|
||||
{owners_list}
|
||||
</AutoComplete>
|
||||
)}
|
||||
</Form.Item>
|
||||
{
|
||||
projectsType !== "deposit" &&
|
||||
<React.Fragment>
|
||||
|
|
|
@ -4,9 +4,9 @@ import nodata from './Images/nodata.png';
|
|||
|
||||
class Nodata extends Component{
|
||||
render(){
|
||||
const { _html } = this.props;
|
||||
const { _html , small } = this.props;
|
||||
return(
|
||||
<div className="none_panels">
|
||||
<div className={small ? "none_panels small":"none_panels"}>
|
||||
<div>
|
||||
<img src={nodata} alt="" />
|
||||
<div className="none_p_title">{_html}</div>
|
||||
|
|
|
@ -163,7 +163,7 @@ class Milepost extends Component {
|
|||
|
||||
return (
|
||||
<Spin spinning={spinings}>
|
||||
<div className="main">
|
||||
<div className="main" style={{minHeight:"400px"}}>
|
||||
<div style={{ display: this.state.display }}>
|
||||
<div className="tagdiv" >
|
||||
<span>里程碑{data && data.issue_tags_count}已创建</span>
|
||||
|
@ -191,10 +191,10 @@ class Milepost extends Component {
|
|||
</div>
|
||||
{
|
||||
data && data.versions && data.versions.length > 0
|
||||
?
|
||||
&&
|
||||
<div className="tagList">
|
||||
{
|
||||
data.versions.length === 0 ? <NoneData></NoneData> : data.versions.map((item, key) => {
|
||||
data.versions.map((item, key) => {
|
||||
return (
|
||||
<div style={{ display: 'block' }} key={key}>
|
||||
<div className="milepostdiv">
|
||||
|
@ -251,8 +251,8 @@ class Milepost extends Component {
|
|||
})
|
||||
}
|
||||
</div>
|
||||
: <NoneData _html="暂时还没有相关数据!" />
|
||||
}
|
||||
{ data && data.versions && data.versions.length === 0 && <NoneData _html="暂无里程碑"></NoneData> }
|
||||
{
|
||||
data && data.versions_count > limit ?
|
||||
<div className="mt30 mb50 edu-txt-center">
|
||||
|
|
|
@ -282,7 +282,7 @@ class MilepostDetail extends Component {
|
|||
</div>
|
||||
{
|
||||
search_count > limit?
|
||||
<div className="mt30 mb10 edu-txt-center">
|
||||
<div className="mt30 pb30 edu-txt-center">
|
||||
<Pagination simple current={page} total={search_count} pageSize={limit} onChange={this.ChangePage}></Pagination>
|
||||
</div>:""
|
||||
}
|
||||
|
|
|
@ -7,9 +7,6 @@
|
|||
.attachment-list-div:hover {
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
.attachment-list-div:hover .attachment-list-delete {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.searchBanner{
|
||||
display: flex;
|
||||
|
|
|
@ -561,9 +561,6 @@ a.issue-type-button.active:hover {
|
|||
.paper-clip-color {
|
||||
color: #29bd8b !important;
|
||||
}
|
||||
.attachment-list-delete {
|
||||
display: none;
|
||||
}
|
||||
.attachment-list-a {
|
||||
color: rgba(0, 0, 0, 0.65) !important;
|
||||
}
|
||||
|
|
|
@ -325,7 +325,7 @@ class order extends Component {
|
|||
|
||||
deletedetail = (id) => {
|
||||
const { projectsId , owner } = this.props.match.params;
|
||||
const url = `/projects/${owner}/${projectsId}/issues/${id}.json`;
|
||||
const url = `/${owner}/${projectsId}/issues/${id}.json`;
|
||||
axios.delete(url, {
|
||||
data: {
|
||||
project_id: projectsId,
|
||||
|
@ -549,7 +549,7 @@ class order extends Component {
|
|||
</ul>
|
||||
{this.renderNew()}
|
||||
</div>
|
||||
<div className="topWrapper">
|
||||
<div className="topWrapper" style={{borderBottom:"none"}}>
|
||||
<div className="target-detail-search">
|
||||
<Search
|
||||
placeholder="输入issue名称进行搜索"
|
||||
|
|
|
@ -49,7 +49,6 @@ export default Form.create()(
|
|||
setOptions(enable_push);
|
||||
setMergeOptions(protected_branch.enable_merge_whitelist);
|
||||
setApproveOptions(protected_branch.enable_approvals_whitelist);
|
||||
console.log("111",getFieldsValue("enable_status_check"));
|
||||
}
|
||||
}
|
||||
}).catch(error=>{});
|
||||
|
@ -126,7 +125,7 @@ export default Form.create()(
|
|||
<Checkbox checked={protect} onChange={changeProtect}>
|
||||
启用分支保护<span className="color-grey-9 ml5 font-12">组织删除并限制Git推送和合并到分支</span>
|
||||
</Checkbox>,
|
||||
"setStyleRule"
|
||||
"setHeight"
|
||||
)}
|
||||
<div className="pl25 shortStyle">
|
||||
{helper(
|
||||
|
@ -146,7 +145,7 @@ export default Form.create()(
|
|||
</Radio.Group>,
|
||||
""
|
||||
)}
|
||||
<div className="pl25 pt5 pb5 mb15">
|
||||
<div className="pl25 mb15">
|
||||
{helper(
|
||||
"",
|
||||
"push_whitelist_usernames",
|
||||
|
@ -173,7 +172,7 @@ export default Form.create()(
|
|||
<Checkbox disabled={!protect} checked={mergeOptions} onChange={changeMergeOption}>
|
||||
启用合并白名单<span className="color-grey-9 ml5 font-12">仅允许白名单用户或团队合并合并请求到此分支</span>
|
||||
</Checkbox>,
|
||||
"setStyleRule"
|
||||
"setHeight"
|
||||
)}
|
||||
<div className="pl25 pt5 pb5">
|
||||
{helper(
|
||||
|
@ -202,9 +201,9 @@ export default Form.create()(
|
|||
<Checkbox disabled={!protect}>
|
||||
启用状态检查
|
||||
</Checkbox>,
|
||||
"setStyleRule",false,true
|
||||
"setHeight",false,true
|
||||
)}
|
||||
<div style={{display:"flex",alignItems:"center"}}>
|
||||
<div style={{display:"flex",alignItems:"center",padding:"10px 0px"}}>
|
||||
{helper(
|
||||
"所需的批准数",
|
||||
"required_approvals",
|
||||
|
@ -222,9 +221,9 @@ export default Form.create()(
|
|||
<Checkbox name="enable_approvals_whitelist" disabled={!protect} checked={approveOptions} onChange={changeWhitelistUsernameOption}>
|
||||
批准仅限列入白名单的用户或团队<span className="color-grey-9 ml5 font-12">只有白名单用户或团队的审核才能计数 没有批准的白名单,任何有写访问权限的人的审核都将计数</span>
|
||||
</Checkbox>,
|
||||
"setStyleRule"
|
||||
"setHeight mb5"
|
||||
)}
|
||||
<div className="pl25 pt5 pb5 mb15">
|
||||
<div className="pl25 mb15">
|
||||
{helper(
|
||||
"",
|
||||
"approvals_whitelist_usernames",
|
||||
|
@ -252,7 +251,7 @@ export default Form.create()(
|
|||
<Checkbox disabled={!protect} name="block_on_rejected_reviews">
|
||||
拒绝审核阻止了合并<span className="color-grey-9 ml5 font-12">如果官方审查人员要求作出改动,即使有足够的批准,合并也不允许</span>
|
||||
</Checkbox>,
|
||||
"setStyleRule",false,true
|
||||
"setHeight mb5",false,true
|
||||
)}
|
||||
|
||||
{helper(
|
||||
|
@ -262,7 +261,7 @@ export default Form.create()(
|
|||
<Checkbox disabled={!protect} name="dismiss_stale_approvals">
|
||||
取消过时的批准<span className="color-grey-9 ml5 font-12">当新的提交更改合并请求内容被推送到分支时,旧的批准将被撤销</span>
|
||||
</Checkbox>,
|
||||
"setStyleRule",false,true
|
||||
"setHeight mb5",false,true
|
||||
)}
|
||||
{helper(
|
||||
"",
|
||||
|
@ -271,7 +270,7 @@ export default Form.create()(
|
|||
<Checkbox disabled={!protect} name="require_signed_commits">
|
||||
需要签名提交
|
||||
</Checkbox>,
|
||||
"setStyleRule",false,true
|
||||
"setHeight mb5",false,true
|
||||
)}
|
||||
{helper(
|
||||
"",
|
||||
|
@ -280,7 +279,7 @@ export default Form.create()(
|
|||
<Checkbox disabled={!protect} name="block_on_outdated_branch">
|
||||
如果拉取请求已经过时,阻止合并<span className="color-grey-9 ml5 font-12">当头部分支落后基础分支时,不能合并</span>
|
||||
</Checkbox>,
|
||||
"setStyleRule",false,true
|
||||
"setHeight",false,true
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,528 +1,53 @@
|
|||
import React, { Component } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
Input,
|
||||
AutoComplete,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Icon,
|
||||
Spin,
|
||||
Pagination,
|
||||
Button,
|
||||
Table,
|
||||
Tooltip
|
||||
} from "antd";
|
||||
import NoneData from "../Nodata";
|
||||
import axios from "axios";
|
||||
import { getImageUrl } from "educoder";
|
||||
import React, { useState } from "react";
|
||||
import {WhiteBack} from '../Component/layout';
|
||||
import AddMember from '../Component/AddMember';
|
||||
import AddGroup from '../Component/AddGroup';
|
||||
import Member from './CollaboratorMember';
|
||||
import Group from './CollaboratorGroup';
|
||||
|
||||
const { Search } = Input;
|
||||
function Collaborator(props){
|
||||
const [ nav , setNav] = useState("1");
|
||||
const [ newId , setNewId] = useState(undefined);
|
||||
const [ newGroupId , setNewGroupId] = useState(undefined);
|
||||
const {projectsId ,owner} = props.match.params;
|
||||
|
||||
const { Option } = AutoComplete;
|
||||
const MENU_LIST = [
|
||||
{
|
||||
id: "Manager",
|
||||
name: "管理员",
|
||||
},
|
||||
{
|
||||
id: "Developer",
|
||||
name: "开发者",
|
||||
},
|
||||
{
|
||||
id: "Reporter",
|
||||
name: "报告者",
|
||||
},
|
||||
];
|
||||
const LIMIT = 15;
|
||||
class Collaborator extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
listData: undefined,
|
||||
user: undefined,
|
||||
user_id: undefined,
|
||||
userDataSource: undefined,
|
||||
page: 1,
|
||||
total_count: undefined,
|
||||
isSpin: true,
|
||||
searchKey: undefined,
|
||||
search: undefined,
|
||||
role: undefined,
|
||||
otherSpin: false,
|
||||
searchSpin: false,
|
||||
roleName: undefined,
|
||||
};
|
||||
const author = props.projectDetail && props.projectDetail.author;
|
||||
|
||||
function getID(id){
|
||||
setNewId(id);
|
||||
}
|
||||
componentDidUpdate=(prevPros)=>{
|
||||
if(prevPros && this.props && !this.props.checkIfLogin()){
|
||||
this.props.history.push("/403")
|
||||
return
|
||||
}
|
||||
function getGroupID(id){
|
||||
setNewGroupId(id);
|
||||
}
|
||||
componentDidMount = () => {
|
||||
// this.check_is_login()
|
||||
if (this.props.project_id) {
|
||||
this.getMember();
|
||||
}
|
||||
};
|
||||
|
||||
// check_is_login =() =>{
|
||||
// if(!this.props.checkIfLogin()){
|
||||
// this.props.history.push("/403")
|
||||
// return
|
||||
// }
|
||||
// };
|
||||
|
||||
componentDidUpdate = (prevState) => {
|
||||
if (
|
||||
this.props.project_id &&
|
||||
this.props.project_id !== prevState.project_id
|
||||
) {
|
||||
this.getMember();
|
||||
}
|
||||
};
|
||||
|
||||
// 获取项目协作者
|
||||
getMember = () => {
|
||||
const { page, search, role } = this.state;
|
||||
const {projectsId ,owner} = this.props.match.params;
|
||||
const url = `/${owner}/${projectsId}/collaborators.json`;
|
||||
axios
|
||||
.get(url, {
|
||||
params: {
|
||||
page,
|
||||
search: search,
|
||||
role: role,
|
||||
limit: LIMIT,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
this.setState({
|
||||
listData: result.data.members,
|
||||
isSpin: false,
|
||||
otherSpin: false,
|
||||
searchSpin: false,
|
||||
total_count: result.data.total_count,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({
|
||||
isSpin: false,
|
||||
otherSpin: false,
|
||||
searchSpin: false,
|
||||
});
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
// 输入用户
|
||||
changeInputUser = (e) => {
|
||||
this.setState({
|
||||
searchKey: e,
|
||||
});
|
||||
this.getUserList(e);
|
||||
};
|
||||
searchMember = (e) => {
|
||||
this.state.search = e;
|
||||
this.setState({
|
||||
searchSpin: true,
|
||||
});
|
||||
this.getMember();
|
||||
};
|
||||
orderMember = (id, name) => {
|
||||
this.setState({
|
||||
isSpin: true
|
||||
})
|
||||
this.state.role = id;
|
||||
this.state.roleName = name;
|
||||
this.getMember();
|
||||
};
|
||||
// 选择用户
|
||||
selectInputUser = (e, option) => {
|
||||
this.setState({
|
||||
user_id: e,
|
||||
searchKey: option.props.searchValue,
|
||||
});
|
||||
this.getUserList(option.props.searchValue);
|
||||
};
|
||||
getUserList = (e) => {
|
||||
const url = `/users/list.json`;
|
||||
axios
|
||||
.get(url, {
|
||||
params: {
|
||||
search: e,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
this.setState({
|
||||
userDataSource: result.data.users,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
// 增加协作者
|
||||
addCollaborator = () => {
|
||||
// const { project_id } = this.props;
|
||||
const { user_id } = this.state;
|
||||
if(user_id){
|
||||
this.setState({
|
||||
otherSpin: true,
|
||||
});
|
||||
const {projectsId ,owner} = this.props.match.params;
|
||||
const url = `/${owner}/${projectsId}/collaborators.json`;
|
||||
axios.post(url, {
|
||||
user_id,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
this.setState({
|
||||
isSpin: true,
|
||||
otherSpin: false,
|
||||
});
|
||||
this.getMember();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({
|
||||
isSpin: false,
|
||||
otherSpin: false,
|
||||
});
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 修改权限
|
||||
changeOperaiton = (e, id) => {
|
||||
// const { project_id, page } = this.state;
|
||||
this.setState({
|
||||
isSpin: true,
|
||||
});
|
||||
const {projectsId ,owner} = this.props.match.params;
|
||||
|
||||
const url = `/${owner}/${projectsId}/collaborators/change_role.json`;
|
||||
axios
|
||||
.put(url, {
|
||||
user_id: id,
|
||||
role: e.key,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
this.setState({
|
||||
isSpin: true,
|
||||
});
|
||||
this.props.showNotification("权限修改成功!");
|
||||
this.getMember();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({
|
||||
isSpin: false,
|
||||
});
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
// 删除协作者
|
||||
deleteUser = (id) => {
|
||||
const { page } = this.state;
|
||||
const {projectsId ,owner} = this.props.match.params;
|
||||
this.props.confirm({
|
||||
content: "确认将此成员从项目中移除?",
|
||||
onOk: () => {
|
||||
const { project_id } = this.props;
|
||||
const url = `/${owner}/${projectsId}/collaborators/remove.json`;
|
||||
axios
|
||||
.delete(url, {
|
||||
data: {
|
||||
user_id: id,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
this.setState({
|
||||
isSpin: true,
|
||||
});
|
||||
this.props.showNotification("成员删除成功!");
|
||||
this.getMember();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({
|
||||
isSpin: false,
|
||||
});
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
changePage = (page) => {
|
||||
this.state.page = page;
|
||||
this.setState({
|
||||
isSpin: true,
|
||||
});
|
||||
this.getMember();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
userDataSource,
|
||||
listData,
|
||||
isSpin,
|
||||
page,
|
||||
total_count,
|
||||
searchKey,
|
||||
otherSpin,
|
||||
searchSpin,
|
||||
roleName,
|
||||
} = this.state;
|
||||
// 获取当前项目的拥有者
|
||||
const { author } = this.props;
|
||||
const get_color = (role) => {
|
||||
if (role === "Manager") {
|
||||
return "text-green";
|
||||
} else if (role === "Developer") {
|
||||
return "text-primary";
|
||||
} else if(role === "Reporter"){
|
||||
return "text-yellow";
|
||||
}else{
|
||||
return "text-gray";
|
||||
}
|
||||
};
|
||||
const member_roles = (item) => {
|
||||
const operation = MENU_LIST.filter((i) => i.id === item.role);
|
||||
return (
|
||||
<span>
|
||||
{author && author.login === item.login ? (
|
||||
<label className={get_color(item.role)}>
|
||||
{operation && operation[0].name}
|
||||
</label>
|
||||
) :
|
||||
item.is_apply_signature ?
|
||||
<label className="text-grey">外围贡献者</label>
|
||||
|
||||
return (
|
||||
<WhiteBack>
|
||||
<div className="flex-a-center baseForm bbr">
|
||||
{
|
||||
author && author.type === "Organization" ?
|
||||
<span>
|
||||
<span style={{cursor:"pointer"}} className={nav === "1" ? "font-18 text-black color-blue":"font-18 text-black"} onClick={()=>setNav("1")}>协作者管理</span>
|
||||
<span style={{cursor:"pointer"}} className={nav === "2" ? "font-18 text-black ml30 color-blue":"font-18 text-black ml30"} onClick={()=>setNav("2")}>团队管理</span>
|
||||
</span>
|
||||
:
|
||||
(
|
||||
<Dropdown overlay={setRoles(`${item.id}`)} placement={"bottomCenter"}>
|
||||
<span className={get_color(item.role)}>
|
||||
{operation && operation[0].name}
|
||||
<Icon type="caret-down" className="ml2" size="13" />
|
||||
</span>
|
||||
</Dropdown>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
const roleTitle = (
|
||||
<div><span className="mr3">角色</span>
|
||||
<Tooltip placement='bottom' title={<div>
|
||||
<div className="mb3">管理员:拥有仓库设置功能、代码库读、写操作</div>
|
||||
<div className="mb3">开发人员:只拥有代码库读、写操作</div>
|
||||
<div className="mb3">报告者:只拥有代码库读操作</div>
|
||||
</div>
|
||||
}>
|
||||
<Icon type="question-circle"></Icon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
const columns = [
|
||||
{
|
||||
title: "头像",
|
||||
dataIndex: "image_url",
|
||||
render: (text, item) => (
|
||||
<span className="f-wrap-alignCenter">
|
||||
<Link
|
||||
to={`/users/${item.login}`}
|
||||
className="show-user-link"
|
||||
>
|
||||
<img
|
||||
src={getImageUrl(`images/${text}`)}
|
||||
alt=""
|
||||
width="32px"
|
||||
height="32px"
|
||||
className="mr3 radius"
|
||||
/>
|
||||
</Link>
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "用户名",
|
||||
dataIndex: "name",
|
||||
render: (text, item) => (
|
||||
<Link to={`/users/${item.login}`} className="show-user-link">
|
||||
{text}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "邮箱",
|
||||
dataIndex: "email",
|
||||
render: (text) => <span>{text}</span>,
|
||||
},
|
||||
{
|
||||
title: "Token值",
|
||||
dataIndex: "token",
|
||||
render: (text) => <span>{text}</span>,
|
||||
},
|
||||
{
|
||||
title: roleTitle,
|
||||
dataIndex: "role_name",
|
||||
render: (text, item) => member_roles(item),
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
dataIndex: "action",
|
||||
render: (text, item) => (
|
||||
<span style={{ justifyContent: "center" }}>
|
||||
{author && author.login !== item.login && (
|
||||
<a
|
||||
className="text-delete"
|
||||
onClick={() => this.deleteUser(item.id)}
|
||||
>
|
||||
删除
|
||||
</a>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
const roles = (id) => (
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
key={0}
|
||||
value={undefined}
|
||||
onClick={(e) => this.orderMember(undefined, "角色筛选")}
|
||||
>
|
||||
全部
|
||||
</Menu.Item>
|
||||
{MENU_LIST.map((item, key) => {
|
||||
return (
|
||||
<Menu.Item
|
||||
key={item.id}
|
||||
value={item.id}
|
||||
onClick={(e) => this.orderMember(item.id, item.name)}
|
||||
>
|
||||
{item.name}
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
);
|
||||
const setRoles = (id) => (
|
||||
<Menu>
|
||||
{MENU_LIST.map((item, key) => {
|
||||
return (
|
||||
<Menu.Item
|
||||
key={item.id}
|
||||
value={item.id}
|
||||
onClick={(e) => this.changeOperaiton(e,id)}
|
||||
>
|
||||
{item.name}
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const source =
|
||||
userDataSource &&
|
||||
userDataSource.map((item, key) => {
|
||||
return (
|
||||
<Option
|
||||
key={key}
|
||||
value={`${item.user_id}`}
|
||||
searchValue={`${item.username}`}
|
||||
>
|
||||
<img
|
||||
className="user_img radius"
|
||||
width="28"
|
||||
height="28"
|
||||
src={getImageUrl(`images/${item && item.image_url}`)}
|
||||
alt=""
|
||||
/>
|
||||
<span className="ml10" style={{ "vertical-align": "middle" }}>
|
||||
{item.username}
|
||||
<span className="color-grey ml10">({item.login})</span>
|
||||
</span>
|
||||
</Option>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<WhiteBack>
|
||||
<div className="flex-a-center baseForm bbr">
|
||||
<span className="font-18 text-black">协作者管理</span>
|
||||
<div className="addPanel">
|
||||
<AutoComplete
|
||||
dataSource={source}
|
||||
value={searchKey}
|
||||
style={{ width: 300 }}
|
||||
onChange={this.changeInputUser}
|
||||
onSelect={this.selectInputUser}
|
||||
placeholder="搜索需要添加的用户..."
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
ghost
|
||||
onClick={this.addCollaborator}
|
||||
className="ml15"
|
||||
loading={otherSpin}
|
||||
>
|
||||
<Icon type="plus" size="16"></Icon>
|
||||
添加成员
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid-item-left baseForm">
|
||||
<Search
|
||||
placeholder="搜索项目成员..."
|
||||
enterButton="搜索"
|
||||
loading={searchSpin}
|
||||
onSearch={(value) => this.searchMember(value)}
|
||||
/>
|
||||
<Dropdown overlay={roles} placement={"bottomCenter"}>
|
||||
<a className="ml180 text-primary">
|
||||
{roleName ? roleName : "角色筛选"}
|
||||
<Icon type="caret-down" size="16"></Icon>
|
||||
</a>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<Spin spinning={isSpin}>
|
||||
<div className="collaboratorList baseForm">
|
||||
{listData && listData.length>0 ? (
|
||||
<Table
|
||||
pagination={false}
|
||||
columns={columns}
|
||||
dataSource={listData}
|
||||
rowKey={(record) => record.id}
|
||||
></Table>
|
||||
) : (
|
||||
<NoneData _html="暂时还没有相关数据!" />
|
||||
)}
|
||||
</div>
|
||||
</Spin>
|
||||
{total_count && total_count > LIMIT ? (
|
||||
<div className="edu-txt-center mt20 mb20">
|
||||
<Pagination
|
||||
showQuickJumper
|
||||
pageSize={LIMIT}
|
||||
current={page}
|
||||
total={total_count}
|
||||
onChange={this.changePage}
|
||||
></Pagination>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</WhiteBack>
|
||||
);
|
||||
}
|
||||
}
|
||||
{
|
||||
nav === "1" ?
|
||||
<AddMember getID={getID} login/>
|
||||
:
|
||||
<AddGroup getGroupID={getGroupID} organizeId={owner}/>
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
{
|
||||
nav === "1" ?
|
||||
<Member newId={newId} projectsId={projectsId} owner={owner} project_id={props.project_id} author={props.author} showNotification={props.showNotification}/>
|
||||
:
|
||||
<Group owner={owner} projectsId={projectsId} newGroupId={newGroupId}/>
|
||||
}
|
||||
</div>
|
||||
</WhiteBack>
|
||||
);
|
||||
}
|
||||
export default Collaborator;
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Table , Button , Popconfirm , Pagination } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
|
||||
const roles = {
|
||||
owner:"所有者",
|
||||
admin:"管理者",
|
||||
write:"开发者",
|
||||
read:"报告者"
|
||||
}
|
||||
const limit = 15;
|
||||
function CollaboratorGroup({newGroupId,owner , projectsId}){
|
||||
const [ list , setList ] = useState(undefined);
|
||||
const [ isSpin , setIsSpin ] = useState(false);
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ total , setTotal ] = useState(0);
|
||||
|
||||
useEffect(()=>{
|
||||
getData();
|
||||
},[])
|
||||
|
||||
function getData(){
|
||||
const url = `/${owner}/${projectsId}/teams.json`;
|
||||
axios.get(url,{
|
||||
params:{
|
||||
page,limit
|
||||
}
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setList(result.data.teams);
|
||||
setTotal(result.data.total_count);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
if(newGroupId){
|
||||
addGroup(newGroupId);
|
||||
}
|
||||
},[newGroupId])
|
||||
// 添加团队
|
||||
function addGroup(id){
|
||||
const url = `/${owner}/${projectsId}/teams.json`;
|
||||
axios.post(url,{
|
||||
team_id:id
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
getData();
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
// 删除团队
|
||||
function deleteGroup(id){
|
||||
const url = `/${owner}/${projectsId}/teams/${id}.json`;
|
||||
axios.delete(url).then(result=>{
|
||||
if(result && result.data){
|
||||
getData();
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title:"团队名",
|
||||
dataIndex:"name",
|
||||
render:(value,item)=>{
|
||||
return <Link to={`/organize/${owner}/group/${item.id}`}>{value}</Link>
|
||||
}
|
||||
},{
|
||||
title:"权限",
|
||||
dataIndex:"authorize",
|
||||
width:"20%",
|
||||
render:(value,item)=>{
|
||||
return roles[value]
|
||||
}
|
||||
},{
|
||||
title:"操作",
|
||||
dataIndex:"operation",
|
||||
width:"25%",
|
||||
render:(value,item)=>{
|
||||
return(
|
||||
item.can_remove && <Popconfirm title={`确定要删除‘${item.name}’团队?`} okText="是" cancelText="否" onConfirm={()=>{deleteGroup(item.id)}}><Button type="danger">删除</Button></Popconfirm>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
return(
|
||||
<div className="padding20-30" style={{minHeight:"400px"}}>
|
||||
<Table
|
||||
dataSource={list}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
loading={isSpin}
|
||||
></Table>
|
||||
{
|
||||
total > limit ?
|
||||
<div className="pb20 mt20 edu-txt-center">
|
||||
<Pagination simple current={page}total={total} pageSize={limit} onChange={(page)=>{setPage(page)}}/>
|
||||
</div>
|
||||
:""
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default CollaboratorGroup;
|
|
@ -0,0 +1,277 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Dropdown, Menu, Icon , Input , Spin , Table , Tooltip , Pagination , Popconfirm } from "antd";
|
||||
import axios from 'axios';
|
||||
import NoneData from "../Nodata";
|
||||
import { Link } from "react-router-dom";
|
||||
import { getImageUrl } from "educoder";
|
||||
|
||||
const { Search } = Input;
|
||||
const LIMIT = 15;
|
||||
const MENU_LIST = [
|
||||
{id: "Manager",name: "管理员"},
|
||||
{id: "Developer",name: "开发者"},
|
||||
{id: "Reporter",name: "报告者"}
|
||||
];
|
||||
function CollaboratorMember({projectsId,owner,project_id,author,showNotification,newId}){
|
||||
const [ roleName , setRoleName ] = useState(undefined);
|
||||
const [ search , setSearch ] = useState(undefined);
|
||||
const [ page , setPage ] = useState(undefined);
|
||||
const [ isSpin , setIsSpin ] = useState(false);
|
||||
const [ role , setRole ] = useState(undefined);
|
||||
const [ listData , setListData ] = useState(undefined);
|
||||
const [ total , setTotal ] = useState(0);
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(newId){
|
||||
addCollaborator(newId);
|
||||
}
|
||||
},[newId])
|
||||
// 增加协作者
|
||||
function addCollaborator(id){
|
||||
if(id){
|
||||
const url = `/${owner}/${projectsId}/collaborators.json`;
|
||||
axios.post(url, {
|
||||
user_id:id,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
getMember();
|
||||
}
|
||||
})
|
||||
.catch((error) => {});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
if(project_id && projectsId && owner) {
|
||||
getMember();
|
||||
}
|
||||
},[project_id,search,page,role])
|
||||
|
||||
// 获取项目协作者
|
||||
function getMember(){
|
||||
setIsSpin(true);
|
||||
const url = `/${owner}/${projectsId}/collaborators.json`;
|
||||
axios.get(url, {
|
||||
params: {
|
||||
page,search,role,limit: LIMIT,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
setListData(result.data.members);
|
||||
setTotal(result.data.total_count);
|
||||
setIsSpin(false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {setIsSpin(false);});
|
||||
};
|
||||
|
||||
function orderMember(id, name){
|
||||
setRole(id);
|
||||
setRoleName(name);
|
||||
};
|
||||
const roles = (id) => (
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
key={0}
|
||||
value={undefined}
|
||||
onClick={(e) => orderMember(undefined, "角色筛选")}
|
||||
>
|
||||
全部
|
||||
</Menu.Item>
|
||||
{MENU_LIST.map((item, key) => {
|
||||
return (
|
||||
<Menu.Item
|
||||
key={item.id}
|
||||
value={item.id}
|
||||
onClick={(e) => orderMember(item.id, item.name)}
|
||||
>
|
||||
{item.name}
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
// 修改权限
|
||||
function changeOperaiton(e, id){
|
||||
const url = `/${owner}/${projectsId}/collaborators/change_role.json`;
|
||||
axios.put(url, {
|
||||
user_id: id,
|
||||
role: e.key,
|
||||
}).then((result) => {
|
||||
if (result) {
|
||||
showNotification("权限修改成功!");
|
||||
getMember();
|
||||
}
|
||||
})
|
||||
.catch((error) => {});
|
||||
};
|
||||
// 删除协作者
|
||||
function deleteUser(id){
|
||||
const url = `/${owner}/${projectsId}/collaborators/remove.json`;
|
||||
axios.delete(url, {
|
||||
data: {user_id: id,},
|
||||
})
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
showNotification("成员删除成功!");
|
||||
getMember();
|
||||
}
|
||||
}).catch((error) => {});
|
||||
};
|
||||
const roleTitle = (
|
||||
<div><span className="mr3">角色</span>
|
||||
<Tooltip placement='bottom' title={<div>
|
||||
<div className="mb3">管理员:拥有仓库设置功能、代码库读、写操作</div>
|
||||
<div className="mb3">开发人员:只拥有代码库读、写操作</div>
|
||||
<div className="mb3">报告者:只拥有代码库读操作</div>
|
||||
</div>
|
||||
}>
|
||||
<Icon type="question-circle"></Icon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
const get_color = (role) => {
|
||||
if (role === "Manager") {
|
||||
return "text-green";
|
||||
} else if (role === "Developer") {
|
||||
return "text-primary";
|
||||
} else {
|
||||
return "text-yellow";
|
||||
}
|
||||
};
|
||||
const setRoles = (id) => (
|
||||
<Menu>
|
||||
{MENU_LIST.map((item, key) => {
|
||||
return (
|
||||
<Menu.Item
|
||||
key={item.id}
|
||||
value={item.id}
|
||||
onClick={(e) => changeOperaiton(e,id)}
|
||||
>
|
||||
{item.name}
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
);
|
||||
const member_roles = (item) => {
|
||||
const operation = MENU_LIST.filter((i) => i.id === item.role);
|
||||
return (
|
||||
<span>
|
||||
{author && author.login === item.login ? (
|
||||
<label className={get_color(item.role)}>
|
||||
{operation && operation[0].name}
|
||||
</label>
|
||||
) : (
|
||||
<Dropdown overlay={setRoles(`${item.id}`)} placement={"bottomCenter"}>
|
||||
<span className={get_color(item.role)}>
|
||||
{operation && operation[0].name}
|
||||
<Icon type="caret-down" className="ml2" size="13" />
|
||||
</span>
|
||||
</Dropdown>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
const columns = [
|
||||
{
|
||||
title: "头像",
|
||||
dataIndex: "image_url",
|
||||
render: (text, item) => (
|
||||
<span className="f-wrap-alignCenter">
|
||||
<Link
|
||||
to={`/users/${item.login}`}
|
||||
className="show-user-link"
|
||||
>
|
||||
<img
|
||||
src={getImageUrl(`images/${text}`)}
|
||||
alt=""
|
||||
width="32px"
|
||||
height="32px"
|
||||
className="mr3 radius"
|
||||
/>
|
||||
</Link>
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "用户名",
|
||||
dataIndex: "name",
|
||||
render: (text, item) => (
|
||||
<Link to={`/users/${item.login}`} className="show-user-link">
|
||||
{text}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "邮箱",
|
||||
dataIndex: "email",
|
||||
render: (text) => <span>{text}</span>,
|
||||
},
|
||||
{
|
||||
title: roleTitle,
|
||||
dataIndex: "role_name",
|
||||
render: (text, item) => member_roles(item),
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
dataIndex: "action",
|
||||
render: (text, item) => (
|
||||
<span style={{ justifyContent: "center" }}>
|
||||
{author && author.login !== item.login && (
|
||||
<Popconfirm title="确认将此成员从项目中移除?" okText="是" cancelText="否" onConfirm={() => deleteUser(item.id)}>
|
||||
<a className="text-delete">删除</a>
|
||||
</Popconfirm>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
return(
|
||||
<React.Fragment>
|
||||
<div className="baseForm" style={{display:'flex',justifyContent:"space-between",alignItems:"center"}}>
|
||||
<Dropdown overlay={roles} placement={"bottomCenter"}>
|
||||
<a className="text-primary">
|
||||
{roleName || "角色筛选"}
|
||||
<Icon type="caret-down" size="16"></Icon>
|
||||
</a>
|
||||
</Dropdown>
|
||||
<Search
|
||||
placeholder="搜索项目成员..."
|
||||
enterButton="搜索"
|
||||
onSearch={setSearch}
|
||||
style={{width:300}}
|
||||
/>
|
||||
</div>
|
||||
<Spin spinning={isSpin}>
|
||||
<div className="collaboratorList baseForm">
|
||||
{ listData && listData.length>0 &&
|
||||
<Table
|
||||
pagination={false}
|
||||
columns={columns}
|
||||
dataSource={listData}
|
||||
rowKey={(record) => record.id}
|
||||
></Table>
|
||||
}
|
||||
{ listData && listData.length === 0 && <NoneData _html="暂时还没有相关数据!" />}
|
||||
</div>
|
||||
</Spin>
|
||||
{total > LIMIT ?
|
||||
<div className="edu-txt-center mt20 mb20">
|
||||
<Pagination
|
||||
showQuickJumper
|
||||
pageSize={LIMIT}
|
||||
current={page}
|
||||
total={total}
|
||||
onChange={()=>setPage(page)}
|
||||
></Pagination>
|
||||
</div>
|
||||
:""}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
export default CollaboratorMember;
|
|
@ -48,8 +48,8 @@ class Index extends Component {
|
|||
|
||||
const flag = pathname === `/projects/${owner}/${projectsId}/setting`;
|
||||
return (
|
||||
<div className="ProjectListIndex">
|
||||
<div style={{width:"28%",borderRadius:"5px",marginBottom:"30%"}}>
|
||||
<Box className="ProjectListIndex">
|
||||
<Short>
|
||||
<ul className="list-l-Menu">
|
||||
<li className={flag ? "active" : ""}>
|
||||
<p>
|
||||
|
@ -120,9 +120,9 @@ class Index extends Component {
|
|||
</p>
|
||||
</li> */}
|
||||
</ul>
|
||||
</div>
|
||||
<div style={{width:"72%",borderRadius:"5px",marginBottom:"30px"}}>
|
||||
<div style={{paddingLeft:"20px",boxSizing:"border-box"}}>
|
||||
</Short>
|
||||
<Long>
|
||||
<Gap>
|
||||
<Switch {...this.props}>
|
||||
{/* 协作者 */}
|
||||
<Route
|
||||
|
@ -178,9 +178,9 @@ class Index extends Component {
|
|||
)}
|
||||
></Route>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Gap>
|
||||
</Long>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ class NewTags extends Component {
|
|||
description: "",
|
||||
id: "",
|
||||
modelname: "",
|
||||
isSpin: false,
|
||||
isSpin: true,
|
||||
};
|
||||
}
|
||||
componentDidUpdate=(prevPros)=>{
|
||||
|
@ -60,6 +60,9 @@ class NewTags extends Component {
|
|||
};
|
||||
|
||||
getList = (page, order_name, order_type) => {
|
||||
this.setState({
|
||||
isSpin:true
|
||||
})
|
||||
const { projectsId , owner } = this.props.match.params;
|
||||
const { limit } = this.state;
|
||||
const url = `/${owner}/${projectsId}/labels.json`;
|
||||
|
@ -76,6 +79,7 @@ class NewTags extends Component {
|
|||
if (result) {
|
||||
this.setState({
|
||||
data: result.data,
|
||||
isSpin:false
|
||||
});
|
||||
}
|
||||
})
|
||||
|
@ -247,7 +251,6 @@ class NewTags extends Component {
|
|||
|
||||
render() {
|
||||
const { data, limit, page, isSpin } = this.state;
|
||||
const { projectsId } = this.props.match.params;
|
||||
const { getFieldDecorator } = this.props.form;
|
||||
|
||||
const menu = (
|
||||
|
@ -409,8 +412,8 @@ class NewTags extends Component {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <NoneData _html="暂时还没有相关数据!" />;
|
||||
} else if(data && data.issue_tags && data.issue_tags.length === 0) {
|
||||
return <NoneData _html="暂时还没有相关数据!" />
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -442,7 +445,7 @@ class NewTags extends Component {
|
|||
message: "请填写标签名字",
|
||||
},
|
||||
],
|
||||
})(<Input placeholder="标签名字" maxLength="10" />)}
|
||||
})(<Input placeholder="标签名字" maxLength="20" />)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="inputcount">
|
||||
|
@ -473,25 +476,23 @@ class NewTags extends Component {
|
|||
) : null}
|
||||
</div>
|
||||
<div className="fr" style={{ marginTop: 5 }}>
|
||||
<Spin spinning={isSpin}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={this.createtagpost}
|
||||
className="fr"
|
||||
>
|
||||
创建标签
|
||||
</Button>
|
||||
{/* <a onClick={this.createtagpost} className="topWrapper_btn fr" >创建标签</a> */}
|
||||
<a onClick={this.newclose} className="a_btn cancel_btn fr">
|
||||
取消
|
||||
</a>
|
||||
</Spin>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={this.createtagpost}
|
||||
className="fr"
|
||||
>
|
||||
创建标签
|
||||
</Button>
|
||||
{/* <a onClick={this.createtagpost} className="topWrapper_btn fr" >创建标签</a> */}
|
||||
<a onClick={this.newclose} className="a_btn cancel_btn fr">
|
||||
取消
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
<div className="padding20-30">
|
||||
{renderList()}
|
||||
<Spin spinning={isSpin}><div style={{minHeight:"350px"}}>{renderList()}</div></Spin>
|
||||
{Paginations}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -507,7 +508,7 @@ class NewTags extends Component {
|
|||
<div className="dialogdiv">
|
||||
<Input
|
||||
placeholder="标签名字"
|
||||
maxLength="10"
|
||||
maxLength="20"
|
||||
className="inptwidth"
|
||||
value={this.state.name}
|
||||
onChange={this.changmodelname}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
||||
export default (({ name , count , bottom , children })=>{
|
||||
export default (({ name , count , bottom , children , url })=>{
|
||||
return(
|
||||
<div className="box">
|
||||
<div className="head">
|
||||
<span>{name}</span>
|
||||
<span>{count}<i className="iconfont icon-youjiantou font-12 ml3"></i></span>
|
||||
<span className="font-16">{name}</span>
|
||||
<Link to={url}>{count}<i className="iconfont icon-youjiantou font-12 ml3"></i></Link>
|
||||
</div>
|
||||
<div className="content">
|
||||
{children}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import React, { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import Modals from './Modals';
|
||||
|
||||
|
||||
const ALink = styled.a`{
|
||||
color:#F73030!important;
|
||||
}`
|
||||
function LeaveTeam({ teamID , onOk , className }){
|
||||
const [ visible , setVisible ] = useState(false);
|
||||
|
||||
return(
|
||||
<React.Fragment>
|
||||
<ALink className={className} onClick={()=>setVisible(true)}>离开团队</ALink>
|
||||
<Modals visible={visible} okText={"确定"} cancelText={"取消"} onCancel={()=>setVisible(false)} onOk={()=>onOk(teamID)}>
|
||||
<p className="font-16 edu-txt-center">确定要离开当前团队吗?</p>
|
||||
</Modals>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
export default LeaveTeam;
|
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
import { Modal } from 'antd';
|
||||
|
||||
function Modals({visible , okText , cancelText , onOk , onCancel , children}){
|
||||
|
||||
return(
|
||||
<Modal
|
||||
visible={visible}
|
||||
okText={okText}
|
||||
onCancel={onCancel}
|
||||
onOk={onOk}
|
||||
cancelText={cancelText}
|
||||
title={"提示"}
|
||||
closable={false}
|
||||
centered
|
||||
>
|
||||
{children}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
export default Modals;
|
|
@ -0,0 +1,63 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Upload , Icon , message } from "antd";
|
||||
import { getUploadActionUrl } from 'educoder';
|
||||
|
||||
function UploadImage({ getImage , url }){
|
||||
const [ imageUrl , setImageUrl ] = useState(undefined);
|
||||
useEffect(()=>{
|
||||
if(url){
|
||||
setImageUrl(url);
|
||||
}
|
||||
},[url])
|
||||
|
||||
function getBase64(img, callback) {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () => callback(reader.result));
|
||||
reader.readAsDataURL(img);
|
||||
reader.onload = function(e) {
|
||||
getImage && getImage(e.target.result); // 上传的图片的编码
|
||||
}
|
||||
}
|
||||
// 上传前
|
||||
function beforeUpload(file){
|
||||
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
|
||||
if (!isJpgOrPng) {
|
||||
message.error('上传的图片只能是JPG或者PNG格式!');
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
if (!isLt2M) {
|
||||
message.error('上传的图片不能超过2MB!');
|
||||
}
|
||||
return isJpgOrPng && isLt2M;
|
||||
}
|
||||
// 上传完成后
|
||||
function handleChange(info){
|
||||
if(info && info.file && info.file.status === "done"){
|
||||
getBase64(info.file.originFileObj, imageUrl =>
|
||||
setImageUrl(imageUrl)
|
||||
);
|
||||
}
|
||||
}
|
||||
return(
|
||||
<Upload
|
||||
name="file"
|
||||
listType="picture-card"
|
||||
className="avatar-uploader"
|
||||
showUploadList={false}
|
||||
action={getUploadActionUrl()}
|
||||
beforeUpload={beforeUpload}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{
|
||||
imageUrl ?
|
||||
<img src={imageUrl} alt="avatar" style={{ width: '100%' }} />
|
||||
:
|
||||
<div>
|
||||
<Icon type={'plus'} />
|
||||
<div className="ant-upload-text">点击上传</div>
|
||||
</div>
|
||||
}
|
||||
</Upload>
|
||||
)
|
||||
}
|
||||
export default UploadImage;
|
|
@ -1,35 +1,26 @@
|
|||
import React from 'react';
|
||||
import Cards from '../../Component/MemberCards';
|
||||
import Nodata from '../../Nodata';
|
||||
|
||||
export default (()=>{
|
||||
return(
|
||||
<div>
|
||||
<div className="MemberBoxThree">
|
||||
<Cards
|
||||
img="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3331079987,1190181307&fm=111&gp=0.jpg"
|
||||
name="陈教授"
|
||||
time="2020-04-29"
|
||||
focusStatus={true}
|
||||
/>
|
||||
<Cards
|
||||
img="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3331079987,1190181307&fm=111&gp=0.jpg"
|
||||
name="陈教授"
|
||||
time="2020-04-29"
|
||||
focusStatus={true}
|
||||
/>
|
||||
<Cards
|
||||
img="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3331079987,1190181307&fm=111&gp=0.jpg"
|
||||
name="陈教授"
|
||||
time="2020-04-29"
|
||||
focusStatus={true}
|
||||
/>
|
||||
<Cards
|
||||
img="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3331079987,1190181307&fm=111&gp=0.jpg"
|
||||
name="陈教授"
|
||||
time="2020-04-29"
|
||||
focusStatus={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
export default (({ data , current_user , successFunc }) => {
|
||||
return (
|
||||
data && data.length > 0 ?
|
||||
<div className="MemberBoxThree">
|
||||
{
|
||||
data.map((item, key) => {
|
||||
return (
|
||||
<Cards
|
||||
img={item.user.image_url}
|
||||
name={item.user.name}
|
||||
time={item.created_at}
|
||||
focusStatus={item.user.watched}
|
||||
is_current_user={current_user && item.user.login === current_user.login}
|
||||
login={item.user.login}
|
||||
successFunc={successFunc}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>:<Nodata _html="暂无团队成员"/>
|
||||
)
|
||||
})
|
|
@ -1,9 +1,12 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getImageUrl } from 'educoder';
|
||||
import Nodata from '../../Nodata';
|
||||
|
||||
const Box=styled.div`{
|
||||
const Box = styled.div`{
|
||||
padding:0px 38px;
|
||||
min-height:400px;
|
||||
}`
|
||||
const Div = styled.div`{
|
||||
display:flex;
|
||||
|
@ -15,23 +18,24 @@ const Div = styled.div`{
|
|||
}
|
||||
}`
|
||||
const Imgs = styled.img`{
|
||||
width:30px;
|
||||
height:30px;
|
||||
width:60px;
|
||||
margin-right:12px;
|
||||
border-radius:50%;
|
||||
}`
|
||||
|
||||
export default (()=>{
|
||||
return(
|
||||
export default (({projects}) => {
|
||||
return (
|
||||
projects && projects.length > 0 ?
|
||||
<Box>
|
||||
<Div>
|
||||
<Imgs src="https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3025493530,1989042357&fm=26&gp=0.jpg"/>
|
||||
<Link to={""}>ajdfwkerijwirjklsf</Link>
|
||||
</Div>
|
||||
<Div>
|
||||
<Imgs src="https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3025493530,1989042357&fm=26&gp=0.jpg"/>
|
||||
<Link to={""}>ajdfwkerijwirjklsf</Link>
|
||||
</Div>
|
||||
</Box>
|
||||
{
|
||||
projects.map((item, key) => {
|
||||
return (
|
||||
<Div>
|
||||
<Imgs src={item.project && getImageUrl(`images/${item.project.owner_image_url}`)}/>
|
||||
<Link to={`/projects/${item.project.owner_name}/${item.project.identifier}`}>{item.project.name}</Link>
|
||||
</Div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Box>:<Nodata _html="暂无团队项目"/>
|
||||
)
|
||||
})
|
|
@ -11,25 +11,33 @@ const Common = Loadable({
|
|||
loader: () => import("./SettingCommon"),
|
||||
loading: Loading,
|
||||
});
|
||||
const Member = Loadable({
|
||||
loader: () => import("./Setting/GroupMemberSetting"),
|
||||
loading: Loading,
|
||||
});
|
||||
const Project = Loadable({
|
||||
loader: () => import("./Setting/GroupProjectSetting"),
|
||||
loading: Loading,
|
||||
});
|
||||
export default (props)=>{
|
||||
const pathname = props.location.pathname;
|
||||
const organizeId = props.match.params.organizeId;
|
||||
const memberId = props.match.params.memberId;
|
||||
const OIdentifier = props.match.params.OIdentifier;
|
||||
const groupId = props.match.params.groupId;
|
||||
|
||||
function returnActive (pathname){
|
||||
let a = 0;
|
||||
if(pathname === `/organize/${organizeId}/member/${memberId}/setting/member`){
|
||||
if(pathname === `/organize/${OIdentifier}/group/${groupId}/setting/member`){
|
||||
a = 1;
|
||||
}else if(pathname === `/organize/${organizeId}/member/${memberId}/setting/project`){
|
||||
}else if(pathname === `/organize/${OIdentifier}/group/${groupId}/setting/project`){
|
||||
a = 2;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
const active = returnActive(pathname);
|
||||
const array = {list:[
|
||||
{name:'基本设置',icon:"icon-base",href:`/organize/${organizeId}/member/${memberId}/setting`},
|
||||
{name:'团队成员管理',icon:"icon-zuzhichengyuan",href:`/organize/${organizeId}/member/${memberId}/setting/member`},
|
||||
{name:'团队项目管理',icon:"icon-zuzhixiangmu",href:`/organize/${organizeId}/member/${memberId}/setting/project`},
|
||||
{name:'基本设置',icon:"icon-base",href:`/organize/${OIdentifier}/group/${groupId}/setting`},
|
||||
{name:'团队成员管理',icon:"icon-zuzhichengyuan",href:`/organize/${OIdentifier}/group/${groupId}/setting/member`},
|
||||
{name:'团队项目管理',icon:"icon-zuzhixiangmu",href:`/organize/${OIdentifier}/group/${groupId}/setting/project`},
|
||||
],
|
||||
active
|
||||
}
|
||||
|
@ -43,7 +51,19 @@ export default (props)=>{
|
|||
<WhiteBack>
|
||||
<Switch>
|
||||
<Route
|
||||
path="/organize/:organizeId/member/:memberId/setting"
|
||||
path="/organize/:OIdentifier/group/:groupId/setting/project"
|
||||
render={() => (
|
||||
<Project {...props} />
|
||||
)}
|
||||
></Route>
|
||||
<Route
|
||||
path="/organize/:OIdentifier/group/:groupId/setting/member"
|
||||
render={() => (
|
||||
<Member {...props} />
|
||||
)}
|
||||
></Route>
|
||||
<Route
|
||||
path="/organize/:OIdentifier/group/:groupId/setting"
|
||||
render={() => (
|
||||
<Common {...props} />
|
||||
)}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React , {useState} from 'react';
|
||||
import { Button } from 'antd';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Button , Popconfirm, Empty , Pagination } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { Box , Short , Long , Gap , WhiteBack , AlignCenterBetween } from '../../Component/layout';
|
||||
import { Box, Short, Long, Gap, WhiteBack, AlignCenterBetween } from '../../Component/layout';
|
||||
import Tabs from '../../Component/Tabs';
|
||||
|
||||
import Memberlist from './GroupDetailMember';
|
||||
import Grouplist from './GroupDetailProject';
|
||||
|
||||
import axios from 'axios';
|
||||
const Leave = styled.a`{
|
||||
display:block;
|
||||
border-radius:5px;
|
||||
|
@ -15,33 +15,130 @@ const Leave = styled.a`{
|
|||
padding:0px 14px;
|
||||
height:30px;
|
||||
line-height:30px;
|
||||
}`
|
||||
export default ((props)=>{
|
||||
const [ nav , setNav ] = useState('0');
|
||||
}`;
|
||||
const limit = 15;
|
||||
export default ((props) => {
|
||||
const [nav, setNav] = useState('0');
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ total , setTotal ] = useState(0);
|
||||
const [ projects , setProjects ] = useState(undefined);
|
||||
const [ members , setMembers ] = useState(undefined);
|
||||
const [ group , setGroup ] = useState(undefined);
|
||||
const { OIdentifier, groupId } = props.match.params;
|
||||
const { current_user } = props;
|
||||
|
||||
return(
|
||||
useEffect(()=>{
|
||||
if(groupId){
|
||||
getInit();
|
||||
}
|
||||
},[groupId]);
|
||||
|
||||
function getInit(){
|
||||
const url = `/organizations/${OIdentifier}/teams/${groupId}.json`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
setGroup(result.data);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
if(nav === "0"){
|
||||
getMember(OIdentifier, groupId,page);
|
||||
}else{
|
||||
get_project(OIdentifier, groupId,page);
|
||||
}
|
||||
},[nav,page])
|
||||
|
||||
// 获取团队成员
|
||||
function getMember( OIdentifier, groupId, page ) {
|
||||
const url = `/organizations/${OIdentifier}/teams/${groupId}/team_users.json`;
|
||||
axios.get(url, {
|
||||
params: {
|
||||
page,
|
||||
limit
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
if (result && result.data) {
|
||||
setMembers(result.data.team_users);
|
||||
setTotal(result.data.total_count);
|
||||
}
|
||||
}).catch((error) => { });
|
||||
};
|
||||
|
||||
// 获取团队项目
|
||||
function get_project(OIdentifier,groupId,page) {
|
||||
const url = `/organizations/${OIdentifier}/teams/${groupId}/team_projects.json`;
|
||||
axios.get(url, {
|
||||
params: {
|
||||
page,limit
|
||||
},
|
||||
}).then((result) => {
|
||||
if (result && result.data && result.data.team_projects.length > 0) {
|
||||
setProjects(result.data.team_projects);
|
||||
setTotal(result.data.total_count);
|
||||
}
|
||||
}).catch((error) => { });
|
||||
}
|
||||
|
||||
// 移除成员
|
||||
function removeUser(username) {
|
||||
const url = `/organizations/${OIdentifier}/teams/${groupId}/team_users/${username}.json`;
|
||||
if (username) {
|
||||
axios.delete(url).then((result) => {
|
||||
if (result && result.data) {
|
||||
|
||||
}
|
||||
}).catch((error) => { });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className="GroupSubLevel">
|
||||
<Short className="g-sub-left">
|
||||
<AlignCenterBetween>
|
||||
<span className="color-grey-3">Owndsknamename</span>
|
||||
<Leave>离开团队</Leave>
|
||||
</AlignCenterBetween>
|
||||
<div className="g-desc">该团队暂无描述</div>
|
||||
<div className="g-tip">
|
||||
<p>管理员团队对 <span>所有仓库</span> 具有操作权限,且对组织具有 <span>管理员权限</span>。 </p>
|
||||
<p>此外,该团队拥有了 <span>创建仓库</span> 的权限:成员可以在组织中创建新的仓库。 </p>
|
||||
<Button type="primary">团队设置</Button>
|
||||
</div>
|
||||
{
|
||||
group ?
|
||||
<div>
|
||||
<AlignCenterBetween>
|
||||
<span className="color-grey-3">{group.name}</span>
|
||||
{group.is_member && !group.is_admin ?
|
||||
<Popconfirm
|
||||
title="确认离开团队吗?"
|
||||
onConfirm={() => removeUser(current_user.login)}
|
||||
okText="确认"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Leave>离开团队</Leave>
|
||||
</Popconfirm>
|
||||
: ""
|
||||
}
|
||||
</AlignCenterBetween>
|
||||
<div className="g-desc">{group.description ? group.description : "暂无描述"}</div>
|
||||
<div className="g-tip">
|
||||
<p>管理员团队对 <span>所有仓库</span> 具有操作权限,且对组织具有 <span>管理员权限</span>。 </p>
|
||||
<p>此外,该团队拥有了 <span>创建仓库</span> 的权限:成员可以在组织中创建新的仓库。 </p>
|
||||
{group.is_admin ? <Button type="primary" onClick={()=>props.history.push(`/organize/${OIdentifier}/group/${groupId}/setting`)}><span className="color-white">团队设置</span></Button> : ""}
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
}
|
||||
</Short>
|
||||
<Long>
|
||||
<Gap>
|
||||
<WhiteBack>
|
||||
<Tabs nav={['团队成员','团队项目']} index={nav} onChange={setNav}>
|
||||
<Tabs nav={['团队成员', '团队项目']} index={nav} onChange={setNav}>
|
||||
{
|
||||
nav === "0" ?
|
||||
<Memberlist />:<Grouplist />
|
||||
nav === "0" ? <Memberlist data={members} current_user={props.current_user} successFunc={()=>getMember( OIdentifier, groupId, page )}/> : <Grouplist projects={projects}/>
|
||||
}
|
||||
</Tabs>
|
||||
{
|
||||
total > limit &&
|
||||
<div className="mt20 pb20 edu-txt-center">
|
||||
<Pagination simple current={page} total={total} pageSize={limit} onChange={(page)=>setPage(page)}/>
|
||||
</div>
|
||||
}
|
||||
</WhiteBack>
|
||||
</Gap>
|
||||
</Long>
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
import React, { forwardRef, useCallback, useEffect, useState } from 'react';
|
||||
import { Form, Input, Radio, Checkbox, Switch, Button, Spin } from 'antd';
|
||||
import { Banner, WhiteBack, AlignCenter, Cancel } from '../../Component/layout';
|
||||
import styled from 'styled-components';
|
||||
import axios from 'axios';
|
||||
const TextArea = Input.TextArea;
|
||||
const Div = styled.div`{
|
||||
padding:20px 30px;
|
||||
}`
|
||||
const OptionStyle = {
|
||||
lineHeight: "25px",
|
||||
height: '25px',
|
||||
display: 'block'
|
||||
}
|
||||
const addStyle = {
|
||||
...OptionStyle,
|
||||
marginBottom: "7px"
|
||||
}
|
||||
|
||||
export default Form.create()(
|
||||
forwardRef(({ form , match, showNotification, history, GroupDetail }) => {
|
||||
|
||||
const [isSpin, setIsSpin] = useState(false);
|
||||
const [check_box, setCheckBox] = useState(false);
|
||||
const [switch_box, setSwtichBox] = useState([]);
|
||||
const [onwers, setOnwers] = useState(false);
|
||||
const [switch_box_code, setSwtichBoxCode] = useState(false);
|
||||
const [switch_box_pull, setSwtichBoxPull] = useState(false);
|
||||
const [switch_box_issue, setSwtichBoxIssue] = useState(false);
|
||||
const [switch_box_release, setSwtichBoxRelease] = useState(false);
|
||||
const { getFieldDecorator, validateFields, setFieldsValue } = form;
|
||||
const { OIdentifier, groupId } = match.params;
|
||||
|
||||
useEffect(() => {
|
||||
if (GroupDetail) {
|
||||
setOnwers(GroupDetail.authorize === "owner");
|
||||
setCheckBox(GroupDetail.can_create_org_project)
|
||||
setSwtichBox(GroupDetail.units)
|
||||
setFieldsValue({
|
||||
...GroupDetail
|
||||
})
|
||||
}
|
||||
}, [GroupDetail])
|
||||
|
||||
useEffect(() => {
|
||||
if (switch_box && switch_box.length > 0) {
|
||||
setSwtichBoxCode(switch_checked("code"))
|
||||
setSwtichBoxPull(switch_checked("pulls"))
|
||||
setSwtichBoxIssue(switch_checked("issues"))
|
||||
setSwtichBoxRelease(switch_checked("releases"))
|
||||
}
|
||||
}, [switch_box])
|
||||
|
||||
const helper = useCallback(
|
||||
(label, name, rules, widget, isRequired, mbValue,className) => (
|
||||
<div className={className}>
|
||||
<span className={isRequired ? `required` : ``}>{label}</span>
|
||||
<Form.Item style={{ marginBottom: `${mbValue}px` || "20px" }}>
|
||||
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
|
||||
</Form.Item>
|
||||
</div>
|
||||
),
|
||||
[]
|
||||
);
|
||||
function saveGroupFrom() {
|
||||
setIsSpin(true)
|
||||
validateFields((error, values) => {
|
||||
if (!error) {
|
||||
values.unit_types = switch_box
|
||||
if (groupId) { // 表示编辑,否则为新建
|
||||
const url = `/organizations/${OIdentifier}/teams/${groupId}.json`;
|
||||
axios.put(url, {
|
||||
...values
|
||||
}).then(result => {
|
||||
if (result && result.data) {
|
||||
showNotification("基本设置更新成功!");
|
||||
history.push(`/organize/${OIdentifier}/group/${groupId}`);
|
||||
}
|
||||
}).catch(error => { })
|
||||
} else {
|
||||
const url = `/organizations/${OIdentifier}/teams.json`;
|
||||
axios.post(url, {
|
||||
...values
|
||||
}).then(result => {
|
||||
if (result && result.data) {
|
||||
showNotification("团队创建成功!");
|
||||
history.push(`/organize/${OIdentifier}/group/${result.data.id}`);
|
||||
}
|
||||
}).catch(error => { })
|
||||
}
|
||||
}
|
||||
});
|
||||
setIsSpin(false)
|
||||
}
|
||||
|
||||
function change_check_box_status() {
|
||||
setCheckBox(!check_box)
|
||||
}
|
||||
|
||||
function switch_checked(code) {
|
||||
return switch_box.indexOf(code) > -1
|
||||
}
|
||||
|
||||
function switch_unit_types(checked, code) {
|
||||
const switch_index = switch_box.indexOf(code)
|
||||
if (checked) {
|
||||
switch_box.push(code)
|
||||
} else {
|
||||
switch_box.splice(switch_index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
function switch_code_types(checked, event) {
|
||||
switch_unit_types(checked, "code")
|
||||
setSwtichBoxCode(checked)
|
||||
}
|
||||
|
||||
function switch_issue_types(checked, event) {
|
||||
switch_unit_types(checked, "issues")
|
||||
setSwtichBoxIssue(checked)
|
||||
}
|
||||
|
||||
function switch_pull_types(checked, event) {
|
||||
switch_unit_types(checked, "pulls")
|
||||
setSwtichBoxPull(checked)
|
||||
}
|
||||
|
||||
function switch_releas_types(checked, event) {
|
||||
switch_unit_types(checked, "releases")
|
||||
setSwtichBoxRelease(checked)
|
||||
}
|
||||
|
||||
function cancelEdit(){
|
||||
if(groupId){
|
||||
history.push(`/organize/${OIdentifier}/group/${groupId}`);
|
||||
}else{
|
||||
history.push(`/organize/${OIdentifier}`);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Spin spinning={isSpin}>
|
||||
<WhiteBack className="mb30">
|
||||
<Banner>{groupId ? "基本设置" : "新建团队"}</Banner>
|
||||
<Div>
|
||||
<Form>
|
||||
{helper(
|
||||
'团队名称:',
|
||||
"name",
|
||||
[{ required: true, message: "请输入团队名称" }],
|
||||
<Input placeholder="请输入团队名称" disabled={onwers}/>, true
|
||||
)}
|
||||
{helper(
|
||||
<span className="mb5">团队描述:<span className="color-grey-8">(描述团队的目的或作用)</span></span>,
|
||||
"description",
|
||||
[],
|
||||
<TextArea
|
||||
placeholder="请输入团队描述"
|
||||
/>
|
||||
)}
|
||||
|
||||
{helper(
|
||||
'项目权限:',
|
||||
"includes_all_project",
|
||||
[],
|
||||
<Radio.Group>
|
||||
<Radio value={false} style={addStyle}>指定项目<span className="color-grey-8 ml10">(团队成员将只能访问添加到团队的项目。 选择此项 <span className="color-grey-3">将不会</span> 自动删除已经添加的项目)</span></Radio>
|
||||
<Radio value={true} style={OptionStyle}>所有项目<span className="color-grey-8 ml10">(团队可以访问所有项目。选择此选项将 <span className="color-grey-3">添加所有现有的</span> 项目到指定团队)</span></Radio>
|
||||
</Radio.Group>, false, 0,onwers ? "hide":""
|
||||
)}
|
||||
{helper(
|
||||
'',
|
||||
"can_create_org_project",
|
||||
[],
|
||||
<Checkbox checked={check_box} onChange={change_check_box_status} style={OptionStyle}>新建项目<span className="color-grey-8 ml10">(成员可以在组织中新建项目。创建者将自动获得新建的项目的管理员权限)</span></Checkbox>, false, 20,onwers ? "hide":""
|
||||
)}
|
||||
{helper(
|
||||
'版本库权限:',
|
||||
"authorize",
|
||||
[],
|
||||
<Radio.Group>
|
||||
<Radio value="read" style={addStyle}>读取权限<span className="color-grey-8 ml10">(成员可以查看和克隆团队项目)</span></Radio>
|
||||
<Radio value="write" style={addStyle}>写入权限<span className="color-grey-8 ml10">(成员可以查看和推送提交到团队项目)</span></Radio>
|
||||
<Radio value="admin" style={OptionStyle}>管理员权限<span className="color-grey-8 ml10">(成员可以拉取和推送到团队项目同时可以添加协作者)</span></Radio>
|
||||
</Radio.Group>, false, 20,onwers ? "hide":""
|
||||
)}
|
||||
</Form>
|
||||
{/* <p className="required">允许访问项目单元:</p>
|
||||
<AlignCenter className="mb10">
|
||||
<Switch checked={switch_box_code} onClick={switch_code_types} />
|
||||
<span className="ml30 color-grey-3">代码库<span className="color-grey-8 ml15">(查看源码、文件、提交和分支)</span></span>
|
||||
</AlignCenter>
|
||||
<AlignCenter className="mb10">
|
||||
<Switch checked={switch_box_issue} onClick={switch_issue_types} />
|
||||
<span className="ml30 color-grey-3">任务<span className="color-grey-8 ml15">(组织 bug 报告、任务和里程碑)</span></span>
|
||||
</AlignCenter>
|
||||
<AlignCenter className="mb10">
|
||||
<Switch checked={switch_box_pull} onClick={switch_pull_types} />
|
||||
<span className="ml30 color-grey-3">合并请求<span className="color-grey-8 ml15">(启用合并请求和代码评审)</span></span>
|
||||
</AlignCenter>
|
||||
<AlignCenter className="mb20">
|
||||
<Switch checked={switch_box_release} onClick={switch_releas_types} />
|
||||
<span className="ml30 color-grey-3">版本发布<span className="color-grey-8 ml15">(跟踪项目版本和下载)</span></span>
|
||||
</AlignCenter>
|
||||
*/}
|
||||
<Button type={"primary"} onClick={saveGroupFrom}>{groupId ? "更新团队设置" : "新建团队"}</Button>
|
||||
<Cancel className="ml30" onClick={() => cancelEdit()}><span className="pl30 pr30">取消</span></Cancel>
|
||||
</Div>
|
||||
</WhiteBack>
|
||||
</Spin>
|
||||
)
|
||||
})
|
||||
)
|
|
@ -1,123 +1,11 @@
|
|||
import React , { forwardRef , useCallback } from 'react';
|
||||
import { Form , Input , Radio , Checkbox , Switch , Button } from 'antd';
|
||||
import { Banner , WhiteBack , AlignCenter , Cancel } from '../../Component/layout';
|
||||
import styled from 'styled-components';
|
||||
import React from 'react';
|
||||
import GroupFrom from './GroupForm'
|
||||
|
||||
const TextArea = Input.TextArea;
|
||||
const Div = styled.div`{
|
||||
padding:20px 30px;
|
||||
}`
|
||||
const OptionStyle = {
|
||||
lineHeight:"25px",
|
||||
height:'25px',
|
||||
display:'block'
|
||||
function GropNew(props){
|
||||
return(
|
||||
<div className="teamDetail">
|
||||
<GroupFrom {...props}></GroupFrom>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const addStyle = {
|
||||
...OptionStyle,
|
||||
marginBottom:"7px"
|
||||
}
|
||||
|
||||
export default Form.create()(
|
||||
forwardRef(({ form })=>{
|
||||
const { getFieldDecorator, validateFields, setFieldsValue } = form;
|
||||
|
||||
const helper = useCallback(
|
||||
(label, name, rules, widget, isRequired , mbValue ) => (
|
||||
<React.Fragment>
|
||||
<span className={isRequired?"required":""}>{label}</span>
|
||||
<Form.Item style={{marginBottom:`${mbValue}px` || "20px"}}>
|
||||
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
|
||||
</Form.Item>
|
||||
</React.Fragment>
|
||||
),
|
||||
[]
|
||||
);
|
||||
return(
|
||||
<WhiteBack className="mb30">
|
||||
<Banner>新建团队</Banner>
|
||||
<Div>
|
||||
<Form>
|
||||
{helper(
|
||||
'团队名称:',
|
||||
"name",
|
||||
[{ required: true, message: "请输入团队名称" }],
|
||||
<Input placeholder="请输入团队名称" />,true
|
||||
)}
|
||||
{helper(
|
||||
<span>团队描述:<span className="color-grey-8">(描述团队的目的或作用)</span></span>,
|
||||
"desc",
|
||||
[],
|
||||
<TextArea
|
||||
placeholder="请输入团队描述"
|
||||
/>
|
||||
)}
|
||||
{helper(
|
||||
'项目权限:',
|
||||
"operation",
|
||||
[],
|
||||
<Radio.Group>
|
||||
<Radio value="0" style={addStyle}>指定项目<span className="color-grey-8">(团队成员将只能访问添加到团队的项目。 选择此项 <span className="color-grey-3">将不会</span> 自动删除已经添加的项目)</span></Radio>
|
||||
<Radio value="1" style={OptionStyle}>所有项目<span className="color-grey-8">(团队可以访问所有项目。选择此选项将 <span className="color-grey-3">添加所有现有的</span> 项目到指定团队)</span></Radio>
|
||||
</Radio.Group>,false,0
|
||||
)}
|
||||
{helper(
|
||||
'',
|
||||
"operation",
|
||||
[],
|
||||
<Checkbox value="0" style={OptionStyle}>新建项目<span className="color-grey-8">(成员可以在组织中新建项目。创建者将自动获得新建的项目的管理员权限。)</span></Checkbox>
|
||||
)}
|
||||
{helper(
|
||||
'权限:',
|
||||
"halfoperation",
|
||||
[],
|
||||
<Radio.Group>
|
||||
<Radio value="0" style={addStyle}>读取权限<span className="color-grey-8">(成员可以查看和克隆团队项目)</span></Radio>
|
||||
<Radio value="1" style={addStyle}>写入权限<span className="color-grey-8">(成员可以查看和推送提交到团队项目)</span></Radio>
|
||||
<Radio value="2" style={OptionStyle}>管理员权限<span className="color-grey-8">(成员可以拉取和推送到团队项目同时可以添加协作者)</span></Radio>
|
||||
</Radio.Group>,false
|
||||
)}
|
||||
<p class="required">允许访问项目单元:</p>
|
||||
<AlignCenter>
|
||||
{helper(
|
||||
'',
|
||||
"allowCode",
|
||||
[],
|
||||
<Switch />,false,0
|
||||
)}
|
||||
<span className="ml30 color-grey-3">代码库<span className="color-grey-8 ml15">(查看源码、文件、提交和分支)</span></span>
|
||||
</AlignCenter>
|
||||
<AlignCenter>
|
||||
{helper(
|
||||
'',
|
||||
"allowtask",
|
||||
[],
|
||||
<Switch />,false,0
|
||||
)}
|
||||
<span className="ml30 color-grey-3">任务<span className="color-grey-8 ml15">(组织 bug 报告、任务和里程碑)</span></span>
|
||||
</AlignCenter>
|
||||
<AlignCenter>
|
||||
{helper(
|
||||
'',
|
||||
"allowPull",
|
||||
[],
|
||||
<Switch />,false,0
|
||||
)}
|
||||
<span className="ml30 color-grey-3">合并请求<span className="color-grey-8 ml15">(启用合并请求和代码评审)</span></span>
|
||||
</AlignCenter>
|
||||
<AlignCenter className="mb20">
|
||||
{helper(
|
||||
'',
|
||||
"allowVersion",
|
||||
[],
|
||||
<Switch />,false,0
|
||||
)}
|
||||
<span className="ml30 color-grey-3">版本发布<span className="color-grey-8 ml15">(跟踪项目版本和下载)</span></span>
|
||||
</AlignCenter>
|
||||
<Button type={"primary"}>新建团队</Button>
|
||||
<Cancel className="ml30">取消</Cancel>
|
||||
</Form>
|
||||
</Div>
|
||||
</WhiteBack>
|
||||
)
|
||||
})
|
||||
)
|
||||
export default GropNew;
|
|
@ -0,0 +1,176 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { WhiteBack , FlexAJ } from '../../../Component/layout';
|
||||
import { Table, Pagination , Popconfirm , Spin } from 'antd';
|
||||
import Title from '../../../Component/Title';
|
||||
import Search from '../../../Component/Search';
|
||||
import AddMember from '../../../Component/AddMember';
|
||||
import './setting.scss';
|
||||
|
||||
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%;
|
||||
}`
|
||||
const limit = 15;
|
||||
export default ((props) => {
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [data, setMembers] = useState(undefined);
|
||||
const [isSpin, setIsSpin] = useState(false);
|
||||
const [identify, setIdentify] = useState(undefined);
|
||||
const [search, setQuery] = useState(undefined);
|
||||
const { OIdentifier, groupId } = props.match.params;
|
||||
|
||||
useEffect(() => {
|
||||
getMember()
|
||||
}, [page, search, identify])
|
||||
|
||||
function getMember() {
|
||||
setIsSpin(true)
|
||||
const url = `/organizations/${OIdentifier}/teams/${groupId}/team_users.json`;
|
||||
axios.get(url, {
|
||||
params: {
|
||||
page,
|
||||
search,
|
||||
identify,
|
||||
limit
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
if (result && result.data) {
|
||||
setMembers(result.data.team_users)
|
||||
setTotal(result.data.total_count)
|
||||
}
|
||||
}).catch((error) => { });
|
||||
setIsSpin(false)
|
||||
};
|
||||
|
||||
// 移除成员
|
||||
function removeUser(username) {
|
||||
setIsSpin(true);
|
||||
const url = `/organizations/${OIdentifier}/teams/${groupId}/team_users/${username}.json`;
|
||||
if (username) {
|
||||
axios.delete(url)
|
||||
.then((result) => {
|
||||
if (result && result.data) {
|
||||
setPage(1);
|
||||
setQuery(undefined);
|
||||
setIdentify(undefined);
|
||||
getMember();
|
||||
}
|
||||
})
|
||||
.catch((error) => { });
|
||||
}
|
||||
setIsSpin(false)
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'Img',
|
||||
width: "7%",
|
||||
render: (value, item) => {
|
||||
return (
|
||||
<Img src={getImageUrl(`images/${item.user.image_url}`)}></Img>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'name',
|
||||
width: "13%",
|
||||
align: "center",
|
||||
render: (value, item) => {
|
||||
return (
|
||||
<Link to={`/users/${item.user.login}`}>{item.user.name}</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
width: "25%",
|
||||
render: (value, item) => {
|
||||
return (
|
||||
item.user.mail
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
width: "15%",
|
||||
render: (value, item) => {
|
||||
return <Popconfirm
|
||||
title="确认移除成员吗?"
|
||||
onConfirm={() => removeUser(item.user.login)}
|
||||
okText="确认"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a className="color-red">移除成员</a>
|
||||
</Popconfirm>
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
function getID(id){
|
||||
addUser(id);
|
||||
}
|
||||
|
||||
function addUser(l) {
|
||||
setIsSpin(true);
|
||||
const url = `/organizations/${OIdentifier}/teams/${groupId}/team_users.json`;
|
||||
if (l) {
|
||||
axios.post(url, {
|
||||
username: l
|
||||
}).then((result) => {
|
||||
if (result && result.data) {
|
||||
setPage(1)
|
||||
setQuery(undefined)
|
||||
setIdentify(undefined)
|
||||
getMember();
|
||||
}
|
||||
})
|
||||
.catch((error) => { });
|
||||
}
|
||||
setIsSpin(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Spin spinning={isSpin}>
|
||||
<WhiteBack style={{minHeight:"400px"}}>
|
||||
<Title>
|
||||
<span>团队成员管理</span>
|
||||
<AddMember getID={getID}/>
|
||||
</Title>
|
||||
<FlexAJ className="padding20-30">
|
||||
<div style={{ width: "580px" }}>
|
||||
<Search placeholder="输入用户名或邮箱、团队名搜索" value={search} onSearch={(value)=>setQuery(value)} />
|
||||
</div>
|
||||
</FlexAJ>
|
||||
<div className="pl30 pr30 pb30">
|
||||
<Table
|
||||
size="small"
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
pagination={false}
|
||||
className="teamMemberTable"
|
||||
></Table>
|
||||
{
|
||||
total > limit ?
|
||||
<div className="edu-txt-center mt30 mb20">
|
||||
<Pagination simple defaultCurrent={page} total={total} pageSize={limit} onChange={(page)=>setPage(page)}></Pagination>
|
||||
</div>
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
</WhiteBack>
|
||||
</Spin>
|
||||
|
||||
)
|
||||
})
|
|
@ -0,0 +1,200 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import { AutoComplete, Pagination, List, Popconfirm, Dropdown, Spin } from 'antd';
|
||||
import { WhiteBack, Blueline, FlexAJ, GroupProjectBackgroup, Greenline, Redline } from '../../../Component/layout';
|
||||
import Title from '../../../Component/Title';
|
||||
const { Option } = AutoComplete;
|
||||
const limit = 15;
|
||||
// 使用了faker数据,后续需要修改
|
||||
function GroupProjectSetting(props) {
|
||||
const [isSpin, setIsSpin] = useState(false);
|
||||
const [projects, setProjects] = useState(undefined);
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [repo_name, setRepoName] = useState(undefined);
|
||||
|
||||
const [ search , setSearch ] = useState("");
|
||||
const [ optionsSource , setOptionsSource ] = useState(undefined);
|
||||
|
||||
const { OIdentifier, groupId } = props.match.params;
|
||||
|
||||
useEffect(() => {
|
||||
get_project()
|
||||
}, [page])
|
||||
|
||||
function get_project() {
|
||||
setIsSpin(true);
|
||||
const url = `/organizations/${OIdentifier}/teams/${groupId}/team_projects.json`;
|
||||
axios.get(url, {
|
||||
params: {page,limit},
|
||||
})
|
||||
.then((result) => {
|
||||
if (result && result.data) {
|
||||
setProjects(result.data.team_projects)
|
||||
setTotal(result.data.total_count)
|
||||
}
|
||||
}).catch((error) => { });
|
||||
setIsSpin(false)
|
||||
}
|
||||
|
||||
// 切换分页
|
||||
function ChangePage(page) {
|
||||
setPage(page);
|
||||
}
|
||||
|
||||
function removeProject(identifier) {
|
||||
setIsSpin(true)
|
||||
const url = `/organizations/${OIdentifier}/teams/${groupId}/team_projects/${identifier}.json`;
|
||||
axios.delete(url)
|
||||
.then((result) => {
|
||||
if (result && result.data.status > -1) {
|
||||
setPage(1)
|
||||
get_project()
|
||||
}
|
||||
}).catch((error) => { });
|
||||
setIsSpin(false)
|
||||
}
|
||||
|
||||
function removeALLProject() {
|
||||
// 移除所有项目
|
||||
}
|
||||
|
||||
function addALLProject() {
|
||||
// 添加所有项目
|
||||
}
|
||||
|
||||
// 添加单个项目
|
||||
function addSingleProject(){
|
||||
if(repo_name){
|
||||
setIsSpin(true);
|
||||
const url = `/organizations/${OIdentifier}/teams/${groupId}/team_projects.json`;
|
||||
axios.post(url, {repo_name})
|
||||
.then((result) => {
|
||||
if (result && result.data.id) {
|
||||
setPage(1)
|
||||
get_project()
|
||||
}
|
||||
setIsSpin(false)
|
||||
}).catch((error) => {setIsSpin(false)});
|
||||
}else{
|
||||
props.showNotification("请选择要添加的项目!");
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索项目有关
|
||||
useEffect(()=>{
|
||||
const url = `/organizations/${OIdentifier}/projects/search.json`;
|
||||
axios.get(url,{
|
||||
params:{
|
||||
search
|
||||
}
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setOptions(result.data.projects);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
},[search])
|
||||
function setOptions(data){
|
||||
const s = data && data.map((item, key) => {
|
||||
return (
|
||||
<Option
|
||||
key={key}
|
||||
value={`${item.id}`}
|
||||
searchValue={`${item.name}`}
|
||||
>
|
||||
{item.name}
|
||||
</Option>
|
||||
);
|
||||
});
|
||||
setOptionsSource(s);
|
||||
}
|
||||
function changeInput(value){
|
||||
let s = value || "";
|
||||
setSearch(s);
|
||||
}
|
||||
function selectInput(value){
|
||||
setRepoName(value);
|
||||
setSearch(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<Spin spinning={isSpin}>
|
||||
<WhiteBack className="mb30">
|
||||
<Title>
|
||||
<FlexAJ style={{width:"100%"}}>
|
||||
<span>团队项目管理</span>
|
||||
<div>
|
||||
<AutoComplete
|
||||
style={{ width: 300 }}
|
||||
placeholder="搜索项目..."
|
||||
onChange={changeInput}
|
||||
onSelect={selectInput}
|
||||
allowClear
|
||||
>
|
||||
{optionsSource}
|
||||
</AutoComplete>
|
||||
<Blueline className="ml30" onClick={()=>addSingleProject()}>+ 添加项目</Blueline>
|
||||
</div>
|
||||
</FlexAJ>
|
||||
</Title>
|
||||
{/* <FlexAJ className="padding20-30">
|
||||
<GroupProjectBackgroup>
|
||||
<FlexAJ>
|
||||
<div>
|
||||
<Popconfirm
|
||||
title="确认添加所有成员的项目吗?"
|
||||
onConfirm={() => addALLProject()}
|
||||
okText="确认"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Greenline bold>添加所有</Greenline>
|
||||
</Popconfirm>
|
||||
|
||||
<Popconfirm
|
||||
title="确认移除所有项目吗?"
|
||||
onConfirm={() => removeALLProject()}
|
||||
okText="确认"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Redline bold className="ml30">移除所有</Redline>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
</FlexAJ>
|
||||
</GroupProjectBackgroup>
|
||||
</FlexAJ>*/}
|
||||
<div className="padding20-30"style={{paddingTop:"0px",minHeight:"400px"}}>
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={projects}
|
||||
renderItem={item => (
|
||||
<List.Item
|
||||
extra={
|
||||
<Popconfirm
|
||||
title="确认移除项目吗?"
|
||||
onConfirm={() => removeProject(item.project.identifier)}
|
||||
okText="确认"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a className="color-red">移除</a>
|
||||
</Popconfirm>
|
||||
}
|
||||
>
|
||||
<List.Item.Meta
|
||||
title={<a href={`/projects/${item.project.owner_name}/${item.project.name}`}>{item.project.owner_name}/{item.project.name}</a>}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
total > limit ?
|
||||
<div className="edu-txt-center mt30 mb20">
|
||||
<Pagination simple defaultCurrent={page} total={total} pageSize={limit} onChange={ChangePage}></Pagination>
|
||||
</div>
|
||||
: ""
|
||||
}
|
||||
</WhiteBack>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
export default GroupProjectSetting;
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
.ant-btn.ant-input-search-button{
|
||||
margin-top: -1px;
|
||||
margin-right: -1px;
|
||||
}
|
|
@ -1,67 +1,32 @@
|
|||
import React , { useState } from 'react';
|
||||
import { AutoComplete , Pagination } from 'antd';
|
||||
import { Banner , Blueline , FlexAJ , Greenback , Redback } from '../../Component/layout';
|
||||
import styled from 'styled-components';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {Spin } from 'antd';
|
||||
import axios from 'axios';
|
||||
import GroupFrom from './GroupForm'
|
||||
|
||||
const Option = AutoComplete.Option;
|
||||
const Div = styled.div`{
|
||||
padding:18px 30px;
|
||||
}`;
|
||||
const LIMIT = 15;
|
||||
const demoData=[{name:"Python相关",id:1},{name:"Python相关",id:2}];
|
||||
export default ()=>{
|
||||
const [ searchKey , setSearchKey ] = useState(undefined);
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ total , setTotal ] = useState(0);
|
||||
function SettingCommon(props){
|
||||
const OIdentifier = props.match.params.OIdentifier;
|
||||
const groupId = props.match.params.groupId;
|
||||
|
||||
function changeInputUser(){}
|
||||
function selectInputUser(){}
|
||||
const [ detail , setDetail ] = useState(undefined);
|
||||
const [isSpin, setIsSpin] = useState(true);
|
||||
|
||||
// 切换分页
|
||||
function ChangePage(page){
|
||||
setPage(page);
|
||||
useEffect(()=>{
|
||||
if(groupId){
|
||||
getDetail();
|
||||
}
|
||||
},[groupId]);
|
||||
|
||||
function getDetail() {
|
||||
const url = `/organizations/${OIdentifier}/teams/${groupId}.json`;
|
||||
axios.get(url).then((result) => {
|
||||
setDetail(result.data)
|
||||
}).catch((error) => { });
|
||||
setIsSpin(false)
|
||||
}
|
||||
const source = demoData && demoData.map((item,key)=>{
|
||||
return(
|
||||
<Option key={key} value={`${item.id}`}>{item.name}</Option>
|
||||
)
|
||||
})
|
||||
return(
|
||||
<div>
|
||||
<Banner>团队项目管理</Banner>
|
||||
<Div>
|
||||
<FlexAJ className="actionNav">
|
||||
<div>
|
||||
<AutoComplete
|
||||
dataSource={source}
|
||||
value={searchKey}
|
||||
style={{ width: 270 }}
|
||||
onChange={changeInputUser}
|
||||
onSelect={selectInputUser}
|
||||
placeholder="搜索项目…"
|
||||
/>
|
||||
<Blueline className="ml30">+ 添加项目</Blueline>
|
||||
</div>
|
||||
<span>
|
||||
<Greenback>添加所有</Greenback>
|
||||
<Redback className="ml20">移除所有</Redback>
|
||||
</span>
|
||||
</FlexAJ>
|
||||
<div className="GSlist">
|
||||
<div>
|
||||
<span>
|
||||
caishi/前端v0.1
|
||||
</span>
|
||||
<a style={{color:"#F73030"}}>移除</a>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
total > LIMIT ?
|
||||
<div className="edu-txt-center mt30 mb20">
|
||||
<Pagination simple defaultCurrent={page} total={total} pageSize={LIMIT} onChange={ChangePage}></Pagination>
|
||||
</div>:""
|
||||
}
|
||||
</Div>
|
||||
</div>
|
||||
<Spin spinning={isSpin}>
|
||||
<GroupFrom {...props} GroupDetail={detail}></GroupFrom>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default SettingCommon;
|
|
@ -3,14 +3,17 @@ import React from 'react';
|
|||
import { Route, Switch } from "react-router-dom";
|
||||
import Loadable from "react-loadable";
|
||||
import Loading from "../../Loading";
|
||||
|
||||
import { withRouter } from "react-router";
|
||||
import { SnackbarHOC } from "educoder";
|
||||
import { CNotificationHOC } from "../../modules/courses/common/CNotificationHOC";
|
||||
import { TPMIndexHOC } from "../../modules/tpm/TPMIndexHOC";
|
||||
|
||||
|
||||
import '../css/index.scss';
|
||||
import './Index.scss';
|
||||
|
||||
const GroupNew = Loadable({
|
||||
loader: () => import("./Group/GroupNew"),
|
||||
loading: Loading,
|
||||
});
|
||||
const New = Loadable({
|
||||
loader: () => import("./New"),
|
||||
loading: Loading,
|
||||
|
@ -19,47 +22,59 @@ const DetailIndex = Loadable({
|
|||
loader: () => import("./Sub/Detail"),
|
||||
loading: Loading,
|
||||
});
|
||||
const SubDetail = Loadable({
|
||||
const SubDetailIndex = Loadable({
|
||||
loader: () => import("./Sub/SubDetail"),
|
||||
loading: Loading,
|
||||
});
|
||||
export default CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(
|
||||
export default withRouter(CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(
|
||||
((props)=>{
|
||||
return (
|
||||
<div className="newMain">
|
||||
<Switch {...props}>
|
||||
<Switch>
|
||||
{/* 组织团队-设置 */}
|
||||
<Route
|
||||
path="/organize/:organizeId/group"
|
||||
render={(props) => {
|
||||
return <SubDetail {...props} />
|
||||
path="/organize/:OIdentifier/group/:groupId/setting"
|
||||
render={(p) => {
|
||||
return <SubDetailIndex {...props} {...p}/>
|
||||
}}
|
||||
></Route>
|
||||
{/* 组织团队-新建 */}
|
||||
<Route
|
||||
path="/organize/:organizeId/member/:memberId/setting"
|
||||
render={(props) => {
|
||||
return <DetailIndex {...props} />
|
||||
path="/organize/:OIdentifier/group/new"
|
||||
render={(p) => {
|
||||
return <GroupNew {...props} {...p}/>
|
||||
}}
|
||||
></Route>
|
||||
{/* 组织团队-子级(包含组织团队列表) */}
|
||||
<Route
|
||||
path="/organize/:organizeId/member"
|
||||
render={(props) => {
|
||||
return <SubDetail {...props} />
|
||||
path="/organize/:OIdentifier/group"
|
||||
render={(p) => {
|
||||
return <DetailIndex {...props} {...p}/>
|
||||
}}
|
||||
></Route>
|
||||
{/* 组织成员 */}
|
||||
<Route
|
||||
path="/organize/:OIdentifier/member"
|
||||
render={(p) => {
|
||||
return <DetailIndex {...props} {...p}/>
|
||||
}}
|
||||
></Route>
|
||||
{/* 新建组织 */}
|
||||
<Route
|
||||
path="/organize/new"
|
||||
render={(props) => {
|
||||
return <New {...props} />
|
||||
render={(p) => {
|
||||
return <New {...props} {...p}/>
|
||||
}}
|
||||
></Route>
|
||||
{/* 组织详情(包含组织设置) */}
|
||||
<Route
|
||||
path="/organize/:organizeId"
|
||||
render={(props) => {
|
||||
return <DetailIndex {...props} />
|
||||
}}
|
||||
path="/organize/:OIdentifier"
|
||||
render={(p) => (
|
||||
<DetailIndex {...props} {...p}/>
|
||||
)}
|
||||
></Route>
|
||||
</Switch>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)))
|
||||
))))
|
|
@ -47,6 +47,7 @@
|
|||
background-color: #fff;
|
||||
max-width: 860px;
|
||||
width: 72%;
|
||||
margin-bottom: 30px;
|
||||
.head{
|
||||
padding:16px 32px;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
@ -62,9 +63,14 @@
|
|||
line-height: 30px;
|
||||
padding:0px 10px;
|
||||
}
|
||||
.ant-btn.ant-input-search-button{
|
||||
margin-top: -1px;
|
||||
margin-right: -1px;
|
||||
}
|
||||
}
|
||||
.team{
|
||||
padding:0px 32px;
|
||||
min-height: 450px;
|
||||
.team_project{
|
||||
padding:22px 0px;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
@ -102,6 +108,7 @@
|
|||
max-width: 340px;
|
||||
padding-left: 20px;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.box{
|
||||
background:rgba(255,255,255,1);
|
||||
|
@ -199,7 +206,20 @@
|
|||
justify-content: left;
|
||||
}
|
||||
.g-body{
|
||||
padding:15px 25px;
|
||||
padding:15px 15px;
|
||||
min-height: 84px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.moreMember {
|
||||
margin:0px 10px;
|
||||
i{
|
||||
height: 44px;
|
||||
width: 44px;
|
||||
line-height: 44px;
|
||||
color: #ddd!important;
|
||||
font-size: 44px!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&>div:nth-child(2n){
|
||||
|
@ -339,3 +359,6 @@
|
|||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
.hide{
|
||||
display: hidden;
|
||||
}
|
|
@ -1,124 +1,113 @@
|
|||
import React from 'react';
|
||||
import { Button } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Search from '../Component/Search';
|
||||
import Sort from '../Component/Sort';
|
||||
import ListCount from '../Component/ListCount';
|
||||
import Box from './Box';
|
||||
import './Index.scss';
|
||||
import styled from 'styled-components';
|
||||
import Item from './ListItem';
|
||||
import Right from './RightBox';
|
||||
import NoData from '../Nodata';
|
||||
|
||||
import { Menu } from 'antd';
|
||||
import { Menu , Pagination , Dropdown , Spin } from 'antd';
|
||||
import axios from 'axios';
|
||||
|
||||
const Span = styled.span`{
|
||||
color:#888;
|
||||
font-size:12px;
|
||||
margin-right:10px;
|
||||
}`
|
||||
const Align = styled.div`{
|
||||
display:flex;
|
||||
aligin:center;
|
||||
}`
|
||||
const ListName = styled.div`{
|
||||
font-size:14px;
|
||||
color:#333;
|
||||
margin-bottom:8px;
|
||||
height:18px;
|
||||
line-height:18px;
|
||||
}`;
|
||||
const ColorListName = styled.div`{
|
||||
color:#5091FF;
|
||||
font-size:14px;
|
||||
margin-bottom:8px;
|
||||
height:18px;
|
||||
line-height:18px;
|
||||
}`
|
||||
const Img = styled.img`{
|
||||
border-radius:50%;
|
||||
width:45px;
|
||||
height:45px;
|
||||
margin-right:12px;
|
||||
}`
|
||||
export default (()=>{
|
||||
function onSearch(value){
|
||||
const limit = 15;
|
||||
function List(props){
|
||||
const [ list , setList ] = useState(undefined);
|
||||
const [ isSpin , setIsSpin ] = useState(false);
|
||||
const [ totalCount , setTotalCount ] = useState(undefined);
|
||||
const [ search , setSearch ] = useState(undefined);
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ sortBy , setSortBy ] = useState("updated_on");
|
||||
const [ sortDirection , setSortDirection ] = useState("asc");
|
||||
|
||||
const OIdentifier = props.match.params.OIdentifier;
|
||||
const organizeDetail = props.organizeDetail;
|
||||
|
||||
useEffect(()=>{
|
||||
if(OIdentifier){
|
||||
setIsSpin(true);
|
||||
getProject();
|
||||
}
|
||||
},[OIdentifier,sortBy,page,search])
|
||||
|
||||
function getProject(){
|
||||
const url = `/organizations/${OIdentifier}/projects.json`;
|
||||
axios.get(url,{
|
||||
params:{
|
||||
search,page,limit,
|
||||
sort_by:sortBy,
|
||||
sort_direction:sortDirection
|
||||
}
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setList(result.data.projects);
|
||||
setTotalCount(result.data.total_count);
|
||||
}
|
||||
setIsSpin(false);
|
||||
}).catch(error=>{setIsSpin(false);})
|
||||
}
|
||||
|
||||
function onSearch(value){
|
||||
setSearch(value);
|
||||
}
|
||||
|
||||
const menu=(
|
||||
<Menu>
|
||||
<Menu onClick={(e)=>setSortBy(e.key)}>
|
||||
<Menu.Item key="updated_on">更新时间排序</Menu.Item>
|
||||
<Menu.Item key="created_on">项目数排序</Menu.Item>
|
||||
<Menu.Item key="created_on">创建时间排序</Menu.Item>
|
||||
<Menu.Item key="praises_count">点赞数排序</Menu.Item>
|
||||
<Menu.Item key="forked_count">fork数排序</Menu.Item>
|
||||
</Menu>
|
||||
)
|
||||
const menu_new=(
|
||||
<Menu>
|
||||
<Menu.Item key="updated_on">新建托管项目</Menu.Item>
|
||||
<Menu.Item key="created_on">新建镜像项目</Menu.Item>
|
||||
<Menu.Item key="updated_on"><Link to={`/projects/deposit/new/${OIdentifier}`}>新建托管项目</Link></Menu.Item>
|
||||
<Menu.Item key="created_on"><Link to={`/projects/mirror/new/${OIdentifier}`}>新建镜像项目</Link></Menu.Item>
|
||||
</Menu>
|
||||
)
|
||||
const leftList = (
|
||||
<div className="team_project">
|
||||
<p className="t_p_title">
|
||||
<span className="flex1">
|
||||
<span className="name">react项目react项目react项目react项目</span>
|
||||
<i className="iconfont icon-banbenku font-20 color-green" />
|
||||
<i className="iconfont icon-jingxiang font-18 color-green" />
|
||||
<i className="iconfont icon-fork font-18 color-orange" />
|
||||
</span>
|
||||
<ListCount fork={1} parise={0}/>
|
||||
</p>
|
||||
<div className="desc">
|
||||
用于构建用户界面的 JavaScript 库
|
||||
</div>
|
||||
<div className="infos">
|
||||
<span className="font-12 color-grey-8">更新于1天前</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return(
|
||||
<div className="list">
|
||||
<div className="list-l">
|
||||
<div className="head">
|
||||
<div style={{width:"370px"}}>
|
||||
<Search placeholder="输入仓库名称进行搜索" onSearch={onSearch}/>
|
||||
</div>
|
||||
<p>
|
||||
<Sort menu={menu_new}>
|
||||
<a className="addBtn mr30">+ 新建项目</a>
|
||||
</Sort>
|
||||
<Sort menu={menu}>
|
||||
<a className="color-blue">排序<i className="iconfont icon-sanjiaoxing-down ml3 font-14"></i></a>
|
||||
</Sort>
|
||||
</p>
|
||||
</div>
|
||||
<div className="team">
|
||||
{leftList}
|
||||
</div>
|
||||
</div>
|
||||
<div className="list-r">
|
||||
<Box name="组织成员" count={8}>
|
||||
<div className="teammembers">
|
||||
<Img src="https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1990625098,3468619056&fm=111&gp=0.jpg" alt="" className="m-img"/>
|
||||
<div>
|
||||
<ListName>陈Professer</ListName>
|
||||
<Align><i className="iconfont icon-shijian color-green mr3 font-13"></i><Span>加入时间:2020-04-28</Span></Align>
|
||||
<div className="list-l">
|
||||
<div>
|
||||
<div className="head">
|
||||
<div style={{width:"370px"}}>
|
||||
<Search placeholder="输入仓库名称进行搜索" onSearch={onSearch}/>
|
||||
</div>
|
||||
<p>
|
||||
{ organizeDetail && organizeDetail.is_admin ?
|
||||
<Sort menu={menu_new}>
|
||||
<a className="addBtn mr30">+ 新建项目</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>
|
||||
</Box>
|
||||
<Box
|
||||
name="组织团队"
|
||||
count={3}
|
||||
bottom={<Button type={'primary'} to={``}>新建团队</Button>}
|
||||
>
|
||||
<div className="teammembers">
|
||||
<div>
|
||||
<ColorListName>陈Professer</ColorListName>
|
||||
<Align>
|
||||
<Span>2名成员</Span>
|
||||
<Span>1个仓库</Span>
|
||||
</Align>
|
||||
{
|
||||
totalCount > limit &&
|
||||
<div className="mb20 mt20" style={{textAlign:"center"}}>
|
||||
<Pagination simple current={page} total={totalCount} onChange={(page)=>setPage(page)}/>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<Right admin={organizeDetail && organizeDetail.is_admin} OIdentifier={OIdentifier} history={props.history}/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
export default List;
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import ListCount from '../Component/ListCount';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Tooltip } from 'antd';
|
||||
function ListItem({item,key,OIdentifier}) {
|
||||
return(
|
||||
<div className="team_project" key={key}>
|
||||
<p className="t_p_title">
|
||||
<span className="flex1">
|
||||
<Link to={`/projects/${OIdentifier}/${item.identifier}`} className="name">{item.identifier}</Link>
|
||||
{ item.forked_from_project_id && <i className="iconfont icon-fork font-18 color-orange ml8" /> }
|
||||
{
|
||||
item.type && item.type !== 0 ?
|
||||
item.type === 2 ?
|
||||
<Tooltip title="该项目是一个镜像" className="ml8">
|
||||
<i className="iconfont icon-banbenku font-18 color-green" />
|
||||
</Tooltip>:
|
||||
<span className="ml8">
|
||||
<i className="iconfont icon-jingxiang font-18 color-green" />
|
||||
</span>:""
|
||||
}
|
||||
</span>
|
||||
<ListCount fork={item.forked_count} parise={item.praises_count}/>
|
||||
</p>
|
||||
<div className="desc">
|
||||
{item.description}
|
||||
</div>
|
||||
<div className="infos">
|
||||
<span className="font-12 color-grey-8">更新于{item.time_ago}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default ListItem;
|
|
@ -1,12 +1,15 @@
|
|||
import React,{ forwardRef , useCallback } from 'react';
|
||||
import React,{ forwardRef , useCallback , useEffect, useState } from 'react';
|
||||
import './Index.scss';
|
||||
import { Form , Input , Cascader , Radio , Checkbox , Upload , Button } from "antd";
|
||||
import { locData } from "../Utils/locData";
|
||||
|
||||
import { Form , Input , Radio , Checkbox , Button, InputNumber } from "antd";
|
||||
import UploadImage from './Component/UploadImage';
|
||||
import axios from 'axios';
|
||||
|
||||
export default Form.create()(
|
||||
forwardRef(({form})=>{
|
||||
const { getFieldDecorator, validateFields, setFieldsValue } = form;
|
||||
forwardRef(({ form , showNotification , history })=>{
|
||||
const [ image , setImage ] = useState(undefined);
|
||||
const [ imageFlag , setImageFlag] = useState(false);
|
||||
const { getFieldDecorator, validateFields , setFieldsValue } = form;
|
||||
|
||||
const radioStyle = {
|
||||
display: 'block',
|
||||
height: '30px',
|
||||
|
@ -15,7 +18,7 @@ export default Form.create()(
|
|||
const helper = useCallback(
|
||||
(label, name, rules, widget, isRequired = true) => (
|
||||
<React.Fragment>
|
||||
<span className={isRequired?"lables must":"lables"}>{label}</span>
|
||||
<span className={isRequired ? "lables must ": "lables"}>{label}</span>
|
||||
<Form.Item>
|
||||
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
|
||||
</Form.Item>
|
||||
|
@ -23,8 +26,41 @@ export default Form.create()(
|
|||
),
|
||||
[]
|
||||
);
|
||||
|
||||
// 获取上传的图片
|
||||
function getImage(image){
|
||||
setImage(image);
|
||||
setImageFlag(false);
|
||||
}
|
||||
|
||||
// 创建组织
|
||||
function createOrganize(){
|
||||
validateFields((error,values)=>{
|
||||
if(!error){
|
||||
if(!image){
|
||||
setImageFlag(true);
|
||||
return;
|
||||
}
|
||||
const url = `/organizations.json`;
|
||||
axios.post(url,{
|
||||
...values,image
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
showNotification("组织创建成功!");
|
||||
history.push(`/organize/${result.data.name}`);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
setFieldsValue({
|
||||
visibility:"common"
|
||||
})
|
||||
},[])
|
||||
|
||||
return(
|
||||
<div className="newMain">
|
||||
<div className="main">
|
||||
<div className="teamBox">
|
||||
<p className="teamBox-title">新建组织</p>
|
||||
|
@ -39,7 +75,7 @@ export default Form.create()(
|
|||
)}
|
||||
{helper(
|
||||
'组织描述',
|
||||
"desc",
|
||||
"description",
|
||||
[{ required: true, message: "请输入组织描述" }],
|
||||
<Input.TextArea
|
||||
autoSize={{ minRows: 3, maxRows: 5 }}
|
||||
|
@ -48,40 +84,38 @@ export default Form.create()(
|
|||
)}
|
||||
{helper(
|
||||
'所在地区',
|
||||
"area",
|
||||
"location",
|
||||
[],
|
||||
<Cascader placeholder="请选择城市" options={locData}/>,false
|
||||
<Input placeholder="请输入地址"/>,false
|
||||
)}
|
||||
{helper(
|
||||
'可见性',
|
||||
"exposure",
|
||||
"visibility",
|
||||
[{ required: true, message: "请选择可见性" }],
|
||||
<Radio.Group name="exposure">
|
||||
<Radio style={radioStyle} value="1" key={1}>公开</Radio>
|
||||
<Radio style={radioStyle} value="2" key={2}>受限<span className="color-grey-8">(仅对登录用户可见)</span></Radio>
|
||||
<Radio style={radioStyle} value="3" key={3}>公开<span className="color-grey-8">(仅对组织成员可见)</span></Radio>
|
||||
<Radio style={radioStyle} value="common" key={1}>公开</Radio>
|
||||
<Radio style={radioStyle} value="limited" key={2}>受限<span className="color-grey-8">(仅对登录用户可见)</span></Radio>
|
||||
<Radio style={radioStyle} value="privacy" key={3}>私有<span className="color-grey-8">(仅对组织成员可见)</span></Radio>
|
||||
</Radio.Group>
|
||||
)}
|
||||
{helper(
|
||||
'选择头像',
|
||||
"photo",
|
||||
[],
|
||||
<Upload></Upload>,false
|
||||
)}
|
||||
|
||||
<p className="font-16 lables must">选择头像</p>
|
||||
<UploadImage getImage={getImage}/>
|
||||
{imageFlag && <p className="color-red" style={{marginTop:"-10px"}}>请上传头像</p> }
|
||||
|
||||
{helper(
|
||||
'权限',
|
||||
"operation",
|
||||
"repo_admin_change_team_access",
|
||||
[],
|
||||
<Checkbox value="1" key={1}>项目管理员可以添加或移除团队的访问权限</Checkbox>,false
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
<p className="mt20">
|
||||
<Button type="primary" className="mr30">创建组织</Button>
|
||||
<Button type="primary" className="mr30" onClick={createOrganize}>创建组织</Button>
|
||||
<Button className="grey">取消</Button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)
|
|
@ -0,0 +1,115 @@
|
|||
import React , { useEffect , useState } from 'react';
|
||||
import { Button } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import Box from './Box';
|
||||
import axios from 'axios';
|
||||
import { getImageUrl } from 'educoder';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Nodata from '../Nodata';
|
||||
|
||||
const Span = styled.span`{
|
||||
color:#888;
|
||||
font-size:12px;
|
||||
margin-right:10px;
|
||||
}`
|
||||
const Align = styled.div`{
|
||||
display:flex;
|
||||
aligin:center;
|
||||
}`
|
||||
const ListName = styled.div`{
|
||||
font-size:14px;
|
||||
color:#333;
|
||||
margin-bottom:8px;
|
||||
height:18px;
|
||||
line-height:18px;
|
||||
}`;
|
||||
const ColorListName = styled.div`{
|
||||
color:#5091FF;
|
||||
font-size:14px;
|
||||
margin-bottom:8px;
|
||||
height:18px;
|
||||
line-height:18px;
|
||||
}`
|
||||
const Img = styled.img`{
|
||||
border-radius:50%;
|
||||
width:45px;
|
||||
height:45px;
|
||||
margin-right:12px;
|
||||
}`
|
||||
function RightBox({ OIdentifier , history , admin }) {
|
||||
const [ memberData, setMemberData ] = useState(undefined);
|
||||
const [ groupData, setGroupData ] = useState(undefined);
|
||||
|
||||
useEffect(()=>{
|
||||
if(OIdentifier){
|
||||
getMember(OIdentifier);
|
||||
getGroup(OIdentifier);
|
||||
}
|
||||
},[OIdentifier])
|
||||
|
||||
function getMember(iden){
|
||||
const url = `/organizations/${iden}/organization_users.json`;
|
||||
axios.get(url,{params:{limit:5}}).then(result=>{
|
||||
if(result && result.data){
|
||||
setMemberData(result.data);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
function getGroup(iden){
|
||||
const url = `/organizations/${iden}/teams.json`;
|
||||
axios.get(url,{params:{limit:5}}).then(result=>{
|
||||
if(result && result.data){
|
||||
setGroupData(result.data);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
return(
|
||||
<div className="list-r">
|
||||
{
|
||||
memberData && memberData.organization_users && memberData.organization_users.length>0 ?
|
||||
<Box name="组织成员" count={memberData && memberData.total_count} url={`/organize/${OIdentifier}/member`}>
|
||||
{
|
||||
memberData.organization_users.map((item,key)=>{
|
||||
return(
|
||||
<div className="teammembers" key={key}>
|
||||
<Link to={`/users/${item.user && item.user.login}`}><Img src={getImageUrl(`images/${item.user && item.user.image_url}`)} alt="" className="m-img"/></Link>
|
||||
<div>
|
||||
<Link to={`/users/${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>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Box>
|
||||
:""
|
||||
}
|
||||
<Box
|
||||
name="组织团队"
|
||||
count={groupData && groupData.total_count}
|
||||
bottom={admin && <Button type={'primary'} onClick={()=>history.push(`/organize/${OIdentifier}/group/new`)}>新建团队</Button>}
|
||||
url={`/organize/${OIdentifier}/group`}
|
||||
>
|
||||
{
|
||||
groupData && groupData.teams && groupData.teams.length>0?
|
||||
<React.Fragment>
|
||||
{groupData.teams.map((item,key)=>{
|
||||
return(
|
||||
<div className="teammembers" key={key}>
|
||||
<div>
|
||||
<Link to={`/organize/${OIdentifier}/group/${item.id}`}><ColorListName>{item.name}</ColorListName></Link>
|
||||
<Align>
|
||||
<Span>{item.num_users}名成员</Span>
|
||||
<Span>{item.num_projects}个仓库</Span>
|
||||
</Align>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</React.Fragment>:<Nodata _html="暂无团队" small/>
|
||||
}
|
||||
</Box>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default RightBox;
|
|
@ -1,9 +1,13 @@
|
|||
import React, { forwardRef , useCallback } from 'react';
|
||||
import { Form , Input , Cascader , Radio ,Checkbox , Divider , Button } from 'antd';
|
||||
import React, { forwardRef , useCallback , useEffect, useState } from 'react';
|
||||
import { Form , Input , Radio ,Checkbox , Divider , Button } from 'antd';
|
||||
import { WhiteBack , FlexAJ } from '../../Component/layout';
|
||||
import Title from '../../Component/Title';
|
||||
import styled from 'styled-components';
|
||||
import { locData } from "../../Utils/locData";
|
||||
import UploadImage from '../Component/UploadImage';
|
||||
import axios from 'axios';
|
||||
import Modals from '../Component/Modals';
|
||||
|
||||
import { getImageUrl } from 'educoder';
|
||||
const TextArea = Input.TextArea;
|
||||
|
||||
const Div = styled.div`{
|
||||
|
@ -15,19 +19,82 @@ const radioStyle = {
|
|||
lineHeight: '30px',
|
||||
};
|
||||
export default Form.create()(
|
||||
forwardRef(({ form })=>{
|
||||
const { getFieldDecorator } = form;
|
||||
forwardRef(({ form , organizeDetail , showNotification , history , current_user , updateFunc })=>{
|
||||
const [ image , setImage ] = useState(undefined);
|
||||
const [ imageFlag , setImageFlag ] = useState(false);
|
||||
const [ password , setPassword ] = useState(undefined);
|
||||
const [ passwordFlag , setPasswordFlag ] = useState(false);
|
||||
const [ visible , setVisible ] = useState(false);
|
||||
const { getFieldDecorator , validateFields , setFieldsValue } = form;
|
||||
|
||||
useEffect(()=>{
|
||||
if(organizeDetail){
|
||||
setFieldsValue({
|
||||
...organizeDetail
|
||||
})
|
||||
setImage(organizeDetail.avatar_url);
|
||||
}
|
||||
},[organizeDetail])
|
||||
|
||||
const helper = useCallback(
|
||||
(label, name, rules, widget , isRequired ) => (
|
||||
(label, name, rules, widget , isRequired , flag ) => (
|
||||
<div>
|
||||
<span className={isRequired?"required":""}>{label}</span>
|
||||
<Form.Item>
|
||||
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
|
||||
{getFieldDecorator(name, { rules, validateFirst: true , valuePropName:flag ? "checked":"value" })(widget)}
|
||||
</Form.Item>
|
||||
</div>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
// 更新
|
||||
function updateDetail(){
|
||||
validateFields((error,values)=>{
|
||||
if(!error){
|
||||
const url = `/organizations/${organizeDetail.id}.json`;
|
||||
axios.patch(url,{
|
||||
...values,image:imageFlag ? image : undefined
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
showNotification("组织信息更新成功!");
|
||||
if(values.name !== organizeDetail.name){
|
||||
console.log("false111");
|
||||
history.push(`/organize/${values.name}/setting`);
|
||||
}
|
||||
updateFunc && updateFunc(values.name,values.description);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
})
|
||||
}
|
||||
function getImage(image){
|
||||
setImageFlag(true);
|
||||
setImage(image);
|
||||
}
|
||||
|
||||
// 删除组织
|
||||
function deleteOrganize(){
|
||||
if(!password){
|
||||
setPasswordFlag(true);
|
||||
}else{
|
||||
setPasswordFlag(false);
|
||||
setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
function onOk(password){
|
||||
const url = `/organizations/${organizeDetail.id}.json`;
|
||||
axios.delete(url,{
|
||||
params:{ password }
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
// 删除后跳转到个人中心的组织页面
|
||||
history.push(`/users/${current_user && current_user.login}/organizes`);
|
||||
}
|
||||
})
|
||||
setVisible(false);
|
||||
}
|
||||
return(
|
||||
<div>
|
||||
<WhiteBack>
|
||||
|
@ -42,46 +109,48 @@ export default Form.create()(
|
|||
)}
|
||||
{helper(
|
||||
"组织描述:",
|
||||
"desc",
|
||||
"description",
|
||||
[],
|
||||
<TextArea placeholder="请输入组织名称" />
|
||||
)}
|
||||
{helper(
|
||||
"官方网站:",
|
||||
"web",
|
||||
"website",
|
||||
[],
|
||||
<Input placeholder="请输入官方网站" />
|
||||
)}
|
||||
{helper(
|
||||
'所在地区:',
|
||||
"area",
|
||||
"location",
|
||||
[],
|
||||
<Cascader placeholder="请选择城市" options={locData}/>
|
||||
<Input placeholder="请输入城市"/>
|
||||
)}
|
||||
{helper(
|
||||
'可见性:',
|
||||
"opacity",
|
||||
"visibility",
|
||||
[],
|
||||
<Radio.Group>
|
||||
<Radio value="0" style={radioStyle}>公开</Radio>
|
||||
<Radio value="0" style={radioStyle}>受限<span>(仅对登录用户可见)</span></Radio>
|
||||
<Radio value="0" style={radioStyle}>私有<span>(仅对组织成员可见)</span></Radio>
|
||||
<Radio value="common" style={radioStyle}>公开</Radio>
|
||||
<Radio value="limited" style={radioStyle}>受限<span>(仅对登录用户可见)</span></Radio>
|
||||
<Radio value="privacy" style={radioStyle}>私有<span>(仅对组织成员可见)</span></Radio>
|
||||
</Radio.Group>
|
||||
)}
|
||||
{helper(
|
||||
'权限:',
|
||||
"operation",
|
||||
"repo_admin_change_team_access",
|
||||
[],
|
||||
<Checkbox value="0" style={radioStyle}>仓库管理员可以添加或移除团队的访问权限</Checkbox>
|
||||
<Checkbox style={radioStyle}>仓库管理员可以添加或移除团队的访问权限</Checkbox>,false,true
|
||||
)}
|
||||
<Divider/>
|
||||
{helper(
|
||||
'最大仓库数:',
|
||||
"number",
|
||||
"max_repo_creation",
|
||||
[],
|
||||
<Input value="-1" style={{width:"350px"}}/>
|
||||
)}
|
||||
<Button type={"primary"}>更新仓库设置</Button>
|
||||
<p>选择头像:</p>
|
||||
<UploadImage url={getImageUrl(`images/${image}`)} getImage={getImage}/>
|
||||
<Button type={"primary"} onClick={updateDetail}>更新仓库设置</Button>
|
||||
</Form>
|
||||
</Div>
|
||||
</WhiteBack>
|
||||
|
@ -93,9 +162,13 @@ export default Form.create()(
|
|||
<FlexAJ>
|
||||
<div>
|
||||
<span className="required">密码:</span>
|
||||
<Input type="password" style={{width:"350px"}} />
|
||||
<Input type="password" placeholder="请输入当前用户的登录密码" style={{width:"350px"}} value={password} onChange={(e)=>setPassword(e.target.value)}/>
|
||||
{ passwordFlag && <span className="color-red ml10">请输入密码</span>}
|
||||
</div>
|
||||
<a className="warningDelete">删除组织</a>
|
||||
<a className="warningDelete" onClick={deleteOrganize}>删除组织</a>
|
||||
<Modals visible={visible} okText={"确定"} cancelText={"取消"} onCancel={()=>setVisible(false)} onOk={()=>onOk(password)}>
|
||||
<p className="font-16 edu-txt-center">确定要删除当前组织吗?</p>
|
||||
</Modals>
|
||||
</FlexAJ>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,71 +1,14 @@
|
|||
import React from 'react';
|
||||
import { WhiteBack , Banner , Blueline } from '../../Component/layout';
|
||||
import styled from 'styled-components';
|
||||
import { WhiteBack , Banner } from '../../Component/layout';
|
||||
import GroupItems from '../TeamGroupItems';
|
||||
|
||||
const SpanName = styled.span`{
|
||||
font-size:16px;
|
||||
color:#333;
|
||||
}`
|
||||
const SpanFoot = styled.span`{
|
||||
margin-right:5px;
|
||||
color:#333
|
||||
}`
|
||||
const ALink = styled.a`{
|
||||
border:1px solid #F73030;
|
||||
color:#F73030!important;
|
||||
height:32px;
|
||||
line-height:30px;
|
||||
display:block;
|
||||
padding:0px 15px;
|
||||
border-radius:2px;
|
||||
}`
|
||||
const ImgContent = styled.img`{
|
||||
height:44px;
|
||||
width:44px;
|
||||
border-radius:50%;
|
||||
margin:5px 20px 5px 0px;
|
||||
}`
|
||||
|
||||
export default ()=>{
|
||||
const limit = 8;
|
||||
function TeamSettingGroup({organizeDetail,history}){
|
||||
return(
|
||||
<WhiteBack>
|
||||
<Banner>组织团队管理</Banner>
|
||||
<div className="groupBox">
|
||||
<div>
|
||||
<p className="g-head">
|
||||
<SpanName>oweners</SpanName>
|
||||
<span className="df">
|
||||
<ALink>离开团队</ALink>
|
||||
<Blueline className="ml15">团队设置</Blueline>
|
||||
</span>
|
||||
</p>
|
||||
<div className="g-body">
|
||||
<ImgContent src="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3331079987,1190181307&fm=111&gp=0.jpg"/>
|
||||
<ImgContent src="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3331079987,1190181307&fm=111&gp=0.jpg"/>
|
||||
</div>
|
||||
<p className="g-foot">
|
||||
<SpanFoot>2 名成员</SpanFoot>
|
||||
<SpanFoot>1 个项目</SpanFoot>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="g-head">
|
||||
<SpanName>oweners</SpanName>
|
||||
<span className="df">
|
||||
<ALink>离开团队</ALink>
|
||||
<Blueline className="ml15">团队设置</Blueline>
|
||||
</span>
|
||||
</p>
|
||||
<div className="g-body">
|
||||
<ImgContent src="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3331079987,1190181307&fm=111&gp=0.jpg"/>
|
||||
<ImgContent src="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3331079987,1190181307&fm=111&gp=0.jpg"/>
|
||||
</div>
|
||||
<p className="g-foot">
|
||||
<SpanFoot>2 名成员</SpanFoot>
|
||||
<SpanFoot>1 个项目</SpanFoot>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</WhiteBack>
|
||||
<GroupItems limit={limit} organizeDetail={organizeDetail} count={4} history={history}/>
|
||||
</WhiteBack>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default TeamSettingGroup;
|
|
@ -26,25 +26,25 @@ const Hooks = Loadable({
|
|||
});
|
||||
export default (( props )=>{
|
||||
const pathname = props.location.pathname;
|
||||
const organizeId = props.match.params.organizeId;
|
||||
const OIdentifier = props.match.params.OIdentifier;
|
||||
|
||||
function returnActive (pathname){
|
||||
let a = 0;
|
||||
if(pathname === `/organize/${organizeId}/setting/member`){
|
||||
if(pathname === `/organize/${OIdentifier}/setting/member`){
|
||||
a = 1;
|
||||
}else if(pathname === `/organize/${organizeId}/setting/group`){
|
||||
}else if(pathname === `/organize/${OIdentifier}/setting/group`){
|
||||
a = 2;
|
||||
}else if(pathname === `/organize/${organizeId}/setting/hooks`){
|
||||
}else if(pathname === `/organize/${OIdentifier}/setting/hooks`){
|
||||
a = 3;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
const active = returnActive(pathname);
|
||||
const array = {list:[
|
||||
{name:'基本设置',icon:"icon-base",href:`/organize/${organizeId}/setting`},
|
||||
{name:'组织成员管理',icon:"icon-zuzhichengyuan",href:`/organize/${organizeId}/setting/member`},
|
||||
{name:'组织团队管理',icon:"icon-zuzhixiangmu",href:`/organize/${organizeId}/setting/group`},
|
||||
{name:'管理web钩子',icon:"icon-zhongqingdianxinicon10",href:`/organize/${organizeId}/setting/hooks`}
|
||||
{name:'基本设置',icon:"icon-base",href:`/organize/${OIdentifier}/setting`},
|
||||
{name:'组织成员管理',icon:"icon-zuzhichengyuan",href:`/organize/${OIdentifier}/setting/member`},
|
||||
{name:'组织团队管理',icon:"icon-zuzhixiangmu",href:`/organize/${OIdentifier}/setting/group`},
|
||||
// {name:'管理web钩子',icon:"icon-zhongqingdianxinicon10",href:`/organize/${OIdentifier}/setting/hooks`}
|
||||
],
|
||||
active
|
||||
}
|
||||
|
@ -57,25 +57,25 @@ export default (( props )=>{
|
|||
<Gap>
|
||||
<Switch>
|
||||
<Route
|
||||
path="/organize/:organizeId/setting/hooks"
|
||||
path="/organize/:OIdentifier/setting/hooks"
|
||||
render={() => (
|
||||
<Hooks {...props} />
|
||||
)}
|
||||
></Route>
|
||||
<Route
|
||||
path="/organize/:organizeId/setting/group"
|
||||
path="/organize/:OIdentifier/setting/group"
|
||||
render={() => (
|
||||
<Group {...props} />
|
||||
)}
|
||||
></Route>
|
||||
<Route
|
||||
path="/organize/:organizeId/setting/member"
|
||||
path="/organize/:OIdentifier/setting/member"
|
||||
render={() => (
|
||||
<Member {...props} />
|
||||
)}
|
||||
></Route>
|
||||
<Route
|
||||
path="/organize/:organizeId/setting"
|
||||
path="/organize/:OIdentifier/setting"
|
||||
render={() => (
|
||||
<Common {...props} />
|
||||
)}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useState } from 'react';
|
||||
import { WhiteBack , Blueline , AlignCenter , FlexAJ } from '../../Component/layout';
|
||||
import { Menu , Table , Pagination , Icon , Tooltip } from 'antd';
|
||||
import Sort from '../../Component/Sort';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { WhiteBack , FlexAJ } from '../../Component/layout';
|
||||
import { Table , Pagination , Popconfirm } from 'antd';
|
||||
import Title from '../../Component/Title';
|
||||
import Search from '../../Component/Search';
|
||||
import SearchUser from '../../Component/SearchUser';
|
||||
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;
|
||||
|
@ -24,85 +24,77 @@ const demoData = [
|
|||
operation:"移除成员",
|
||||
}
|
||||
]
|
||||
export default (()=>{
|
||||
export default (({organizeDetail})=>{
|
||||
const [ choiceId , serChoiceId ] = useState(undefined);
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ limit , setLimit ] = useState(15);
|
||||
const [ total , setTotal ] = useState(0);
|
||||
const [ search , setSearch ] = useState(undefined);
|
||||
const [ data , setData ] = useState(demoData);
|
||||
|
||||
// 获取搜索用户框里面选择的用户ID,方便添加组织成员
|
||||
function getUser(id){
|
||||
console.log(id);
|
||||
serChoiceId(id);
|
||||
}
|
||||
// 搜索
|
||||
function onSearch(value){
|
||||
console.log(value);
|
||||
useEffect(()=>{
|
||||
if(organizeDetail && organizeDetail.id){
|
||||
getData(search);
|
||||
}
|
||||
},[organizeDetail,search])
|
||||
|
||||
function getData(search){
|
||||
const url = `/organizations/${organizeDetail.id}/organization_users.json`;
|
||||
axios.get(url,{
|
||||
params:{
|
||||
page,limit, search
|
||||
}
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setData(result.data.organization_users);
|
||||
setTotal(result.data.total_count);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
// 切换分页
|
||||
function ChangePage(page){
|
||||
setPage(page);
|
||||
}
|
||||
|
||||
const menu=(
|
||||
<Menu>
|
||||
<Menu.Item key="all">全部</Menu.Item>
|
||||
<Menu.Item key="Manager">管理员</Menu.Item>
|
||||
<Menu.Item key="Developer">开发者</Menu.Item>
|
||||
<Menu.Item key="Reporter">报告者</Menu.Item>
|
||||
</Menu>
|
||||
)
|
||||
const roleTitle = (
|
||||
<div><span className="mr3">角色</span>
|
||||
<Tooltip placement='bottom' title=
|
||||
{
|
||||
<div>
|
||||
<div className="mb3">管理员:拥有仓库设置功能、代码库读、写操作</div>
|
||||
<div className="mb3">开发人员:只拥有代码库读、写操作</div>
|
||||
<div className="mb3">报告者:只拥有代码库读操作</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon type="question-circle"></Icon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
const columns = [
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'Img',
|
||||
width:"7%",
|
||||
render:(value,item)=>{
|
||||
dataIndex: 'user',
|
||||
width:"5%",
|
||||
render:(value)=>{
|
||||
return(
|
||||
<Img src={getImageUrl(item.img)}></Img>
|
||||
value && <Link to={`/users/${value && value.login}`}><Img src={getImageUrl('images/'+value.image_url)}></Img> </Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'name',
|
||||
width:"13%",
|
||||
align:"center"
|
||||
dataIndex: 'user',
|
||||
width:"15%",
|
||||
render:(value,item)=>{
|
||||
return <Link to={`/users/${value && value.login}`}>{value && value.name}</Link>
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
dataIndex: 'user',
|
||||
width:"25%",
|
||||
align:"left",
|
||||
render:(value)=>{
|
||||
return value && value.mail
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '所属团队',
|
||||
dataIndex: 'team',
|
||||
dataIndex: 'team_names',
|
||||
width:"20%",
|
||||
},
|
||||
{
|
||||
title: roleTitle,
|
||||
dataIndex: 'role',
|
||||
width:"20%",
|
||||
render:(value,item)=>{
|
||||
return(
|
||||
item.role
|
||||
)
|
||||
render:(value)=>{
|
||||
let array = value && value.length > 0 && value.map((i,k)=>{
|
||||
return (i+",")
|
||||
})
|
||||
console.log(array && array[0]);
|
||||
return array && array[0].substring(0,array[0].length-1);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -110,28 +102,41 @@ export default (()=>{
|
|||
dataIndex: 'operation',
|
||||
width:"15%",
|
||||
render:(value,item)=>{
|
||||
return <a className="color-grey-8">移除成员</a>
|
||||
let c_isAdmin = organizeDetail && organizeDetail.is_admin;
|
||||
let last = (data && data.length === 1) && (item.team_names && item.team_names.length === 1 && item.team_names[0]==="Owners");
|
||||
return (
|
||||
c_isAdmin && !last && <Popconfirm title="是否将此成员移出组织?" okText={"是"} cancelText={"否"} onConfirm={()=>deleteMember(item.user && item.user.login)}><a className="color-red">移除成员</a></Popconfirm>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
function deleteMember(login){
|
||||
const url = `/organizations/${organizeDetail && organizeDetail.id}/organization_users/${login}.json`;
|
||||
axios.delete(url).then(result=>{
|
||||
if(result && result.data){
|
||||
getData();
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
return(
|
||||
<WhiteBack>
|
||||
<Title>
|
||||
<span>组织成员管理</span>
|
||||
<AlignCenter>
|
||||
{/* <AlignCenter>
|
||||
<SearchUser getUser={getUser}/>
|
||||
<Blueline className="ml30">+ 添加用户</Blueline>
|
||||
</AlignCenter>
|
||||
</AlignCenter> */}
|
||||
</Title>
|
||||
<FlexAJ className="padding20-30">
|
||||
<div style={{width:"580px"}}>
|
||||
<Search placeholder="输入用户名或邮箱、团队名搜索" onSearch={onSearch}/>
|
||||
<Search placeholder="输入用户名或邮箱、团队名搜索" onSearch={(value)=>{setSearch(value)}}/>
|
||||
</div>
|
||||
<Sort menu={menu}>
|
||||
{/* <Sort menu={menu}>
|
||||
<a className="color-blue">角色筛选<i className="iconfont icon-sanjiaoxing-down ml3 font-14"></i></a>
|
||||
</Sort>
|
||||
</Sort> */}
|
||||
</FlexAJ>
|
||||
<div className="pl30 pr30 pb30">
|
||||
<div className="pl30 pr30 pb30" style={{minHeight:"400px"}}>
|
||||
<Table
|
||||
size="small"
|
||||
columns={columns}
|
||||
|
@ -142,7 +147,7 @@ export default (()=>{
|
|||
{
|
||||
total > limit ?
|
||||
<div className="edu-txt-center mt30 mb20">
|
||||
<Pagination simple defaultCurrent={page} total={total} pageSize={limit} onChange={ChangePage}></Pagination>
|
||||
<Pagination simple current={page} total={total} pageSize={limit} onChange={ChangePage}></Pagination>
|
||||
</div>
|
||||
:""
|
||||
}
|
||||
|
|
|
@ -1,12 +1,24 @@
|
|||
import React , { useEffect , useState } from 'react';
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import { Route, Switch , Link } from "react-router-dom";
|
||||
import Loadable from "react-loadable";
|
||||
import Loading from "../../../Loading";
|
||||
|
||||
import {AlignCenter} from '../../Component/layout';
|
||||
import Cards from '../../Component/Cards';
|
||||
|
||||
import axios from 'axios';
|
||||
import '../Index.scss';
|
||||
|
||||
const GroupDetails = Loadable({
|
||||
loader: () => import("../Group/GroupDetails"),
|
||||
loading: Loading,
|
||||
});
|
||||
const Group = Loadable({
|
||||
loader: () => import("../TeamGroup"),
|
||||
loading: Loading,
|
||||
});
|
||||
const Member = Loadable({
|
||||
loader: () => import("../TeamMember"),
|
||||
loading: Loading,
|
||||
});
|
||||
const DetailIndex = Loadable({
|
||||
loader: () => import("../List"),
|
||||
loading: Loading,
|
||||
|
@ -15,39 +27,113 @@ const Setting = Loadable({
|
|||
loader: () => import("../Setting/TeamSettingIndex"),
|
||||
loading: Loading,
|
||||
});
|
||||
const GroupSetting = Loadable({
|
||||
loader: () => import("../Group/GroupDetailSetting"),
|
||||
loading: Loading,
|
||||
});
|
||||
export default ((props)=>{
|
||||
function Detail(props){
|
||||
const OIdentifier = props.match.params.OIdentifier;
|
||||
const pathname = props.location.pathname;
|
||||
|
||||
const [ detail , setDetail ] = useState(undefined);
|
||||
const [ flag , setFlag ] = useState(true);
|
||||
const [ buttonflag , setButtonflagFlag ] = useState(false);
|
||||
|
||||
// 设置页面:顶部不需要设置按钮了
|
||||
useEffect(()=>{
|
||||
if(pathname){
|
||||
if(pathname.indexOf(`/organize/${OIdentifier}/setting`)>-1){
|
||||
setFlag(false);
|
||||
}else{
|
||||
setFlag(true);
|
||||
}
|
||||
if(pathname.indexOf(`/organize/${OIdentifier}/group`)>-1 || pathname.indexOf(`/organize/${OIdentifier}/member`)>-1){
|
||||
setButtonflagFlag(true);
|
||||
}else{
|
||||
setButtonflagFlag(false);
|
||||
}
|
||||
}
|
||||
},[pathname])
|
||||
|
||||
useEffect(()=>{
|
||||
if(OIdentifier){
|
||||
getDetail(OIdentifier);
|
||||
}
|
||||
},[OIdentifier]);
|
||||
|
||||
function getDetail(id) {
|
||||
const url = `/organizations/${id}.json`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
setDetail(result.data);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
function updateDetail(name,desc){
|
||||
let d = detail;
|
||||
d.name = name;
|
||||
d.description = desc;
|
||||
setDetail(d);
|
||||
}
|
||||
return(
|
||||
<div className="teamDetail">
|
||||
<Cards
|
||||
title="组织名称"
|
||||
desc="组织名称组织名称组织名称组织名称组织名称"
|
||||
rightBtn={<a className="color-blue">设置<i className="iconfont icon-shezhi2 ml3"></i></a>}
|
||||
img={`https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=4193840146,2109186388&fm=26&gp=0.jpg`}
|
||||
/>
|
||||
{
|
||||
detail &&
|
||||
<Cards
|
||||
src={`/organize/${detail.name}`}
|
||||
title={detail.name}
|
||||
desc={!buttonflag && detail.description}
|
||||
img={detail.avatar_url}
|
||||
rightBtn={
|
||||
<React.Fragment>
|
||||
{flag && !buttonflag && detail.is_admin ? <AlignCenter className="color-blue">
|
||||
<Link to={`/organize/${OIdentifier}/setting`} className="color-blue">设置</Link>
|
||||
<i className="iconfont icon-shezhi2 ml3"></i>
|
||||
</AlignCenter> :""}
|
||||
{buttonflag &&
|
||||
<span className="subNavs">
|
||||
<Link to={`/organize/${OIdentifier}/member`} className={pathname ===`/organize/${OIdentifier}/member` ? "active":""}><span>组织成员</span>{detail.num_users && <lable>{detail.num_users}</lable>}</Link>
|
||||
<Link to={`/organize/${OIdentifier}/group`} className={pathname ===`/organize/${OIdentifier}/group` ? "active":""}><span>组织团队</span>{detail.num_teams &&<lable>{detail.num_teams}</lable>}</Link>
|
||||
</span>
|
||||
}
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
}
|
||||
<Switch {...props}>
|
||||
|
||||
{/* 组织团队-详情 */}
|
||||
<Route
|
||||
path="/organize/:organizeId/member/:memberId/setting"
|
||||
render={() => {
|
||||
return <GroupSetting {...props} />
|
||||
path="/organize/:OIdentifier/group/:groupId"
|
||||
render={(p) => {
|
||||
return <GroupDetails {...props} {...p} group={detail}/>
|
||||
}}
|
||||
></Route>
|
||||
{/* 组织成员 */}
|
||||
<Route
|
||||
path="/organize/:OIdentifier/member"
|
||||
render={(p) => {
|
||||
return <Member {...props} {...p} organizeDetail={detail}/>
|
||||
}}
|
||||
></Route>
|
||||
{/* 组织团队 */}
|
||||
<Route
|
||||
path="/organize/:OIdentifier/group"
|
||||
render={(p) => {
|
||||
return <Group {...props} {...p} organizeDetail={detail}/>
|
||||
}}
|
||||
></Route>
|
||||
<Route
|
||||
path="/organize/:organizeId/setting"
|
||||
render={() => {
|
||||
return <Setting {...props} />
|
||||
path="/organize/:OIdentifier/setting"
|
||||
render={(p) => {
|
||||
return <Setting {...props} {...p} organizeDetail={detail} updateFunc={updateDetail}/>
|
||||
}}
|
||||
></Route>
|
||||
<Route
|
||||
path="/organize/:organizeId"
|
||||
render={() => {
|
||||
return <DetailIndex {...props} />
|
||||
path="/organize/:OIdentifier"
|
||||
render={(p) => {
|
||||
return <DetailIndex {...props} {...p} organizeDetail={detail}/>
|
||||
}}
|
||||
></Route>
|
||||
</Switch>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
export default Detail;
|
|
@ -1,64 +1,77 @@
|
|||
import React from 'react';
|
||||
import React , {useEffect , useState} from 'react';
|
||||
import { Route, Switch , Link } from "react-router-dom";
|
||||
import Loadable from "react-loadable";
|
||||
import Loading from "../../../Loading";
|
||||
import '../Index.scss';
|
||||
import axios from 'axios';
|
||||
|
||||
import Cards from '../../Component/Cards';
|
||||
|
||||
const GroupNew = Loadable({
|
||||
loader: () => import("../Group/GroupNew"),
|
||||
loading: Loading,
|
||||
});
|
||||
const Group = Loadable({
|
||||
loader: () => import("../TeamGroup"),
|
||||
loading: Loading,
|
||||
});
|
||||
const Member = Loadable({
|
||||
loader: () => import("../TeamMember"),
|
||||
loading: Loading,
|
||||
});
|
||||
const GroupDetails = Loadable({
|
||||
loader: () => import("../Group/GroupDetails"),
|
||||
const GroupSetting = Loadable({
|
||||
loader: () => import("../Group/GroupDetailSetting"),
|
||||
loading: Loading,
|
||||
});
|
||||
|
||||
export default ((props)=>{
|
||||
const OIdentifier = props.match.params.OIdentifier;
|
||||
const groupId = props.match.params.groupId;
|
||||
const pathname = props.location.pathname;
|
||||
const [ detail , setDetail ] = useState(undefined);
|
||||
const [ flag , setFlag ] = useState(false);
|
||||
|
||||
// 设置页面:顶部不需要设置按钮了
|
||||
useEffect(()=>{
|
||||
if(pathname){
|
||||
if(pathname.indexOf(`/organize/${OIdentifier}/group/${groupId}/setting`)>-1){
|
||||
setFlag(false);
|
||||
}else{
|
||||
setFlag(true);
|
||||
}
|
||||
}
|
||||
},[pathname])
|
||||
|
||||
useEffect(()=>{
|
||||
if(OIdentifier && groupId){
|
||||
getDetail(OIdentifier,groupId);
|
||||
}
|
||||
},[OIdentifier,groupId]);
|
||||
|
||||
function getDetail(id,gId) {
|
||||
const url = `/organizations/${id}/teams/${gId}.json`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
setDetail(result.data);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
return(
|
||||
<div className="teamDetail">
|
||||
<Cards
|
||||
title="组织名称"
|
||||
rightBtn={
|
||||
<span className="subNavs">
|
||||
<Link to={``} className="active"><span>组织成员</span><lable>13</lable></Link>
|
||||
<Link to={``}><span>组织团队</span><lable>13</lable></Link>
|
||||
</span>
|
||||
}
|
||||
img={`https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=4193840146,2109186388&fm=26&gp=0.jpg`}
|
||||
/>
|
||||
<div className="teamDetail" style={{paddingTop:"0px"}}>
|
||||
<div>
|
||||
<i className="iconfont icon-zuobiao mr5"></i>
|
||||
<Link to={`/organize/${OIdentifier}`}>{OIdentifier}</Link>
|
||||
<i className="iconfont icon-youjiantou ml3 mr3 font-12 color-grey-9"></i>
|
||||
<span className="color-grey-9">{detail ? detail.name : "新建团队"}</span>
|
||||
</div>
|
||||
{
|
||||
detail &&
|
||||
<Cards
|
||||
src={`/organize/${OIdentifier}/group/${groupId}`}
|
||||
title={detail.name}
|
||||
rightBtn={
|
||||
flag && <span className="subNavs">
|
||||
<Link to={`/organize/${OIdentifier}/member`} className={pathname ===`/organize/${OIdentifier}/member` ? "active":""}><span>组织成员</span>{detail.num_users && <lable>{detail.num_users}</lable>}</Link>
|
||||
<Link to={`/organize/${OIdentifier}/group`} className={pathname ===`/organize/${OIdentifier}/group` ? "active":""}><span>组织团队</span>{detail.num_teams &&<lable>{detail.num_teams}</lable>}</Link>
|
||||
</span>
|
||||
}
|
||||
desc={!flag && detail.description}
|
||||
/>
|
||||
}
|
||||
<Switch {...props}>
|
||||
{/* 组织团队-设置 */}
|
||||
<Route
|
||||
path="/organize/:organizeId/member/:memberId"
|
||||
render={(props) => {
|
||||
return <GroupDetails {...props} />
|
||||
}}
|
||||
></Route>
|
||||
<Route
|
||||
path="/organize/:organizeId/member"
|
||||
render={(props) => {
|
||||
return <Member {...props} />
|
||||
}}
|
||||
></Route>
|
||||
<Route
|
||||
path="/organize/:organizeId/group/new"
|
||||
render={(props) => {
|
||||
return <GroupNew {...props} />
|
||||
}}
|
||||
></Route>
|
||||
<Route
|
||||
path="/organize/:organizeId/group"
|
||||
render={(props) => {
|
||||
return <Group {...props} />
|
||||
path="/organize/:OIdentifier/group/:groupId/setting"
|
||||
render={(p) => {
|
||||
return <GroupSetting {...props} {...p}/>
|
||||
}}
|
||||
></Route>
|
||||
</Switch>
|
||||
|
|
|
@ -1,49 +1,14 @@
|
|||
import React from 'react';
|
||||
import { Banner } from '../Component/layout';
|
||||
import styled from 'styled-components';
|
||||
import GroupItems from './TeamGroupItems';
|
||||
|
||||
const SpanName = styled.span`{
|
||||
font-size:16px;
|
||||
color:#333;
|
||||
}`
|
||||
const SpanFoot = styled.span`{
|
||||
margin-right:5px;
|
||||
color:#333
|
||||
}`
|
||||
const ALink = styled.a`{
|
||||
border:1px solid #F73030;
|
||||
color:#F73030!important;
|
||||
height:30px;
|
||||
line-height:30px;
|
||||
padding:0px 15px;
|
||||
border-radius:5px;
|
||||
}`
|
||||
const ImgContent = styled.img`{
|
||||
height:44px;
|
||||
width:44px;
|
||||
border-radius:50%;
|
||||
margin:5px 20px 5px 0px;
|
||||
}`
|
||||
export default (()=>{
|
||||
const limit = 14;
|
||||
function TeamGroup({organizeDetail,history}){
|
||||
return(
|
||||
<div>
|
||||
<div style={{background:"#fff",marginBottom:"30px"}}>
|
||||
<Banner>组织团队</Banner>
|
||||
<div className="groupBox">
|
||||
<div>
|
||||
<p className="g-head">
|
||||
<SpanName>oweners</SpanName>
|
||||
<ALink>离开团队</ALink>
|
||||
</p>
|
||||
<div className="g-body">
|
||||
<ImgContent src="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3331079987,1190181307&fm=111&gp=0.jpg"/>
|
||||
<ImgContent src="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3331079987,1190181307&fm=111&gp=0.jpg"/>
|
||||
</div>
|
||||
<p className="g-foot">
|
||||
<SpanFoot>2 名成员</SpanFoot>
|
||||
<SpanFoot>1 个项目</SpanFoot>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<GroupItems limit={limit} organizeDetail={organizeDetail} count={7} history={history}/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
export default TeamGroup;
|
|
@ -0,0 +1,119 @@
|
|||
import React , { useEffect , useState } from 'react';
|
||||
import Nodata from '../Nodata';
|
||||
import axios from 'axios';
|
||||
import { getImageUrl } from 'educoder';
|
||||
import { Pagination , Popconfirm , Spin } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import LeaveTeam from './Component/LeaveTeam';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const SpanFoot = styled.span`{
|
||||
margin-right:5px;
|
||||
color:#333
|
||||
}`
|
||||
const ImgContent = styled.img`{
|
||||
height:44px;
|
||||
width:44px;
|
||||
border-radius:50%;
|
||||
margin:5px 10px;
|
||||
}`
|
||||
function TeamGroupItems({organizeDetail,limit, count , history}){
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ isSpin , setIsSpin ] = useState(true);
|
||||
const [ total , setTotal ] = useState(0);
|
||||
const [ list , setList ] = useState(undefined);
|
||||
|
||||
useEffect(()=>{
|
||||
if(organizeDetail){
|
||||
getData();
|
||||
}
|
||||
},[organizeDetail]);
|
||||
|
||||
function getData(){
|
||||
setIsSpin(true);
|
||||
const url = `/organizations/${organizeDetail.id}/teams.json`;
|
||||
axios.get(url,{
|
||||
params:{ page ,limit}
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setList(result.data.teams);
|
||||
setTotal(result.data.total_count);
|
||||
setIsSpin(false);
|
||||
}
|
||||
})
|
||||
}
|
||||
// 离开团队
|
||||
function outTeam(id){
|
||||
const url = `/organizations/${organizeDetail.id}/teams/${id}/team_users/quit.json`;
|
||||
axios.delete(url).then(result =>{
|
||||
if(result && result.data){
|
||||
getData();
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
// 团队设置
|
||||
function toGroupSetting(id){
|
||||
history.push(`/organize/${organizeDetail && organizeDetail.name}/group/${id}/setting`);
|
||||
}
|
||||
// 解散团队
|
||||
function disMissGroup(id){
|
||||
const url = `/organizations/${organizeDetail.id}/teams/${id}.json`;
|
||||
axios.delete(url).then(result=>{
|
||||
if(result && result.data){
|
||||
getData();
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
return(
|
||||
<Spin spinning={isSpin}>
|
||||
<div style={{minHeight:"400px"}}>
|
||||
{
|
||||
list && list.length > 0 &&
|
||||
<div className="groupBox">
|
||||
{
|
||||
list.map((item,key)=>{
|
||||
return(
|
||||
<div key={key}>
|
||||
<p className="g-head">
|
||||
<Link to={`/organize/${organizeDetail.name}/group/${item.id}`} className="color-grey-3 font-16">{item.name}</Link>
|
||||
<span>
|
||||
{ item.is_admin && item.authorize!=="owner" && <Popconfirm title={`确定解散团队${item.name}?`} okText="是" cancelText="否" onConfirm={()=>disMissGroup(item.id)}><a className="color-red">解散团队</a></Popconfirm>}
|
||||
{ item.is_member && <LeaveTeam className="ml15" teamID={item.id} onOk={outTeam}/>}
|
||||
{ item.is_admin && <a className="ml15 color-blue" onClick={()=>toGroupSetting(item.id)}>团队设置</a> }
|
||||
</span>
|
||||
</p>
|
||||
<div className="g-body">
|
||||
{
|
||||
item.users && item.users.map((i,k)=>{
|
||||
return(
|
||||
k < count ? <Link to={`/users/${i.login}`}><ImgContent title={i.name} key={k} src={getImageUrl(`images/${i.image_url}`)}/></Link>
|
||||
:
|
||||
k === count ?
|
||||
<Link to={`/organize/${organizeDetail && organizeDetail.name}/group/${item.id}`} className="moreMember" title="查看更多" ><i className="iconfont icon-zhunbeizhong"></i></Link>
|
||||
:""
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<p className="g-foot">
|
||||
<SpanFoot>{item.num_users} 名成员</SpanFoot>
|
||||
<SpanFoot>{item.num_projects} 个项目</SpanFoot>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{ list && list.length > 0 && <Nodata _html="暂无数据"/> }
|
||||
{
|
||||
total > limit &&
|
||||
<div className="mt20 pb20 edu-txt-center">
|
||||
<Pagination simple current={page} total={total} pageSize={limit} onChange={(page)=>setPage(page)}/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
export default TeamGroupItems;
|
|
@ -1,25 +1,72 @@
|
|||
import React from 'react';
|
||||
import { Banner } from '../Component/layout';
|
||||
import React ,{ useEffect , useState } from 'react';
|
||||
import { Banner , WhiteBack } from '../Component/layout';
|
||||
import Cards from '../Component/MemberCards';
|
||||
import axios from 'axios';
|
||||
import Nodata from '../Nodata';
|
||||
import { Pagination , Spin } from 'antd';
|
||||
|
||||
const limit = 15;
|
||||
function TeamMember({organizeDetail,current_user}){
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ total , setTotal ] = useState(0);
|
||||
const [ isSpin , setIsSpin ] = useState(true);
|
||||
const [ list , setList ] = useState(undefined);
|
||||
|
||||
useEffect(()=>{
|
||||
if(organizeDetail){
|
||||
getData();
|
||||
}
|
||||
},[organizeDetail,page]);
|
||||
|
||||
function getData(){
|
||||
setIsSpin(true);
|
||||
const url = `/organizations/${organizeDetail.id}/organization_users.json`;
|
||||
axios.get(url,{
|
||||
page,limit
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setList(result.data.organization_users);
|
||||
setTotal(result.data.total_count);
|
||||
setIsSpin(false);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default (()=>{
|
||||
return(
|
||||
<div>
|
||||
<WhiteBack style={{marginBottom:"30px"}}>
|
||||
<Banner>组织成员</Banner>
|
||||
<div className="memberBox">
|
||||
<Cards
|
||||
img="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3331079987,1190181307&fm=111&gp=0.jpg"
|
||||
name="陈教授"
|
||||
time="2020-04-29"
|
||||
focusStatus={true}
|
||||
/>
|
||||
<Cards
|
||||
img="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3331079987,1190181307&fm=111&gp=0.jpg"
|
||||
name="陈教授"
|
||||
time="2020-04-29"
|
||||
focusStatus={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Spin spinning={isSpin}>
|
||||
<div style={{minHeight:"400px"}}>
|
||||
{
|
||||
list && list.length > 0 &&
|
||||
<div className="memberBox">
|
||||
{
|
||||
list.map((item,key)=>{
|
||||
return(
|
||||
item.user && <Cards
|
||||
user={item.user}
|
||||
img={item.user.image_url}
|
||||
name={item.user.name}
|
||||
time={item.created_at}
|
||||
focusStatus={item.user.watched}
|
||||
is_current_user={current_user && current_user.login === item.user.login}
|
||||
login={item.user && item.user.login}
|
||||
successFunc={getData}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{ list && list.length === 0 && <Nodata _html="暂无数据"/> }
|
||||
</div>
|
||||
</Spin>
|
||||
{
|
||||
total >limit &&
|
||||
<div className="mt20 pb20 edu-txt-center">
|
||||
<Pagination simple current={page} pageSize={limit} total={total} onChange={(page)=>setPage(page)}/>
|
||||
</div>
|
||||
}
|
||||
</WhiteBack>
|
||||
)
|
||||
})
|
||||
}export default TeamMember;
|
|
@ -93,7 +93,7 @@ class CommonUsers extends Component {
|
|||
{count === 0 ? (
|
||||
<NoneData _html="暂时还没有相关数据!" />
|
||||
) : (
|
||||
<UserList users={users} userClass={'w-25'} {...this.props}></UserList>
|
||||
<UserList users={users} userClass={'w-25'} successFunc={this.getUsersList} {...this.props}></UserList>
|
||||
)}
|
||||
</div>
|
||||
</Spin>
|
||||
|
|
|
@ -1,80 +1,44 @@
|
|||
import React, { Component } from "react";
|
||||
import React , {useState} from "react";
|
||||
import axios from "axios";
|
||||
import { Button } from "antd";
|
||||
import "./list.css";
|
||||
|
||||
class FocusButton extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
is_watch: false,
|
||||
id: "",
|
||||
isSpin: false,
|
||||
fontClass: "font-12",
|
||||
starText: "关注",
|
||||
is_block: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
this.set_watch();
|
||||
};
|
||||
|
||||
set_watch = () => {
|
||||
this.setState({
|
||||
is_watch: this.props.is_watch,
|
||||
id: this.props.id,
|
||||
isSpin: false,
|
||||
fontClass: this.props.fontClass ? this.props.fontClass : "font-12",
|
||||
starText: this.props.starText ? this.props.starText : "关注",
|
||||
is_block: this.props.is_block === undefined ? false : this.props.is_block
|
||||
});
|
||||
};
|
||||
|
||||
function FocusButton({is_watch , fontClass, starText, is_block , id , successFunc}){
|
||||
const [ isSpin , setIsSpin ] = useState(false);
|
||||
// 关注和取消关注
|
||||
focusFunc = (flag) => {
|
||||
const { id } = this.state;
|
||||
this.setState({ isSpin: true });
|
||||
function focusFunc(flag){
|
||||
setIsSpin(true);
|
||||
axios({
|
||||
method: flag ? "delete" : "post",
|
||||
url: `/watchers/${flag ? "unfollow" : "follow"}.json`,
|
||||
params: {
|
||||
target_type: "user",
|
||||
id: id,
|
||||
id,
|
||||
},
|
||||
}).then((result) => {
|
||||
if (result && result.data.status === 0) {
|
||||
successFunc && successFunc();
|
||||
}
|
||||
setIsSpin(false);
|
||||
})
|
||||
.then((result) => {
|
||||
if (result && result.data.status === 0) {
|
||||
this.setState({
|
||||
is_watch: result.data.watched,
|
||||
isSpin: false,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({ isSpin: false })
|
||||
});
|
||||
.catch((error) => {setIsSpin(false);});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { is_watch, isSpin, fontClass, starText, is_block } = this.state;
|
||||
|
||||
return (
|
||||
<Button type={is_watch ? "default" : "primary"} ghost={!is_watch} block={is_block} loading={isSpin} onClick={() => this.focusFunc(is_watch)}>
|
||||
{is_watch ? (
|
||||
return (
|
||||
<Button type={is_watch ? "default" : "primary"} ghost={!is_watch} block={is_block} loading={isSpin} onClick={() => focusFunc(is_watch)}>
|
||||
{is_watch ? (
|
||||
<span className="">
|
||||
<i className="iconfont icon-shixing font-15 text-yellow mr-4"></i>
|
||||
<span className={fontClass || "font-12"}>已关注</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="">
|
||||
<i className="iconfont icon-shixing font-15 text-yellow mr-4"></i>
|
||||
<span className={fontClass}>已关注</span>
|
||||
<i className="iconfont icon-kongxing font-15"></i>
|
||||
<span className={fontClass}>{starText || "关注"}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="">
|
||||
<i className="iconfont icon-kongxing font-15"></i>
|
||||
<span className={fontClass}>{starText}</span>
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export default FocusButton;
|
||||
|
|
|
@ -115,7 +115,7 @@ class ForkUsers extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { users, count, isSpin } = this.state;
|
||||
const { users, isSpin } = this.state;
|
||||
|
||||
return (
|
||||
<div className="pbt15">
|
||||
|
@ -123,12 +123,9 @@ class ForkUsers extends Component {
|
|||
<div className="plr-20 user-list-items">
|
||||
<div className="font-18 pb-10 border-b-line">Fork列表</div>
|
||||
<Spin spinning={isSpin}>
|
||||
<div className="w-100 inline-block">
|
||||
{count === 0 ? (
|
||||
<NoneData _html="暂时还没有相关数据!" />
|
||||
) : (
|
||||
this.renderList(users)
|
||||
)}
|
||||
<div className="w-100 inline-block" style={{minHeight:"400px"}}>
|
||||
{users && users.length === 0 ? <NoneData _html="暂时还没有相关数据!" /> :"" }
|
||||
{this.renderList(users)}
|
||||
</div>
|
||||
</Spin>
|
||||
{this.Paginations()}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue