Merge pull request '修改头像、组织添加成员等功能' (#333) from caishi/forgeplus-react:gitlink_server into gitlink_server

This commit is contained in:
xxq250 2022-01-07 16:57:21 +08:00
commit 7416d9bcb6
21 changed files with 510 additions and 75 deletions

13
package-lock.json generated
View File

@ -3934,6 +3934,11 @@
"warning": "^4.0.3"
}
},
"cropperjs": {
"version": "1.5.12",
"resolved": "https://registry.npmmirror.com/cropperjs/download/cropperjs-1.5.12.tgz",
"integrity": "sha1-2cDbK/uMDXadUXOej5FrvEThD1A="
},
"cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
@ -14227,6 +14232,14 @@
"countup.js": "^2.0.8"
}
},
"react-cropper": {
"version": "2.1.8",
"resolved": "https://registry.npmmirror.com/react-cropper/download/react-cropper-2.1.8.tgz?cache=0&sync_timestamp=1634379691101&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Freact-cropper%2Fdownload%2Freact-cropper-2.1.8.tgz",
"integrity": "sha1-vzWn3mV2n4rTV+iuiE55H+P+khI=",
"requires": {
"cropperjs": "^1.5.12"
}
},
"react-datepicker": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-2.14.1.tgz",

View File

@ -85,6 +85,7 @@
"react-content-loader": "^3.1.1",
"react-cookies": "^0.1.1",
"react-countup": "^6.1.0",
"react-cropper": "^2.1.8",
"react-datepicker": "^2.14.1",
"react-dev-utils": "^9.2.0-next.80",
"react-dom": "^16.13.1",

View File

@ -7,6 +7,8 @@ export const Banner = styled.div`{
border-bottom:1px solid #eee;
background-color:#fff;
border-radius:5px 5px 0px 0px;
justify-content: space-between;
display: flex;
}`
export const AlignCenterBetween = styled.div`{
display:flex;

View File

@ -42,8 +42,8 @@ class NewHeader extends Component {
setevaluatinghides: false,
occupation: 0,
mydisplay: false,
headtypesonClickbool: false,
headtypess: "/",
// headtypesonClickbool: false,
// headtypess: "/",
settings: null,
visiblemyss: false,
openSearch:false,
@ -137,12 +137,12 @@ class NewHeader extends Component {
AccountProfiletype: false
})
};
headtypesonClick = (url, bool) => {
this.setState({
headtypess: url,
headtypesonClickbool: bool,
})
}
// headtypesonClick = (url, bool) => {
// this.setState({
// headtypess: url,
// headtypesonClickbool: bool,
// })
// }
//获取数据为空的时候
gettablogourlnull = () => {
this.setState({
@ -295,12 +295,14 @@ class NewHeader extends Component {
{...this.props}
{...this.state}
/> : ""}
<div style={{width:"78px"}}>
{
publicNav &&
<a href={'https://www.ccf.org.cn/'} className={"fl pr15"}>
<a href={'https://www.ccf.org.cn/'} className={"fl"}>
<img src={MainLogo} alt="ccf" />
</a>
}
</div>
{
settings && settings.nav_logo_url ?
<a href={settings && settings.new_course.default_url} className={"fl mr50"}>
@ -316,26 +318,26 @@ class NewHeader extends Component {
{
settings.navbar && settings.navbar.map((item, key) => {
var new_link = item.link;
var user_login = current_user && current_user.login;
var is_hidden = item.hidden
if (new_link && (new_link.indexOf("courses") > -1 || new_link.indexOf("contests") > -1)) {
if (user_login) {
if (new_link.indexOf("courses") > -1) {
new_link = new_link.replace(/courses/g, user_login + "/courses")
} else if (new_link.indexOf("contests") > -1) {
new_link = new_link.replace(/contests/g, user_login + "/contests")
}
} else {
is_hidden = true
}
}
if (user_login && (new_link && new_link.indexOf("homes") > -1)) {
new_link = new_link.replace(/homes/g, user_login + "/user_activities")
}
// var user_login = current_user && current_user.login;
var is_hidden = item.hidden;
// if (new_link && (new_link.indexOf("courses") > -1 || new_link.indexOf("contests") > -1)) {
// if (user_login) {
// if (new_link.indexOf("courses") > -1) {
// new_link = new_link.replace(/courses/g, user_login + "/courses")
// } else if (new_link.indexOf("contests") > -1) {
// new_link = new_link.replace(/contests/g, user_login + "/contests")
// }
// } else {
// is_hidden = true
// }
// }
// if (user_login && (new_link && new_link.indexOf("homes") > -1)) {
// new_link = new_link.replace(/homes/g, user_login + "/user_activities")
// }
var waiLian = (new_link && str.filter(item=>new_link.indexOf(item)>-1) );
var wl = waiLian && waiLian.length>0;
return (
<li key={key} onClick={() => this.headtypesonClick(item.link, true)} className={`${this.matchpaths(new_link) === true ? 'pr active' : 'pr'}`} style={!is_hidden ? { display: 'flex' } : { display: 'none' }}>
<li key={key} className={`${this.matchpaths(new_link) === true ? 'pr active' : 'pr'}`} style={{display:!is_hidden ? 'flex' : 'none'} }>
<a href={new_link} target={wl ? "_self":"_blank"}>{item.name}</a>
</li>
)
@ -350,7 +352,6 @@ class NewHeader extends Component {
{
current_user && (current_user.main_site || current_user.login) && (settings && settings.add && settings.add.length>0)?
<Dropdown overlay={this.addMenu(settings && settings.add)} placement="bottomRight">
{/* <i className="iconfont icon-tianjiafangda ml30 mr15"></i> */}
<img src={require(`./img/add.png`)} alt="" width="16px" className="mr15 ml30"/>
</Dropdown>:""
}

View File

@ -36,8 +36,9 @@ const Infos = styled.div`
}
& > .f-wrap-between {
padding: 14px 20px 14px 16px;
border-radius: 3px 3px 0px 0px;
border-radius: 0px 0px 3px 3px;
border: 1px solid #D0D0D0;
border-top: none;
.df{
align-items: center;
& .underline:hover{

View File

@ -321,7 +321,7 @@ class MilepostDetail extends Component {
</div>
{
search_count > limit?
<div className="mt30 pb30 edu-txt-center">
<div className="pt30 pb30 edu-txt-center" style={{borderTop:"1px solid #eee"}}>
<Pagination simple current={page} total={search_count} pageSize={limit} onChange={this.ChangePage}></Pagination>
</div>:""
}

View File

@ -232,6 +232,9 @@
border-bottom: 1px solid #eee;
padding: 16px 0px 16px 20px;
}
.issueItem:last-child{
border-bottom: none;
}
.issueNo {
padding: 0px 5px;
border-radius: 4px;

View File

@ -6,7 +6,7 @@ import moment from 'moment';
import NoneData from "../Nodata";
import OrderItem from "./OrderItem";
import CheckProfile from '../Component/ProfileModal/Profile';
import cookie from 'react-cookies';
import axios from "axios";
@ -76,6 +76,14 @@ class order extends Component {
}
componentDidMount = () => {
// const selectParams = cookie.load('selectParams');
// let states = selectParams.select_params;
// this.setState({
// ...states
// },()=>{
// this.getSelectList();
// this.getIssueList('1');
// })
this.getSelectList();
this.getIssueList('1');
};
@ -102,11 +110,11 @@ class order extends Component {
// 获取列表数据
getIssueList = (status_type, begin, end) => {
cookie.remove('selectParams');
this.setState({
isSpin: true
})
const { select_params } = this.state;
console.log(select_params);
const { projectsId, owner } = this.props.match.params;
const url = `/${owner }/${projectsId}/issues.json`;
axios
@ -120,7 +128,9 @@ class order extends Component {
})
.then((result) => {
if (result) {
const issues = result.data.issues
const issues = result.data.issues;
// let inFifteenMinutes = new Date(new Date().getTime() + 24 * 3600 * 1000);
// cookie.save('selectParams', {states:this.state},{ expires: inFifteenMinutes,path:`/${owner}/${projectsId}/issues` });
this.setState({
data: result.data,
issues: issues,
@ -193,11 +203,12 @@ class order extends Component {
select_params,
author_id,
assigned_to_id
});
},()=>{
if (!toGet) {
const { status_type } = this.state;
this.getIssueList(status_type);
}
});
};
renderMenu = (array, name, id, toGet) => {
@ -852,7 +863,7 @@ class order extends Component {
)}
{
search_count > select_params.limit ?
<div className="mt30 mb30 edu-txt-center">
<div className="pt30 mb30 edu-txt-center" style={{borderTop:"1px solid #eee"}}>
<Pagination
simple
defaultCurrent={select_params.page}

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import { Route, Switch } from "react-router-dom";
import { withRouter } from "react-router";
@ -11,6 +11,7 @@ import Loading from "../../Loading";
import { Box , Gap , LongWidth } from '../Component/layout';
import { getImageUrl } from 'educoder';
import { Link } from 'react-router-dom';
import Avatar from '../users/Avatar/Index';
import './Index.scss';
import { useEffect } from "react";
@ -52,18 +53,38 @@ function Index(props){
const { pathname } = props.location;
const notice_url = mygetHelmetapi && mygetHelmetapi.common && mygetHelmetapi.common.notice;
const [ avatarVisible , setAvatarVisible ] = useState(false);
useEffect(()=>{
if(checkIfLogin() === false){
props.history.push('/login');
}
},[])
function onCancelAvatar(){
setAvatarVisible(false);
const { resetUserInfo } = props;
resetUserInfo && resetUserInfo();
}
return(
<div className="newMain clearfix whiteBack">
{
avatarVisible &&
<Avatar
onCancel={onCancelAvatar}
avatarImg={getImageUrl(`/${current_user && current_user.image_url}`)}
login={current_user && current_user.login}
/>
}
<div className="boies">
<Box>
<div className="shortW">
<div className="userDetail">
<div className="userHeadPhoto">
<img src={getImageUrl(`/${current_user && current_user.image_url}`)} alt=""/>
<span className="userUpdateBox"onClick={()=>setAvatarVisible(true)}>修改头像</span>
</div>
<span>{current_user && current_user.username}</span>
</div>
<ul className="securityUl ul-border-buttom">

View File

@ -194,3 +194,30 @@
.blue-Purple{
color: #466AFF !important;
}
.userHeadPhoto{
width: 48px;
position: relative;
margin:0px auto;
.userUpdateBox{
font-size: 11px!important;
width: 100%;
height: 100%;
line-height: 48px;
text-align: center;
position: absolute;
left: 0px;
top:0px;
background-color: rgba(0, 0, 0, 0.2);
display: none!important;
color: white!important;
transition: 1s;
border-radius: 50%;
cursor: pointer;
}
&:hover{
.userUpdateBox{
display: block!important;
}
}
}

View File

@ -76,10 +76,8 @@ function Index(props) {
subTitle={`删除后未来事件将不会推送至此Webhook地址${url}`}
/>
<Banner>
<FlexAJ>
<span>Webhooks(网络钩子)</span>
<Button type="primary" size="large" onClick={addFunc}>添加Webhook</Button>
</FlexAJ>
</Banner>
<div className="hookpanel">
<p className="color-grey-3">每当特定事件如push代码合并请求被编辑发生时我们将通过webhook给您提供的远程URL发送post请求您可以在我们的<a className="color-blue hoverLine" target="_blank" href="https://forum.trustie.net/forums/3408/detail">webhooks指南</a>中了解更多信息</p>

View File

@ -287,10 +287,10 @@ function New({ form , match , showNotification , history }) {
<Checkbox value="pull_request_label">合并请求标签</Checkbox>
<span>合并请求的标签被更新或清除</span>
</span> */}
<span>
{/* <span>
<Checkbox value="pull_request_review">合并请求审查</Checkbox>
<span>合并请求被批准拒绝或提出审查意见审查人员的修改审查线程已解决或未解决</span>
</span>
</span> */}
{/* <span>
<Checkbox value="pull_request_sync">合并请求被同步</Checkbox>
<span>合并请求被同步</span>

View File

@ -0,0 +1,69 @@
import React ,{ useState , useEffect } from 'react';
import { Modal , Spin } from 'antd';
import axios from 'axios';
function AddMemberBox({className,orzId,history,OIdentifier}){
const [ visible , setVisible ] = useState(false);
const [ isSpin , setIsSpin ] = useState(true);
const [ list , setList ] = useState(undefined);
useEffect(()=>{
if(visible && orzId){
InitData();
}
},[orzId,visible])
function InitData(){
const url = `/organizations/${orzId}/teams.json`;
axios.get(url,{
params:{
is_full:true
}
}).then(result=>{
if(result){
setList(result.data.teams);
setIsSpin(false);
}
}).catch(error=>{})
}
function chooseGroup(id){
history.push(`/${OIdentifier}/teams/${id}/setting/member`);
}
return(
<div>
<Modal
visible={visible}
width="600px"
centered
title="添加成员"
onCancel={()=>setVisible(false)}
footer={null}
className="addMemberBody"
>
<div>
<p className="font-16 pt40 pb20 edu-txt-center">请选择想要添加成员的组织团队</p>
<Spin spinning={isSpin}>
<div className="addForGroupList">
{
list && list.length > 0 ?
<ul>
{
list.map((i,k)=>{
return(
<li onClick={()=>chooseGroup(i.id)}>{i.nickname}</li>
)
})
}
</ul>
:""
}
</div>
</Spin>
</div>
</Modal>
<a className={className} onClick={()=>setVisible(true)}>+ 添加成员</a>
</div>
)
}
export default AddMemberBox;

View File

@ -370,3 +370,47 @@
.hide{
display: none;
}
.addMemberBtn{
display: flex;
height: 34px;
line-height: 32px;
padding:0px 14px;
border-radius: 3px;
border:1px solid #d0d0d0;
font-size: 14px;
&:hover{
border-color: #466AFF;
}
}
.addMemberBody{
.ant-modal-body{
padding:0px;
}
.addForGroupList{
padding:20px 40px;
max-height: 315px;
overflow-y: auto;
ul{
display: flex;
flex-wrap: wrap;
width: 450px;
margin:0px auto;
}
li{
cursor: pointer;
border:1px solid #d0d0d0;
border-radius:4px;
margin:0px 10px 15px 10px!important;
height: 40px;
line-height: 40px;
width: 130px;
text-align: center;
&.active,&:hover{
color: #fff;
background-color: rgba(65, 84, 241, 1);
}
}
}
}

View File

@ -7,6 +7,7 @@ import styled from 'styled-components';
import { getImageUrl } from 'educoder';
import axios from 'axios';
import { Link } from 'react-router-dom';
import AddMemberBox from '../Component/AddMemberBox';
const Img = styled.img`{
width:30px;
@ -14,7 +15,8 @@ const Img = styled.img`{
border-radius:50%;
}`
const limit = 15;
export default (({organizeDetail})=>{
export default (({organizeDetail,history,match})=>{
const OIdentifier = match.params.OIdentifier;
const [ page , setPage ] = useState(1);
const [ total , setTotal ] = useState(0);
const [ search , setSearch ] = useState(undefined);
@ -116,6 +118,12 @@ export default (({organizeDetail})=>{
<SearchUser getUser={getUser}/>
<Blueline className="ml30">+&nbsp;添加用户</Blueline>
</AlignCenter> */}
<AddMemberBox
className="addMemberBtn"
orzId={organizeDetail && organizeDetail.id}
history={history}
OIdentifier={OIdentifier}
/>
</Title>
<FlexAJ className="padding20-30">
<div style={{width:"580px"}}>

View File

@ -4,9 +4,11 @@ import Cards from '../Component/MemberCards';
import axios from 'axios';
import Nodata from '../Nodata';
import { Pagination , Spin } from 'antd';
import AddMemberBox from './Component/AddMemberBox';
const limit = 15;
function TeamMember({organizeDetail,current_user}){
function TeamMember({organizeDetail,current_user,history,match}){
const OIdentifier = match.params.OIdentifier;
const [ page , setPage ] = useState(1);
const [ total , setTotal ] = useState(0);
const [ isSpin , setIsSpin ] = useState(true);
@ -34,7 +36,17 @@ function TeamMember({organizeDetail,current_user}){
return(
<WhiteBack style={{marginBottom:"30px",border:'1px solid #eee'}}>
<Banner>组织成员</Banner>
<Banner>组织成员
{
organizeDetail && organizeDetail.is_admin &&
<AddMemberBox
className="addMemberBtn"
orzId={organizeDetail && organizeDetail.id}
history={history}
OIdentifier={OIdentifier}
/>
}
</Banner>
<Spin spinning={isSpin}>
<div style={{minHeight:"400px"}}>
{

View File

@ -0,0 +1,102 @@
import React ,{useEffect, useRef, useState} from 'react';
import './Index.scss';
import { Modal , message } from 'antd';
import Cropper from 'react-cropper';
import 'cropperjs/dist/cropper.css';
import axios from 'axios';
function Index({onCancel,avatarImg,login}){
const [ avatarPhoto , setAvatarPhoto ] = useState(avatarImg);
useEffect(()=>{
if(avatarImg){
setAvatarPhoto(avatarImg);
}
},[avatarImg])
const cropper = useRef();
const saveAvatar = async () => {
const imgUrl = cropper.current.cropper.getCroppedCanvas().toDataURL("image/jpeg");
if (!imgUrl) {
message.info('请先上传图片');
}
const url = `/users/${login}/update_image.json`;
axios.put(url,{
image:imgUrl
}).then(result=>{
if(result){
message.success("头像修改成功!");
onCancel(true);
}
}).catch(error=>{})
}
function onChange(e){
let files;
if (e.dataTransfer) {
files = e.dataTransfer.files;
} else if (e.target) {
files = e.target.files;
}
if (!files || (files && files.length===0)) {
return;
}
const file = files[0];
if (!/^image\/\w+/.test(file.type)) {
message.info('请选择一个图片格式的文件');
return;
}
if (file.size > 2 * 1024 * 1024) {
message.info('仅支持文件大小小于2M的文件');
return;
}
const reader = new FileReader();
reader.onload = () => {
setAvatarPhoto(reader.result);
};
reader.readAsDataURL(files[0]);
}
return(
<Modal
visible={true}
width="638px"
footer={null}
centered
title="修改头像"
onCancel={()=>onCancel(false)}
className="avatarBox"
>
<div className="avatarDiv">
<div>
<Cropper
style={{ height: 320, width: 320 }}
src={avatarPhoto}
guides={false}
preview="#updateAvatarImg"
ref={cropper}
aspectRatio={1}
/>
{/* <span className={"tips"}>仅支持JPG、GIF、PNG且文件小于2M</span> */}
</div>
<div className="previewBox">
<div className={"previewImg"} id="updateAvatarImg"></div>
<div className="uploadBtn">
<label className={"uploadButton"} id="uploadBtn" htmlFor="inputImage">
<input type="file" className="sr-only" id="inputImage" name="file" accept="image/*" style={{ display: "none" }} onChange={onChange}></input>
点击上传
</label>
<a onClick={saveAvatar}>保存头像</a>
</div>
</div>
</div>
</Modal>
)
}
export default Index;

View File

@ -0,0 +1,66 @@
.avatarBox{
position: relative;
.ant-modal-header{
background-color: rgba(242, 242, 255, 1);
.ant-modal-title{
text-align: left;
}
}
.ant-modal-body{
position: relative;
}
.avatarDiv{
display: flex;
.previewBox{
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
margin-left: 30px;
.uploadBtn{
margin-bottom: 30px;
display: flex;
a,label{
cursor: pointer;
display: block;
height: 32px;
line-height: 30px;
margin:0px 10px;
width: 100px;
border:1px solid rgba(208, 208, 208, 1);
background-color: white;
border-radius:4px;
text-align: center;
color:#666666;
span{
display: block;
.ant-upload.ant-upload-select {
width: 100%;
height: 32px;
}
.ant-upload-list{
display: none;
}
}
}
a{
background-color: rgba(65, 84, 241, 1);
color: white;
border-color: rgba(65, 84, 241, 1);
&:hover{
color: white!important;
}
}
}
}
.previewImg{
overflow: hidden;
background-color: #fff;
border-radius: 50%;
text-align: center;
width: 100px!important;
height: 100px!important;
}
}
}

View File

@ -137,7 +137,6 @@
.infoBox span {
margin-left: 10px;
}
.headimg {
position: relative;
display: block;
@ -150,8 +149,7 @@
.headimg span {
position: absolute;
bottom: -6px;
right: 0px;
left: 65px;
right: -16px;
}
.headimg span i {
font-size: 25px !important;

View File

@ -133,6 +133,32 @@ $flex:flex;
margin-left:10px ;
}
}
.headimg-div{
width: 110px;
height: 110px;
margin:0px auto;
position: relative;
.updateAvatar{
cursor: pointer;
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
content: "";
left: 0px;
top:0px;
background-color: rgba(0,0,0,0.2);
display: none;
align-items: center;
justify-content: center;
color: white;
transition: 1s;
}
&:hover .updateAvatar{
display: flex;
}
}
.headimg{
position: relative;
display: block;
@ -145,7 +171,7 @@ $flex:flex;
position: absolute;
bottom: -6px;
right: 0px;
left: 65px;
z-index: 11;
i{
font-size: 25px!important;
border-radius: 50%;

View File

@ -1,11 +1,12 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Button, Spin , Menu } from "antd";
import { Spin , Menu } from "antd";
import FocusButton from "../UsersList/focus_button";
import axios from "axios";
import { getImageUrl } from "educoder";
import { Route, Switch } from "react-router-dom";
import Avatar from './Avatar/Index';
import "./new_user.css";
import "../css/index.scss";
@ -14,10 +15,10 @@ import './Index.scss';
import Loadable from "react-loadable";
import Loading from "../../Loading";
const UpdateInfo = Loadable({
loader: () => import("./Material/Index"),
loading: Loading,
});
// const UpdateInfo = Loadable({
// loader: () => import("./Material/Index"),
// loading: Loading,
// });
const InfosDevOps = Loadable({
loader: () => import("./devOpsCI"),
loading: Loading,
@ -66,7 +67,8 @@ class Infos extends Component {
project_type: undefined,
route_type: undefined,
undo_events:0,
menuKey:"0"
menuKey:"0",
avatarVisible:false
};
}
@ -193,17 +195,42 @@ class Infos extends Component {
})
}
onCancelAvatar=(reset)=>{
this.setState({
avatarVisible:false
})
if(reset){
const { menuKey } = this.state;
if(menuKey === "2" || menuKey === "3"){
window.location.reload();
}else{
this.fetchUser();
const { resetUserInfo } = this.props;
resetUserInfo && resetUserInfo();
}
}
}
render() {
const { current_user } = this.props;
const { username } = this.props.match.params;
const { user, isSpin, route_type , undo_events , menuKey } = this.state;
const { user, isSpin, route_type , undo_events , menuKey , avatarVisible } = this.state;
return (
<div className="newMain clearfix">
{
avatarVisible &&
<Avatar
onCancel={this.onCancelAvatar}
avatarImg={getImageUrl(`/${user && user.image_url}`)}
login={current_user && current_user.login}
/>
}
<Spin spinning={isSpin}>
<div className="new-content-flex">
<div className="list-left" style={{border:"none"}}>
<div className="bgcF">
<div className="list-l-Menu text-center" style={{padding:"20px 25px"}}>
<div className="headimg-div">
<span className="headimg">
<img src={getImageUrl(`/${user && user.image_url}`)} alt=""/>
<span>
@ -215,7 +242,12 @@ class Infos extends Component {
}
</span>
</span>
{
current_user && current_user.login && current_user.login === username ?
<span className="updateAvatar" onClick={()=>{this.setState({avatarVisible:true})}}>修改头像</span>
:""
}
</div>
<div className="text-center mt15 font-24 task-hide" title={user && user.username}>
{user && user.username}
</div>