Merge branch 'gitlink_server' of http://106.75.45.236:3000/Gitlink/forgeplus-react into feature_bot

This commit is contained in:
谢思 2022-06-21 15:44:39 +08:00
commit ed32e55706
12 changed files with 403 additions and 62 deletions

View File

@ -89,8 +89,13 @@ export function initAxiosInterceptors(props) {
if (response.data.status === 404) {
let responseURL = response.request ? response.request.responseURL:'';
// 组织和个人的拥有情况404不跳转
if (responseURL.indexOf('/api/users/') === -1 && responseURL.indexOf('/api/organizations/') === -1 ) {
locationurl('/nopage');
// 邀请页面不进行404跳转
if( window.location.pathname.includes('/invite') && (responseURL.includes('/simple.json')||responseURL.includes('/detail.json')||responseURL.includes('/menu_list.json'))){
}else{
locationurl('/nopage');
}
}
}

View File

@ -11,6 +11,7 @@ export function getImageUrl(path) {
// https://www.educoder.net
// https://testbdweb.trustie.net
// const local = 'http://localhost:3000'
path && !path.startsWith('/') && !path.startsWith('http') && (path = '/'.concat(path));
const local = 'https://testforgeplus.trustie.net';
if (isDev) {
return `${local}/${path}`

View File

@ -80,7 +80,7 @@ function AddMember({getID,login,showNotification}){
<AutoComplete
dataSource={source}
value={searchKey}
style={{ width: 300 }}
style={{ width: 250 }}
onChange={changeInputUser}
onSelect={selectInputUser}
placeholder="搜索需要添加的用户..."

92
src/forge/Invite/Index.js Normal file
View File

@ -0,0 +1,92 @@
import React, { useState, useEffect } from 'react';
import { Modal, Button, } from 'antd';
import { Base64 } from 'js-base64';
import './index.scss';
import { Link } from 'react-router-dom';
import { getImageUrl } from 'educoder'
import axios from 'axios';
const identify = {
manager: "管理员",
developer: "开发者",
reporter: "报告者",
owner: "所有者",
}
function Invite({ history, current_user, match, projectDetail, showNotification }) {
if (!current_user.login) {
let { pathname, search } = window.location;
window.location.href = `/login?go_page=${pathname}${search}`;
}
let permission = projectDetail && projectDetail.permission;
const { projectsId, owner } = match.params;
let inviteString = window.location.search && window.location.search.split('?invite=')[1];
let data = inviteString && JSON.parse(Base64.decode(inviteString));
const [detail, setDetail] = useState({});
const [visible, setVisible] = useState(true);
useEffect(() => {
if (permission && detail.role && detail.role == permission.toLocaleLowerCase() || permission == 'Owner') {
showNotification(`您已经是${identify[detail.role]}`)
setTimeout(() => {
history.push(`/${owner}/${projectsId}`);
}, 2000)
} else {
setVisible(true)
}
}, [permission, detail.role]);
useEffect(() => {
const url = `/${owner}/${projectsId}/project_invite_links/show_link.json?invite_sign=${data.sign}`;
axios.get(url).then(res => {
if (res && res.data) {
setDetail(res.data);
} else {
showNotification('查询邀请链接失败');
}
})
}, [])
function accept() {
const url = `/${owner}/${projectsId}/project_invite_links/redirect_link.json?invite_sign=${data.sign}`;
axios.post(url).then(res => {
if (res && res.data.message == 'success') {
if (detail.is_apply) {
setVisible(false);
Modal.success({ content: '提交申请成功,请等待该仓库管理员审核' });
} else {
history.push(`/${owner}/${projectsId}`);
}
}
}).catch(error => { })
}
return (
<div className="">
{data && <Modal
visible={visible}
className="invite_development"
title={<div className="ownerImage"><img src={detail.project && getImageUrl(detail.project.owner.image_url)} /></div>}
width="548px"
closable={true}
onCancel={() => { setVisible(false); history.push(`/${current_user.login}`) }}
centered
okText={'接受'}
cancelText={'拒绝'}
onOk={accept}
maskClosable={false}
>
<Link className="invite_project link" target="_blank" to={`/${data.ownerLogin}/${data.projectId}`}>{detail.project && detail.project.owner.name}/{detail.project && detail.project.name}</Link>
<div className="invite_content">
<Link className="link" to={`/${detail.user && detail.user.login}`}>{detail.user && detail.user.name}</Link> {identify[detail.role]}
是否接受邀请
</div>
</Modal>}
</div>
)
}
export default Invite;

100
src/forge/Invite/index.scss Normal file
View File

@ -0,0 +1,100 @@
.invite_development {
font-family: PingFang SC;
height: 366px;
background-image: linear-gradient(
359.37deg,
#ebf3ff 0%,
#eff5ff 55.01%,
#cfdeff 100%
);
border: 1.5px solid #ffffff;
border-radius: 4px;
.ant-modal-close{
top:0 !important;
}
.ant-modal-close-x{
font-size: 30px;
color: #666;
width: 48px;
height: 48px;
line-height: 48px;
transform: scaleX(1.05);
}
.ant-modal-content {
background-color: inherit;
}
.ant-modal-header {
background: inherit;
border: 0;
padding-bottom: 0;
}
.ownerImage {
position: relative;
top:-66px;
width: 100px;
height: 100px;
background-color: #e9f1ff;
border: 1px solid #ffffff;
border-radius: 50%;
margin:0 auto;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
img{
width: 100%;
}
}
.invite_project {
display: block;
margin: 0 auto;
font-weight: 500;
font-size: 20px;
line-height: 22px;
text-align: center;
margin-bottom: 30px;
}
.invite_content {
width: 422px;
padding: 24px 48px;
background-color: rgba(225, 231, 255, 0.71);
border-radius: 4px 4px 0px 0px;
margin: 10px auto;
font-weight: 500;
font-size: 17px;
line-height: 32px;
text-align: center;
color: #151d40;
}
.link {
cursor: pointer;
color: $primary-color;
&:hover {
color: $primary-color-hover;
}
}
.ant-modal-footer {
text-align: center;
border: 0;
padding-bottom: 55px;
button {
width: 150px;
height: 42px;
border-radius: 5px;
font-size: 15px;
&:first-child {
background-color: rgba(196, 0, 14, 0.07);
border: 1px solid;
border-color: #f60011;
color: #f60011;
&:hover {
opacity: 0.75;
}
}
& + button {
margin-left: 30px;
}
}
}
}

View File

@ -4,6 +4,7 @@ import { Link, Route, Switch } from 'react-router-dom';
import { Content, AlignTop } from '../Component/layout';
import DetailBanner from './sub/DetailBanner';
import cookie from 'react-cookies';
import { Base64 } from 'js-base64';
import '../css/index.scss'
import './list.scss';
@ -145,6 +146,10 @@ const WikiEdit = Loadable({
loader: () => import('../Wiki/EditWiki'),
loading: Loading,
});
const Invite = Loadable({
loader: () => import('../Invite/Index'),
loading: Loading,
});
/**
* permissionManager:管理员Reporter报告人员(只有读取权限)Developer开发人员除不能设置仓库信息外
*/
@ -227,12 +232,12 @@ class Detail extends Component {
})
}
clearIssueCookies=(history)=>{
clearIssueCookies = (history) => {
const { pathname } = history;
const { projectsId , owner } = this.props.match.params;
const { projectsId, owner } = this.props.match.params;
let currentIssue = pathname.indexOf(`/${owner}/${projectsId}/issues`) === -1;
if (currentIssue) {
cookie.save('states', undefined,{ expires: 0,path:`/` });
cookie.save('states', undefined, { expires: 0, path: `/` });
}
}
@ -304,15 +309,15 @@ class Detail extends Component {
},
disconnected: () => {
console.log("###### cannot connected! ######");
},
},
received: data => {
console.log(`###### ---received data--- ######`);
console.log(data);
if (data) {
if(deleteFlag){
if (deleteFlag) {
this.props.showNotification("镜像同步成功!");
window.location.reload();
}else{
} else {
if (data.project && data.project.mirror_status === 2) {
this.deleteProjectBack();
}
@ -326,20 +331,20 @@ class Detail extends Component {
cable.subscriptions.consumer.disconnect();
}
},
onerror:()=>{
onerror: () => {
console.log("###### cannot connected! ######");
}
});
this.timerChannel = setTimeout(this.reloadDetail,5000);
this.timerChannel = setTimeout(this.reloadDetail, 5000);
}
reloadDetail=()=>{
if(this.state.firstSync||this.state.secondSync){
reloadDetail = () => {
if (this.state.firstSync || this.state.secondSync) {
window.location.reload();
}
}
deleteProjectBack = () => {
const { history } = this.props;
@ -364,22 +369,33 @@ class Detail extends Component {
axios.get(url).then((result) => {
if (result && result.data) {
if (result.data.status === 404) {
this.props.history.push('/nopage');
if (window.location.pathname.includes('/invite')) {
let inviteString = window.location.search && window.location.search.split('?invite=')[1];
let data = inviteString && JSON.parse(Base64.decode(inviteString));
const { project = {}, projectDetail = {} } = this.state
this.setState({
project: Object.assign(project, { author: { name: data.ownerName } }),
projectDetail: Object.assign(projectDetail, { name: data.projectName })
});
} else {
this.props.history.push('/nopage');
}
} else {
this.setState({
projectDetail: result.data,
project_id: result.data.project_id,
isManager: result.data.permission && (result.data.permission === "Manager" || result.data.permission === "Admin" || result.data.permission === "Owner"),
isReporter: result.data.permission && result.data.permission === "Reporter",
isDeveloper: result.data.permission && result.data.permission === "Developer",
http_url: result.data.clone_url,
praised: result.data.praised,
watched: result.data.watched,
watchers_count: result.data.watchers_count,
praises_count: result.data.praises_count,
forked_count: result.data.forked_count,
defaultBranch: result.data.default_branch
})
}
this.setState({
projectDetail: result.data,
project_id: result.data.project_id,
isManager: result.data.permission && (result.data.permission === "Manager" || result.data.permission === "Admin" || result.data.permission === "Owner"),
isReporter: result.data.permission && result.data.permission === "Reporter",
isDeveloper: result.data.permission && result.data.permission === "Developer",
http_url: result.data.clone_url,
praised: result.data.praised,
watched: result.data.watched,
watchers_count: result.data.watchers_count,
praises_count: result.data.praises_count,
forked_count: result.data.forked_count,
defaultBranch: result.data.default_branch
})
}
}).catch((error) => { })
}
@ -464,7 +480,7 @@ class Detail extends Component {
const url = `/${owner}/${projectsId}/forks.json`;
axios.post(url).then(result => {
if (result && result.data.status === 0) {
if(result.data.message === "fork失败你已拥有了这个项目"){
if (result.data.message === "fork失败你已拥有了这个项目") {
this.props.history.push(`/${current_user && current_user.login}/${projectsId}`);
return;
}
@ -490,7 +506,7 @@ class Detail extends Component {
axios.post(url).then(result => {
if (result && result.data && result.data.status === 0) {
this.setState({
secondSync:true
secondSync: true
})
this.canvasChannel(true);
} else {
@ -527,10 +543,9 @@ class Detail extends Component {
let pathname = checkPathname(projectsId, owner, url);
const { state } = this.props.history.location;
const common = {
getDetail: this.getDetail,
getBanner:this.getBanner,
getBanner: this.getBanner,
changeOpenDevops: this.changeOpenDevops,
defaultBranch
}
@ -549,7 +564,7 @@ class Detail extends Component {
<Link to={`/${owner}/${projectsId}`} className="projectN mt6">{projectDetail && projectDetail.name}</Link>
</div>
{projectDetail && projectDetail.private && <span className="privateTag mt6">私有</span>}
{ !platform && <span className="privateTag red mt6">只读</span> }
{!platform && <span className="privateTag red mt6">只读</span>}
</AlignTop>
<div className="mt8">
{
@ -718,7 +733,7 @@ class Detail extends Component {
(props) => (<Setting {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
{/*修改里程碑*/}
<Route path="/:owner/:projectsId/milestones/:meilid/edit"
render={
@ -758,18 +773,18 @@ class Detail extends Component {
{/* 修改详情 edit*/}
<Route path="/:owner/:projectsId/issues/:orderId/edit"
render={
(props) => (<OrderupdateDetail {...this.props} {...props} {...this.state} {...common} form_type={"edit"}/>)
(props) => (<OrderupdateDetail {...this.props} {...props} {...this.state} {...common} form_type={"edit"} />)
}
></Route>
{/* 复制详情 copyetail*/}
<Route path="/:owner/:projectsId/issues/:orderId/copyetail"
render={
(props) => (<OrderupdateDetail {...this.props} {...props} {...this.state} {...common} form_type={"copy"}/>)
(props) => (<OrderupdateDetail {...this.props} {...props} {...this.state} {...common} form_type={"copy"} />)
}
></Route>
{/* 任务详情 */}
<Route path="/:owner/:projectsId/issues/:orderId"
{/* 任务详情 */}
<Route path="/:owner/:projectsId/issues/:orderId"
render={
(props) => (<OrderDetail {...this.props} {...this.state} {...props} {...common} />)
}
@ -861,6 +876,12 @@ class Detail extends Component {
(props) => (<CoderDepot {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
{/* 邀请 */}
<Route path="/:owner/:projectsId/invite"
render={
(props) => (<Invite {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
<Route path="/:owner/:projectsId/:subIndex"
render={
(props) => (<CoderRootIndex {...this.props} {...props} {...this.state} {...common} />)

View File

@ -4,6 +4,7 @@ import AddMember from '../Component/AddMember';
import AddGroup from '../Component/AddGroup';
import Member from './CollaboratorMember';
import Group from './CollaboratorGroup';
import MemberByLink from './CollaboratorMemberByLink';
function Collaborator(props){
const [ nav , setNav] = useState("1");
@ -23,11 +24,10 @@ function Collaborator(props){
setNewGroupId(id);
}
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");setNewId(undefined)}}>协作者管理</span>
@ -35,22 +35,26 @@ function Collaborator(props){
</span>
:
<span className="font-18 text-black">协作者管理</span>
}
} */}
<span>
<span style={{cursor:"pointer"}} className={nav === "1" ? "font-15 text-black color-blue":"font-15 text-black"} onClick={()=>{setNav("1");setNewId(undefined)}}>协作者管理</span>
<span style={{cursor:"pointer"}} className={nav === "3" ? "font-15 text-black color-blue ml30":"font-15 text-black ml30"} onClick={()=>{setNav("3");}}>邀请成员</span>
{author && author.type === "Organization" && <span style={{cursor:"pointer"}} className={nav === "2" ? "font-15 text-black ml30 color-blue":"font-15 text-black ml30"} onClick={()=>{setNav("2");setNewId(undefined);setNewGroupId(undefined)}}>团队管理</span>}
</span>
{
nav === "1" &&
<AddMember getID={getID} login showNotification={props.showNotification}/>
}
{
(nav !== "1" && addOperation) &&
(nav === "2" && addOperation) &&
<AddGroup getGroupID={getGroupID} organizeId={owner}/>
}
</div>
<div>
{
nav === "1" ?
<Member newId={newId} flag={newIdFlag} projectsId={projectsId} owner={owner} project_id={props.project_id} author={props.projectDetail && props.projectDetail.author} showNotification={props.showNotification}/>
:
<Group setAddOperation={setAddOperation} owner={owner} projectsId={projectsId} newGroupId={newGroupId}/>
nav === "1" ? <Member newId={newId} flag={newIdFlag} projectsId={projectsId} owner={owner} project_id={props.project_id} author={props.projectDetail && props.projectDetail.author} showNotification={props.showNotification}/>
: nav === "2" ? <Group setAddOperation={setAddOperation} owner={owner} projectsId={projectsId} newGroupId={newGroupId}/>
: <MemberByLink newId={newId} flag={newIdFlag} projectsId={projectsId} owner={owner} project_id={props.project_id} author={props.projectDetail && props.projectDetail.author} showNotification={props.showNotification}/>
}
</div>
</WhiteBack>

View File

@ -0,0 +1,92 @@
import React, { useEffect, useState, useImperativeHandle, forwardRef } from 'react';
import { Select, Checkbox, Button, Input, Spin, Table, Tooltip, Pagination, Popconfirm } from "antd";
import axios from 'axios';
import { Base64 } from 'js-base64';
import NoneData from "../Nodata";
import { Link } from "react-router-dom";
import { getImageUrl } from "educoder";
const { Search } = Input;
const LIMIT = 15;
const optionList = [
{ value: "manager", name: "管理员 - 拥有仓库设置功能、代码库读、写操作权限" },
{ value: "developer", name: "开发人员 - 拥有代码库读、写操作权限" },
{ value: "reporter", name: "报告者 - 拥有代码库读操作权限" }
];
function CollaboratorMemberByLink({ projectsId, owner, project_id, author, showNotification, newId, flag }) {
const [role, setRole] = useState('developer');
const [is_apply, setIs_apply] = useState(true);
const [inviteUrl, setinviteUrl] = useState('');
const [copy, setCopy] = useState(false);
useEffect(() => {
const url = `/${owner}/${projectsId}/project_invite_links/current_link.json`;
axios.get(url, {
params: {
role,
is_apply
}
}).then(res => {
if (res && res.data) {
let params = {
projectName: res.data.project.name,
projectId: res.data.project.identifier,
// role: res.data.role,
ownerLogin: res.data.project.owner.login,
ownerName: res.data.project.owner.name,
// userName: res.data.user.name,
// userLogin: res.data.user.login,
sign:res.data.sign,
};
let urlParams = JSON.stringify(params);
let content = Base64.encode(urlParams);
setinviteUrl(`${window.location.origin}/${owner}/${projectsId}/invite?invite=${content}`);
setCopy(false);
}
}).catch(error => { })
}, [role, is_apply]);
function inviteClick() {
const copyEle = document.querySelector('#inviteUrl'); //
if (!copyEle) {
console.error("您的CopyTool未设置正确的inputId");
return;
}
copyEle.select(); //
if (document.execCommand('copy')) {
document.execCommand('copy');
setCopy(true);
}
}
return (
<div className='addMemByLinkBox'>
<div className='font-16 mt20 mb10'>请选择邀请用户权限</div>
<Select className='selectBox' defaultValue="developer" onChange={(v) => { setRole(v) }}>
{optionList.map(item => {
return <Select.Option value={item.value} key={item.value}>{item.name}</Select.Option>
})}
</Select>
<Checkbox checked={is_apply} className='font-15 checkBox' onChange={e => { setIs_apply(e.target.checked) }}>需要管理员审核</Checkbox>
<div className='font-16 mt25 mb10'>邀请链接</div>
<Input
id="inviteUrl"
value={inviteUrl}
readOnly
addonAfter={<Button type='primary' onClick={inviteClick}>{copy ? '复制成功' : '复制链接'}</Button>} className='linkBox'
/>
<div className='tipBox mt25'>
<div>: </div>
<div className='ml5'>
1管理员可通过分享邀请链接的方式邀请其他成员加入项目<br />
2若已勾选管理员审核选项用户接收邀请后管理员可在个人主页中待办事项窗口审核成员审核信息若不需要管理员审核成员接收邀请后将直接加入项目
</div>
</div>
</div>
)
}
export default forwardRef(CollaboratorMemberByLink);

View File

@ -57,7 +57,7 @@ class Index extends Component {
<li className={flag ? "active" : ""}>
<p>
<Link to={`/${owner}/${projectsId}/settings`} className="w-100">
<i className="iconfont icon-huabanfuben font-18 mr10"></i>
<i className="iconfont icon-huabanfuben font-15 mr10"></i>
</Link>
</p>
</li>
@ -68,7 +68,7 @@ class Index extends Component {
>
<p>
<Link to={`/${owner}/${projectsId}/settings/collaborators`} className="w-100">
<i className="iconfont icon-chengyuan font-18 mr10"></i>
<i className="iconfont icon-chengyuan font-15 mr10"></i>
协作者管理
</Link>
</p>
@ -80,7 +80,7 @@ class Index extends Component {
>
<p>
<Link to={`/${owner}/${projectsId}/settings/webhooks`} className="w-100">
<i className="iconfont icon-a-xuanzhongwebhookicon font-18 mr10 color-grey-9"></i>
<i className="iconfont icon-a-xuanzhongwebhookicon font-15 mr10 color-grey-9"></i>
网络钩子
</Link>
</p>
@ -92,7 +92,7 @@ class Index extends Component {
>
<p>
<Link to={`/${owner}/${projectsId}/settings/branches`} className="w-100">
<i className="iconfont icon-fenzhi font-20 mr10"></i>
<i className="iconfont icon-fenzhi font-15 mr10"></i>
分支设置
</Link>
</p>
@ -102,7 +102,7 @@ class Index extends Component {
>
<p>
<Link to={`/${owner}/${projectsId}/settings/labels`} className="w-100">
<i className="iconfont icon-xiangmubiaoqian font-18 mr10 color-grey-6"></i>
<i className="iconfont icon-xiangmubiaoqian font-15 mr10 color-grey-6"></i>
项目标记
</Link>
</p>

View File

@ -23,7 +23,7 @@
content: '';
}
.baseForm{
padding:15px 30px!important;
padding:15px 0!important;
}
.collaboratorList{
min-height: 400px;
@ -243,4 +243,31 @@
&>div:last-child{
border-bottom: none;
}
}
.addMemByLinkBox{
color:#202d40;
.selectBox {
width: 55%;
display: block;
margin-bottom: 18px;
}
.checkBox{
color:#151d40;
}
.tipBox{
background-color:rgba(199, 209, 255, 0.17);
color:#7e849e;
padding: 20px 150px 20px 25px;
display: flex;
}
.linkBox{
width: 55%;
.ant-input-group-addon{
padding: 0;
}
.ant-btn{
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
}

View File

@ -225,24 +225,24 @@ form{
margin-bottom: 12px;
border-radius:2px;
background-color: #fff;
// box-shadow: 0px 0px 2px rgba(0,0,0,0.2);
padding: 20px 0;
border:1px solid rgba(79, 108, 188, 0.21);
&>li{
font-size: 1rem;
padding:0px 0px 0px 20px;
font-size: 15px;
padding:0px 0px 0px 25px;
box-sizing: border-box;
color: #333;
position: relative;
& > p{
height: 62px;
line-height: 62px;
height: 49px;
width: 100%;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
padding-right: 20px;
margin:0px;
border-bottom: 1px solid #eee;
a{
width:100%;
color: #202d40;
@ -264,10 +264,9 @@ form{
& li.active::before{
position: absolute;
left: 0px;
top: 15px;
width: 6px;
content: '';
height: 33px;
height: 100%;
// background: #4CACFF;
background: $primary-color;
}

View File

@ -180,7 +180,7 @@ class InfosUser extends Component {
placeholder="输入项目名称关键字进行搜索"
enterButton="搜索"
size="large"
onSearch={this.get_projects}
onSearch={()=>{this.get_projects()}}
className="list-r-Search"
value={search}
onChange={this.changeSearchValue}