forked from Gitlink/forgeplus-react
Merge pull request '区块确权' (#515) from durian/forgeplus-react:gitlink_server_issue into gitlink_server_issue
This commit is contained in:
commit
12d41b5e70
|
@ -61,6 +61,11 @@ function Contributors({owner,projectsId,currentLogin}){
|
|||
return str && str.substr(0,str.length - 1);
|
||||
}
|
||||
|
||||
function getBlockchain(userLogin){
|
||||
const user = list.filter(item =>{ return item.login === userLogin});
|
||||
return user[0] && user[0].contribution_perc;
|
||||
}
|
||||
|
||||
function setMenusFunc(data){
|
||||
if(data){
|
||||
let ele = (
|
||||
|
@ -94,6 +99,10 @@ function Contributors({owner,projectsId,currentLogin}){
|
|||
<span>{data.following_count}</span>
|
||||
<span>关注数</span>
|
||||
</Link>
|
||||
{getBlockchain(data.login) && <a>
|
||||
<span>{getBlockchain(data.login)}</span>
|
||||
<span>贡献度</span>
|
||||
</a>}
|
||||
</AlignCenter>
|
||||
<div className={"pb20"} style={{display:"flex",justifyContent:'center'}}>
|
||||
{
|
||||
|
|
|
@ -187,8 +187,8 @@ function IssueCommentList(props){
|
|||
<div>
|
||||
<Link to={`/${i.user.login}`}><img src={getImageUrl(i.user.image_url)} alt="" className='commentUserImg mr8'/></Link>
|
||||
<Link to={`/${i.user.login}`}>{i.user.name}</Link>
|
||||
<span className='ml5 timeAgo mr3'>回复</span>
|
||||
<span>{i.reply_user.name}</span>
|
||||
{i.reply_user && <span className='ml5 timeAgo mr3'>回复</span>}
|
||||
<span>{i.reply_user && i.reply_user.name}</span>
|
||||
<span className='ml15 timeAgo font-15'>{timeAgo(i.created_at)}</span>
|
||||
</div>
|
||||
{login && <div>
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
justify-content: space-between;
|
||||
}
|
||||
.commentRenderHtml.markdown-body p{
|
||||
font-size: 13px !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.commentReply{
|
||||
padding: 15px 0 0 20px;
|
||||
|
|
|
@ -85,6 +85,7 @@ function Details(props){
|
|||
const url = `/v1/${owner}/${projectsId}/issues/${index}`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
console.log('result', result);
|
||||
let data = result.data;
|
||||
setDetails(data);
|
||||
|
||||
|
|
|
@ -8,14 +8,25 @@ import { getImageUrl } from 'educoder';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
function Contribute(props){
|
||||
console.log('props', props);
|
||||
const [ list , setList ] = useState(undefined);
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ total , setTotal ] = useState(0);
|
||||
const [ isSpin , setIsSpin ] = useState(true);
|
||||
|
||||
const {project} = props;
|
||||
const owner = props.match.params.owner;
|
||||
const projectsId = props.match.params.projectsId;
|
||||
const LIMIT = 20;
|
||||
|
||||
useEffect(()=>{
|
||||
if(project){
|
||||
// 更改网页标题
|
||||
const {name, author} = project;
|
||||
document.title = `贡献者列表-${author.name}/${name}`;
|
||||
}
|
||||
}, [project])
|
||||
|
||||
useEffect(()=>{
|
||||
if(owner && projectsId){
|
||||
getData();
|
||||
|
@ -49,11 +60,11 @@ function Contribute(props){
|
|||
{
|
||||
list.map((item,key)=>{
|
||||
return(
|
||||
<AlignCenter>
|
||||
<AlignCenter className='mb15'>
|
||||
<img alt="" style={{borderRadius:"50%",marginRight:"10px"}} src={getImageUrl(`/${item.image_url}`)} width="50px" height="50px"/>
|
||||
<div>
|
||||
<Link to={`/${item.login}`} className="font-16">{item.name}</Link>
|
||||
<p className="font-12 color-grey-9">提交{item.contributions}次</p>
|
||||
<p className="font-12 color-grey-9">提交{item.contributions}次{item.contribution_perc ? ` I 贡献占比${item.contribution_perc}` : ''}</p>
|
||||
</div>
|
||||
</AlignCenter>
|
||||
)
|
||||
|
|
|
@ -51,6 +51,9 @@ class Index extends Component {
|
|||
languageFlag:false,
|
||||
ignoreFlag:false,
|
||||
licenseFlag:false,
|
||||
|
||||
// 区块链确权
|
||||
tokenConTri: false
|
||||
}
|
||||
}
|
||||
componentDidMount = () => {
|
||||
|
@ -200,14 +203,13 @@ class Index extends Component {
|
|||
isSpin: true
|
||||
})
|
||||
const { projectsType } = this.props.match.params;
|
||||
const {
|
||||
project_language_id, project_category_id, license_id, ignore_id , owners_id ,
|
||||
ignoreFlag,licenseFlag,categoreFlag,languageFlag
|
||||
} = this.state;
|
||||
const { project_language_id, project_category_id, license_id, ignore_id, owners_id, ignoreFlag, licenseFlag, categoreFlag, languageFlag, tokenConTri} = this.state;
|
||||
const decoderPass = Base64.encode(values.password);
|
||||
const url = (projectsType && projectsType === "mirror") ? "/projects/migrate.json" : "/projects.json";
|
||||
// 新建项目的时候,暂存数据,如果失败,返回的时候可以重新赋值
|
||||
sessionStorage.newProjectValue=JSON.stringify({...values,project_language_id,project_category_id,license_id,ignore_id});
|
||||
// 贡献值激励确权机制
|
||||
const {blockchain_init} = values;
|
||||
axios.post(url, {
|
||||
...values,
|
||||
auth_password:decoderPass,
|
||||
|
@ -215,7 +217,10 @@ class Index extends Component {
|
|||
project_category_id:categoreFlag ? project_category_id : undefined,
|
||||
license_id:licenseFlag ? license_id : undefined,
|
||||
ignore_id:ignoreFlag ? ignore_id : undefined,
|
||||
user_id:owners_id
|
||||
user_id:owners_id,
|
||||
blockchain: tokenConTri,
|
||||
blockchain_token_all: 10000,
|
||||
blockchain_init_token: blockchain_init && blockchain_init/100
|
||||
}).then((result) => {
|
||||
if (result && result.data.id) {
|
||||
projectsType && projectsType !== "mirror" && this.props.showNotification(`项目创建成功!`);
|
||||
|
@ -343,6 +348,7 @@ class Index extends Component {
|
|||
const { getFieldDecorator } = this.props.form;
|
||||
// 项目类型:deposit-托管项目,mirror-镜像项目
|
||||
const { projectsType } = this.props.match.params;
|
||||
const {current_user:{open_blockchain}} = this.props;
|
||||
|
||||
const {
|
||||
CategoryList,
|
||||
|
@ -365,7 +371,8 @@ class Index extends Component {
|
|||
ignoreFlag,
|
||||
licenseFlag,
|
||||
languageFlag,
|
||||
categoreFlag
|
||||
categoreFlag,
|
||||
tokenConTri
|
||||
} = this.state;
|
||||
return (
|
||||
<div className="main back-white" style={{padding:"0px",border:"none"}}>
|
||||
|
@ -630,6 +637,32 @@ class Index extends Component {
|
|||
)}
|
||||
</Form.Item>
|
||||
}
|
||||
|
||||
{/* 确权 仅在新建项目时 */}
|
||||
{open_blockchain && projectsType === "deposit" && <div className='mt10 mb10'>
|
||||
<p className='font-16'>确权</p>
|
||||
<div className='mt5 mb5 color-grey3'><Checkbox checked={tokenConTri} onChange={(e)=>{this.setState({tokenConTri:e.target.checked})}}>加入贡献值激励确权机制</Checkbox></div>
|
||||
<Form.Item
|
||||
className="flex1"
|
||||
extra="Token指本项目的虚拟货币,决定用户在本仓库贡献度的比例,可以通过issue悬赏或在个人主页贡献确权页进行转移。创建项目时,每个项目的Token总量为10000,您可以分配您可获取的Token数量(未分配的Token将保留至项目Token余额)"
|
||||
>
|
||||
{getFieldDecorator('blockchain_init', {
|
||||
rules: [{
|
||||
required: tokenConTri, message: '初始token值为一个不超过10000的正整数'
|
||||
},
|
||||
{ validator: (rule,val,callback) =>{
|
||||
if(val>10000){
|
||||
callback('初始token值最大值10000');
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
}}],
|
||||
})(
|
||||
<Input placeholder='请填写您(项目创建者)获取的初始Token值'/>
|
||||
)}
|
||||
</Form.Item>
|
||||
</div>}
|
||||
|
||||
<div className="mt20">
|
||||
注:<span className="ant-form-item-required"></span> 为必填项,否则为选填
|
||||
</div>
|
||||
|
|
|
@ -24,7 +24,7 @@ export default withRouter(
|
|||
secondRouter = props.location.pathname.split('/')[2];
|
||||
}
|
||||
|
||||
let userRouterArr = ['statistics', 'projects', 'notice', 'devops', 'organizes', 'info', 'following', 'followers', 'password' , "general"];
|
||||
let userRouterArr = ['statistics', 'projects', 'notice', 'devops', 'organizes', 'info', 'following', 'followers', 'password' , "general", "blockchain"];
|
||||
return (
|
||||
<Switch>
|
||||
|
||||
|
|
|
@ -278,7 +278,7 @@ ul.ant-menu.menuStyle{
|
|||
.infosRightMenu{
|
||||
.ant-menu-item{
|
||||
padding:0px;
|
||||
margin:0px 30px 0px 0px !important;
|
||||
margin:0px 17px 0px 0px !important;
|
||||
font-size: 17px;
|
||||
height: 32px;
|
||||
line-height: 0px;
|
||||
|
|
|
@ -63,6 +63,11 @@ const Personal = Loadable({
|
|||
loading: Loading,
|
||||
})
|
||||
|
||||
// 贡献确权
|
||||
const Contribution = Loadable({
|
||||
loader: () => import('./contribution/index'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
class Infos extends Component {
|
||||
constructor(props) {
|
||||
|
@ -108,6 +113,8 @@ class Infos extends Component {
|
|||
this.setState({menuKey:undefined,route_type:"following"});
|
||||
}else if(pathname === `/${username}/followers`){
|
||||
this.setState({menuKey:undefined,route_type:"followers"});
|
||||
}else if(pathname === `/${username}/blockchain`){
|
||||
this.setState({menuKey:'7',route_type:undefined});
|
||||
}else{
|
||||
this.setState({menuKey:"6",route_type:undefined});
|
||||
}
|
||||
|
@ -394,6 +401,10 @@ class Infos extends Component {
|
|||
{ user && user.user_org_count && user.user_org_count > 0 ? <span className="menuNum">({user.user_org_count})</span>:""}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
{/* 当前用户+配置了贡献确权 */}
|
||||
{current_user && user && user.login === current_user.login && current_user.open_blockchain && <Menu.Item key="7">
|
||||
<Link to={`/${user && user.login}/blockchain`}><i className="iconfont icon-zuzhi"></i>贡献确权</Link>
|
||||
</Menu.Item>}
|
||||
</Menu>
|
||||
}
|
||||
{user && (
|
||||
|
@ -440,6 +451,12 @@ class Infos extends Component {
|
|||
return <Organize {...this.props} {...this.state} />;
|
||||
}}
|
||||
></Route>
|
||||
<Route
|
||||
path="/:username/blockchain"
|
||||
render={() => {
|
||||
return <Contribution {...this.props} {...this.state} />;
|
||||
}}
|
||||
></Route>
|
||||
{/* <Route
|
||||
path="/:username/info"
|
||||
render={() => {
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
|
||||
import React , { useEffect , useState } from 'react';
|
||||
import { Button, Dropdown, Input, Menu , message, Pagination } from 'antd';
|
||||
import Search from '../../Component/Search';
|
||||
import Item from '../Team-item';
|
||||
import Nodata from '../../Nodata';
|
||||
import axios from 'axios';
|
||||
import CheckProfile from '../../Component/ProfileModal/Profile';
|
||||
import './index.scss';
|
||||
import SearchUser from '../../Component/SearchUser';
|
||||
import { FlexAJ } from '../../Component/layout';
|
||||
|
||||
// 个人中心-贡献确权
|
||||
function Contribution(props){
|
||||
// 接口暂不支持分页
|
||||
const limit = 15;
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(undefined);
|
||||
// 转账列表
|
||||
const [reload, setReload] = useState(undefined);
|
||||
const [list, setList] = useState(undefined);
|
||||
// 选项 1 转账 0 记录
|
||||
const [ type, setType] = useState(1);
|
||||
// 目标转账用户id/login
|
||||
const [userLogin, setUserLogin] = useState({});
|
||||
const [error1, setError1] = useState(undefined);
|
||||
// 转账数值
|
||||
const [tokenCount, setTokenCount] = useState({});
|
||||
const [error2, setError2] = useState(undefined);
|
||||
// 操作仓库id
|
||||
const [proId, setProId] = useState(undefined);
|
||||
const { checkIfLogin , showLoginDialog , current_user:{user_id, login}, current_user } = props;
|
||||
console.log('current_user', current_user);
|
||||
const {user} = props;
|
||||
|
||||
useEffect(()=>{
|
||||
// token转账列表
|
||||
axios.get(`/users/blockchain/balance.json`,{params: {user_id}}).then(res=>{
|
||||
if(res && res.data){
|
||||
setList(res.data.projects);
|
||||
setTotal(res.data.total_count);
|
||||
}
|
||||
})
|
||||
}, [reload])
|
||||
|
||||
useEffect(()=>{
|
||||
if(user){
|
||||
// 更改网页标题
|
||||
const {username, login} = user;
|
||||
document.title = `贡献确权-${username}/${login}`;
|
||||
}
|
||||
},[user])
|
||||
|
||||
// 目标转账用户
|
||||
function getUser(id, login){
|
||||
const userLoginObject = Object.assign(userLogin);
|
||||
userLoginObject[id] = login;
|
||||
setUserLogin(userLoginObject);
|
||||
}
|
||||
|
||||
// token转账值
|
||||
function setToken(id, token){
|
||||
const tokenCountObject = Object.assign(tokenCount);
|
||||
tokenCountObject[id] = token;
|
||||
setTokenCount(tokenCountObject);
|
||||
}
|
||||
|
||||
// 确认转账
|
||||
function confirmTransfer(id, identifier, max){
|
||||
setError1(undefined);
|
||||
setError2(undefined);
|
||||
setProId(id);
|
||||
const userLoginById = userLogin[id];
|
||||
const tokenCountById = tokenCount[id];
|
||||
if(!userLoginById || !tokenCountById){
|
||||
!userLoginById && setError1('请确定目标转账用户');
|
||||
if(!tokenCountById){
|
||||
setError2('请输入token数额');
|
||||
}
|
||||
}else if(!/^[1-9]+[0-9]*]*$/.test(tokenCountById) || tokenCountById>max){
|
||||
setError2('转账token数额必须为不超过token总数的正整数');
|
||||
}else{
|
||||
console.log('userLogin', userLoginById, 'tokenCount', tokenCountById);
|
||||
axios.post(`/users/blockchain/transfer`, {
|
||||
owner_login: login,
|
||||
project_identifier: identifier,
|
||||
user_id,
|
||||
transfer_amount: tokenCountById,
|
||||
transfer_login: userLoginById,
|
||||
payer_id: user_id,
|
||||
project_id: id
|
||||
}).then(res=>{
|
||||
console.log('res', res);
|
||||
if(res && res.data && res.data.status === 2){
|
||||
setReload(Math.random());
|
||||
message.success('转账成功');
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return(<div className='mt15 contribution'>
|
||||
{/* 转账/记录切换 */}
|
||||
{/* <div className='contHead'>
|
||||
<a className={`itemCont ${type ? 'active' : ''}`} onClick={()=>{setType(1)}}>token转账</a>
|
||||
<a className={`itemCont ${type ? '' : 'active'}`} onClick={()=>{setType(0)}}>token记录</a>
|
||||
</div> */}
|
||||
{/* token转账 */}
|
||||
{type ? <div className='contributionTable mt25'>
|
||||
{list && list.length > 0 && <div className='flexCenterCont tableHeadTri mb5'>
|
||||
<div className='tableColumn1'>项目名称</div>
|
||||
<div className='tableColumn2'>token总量</div>
|
||||
<div className='tableColumn3'>目标转账用户</div>
|
||||
<div className='tableColumn4'>转账token数额</div>
|
||||
<div className='tableColumn5'>操作</div>
|
||||
</div>}
|
||||
{list && !list.length && <Nodata _html="暂无数据"/>}
|
||||
{list && list.map(item=>{return <div key={item.id} className='flexCenterCont transferAccount'>
|
||||
<div className='tableColumn1'>{item.name}</div>
|
||||
<div className='tableColumn2'>{item.balance}</div>
|
||||
<div className='tableColumn3'>
|
||||
<SearchUser getUser={(login)=>{getUser(item.id, login)}} width={"100%"} placeholder="搜索转账目标用户"/>
|
||||
{proId === item.id && error1 && <div className='errorTipTri'>{error1}</div>}
|
||||
</div>
|
||||
<div className='tableColumn4'>
|
||||
<Input onChange={(e)=>{setToken(item.id, e.target.value)}} placeholder='请输入token数额'/>
|
||||
{proId === item.id && error2 && <div className='errorTipTri'>{error2}</div>}
|
||||
</div>
|
||||
<div className='tableColumn5'><Button className='confirmTransfer' onClick={()=>{confirmTransfer(item.id, item.identifier, item.balance)}}>确认</Button></div>
|
||||
</div>})}
|
||||
</div> : <div className='contributionTable mt20 record'>
|
||||
<FlexAJ className='contributionRecordItem font-15'>
|
||||
<span><span className='themeSpan'>蒋宇航</span>在<span className='themeSpan'>GilLink/确实开源</span>给你转账了1000个tiken</span>
|
||||
<span className='tableHeadTri'>6小时前</span>
|
||||
</FlexAJ>
|
||||
<FlexAJ className='contributionRecordItem font-15'>
|
||||
<span><span className='themeSpan'>蒋宇航</span>在<span className='themeSpan'>GilLink/确实开源</span>给你转账了1000个tiken</span>
|
||||
<span className='tableHeadTri'>6小时前</span>
|
||||
</FlexAJ>
|
||||
</div>}
|
||||
{/* {total > limit && <div className='mt20 paginationTri'>
|
||||
<Pagination simple current={page} pageSize={limit} total={total} onChange={(page)=>setPage(page)}/>
|
||||
</div>} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Contribution;
|
|
@ -0,0 +1,79 @@
|
|||
.contHead{
|
||||
&>.itemCont{
|
||||
display: inline-block;
|
||||
width:83px;
|
||||
height:32px;
|
||||
background-color:rgba(250, 251, 252, 0);
|
||||
border:1px solid #d0d0d0;
|
||||
border-radius:0px 4px 4px 0px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
&:first-of-type{
|
||||
border-radius:4px 0px 0px 4px;
|
||||
border-right: none;
|
||||
}
|
||||
&:last-of-type{
|
||||
border-left: none;
|
||||
}
|
||||
&.active{
|
||||
border-color: $primary-color;
|
||||
background-color: $primary-color;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
.contribution{
|
||||
.flexCenterCont{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.contributionTable{
|
||||
background-color:rgba(250, 252, 255, 0.89);
|
||||
border-radius:4px 4px 0px 0px;
|
||||
padding: 20px 20px 40px 20px;
|
||||
}
|
||||
.tableHeadTri{
|
||||
color:#898d9d;
|
||||
}
|
||||
.transferAccount{
|
||||
color:#40424a;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.tableColumn3, .tableColumn4{
|
||||
width: 200px;
|
||||
}
|
||||
.tableColumn1{
|
||||
width: 130px;
|
||||
}
|
||||
.tableColumn5{
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
.tableColumn2{
|
||||
flex: 0.5;
|
||||
}
|
||||
.confirmTransfer{
|
||||
border-color: $primary-color;
|
||||
color: $primary-color;
|
||||
}
|
||||
.errorTipTri{
|
||||
width: 400px;
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
.record.contributionTable{
|
||||
padding: 0px 20px;
|
||||
.contributionRecordItem{
|
||||
padding: 15px 0;
|
||||
}
|
||||
.contributionRecordItem+.contributionRecordItem{
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
.themeSpan{
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
.paginationTri{
|
||||
text-align: center;
|
||||
}
|
Loading…
Reference in New Issue