add team codes

This commit is contained in:
Sylor-huang 2021-01-31 22:24:46 +08:00
parent bd85703412
commit 7eef81b91a
12 changed files with 957 additions and 364 deletions

View File

@ -1,6 +1,8 @@
import React from 'react';
import './Component.scss';
import { Button } from 'antd';
import styled from 'styled-components';
import FocusButton from "../UsersList/focus_button";
const Img = styled.img`{
border-radius:50%;
@ -56,17 +58,18 @@ const Div = styled.div`{
align-items: center;
border:1px solid #eee;
}`
export default (({img,name,time, focusStatus})=>{
return(
export default (({ img, name, time, focusStatus, is_current_user, login }) => {
return (
<Div>
<Img src={img}/>
<Img src={img} />
<div className="m-infos">
<Name>{name}</Name>
<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} />
}
</div>
</Div>

View File

@ -2,10 +2,9 @@ import React from "react";
import { Input } from "antd";
const { Search } = Input;
export default ({ placeholder , onSearch , value , onChange }) => {
export default ({ placeholder , onSearch , onChange }) => {
return (
<Search
value={value}
allowClear
placeholder={placeholder}
enterButton={'搜索'}

View File

@ -40,7 +40,7 @@ export default ({ getUser })=>{
return (
<Option
key={key}
value={`${item.user_id}`}
value={`${item.login}`}
searchValue={`${item.username}`}
>
<img

View File

@ -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;
@ -154,12 +156,9 @@ export const Content = styled.div`{
background-color:#fff;
justify-content: center;
}`
export const RadioList = styled.span`{
color:#888888;
margin-left: 8px;
}`
export const RadioListBold = styled.span`{
color:#333 !important;
margin: 0 2px;
export const GroupProjectBackgroup = styled.div`{
background:#fafafa;
padding: 30px;
width:100%;
}`

View File

@ -1,35 +1,68 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import Cards from '../../Component/MemberCards';
import { getImageUrl } from 'educoder';
import axios from 'axios';
import { Spin, Empty,Pagination } from 'antd';
export default ((props) => {
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(15);
const [total, setTotal] = useState(0);
const [data, setMembers] = useState(undefined);
const [isSpin, setIsSpin] = useState(false);
const { OIdentifier, groupId } = props.match.params;
const { current_user } = props;
export default (()=>{
return(
<div>
useEffect(() => {
getMember()
}, [page])
function getMember() {
setIsSpin(true)
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) => { });
setIsSpin(false)
};
return (
<Spin spinning={isSpin}>
<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}
/>
{
data && data.length > 0 ? data.map((item, key) => {
return (
<Cards
img={getImageUrl(`images/${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}
/>
)
})
:
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
}
</div>
</div>
{
total > limit ?
<div className="edu-txt-center mt30 mb20">
<Pagination simple defaultCurrent={page} total={total} pageSize={limit} onChange={ChangePage}></Pagination>
</div>
: ""
}
</Spin>
)
})

View File

@ -1,8 +1,10 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Spin, Empty, Pagination } from 'antd';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
const Box=styled.div`{
import { getImageUrl } from 'educoder';
const Box = styled.div`{
padding:0px 38px;
}`
const Div = styled.div`{
@ -21,17 +23,76 @@ const Imgs = styled.img`{
border-radius:50%;
}`
export default (()=>{
return(
const faker_projects = [
{
id: 2,
project: {
owner_name: "ceshi_org_1",
name: "jajaj0",
identifier: "ssjsksjsk"
}
},
{
id: 3,
project: {
owner_name: "sylor_test",
name: "sylor_test",
identifier: "sylor_test"
}
}
]
export default ((props) => {
const [isSpin, setIsSpin] = useState(false);
const [projects, setProjects] = useState(undefined);
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(15);
const [total, setTotal] = useState(0);
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 && result.data.team_projects.length > 0) {
setProjects(result.data.team_projects)
setTotal(result.data.total_count)
} else {
setProjects(faker_projects)
}
}).catch((error) => { });
setIsSpin(false)
}
return (
<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>
<Spin spinning={isSpin}>
{
projects && projects.length > 0 ? projects.map((item, key) => {
return (
<Div>
<Imgs src={item.project.image_url ? getImageUrl(`images/${item.project.image_url}`) : "https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3025493530,1989042357&fm=26&gp=0.jpg"}/>
<Link to={`/projects/${item.project.owner_name}/${item.project.name}`}>{item.project.name}</Link>
</Div>
)
})
:
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
}
</Spin>
</Box>
)
})

View File

@ -1,12 +1,12 @@
import React , {useState} from 'react';
import { Button } from 'antd';
import React, { useState, useEffect } from 'react';
import { Button, Spin, Popconfirm, Empty } 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;
@ -16,30 +16,88 @@ const Leave = styled.a`{
height:30px;
line-height:30px;
}`
export default ((props)=>{
const [ nav , setNav ] = useState('0');
export default ((props) => {
const [nav, setNav] = useState('0');
const [isSpin, setIsSpin] = useState(false);
const [userSpin, setUserSpin] = useState(false);
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(15);
const [total, setTotal] = useState(0);
const [group, setGroup] = useState(undefined);
const { OIdentifier, groupId } = props.match.params;
const { current_user } = props;
return(
useEffect(() => {
setIsSpin(true)
const url = `/organizations/${OIdentifier}/teams/${groupId}.json`;
axios.get(url)
.then((result) => {
if (result && result.data) {
setGroup(result.data)
}
}).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)
this.getMember();
}
})
.catch((error) => { });
}
setIsSpin(false)
}
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>
<Spin spinning={isSpin}>
{
group ?
<div>
<AlignCenterBetween>
<span className="color-grey-3">{group.name}</span>
{group.is_member ?
<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" href={`/organize/${OIdentifier}/group/${groupId}/setting`}><span className="color-white">团队设置</span></Button> : ""}
</div>
</div>
:
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
}
</Spin>
</Short>
<Long>
<Gap>
<WhiteBack>
<Tabs nav={['团队成员','团队项目']} index={nav} onChange={setNav}>
<Tabs nav={['团队成员', '团队项目']} index={nav} onChange={setNav}>
{
nav === "0" ?
<Memberlist />:<Grouplist />
<Memberlist {...props} /> : <Grouplist {...props}/>
}
</Tabs>
</WhiteBack>

View File

@ -0,0 +1,208 @@
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,props, match, showNotification, history, GroupDetail }) => {
const [isSpin, setIsSpin] = useState(false);
const [check_box, setGroupDetail] = useState(false);
const [switch_box, setSwtichBox] = useState([]);
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) {
setGroupDetail(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) => (
<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>
),
[]
);
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(`/organizations/${OIdentifier}/teams/${groupId}`);
}
}).catch(error => { })
} else {
const url = `/organizations/${OIdentifier}/teams.json`;
axios.post(url, {
...values
}).then(result => {
if (result && result.data) {
showNotification("团队创建成功!");
history.push(`/organizations/${OIdentifier}/teams/${result.data.id}`);
}
}).catch(error => { })
}
}
});
setIsSpin(false)
}
function change_check_box_status() {
setGroupDetail(!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(`/organizations/${OIdentifier}/teams/${groupId}`);
}else{
history.push(`/organizations/${OIdentifier}/teams`);
}
}
return (
<Spin spinning={isSpin}>
<WhiteBack className="mb30">
<Banner>{groupId ? "基本设置" : "新建团队"}</Banner>
<Div>
<Form>
{helper(
'团队名称:',
"name",
[{ required: true, message: "请输入团队名称" }],
<Input placeholder="请输入团队名称" />, true
)}
{helper(
<span className="mb5">团队描述<span className="color-grey-8">(描述团队的目的或作用)</span></span>,
"description",
[],
<TextArea
placeholder="请输入团队描述"
/>
)}
{helper(
'项目权限:',
"includes_all_project",
[],
<Radio.Group>
<Radio value={true} style={addStyle}>指定项目<span className="color-grey-8 ml10">(团队成员将只能访问添加到团队的项目 选择此项 <span className="color-grey-3">将不会</span> 自动删除已经添加的项目)</span></Radio>
<Radio value={false} style={OptionStyle}>所有项目<span className="color-grey-8 ml10">(团队可以访问所有项目选择此选项将 <span className="color-grey-3">添加所有现有的</span> 项目到指定团队)</span></Radio>
</Radio.Group>, false, 0
)}
{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
)}
{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
)}
</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>
)
})
)

View File

@ -1,123 +1,9 @@
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(
<GroupFrom {...props}></GroupFrom>
)
}
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;

View File

@ -1,9 +1,315 @@
import React , { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { WhiteBack, Blueline, AlignCenter, FlexAJ } from '../../../Component/layout';
import { Menu, Table, Pagination, Icon, Tooltip, Popconfirm, Dropdown, Spin } from 'antd';
import Sort from '../../../Component/Sort';
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;
height:30px;
border-radius:50%;
}`
export default ((props) => {
const [login, setAddUserLogin] = useState(undefined);
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(15);
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;
const { current_user } = props;
useEffect(() => {
getMember()
}, [page, search, identify])
function GroupMemberSetting() {
return(
<div>团队成员管理</div>
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)
};
// Login
function getUser(login) {
setAddUserLogin(login)
}
function addUser() {
setIsSpin(true)
const url = `/organizations/${OIdentifier}/teams/${groupId}/team_users.json`;
if (login) {
axios.post(url, {
username: login
})
.then((result) => {
if (result && result.data) {
setPage(1)
setQuery(undefined)
setIdentify(undefined)
this.getMember();
}
})
.catch((error) => { });
}
setIsSpin(false)
}
//
function onSearch(value) {
setQuery(value)
}
//
function onSelectMenu(e) {
let select_role = e.key
setPage(1)
setQuery(undefined)
setIdentify(select_role === "all" ? undefined : select_role)
}
//
function ChangePage(page) {
setPage(page);
}
//
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)
this.getMember();
}
})
.catch((error) => { });
}
setIsSpin(false)
}
const MENU_LIST = [
{
id: "Manager",
name: "管理员",
desc: "拥有仓库设置功能、代码库读、写操作"
},
{
id: "Developer",
name: "开发者",
desc: "只拥有代码库读、写操作"
},
{
id: "Reporter",
name: "报告者",
desc: "只拥有代码库读操作"
},
];
const menu = (
<Menu onSelect={onSelectMenu}>
<Menu.Item key="all">全部</Menu.Item>
{MENU_LIST.map((item, key) => {
return (
<Menu.Item
key={item.id}
value={item.id}
>
{item.name}
</Menu.Item>
);
})}
</Menu>
)
}
export default GroupMemberSetting;
const roleTitle = (
<div><span className="mr3">角色</span>
<Tooltip placement='bottom' title=
{
<div>
{MENU_LIST.map((item, key) => {
return (
<div className="mb3">{item.name}{item.desc}</div>
);
})}
</div>
}
>
<Icon type="question-circle"></Icon>
</Tooltip>
</div>
);
function 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) => this.changeOperaiton(e, id)}
>
{item.name}
</Menu.Item>
);
})}
</Menu>
);
//
function changeOperaiton(e, id) {
console.log(e)
};
const member_roles = (item) => {
const operation = MENU_LIST.filter((i) => i.id === item.identify);
return (
<span>
{operation && operation[0] ?
<span>
{current_user && current_user.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>
: ""
}
</span>
);
};
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: roleTitle,
dataIndex: 'role',
width: "20%",
render: (value, item) => member_roles(item.user),
},
{
title: '操作',
dataIndex: 'operation',
width: "15%",
render: (value, item) => {
return <Popconfirm
title="确认移除成员吗?"
onConfirm={() => removeUser(item.user.login)}
okText="确认"
cancelText="取消"
>
<a className="color-grey-8" href="#">移除成员</a>
</Popconfirm>
}
}
]
return (
<Spin spinning={isSpin}>
<WhiteBack>
<Title>
<span>组织成员管理</span>
<AlignCenter>
<SearchUser getUser={getUser} />
<Blueline className="ml30" onClick={addUser}>+&nbsp;添加用户</Blueline>
</AlignCenter>
</Title>
<FlexAJ className="padding20-30">
<div style={{ width: "580px" }}>
<Search placeholder="输入用户名或邮箱、团队名搜索" value={search} onSearch={onSearch} />
</div>
<Sort menu={menu}>
<a className="color-blue">角色筛选<i className="iconfont icon-sanjiaoxing-down ml3 font-14"></i></a>
</Sort>
</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={ChangePage}></Pagination>
</div>
: ""
}
</div>
</WhiteBack>
</Spin>
)
})

View File

@ -1,9 +1,177 @@
import React , { useState } from 'react';
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 faker_projects = [
{
id: 2,
project: {
owner_name: "ceshi_org_1",
name: "jajaj0",
identifier: "ssjsksjsk"
}
},
{
id: 3,
project: {
owner_name: "sylor_test",
name: "sylor_test",
identifier: "sylor_test"
}
}
]
// 使faker
function GroupProjectSetting(props) {
const [isSpin, setIsSpin] = useState(false);
const [projects, setProjects] = useState(undefined);
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(15);
const [total, setTotal] = useState(0);
const [search, setQuery] = useState(undefined);
const [repo_name, setRepoName] = useState(undefined);
const { OIdentifier, groupId } = props.match.params;
useEffect(() => {
get_project()
}, [page, search])
function GroupProjectSetting() {
return(
<div>团队项目管理</div>
function get_project() {
setIsSpin(true)
const url = `/organizations/${OIdentifier}/teams/${groupId}/team_projects.json`;
axios
.get(url, {
params: {
page,
search,
limit
},
})
.then((result) => {
if (result && result.data && result.data.team_projects.length > 0) {
setProjects(result.data.team_projects)
setTotal(result.data.total_count)
} else {
setProjects(faker_projects)
}
}).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)
setQuery(undefined)
get_project()
}
}).catch((error) => { });
setIsSpin(false)
}
function removeALLProject() {
//
}
function addALLProject() {
//
}
//
function addSingleProject(){
setIsSpin(true)
const url = `/organizations/${OIdentifier}/teams/${groupId}/team_projects.json`;
axios.post(url, {repo_name: repo_name})
.then((result) => {
if (result && result.data.id) {
setPage(1)
setQuery(undefined)
get_project()
}
}).catch((error) => { });
setIsSpin(false)
}
return (
<Spin spinning={isSpin}>
<WhiteBack className="mb30">
<Title>
<span>团队项目管理</span>
</Title>
<FlexAJ className="padding20-30">
<GroupProjectBackgroup>
<FlexAJ>
<div>
<AutoComplete
style={{ width: 300 }}
placeholder="搜索项目..."
/>
<Blueline className="ml30" onClick={()=>addSingleProject()}>+&nbsp;添加项目</Blueline>
</div>
<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">
<List
itemLayout="horizontal"
dataSource={projects}
renderItem={item => (
<List.Item
extra={
<Popconfirm
title="确认移除项目吗?"
onConfirm={() => removeProject(item.project.identifier)}
okText="确认"
cancelText="取消"
>
<a className="color-red" href="#">移除</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;

View File

@ -1,160 +1,32 @@
import React, { forwardRef, useCallback, useEffect, useState } from 'react';
import { Form, Input, Radio, Switch, Divider, Button } from 'antd';
import { WhiteBack, RadioList, RadioListBold } from '../../Component/layout';
import Title from '../../Component/Title';
import styled from 'styled-components';
import React, { useEffect, useState } from 'react';
import {Spin } from 'antd';
import axios from 'axios';
const TextArea = Input.TextArea;
import GroupFrom from './GroupForm'
const Div = styled.div`{
padding:20px 30px;
}`
const radioStyle = {
display: 'block',
height: '30px',
lineHeight: '30px',
};
export default Form.create()(
forwardRef(({ form, organizeDetail, showNotification, history, current_user }) => {
const [image, setImage] = useState(undefined);
const [imageFlag, setImageFlag] = useState(false);
const [password, setPassword] = useState(undefined);
const [passwordFlag, setPasswordFlag] = useState(false);
const { getFieldDecorator, validateFields, setFieldsValue } = form;
function SettingCommon(props){
const OIdentifier = props.match.params.OIdentifier;
const groupId = props.match.params.groupId;
useEffect(() => {
if (organizeDetail) {
setFieldsValue({
...organizeDetail
})
setImage(organizeDetail.avatar_url);
}
}, [organizeDetail])
const [ detail , setDetail ] = useState(undefined);
const [isSpin, setIsSpin] = useState(true);
const helper = useCallback(
(label, name, rules, widget, isRequired, flag) => (
<div>
<span className={isRequired ? "required" : ""}>{label}</span>
<Form.Item>
{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("组织信息更新成功!");
history.push(`/organize/${values.name}/setting`);
}
}).catch(error => { })
}
})
}
function getImage(image) {
setImageFlag(true);
setImage(image);
useEffect(()=>{
if(groupId){
getDetail();
}
},[groupId]);
//
function deleteOrganize() {
if (!password) {
setPasswordFlag(true);
return
} else {
setPasswordFlag(false);
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`);
}
})
}
}
return (
<div>
<WhiteBack>
<Title primary>基本设置</Title>
<Div>
<Form>
{helper(
"团队名称:",
"name",
[{ required: true, message: "请输入团队名称:" }],
<Input placeholder="请输入团队名称:" />, true
)}
{helper(
"团队描述:",
"description",
[],
<div style={{ marginTop: '4px' }}>
<TextArea placeholder="请输入团队描述团队的目的或作用" />
</div>
)}
{helper(
'项目权限:',
"visibility",
[],
<Radio.Group>
<Radio value="common" style={radioStyle}><span className="color-black">指定项目</span><RadioList>(团队成员将只能访问添加到团队的项目 选择此项<RadioListBold>将不会</RadioListBold>自动删除已经添加的项目)</RadioList></Radio>
<Radio value="limited" style={radioStyle}><span className="color-black">所有项目</span><RadioList>(团队可以访问所有项目选择此选项将 <RadioListBold>添加所有现有的</RadioListBold>项目到指定团队)</RadioList></Radio>
<Radio value="privacy" style={radioStyle}><span className="color-black">新建项目</span><RadioList>(成员可以在组织中新建项目创建者将自动获得新建的项目的管理员权限)</RadioList></Radio>
</Radio.Group>
)}
{helper(
'权限:',
"repo_admin_change_team_access",
[],
<Radio.Group>
<Radio value="common" style={radioStyle}><span className="color-black">读取权限</span><RadioList>(成员可以查看和克隆团队项目)</RadioList></Radio>
<Radio value="limited" style={radioStyle}><span className="color-black">写入权限</span><RadioList>(成员可以查看和推送提交到团队项目)</RadioList></Radio>
<Radio value="privacy" style={radioStyle}><span className="color-black">管理员权限</span><RadioList>(成员可以拉取和推送到团队项目同时可以添加协作者)</RadioList></Radio>
</Radio.Group>
)}
<Divider />
{helper(
'允许访问项目单元:',
"max_repo_creation",
[{ required: true, message: "请至少选择一项" }],
<div>
<div>
<Switch defaultChecked className="group-setting-switch"/>
<span><span className="color-black ml15">代码库</span><RadioList>(查看源码文件提交和分支)</RadioList></span>
</div>
<div>
<Switch className="group-setting-switch"/>
<span><span className="color-black ml15">任务</span><RadioList>(组织 bug 报告任务和里程碑)</RadioList></span>
</div>
<div>
<Switch className="group-setting-switch"/>
<span><span className="color-black ml15">合并请求</span><RadioList>(启用合并请求和代码评审)</RadioList></span>
</div>
<div>
<Switch className="group-setting-switch"/>
<span><span className="color-black ml15">版本发布</span><RadioList>(跟踪项目版本和下载)</RadioList></span>
</div>
</div>,
true
)}
<Button type={"primary"} onClick={updateDetail}>更新团队设置</Button>
<Button type={"text"} onClick={updateDetail} className="ml30"><span className="pl30 pr30">取消</span></Button>
</Form>
</Div>
</WhiteBack>
</div>
)
})
)
function getDetail() {
const url = `/organizations/${OIdentifier}/teams/${groupId}.json`;
axios.get(url).then((result) => {
setDetail(result.data)
}).catch((error) => { });
setIsSpin(false)
}
return(
<Spin spinning={isSpin}>
<GroupFrom {...props} GroupDetail={detail}></GroupFrom>
</Spin>
)
}
export default SettingCommon;