diff --git a/src/AppConfig.js b/src/AppConfig.js index b4107e46..f771b9ea 100644 --- a/src/AppConfig.js +++ b/src/AppConfig.js @@ -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'); + } } } diff --git a/src/common/UrlTool.js b/src/common/UrlTool.js index 53540e38..24512d67 100644 --- a/src/common/UrlTool.js +++ b/src/common/UrlTool.js @@ -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}` diff --git a/src/forge/Component/AddMember.jsx b/src/forge/Component/AddMember.jsx index f453be05..62b87cbd 100644 --- a/src/forge/Component/AddMember.jsx +++ b/src/forge/Component/AddMember.jsx @@ -80,7 +80,7 @@ function AddMember({getID,login,showNotification}){ { + 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 ( +
+ {data &&
} + width="548px" + closable={true} + onCancel={() => { setVisible(false); history.push(`/${current_user.login}`) }} + centered + okText={'接受'} + cancelText={'拒绝'} + onOk={accept} + maskClosable={false} + > + {detail.project && detail.project.owner.name}/{detail.project && detail.project.name} +
+ {detail.user && detail.user.name} 邀请您以{identify[detail.role]}的身份加入此代码库 + 是否接受邀请? +
+ } + + ) +} + + +export default Invite; \ No newline at end of file diff --git a/src/forge/Invite/index.scss b/src/forge/Invite/index.scss new file mode 100644 index 00000000..87912b65 --- /dev/null +++ b/src/forge/Invite/index.scss @@ -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; + } + } + } +} diff --git a/src/forge/Main/Detail.js b/src/forge/Main/Detail.js index b20b98be..b6b90b9b 100644 --- a/src/forge/Main/Detail.js +++ b/src/forge/Main/Detail.js @@ -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, +}); /** * permission:Manager:管理员,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 { {projectDetail && projectDetail.name} {projectDetail && projectDetail.private && 私有} - { !platform && 只读 } + {!platform && 只读}
{ @@ -718,7 +733,7 @@ class Detail extends Component { (props) => () } > - + {/*修改里程碑*/} () + (props) => () } > {/* 复制详情 copyetail*/} () + (props) => () } > - - {/* 任务详情 */} - () } @@ -861,6 +876,12 @@ class Detail extends Component { (props) => () } > + {/* 邀请 */} + () + } + > () diff --git a/src/forge/Settings/Collaborator.js b/src/forge/Settings/Collaborator.js index cca2520a..d22862aa 100644 --- a/src/forge/Settings/Collaborator.js +++ b/src/forge/Settings/Collaborator.js @@ -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 (
- { + {/* { author && author.type === "Organization" ? {setNav("1");setNewId(undefined)}}>协作者管理 @@ -35,22 +35,26 @@ function Collaborator(props){ : 协作者管理 - } + } */} + + {setNav("1");setNewId(undefined)}}>协作者管理 + {setNav("3");}}>邀请成员 + {author && author.type === "Organization" && {setNav("2");setNewId(undefined);setNewGroupId(undefined)}}>团队管理} + { nav === "1" && } { - (nav !== "1" && addOperation) && + (nav === "2" && addOperation) && }
{ - nav === "1" ? - - : - + nav === "1" ? + : nav === "2" ? + : }
diff --git a/src/forge/Settings/CollaboratorMemberByLink.jsx b/src/forge/Settings/CollaboratorMemberByLink.jsx new file mode 100644 index 00000000..ea34ec8f --- /dev/null +++ b/src/forge/Settings/CollaboratorMemberByLink.jsx @@ -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 ( +
+
请选择邀请用户权限
+ + + { setIs_apply(e.target.checked) }}>需要管理员审核 + +
邀请链接
+ {copy ? '复制成功' : '复制链接'}} className='linkBox' + /> +
+
注:
+
+ 1、管理员可通过分享邀请链接的方式,邀请其他成员加入项目
+ 2、若已勾选管理员审核选项,用户接收邀请后管理员可在个人主页中“待办事项”窗口审核成员审核信息,若不需要管理员审核,成员接收邀请后,将直接加入项目 +
+
+
+ ) +} +export default forwardRef(CollaboratorMemberByLink); \ No newline at end of file diff --git a/src/forge/Settings/Index.js b/src/forge/Settings/Index.js index a22f55ab..6efc9338 100644 --- a/src/forge/Settings/Index.js +++ b/src/forge/Settings/Index.js @@ -57,7 +57,7 @@ class Index extends Component {
  • - 基本设置 + 基本设置

  • @@ -68,7 +68,7 @@ class Index extends Component { >

    - + 协作者管理

    @@ -80,7 +80,7 @@ class Index extends Component { >

    - + 网络钩子

    @@ -92,7 +92,7 @@ class Index extends Component { >

    - + 分支设置

    @@ -102,7 +102,7 @@ class Index extends Component { >

    - + 项目标记

    diff --git a/src/forge/Settings/setting.scss b/src/forge/Settings/setting.scss index 27a9f063..74d3edf4 100644 --- a/src/forge/Settings/setting.scss +++ b/src/forge/Settings/setting.scss @@ -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; + } + } } \ No newline at end of file diff --git a/src/forge/css/index.scss b/src/forge/css/index.scss index 49ab596b..9bcd0e24 100644 --- a/src/forge/css/index.scss +++ b/src/forge/css/index.scss @@ -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; } diff --git a/src/forge/users/InfosUser.js b/src/forge/users/InfosUser.js index 4a5c7d9b..072b272c 100644 --- a/src/forge/users/InfosUser.js +++ b/src/forge/users/InfosUser.js @@ -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}