Merge pull request '区块确权、issue声明、新版issue、softbot' (#520) from durian/forgeplus-react:feature_bot into gitlink_server

This commit is contained in:
yystopf 2023-03-16 10:23:57 +08:00
commit 467282d66c
113 changed files with 9400 additions and 372 deletions

27
package-lock.json generated
View File

@ -7847,7 +7847,7 @@
},
"fsevents": {
"version": "1.2.13",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-1.2.13.tgz",
"resolved": "http://173.15.15.82:8081/repository/npm-all/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"optional": true,
"requires": {
@ -11299,7 +11299,7 @@
"dependencies": {
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
"resolved": "http://173.15.15.82:8081/repository/npm-all/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
@ -15439,6 +15439,27 @@
"object-assign": "^4.1.1"
}
},
"react-copy-to-clipboard": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
"requires": {
"copy-to-clipboard": "^3.3.1",
"prop-types": "^15.8.1"
},
"dependencies": {
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
}
}
},
"react-countup": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.1.0.tgz",
@ -20199,7 +20220,7 @@
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz",
"resolved": "http://173.15.15.82:8081/repository/npm-all/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true

View File

@ -69,6 +69,7 @@
"react-color": "^2.18.0",
"react-content-loader": "^3.1.1",
"react-cookies": "^0.1.1",
"react-copy-to-clipboard": "^5.1.0",
"react-countup": "^6.1.0",
"react-cropper": "^2.1.8",
"react-datepicker": "^2.14.1",

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2340181 */
src: url('iconfont.woff2?t=1653550272457') format('woff2'),
url('iconfont.woff?t=1653550272457') format('woff'),
url('iconfont.ttf?t=1653550272457') format('truetype');
src: url('iconfont.woff2?t=1678758582072') format('woff2'),
url('iconfont.woff?t=1678758582072') format('woff'),
url('iconfont.ttf?t=1678758582072') format('truetype');
}
.iconfont {
@ -13,6 +13,150 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-kaiqi:before {
content: "\e95a";
}
.icon-a-31shijian:before {
content: "\e944";
}
.icon-chengyuan2:before {
content: "\e945";
}
.icon-a-bianji12:before {
content: "\e946";
}
.icon-fuzhi-shanchu:before {
content: "\e947";
}
.icon-chuangjianqianbao:before {
content: "\e948";
}
.icon-biaoji2:before {
content: "\e949";
}
.icon-lichengbeiicon1:before {
content: "\e94a";
}
.icon-biaoji:before {
content: "\e94b";
}
.icon-fenzhi3:before {
content: "\e94c";
}
.icon-riqi:before {
content: "\e94d";
}
.icon-ruliriqixiao:before {
content: "\e94e";
}
.icon-shanchu8:before {
content: "\e94f";
}
.icon-sousuo6:before {
content: "\e950";
}
.icon-quanxian:before {
content: "\e951";
}
.icon-lichengbeiicon2:before {
content: "\e952";
}
.icon-xinjian3:before {
content: "\e953";
}
.icon-xuanzhong4:before {
content: "\e954";
}
.icon-youxian:before {
content: "\e955";
}
.icon-a-xiaoxi1:before {
content: "\e956";
}
.icon-yixiuicon2:before {
content: "\e957";
}
.icon-xiugaibaobiaomoban:before {
content: "\e958";
}
.icon-a-zu1614:before {
content: "\e959";
}
.icon-BOTpeizhi:before {
content: "\e942";
}
.icon-kaifabot:before {
content: "\e943";
}
.icon-jiqiren:before {
content: "\e940";
}
.icon-a-jiqirenzhineng-02:before {
content: "\e941";
}
.icon-yuyue-lishi-shijian:before {
content: "\e93f";
}
.icon-a-duobianxing1:before {
content: "\e93c";
}
.icon-a-duobianxing2:before {
content: "\e93d";
}
.icon-sousuo5:before {
content: "\e93e";
}
.icon-baogaoppt:before {
content: "\e93b";
}
.icon-gailan2:before {
content: "\e93a";
}
.icon-bianji7:before {
content: "\e939";
}
.icon-ROBOT:before {
content: "\e937";
}
.icon-a-zu1404:before {
content: "\e938";
}
.icon-tuichuicon:before {
content: "\e936";
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,258 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "34197190",
"name": "开启",
"font_class": "kaiqi",
"unicode": "e95a",
"unicode_decimal": 59738
},
{
"icon_id": "33949760",
"name": "3.1 时间",
"font_class": "a-31shijian",
"unicode": "e944",
"unicode_decimal": 59716
},
{
"icon_id": "33949761",
"name": "成员",
"font_class": "chengyuan2",
"unicode": "e945",
"unicode_decimal": 59717
},
{
"icon_id": "33949762",
"name": "编辑 (1)",
"font_class": "a-bianji12",
"unicode": "e946",
"unicode_decimal": 59718
},
{
"icon_id": "33949763",
"name": "复制-删除",
"font_class": "fuzhi-shanchu",
"unicode": "e947",
"unicode_decimal": 59719
},
{
"icon_id": "33949764",
"name": "创建钱包",
"font_class": "chuangjianqianbao",
"unicode": "e948",
"unicode_decimal": 59720
},
{
"icon_id": "33949765",
"name": "标记2",
"font_class": "biaoji2",
"unicode": "e949",
"unicode_decimal": 59721
},
{
"icon_id": "33949766",
"name": "里程碑icon",
"font_class": "lichengbeiicon1",
"unicode": "e94a",
"unicode_decimal": 59722
},
{
"icon_id": "33949767",
"name": "标记",
"font_class": "biaoji",
"unicode": "e94b",
"unicode_decimal": 59723
},
{
"icon_id": "33949768",
"name": "分支",
"font_class": "fenzhi3",
"unicode": "e94c",
"unicode_decimal": 59724
},
{
"icon_id": "33949769",
"name": "日期",
"font_class": "riqi",
"unicode": "e94d",
"unicode_decimal": 59725
},
{
"icon_id": "33949770",
"name": "入离日期小",
"font_class": "ruliriqixiao",
"unicode": "e94e",
"unicode_decimal": 59726
},
{
"icon_id": "33949771",
"name": "删除",
"font_class": "shanchu8",
"unicode": "e94f",
"unicode_decimal": 59727
},
{
"icon_id": "33949772",
"name": "搜索",
"font_class": "sousuo6",
"unicode": "e950",
"unicode_decimal": 59728
},
{
"icon_id": "33949773",
"name": "权限",
"font_class": "quanxian",
"unicode": "e951",
"unicode_decimal": 59729
},
{
"icon_id": "33949774",
"name": "里程碑icon2",
"font_class": "lichengbeiicon2",
"unicode": "e952",
"unicode_decimal": 59730
},
{
"icon_id": "33949775",
"name": "新建",
"font_class": "xinjian3",
"unicode": "e953",
"unicode_decimal": 59731
},
{
"icon_id": "33949776",
"name": "选中",
"font_class": "xuanzhong4",
"unicode": "e954",
"unicode_decimal": 59732
},
{
"icon_id": "33949777",
"name": "优先",
"font_class": "youxian",
"unicode": "e955",
"unicode_decimal": 59733
},
{
"icon_id": "33949778",
"name": "消息 (1)",
"font_class": "a-xiaoxi1",
"unicode": "e956",
"unicode_decimal": 59734
},
{
"icon_id": "33949779",
"name": "易修icon",
"font_class": "yixiuicon2",
"unicode": "e957",
"unicode_decimal": 59735
},
{
"icon_id": "33949780",
"name": "修改报表模版",
"font_class": "xiugaibaobiaomoban",
"unicode": "e958",
"unicode_decimal": 59736
},
{
"icon_id": "33949781",
"name": "组 1614",
"font_class": "a-zu1614",
"unicode": "e959",
"unicode_decimal": 59737
},
{
"icon_id": "33731642",
"name": "BOT配置",
"font_class": "BOTpeizhi",
"unicode": "e942",
"unicode_decimal": 59714
},
{
"icon_id": "33731643",
"name": "开发bot",
"font_class": "kaifabot",
"unicode": "e943",
"unicode_decimal": 59715
},
{
"icon_id": "19591156",
"name": "机器人",
"font_class": "jiqiren",
"unicode": "e940",
"unicode_decimal": 59712
},
{
"icon_id": "25885545",
"name": "机器人、智能-02",
"font_class": "a-jiqirenzhineng-02",
"unicode": "e941",
"unicode_decimal": 59713
},
{
"icon_id": "6978097",
"name": "预约-历史-时间",
"font_class": "yuyue-lishi-shijian",
"unicode": "e93f",
"unicode_decimal": 59711
},
{
"icon_id": "31659662",
"name": "多边形 1",
"font_class": "a-duobianxing1",
"unicode": "e93c",
"unicode_decimal": 59708
},
{
"icon_id": "31659664",
"name": "多边形 2",
"font_class": "a-duobianxing2",
"unicode": "e93d",
"unicode_decimal": 59709
},
{
"icon_id": "31659665",
"name": "搜索",
"font_class": "sousuo5",
"unicode": "e93e",
"unicode_decimal": 59710
},
{
"icon_id": "31424249",
"name": "报告ppt",
"font_class": "baogaoppt",
"unicode": "e93b",
"unicode_decimal": 59707
},
{
"icon_id": "31318120",
"name": "概览",
"font_class": "gailan2",
"unicode": "e93a",
"unicode_decimal": 59706
},
{
"icon_id": "31275425",
"name": "编辑",
"font_class": "bianji7",
"unicode": "e939",
"unicode_decimal": 59705
},
{
"icon_id": "30382389",
"name": "ROBOT",
"font_class": "ROBOT",
"unicode": "e937",
"unicode_decimal": 59703
},
{
"icon_id": "30382697",
"name": "组 1404",
"font_class": "a-zu1404",
"unicode": "e938",
"unicode_decimal": 59704
},
{
"icon_id": "29934961",
"name": "退出icon",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -49,7 +49,7 @@
<script src="https://gw.alipayobjects.com/os/lib/react-dom/16.14.0/umd/react-dom.%REACT_URL%.js"></script>
<script src="%PUBLIC_URL%js/jquery-1.8.3.min.js"></script>
<script src="%PUBLIC_URL%js/js_min_all.js"></script>
<script src="%PUBLIC_URL%js/common.js"></script>
<!-- <script src="%PUBLIC_URL%js/common.js"></script> -->
<script src="%PUBLIC_URL%js/codemirror/codemirror.js"></script>
<script src="%PUBLIC_URL%js/editormd/editormd.min.js"></script>
<script src="%PUBLIC_URL%js/codemirror/merge/merge.js"></script>

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
<!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"> -->
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--

View File

@ -110,7 +110,7 @@ body {
/* 图片点击放大的场景,隐藏图片链接 */
.editormd-image-click-expand .editormd-image-dialog {
height: 234px !important;
height: 244px !important;
}
.editormd-image-click-expand .editormd-image-dialog .image-link {

View File

@ -126,6 +126,15 @@ const Glcc = Loadable({
loader : () => import("./glcc"),
loading: Loading,
})
const ConfigurateBot = Loadable({
loader: () => import("./forge/SecuritySetting/bot/exploit/configuration"),
loading: Loading,
});
const SoftBot = Loadable({
loader : () => import("./softbot"),
loading: Loading,
})
const WebIDE = Loadable({
loader: () => import('./forge/Newfile/webIDE'),
loading: Loading,
@ -136,7 +145,7 @@ const WebIDE = Loadable({
// })
// 此处仅维护前端可能的一级路由,不用进行项目或者组织判断的字段。
const keyWord = ["explore", "settings", "setting", "mulan", "wiki", "issues", "setting", "trending", "code", "projects", "pulls", "mine", "login", "register", "email", "export", "nopage", "404", "403", "500", "501", "search", "organize", "login", "register", "resetPassword", "aboutus","educoder", "glcc","bindlogin"];
const keyWord = ["explore", "settings", "setting", "mulan", "wiki", "issues", "setting", "trending", "code", "projects", "pulls", "mine", "login", "register", "email", "export", "nopage", "404", "403", "500", "501", "search", "organize", "login", "register", "resetPassword", "aboutus","educoder", "glcc","bindlogin", "softbot"];
class App extends Component {
constructor(props) {
@ -342,6 +351,13 @@ class App extends Component {
}
}>
</Route>
{/* 上架bot配置 */}
<Route
path="/settings/exploitBot/configuration/:id"
render={(p) => (
<ConfigurateBot {...this.props} {...p}/>
)}
></Route>
<Route
path={"/settings"}
render={
@ -350,7 +366,6 @@ class App extends Component {
}
}>
</Route>
{/*403*/}
<Route path="/403" component={Shixunauthority} />
@ -418,6 +433,14 @@ class App extends Component {
render={(props) => <Glcc {...this.props} {...props} />}
></Route>
{/* soft市场页 */}
<Route
path="/softbot"
render={(props) => (
<SoftBot {...this.props} {...props} />
)}
></Route>
{/* 组织 */}
<Route path={"/organize"}
render={

View File

@ -277,7 +277,7 @@ class Activity extends Component{
}
<div className="commentsBox">
<div className="trendsTop">
<span>Issue/Pr记录</span>
<span>疑修/合并请求记录</span>
<div className="branchDropdown f-wrap-alignCenter">
<Dropdown overlay={this.menu()} trigger={['click']} placement="bottomLeft">
<a className="ant-dropdown-link">

View File

@ -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'}}>
{

View File

@ -1,8 +1,14 @@
import React from 'react';
function Profile({children,sureFunc,showCompeleteDialog , completeProfile, className}) {
function Profile({children,sureFunc,showCompeleteDialog , completeProfile, className,checkIfLogin,showLoginDialog,checklogin}) {
function checkProfile() {
if(checklogin){
if(checkIfLogin()===false){
showLoginDialog();
return;
}
}
if(!completeProfile){
showCompeleteDialog && showCompeleteDialog();
}else{

View File

@ -0,0 +1,65 @@
import React , { useState , useEffect } from 'react';
import { Form , Input, Button } from 'antd';
import ColorCard from './colorCard';
import axios from 'axios';
function AddTagsBox({onSuccess , form ,owner , projectsId,onCancel}){
const { getFieldDecorator , getFieldsValue , validateFields } = form;
const [ colors , setColors ] = useState(undefined);
const [ defaultColor , setDefaultColor ]= useState(undefined);
useEffect(()=>{
const c = Math.random().toString(16).substr(-6);
c && setDefaultColor(`#${c}`);
},[])
function saveFunc(){
validateFields((error,values)=>{
if(!error){
const { desc , name } = getFieldsValue();
const url = `/v1/${owner}/${projectsId}/issue_tags`;
axios.post(url,{
name,description:desc,color:colors || defaultColor
}).then(result=>{
result && onSuccess();
}).catch(error=>{})
}
})
}
function getColor(colors){
setColors(colors);
}
return(
<Form className="addTags_form" hideRequiredMark={true}>
<p className="manageTitle"><i className="iconfont icon-xiangzuojiantou font-15 color-grey mr5 cursor" onClick={onCancel}/>创建标记</p>
<div className="inline">
<Form.Item colon={false} style={{flex:1,marginRight:'20px'}}>
{getFieldDecorator('name',{
rules:[
{
required:true,
message:"请输入标记名称"
}
],
validateTrigger:"onInput",
})(<Input placeholder="标记名称15字以内" maxLength={15}/>)}
</Form.Item>
<Form.Item style={{marginTop:"7px"}}>
<ColorCard getColor={getColor} defaultColor={defaultColor}/>
</Form.Item>
</div>
<Form.Item colon={false}>
{getFieldDecorator('desc',{rules:[],
validateTrigger:"onInput",
})(<Input placeholder="描述30字以内" maxLength={30}/>)}
</Form.Item>
<div style={{textAlign:'center'}}>
<Button style={{width:"80px"}} className="cancelTags" onClick={onCancel}>取消</Button>
<Button style={{width:"80px"}} className="ml20" type="primary" onClick={saveFunc}>确认</Button>
</div>
</Form>
)
}
export default Form.create({ name: 'Bind' })(AddTagsBox);

View File

@ -0,0 +1,219 @@
import React,{ useState , useEffect , forwardRef ,useImperativeHandle , useRef } from 'react';
import Menus from './menus';
import axios from 'axios';
const array =[
{id:1,name:"最新创建"},
{id:2,name:"最早创建"},
{id:3,name:"最新更新"},
{id:4,name:"最早更新"},
{id:5,name:"高优先级"},
{id:6,name:"低优先级"},
{id:7,name:"高悬赏金额"},
{id:8,name:"低悬赏金额"}
]
const array1 =[
{id:1,name:"最新创建"},
{id:2,name:"最早创建"},
{id:3,name:"最新更新"},
{id:4,name:"最早更新"},
{id:5,name:"高优先级"},
{id:6,name:"低优先级"},
]
function AllMenus({owner,projectsId,chooseFunc,update,defaultNames,defaultIds,open_blockchain},ref){
//
const [ authorList , setAuthorList ] = useState(undefined);
const [ author , setAuthor ] = useState(undefined);
const [ tagList , setTagList ] = useState(undefined);
const [ tag , setTag ] = useState(undefined);
const [ millstoneList , setMillstoneList ] = useState(undefined);
const [ millstone , setMillstone ] = useState(undefined);
const [ chargeList , setChargeList ] = useState(undefined);
const [ charge , setCharge ] = useState(undefined);
const [ statusList , setStatusList ] = useState(undefined);
const [ prioritiesList , setPrioritiesList ] = useState(undefined);
const [ ids , setIds ] = useState({author_id:undefined,issue_priorities_id:undefined,issue_tag_ids:undefined,milestone_id:undefined,sort_by:undefined,status_id:undefined,assigner_id:undefined});
const [ names , setNames ] = useState({author_name:undefined,issue_priorities_name:undefined,issue_tag_name:undefined,milestone_name:undefined,sortby_name:undefined,status_name:undefined,assigner_name:undefined});
useEffect(()=>{
if(defaultNames){
setNames(defaultNames);
}
},[defaultNames])
useEffect(()=>{
if(defaultIds){
console.log("defaule:",defaultIds);
setIds(defaultIds);
}
},[defaultIds])
useImperativeHandle(ref, () => ({
clearChoose: () => {
// Ids\namesundefined
setIds({author_id:undefined,issue_tag_ids:undefined,issue_priorities_id:undefined,milestone_id:undefined,sort_by:undefined,status_id:undefined,assigner_id:undefined});
setNames({author_name:undefined,issue_tag_name:undefined,issue_priorities_name:undefined,milestone_name:undefined,sortby_name:undefined,status_name:undefined,assigner_name:undefined});
}
}))
//
useEffect(()=>{
getSendPerson();
},[author])
function getSendPerson(){
const url = `/v1/${owner}/${projectsId}/issue_authors`;
axios.get(url,{params:{keyword:author,only_name:true}}).then(result=>{
if(result && result.data){
setAuthorList(result.data.authors);
}
})
}
//
useEffect(()=>{
getSign();
},[tag])
function getSign(){
const url = `/v1/${owner}/${projectsId}/issue_tags`;
axios.get(url,{params:{keyword:tag,only_name:true}}).then(result=>{
if(result && result.data){
setTagList(result.data.issue_tags);
}
})
}
//
useEffect(()=>{
getMillstone();
},[millstone])
function getMillstone(){
const url = `/v1/${owner}/${projectsId}/milestones`;
axios.get(url,{params:{keyword:millstone,only_name:true}}).then(result=>{
if(result && result.data){
setMillstoneList(result.data.milestones);
}
})
}
//
useEffect(()=>{
getCharge();
},[charge,update])
function getCharge(){
if(update){
const url = `/v1/${owner}/${projectsId}/collaborators`;
axios.get(url,{params:{keyword:charge,only_name:true}}).then(result=>{
if(result && result.data){
setChargeList(result.data.collaborators);
}
})
}else{
const url = `/v1/${owner}/${projectsId}/issue_assigners`;
axios.get(url,{params:{keyword:charge,only_name:true}}).then(result=>{
if(result && result.data){
setChargeList(result.data.assigners);
}
})
}
}
//
useEffect(()=>{
getPriorities();
},[])
function getPriorities(){
const url = `/v1/${owner}/${projectsId}/issue_priorities`;
axios.get(url).then(result=>{
if(result && result.data){
setPrioritiesList(result.data.priorities);
}
})
}
//
useEffect(()=>{
getStatus();
},[])
function getStatus(){
const url = `/v1/${owner}/${projectsId}/issue_statues`;
axios.get(url).then(result=>{
if(result && result.data){
setStatusList(result.data.statues);
}
})
}
function choose(id,name){
let copy = {...ids,author_id:id && id.length>0 ? id.join(","):undefined};
let copyname = { ...names,author_name:name};
setIds(copy);setNames(copyname);
chooseFunc(copy,copyname);
}
return(
<ul className="dropboxul">
{ !update && <Menus
update={update}
ids={ids && ids.author_id}
names={names && names.author_name}
name={"发布人" } size={"large"} imgControl
lists={authorList} searchFunc={(value)=>setAuthor(value)}
chooseFunc={(id,name)=>choose(id,name,'author_name')}
/>
}
{update && <Menus
update={update}
ids={ids && ids.issue_priorities_id}
name={"优先级" } size={"small"}
lists={prioritiesList}
names={names && names.issue_priorities_name}
chooseFunc={(id,name)=>{let copy = {...ids,issue_priorities_id:id && id.length>0 ? id.join(","):undefined};let copyname = { ...names,issue_priorities_name:name};setNames(copyname);setIds(copy);chooseFunc(copy,copyname)}}
/>
}
<Menus
update={update}
ids={ids && ids.issue_tag_ids}
name={"标记"} size={"large"}
double
names={names && names.issue_tag_name}
lists={tagList} searchFunc={(value)=>setTag(value)}
chooseFunc={(id,name)=>{let copy = {...ids,issue_tag_ids:id && id.length>0 ? id.join(","):undefined};let copyname = { ...names,issue_tag_name:name};setNames(copyname);setIds(copy);chooseFunc(copy,copyname)}}
/>
<Menus
update={update}
ids={ids && ids.milestone_id}
name={"里程碑"} size={"large"}
names={names && names.milestone_name}
lists={millstoneList} searchFunc={(value)=>setMillstone(value)}
chooseFunc={(id,name)=>{let copy = {...ids,milestone_id:id && id.length>0 ? id.join(","):undefined};let copyname = { ...names,milestone_name:name};setNames(copyname);setIds(copy);chooseFunc(copy,copyname)}}
/>
<Menus
update={update}
ids={ids && ids.assigner_id}
name={"负责人"} size={"large"} imgControl
names={names && names.assigner_name}
lists={chargeList} searchFunc={(value)=>setCharge(value)}
double={update ? true : false }
chooseFunc={(id,name)=>{let copy = {...ids,assigner_id:id && id.length>0 ? id.join(","):undefined};let copyname = { ...names,assigner_name:name};setNames(copyname);setIds(copy);chooseFunc(copy,copyname)}}
/>
<Menus
update={update}
ids={ids && ids.status_id}
name={"状态"} size={"small"}
lists={statusList}
names={names && names.status_name}
chooseFunc={(id,name)=>{let copy = {...ids,status_id:id && id.length>0 ? id.join(","):undefined};let copyname = { ...names,status_name:name};setNames(copyname);setIds(copy);chooseFunc(copy,copyname)}}
/>
{ !update && <Menus
ids={ids && ids.sort_by}
name={"排序"} size={"small"}
lists={open_blockchain ? array : array1}
names={names && names.sortby_name}
chooseFunc={(id,name)=>{let copy = {...ids,sort_by:id && id.length>0 ? id.join(","):undefined};let copyname = { ...names,sortby_name:name};setNames(copyname);setIds(copy);chooseFunc(copy,copyname)}}
/>
}
</ul>
)
}
export default forwardRef(AllMenus);

View File

@ -0,0 +1,302 @@
import React ,{ useState , useEffect , useRef , forwardRef } from 'react';
import { findDOMNode } from 'react-dom';
import { Dropdown , Menu , Input , Button, Form , Tooltip } from 'antd';
import { getImageUrl } from 'educoder';
import AddTagsBox from './addTagsBox';
const { Search } = Input;
/**
*
* @param {placeholder} placeholder:默认显示内容为空时
* @param {editFlag} editFlag:是否有编辑权限
* @param {searchFlag} searchFlag:是否显示搜索框
* @param {selectValueList} selectValueList:将选中的list保存
* @param {searchFunc} searchFunc:搜索方法
* @param {onAdd} onAdd:标记管理
* @param {headImg} headImg:是否显示头像
* @param {menus} menus:下拉列表
* @param {chooseFunc} chooseFunc:选择下拉列表
* @param {double} double:undefined为单选有值就是最多选的数量
* @param {colorFlag} colorFlag:选中值需根据颜色显示背景
* @param {mustFlag} mustFlag:必须选择一个
* @param {removeFlag} removeFlag:移除按钮
* @returns
*/
function ChooseMenu({
placeholder ="未设置",
editFlag,
searchFlag,
selectValueList,
searchFunc,
onAdd,
headImg,
menus,
auto,
chooseFunc,double,colorFlag,mustFlag,removeFlag,owner,projectsId
}){
const [ visible , setVisible ] = useState(false);
const [ searchValue , setSearchValue ]= useState(undefined);
const [ valuesId , setValuesId ] = useState([]);
const [ count , setCount ] = useState(double);
const [ content , setContent ] = useState(false);
const [ saveList , setSaveList ] = useState(undefined);
const refFa = useRef(null);
const refBox = useRef(null);
useEffect(() => {
document.addEventListener('click', clickMe , false);
}, [])
const clickMe = ({ target }) => {
//
const faComponent = findDOMNode(refFa.current);
const boxComponent = findDOMNode(refBox.current);
if (faComponent && boxComponent) {
const isChild = faComponent.contains(target);
const isBox = boxComponent.contains(target);
const arr = ["removeicon","iconfont icon-guanbi font-12 color-white","iconfont icon-guanbi font-12 color-blue","icon-a-bianji12","color-blue font-15 tagManage","iconfont icon-xiangzuojiantou font-15 color-grey mr5 cursor","cover","ant-btn cancelTags"];
const pointer = arr.includes(target.className);
if(!isChild && !isBox && !pointer){
setVisible(false);
setContent(false);
}
}
}
// id
useEffect(()=>{
if(selectValueList && selectValueList.length > 0 && visible ){
renderSelectList(selectValueList);
setSaveList(selectValueList);
setCount(selectValueList.length);
}else{
setValuesId([]);
setCount(0);
setSaveList(undefined);
}
if(!visible){
setSearchValue(undefined);
searchFunc(undefined);
}
},[selectValueList,visible])
function renderSelectList(list){
let a = list && list.length > 0 && list.map((i,k)=>{
return i.id ? i.id.toString() : i.name
})
setValuesId(a);
}
//
function changeSearchvalue(e){
setSearchValue(e.target.value);
searchFunc(e.target.value);
}
function renderNames(nameArrs){
return <React.Fragment>
{
nameArrs && nameArrs.length>0?
nameArrs.map((i,k)=>{
return(
<p style={{display:"flex",alignItems:"center"}} className={removeFlag?"removeFlag":''}>
{i.image_url && <img src={getImageUrl(i.image_url)} alt="" width="28px" height="28px" style={{borderRadius:"50%",marginTop:"5px"}} className="mr5"/>}
{
colorFlag ?
(
colorFlag === "2" ?
<span className={"colorsquare task-hide"} style={{backgroundColor:`${i.color || "#000"}`,paddingRight:"18px"}}>
{i.name}
</span>
:
<span className={"colorsborder task-hide"} style={{borderColor:`${i.color}`,color:`${i.color}`}}>{i.name}</span>
)
:
<span className={"task-hide"}>{i.name}</span>
}
{ !colorFlag && removeFlag && <a className="removeicon" onClick={()=>removeValueFunc(i.id)}><i className="iconfont icon-shanchu8 font-14"></i></a>}
{ colorFlag && removeFlag && <a className="removeicon" onClick={()=>removeValueFunc(i.id)} style={{display:'block',right:"19px",position:"absolute"}}><i className="iconfont icon-guanbi font-12 color-white"></i></a>}
</p>
)
})
:<span>{placeholder}</span>
}
</React.Fragment>
}
//
function chooseMenu(i){
let list = saveList && saveList.length > 0 ? [...saveList]:[] ;
let relist = [];
relist = list && list.length > 0 ? list:[];
let filter = [];
if(i.id){
filter = list.filter(j=>j.id === i.id);
}else{
filter = list.filter(j=>j.name === i.name);
}
if(filter && filter.length > 0){
if(i.id){
relist = list.filter(j=>j.id !== i.id);
}else{
relist = list.filter(j=>j.name !== i.name);
}
}else{
if(double && (saveList && saveList.length >= double)){
setCount(-1);
return;
}
if(double){
relist.push(i);
}else{
relist = [i];
}
}
renderSelectList(relist);
setSaveList(relist);
setCount(relist ? relist.length :0);
if(!double){
setVisible(false);
setContent(false);
chooseFunc(relist);
}
}
//
function onSureFunc(){
setVisible(false);
setContent(false);
chooseFunc(saveList);
}
//
function removeSaveFunc(id){
let list = [...saveList];
let filter = list.filter(j=>j.id !== id);
setSaveList(filter);
renderSelectList(filter);
setCount(filter ? filter.length :0);
}
//
function removeValueFunc(id){
let list = [...selectValueList];
let filter = list.filter(j=>j.id !== id);
setSaveList(filter);
renderSelectList(filter);
setCount(filter ? filter.length :0);
chooseFunc(filter);
}
function onSuccess(){
setContent(false);
onAdd();
}
return(
<li>
<span>
{placeholder}
{
!editFlag &&
<Dropdown
visible={visible}
overlayClassName={"overlayChooseStyle"}
placement="bottomRight"
trigger={['click']}
overlay={
<div ref={refFa}>
{
!content ?
<div>
{
double && saveList && saveList.length>0?
<ul className="choosedul">
{
saveList.map((i,k)=>{
return(
<li style={{backgroundColor:`${i.color}` || "#eff2ff",borderColor:`${i.color}` || "#466aff",color:`${colorFlag?"#FFF":"#466aff"}`}}>
<span className="task-hide">{i.name}</span>
<span className="removeicon" onClick={()=>removeSaveFunc(i.id)}><i className={`iconfont icon-guanbi font-12 ${colorFlag? "color-white":"color-blue"}`}></i></span>
</li>
)
})
}
</ul>
:""
}
{ searchFunc &&
<div className="searchbox">
<Search
placeholder={`请输入${placeholder}名称进行搜索`}
value={searchValue}
onChange={changeSearchvalue}
style={{marginRight:"18px"}}
/>
</div>
}
{
menus && menus.length >0?
<Menu className={auto && "piecemenu"} selectedKeys={valuesId}>
{
menus.map((i,k)=>{
return(
<Menu.Item key={i.id || i.name} className={colorFlag ?"colorli":"commonli"} style={{backgroundColor:colorFlag?`${i.color || "#000"}`:"#f4f6fe"}} onClick={()=>chooseMenu(i)}>
{
auto ? <span><span className="task-hide">{i.name}</span></span>
:
<Tooltip display={auto?false:true} placement={"bottom"} title={i.name}><span><span className="task-hide">{i.name}</span></span></Tooltip>
}
</Menu.Item>
)
})
}
</Menu>
:
<div className="menusEmpty">
<p>{searchValue ? <span>暂无{placeholder}{searchValue}</span>: `暂无${placeholder}`}</p>
</div>
}
<div className="counttips">
<div>
{ onAdd &&
<a className="color-blue font-15 tagManage" onClick={()=>{setContent(true)}}>
<i className="iconfont icon-a-bianji12 font-14 mr5"></i>创建标记</a>
}
</div>
{
double && (count<0 ?
<p className="color-red font-13">最多添加{double}{placeholder}</p>
:
<p className="font-13" style={{color:"#898d9d"}}>还可添加{double - count}{placeholder}</p>
)
}
</div>
{
double &&
<div style={{textAlign:'center'}}>
<Button style={{width:"80px"}} onClick={()=>setVisible(false)}>取消</Button>
<Button style={{width:"80px"}} className="ml20" type="primary" onClick={onSureFunc}>确认</Button>
</div>
}
</div>
:
<AddTagsBox
owner={owner}
projectsId={projectsId}
visible={visible}
onCancel={()=>setContent(false)}
onSuccess={onSuccess} />
}
</div>
}>
<a ref={refBox} onClick={()=>setVisible(visible ? false : true)}>
<i className="iconfont icon-a-bianji12 font-13" style={{color:"#898d9d"}}></i>
</a>
</Dropdown>
}
</span>
<div className={selectValueList && selectValueList.length > 0 ? "operatevalue color-grey-3":"operatevalue"} style={{display:colorFlag?"flex":"block"}}>{renderNames(selectValueList)}</div>
</li>
)
}
export default Form.create()(forwardRef(ChooseMenu));

View File

@ -0,0 +1,47 @@
import React,{ useState , useEffect } from 'react';
import { SketchPicker } from 'react-color';
function ColorCard({getColor,defaultColor}){
const [ displayColorPicker , setDisplayColorPicker ] = useState(false);
const [ textcolor , setTextColor ] = useState("#F17013");
useEffect(()=>{
if(defaultColor){
setTextColor(defaultColor);
}
},[defaultColor])
//
function handleChange(color){
setTextColor(color.hex);
getColor(color ? color.hex : defaultColor);
}
//
function handleClick(){
setDisplayColorPicker(!displayColorPicker);
// reduction();
}
//
function reduction(){
setTextColor("#F17013");
// setColor({r: '241',g: '112',b: '19',a: '1',});
}
return(
<div>
<div className="swatch" onClick={handleClick}>
<div className="color" style={{backgroundColor:`${textcolor}`}}></div>
<p style={{ paddingLeft: 5 }}>{textcolor}</p>
</div>
{displayColorPicker ? (
<div className="popover">
<div className="cover" onClick={()=>{setDisplayColorPicker(false)}} />
<SketchPicker color={textcolor} onChange={handleChange} />
</div>
) : null}
</div>
)
}
export default ColorCard;

View File

@ -0,0 +1,132 @@
import React from 'react';
import { Input, Button, message } from 'antd';
import { getImageUrl, timeAgo } from 'educoder';
import MDEditor from "../../../../modules/tpm/challengesnew/tpm-md-editor";
import Upload from "../../../../forge/Upload/Index";
import UploadImg from "../../../../forge/Images/upload.png";
import './list.scss';
import '../../../../forge/Order/order.scss';
import { useState } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
function EditComment(props){
const {owner, projectsId, index} = props.match.params;
const {current_user, current_user:{login}, showLoginDialog, showNotification, reloadComment, cancelMd, parentId, replyId, updateId, showUserImg=true} = props;
const [content, setContent] = useState(props.content);
const [quillFlag, setQuillFlag] = useState(false);
// loading
const [journalSpin, setJournalSpin] = useState(false);
const [atWhoLoginList, setAtWhoLoginList] = useState(undefined);
const [fileList, setFileList] = useState(undefined);
const [attachmentClean, setAttachmentClean] = useState(true);
//
function addJournals(){
if(!content){
setQuillFlag(true);
return
}
setJournalSpin(true);
if(updateId){
//
axios.patch(`/v1/${owner}/${projectsId}/issues/${index}/journals/${updateId}`,{
notes: content,
attachment_ids: fileList,
receivers_login:atWhoLoginList
}).then(res=>{
if(res && res.data){
//
reloadComment();
message.success('评论成功');
cancelMd();
setContent(undefined);
}
setJournalSpin(false);
})
}else{
//
const params = {
parent_id: parentId,
reply_id: replyId,
notes: content,
attachment_ids: fileList,
receivers_login:atWhoLoginList
}
axios.post(`/v1/${owner}/${projectsId}/issues/${index}/journals`, params).then(res=>{
if(res && res.data){
//
reloadComment();
message.success('评论成功');
cancelMd();
setContent(undefined);
}
setJournalSpin(false);
})
}
};
return(
<div className="grid-item-top pb10">
<Link
to={`/${current_user && current_user.login}`}
className="show-user-link mr10"
>
<img
className="radius"
src={getImageUrl(
`/${current_user && current_user.image_url}`
)}
alt=""
width="30"
height="30"
style={{display: showUserImg ? '' : 'none'}}
/>
</Link>
<div style={{position:"relative"}}>
<MDEditor
placeholder={"添加评论..."}
height={300}
mdID={"orderdetail-add-descriptions" + replyId}
initValue={content}
onChange={(value)=>{setQuillFlag(false);setContent(value);}}
isCanAtme = {true}
changeAtWhoLoginList = {(loginList)=>{setAtWhoLoginList(loginList); setAttachmentClean(true)}}
owner = {owner}
projectsId = {projectsId}
></MDEditor>
<p className="quillFlagBox">
{quillFlag && <span>请输入评论内容</span>}
</p>
<Upload
className="commentStyle"
isComplete={attachmentClean}
load={(fileList)=>{setFileList(fileList)}}
icon={
<img
src={UploadImg}
width="58"
alt=""
style={{ marginBottom: 15 }}
/>
}
size={100}
showNotification={showNotification}
defaultFileList={props.defaultFileList}
/>
<p className="clearfix mt20">
<Button
type="primary"
onClick={addJournals}
loading={journalSpin}
className="mr15"
>
评论
</Button>
<Button onClick={()=>{cancelMd()}}>取消</Button>
</p>
</div>
</div>
)
}
export default EditComment;

View File

@ -0,0 +1,240 @@
import React, { useEffect, useState } from 'react';
import { Button, Popconfirm, Radio, Input, message, Tooltip, Spin, Pagination } from 'antd';
import axios from 'axios';
import { getImageUrl, timeAgo } from 'educoder';
import RenderHtml from '../../../../components/render-html';
import Attachment from '../../../Upload/attachment';
import EditComment from './editComment';
import Nodata from '../../../Nodata';
import CheckProfile from '../../../Component/ProfileModal/Profile';
import './list.scss';
import { Link } from 'react-router-dom';
function IssueCommentList(props){
const{history, history: {location}, reload, reloadComment, showNotification, current_user:{login, admin, image_url, user_id}, isManager, showLoginDialog, issueInfo:{author}} = props;
const {owner, projectsId, index} = props.match.params;
const [category, setCategory] = useState('comment');
const [journals, setJournals] = useState(undefined);
// /markdown1 2 3 4 5
const [showEdit, setShowEdit] = useState(false);
const [parentId, setParentId] = useState(undefined);
const [replyId, setReplyId] = useState(undefined);
// id
const [updateId, setUpdateId] = useState(undefined);
//
const [open, setOpen] = useState(undefined);
// css 线
const [spin, setSpin] = useState(false);
// issue
const [journalsCount, setJournalsCount] = useState(undefined);
//
const limit = 50;
const [page, setPage] = useState(1);
const [totalCount, setTotalCount] = useState(undefined);
// icon
const journalsIcon ={
'issue': 'icon-chuangjianqianbao',
'branch_name': 'icon-fenzhi3',
'assigner': 'icon-chengyuan2',
'status_id': 'icon-xiugaibaobiaomoban',
'fixed_version_id': 'icon-lichengbeiicon2',
'due_date': 'icon-riqi',
'issue_tag': 'icon-biaoji2',
'description': 'icon-xiugaibaobiaomoban',
'subject': 'icon-xiugaibaobiaomoban',
'start_date': 'icon-riqi',
'priority_id': 'icon-youxian',
'attachment': 'icon-xiugaibaobiaomoban'
}
useEffect(()=>{
setSpin(true);
axios.get(`/v1/${owner}/${projectsId}/issues/${index}/journals`,{params:{
category,
page,
limit
}}).then(res=>{
if(res && res.data){
const {journals, total_comment_journals_count, total_count} = res.data;
if(category === 'all'){
const array = [];
let start = undefined;
let isJournalCount = 0;
journals.map((item, index)=>{
!isJournalCount && (start = index)
item.is_journal_detail && (isJournalCount++)
if(!item.is_journal_detail && isJournalCount < 10){
isJournalCount = 0;
return
}
if(isJournalCount >= 10 && (!item.is_journal_detail || journals.length-1 === index)){
array.push({start, count: isJournalCount});
isJournalCount = 0;
}
})
array.map((item, i)=>{
journals[item.start].numCount = item.count-1;
for (let index = 0; index < item.count; index++) {
journals[item.start+index].start = journals[item.start].id;
journals[item.start+index].closeAndSpan = true;
}
})
}
setTotalCount(total_count);
setJournalsCount(total_comment_journals_count);
setJournals(journals);
setSpin(false)
}
})
}, [reload, category, page])
function commentCtx(v){
return <RenderHtml className="break_word_comments imageLayerParent commentRenderHtml" value={v} url={location}/>;
};
//
function deleteComment(id){
axios.delete(`/v1/${owner}/${projectsId}/issues/${index}/journals/${id}`).then(res=>{
if(res && res.data && !res.data.status){
reloadComment();
message.success('删除成功');
}
})
}
// markdown
function cancelMd(){
setShowEdit(false);
}
return(
<div className="commentListBox">
{/* 全部 / 评论 / 操作日志 */}
<div className='typeActionBox mt30 mb20'>
<Radio.Group onChange={(e)=>{setSpin(true);setPage(1);setCategory(e.target.value)}} value={category}>
<Radio value={'comment'} className='typeActionRadio font-14'>评论<span className='journalsCount font-13 ml5'>{journalsCount}</span></Radio>
<Radio value={'operate'} className='typeActionRadio font-14'>操作日志</Radio>
<Radio value={'all'} className='typeActionRadio font-14'>全部</Radio>
</Radio.Group>
</div>
{/* 添加评论 */}
{category !== 'operate' && <div className="pt5 pb30">
{login ? showEdit === 1 ? <EditComment {...props} cancelMd={cancelMd}/> : <div className="addComments">
<img src={getImageUrl(image_url)} alt="" />
<div style={{flex:1}}>
<CheckProfile {...props} sureFunc={()=>{setShowEdit(1)}}>
<Input className='addCommentBox' placeholder="添加评论"/>
</CheckProfile>
</div>
</div> : <div className='unLoginComment font-15 pl20'>
{/* 未登录用户 */}
<a className='mr5 loginBtn' onClick={()=>{showLoginDialog()}}>登录</a>并参与评论与回复
</div>}
</div>}
{/* 评论/操作日志 展示列表 */}
<Spin spinning={spin}>
<div className={`issueCommentsBox ${category === 'comment' && !spin && 'justComment'}`}>
{journals && (journals.length > 0 && journals.map(item=>{return item.is_journal_detail ? (!item.closeAndSpan || item.id === item.start || open === item.start) ? <div key={item.id} className='operationLog'>
{/* 操作日志 */}
<div className='operationCommentBor'></div>
<div className='flexCenter font-14' style={{width: '100%'}}>
<div className='flexCenter opBox'>
<span className='iconBackBox mr10'><i className={`iconfont font-12 ${journalsIcon[item.operate_category]}`}></i></span>
<div className='task-hide' style={{maxWidth: item.closeAndSpan && item.id === item.start? '550px' : '700px'}}>
<Link to={`/${item.user.login}`}><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr5'/></Link>
<Link to={`/${item.user.login}`}>{item.user.name}&nbsp;</Link>
{(item.user.name.length + item.operate_content.length) > 62 ? <Tooltip title={<div><span>{item.user.name} </span><span dangerouslySetInnerHTML={{__html:item.operate_content}}></span></div>}><span dangerouslySetInnerHTML={{__html:item.operate_content}}></span></Tooltip> : <span dangerouslySetInnerHTML={{__html:item.operate_content}}></span>}
</div>
<span className='ml15 timeAgo font-14'>{timeAgo(item.created_at)}</span>
</div>
{(item.closeAndSpan && item.id === item.start) && <a className='primaryColor' onClick={()=>{setOpen(open === item.id ? undefined : item.id)}}>{open === item.id ? `点击收起操作日志` : `已折叠${item.numCount}条, 点击查看`}<i className={`iconfont ${open === item.id ? `icon-sanjiaoxing-up` : 'icon-sanjiaoxing-down'} font-15`}></i></a>}
</div>
</div> : '' : <div key={item.id} className='commentContentBox pb30'>
{/* 评论 */}
<div className='commentOperationBor'></div>
<Link to={`/${item.user.login}`}><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr15'/></Link>
<div className='commentContentRight'>
{/* 判断是否是编辑状态 */}
{(showEdit === 2 && updateId === item.id) ? <div className='mt15 mr20'><EditComment {...props} cancelMd={cancelMd} updateId={updateId} reloadComment={reloadComment} content={item.notes} defaultFileList={item.attachments} showUserImg={false}/></div> : <div>
<div className='commentContent'>
<div className='flexCenter font-14'>
<div>
<Link to={`/${item.user.login}`}>{item.user.name}</Link>
<span className='ml15 timeAgo font-14'>{timeAgo(item.created_at)}</span>
</div>
{login && <div>
{/* 平台管理员/仓库管理员/发布评论者/issue创建者 */}
{(admin || isManager || login === item.user.login || user_id === author.id) && <Popconfirm
placement="bottom"
title={`确定要删除此条评论吗?${item.children_journals.length > 0 ? '子评论也将被一起删除。' : ''}`}
okText="是"
cancelText="否"
onConfirm={() => deleteComment(item.id)}
>
<Button type='link' className='color-grey-89'><i className='iconfont icon-fuzhi-shanchu font-14 mr8'></i>删除</Button>
</Popconfirm>}
{/* 仅评论者可修改 */}
{login === item.user.login && <Button type='link' className='color-grey-89' onClick={()=>{setUpdateId(item.id); setShowEdit(2)}}><i className='iconfont icon-a-bianji12 font-14 mr8'></i>修改</Button>}
<CheckProfile {...props} sureFunc={()=>{setParentId(item.id); setReplyId(item.id); setShowEdit(3)}}><Button type='link' className='color-grey-89'><i className='iconfont icon-a-xiaoxi1 font-14 mr8'></i>回复</Button></CheckProfile>
</div>}
</div>
<div className='contentHtml mb5'>{commentCtx(item.notes)}</div>
{item && item.attachments && item.attachments.length > 0 && <div className='attachmentBox mb5'><Attachment
attachments={item.attachments}
showNotification={showNotification}
canDelete={false}
/></div>}
</div>
</div>}
{showEdit === 3 && replyId === item.id && <div className='contentHtml mr20'><EditComment {...props} cancelMd={cancelMd} parentId={parentId} replyId={replyId} reloadComment={reloadComment}/></div>}
{/* 评论回复部分 */}
{item.children_journals.map(i =>{return <div className='commentReply' key={i.id}>
{(showEdit === 4 && updateId === i.id) ? <div className='mr20'><EditComment {...props} cancelMd={cancelMd} updateId={updateId} reloadComment={reloadComment} content={i.notes} defaultFileList={i.attachments}/></div> : <div>
<div className='flexCenter'>
<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>
{i.reply_user && <span className='ml5 timeAgo mr3'>回复</span>}
<span>{i.reply_user && i.reply_user.name}</span>
<span className='ml15 timeAgo font-14'>{timeAgo(i.created_at)}</span>
</div>
{login && <div>
{/* 平台管理员/仓库管理员/发布评论者/回复评论者/issue创建者 */}
{(admin || isManager || login === i.user.login || (i.reply_user && login === i.reply_user.login) || user_id === author.id) && <Popconfirm
placement="bottom"
title={"确定要删除当前回复吗?"}
okText="是"
cancelText="否"
onConfirm={() => deleteComment(i.id)}
>
<Button type='link' className='color-grey-89'><i className='iconfont icon-fuzhi-shanchu font-14 mr8'></i>删除</Button>
</Popconfirm>}
{/* 仅回复评论者可修改 */}
{login === i.user.login && <Button type='link' className='color-grey-89' onClick={()=>{setUpdateId(i.id); setShowEdit(4)}}><i className='iconfont icon-a-bianji12 font-14 mr8'></i>修改</Button>}
<CheckProfile {...props} sureFunc={()=>{setParentId(item.id);setReplyId(i.id); setShowEdit(5)}}>
<Button type='link' className='color-grey-89'><i className='iconfont icon-a-xiaoxi1 font-14 mr8'></i>回复</Button>
</CheckProfile>
</div>}
</div>
<div className='contentHtml mt5 mb10'>{commentCtx(i.notes)}</div>
{i && i.attachments && i.attachments.length > 0 && <div className='attachmentBox'><Attachment
attachments={i.attachments}
showNotification={showNotification}
canDelete={false}
/></div>}
</div>}
{showEdit === 5 && replyId === i.id && <div className='contentHtml mr20'><EditComment {...props} cancelMd={cancelMd} parentId={parentId} replyId={replyId} reloadComment={reloadComment}/></div>}
</div>})}
</div>
</div>}))}
</div>
</Spin>
{totalCount>limit && <div className='mt20 paginationIssueComment mb20'>
<Pagination simple current={page} pageSize={limit} total={totalCount} onChange={(page)=>setPage(page)}/>
</div>}
{/* 无数据 新建第一条评论时暂无数据隐藏 用户未登录隐藏*/}
{journals && !journals.length && showEdit !== 1 && login && <Nodata _html="暂无数据"/>}
</div>
)
}
export default IssueCommentList;

View File

@ -0,0 +1,195 @@
.typeActionBox{
padding: 10px 20px 4px;
background-color:rgba(241, 243, 252, 0.55);
border-radius:4px;
.typeActionRadio{
color: #333;
display: inline-flex;
align-items: center;
margin-right: 25px;
&.ant-radio-wrapper-checked{
color: $primary-color;
}
}
.journalsCount{
color:#5e6685;
background-color:rgba(70, 106, 255, 0.09);
border-radius:10px;
display: inline-block;
padding: 0px 6px;
}
}
.commentUserImg{
height: 28px;
width: 28px;
border-radius: 50%;
object-fit: cover;
}
.issueCommentsBox{
.iconBackBox{
display: inline-block;
width:24px;
height:24px;
border-radius: 50%;
line-height: 24px;
text-align: center;
background-color:#f2f3f5;
}
.operationLog, .commentContentBox{
position: relative;
min-height: 62px;
display: flex;
>.flexCenter, >a{
z-index: 2;
}
&::before, &::after{
content: '';
width: 1px;
height: 50%;
position: absolute;
background: #eee;
left: 12px;
z-index: 1;
}
&::after{
background: #eee;
top: 50%;
}
}
.commentContentBox+.operationLog .operationCommentBor, .operationLog+.commentContentBox .commentOperationBor{
border-top: 1px solid #eee;
width: 98.5%;
position: absolute;
left: 12px;
}
.operationLog+.commentContentBox{
&>a, &>.commentContentRight{
margin-top: 25px;
}
}
.timeAgo{
color:#acb0bf;
}
.operationLog:last-child::after, .operationLog:first-child::before, .commentContentBox:last-child::after, &.justComment .commentContentBox::before, &.justComment .commentContentBox::after{
display: none;
}
.operationLog:first-child, .commentContentBox+.operationLog{
margin-top: -15px;
}
.commentContentBox:last-child::before{
height: 35%;
}
.flexCenter{
display: flex;
align-items: center;
justify-content: space-between;
}
.commentRenderHtml.markdown-body p{
font-size: 14px !important;
}
.commentReply{
padding: 15px 0 0 20px;
background-color: rgba(238, 240, 246, 0.41);
&>div{
padding-bottom: 5px;
}
&+.commentReply>div{
margin-top: -15px;
padding-top: 15px;
border-top: 1px dashed #eee;
}
}
.opBox{
justify-content: flex-start;
// flex: 1;
// width: 100%;
}
.commentContentBox{
display: flex;
}
.commentContentRight{
flex: 1;
background-color:#fafafc;
border:1px solid rgba(42, 97, 255, 0.23);
border-radius:6px;
position: relative;
top: -6px;
padding: 10px 15px 16px;
&::before{
content: '';
display: block;
position: absolute;
top: 13px;
left: -14px;
width: 0;
height: 0;
overflow: hidden;
font-size: 0;
line-height: 0;
border: 7px;
border-style: solid;
border-color: transparent rgba(42, 97, 255, 0.23) transparent transparent;
}
&::after{
content: '';
display: block;
width: 0;
height: 0;
overflow: hidden;
font-size: 0;
line-height: 0;
border: 6px;
border-style: solid;
border-color: transparent #fafafc transparent transparent;
position: absolute;
top: 14px;
left: -11px;
}
}
}
.primaryColor, .primaryColor:link{
color: $primary-color;
}
.attachmentBox{
margin-top: -8px;
}
.color-grey-89{
color: #898d9d;
}
// 添加评论样式
.unLoginComment{
height:58px;
line-height: 58px;
background-color:rgba(241, 243, 252, 0.55);
border-radius:4px;
color: rgba(84, 87, 103, 1);
.loginBtn{
color: $primary-color;
}
}
.quillFlagBox{
color: red;
margin-top: -10px;
margin-bottom: 5px !important;
}
.addComments{
display: flex;
align-items: center;
background-color:rgba(241, 243, 252, 0.55);
border-radius:4px;
padding:7px 10px;
&>img{
height: 28px;
width: 28px;
border-radius: 50%;
margin-right: 13px;
}
}
.addCommentBox{
width: 100%;
height: 30px;
border: none;
}
.paginationIssueComment{
text-align: center;
}

View File

@ -0,0 +1,22 @@
import React ,{ useState } from 'react';
import { Tooltip } from 'antd';
import { CopyToClipboard } from 'react-copy-to-clipboard';
function Copy({ children , value }){
const [ title , setTitle ] = useState("点击复制链接");
let host = window.location.hostname === "localhost" ? "testforgeplus.trustie.net" : window.location.hostname;
let protocol = window.location.protocol;
return(
<CopyToClipboard text={`${protocol}//${host}${value}`}
onCopy={() => setTitle("复制成功")}>
<Tooltip
placement="bottom"
title={title}
>
{children}
</Tooltip>
</CopyToClipboard>
)
}
export default Copy;

View File

@ -0,0 +1,91 @@
import React from 'react';
import { getImageUrl } from 'educoder';
import gold from '../Img/gold.png';
import { Link } from "react-router-dom";
import Copy from '../Component/copy';
import { Tooltip } from 'antd';
// issue
function Datas({checkbox ,item , projectsId,owner}){
function statusTag(name){
switch (name) {
case "低":
return "status low";
case "正常":
return "status normals";
case "高":
return "status hight";
default:
return "status urgent";
}
}
function renderName(list){
let l = list.map(i=>{return i.name});
return l.join(",");
}
return(
<div>
<div className="issuedetail">
{checkbox}
<div style={{flex:1}}>
<div className="idetails">
<span className={statusTag(item.priority_name)}>{item.priority_name}</span>
{/* <img src={issue} alt="" width="16px" className="mr5" /> */}
<Link to={`/${owner}/${projectsId}/issues/${item.project_issues_index}`} style={{maxWidth:`${item.tags ? 735 - item.tags.length*120 : 735}px`}} title={item.subject}>{item.subject}</Link>
{
item.tags && item.tags.length>0?
item.tags.map((i,k)=>{
return(
<span style={{backgroundColor: `${i.color}`}} className="ml8 tagscolor task-hide" title={i.name}>{i.name}</span>
)
})
:""
}
</div>
<div>
<div className="infos">
<div className="ilog">
{ item.project_issues_index &&
<Copy value={`/${owner}/${projectsId}/issues/${item.project_issues_index}`}><span className="number">#{item.project_issues_index}</span></Copy>
}
</div>
<Link to={`/${item.author && item.author.login}`}><i className="iconfont icon-chengyuan2 mr3 font-12" style={{color:'#898d9d'}}></i></Link>
<span className="mr12"><Link style={{color:"#898d9d"}} to={`/${item.author && item.author.login}`}>{item.author && item.author.name}</Link></span>
<span className="mr12">{item.created_at} 发布</span>
<span className="mr20">{item.updated_at}更新</span>
{item.blockchain_token_num && <span className="mr30"><img src={gold} alt="" width="13px" className="mr3"/>{item.blockchain_token_num}</span>}
{item.milestone_name && <span style={{maxWidth:item.blockchain_token_num ? "261px":"340px"}} title={item.milestone_name} className="task-hide"><i className="iconfont icon-lichengbeiicon1 font-12 mr3"></i>{item.milestone_name}</span> }
</div>
</div>
</div>
</div>
<div className="issuecondition">
{
item.assigners && item.assigners.length > 0 ?
<Tooltip title={renderName(item.assigners)} placement="bottomRight">
<div className={item.assigners.length > 1 ?"principal hovers":"principal"}>
{/* {
item.assigners.map((i,k)=>{
return(
k<5 && <Link to={`/${i.login}`} style={{right:`${(item.assigners.length-k-1)*(22-7)}px`,zIndex:k+1}}><img src={getImageUrl(i.image_url)} alt="" /></Link>
)
})
} */}
<span className="task-hide" style={{wordBreak:'break-all'}}>{renderName(item.assigners)}</span>
</div>
</Tooltip>
:""
}
<div style={{color:item.status_name === "已解决" ?"#28bd6c":'#40424a'}}>{item.status_name}</div>
<div className="commentnum">
<Link to={`/${owner}/${projectsId}/issues/${item.project_issues_index}#commentList`}><i className="iconfont icon-a-xiaoxi1 mr5 font-15"></i>{item.comment_journals_count}</Link>
</div>
</div>
</div>
)
}
export default Datas;

View File

@ -0,0 +1,140 @@
import React,{useRef,useEffect, useState , forwardRef } from 'react';
import { Dropdown , Calendar , Select, Radio, Col, Row } from 'antd';
import { findDOMNode } from 'react-dom';
import moment from 'moment';
const { Group , Button } = Radio;
function Date({name , today , setDate , editFlag},ref){
const [ visible , setVisible ] = useState(false);
const [ time , setTime ] = useState();
useEffect(()=>{
if(today){
setTime(today);
}
},[today])
const refFa = useRef(null);
const refBox = useRef(null);
useEffect(() => {
document.addEventListener('click', clickMe , false);
}, [])
const clickMe = ({ target }) => {
//
const faComponent = findDOMNode(refFa.current);
const boxComponent = findDOMNode(refBox.current);
if (faComponent && boxComponent) {
const isChild = faComponent.contains(target);
const isBox = boxComponent.contains(target);
if(!isChild && !isBox){
setVisible(false);
}
}
}
function onSelect(t){
setTime(moment(t).format('YYYY-MM-DD'));
setDate(moment(t).format('YYYY-MM-DD'));
setVisible(false);
}
const overlay=(
<div ref={refFa} style={{ width: 280, border: '1px solid #d9d9d9', borderRadius: 4 ,backgroundColor:"#fff",marginTop:"-10px"}}>
<Calendar
fullscreen={false}
headerRender={({ value, type, onChange, onTypeChange }) => {
const start = 0;
const end = 12;
const monthOptions = [];
const current = value.clone();
const localeData = value.localeData();
const months = [];
for (let i = 0; i < 12; i++) {
current.month(i);
months.push(localeData.monthsShort(current));
}
for (let index = start; index < end; index++) {
monthOptions.push(
<Select.Option className="month-item" key={`${index}`}>
{months[index]}
</Select.Option>,
);
}
const month = value.month();
const year = value.year();
const options = [];
for (let i = year - 10; i < year + 10; i += 1) {
options.push(
<Select.Option key={i} value={i} className="year-item">
{i}
</Select.Option>,
);
}
return (
<div style={{ padding: 10 }}>
<Row type="flex" justify="space-between">
<Col>
<Group size="small" onChange={e => onTypeChange(e.target.value)} value={type}>
<Button value="month">日期</Button>
<Button value="year">月份</Button>
</Group>
</Col>
<Col>
<Select
size="small"
dropdownMatchSelectWidth={false}
className="my-year-select"
style={{ width: 80 }}
onChange={newYear => {
const now = value.clone().year(newYear);
onChange(now);
}}
value={String(year)}
>
{options}
</Select>
</Col>
<Col>
<Select
size="small"
dropdownMatchSelectWidth={false}
value={String(month)}
onChange={selectedMonth => {
const newValue = value.clone();
newValue.month(parseInt(selectedMonth, 10));
onChange(newValue);
}}
>
{monthOptions}
</Select>
</Col>
</Row>
</div>
);
}}
onSelect={onSelect}
/>
</div>
)
return(
<Dropdown
placement={"bottomLeft"}
overlay={overlay}
visible={visible}
trigger={['click']}
>
<li>
<span>
{name}
{!editFlag && <a ref={refBox} onClick={()=>setVisible(visible ? false : true)}><i className="iconfont icon-riqi font-14" style={{color:"#898d9d"}}></i></a> }
</span>
<p className={time ? "operatevalue color-grey-3 task-hide" : "operatevalue task-hide"}>{time || "未设置"}</p>
</li>
</Dropdown>
)
}
export default forwardRef(Date);

View File

@ -0,0 +1,32 @@
import React from "react";
import Modals from '../../Component/PublicModal/Index';
import { AlignTop } from '../../Component/layout';
import { Button } from 'antd';
function DelBox({visible,onCancel,onSuccess,content,title}){
return(
<Modals
visible={visible}
onCancel={onCancel}
title={title || "删除疑修"}
btn={
<div>
<Button size={'large'} onClick={onCancel}>取消</Button>
<Button type={"danger"} size={"large"} onClick={onSuccess}>确认删除</Button>
</div>
}
>
<div className="desc">
<AlignTop className="deldesc">
<i className="iconfont icon-jinggao1 mr10 font-20 red"></i>
{ content ?
content
:
<div style={{paddingTop:"3px"}}><p className="font-15 mb20">您确定要删除所有选中的疑修</p><p className="color-grey-6">此操作将清空所有已选中的疑修请谨慎操作</p></div>
}
</AlignTop>
</div>
</Modals>
)
}
export default DelBox;

View File

@ -0,0 +1,49 @@
import React,{useState, useEffect, useRef, useImperativeHandle,forwardRef} from 'react';
import { Dropdown } from 'antd';
import { findDOMNode } from 'react-dom';
function Drop({overlay , children , placement, overlayClassName},ref){
const [ visible , setVisible ] = useState(false);
const refFa = useRef(null);
const refBox = useRef(null);
useImperativeHandle(ref, () => ({
clearVisible: (v) => {
// dropmenuchoose0
setVisible(v);
}
}))
useEffect(() => {
document.addEventListener('click', clickMe , false);
}, [])
const clickMe = ({ target }) => {
//
const faComponent = findDOMNode(refFa.current);
const boxComponent = findDOMNode(refBox.current);
if (faComponent && boxComponent) {
const isChild = faComponent.contains(target);
const isBox = boxComponent.contains(target);
if(!isChild && !isBox){
setVisible(false);
}
}
}
return(
<li>
<Dropdown
placement={placement}
visible={visible}
overlay={<div ref={refFa}>{overlay}</div>}
trigger={['click']}
overlayClassName={overlayClassName}
>
<span className="dropspan" ref={refBox} onClick={()=>setVisible(visible ? false : true)}>
{children}
</span>
</Dropdown>
</li>
)
}
export default forwardRef(Drop);

View File

@ -0,0 +1,210 @@
import React ,{ useState , useEffect , useRef } from 'react';
import { findDOMNode } from 'react-dom';
import { Dropdown , Menu , Input , message} from 'antd';
import { getImageUrl } from 'educoder';
const { Search } = Input;
/**
*
* @param {placeholder} placeholder:默认显示内容为空时
* @param {editFlag} editFlag:是否有编辑权限
* @param {searchFlag} searchFlag:是否显示搜索框
* @param {selectValueList} selectValueList:将选中的list保存
* @param {searchFunc} searchFunc:搜索方法
* @param {onAdd} onAdd:标记管理
* @param {headImg} headImg:是否显示头像
* @param {menus} menus:下拉列表
* @param {chooseFunc} chooseFunc:选择下拉列表
* @param {double} double:undefined为单选有值就是最多选的数量
* @param {colorFlag} colorFlag:选中值需根据颜色显示背景
* @param {mustFlag} mustFlag:必须选择一个
* @param {removeFlag} removeFlag:移除按钮
* @returns
*/
function DropMenu({
placeholder ="未设置",
editFlag,
searchFlag,
selectValueList,
searchFunc,
onAdd,
headImg,
menus,
chooseFunc,double,colorFlag,mustFlag,removeFlag
}){
const [ visible , setVisible ] = useState(false);
const [ searchValue , setSearchValue ]= useState(undefined);
const [ valuesId , setValuesId ] = useState([]);
const [ valuesName , setValuesName ] = useState([]);
const refFa = useRef(null);
const refBox = useRef(null);
// id
useEffect(()=>{
if(selectValueList && selectValueList.length > 0 ){
renderSelectList(selectValueList);
}else{
setValuesId([])
}
},[selectValueList])
function renderSelectList(list){
let a = list && list.length > 0 && list.map((i,k)=>{
return i.id ? i.id.toString() : i.name
})
setValuesId(a);
setValuesName(list);
}
useEffect(() => {
document.addEventListener('click', clickMe , false);
}, [])
const clickMe = ({ target }) => {
//
const faComponent = findDOMNode(refFa.current);
const boxComponent = findDOMNode(refBox.current);
if (faComponent && boxComponent) {
const isChild = faComponent.contains(target);
const isBox = boxComponent.contains(target);
if(!isChild && !isBox){
setVisible(false);
}
}
}
//
function changeSearchvalue(e){
setSearchValue(e.target.value);
searchFunc(e.target.value);
}
function chooseMenu(i){
let list = selectValueList;
let re = [];
re = list && list.length > 0 ? list:[];
let filter = [];
if(i.id){
filter = list.filter(j=>j.id === i.id);
}else{
filter = list.filter(j=>j.name === i.name);
}
if(filter && filter.length > 0){
if(!mustFlag){
if(i.id){
re = list.filter(j=>j.id !== i.id);
}else{
re = list.filter(j=>j.name !== i.name);
}
renderSelectList(re);
chooseFunc(re);
!double && setVisible(false);
}else{
setVisible(false);
}
}else{
if(double && (selectValueList && selectValueList.length >= double)){
message.info(`最多只能添加${double}${placeholder}`);
return;
}
if(double){
re.push(i);
}else{
re = [i];
setVisible(false);
}
renderSelectList(re);
chooseFunc(re);
}
}
function showRemoveFunc(item){
let l = selectValueList ;
l = l.filter(i=>(i.id ? i.id.toString() : i.name) !== item.toString());
renderSelectList(l);
chooseFunc(l);
}
function renderNames(nameArrs){
return <React.Fragment>
{
nameArrs && nameArrs.length>0?
nameArrs.map((i,k)=>{
return(
<p style={{display:"flex",alignItems:"center"}} className={removeFlag?"removeFlag":''}>
{i.image_url && <img src={getImageUrl(i.image_url)} alt="" width="28px" height="28px" style={{borderRadius:"50%",marginTop:"5px"}} className="mr5"/>}
{
colorFlag ?
(
colorFlag === "2" ?
<span className={"colorsquare task-hide"} style={{backgroundColor:`${i.color}`}}>{i.name}</span>
:
<span className={"colorsborder task-hide"} style={{borderColor:`${i.color}`,color:`${i.color}`}}>{i.name}</span>
)
:
<span className={"task-hide"}>{i.name}</span>
}
{ removeFlag && <a className="removeicon" onClick={()=>showRemoveFunc(i.id || i.name)}><i className="iconfont icon-shanchu8 font-14"></i></a>}
</p>
)
})
:<span>{placeholder}</span>
}
</React.Fragment>
}
return(
<li>
<span>
{placeholder}
{!editFlag &&
<a ref={refBox} onClick={()=>setVisible(visible ? false : true)}>
<i className="iconfont icon-a-bianji12 font-13" style={{color:"#898d9d"}}></i>
</a>
}
</span>
<Dropdown
visible={visible}
overlayClassName={"overlayStyle"}
placement="bottomLeft"
trigger={['click']}
overlay={
<div ref={refFa}>
{ searchFunc &&
<div className="searchbox">
<Search
placeholder={`搜索${placeholder}`}
value={searchValue}
onChange={changeSearchvalue}
style={{marginRight:"18px"}}
/>
</div>
}
{
menus && menus.length >0?
<Menu className="piecemenu" selectedKeys={valuesId}>
{
menus.map((i,k)=>{
return(
<Menu.Item key={i.id || i.name} onClick={()=>chooseMenu(i)}>
{ headImg && <img src={getImageUrl(i.image_url)} alt="" width="22px" className="mr5 radius"/>}
{i.color && <span style={{backgroundColor:i.color}} className="colorpiece"></span>}
<span className="task-hide">{i.name}</span>
</Menu.Item>
)
})
}
</Menu>
:
<div className="menusEmpty">
<p>{searchValue ? <span>暂无{placeholder}{searchValue}</span>: `暂无${placeholder}`}</p>
</div>
}
{ onAdd && <div className="pl35 pr20 pb20"><a className="color-blue font-15" onClick={()=>{setVisible(false);onAdd()}}><i className="iconfont icon-a-bianji12 font-14 mr5"></i>创建标记</a></div>}
</div>
}>
<div className={selectValueList && selectValueList.length > 0 ? "operatevalue color-grey-3":"operatevalue"}>{renderNames(selectValueList)}</div>
</Dropdown>
</li>
)
}
export default DropMenu;

View File

@ -0,0 +1,90 @@
import React ,{ useState , useRef , useEffect } from 'react';
import { Input , Menu , Icon } from 'antd';
import { getImageUrl } from 'educoder';
import Drop from './drop';
const { Search } = Input;
/**
* @param {name} 未选择时显示的内容
* @param {lists} 下拉列表从接口获取需一一填充
* @param {imgControl} 控制是否显示头像
* @param {size} 下拉框宽度small120px,large:260px
* @param {ids} 默认选择的项的id数组
* @param {search} 搜索方法
* @param {chooseFunc} 选择项后需调用列表的查询方法
* @param {update} 编辑状态
* @param {double} 是否可以多选
*/
function Menus({name, ids , lists , size , imgControl , searchFunc , chooseFunc , update , double ,names },ref){
const dropRef = useRef(null);
const [ chooseValue , setChooseValue ] = useState([]);
const [ showValue , setShowValue ] = useState(undefined);
useEffect(()=>{
setChooseValue(ids);
setShowValue(names);
},[ids,names])
// menus
function changeMenusValue(i){
let l = ids;
let id = i.id.toString();
if(double){
l = ids ? ids.split(",") : [] ;
let nameArr = names ? names.split(",") : [] ;
if(l && l.indexOf(id)>=0){
l = l.filter(k=>k.toString() !== id);
nameArr = nameArr.filter(k=>k.toString() !== i.name);
}else{
l.push(id);
nameArr.push(i.name);
}
setChooseValue(l);
setShowValue(nameArr.join(","));
chooseFunc(l,nameArr.join(","));
}else{
if(l && l.indexOf(id)>=0){
setChooseValue([]);
setShowValue(undefined);
chooseFunc([]);
}else{
setChooseValue([`${i.id}`]);
setShowValue([i.name]);
chooseFunc([id],i.name);
}
dropRef.current && dropRef.current.clearVisible(false);
}
}
function menu(data){
return <div className={`overlaydrop ${size}`}>
{ size !== "small" &&
<div className="pb10"><Search placeholder={`搜索${name}`} onChange={(e)=>searchFunc(e.target.value)}/></div>
}
{
data && data.length>0 ?
<Menu selectedKeys={chooseValue}>
{
data.map((i,j)=>{
return <Menu.Item key={i.id}>
{imgControl && <img src={getImageUrl(i.image_url)} alt=""/>}
{i.color && <span style={{backgroundColor:i.color,marginRight:"0px"}} className="colorpiece"></span>}
<span className="task-hide" onClick={()=>changeMenusValue(i)}>{i.name}</span>
</Menu.Item>
})
}
</Menu>
:
<div className="pl15">暂无{ids ? <span>'{name}'</span>: name}</div>
}
</div>
}
return(
<Drop ref={dropRef} overlay={menu(lists)} placement={"bottomRight"} >
<span className="task-hide">{showValue || (update ? `更换${name}` : name) }</span>
<Icon type="caret-down" className="ml5 color-grey-6" />
</Drop>
)
}
export default Menus;

View File

@ -0,0 +1,126 @@
import React , { forwardRef , useState } from 'react';
import { Form , Input , Button } from 'antd';
import MDEditor from "../../../modules/tpm/challengesnew/tpm-md-editor";
import Upload from "../../Upload/Index";
import Attachments from "../../Upload/attachment";
import UploadImg from '../Img/UploadImg.png';
function NewPanel(props,ref){
const [ description , setDescription ] = useState("");
const [ fileList , setFileList] = useState(undefined);
const [ attachments , setAttachments ] = useState([]);
const [ receivers_login , setReceiversLogin ] = useState(undefined);
const { createFunc , title , desc , files , onCancel , owner , projectsId } = props;
const { form: { getFieldDecorator, validateFields , setFieldsValue } } = props;
useState(()=>{
title && setTimeout(()=>{
setFieldsValue({subject:title});
},100)
desc && setDescription(desc);
files && setAttachments(files);
},[title , desc , files])
function onContentChange(value){
setDescription(value);
}
function UploadFunc(f){
let list = [];
let arr = [];
if(files && files.length>0 ){
arr = files.map(i=>{return i.id});
}
list = arr && arr.length>0 ? f.concat(arr) : f ;
setFileList(list);
};
function deleteLoad(id){
let arr = [];
let list = files
if(list && list.length>0 ){
arr = list.filter(i=>i.id !== id);
}
setFileList(arr.map(i=>{return i.id}));
}
function changeAtWhoLoginList(loginList){
let list = new Set(receivers_login);
loginList.map(item => list.add(item));
setReceiversLogin(Array.from(list));
};
function sureFunc(){
validateFields((error,values)=>{
if (!error) {
createFunc(values,fileList,receivers_login,description);
}
})
}
function cancelFunc(){
if(onCancel){
onCancel();
}else{
window.history.back(-1);
}
}
return(
<React.Fragment>
<Form className="explain">
<Form.Item>
{getFieldDecorator("subject",{
rules:[{required:true,message:"请输入疑修标题"}]
})(
<Input placeholder="标题" maxLength={100}/>
)}
</Form.Item>
</Form>
<div style={{position: 'relative'}}>
<MDEditor
placeholder={"请输入描述信息"}
height={392}
mdID={"order-new-description"}
initValue={description}
onChange={onContentChange}
className="mt20"
isCanAtme = {true}
owner = {owner}
projectsId = {projectsId}
changeAtWhoLoginList = {changeAtWhoLoginList}
></MDEditor>
</div>
<div className="pb20">
<Upload
className="commentStyle mt20"
isComplete={true}
load={UploadFunc}
icon={
<img
src={UploadImg}
width="58"
alt=""
style={{ marginBottom: 15 }}
/>
}
size={100}
showNotification={props.showNotification}
/>
{attachments && attachments.length > 0 &&
<Attachments
attachments={attachments}
showNotification={props.showNotification}
canDelete={true}
deleteLoad={deleteLoad}
></Attachments>
}
</div>
<div style={{display:"flex"}}>
<Button type="primary" className="operateButton" style={{width:"100px"}} onClick={sureFunc}>{ title ? "保存" :"创建"}</Button>
<Button className="ml30" style={{width:"100px"}} onClick={cancelFunc}>取消</Button>
</div>
</React.Fragment>
)
}
export default forwardRef(NewPanel);

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

BIN
src/forge/Issues/Img/pr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,514 @@
import React , { useEffect , useState , forwardRef , useRef } from 'react';
import { Spin , Form , InputNumber, message } from 'antd';
import { getImageUrl } from 'educoder';
import { Link } from 'react-router-dom';
import { Box , LongWidth } from '../../Component/layout';
import RenderHtml from "../../../components/render-html";
import DropMenu from '../Component/dropMenu';
import NewPanel from '../Component/newPanel';
import Attachments from "../../Upload/attachment";
import DelBox from '../Component/delBox';
import Date from '../Component/date';
import moment from 'moment';
import Copy from '../Component/copy';
import axios from 'axios';
import CommentList from '../Component/comments/list';
import Claims from '../../claims/claims';
import ChooseMenu from '../Component/chooseMenu';
function Details(props){
const owner = props.match.params.owner;
const projectsId = props.match.params.projectsId;
const index = props.match.params.index;
const [ details , setDetails ] = useState(undefined);
const [ statusList , setStatusList ] = useState([]);
const [ prioritiesList , setPrioritiesList ] = useState([]);
const [ chargeList , setChargeList ] = useState([]);
const [ charge , setCharge ] = useState(undefined);
const [ millstoneList , setMillstoneList ] = useState([]);
const [ millstone , setMillstone ] = useState(undefined);
const [ tagList , setTagList ] = useState(undefined);
const [ tag , setTag ] = useState(undefined);
const [ branchList , setBranchList ] = useState(undefined);
const [ branch , setBranch ] = useState(undefined);
const [ orderId , setOrderId ] = useState(undefined);
const [ delVisible , setDelVisible] = useState(false);
const [ edit , setEdit] = useState(false);
const [ copy , setCopy] = useState(false);
const [ start_date, setStartDate ] = useState("");
const [ due_date, setDueDate ] = useState("");
const [ commentReload, setCommentReload] = useState(undefined);
const [ editFlag , setEditFlag ] = useState(false);
const [ amountEditFlag , setAmountEditFlag ] = useState(false);
const [ assigner_choose , setAssigner_choose ] = useState([]);
const [ prioritie_choose , setPrioritie_choose ] = useState([]);
const [ status_choose , setStatus_choose ] = useState([]);
const [ tag_choose , setTag_choose ] = useState([]);
const [ millstone_choose , setMillstone_choose ] = useState([]);
const [ branch_choose , setBranch_choose ] = useState([]);
const [ rewardAmount , setRewardAmount ] = useState(undefined);
const [ rewardFlag , setRewardFlag ] = useState(false);
const pathname = props.history.location.pathname;
const permission = props && props.projectDetail && props.projectDetail.permission;
let colors = ["#1abcb1","#28be6c","#e67e22","#db3d1d"];
const {projectDetail , open_blockchain ,current_user } = props;
useEffect(()=>{
if(pathname === `/${owner}/${projectsId}/issues/${index}/copy`){
setEdit(true);
setCopy(true);
}else{
setEdit(false);
setCopy(false);
}
},[pathname])
useEffect(()=>{
if(projectDetail && details){
const { author, name} = projectDetail;
document.title = `${details.subject}-疑修-${author.name}/${name}`;
}
},[details,projectDetail])
useEffect(()=>{
if(index){
Init();
}
},[index])
function Init(){
const url = `/v1/${owner}/${projectsId}/issues/${index}`;
axios.get(url).then(result=>{
if(result && result.data){
let data = result.data;
setDetails(data);
let per = data && !data.user_permission;
setEditFlag(per);
const aut = current_user && data.author && (data.author.login === current_user.login);
const con = data.pull_fixed === false && aut;
setAmountEditFlag(con);
setAssigner_choose(data.assigners);
if(data.priority && data.priority.id){
setPrioritie_choose([{...data.priority,color:colors[data.priority.id-1]}]);
}else{
setPrioritie_choose([]);
}
setStatus_choose(data.status && data.status.id ? [data.status]:[]);
setTag_choose(data.tags);
setMillstone_choose(data.milestone && data.milestone.id ? [data.milestone]:[]);
setBranch_choose(data.branch_name ? [{name:data.branch_name}] : []);
setRewardAmount(data.blockchain_token_num);
data.start_date && setStartDate(moment(data.start_date).format('YYYY-MM-DD'));
data.due_date && setDueDate(moment(data.due_date).format('YYYY-MM-DD'));
setOrderId(data.id);
}
}).catch(error=>{
console.log(error);
})
}
function statusTag(name){
switch (name) {
case "低":
return "status low";
case "正常":
return "status normals";
case "高":
return "status hight";
default:
return "status urgent";
}
}
//
useEffect(()=>{
getStatus();
},[])
function getStatus(){
const url = `/v1/${owner}/${projectsId}/issue_statues`;
axios.get(url).then(result=>{
if(result && result.data){
setStatusList(result.data.statues);
}
})
}
//
useEffect(()=>{
getPriorities();
},[])
function getPriorities(){
const url = `/v1/${owner}/${projectsId}/issue_priorities`;
axios.get(url).then(result=>{
if(result && result.data){
let priorities = result.data.priorities;
let array = addColorForPriorities(priorities);
setPrioritiesList(array);
}
})
}
function addColorForPriorities(list){
let array =[];
if(list && list.length>0){
array = list.map((i,k)=>{
let arr = {...i,color:colors[k]}
return arr;
})
}
return array;
}
//
useEffect(()=>{
getCharge();
},[charge])
function getCharge(){
const url = `/v1/${owner}/${projectsId}/collaborators`;
axios.get(url,{params:{keyword:charge,only_name:true}}).then(result=>{
if(result && result.data){
setChargeList(result.data.collaborators);
}
})
}
//
useEffect(()=>{
getMillstone();
},[millstone])
function getMillstone(){
const url = `/v1/${owner}/${projectsId}/milestones`;
axios.get(url,{params:{keyword:millstone,only_name:true}}).then(result=>{
if(result && result.data){
setMillstoneList(result.data.milestones);
}
})
}
//
useEffect(()=>{
getSign();
},[tag])
function getSign(){
const url = `/v1/${owner}/${projectsId}/issue_tags`;
axios.get(url,{params:{keyword:tag,only_name:true}}).then(result=>{
if(result && result.data){
setTagList(result.data.issue_tags);
}
})
}
//
useEffect(()=>{
getBranch(branch);
},[branch])
function getBranch(branch){
if(branch){
let b = [...branchList];
let l = b.filter(i=>i.name.indexOf(branch)>-1);
setBranchList(l);
return ;
}
const url = `/${owner}/${projectsId}/branches.json`;
axios.get(url,{params:{keyword:tag}}).then(result=>{
if(result && result.data){
setBranchList(result.data);
}
})
}
// (!copy:)
function saveForeach(c,s,p,t,m,b,sDate,eDate,rewardAmount){
if(!copy){
const url = `/v1/${owner}/${projectsId}/issues/${index}`;
axios.patch(url,{
branch_name: b ? b.join(",") :undefined,
status_id: s ? s.join(",") : undefined,
priority_id: p ? p.join(",") :undefined,
milestone_id: m ? m.join(",") :undefined,
issue_tag_ids: t?t:undefined,
assigner_ids: c?c:undefined,
start_date:sDate,
due_date:eDate,
blockchain_token_num:rewardAmount
}).then(result=>{
if(result){
Init();
//
setCommentReload(Math.random());
}
}).catch(error=>{})
}
}
function deleteFunc(){
const url = `/v1/${owner}/${projectsId}/issues/${index}`;
axios.delete(url).then(result=>{
if(result){
props.showNotification("疑修删除成功!");
props.history.push(`/${owner}/${projectsId}/issues`);
}
}).catch(error=>{})
}
//
function createFunc(values,fileList,receivers_login,description){
let params ={
subject: values.subject,
attachment_ids: fileList,
receivers_login: receivers_login,
description: description
}
if(!copy){
//
const url = `/v1/${owner}/${projectsId}/issues/${index}`;
axios.patch(url,{
...params
}).then(result=>{
if(result){
props.showNotification("疑修更新成功!");
//
setCommentReload(Math.random());
Init();
setEdit(false);
}
}).catch(error=>{})
}else{
//
const url = `/v1/${owner}/${projectsId}/issues`;
let b = branch_choose && branch_choose.length>0 ? branch_choose.map(i=>{return i.name}) :undefined;
let t = tag_choose && tag_choose.length>0 ? tag_choose.map(i=>{return i.id}) :undefined;
let a = assigner_choose && assigner_choose.length>0 ? assigner_choose.map(i=>{return i.id}) :undefined;
let m = millstone_choose && millstone_choose.length>0 ? millstone_choose.map(i=>{return i.id}) :undefined;
let p = prioritie_choose && prioritie_choose.length>0 ? prioritie_choose.map(i=>{return i.id}) :undefined;
let s = status_choose && status_choose.length>0 ? status_choose.map(i=>{return i.id}) :undefined;
axios.post(url,{
...params,
branch_name: b && b.join(","),
status_id: s && s.join(","),
priority_id: p && p.join(","),
milestone_id: m && m.join(","),
issue_tag_ids: t,
assigner_ids: a,
start_date,
due_date
}).then(result=>{
if(result && result.data && result.data.project_issues_index){
props.showNotification("任务复制成功!");
props.history.push(`/${owner}/${projectsId}/issues/${result.data.project_issues_index}`);
}
}).catch(error=>{})
}
}
//
function onCancel(){
if(copy){
props.history.push(`/${owner}/${projectsId}/issues/${index}`);
}else{
setEdit(false);
}
}
function refreshFunc(){
//
setCommentReload(Math.random());
}
//
function amountBlur(e){
if(!editFlag){
setRewardFlag(false);
const value = e.target.value;
if(value<0){
message.info("请输入大于0的正整数");
setRewardAmount(rewardAmount);
return;
}
setRewardAmount(e.target.value);
if((value && value >= 0) || !value){
saveForeach(undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,value);
}
}
}
function changeAmount(value){
if(amountEditFlag){
setRewardAmount(value);
}else{
setRewardAmount(rewardAmount);
}
}
return(
details ?
<Box>
<LongWidth>
<DelBox
visible={delVisible}
onCancel={()=>setDelVisible(false)}
onSuccess={deleteFunc}
content={<div style={{paddingTop:"3px"}}><p className="font-15 mb20">您确定要删除当前疑修</p></div>}
/>
{
edit ?
<div style={{paddingTop:"25px"}} >
<NewPanel
{...props}
onCancel={onCancel}
title={details.subject}
desc={details.description}
files={copy ? undefined : details.attachments}
createFunc={createFunc}
owner = {owner} projectsId = {projectsId}
/>
</div>
:
<div>
<div className="editpanel">
<div className="detailbanner">
<div className="detailtitle">
<div className="mb12">
{details.priority && <span className={statusTag(details.priority.name)}>{ details.priority.name }</span> }
<p className="name">{details.subject}</p>
</div>
<div>
<div className="ilog mt5">
<Copy value={`/${owner}/${projectsId}/issues/${index}`}><span className="number">#{details.project_issues_index}</span></Copy>
</div>
{
details.author &&
<div>
<Link to={`/${details.author.login}`} className="author"><img src={getImageUrl(details.author.image_url)} alt="" />{details.author.name}</Link>
<span className="ml10" style={{color:"#898d9d"}}>添加于{details.created_at}</span>
</div>
}
</div>
</div>
{
details.user_permission &&
<ul className="detailoperate">
<li><a className="color-blue" onClick={()=>setEdit(true)}><i className="iconfont icon-a-bianji12 font-12 mr5"></i>编辑</a></li>
<li><Link to={`/${owner}/${projectsId}/issues/${index}/copy`} className="color-blue ml20"><i className="iconfont icon-a-fuzhi2 font-12 mr5"></i>复制</Link></li>
<li><a className="color-red ml20" onClick={()=>setDelVisible(true)}><i className="iconfont icon-fuzhi-shanchu font-12 mr5"></i>删除</a></li>
</ul>
}
</div>
<div class="descPanel">
{details.description ?
<RenderHtml className="break_word_comments imageLayerParent" value={details.description} url={props.history.location} />
:
<span className="color-grey-9 ml3 mr3">暂无描述</span>
}
{details.attachments && details.attachments.length > 0 ?
<Attachments
attachments={details.attachments}
showNotification={props.showNotification}
/>
: ""
}
</div>
</div>
<div id="commentList">
<CommentList {...props} issueInfo={details} reload = {commentReload} reloadComment = {()=>{setCommentReload(Math.random())}}/>
</div>
</div>
}
</LongWidth>
<div className="shortwidth mt25">
{orderId && <div className="claimpart"><Claims issue_id={orderId} {...props} refreshFunc={refreshFunc}/></div>}
<ChooseMenu
placeholder="负责人"
menus={chargeList}
searchFunc={(value)=>{setCharge(value)}}
headImg
selectValueList={assigner_choose}
editFlag={editFlag}
chooseFunc={(list)=>{setAssigner_choose(list);let l = list && list.length>0 ?list.map(i=>{return i.id || i.name}):[];saveForeach(l);}}
double={5}
removeFlag
/>
<DropMenu
placeholder="状态"
menus={statusList}
selectValueList={status_choose}
mustFlag
editFlag={editFlag}
chooseFunc={(list)=>{setStatus_choose(list);let l = list && list.length>0 ?list.map(i=>{return i.id || i.name}):[];saveForeach(undefined,l);}}
/>
<DropMenu
placeholder="优先级"
menus={prioritiesList}
selectValueList={prioritie_choose}
colorFlag="1"
mustFlag
editFlag={editFlag}
chooseFunc={(list)=>{setPrioritie_choose(list);let l = list && list.length>0 ?list.map(i=>{return i.id || i.name}):[];saveForeach(undefined,undefined,l);}}
/>
<ChooseMenu
placeholder="标记"
menus={tagList}
searchFunc={(value)=>{setTag(value)}}
selectValueList={tag_choose}
chooseFunc={(list)=>{setTag_choose(list);let l = list && list.length>0 ?list.map(i=>{return i.id || i.name}):[];saveForeach(undefined,undefined,undefined,l);}}
double={3}
colorFlag="2"
removeFlag
editFlag={editFlag}
onAdd={permission && permission !== "Reporter" ? ()=>{getSign();} :false}
owner={owner}
projectsId={projectsId}
/>
<ChooseMenu
placeholder="里程碑"
menus={millstoneList}
searchFunc={(value)=>{setMillstone(value)}}
selectValueList={millstone_choose}
editFlag={editFlag}
auto
chooseFunc={(list)=>{setMillstone_choose(list);let l = list && list.length>0 ?list.map(i=>{return i.id || i.name}):[];saveForeach(undefined,undefined,undefined,undefined,l);}}
/>
<ChooseMenu
placeholder="关联分支"
menus={branchList}
auto
searchFunc={(value)=>{setBranch(value)}}
selectValueList={branch_choose}
editFlag={editFlag}
chooseFunc={(list)=>{setBranch_choose(list);let l = list && list.length>0 ?list.map(i=>{return i.id || i.name}):[];saveForeach(undefined,undefined,undefined,undefined,undefined,l);}}
/>
{
open_blockchain &&
<li style={{paddingBottom:"0px"}}>
<span>
悬赏金额
{/* {!editFlag && <a onClick={editReward}><i className="iconfont icon-a-bianji12 font-13" style={{color:"#898d9d"}}></i></a> } */}
</span>
<InputNumber
onBlur={amountBlur}
placeholder="请输入悬赏金额"
style={{width:"100%",color:rewardAmount?"#333":"#acb0bf"}}
value={rewardAmount}
onChange={changeAmount}
className="borderNo mt5"
disabled={!amountEditFlag}
/>
</li>
}
<Date name="开始日期" today={start_date} setDate={(date)=>{setStartDate(date);saveForeach(undefined,undefined,undefined,undefined,undefined,undefined,date)}} editFlag={editFlag}/>
<Date name="结束日期" today={due_date} setDate={(date)=>{setDueDate(date);saveForeach(undefined,undefined,undefined,undefined,undefined,undefined,undefined,date)}} editFlag={editFlag}/>
</div>
</Box>
:
<div style={{width:"100%",display:"flex",alignItems:"center",justifyContent:'center',height:"400px"}}><Spin /></div>
)
}
export default Form.create()(forwardRef(Details));

View File

@ -0,0 +1,462 @@
import React , { useRef , useState , useEffect } from 'react';
import { Dropdown , Menu , Icon , Input , Checkbox , Pagination, Button , Tooltip ,Spin , DatePicker } from 'antd';
import bj from '../Img/biaoji.png';
import issueEmp from '../Img/issue-big.png';
import { Link } from "react-router-dom";
import AllMenus from '../Component/allMenus';
import CheckProfile from '../../Component/ProfileModal/Profile';
import Datas from '../Component/datas';
import DelBox from '../Component/delBox';
import axios from 'axios';
import cookie from 'react-cookies';
import emp from '../Img/emp.png';
import moment from 'moment';
const { RangePicker } = DatePicker;
const { Search } = Input;
const defaultLimit = 15;
function List(props){
const [ visible , setVisible ] = useState(false);
const [ issueList , setIssueList ] = useState(undefined);
const [ closedCount , setClosedCount ] = useState(0);
const [ openedCount , setOpenedCount ] = useState(0);
const[ aboutMe , setAboutMe ] = useState("all");
const[ value , setValue ] = useState("");
const[ keyword , setKeyword ] = useState(undefined);
const [ category , setCategory] = useState("opened");
const [ begin_date , setBegin] = useState(undefined);
const [ end_date , setEnd] = useState(undefined);
const [ allValue , setAllValue ] = useState([]);
const [ allIds , setAllIds ] = useState([]);
const [ checkAll , setCheckAll ] = useState(false);
const [ names , setNames ] = useState(undefined);
const [ updateIds , setUpdateIds ] = useState(undefined);
const [ updateChooseIds , setUpdateChooseIds ] = useState(undefined);
const [ page , setPage ] = useState(1);
const [ limit , setLimit ] = useState(15);
const [ total , setTotal ] = useState(undefined);
const [ issueTotal , setIssueTotal ] = useState(0);
const [ clearFlag , setClearFlag ] = useState(false);
const [ has_created_issues , setHas_created_issues] = useState(false);
const menuRef = useRef(null);
const owner = props.match.params.owner;
const projectsId = props.match.params.projectsId;
const permission = props && props.projectDetail && props.projectDetail.permission;
const { projectDetail , current_user , open_blockchain} = props;
useEffect(()=>{
if(projectDetail){
const { author, name } = projectDetail;
document.title = `疑修-${author.name}/${name}`;
}
},[projectDetail])
useEffect(()=>{
const datas = cookie.load('issuestates');
let states = datas === "undefined" ? undefined : datas;
console.log(states);
if(states){
setAboutMe(states.participant_category);
setCategory(states.category);
setPage(states.page);
setLimit(states.limit || defaultLimit);
setUpdateIds({...states});
setNames(states.names);
setBegin(states.begin_date);
setEnd(states.end_date);
// if(states.participant_category ==="all" && states.category === "opened" && states.page === 1 && states.limit===defaultLimit)
// Init({...states},states.page,states.names);
}else{
Init();
}
},[])
useEffect(()=>{
let v = allValue && allValue > 0;
if((updateIds && !v && (updateIds.author_id || updateIds.issue_priorities_id || updateIds.issue_tag_ids || updateIds.milestone_id || updateIds.sort_by || updateIds.status_id ||updateIds.assigner_id)) || value || end_date || begin_date){
setClearFlag(true);
}else{
setClearFlag(false);
}
},[updateIds,value,allValue,begin_date,end_date])
// hook
const useUpdateEffect = (fn, inputs) => {
const didMountRef = useRef(false);
useEffect(() => {
if (didMountRef.current) fn();
else didMountRef.current = true;
}, inputs);
};
//
function handleAllDisabled(){
Init(updateIds,page,names);
}
// 使hook
useUpdateEffect(handleAllDisabled, [aboutMe,keyword,category,limit,end_date,updateIds]);
function getDirection(id){
if(!id) return undefined;
if(id === "1" || id === "3" || id === "5"|| id === "7"){
return "desc"
}else{
return "asc"
}
}
function getBy(id){
if(!id) return undefined;
if(id === "1" || id === "2"){
return "issues.created_on"
}else if(id === "3" || id === "4"){
return "issues.updated_on"
}else if(id === "5" || id === "6"){
return "issue_priorities.position"
}else{
return "issues.blockchain_token_num"
}
}
// issue
function Init(params,p,names){
setTotal(undefined);
const datas = cookie.load('issuestates');
let states = datas === "undefined" ? undefined : datas;
if(states){
cookie.remove('issuestates');
}
const url = `/v1/${owner}/${projectsId}/issues`;
axios.get(url,{
params:{
...params,
page:p || 1,
keyword,
participant_category:aboutMe,
category,
limit,
begin_date,end_date,
sort_direction:getDirection(params && params.sort_by),
sort_by:getBy(params && params.sort_by),names:undefined
}
}).then(result=>{
if(result){
setIssueList(result.data.issues);
setTotal(result.data.total_count);
setClosedCount(result.data.closed_count);
setOpenedCount(result.data.opened_count);
const ids = result.data.issues.length>0 ? result.data.issues.map(i=>{return i.id}) : [];
setAllIds(ids);
setIssueTotal(result.data.total_issues_count);
setHas_created_issues(result.data.has_created_issues);
const d = {...params,keyword,participant_category:aboutMe,category,limit,page:p || 1,
sort_direction:getDirection(params && params.sort_by),begin_date,end_date,
sort_by:getBy(params && params.sort_by),names:names};
let inFifteenMinutes = new Date(new Date().getTime() + 24 * 3600 * 1000);
cookie.save('issuestates', {...d},{ expires: inFifteenMinutes,path:`/` });
}
}).then(error=>{})
}
//
const menu = (
<Menu selectedKeys={[`${aboutMe}`]} onClick={chooseAboutMe}>
<Menu.Item key={"all"}>全部</Menu.Item>
<Menu.Item key={"aboutme"}><Tooltip title="指我创建的、我负责的和@我的疑修">与我相关</Tooltip></Menu.Item>
<Menu.Item key={"assignedme"}>我负责的</Menu.Item>
<Menu.Item key={"authoredme"}>我创建的</Menu.Item>
<Menu.Item key={"atme"}>@我的</Menu.Item>
</Menu>
)
function chooseAboutMe(e){
setPage(1);
// setCategory("opened");
setAboutMe(e.key);
}
//
function clearCondition(){
setKeyword(undefined);
setAboutMe("all");
setCategory("opened");
setUpdateIds(undefined);
setAllValue([]);
setPage(1);
setValue(undefined);
setNames(undefined);
setEnd(undefined);
setBegin(undefined);
// if(!value){
// Init();
// }
//
menuRef.current && menuRef.current.clearChoose();
}
// issue
function chooseAll(e){
setCheckAll(e.target.checked);
//
menuRef.current && menuRef.current.clearChoose();
if(e.target.checked){
setAllValue(allIds);
}else{
setAllValue([]);
}
}
// issue
function checkIssues(value){
//
menuRef.current && menuRef.current.clearChoose();
setAllValue(value);
if(value.length === allIds.length){
setCheckAll(true);
}else{
setCheckAll(false);
}
}
//
function cancelUpdate(){
setAllValue([]);
setCheckAll(false);
//
menuRef.current && menuRef.current.clearChoose();
}
// allValue updateIds
function sureUpdate(){
const url = `/v1/${owner}/${projectsId}/issues/batch_update`;
axios.patch(url,{
assigner_ids: updateChooseIds && updateChooseIds.assigner_id && updateChooseIds.assigner_id.split(","),
ids: allValue,
issue_tag_ids: updateChooseIds && updateChooseIds.issue_tag_ids && updateChooseIds.issue_tag_ids.split(","),
milestone_id: updateChooseIds && updateChooseIds.milestone_id,
priority_id: updateChooseIds && updateChooseIds.issue_priorities_id,
status_id: updateChooseIds && updateChooseIds.status_id
}).then(result=>{
if(result){
setUpdateIds(undefined);
setAllValue([]);
setTotal(undefined);
Init();
cancelUpdate();
}
}).catch(error=>{})
}
//
function changepage(p){
setPage(p);
Init(updateIds,p,names);
if (document) { //
if (document.documentElement || document.body) {
document.documentElement.scrollTop = document.body.scrollTop = 0; //
}
}
}
// issue func
function onSuccess(){
const url = `/v1/${owner}/${projectsId}/issues/batch_destroy`;
axios.delete(url,{
params:{ids:allValue}
}).then(result=>{
if(result){
setUpdateIds(undefined);
setAllValue([]);
setVisible(false);
setCheckAll(false);
props.showNotification("疑修删除成功!");
Init();
}
}).catch(error=>{})
}
function chooseFunc(ids,n){
setNames(n);
setPage(1);
if(allValue && allValue.length>0){
// ids便
// setUpdateIds(ids);
setUpdateChooseIds(ids);
}else{
if(ids.status_id === "5"){
setCategory("closed");
}else{
setCategory("opened");
}
setUpdateIds(ids);
setTotal(undefined);
// Init(ids,1,n);
}
}
function chageLimit(c,p){
setPage(1);
setLimit(p);
}
function changeCategory(value){
if(value !== category){
let ids = {...updateIds};
let ns = {...names};
if(value === "closed"){
ns = {...ns,status_name:"关闭"}
ids = {...ids,status_id:'5'};
setNames(ns)
setUpdateIds(ids);
}else{
ns = {...ns,status_name:undefined}
ids = {...ids,status_id:undefined};
setNames(ns)
setUpdateIds(ids);
}
setCategory(value);
setPage(1);
setTotal(undefined);
}
}
function changeSearchValueFunc(e){
setValue(e.target.value);
if(e.target.value===""){
setKeyword(undefined);
}
}
//
function changeBeginTime(data, value){
setPage(1);
setBegin(value[0] || '');
setEnd(value[1] || '');
}
return(
<div>
<DelBox visible={visible} onCancel={()=>setVisible(false)} onSuccess={onSuccess}/>
<div className="pageheader">
<div>
{
(current_user && current_user.login) &&
<Dropdown overlay={menu} trigger={['click']} placement="bottomLeft" arrow={{pointAtCenter: true}}>
<span className="dorpdownButton mr20">
<span>{aboutMe === "all" ? "全部":aboutMe === "aboutme"?"与我相关":aboutMe === "assignedme"?"我负责的":aboutMe === "authoredme"?"我创建的":"@我的"}</span>
<Icon type="caret-down" className="ml5 color-grey-6" />
</span>
</Dropdown>
}
<Search
placeholder="输入关键字搜索疑修"
value={value}
onChange={changeSearchValueFunc}
onSearch={()=>setKeyword(value)}
style={{ width: 354 , height : 32 }}
allowClear
/>
{
clearFlag &&
<a className="color-blue ml25" onClick={clearCondition} style={{ display: "flex" , alignItems: "center"}}>
<i className="iconfont icon-roundclose font-16 mr5"></i>清除筛选条件</a>
}
</div>
<div>
<RangePicker value={[begin_date ? moment(begin_date, 'YYYY-MM-DD') : "",end_date ? moment(end_date, 'YYYY-MM-DD') : ""]} onChange={changeBeginTime} style={{width:240,marginRight:20}}/>
{
permission && permission !== "Reporter" &&
<Link to={`/${owner}/${projectsId}/issues/sign`} className="dorpdownButton"><img src={bj} alt="" className="mr5" />标记管理</Link>
}
<CheckProfile {...props} sureFunc={()=>{props.history.push(`/${owner}/${projectsId}/issues/new`)}} checklogin className="operateButton ml20">创建疑修</CheckProfile>
</div>
</div>
<div className="lists">
<div className="listheader">
<div style={{display:"flex"}}>
<Checkbox value="all" style={{marginRight: "16px",display: (permission && permission !== "Reporter") ?"block":"none"}} checked={checkAll} onChange={chooseAll}></Checkbox>
{
allValue && allValue.length>0 ?
<span>选择{allValue.length}个issue</span>
:
<ul className="statusul">
<li className={category === "all" ?"active":""} onClick={()=>{changeCategory("all")}}>全部<span>{issueTotal}</span></li>
<li className={category === "opened" ?"active":""} onClick={()=>{changeCategory("opened")}}>开启中<span>{openedCount}</span></li>
<li className={category === "closed" ?"active":""} onClick={()=>{changeCategory("closed")}}>已关闭<span>{closedCount}</span></li>
</ul>
}
</div>
<div className="menusul">
<AllMenus
ref={menuRef}
update={allValue && allValue.length>0}
owner={owner}
projectsId={projectsId}
chooseFunc={chooseFunc}
defaultNames={names}
defaultIds={allValue && allValue.length>0 ? undefined : updateIds}
open_blockchain={open_blockchain}
/>
{
allValue && allValue.length>0 ?
<div>
<Button type="primary" ghost onClick={sureUpdate}>确定</Button>
<Button type="danger" ghost className="ml10" onClick={()=>setVisible(true)}>删除</Button>
<Button ghost className="ml10 mr10" onClick={cancelUpdate}>取消</Button>
</div>
:""
}
</div>
</div>
{
total === 0 &&
(!has_created_issues ?
<div className="listempty">
<img src={issueEmp} alt="" width="68px" />
<p className="font-22 mt5 mb10">欢迎使用疑修(Issue)</p>
<p className="font-15">疑修用于记录与跟踪待办事项项目bug功能需求等在使用之前请您先<CheckProfile {...props} checklogin sureFunc={()=>{props.history.push(`/${owner}/${projectsId}/issues/new`)}} className="color-blue">创建一个疑修</CheckProfile></p>
</div>
:
<div className="dataempty"><img src={emp} alt="" /></div>
)
}
{
total > 0 &&
<React.Fragment>
<Checkbox.Group name="issues" onChange={checkIssues} value={allValue} style={{ width: "100%"}}>
<div className="listdatas">
{issueList.map((item,key)=>{
return(
<Datas
key={key}
checkbox={(permission && permission !== "Reporter") && <Checkbox value={item.id} key={item.id} style={{marginRight: "16px"}}></Checkbox> }
item={item}
owner={owner}
projectsId={projectsId}
/>
)
})
}
</div>
</Checkbox.Group>
{
total > defaultLimit &&
<div className="pt25 pb30" style={{textAlign:"right"}}>
<Pagination total={total} onShowSizeChange={chageLimit} current={page} pageSize={limit} onChange={changepage} showSizeChanger pageSizeOptions={[15,30,40,50]} showQuickJumper />
</div>
}
</React.Fragment>
}
{total === undefined && <div style={{height:344,display:"flex",alignItems:"center",justifyContent:"center"}}><Spin /></div> }
</div>
</div>
)
}
export default List;

View File

@ -0,0 +1,302 @@
import React , { useEffect , useState , forwardRef , useRef } from 'react';
import { Form , InputNumber } from 'antd';
import { Box , LongWidth } from '../../Component/layout';
import DropMenu from '../Component/dropMenu';
import ChooseMenu from '../Component/chooseMenu';
import AddTagsBox from '../Component/addTagsBox';
import Date from '../Component/date';
import NewPanel from '../Component/newPanel';
import axios from 'axios';
function New(props){
// history search
const {location:{search}, projectDetail} = props;
const feedBack = search && search.indexOf('type=feedback') !== -1;
// id
const milepostId = props.match.params.milepostId;
const [ statusList , setStatusList ] = useState([]);
const [ prioritiesList , setPrioritiesList ] = useState([]);
const [ chargeList , setChargeList ] = useState([]);
const [ charge , setCharge ] = useState(undefined);
const [ millstoneList , setMillstoneList ] = useState([]);
const [ millstone , setMillstone ] = useState(undefined);
const [ tagList , setTagList ] = useState(undefined);
const [ tag , setTag ] = useState(undefined);
const [ branchList , setBranchList ] = useState(undefined);
const [ branch , setBranch ] = useState(undefined);
const [ assigner_choose , setAssigner_choose ] = useState([]);
const [ prioritie_choose , setPrioritie_choose ] = useState([]);
const [ status_choose , setStatus_choose ] = useState([]);
const [ tag_choose , setTag_choose ] = useState([]);
const [ millstone_choose , setMillstone_choose ] = useState([]);
const [ branch_choose , setBranch_choose ] = useState([]);
const [ start_date, setStartDate ] = useState("");//moment().format('YYYY-MM-DD')
const [ due_date, setDueDate ] = useState("");
const [ rewardAmount , setRewardAmount ] = useState(undefined);
const [ rewardFlag , setRewardFlag ] = useState(false);
const owner = props.match.params.owner;
const projectsId = props.match.params.projectsId;
const permission = props && props.projectDetail && props.projectDetail.permission;
const { open_blockchain } = props;
const ref = useRef(null);
useEffect(()=>{
if(projectDetail){
if(feedBack){
document.title = `意见反馈`;
return;
}
const { author, name} = projectDetail;
document.title = `新建疑修-${author.name}/${name}`
}
},[projectDetail, feedBack])
useEffect(()=>{
if(milepostId){
getMillstone(milepostId);
}
},[milepostId])
//
useEffect(()=>{
getStatus();
},[])
function getStatus(){
const url = `/v1/${owner}/${projectsId}/issue_statues`;
axios.get(url).then(result=>{
if(result && result.data){
let status = result.data.statues;
setStatusList(status);
if(status && status.length>0){
setStatus_choose(status.filter(i=>i.id === 1));
}
}
})
}
//
useEffect(()=>{
getPriorities();
},[])
function getPriorities(){
const url = `/v1/${owner}/${projectsId}/issue_priorities`;
axios.get(url).then(result=>{
if(result && result.data){
let priorities = result.data.priorities;
let array =[];
let colors = ["#1abcb1","#28be6c","#e67e22","#db3d1d"];
if(priorities && priorities.length>0){
array = priorities.map((i,k)=>{
let arr = {...i,color:colors[k]}
return arr;
})
}
setPrioritiesList(array);
setPrioritie_choose(array.filter(i=>i.id === 2));
}
})
}
//
useEffect(()=>{
getCharge();
},[charge])
function getCharge(){
const url = `/v1/${owner}/${projectsId}/collaborators`;
axios.get(url,{params:{keyword:charge,only_name:true}}).then(result=>{
if(result && result.data){
setChargeList(result.data.collaborators);
if(feedBack && !charge){
// -
const {collaborators} = result.data;
const id = collaborators.filter(item=>item.id === 86107);
setAssigner_choose(id.length === 0 ? [collaborators[0]] : id)
}
}
})
}
//
useEffect(()=>{
getMillstone(millstone);
},[millstone])
function getMillstone(keyword){
const url = `/v1/${owner}/${projectsId}/milestones`;
axios.get(url,{params:{keyword,only_name:true}}).then(result=>{
if(result && result.data){
let milestones = result.data.milestones;
if(milepostId && milepostId === keyword){
setMillstone_choose(milestones);
}else{
setMillstoneList(milestones);
}
}
})
}
//
useEffect(()=>{
getSign();
},[tag])
function getSign(){
const url = `/v1/${owner}/${projectsId}/issue_tags`;
axios.get(url,{params:{keyword:tag,only_name:true}}).then(result=>{
if(result && result.data){
setTagList(result.data.issue_tags);
}
})
}
//
useEffect(()=>{
getBranch(branch);
},[branch])
function getBranch(branch){
if(branch){
let b = [...branchList];
let l = b.filter(i=>i.name.indexOf(branch)>-1);
setBranchList(l);
return ;
}
const url = `/${owner}/${projectsId}/branches.json`;
axios.get(url,{params:{keyword:tag}}).then(result=>{
if(result && result.data){
setBranchList(result.data);
}
})
}
//
function createFunc(values,fileList,receivers_login,description){
const { subject } = values;
const url = `/v1/${owner}/${projectsId}/issues`;
let b = branch_choose && branch_choose.length>0 ? branch_choose.map(i=>{return i.name}) :undefined;
let t = tag_choose && tag_choose.length>0 ? tag_choose.map(i=>{return i.id}) :undefined;
let a = assigner_choose && assigner_choose.length>0 ? assigner_choose.map(i=>{return i.id}) :undefined;
let m = millstone_choose && millstone_choose.length>0 ? millstone_choose.map(i=>{return i.id}) :undefined;
let p = prioritie_choose && prioritie_choose.length>0 ? prioritie_choose.map(i=>{return i.id}) :undefined;
let s = status_choose && status_choose.length>0 ? status_choose.map(i=>{return i.id}) :undefined;
axios.post(url,{
description,subject,
branch_name:b && b.join(","),
status_id:s && s.join(","),
priority_id:p && p.join(","),
milestone_id:m && m.join(","),
issue_tag_ids:t,
assigner_ids:a,
attachment_ids:fileList,
start_date,due_date,receivers_login,
blockchain_token_num:rewardAmount
}).then(result=>{
if(result && result.data && result.data.project_issues_index){
props.showNotification("任务创建成功!");
props.history.push(`/${owner}/${projectsId}/issues/${result.data.project_issues_index}`);
}
}).catch(error=>{})
}
function changeAmount(value){
if(value && value > 0){
setRewardAmount(value);
}
if(!value){
setRewardAmount(value);
}
}
return(
<div>
<p className="font-17 color-grey-3 mt20 mb15">新建疑修</p>
<Box>
<LongWidth>
{/* 意见反馈初始化内容 */}
<NewPanel {...props} createFunc={createFunc} owner = {owner} projectsId = {projectsId} desc={feedBack ? "####问题描述\n\n\n####重现问题步骤\n\n\n####截图\n\n\n####建议解决办法\n" : undefined}/>
</LongWidth>
<div className="shortwidth">
<ChooseMenu
placeholder="负责人"
menus={chargeList}
searchFunc={(value)=>{setCharge(value)}}
headImg
selectValueList={assigner_choose}
chooseFunc={(list)=>{setAssigner_choose(list);}}
double={5}
removeFlag
/>
<DropMenu
placeholder="状态"
menus={statusList}
selectValueList={status_choose}
mustFlag
editFlag={false}
chooseFunc={(list)=>{setStatus_choose(list);}}
/>
<DropMenu
placeholder="优先级"
menus={prioritiesList}
selectValueList={prioritie_choose}
colorFlag="1"
mustFlag
chooseFunc={(list)=>{setPrioritie_choose(list);}}
/>
<ChooseMenu
placeholder="标记"
menus={tagList}
searchFunc={(value)=>{setTag(value)}}
selectValueList={tag_choose}
chooseFunc={(list)=>{setTag_choose(list);}}
double={3}
colorFlag="2"
removeFlag
onAdd={permission && permission !== "Reporter" ? ()=>{getSign();} :false}
owner={owner}
projectsId={projectsId}
/>
<ChooseMenu
placeholder="里程碑"
menus={millstoneList}
auto
searchFunc={(value)=>{setMillstone(value)}}
selectValueList={millstone_choose}
editFlag={milepostId ? true : false}
chooseFunc={(list)=>{setMillstone_choose(list);}}
/>
<ChooseMenu
placeholder="关联分支"
menus={branchList}
auto
searchFunc={(value)=>{setBranch(value)}}
selectValueList={branch_choose}
chooseFunc={(list)=>{setBranch_choose(list);}}
/>
{
open_blockchain &&
<li style={{paddingBottom:"0px"}}>
<span>
悬赏金额
{/* {!(milepostId ? true : false) && <a onClick={editReward}><i className="iconfont icon-a-bianji12 font-13" style={{color:"#898d9d"}}></i></a> } */}
</span>
<InputNumber
placeholder="请输入悬赏金额"
style={{width:"100%",color:rewardAmount?"#333":"#acb0bf"}}
value={rewardAmount}
onChange={changeAmount}
className="borderNo mt5" disabled={milepostId ? true : false}/>
</li>
}
<Date name="开始日期" today={start_date} setDate={(date)=>setStartDate(date)}/>
<Date name="结束日期" today={due_date} setDate={(date)=>setDueDate(date)}/>
</div>
</Box>
</div>
)
}
export default Form.create()(forwardRef(New));

View File

@ -0,0 +1,267 @@
import React , { useEffect , useState } from 'react';
import create from '../Img/create.png';
import { Menu , Dropdown , Icon, Input, Button , Spin , Pagination } from 'antd';
import ColorCard from '../Component/colorCard';
import DelBox from '../Component/delBox';
import axios from 'axios';
import {Link} from 'react-router-dom';
const limit = 15;
function Sign(props){
const [ edit , setEdit ] = useState(false);
const [ colors , setColors ] = useState(undefined);
const [ delVisible , setDelVisible ] = useState(false);
const [ editId , setEditId ] = useState(undefined);
const [ list , setList ] = useState(undefined);
const [ total , setTotal ] = useState(undefined);
const [ page , setPage ] = useState(1);
const [ name , setName ] = useState(undefined);
const [ desc , setDesc ] = useState(undefined);
const [ order_name , setOrderName ] = useState(undefined);
const [ order_type , setOrderType ] = useState(undefined);
const [ nameFlag , setNameFlag ] = useState(undefined);
const [ defaultColor , setDefaultColor ]= useState(undefined);
const [ arrayName , setArrayName ] = useState("标记");
const [ key , setKey ] = useState("0");
const owner = props.match.params.owner;
const projectsId = props.match.params.projectsId;
const { projectDetail } = props;
const permission = projectDetail && props.projectDetail.permission;
useEffect(()=>{
if(permission !== undefined){
if(!permission || (permission && permission === "Reporter")){
window.location.href = "/403";
}
}
},[permission])
useEffect(()=>{
if(projectDetail){
const { author, name } = projectDetail;
document.title = `项目标记-${author.name}/${name}`;
}
},[projectDetail])
useEffect(()=>{
Init();
},[page,order_name,order_type])
function Init(){
const url = `/v1/${owner}/${projectsId}/issue_tags`;
axios.get(url,{
params:{
page,limit,sort_by:order_name,sort_direction:order_type
}
}).then(result=>{
if(result && result.data){
setList(result.data.issue_tags);
setTotal(result.data.total_count);
}
})
}
function arrayList(e){
const { eventKey , value , children , createName } = e.item.props;
setPage(1);
if(value === order_type && createName === order_name){
setOrderName(undefined);
setOrderType(undefined);
setArrayName("标记");
setKey("0");
}else{
setOrderName(createName);
setOrderType(value);
setArrayName(children);
setKey(eventKey);
}
}
const menu = (
<Menu onClick={arrayList} selectedKeys={[`${key}`]}>
<Menu.Item key={"1"} value="desc" createName="created_at">
按创建时间降序排序
</Menu.Item>
<Menu.Item key={"2"} value="asc" createName="created_at">
按创建时间升序排序
</Menu.Item>
<Menu.Item key={"3"} value="desc" createName="issues_count">
按疑修数量降序排序
</Menu.Item>
<Menu.Item key={"4"} value="asc" createName="issues_count">
按疑修数量升序排序
</Menu.Item>
<Menu.Item key={"5"} value="desc" createName="pull_requests_count">
按合并请求数量降序排序
</Menu.Item>
<Menu.Item key={"6"} value="asc" createName="pull_requests_count">
按合并请求数量升序排序
</Menu.Item>
</Menu>
);
function getColor(colors){
setColors(colors);
}
function deleteFunc(){
if(editId){
const url = `/v1/${owner}/${projectsId}/issue_tags/${editId}`;
axios.delete(url).then(result=>{
if(result){
props.showNotification("项目标记删除成功!");
if(list && list.length === 1 && page>1){
setPage(page-1);
}else{
Init();
}
setDelVisible(false);
}
}).catch(error=>{})
}
}
function sureSave(){
if(!name){
setNameFlag(true);
}else{
setNameFlag(false);
if(editId){
//
const url = `/v1/${owner}/${projectsId}/issue_tags/${editId}`;
axios.patch(url,{
color: colors || defaultColor,
description: desc,
name
}).then(result=>{
if(result){
props.showNotification("标记编辑成功!");
Init();
cancel();
}
}).catch(error=>{})
}else{
//
const url = `/v1/${owner}/${projectsId}/issue_tags`;
axios.post(url,{
color: colors || defaultColor,
description: desc,
name
}).then(result=>{
if(result){
props.showNotification("标记新增成功!");
Init();
cancel();
}
}).catch(error=>{})
}
}
}
function cancel(){
setNameFlag(false);
setName(undefined);
setEdit(false);
setDesc(undefined);
}
function editFunc(i){
setEditId(i.id);
setDefaultColor(i.color);
setName(i.name);
setDesc(i.description);
setEdit(true);
}
function onChangeValue(e){
setName(e.target.value);
if(e.target.value){
setNameFlag(false);
}else{
setNameFlag(true);
}
}
function createSign(){
const c = Math.random().toString(16).substr(-6);
c && setDefaultColor(`#${c}`);
setEdit(true);
}
return(
<div>
<DelBox
visible={delVisible}
onCancel={()=>setDelVisible(false)}
onSuccess={deleteFunc}
title="删除标记"
content={<div style={{paddingTop:"3px"}}><p className="font-15 mb20">您确定要删除当前标记</p></div>}
/>
<div className="between mt30 mb25">
<span><Link to={`/${owner}/${projectsId}/issues`}>疑修</Link> / 项目标记</span>
<a className="operateButton" onClick={createSign}><img src={create} alt="" className="mr5"/>创建标记</a>
</div>
{
edit &&
<div className="editbar">
<div>
<Input style={{width:"163px"}} className={nameFlag ?"inputred":""} value={name} onChange={onChangeValue} placeholder="名称15字以内"/>
{ nameFlag && <p className="red">请输入标记名称</p>}
</div>
<Input style={{width:"276px"}} value={desc} onChange={(e)=>setDesc(e.target.value)} placeholder="描述30字以内"/>
<ColorCard
getColor={getColor}
defaultColor={defaultColor}
/>
<span>
<Button type="primary" ghost onClick={sureSave}>确定</Button>
<Button ghost className="ml15" onClick={cancel}>取消</Button>
</span>
</div>
}
<div className="between bluebar">
<span className="font-17">项目标记({total || 0})</span>
<Dropdown overlay={menu} trigger={["click"]} placement="bottomRight">
<span className="cursor">{arrayName}<Icon type="caret-down" className="ml5 color-grey-6" /></span>
</Dropdown>
</div>
{
list && list.length > 0 ?
<ul className="signlist">
{
list.map((i,k)=>{
return(
<li>
<p>
<span className="square mr10" style={{backgroundColor:`${i.color}`}}></span>
<i className="iconfont icon-biaoji mr3 font-12 " style={{color:`${i.color}`}}></i>
<span className="task-hide" title={i.name} style={{maxWidth:210}}>{i.name}</span>
</p>
<p style={{flex:2}} className="task-hide">{i.description}</p>
<p>
<span className="mr12">{i.issues_count || 0} 疑修</span>
<span className="line">{i.pull_requests_count || 0} 合并请求</span>
</p>
<p>
<a className="color-blue mr12" onClick={()=>editFunc(i)}>编辑</a>
<a className="color-red line" onClick={()=>{setEditId(i.id);setDelVisible(true);}}>删除</a>
</p>
</li>
)
})
}
</ul>
:""
}
{
total > limit &&
<div style={{paddingBottom:"30px",textAlign:"center"}}>
<Pagination total={total} current={page} onChange={(p)=>setPage(p)} pageSize={limit}/>
</div>
}
{total === undefined && <div style={{height:344,display:"flex",alignItems:"center",justifyContent:"center"}}><Spin /></div> }
</div>
)
}
export default Sign;

View File

@ -0,0 +1,80 @@
import React,{useEffect} from 'react';
import { Route, Switch } from "react-router-dom";
import Loadable from "react-loadable";
import Loading from "../../Loading";
import './index.scss';
const List = Loadable({
loader: () => import("./Pages/list"),
loading: Loading,
});
const Detail = Loadable({
loader: () => import("./Pages/details"),
loading: Loading,
});
const Sign = Loadable({
loader: () => import("./Pages/sign"),
loading: Loading,
});
const New = Loadable({
loader: () => import("./Pages/new"),
loading: Loading,
});
function Index(props){
const pathname = props.history.location.pathname;
const { project } = props;
const open_blockchain = project && project.open_blockchain;
useEffect(() => {
if (document) { //
if (document.documentElement || document.body) {
document.documentElement.scrollTop = document.body.scrollTop = 0; //
}
}
}, [pathname]);
return(
<div className="pagebox">
<Switch>
<Route
path="/:owner/:projectsId/issues/:index/copy"
render={(p) => (
<Detail {...props} {...p} open_blockchain={open_blockchain}/>
)}
></Route>
{/* 里程碑创建issue */}
<Route
path="/:owner/:projectsId/issues/:milepostId/new"
render={(p) => (
<New {...props} {...p} open_blockchain={open_blockchain}/>
)}
></Route>
<Route
path="/:owner/:projectsId/issues/new"
render={(p) => (
<New {...props} {...p} open_blockchain={open_blockchain}/>
)}
></Route>
<Route
path="/:owner/:projectsId/issues/sign"
render={(p) => (
<Sign {...props} {...p} open_blockchain={open_blockchain}/>
)}
></Route>
<Route
path="/:owner/:projectsId/issues/:index"
render={(p) => (
<Detail {...props} {...p} open_blockchain={open_blockchain}/>
)}
></Route>
<Route
path="/:owner/:projectsId/issues"
render={(p) => (
<List {...props} {...p} open_blockchain={open_blockchain}/>
)}
></Route>
</Switch>
</div>
)
}
export default Index;

866
src/forge/Issues/index.scss Normal file
View File

@ -0,0 +1,866 @@
.borderNo.ant-input,.borderNo .ant-input-number-input,.borderNo,.borderNo.ant-input-number-focused{
border:none!important;
padding:4px 0px!important;
background-color: transparent!important;
box-shadow: none!important;
.ant-input-number-handler-wrap{
display: none;
}
&:focus{
border:none!important;
box-shadow: none!important;
}
}
.pagebox{
width: 1200px;
margin:0px auto;
}
// 删除issue弹框
.deldesc{
justify-content: center;
.red{
color: #DF0002;
}
}
// 可公用样式在issue的页面中
// 可点击(有向下箭头且点击出现下拉框移入灰色背景加深)的灰色按钮样式
.dorpdownButton{
padding:0px 15px;
height:32px;
color: #333333!important;
cursor: pointer;
line-height: 32px;
background-color:#fafbfc;
border:1px solid #d0d0d0;
border-radius: 4px;
display: flex;
align-items: center;
min-width: 107px;
box-sizing: border-box;
&>span{
display: block;
width: 56px;
text-align: center;
}
&:hover{
background-color: #f3f4f6;
}
}
// 操作按钮-蓝色
.operateButton{
padding:0px 15px;
height:32px;
color: #fff!important;
cursor: pointer;
line-height: 30px;
background-color:rgba(70, 106, 255, 1);
border:1px solid rgba(70, 106, 255, 1);
border-radius: 4px;
display: block;
&:hover{
background-color:#708cff;
border-color:#708cff;
}
}
// flex-左右上下居中
.between{
display: flex;
align-items: center;
justify-content: space-between;
}
// 蓝色背景操作栏
.bluebar{
height:50px;
background-color:#fafcff;
border:1px solid rgba(42, 97, 255, 0.23);
border-radius:4px 4px 0px 0px;
padding:0px 26px;
}
// 按钮样式
.ant-btn-background-ghost{
border-color:#acb0bf;
color: #acb0bf;
background-color: #fff!important;
&.ant-btn-primary{
color: #466aff;
border-color: #466aff;
background-color: #fff!important;
}
&.ant-btn-danger{
color: #f60011;
border-color: #f60011;
background-color:rgba(196, 0, 14, 0.09)!important;
}
}
.lists{
margin-bottom: 50px;
}
.dataempty{
height: 420px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid;
border-color: #d9d9d9;
border-radius: 0px 0px 4px 4px;
border-top: none;
}
// 列表为空样式
.listempty{
display: flex;
flex-direction: column;
align-items: center;
background-color:#fafcff;
border:1px solid rgba(42, 97, 255, 0.23);
border-top: none;
border-radius:0px 0px 4px 4px;
height: 344px;
padding-top: 62px;
color: #333;
}
// 列表页面样式
.pageheader{
display: flex;
justify-content: space-between;
padding:30px 0px;
align-items: center;
&>div{
display: flex;
align-items: center;
}
}
.listheader{
display: flex;
justify-content: space-between;
align-items: center;
height:50px;
background-color:#fafcff;
border:1px solid;
border-color:rgba(42, 97, 255, 0.23);
border-radius:4px 4px 0px 0px;
padding-left: 18px;
.menusul{
display: flex;
align-items: center;
}
ul{
display: flex;
align-items: center;
&.statusul{
li{
width: 106px;
font-size: 15px;
&>span{
height:19px;
line-height: 17px;
color: #666;
background-color:rgba(70, 106, 255, 0.09);
border-radius:10px;
margin-left: 5px;
padding:0px 6px;
font-size: 13px;
display: block;
border:1px solid ;
border-color: rgba(70, 106, 255, 0.01);
min-width: 30px;
text-align: center;
}
}
}
li{
color: #898d9d;
display: flex;
align-items: center;
cursor: pointer;
&.active{
color: #466aff;
// font-weight: 500;
&>span{
font-weight: normal;
color: #466aff;
border-color: #466aff;
background-color: #fff;
}
}
}
}
.dropboxul{
padding-right: 20px;
li{
min-width: 106px;
display: flex;
justify-content: flex-end;
cursor: default;
.dropspan{
display: flex;
align-items: center;
cursor: pointer;
&>span{
max-width: 80px;
display: inline-block;
text-align: right;
}
}
}
}
}
// issue列表
.listdatas{
border:1px solid #d0d0d0;
border-top: none;
border-radius: 0px 0px 2px 2px;
&>div:not(.none_panels){
padding:20px 18px;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
justify-content: space-between;
&:hover{
background-color:rgba(231, 233, 242, 0.26);
}
&:last-child{
border-bottom: none;
}
.issuedetail{
display: flex;
align-items: flex-start;
flex:1;
}
}
.idetails{
display: flex;
align-items: center;
&>a{
color: #40424a;
margin-right: 10px;
font-size: 15px;
display: block;
overflow: hidden;
text-overflow: ellipsis;
max-width: 375px;
white-space: nowrap;
}
.tagscolor{
padding:0px 6px;
border-radius: 2px;
height: 20px;
line-height: 20px;
max-width: 108px;
font-size: 13px;
cursor: default;
color: #fff;
}
}
.infos{
display: flex;
align-items: center;
color: #898d9d;
margin-top: 12px;
font-size: 13px;
// img{
// height: 22px;
// width: 22px;
// margin-right: 4px;
// border-radius: 50%;
// }
}
.issuecondition{
display: flex;
align-items: center;
color: #40424a;
.principal{
position: relative;
display: flex;
justify-content: flex-end;
width: 150px;
text-align: justify;
// &.hovers:hover{
// a{
// position:initial;
// }
// }
// a{
// position: absolute;
// right: 0px;
// transition: 0.6s;
// }
// img,span{
// width: 22px;
// height: 22px;
// border-radius: 50%;
// }
// span{
// background-color:#ced5ef;
// text-align: center;
// line-height: 12px;
// color:#000000;
// z-index: 6;
// }
}
&>div{
width: 90px;
text-align: right;
}
.commentnum{
padding-right: 5px;
display: flex;
align-items: center;
justify-content: flex-end;
}
}
}
// 状态样式-公用
.status{
display: block;
height:22px;
border-radius:6px;
text-align: center;
margin-right: 10px;
line-height: 20px;
width: 45px;
font-size: 14px;
border:1px solid #fff;
&.normals{
border-color:#28be6c;
color:#28be6c;
}
&.hight{
border-color: #e67e22;
color: #e67e22;
}
&.urgent{
border-color: #db3d1d;
color: #db3d1d;
}
&.low{
border-color: #1abcb1;
color: #1abcb1;
}
}
.ilog{
min-width: 45px;
margin-right: 10px;
clear: both;
.number{
background-color:rgba(213, 220, 246, 0.36);
border-radius:4px;
height: 19px;
line-height: 19px;
color: #666666;
text-align: center;
float: left;
padding:0px 4px;
cursor: pointer;
font-size: 13px;
}
}
// colorCard
.color{
width: 20px;
height: 20px;
border-radius: 2px;
}
.swatch{
padding: 5px;
background: #fff;
border-radius: 1px;
width: 92px;
height: 28px;
line-height: 20px;
box-shadow: 0 0 0 1px rgba(0,0,0,.1);
display: flex;
cursor: pointer;
}
.popover {
position: absolute;
z-index: 2;
}
.cover {
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.modalcolor {
width: 20px;
height: 20px;
border-radius: 2px;
}
// 标记管理页面
.editbar{
padding:18px 20px;
background-color:rgba(218, 225, 251, 0.24);
display: flex;
align-items: center;
margin-bottom: 30px;
justify-content: space-between;
&>div{
position: relative;
.inputred{
border-color: red;
}
.red{
position: absolute;
left: 166px;
width: 100%;
top: 2px;
color: red;
}
}
}
.signlist{
padding:10px 0px;
margin-bottom: 40px!important;
&>li{
display: flex;
margin-bottom: 5px;
padding:10px 20px;
&>p{
flex:1;
color: 40424a;
&:first-child{
flex:1.2;
display: flex;
align-items: center;
justify-content: flex-start;
}
&:last-child{
display: flex;
align-items: center;
justify-content: flex-start;
justify-content: flex-end;
}
.square{
display: block;
width: 22px;
height: 15px;
border-radius:2px;
}
.line{
position: relative;
padding-left: 13px;
&::before{
position: absolute;
height:13px;
width: 1px;
left: 0px;
top:7px;
background-color:#d5d5d5;
content: "";
}
}
}
}
}
// 新建页面样式
.shortwidth{
width: 330px;
padding-left: 50px;
&>li{
border-bottom: 1px solid rgba(172, 176, 191, 0.17);
padding-bottom: 8px;
margin-bottom: 22px!important;
&>span{
font-size: 15px;
position: relative;
color: #40424a;
display: flex;
width: 100%;
height: 20px;
line-height: 20px;
font-weight: 600;
a{
position: absolute;
right: 0px;
}
}
.operatevalue{
line-height: 30px;
color: #acb0bf;
margin-top: 10px;
display: flex;
flex-wrap: wrap;
.colorsborder{
padding: 0px 8px;
height: 20px;
line-height: 18px;
border-radius: 4px;
text-align: center;
border:1px solid ;
font-size: 13px;
}
.colorsquare{
padding: 0px 8px;
height: 20px;
line-height: 20px;
border-radius: 4px;
text-align: center;
color: #fff;
font-size: 13px;
}
&>p{
margin-top: 5px!important;
position: relative;
padding-right: 15px;
&.removeFlag>span{
max-width: 230px;
}
.removeicon{
display: none;
}
}
.removeFlag:hover{
border-radius:2px;
.removeicon{
display: block;
}
}
&>p:first-child{
margin-top: 0px;
}
}
}
}
.addTags_form{
.ant-row.ant-form-item{
margin-bottom: 12px;
}
.manageTitle{
margin-bottom: 20px!important;
padding-bottom: 15px;
line-height: 22px;
color:#5f6872;
font-size:16px;
border-bottom:1px solid #e1e3e8;
}
.inline{
display: flex;
align-items: flex-start;
}
}
.addTagsModal{
.ant-modal-content{
background-image:linear-gradient(359.37deg,#ebf3ff 0%,#f8fbff 55.01%,#f1f5ff 100%);
}
.ant-modal-header{
background-color: unset;
padding:0px 24px;
height: 50px;
line-height: 50px;
.ant-modal-title{
text-align: left;
color:#333333;
font-size:17px;
font-weight: normal!important;
height: 50px;
line-height: 50px!important;
}
}
.ant-modal-close{
top:0px !important;
}
.popover{
left: 94px;
top:0px;
}
}
.menusEmpty{
padding:12px 20px 10px 16px;
&>p{
padding:10px 16px;
}
&>a{
padding:10px 16px;
display: block;
border-top: 1px solid rgba(172, 176, 191, 0.17);
}
}
.piecemenu{
li{
display: flex;
align-items: center;
}
}
.colorpiece{
display: block;
width: 22px!important;
height: 15px;
margin-right: 9px;
border-radius:2px;
}
.overlayStyle{
background-color:#ffffff;
border-radius:6px;
box-shadow:0px 0px 10px rgba(24, 54, 181, 0.17);
max-width: 280px;
z-index: 100;
.searchbox{
padding:12px 20px 0px 16px;
}
.ant-menu{
padding:10px 16px 10px 16px;
max-height: 324px;
overflow-y: auto;
li{
padding:0px 16px;
height: 38px;
line-height: 38px;
border-bottom: none;
color:#898d9d;
position: relative;
width: 100%;
box-sizing: border-box;
&:hover,&.ant-menu-item-selected{
background-color:rgba(70, 106, 255, 0.07);
color:#40424a;
}
&.ant-menu-item-selected::before{
content:"";
position: absolute;
right: 15px;
color: #acb0bf;
top: 0px;
}
}
}
}
//详情页面
.editpanel{
padding:20px;
margin-top: 25px;
border:1px solid rgba(42, 97, 255, 0.23);
border-radius: 2px;
}
.detailbanner{
// margin-top: 25px;
// height:82px;
// background-color:#fafcff;
// border:1px solid rgba(42, 97, 255, 0.23);
// border-radius:4px;
// padding:12px 25px 12px 20px;
display: flex;
align-items: center;
justify-content: space-between;
.detailtitle{
flex: 1;
&>div{
display: flex;
align-items: flex-start;
}
.name{
font-size: 16px;
font-weight: 700;
color:#333;
line-height: 22px;
max-width: 580px;
word-break: break-all;
}
.author{
img{
width: 26px;
height: 26px;
margin-right: 4px;
border-radius: 50%;
}
color: #466aff;
}
}
.detailoperate{
display: flex;
align-items: center;
}
}
.descPanel{
margin-top:30px;
// padding:20px;
// background-color: #f7f8fd;
// border:1px solid #c9d8ff;
// border-radius: 2px;
}
// 新建页面
.explain{
.ant-form-explain{
position: absolute;
}
}
.overlaydrop{
&.large{
width: 260px;
}
&.small{
width: 120px;
padding:12px 0px!important;
}
padding:18px 15px;
position: relative;
border-radius:6px;
box-shadow:0px 0px 10px rgba(24, 54, 181, 0.17);
background-color: #fff;
.ant-menu{
max-height: 305px;
overflow-y: auto;
li{
height: 38px;
line-height: 38px;
border-bottom: none!important;
display: flex;
align-items: center;
padding-left: 6px;
padding-right: 0px;
img{
width: 24px;
height: 24px;
border-radius: 50%;
}
span{
display: block;
width:100%;
margin-left: 10px;
padding-right: 25px;
}
&:hover,&.ant-menu-item-selected{
background-color:rgba(70, 106, 255, 0.07);
color:#40424a;
}
&.ant-menu-item-selected::before{
content:"";
position: absolute;
right: 15px;
color: #acb0bf;
top: 0px;
}
}
}
}
// 声明
.claimpart{
padding-bottom: 15px;
border-bottom: 1px solid #eee;
margin-bottom: 20px;
}
// issue版本2
.overlayChooseStyle{
width: 636px;
background-color:#ffffff;
border-radius:6px;
box-shadow:0px 0px 10px rgba(24, 54, 181, 0.17);
z-index: 100;
padding:30px 20px;
.choosedul{
display: flex;
flex-wrap: wrap;
padding-bottom: 10px;
border-bottom: 1px solid #e4e4e6;
margin-bottom: 20px!important;
li{
height:24px;
background-color:#eff2ff;
border:1px solid;
border-color:#466aff;
border-radius:2px;
color:#466aff;
font-size:14px;
margin-right: 15px;
margin-bottom: 10px!important;
padding:0px 7px;
display: flex;
align-items: center;
span{
display: block;
max-width:120px ;
}
}
}
.counttips{
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.ant-menu{
display: flex;
flex-wrap: wrap;
padding:20px 0px 5px 0px;
box-sizing: border-box;
max-height: 307px;
overflow-y: auto;
&.piecemenu li{
width: auto;
&>span{
display: flex;
}
span.task-hide{
max-width: unset;
}
&:nth-child(5n){
margin-right: 24px!important;
}
}
li{
width: 100px;
height:32px;
line-height:30px;
text-align: center;
font-size:14px;
border-radius:2px;
margin-right: 24px!important;
margin-bottom: 15px!important;
border:1px solid!important;
border-color: #f4f6fe!important;
padding:0px 5px;
&.commonli{
background-color:#f4f6fe!important;
}
.removeicon{
display: block;
}
span.task-hide{
color: #3f455b;
margin:0px auto;
position: relative;
color:#3f455b;
max-width: 80px;
display: inline-block;
}
span{
position: relative;
margin:0px auto;
}
&.colorli{
border-color: transparent!important;
span{
color: #fff;
position: relative;
margin:0px auto;
}
}
&.commonli.ant-menu-item-selected{
background-color:#e2e8ff!important;
color:#3f5097;
border-color:#466aff!important;
}
&.colorli.ant-menu-item-selected{
border-color: transparent!important;
}
&.colorli.ant-menu-item-selected{
&>span::before{
content:"";
position: absolute;
right: -12px;
color: #fff;
top: -16px;
}
}
&:nth-child(5n){
margin-right: 0px!important;
}
}
}
}

View File

@ -476,7 +476,7 @@ function CoderDepot(props){
}
{
issuesFlag &&
<a onClick={createIssue}>+ 疑修</a>
<CheckProfile {...props} sureFunc={createIssue} checklogin>+ 疑修</CheckProfile>
}
</div>

View File

@ -22,7 +22,8 @@ function CoderDepotReadme({ operate , history , readme , ChangeFile }){
const items = $.map($("#readme").find("h1,h2,h3,h4,h5,h6"), function (el, _) {
const anchor = el.id;
const level = el.tagName.replace("H", "");
const href = `#${anchor}`;
let id = anchor.replace(/[.,/#!$%^&*;:{}=\-_`~():,。¥;「」|?》《~·【】‘、!]/g,"");
const href = `#${id}`;
return { href:`${href}`,text:el.textContent , level:level }
});
setMenuList(items);

View File

@ -34,7 +34,7 @@ const OrderDetail = Loadable({
loading: Loading,
})
const OrderIndex = Loadable({
loader: () => import('../Order/order'),
loader: () => import('../Issues/index'),
loading: Loading,
})
const CoderRootIndex = Loadable({
@ -89,7 +89,7 @@ const UpdateMerge = Loadable({
})
const MilepostDetail = Loadable({
loader: () => import('../Order/MilepostDetail'),
loader: () => import('../Order/MilepostDetail.jsx'),
loading: Loading,
})
const WatchUsers = Loadable({
@ -247,6 +247,7 @@ class Detail extends Component {
let currentIssue = pathname.indexOf(`/${owner}/${projectsId}/issues`) === -1;
if (currentIssue) {
cookie.save('states', undefined, { expires: 0, path: `/` });
cookie.save('issuestates', undefined, { expires: 0, path: `/` });
}
}
@ -742,11 +743,11 @@ class Detail extends Component {
}
></Route>
{/* 标签列表 */}
<Route path="/:owner/:projectsId/issues/tags"
{/* <Route path="/:owner/:projectsId/issues/tags"
render={
(props) => (<TagList {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
></Route> */}
{/* 仓库设置 */}
<Route path="/:owner/:projectsId/settings"
render={
@ -767,7 +768,7 @@ class Detail extends Component {
}
></Route>
{/*里程碑详情*/}
<Route path="/:owner/:projectsId/milestones/:meilid"
<Route path="/:owner/:projectsId/milestones/:mileId"
render={
(props) => (<MilepostDetail {...this.props} {...props} {...this.state} {...common} />)
}
@ -779,36 +780,36 @@ class Detail extends Component {
}
></Route>
{/* 里程碑页面新建任务 */}
<Route path="/:owner/:projectsId/issues/:milepostId/new"
{/* <Route path="/:owner/:projectsId/issues/:milepostId/new"
render={
(props) => (<OrderNew {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
></Route> */}
{/* 新建任务 */}
<Route path="/:owner/:projectsId/issues/new"
{/* <Route path="/:owner/:projectsId/issues/new"
render={
(props) => (<OrderNew {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
></Route> */}
{/* 修改详情 edit*/}
<Route path="/:owner/:projectsId/issues/:orderId/edit"
{/* <Route path="/:owner/:projectsId/issues/:orderId/edit"
render={
(props) => (<OrderupdateDetail {...this.props} {...props} {...this.state} {...common} form_type={"edit"} />)
}
></Route>
></Route> */}
{/* 复制详情 copyetail*/}
<Route path="/:owner/:projectsId/issues/:orderId/copyetail"
{/* <Route path="/:owner/:projectsId/issues/:orderId/copyetail"
render={
(props) => (<OrderupdateDetail {...this.props} {...props} {...this.state} {...common} form_type={"copy"} />)
}
></Route>
></Route> */}
{/* 任务详情 */}
<Route path="/:owner/:projectsId/issues/:orderId"
{/* <Route path="/:owner/:projectsId/issues/:orderId"
render={
(props) => (<OrderDetail {...this.props} {...this.state} {...props} {...common} />)
}
></Route>
></Route> */}
{/* 动态 */}
<Route path="/:owner/:projectsId/activity"
render={

View File

@ -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>
)

View File

@ -40,7 +40,7 @@ function ReadmeCatelogue({ menuList , hash }) {
menu.map((item,key)=>{
return(
<div style={{paddingLeft:`${item.level *15}px`}} className={goHref===item.href?"items active":"items"}>
<Link href={`#${item.text}`} title={item.text} />
<Link href={`${item.href}`} title={item.text} />
</div>
)
})

View File

@ -73,7 +73,6 @@ function Index(props) {
dataIndex: "name",
key: 1,
ellipsis: true,
width: "160px",
className: "branchNameColumn",
render: (txt, item) => {
return (
@ -112,7 +111,7 @@ function Index(props) {
dataIndex: "commit_id",
key: 4,
ellipsis: true,
width: "300px",
width: "250px",
render: (txt, item) => {
return <div className="treecopy">
<div className='treeCopyBox'>
@ -130,6 +129,7 @@ function Index(props) {
title: "分支类型",
dataIndex: "default_branch",
key: 5,
width: "150px",
render: (txt, item) => {
return isManager ? txt === item.name ? <a className='btn-83' style={{width: '93px'}}>默认分支</a> : <Select value={1} onChange={(value)=>{updateDefaultBranch(value, item)}}>
<Select.Option value={0}>默认分支</Select.Option>
@ -263,16 +263,10 @@ function Index(props) {
//
function addBranchBtn(){
setAddBranch(true);
const url = `/v1/${owner}/${projectsId}/branches.json`;
Axios.get(url,{
params:{
page: 1,
limit: total
}
}).then(result=>{
const url = `/v1/${owner}/${projectsId}/branches/all.json`;
Axios.get(url).then(result=>{
if(result){
const {branches} = result.data;
setBranchList(branches);
setBranchList(result.data);
}
}).catch(error=>{console.log('error', error);})
}
@ -393,7 +387,7 @@ function Index(props) {
<Input placeholder='请输入分支名称' className='createBranchInput' onChange={checkNewBranchName} maxLength={100}/>
<div className='newBranchError mt5'>{newBranchError}</div>
<div className='font-15 createBranchLabel mt20'>分支起点</div>
<Select showSearch style={{width: '340px'}} className='createBranchInput' defaultValue={list[0] && list[0].name} onChange={(value)=>{setNewFormBranchName(value)}} getPopupContainer={triggerNode => triggerNode.parentNode}>
<Select showSearch style={{width: '340px'}} className='createBranchInput' placeholder="请选择分支起点" onChange={(value)=>{setNewFormBranchName(value)}} getPopupContainer={triggerNode => triggerNode.parentNode}>
{branchList && branchList.map((item, index)=>{
return <Select.Option value={item.name} key={index}>{item.name}</Select.Option>
})}

View File

@ -42,7 +42,7 @@ class MergeItem extends Component {
};
render() {
const { issues, project_name, project_author_name , user_admin_or_developer} = this.props;
const { issues, project_name, project_author_name , user_admin_or_developer , open_blockchain} = this.props;
const { projectsId , owner } = this.props.match.params;
const { current_user } = this.props;
const renderList = () => {
@ -143,6 +143,17 @@ class MergeItem extends Component {
onMouseMove={() => this.onMouseMove(item.id)}
onMouseOut={() => this.onMouseOut()}
>
{item.attached_issues && item.attached_issues.length>0 && open_blockchain &&
<li>
{
item.attached_issues.map((i,k)=>{
return(
<Link to={`/${owner}/${projectsId}/issues/${i.project_issues_index}`} className="issueIndex">#{i.project_issues_index}</Link>
)
})
}
</li>
}
<li>{item.priority}</li>
<li>{this.set_issue_tags(item.issue_tags)}</li>
<li>

View File

@ -350,7 +350,7 @@ class MessageCount extends Component {
pull_request,
conflict_files
} = this.state;
const { current_user, projectDetail } = this.props;
const { current_user, projectDetail ,project } = this.props;
const menu = (
<Menu onClick={(e) => this.getOption(e)}>
<Menu.Item key={"merge"} value="合并请求">
@ -370,6 +370,7 @@ class MessageCount extends Component {
const permission = projectDetail && (projectDetail.permission === "Admin" || projectDetail.permission === "Owner" || projectDetail.permission === "Manager");
const userLogin = current_user && current_user.login;
const operate = userLogin && projectDetail && pr_status === 0 && permission;
const open_blockchain = project && project.open_blockchain;
return (
<div>
{data ? (
@ -444,6 +445,19 @@ class MessageCount extends Component {
: "更新于"}
</span>
<span className="color-grey-8 ml5">{data.pr_time}</span>
{
open_blockchain && data.attached_issues && data.attached_issues.length>0 ?
<span className="color-grey-8 ml25">关联issue:
{
data.attached_issues.map((i,k)=>{
return(
<Link className="issueIndex ml5" to={`/${owner}/${projectsId}/issues/${i.project_issues_index}`}>#{i.project_issues_index}</Link>
)
})
}
</span>
:""
}
<span className="ml25">
<span className="color-grey-8">审查人员</span>
{data.issue.assign_user_name ? (

View File

@ -236,6 +236,8 @@ class merge extends Component {
}
render() {
const { projectsId , owner } = this.props.match.params;
const { project } = this.props;
const open_blockchain = project && project.open_blockchain;
const {
issue_chosen,
issues,
@ -318,6 +320,7 @@ class merge extends Component {
</ul>
</div>
<ul className="topWrapper_select">
{ open_blockchain && <li>关联疑修</li> }
<li>
<Dropdown
className="topWrapperSelect"
@ -417,6 +420,7 @@ class merge extends Component {
{...this.props}
{...this.state}
user_admin_or_developer={data && data.user_admin_or_developer}
open_blockchain={open_blockchain}
></MergeItem>
</div>
):""}

View File

@ -25,7 +25,9 @@ class MergeForm extends Component {
issue_tags: undefined,
issue_versions: undefined,
issue_priories: undefined,
atWhoLoginList:undefined
atWhoLoginList:undefined,
issue_lists:undefined,
issue_id:undefined
};
}
@ -33,6 +35,7 @@ class MergeForm extends Component {
// this.check_is_login();
this.get_default_selects();
this.set_defatul();
this.getIssueList();
};
componentDidUpdate=(prevPros)=>{
const { projectsId ,owner } = this.props.match.params;
@ -47,6 +50,22 @@ class MergeForm extends Component {
return
}
}
getIssueList=()=>{
const { projectsId ,owner } = this.props.match.params;
const url = `/v1/${owner}/${projectsId}/issues`;
axios.get(url,{
params:{
only_name:true
}
}).then(result=>{
if(result && result.data){
this.setState({
issue_lists:result.data.issues
})
}
})
}
get_default_selects = () => {
const { projectsId ,owner } = this.props.match.params;
@ -72,23 +91,25 @@ class MergeForm extends Component {
set_defatul = () => {
const { data, merge_type } = this.props;
if (data && merge_type === "edit") {
console.log("dddddddd",data);
this.setState({
desc: data.body,
issue_tag_ids: data.issue_tag_ids ? data.issue_tag_ids[0] : undefined,
fixed_version_id: data.fixed_version_id ? String(data.fixed_version_id) : undefined,
assigned_to_id: data.assigned_to_id ? String(data.assigned_to_id) : undefined,
priority_id: data.priority_id ? String(data.priority_id) :undefined,
issue_id:data.attached_issues && data.attached_issues.length>0 ? String(data.attached_issues[0].id) :undefined,
title: data.title,
},()=>{
this.InitData();
});
}
this.InitData();
};
InitData = () => {
setTimeout(() => {
this.props.form.setFieldsValue({
...this.state,
});
}, 100);
console.log(this.state.issue_id);
this.props.form.setFieldsValue({
...this.state,
});
};
onPanelChange = (time, mode) => {
@ -125,7 +146,7 @@ class MergeForm extends Component {
return list.map((item, key) => {
return (
<Option key={key + 1} value={item.id + ""}>
{item.name}
{item.name || item.subject}
</Option>
);
});
@ -161,6 +182,7 @@ class MergeForm extends Component {
files_count,
commits_count,
receivers_login:atWhoLoginList,
attached_issue_ids:values.issue_id ? [values.issue_id] :undefined
})
.then((result) => {
if (result) {
@ -191,6 +213,7 @@ class MergeForm extends Component {
head: pull,
base: merge,
receivers_login:atWhoLoginList,
attached_issue_ids:values.issue_id ? [values.issue_id] :undefined
})
.then((result) => {
if (result) {
@ -234,7 +257,8 @@ class MergeForm extends Component {
};
render() {
const { merge_type } = this.props;
const { merge_type , project } = this.props;
const open_blockchain = project && project.open_blockchain;
const { getFieldDecorator } = this.props.form;
const { projectsId, mergeId ,owner } = this.props.match.params;
const {
@ -249,6 +273,8 @@ class MergeForm extends Component {
issue_tags,
issue_versions,
issue_priories,
issue_lists,
issue_id
} = this.state;
return (
<div>
@ -275,17 +301,19 @@ class MergeForm extends Component {
initialValue: title,
})(<Input placeholder="标题" maxLength={200} />)}
</Form.Item>
<MDEditor
placeholder={"请输入合并请求的描述..."}
height={450}
mdID={"merge-new-description"}
initValue={desc}
onChange={this.onContentChange}
isCanAtme = {true}
changeAtWhoLoginList = {this.changeAtWhoLoginList}
owner = {owner}
projectsId = {projectsId}
></MDEditor>
<div style={{position:"relative"}}>
<MDEditor
placeholder={"请输入合并请求的描述..."}
height={450}
mdID={"merge-new-description"}
initValue={desc}
onChange={this.onContentChange}
isCanAtme = {true}
changeAtWhoLoginList = {this.changeAtWhoLoginList}
owner = {owner}
projectsId = {projectsId}
></MDEditor>
</div>
<p className="clearfix mt20">
<Button
type="primary"
@ -379,6 +407,25 @@ class MergeForm extends Component {
</Select>
)}
</Form.Item>
<Form.Item style={{display:open_blockchain ? "block" :"none"}}>
{getFieldDecorator("issue_id", {
initialValue: issue_id,
})(
<Select
placeholder={"关联疑修"}
showSearch
className="searchIcon"
suffixIcon={<i className="iconfont icon-sousuo1"></i>}
optionFilterProp="children"
filterOption={(input, option) =>
option.props.value && option.props.children && option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
<Option key={0} value={""}>{issue_lists && issue_lists.length > 0 ? "未选择疑修" : "当前仓库还没有疑修"}</Option>
{this.renderSelect(issue_lists)}
</Select>
)}
</Form.Item>
{/* <Form.Item label="">
{getFieldDecorator("checkbox-group")(
<Checkbox.Group>

View File

@ -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: parseInt(blockchain_init)
}).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,34 @@ 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>
{tokenConTri && <Form.Item
className="flex1"
extra="Token指本项目的虚拟货币决定用户在本仓库贡献度的比例可以通过issue悬赏或在个人主页贡献确权页进行转移。创建项目时每个项目的Token总量为10000您可以分配您可获取的Token数量未分配的Token将保留至项目Token余额"
>
{getFieldDecorator('blockchain_init', {
rules: [{
required: true, message: '初始token值为一个不超过10000的正整数'
},
{ validator: (rule,val,callback) =>{
if(!/^[1-9]+[0-9]*]*$/.test(val)){
callback('初始token值为正整数');
}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>

View File

@ -1,29 +1,23 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { Dropdown, Icon, Menu, Pagination, Typography, Popconfirm, Spin } from 'antd';
import NoneData from '../Nodata';
import { Dropdown, Icon, Menu, Pagination, Popconfirm, Spin, Button, Tooltip } from 'antd';
import emp from '../../forge/Issues/Img/emp.png';
import axios from 'axios';
import './order.scss';
import CheckProfile from '../Component/ProfileModal/Profile';
const { Text } = Typography;
import './milepost.scss';
class Milepost extends Component {
constructor(props) {
super(props);
this.state = {
data: undefined,
limit: 15,
limit: 10,
page: 1,
order_type: undefined,
//新建标签区域是否显示 none 隐藏 block 显示
display: 'none',
// 里程碑 开启/关闭 状态
status: 'open',
openselect: 1,
closeselect: undefined,
order_name: undefined,
spinings: true
spinings: true,
// tooltip显隐
visible: false
}
}
componentDidUpdate(){
@ -31,7 +25,7 @@ class Milepost extends Component {
}
componentDidMount = () => {
this.getList(1, this.state.status, 'desc');
this.getList(1, this.state.limit, this.state.status);
this.updateDocumentTitle();
}
@ -44,13 +38,12 @@ class Milepost extends Component {
}
}
getList = (page, status, order_type, order_name) => {
getList = (page, limit, category, sort_by, sort_direction) => {
const { projectsId ,owner } = this.props.match.params;
const { limit } = this.state;
const url = `/${owner}/${projectsId}/milestones.json`;
const url = `/v1/${owner}/${projectsId}/milestones.json`;
axios.get(url, {
params: {
page, limit, status, order_type, order_name
page, limit: limit, category, sort_by, sort_direction
}
}).then((result) => {
if (result) {
@ -65,45 +58,24 @@ class Milepost extends Component {
}
opneMilelist = (type) => {
const { order_name } = this.state;
if (type) {
const { current_user } = this.props;
if (type === 1) {
this.setState({
status: 'open',
openselect: current_user.user_id,
closeselect: undefined,
})
this.getList(1, 'open', 'desc', order_name);
} else {
this.setState({
status: 'closed',
openselect: undefined,
closeselect: current_user.user_id
})
this.getList(1, 'closed', 'desc', order_name);
}
this.setState({
status: type === 1 ? 'open' : 'closed'
})
this.getList(1, this.state.limit, type === 1 ? 'open' : 'closed', this.state.order_name, this.state.order_type);
}
}
updatestatusemile = (status, arr) => {
const { projectsId , owner } = this.props.match.params;
const url = `/${owner}/${projectsId}/milestones/${arr.id}/update_status.json`;
const { current_user } = this.props;
axios.post(url, {
project_id: projectsId,
id: arr.id,
status: status
}).then(result => {
if (result) {
this.setState({
status: status,
closeselect: status === "closed" ? current_user.user_id : undefined,
openselect: status === "closed" ? undefined : current_user.user_id
})
this.getList(1, status, 'desc');
this.getList(1, this.state.limit, this.state.status, this.state.order_name, this.state.order_type);
const { getDetail } = this.props;
getDetail && getDetail();
}
@ -122,7 +94,7 @@ class Milepost extends Component {
}
}).then((result) => {
if (result) {
this.getList(1, this.state.status, 'desc');
this.getList(1, this.state.limit, this.state.status, this.state.order_name, this.state.order_type);
const { getDetail } = this.props;
getDetail && getDetail();
}
@ -136,39 +108,44 @@ class Milepost extends Component {
this.setState({
page
})
const { status } = this.state;
this.getList( page , status );
this.getList(page, this.state.limit, this.state.status, this.state.order_name, this.state.order_type);
}
// 排序
arrayList = (e) => {
const name = e.key !== 'order' ? e.key : undefined;
const type = e.item.props.value !== 'order' ? e.item.props.value : undefined;
this.setState({
order_name: e.key,
order_type: e.item.props.value
order_name: name,
order_type: type
})
this.getList(1, this.state.status, e.item.props.value, e.key);
this.getList(1, this.state.limit, this.state.status, name, type);
}
//控制新建标签页是否显示
newshow = () => {
// 分页组件
onShowSizeChange = (current, pageSize) =>{
this.setState({
display: 'block'
});
};
newclose = () => {
this.setState({
display: 'none'
});
};
page: 1,
limit: pageSize
})
this.getList(1, pageSize, this.state.status, this.state.order_name, this.state.order_type);
}
// tooltip判断是否显示
// onVisibleChange = (visible, value) => {
// console.log('visible, value', visible, value);
// this.setState({
// visible: value.length >24 ? visible : false
// })
// }
render() {
const { data, limit, page, openselect, closeselect, spinings } = this.state;
const { data, limit, page, spinings, status, order_name, order_type, visible } = this.state;
const { projectsId , owner } = this.props.match.params;
const { isManager, isDeveloper} = this.props;
const menu = (
<Menu className="orderCondition" onClick={this.arrayList}>
<Menu className="orderCondition milepostSort" onClick={this.arrayList}>
<Menu.Item key={'order'} value={'order'}>默认排序</Menu.Item>
<Menu.Item key={'effective_date'} value="desc">到期日从后到先</Menu.Item>
<Menu.Item key={'effective_date'} value="asc">到期日从先到后</Menu.Item>
<Menu.Item key={'percent'} value="asc">完成度从低到高</Menu.Item>
@ -178,103 +155,90 @@ class Milepost extends Component {
</Menu>
)
// 排序字段 选中显示文本
const sortByMile = {
'effective_date':{
'desc': '到期日从后到先',
'asc': '到期日从先到后'
},
'percent':{
'desc': '完成度从低到高',
'asc': '完成度从高到低'
},
'issues_count':{
'desc': '任务从多到少',
'asc': '任务从少到多'
},
'order':{
'order': '排序'
}
}
return (
<Spin spinning={spinings}>
<div className="main mt20" style={{minHeight:"400px"}}>
<div style={{ display: this.state.display }}>
<div className="tagdiv" >
<span>里程碑{data && data.issue_tags_count}已创建</span>
<div className="main milepostBox">
{/* 创建里程碑按钮 */}
{/* <div className='createMilepost mb25'>
{(isManager || isDeveloper) && <Button type='primary' onClick={() => {this.props.history.push(`/${owner}/${projectsId}/milestones/new`)}} className='createMilepostBtn'>+ 创建里程碑</Button>}
</div> */}
{/* 里程碑列表表头 */}
<div className='milepostHead flexSpaceBetween'>
<div>
<span className={`pointBox font-15 postStatus ${status === "closed" ? "" : "active"}`} onClick={() => this.opneMilelist(1)}>开启中<span className='statusCount font-13'>{data && data.opening_milestone_count}</span></span>
<span className={`pointBox ml35 font-15 postStatus ${status === "closed" ? "active" : ""}`} onClick={() => this.opneMilelist(2)}>已关闭<span className='statusCount font-13'>{data && data.closed_milestone_count}</span></span>
</div>
<div>
{(isManager || isDeveloper) && <Button type='primary' onClick={() => {this.props.history.push(`/${owner}/${projectsId}/milestones/new`)}} className='createMilepostBtn mr20'>+ 创建里程碑</Button>}
<Dropdown className="topWrapperSelect" overlay={menu} trigger={['click']} placement="bottomCenter">
<span className='pointBox'>{order_name ? sortByMile[order_name][order_type] : '排序'}<Icon type="caret-down" className="ml5"/></span>
</Dropdown>
</div>
</div>
<div className="topWrapper" style={{borderBottom:"1px solid #eee"}}>
<div className="topWrapper_type_infos">
<li className={openselect ? "active" : ""} onClick={() => this.opneMilelist(1)}>{data && data.open_count}个开启中</li>
<li className={closeselect ? "active" : ""} onClick={() => this.opneMilelist(2)}>{data && data.closed_count}个已关闭</li>
</div>
<div className="topWrapper_select">
<ul className="topWrapper_select mb-0">
<li>
<Dropdown className="topWrapperSelect" overlay={menu} trigger={['click']} placement="bottomCenter">
<span>排序<Icon type="caret-down" className="ml5" /></span>
</Dropdown>
</li>
</ul>
{
data && data.user_admin_or_member ?
<CheckProfile {...this.props} className="topWrapper_btn" sureFunc={() => {this.props.history.push(`/${owner}/${projectsId}/milestones/new`)}}>新的里程碑</CheckProfile>
: ''
}
</div>
</div>
{
data && data.versions && data.versions.length > 0
&&
<div className="tagList">
{
data.versions.map((item, key) => {
return (
<div style={{ display: 'block' }} key={key}>
<div className="milepostdiv">
<div className="milepostwidth">
<div className="grid-item width100">
<i className="iconfont icon-lubiaosignpost3 font-12 mr3"></i>
<Link to={`/${owner}/${projectsId}/milestones/${item.id}`} className="font-16">{item.name}</Link>
</div>
</div>
</div>
<div className="milepostdiv" style={{ marginTop: 5 }}>
<div className="milepostrighe">
<div className="grid-item mr10">
<i className="iconfont icon-rili font-14 mr5"></i>
<span className={item.effective_date ? "color-red" : "color-grey-c"}>{item.effective_date || "暂无截止时间"}</span>
</div>
<div className="grid-item mr10 color-grey-9">
<i className="iconfont icon-issue font-14 mr5"></i>
<span>{item.open_issues_count}个开启</span>
</div>
<div className="grid-item mr10 color-grey-9">
<i className="iconfont icon-shanchudiao font-14 mr5"></i>
<span>{item.close_issues_count}个关闭</span>
</div>
</div>
{
data && data.user_admin_or_member ?
<div className="milepostleft">
<div className="grid-item ml15 color-grey-9">
<i className="iconfont icon-bianji3 font-14 mr5"></i>
<Link to={`/${owner}/${projectsId}/milestones/${item.id}/edit`} className="color-grey-9">编辑</Link>
</div>
<div className="grid-item ml15 color-grey-9">
<i className={item.status === "closed" ? "iconfont icon-gouxuan font-14 mr5":"iconfont icon-yiguanbi1 font-14 mr5"}></i>
<a onClick={() => this.updatestatusemile(item.status === "closed" ? "open" : "closed", item)} className="color-grey-9">{this.state.status === "closed" ? "开启" : "关闭"}</a>
</div>
<div className="grid-item ml15 color-grey-9">
<i className="iconfont icon-lajitong font-14 mr5" ></i>
<Popconfirm placement="bottom" title={'是否删除里程碑?'} okText="是" cancelText="否" onConfirm={() => this.closemile(item)}>
<a className="color-grey-9">删除</a>
</Popconfirm>
</div>
</div>
: ''
}
</div>
<div className="milepostdiv" style={{ marginTop: 5 }}>
<div className="textwidth">
<Text type="secondary" ellipsis={{ rows: 30, expandable: false, onExpand: Function }} >{item.description}</Text>
</div>
</div>
{/* 里程碑列表展示 */}
{data && data.milestones && data.milestones.length === 0 && <div className="milestonesNoDate"><img src={emp} alt=""/></div>}
{data && data.milestones && data.milestones.length > 0 && <div className='milepostList'>
{data.milestones.map((item, key)=>{
return <div className='flexSpaceBetween milepostItemBox'>
<div>
<div className="flexSpaceBetween">
<i className="iconfont icon-lichengbeiicon1 font-12 mr10 primaryColor"></i>
<Link to={`/${owner}/${projectsId}/milestones/${item.id}`} className="font-16 task-hide milepostInfo">{item.name}</Link>
</div>
{item.description && item.description.length > 44 ? <Tooltip title={item.description} placement={'topLeft'} overlayStyle={{width: '600px'}}><span className='color-grey-89 task-hide milepostInfo'>{item.description}</span></Tooltip> : <span className='color-grey-89 milepostInfo'>{item.description}</span>}
</div>
<div className="flexSpaceBetween actionMileBox">
<div className="grid-item effectiveDate">
<i className={`iconfont icon-a-31shijian font-15 mr10 ${item.effective_date ? "effectiveDate" : "color-grey-89"}`}></i>
<span className={`${item.effective_date ? "" : "color-grey-89"} ${item.effective_date && status !== 'closed' && new Date().getTime()-new Date(`${item.effective_date} 24:0`).getTime() > 0 ? 'outTime' : ''}`}>{item.effective_date || "暂无截止时间"}</span>
</div>
<div className="mr10 effectiveDate">
<span>{item.opened_issues_count || 0}个开启</span>
<span className='color-grey-89'> | </span>
<span>{item.close_issues_count || 0}个关闭</span>
</div>
{
(isManager || isDeveloper) && <div className="flexSpaceBetween">
<Link to={`/${owner}/${projectsId}/milestones/${item.id}/edit`} className="primaryColor"><i className="iconfont icon-a-bianji12 font-14 mr5 primaryColor"></i></Link>
<div className="grid-item ml15">
<i className={`effectiveDate iconfont font-14 mr5 ${item.status === "closed" ? "icon-gouxuan":"icon-shanchu8"}`}></i>
<a onClick={() => this.updatestatusemile(item.status === "closed" ? "open" : "closed", item)} className="effectiveDate">{item.status === "closed" ? "开启" : "关闭"}</a>
</div>
)
})
}
<div className="grid-item ml15">
<i className="iconfont icon-fuzhi-shanchu font-14 mr5 colorRed" ></i>
<Popconfirm placement="bottom" title={'是否删除里程碑?'} okText="是" cancelText="否" onConfirm={() => this.closemile(item)}>
<a className="colorRed">删除</a>
</Popconfirm>
</div>
</div>
}
</div>
</div>
}
{ data && data.versions && data.versions.length === 0 && <NoneData _html="暂无里程碑"></NoneData> }
})}
</div>}
{
data && data.versions_count > limit ?
<div className="mt30 mb50 edu-txt-center">
<Pagination simple current={page} total={data && data.versions_count} pageSize={limit} onChange={this.ChangePage}></Pagination>
data && data.total_count > limit ?
<div className="mt30 mb50 edu-txt-right">
<Pagination current={page} total={data && data.total_count} pageSize={limit} onChange={this.ChangePage} showQuickJumper showSizeChanger onShowSizeChange={this.onShowSizeChange}></Pagination>
</div> : ""
}
</div>

View File

@ -0,0 +1,267 @@
import React , { useRef , useState , useEffect } from 'react';
import { Menu, Input , Checkbox , Pagination, Button , Tooltip ,Spin } from 'antd';
import { Link } from "react-router-dom";
import AllMenus from './component/allMenus';
import Datas from '../Issues/Component/datas';
import axios from 'axios';
import { FlexAJ } from '../Component/layout';
import CheckProfile from '../Component/ProfileModal/Profile';
import emp from '../../forge/Issues/Img/emp.png';
import './milepost.scss';
import './order.scss';
import '../Issues/index.scss';
function MilepostDetail(props){
const [ milepost, setMilepost] = useState(undefined);
const [ issueList , setIssueList ] = useState(undefined);
const [ closedCount , setClosedCount ] = useState(0);
const [ openedCount , setOpenedCount ] = useState(0);
const[ aboutMe , setAboutMe ] = useState("aboutme");
const[ keyword , setKeyword ] = useState(undefined);
const [ category , setCategory] = useState("all");
const [ allValue , setAllValue ] = useState([]);
const [ allIds , setAllIds ] = useState([]);
const [ checkAll , setCheckAll ] = useState(false);
const [ updateIds , setUpdateIds ] = useState([]);
const [ limit, setLimit] = useState(10);
const [ page , setPage ] = useState(1);
const [ total , setTotal ] = useState(undefined);
const [ issuesCount, setIssueCount] = useState(undefined);
const menuRef = useRef(null);
const { projectsId, mileId , owner } = props.match.params;
const {current_user, projectDetail, showLoginDialog, history} = props;
const permission = props && props.projectDetail && props.projectDetail.permission;
//
const sortBy = {
'1': {'sort_by': 'issues.created_on', 'sort_direction': 'desc'},
'2': {'sort_by': 'issues.created_on', 'sort_direction': 'asc'},
'3': {'sort_by': 'issues.updated_on', 'sort_direction': 'desc'},
'4': {'sort_by': 'issues.updated_on', 'sort_direction': 'asc'},
'5': {'sort_by': 'issue_priorities.position', 'sort_direction': 'desc'},
'6': {'sort_by': 'issue_priorities.position', 'sort_direction': 'asc'}
}
useEffect(()=>{
Init();
},[aboutMe, keyword, category, page, limit])
useEffect(()=>{
updateDocumentTitle()
}, [projectDetail, milepost])
//
function updateDocumentTitle(){
if(projectDetail && milepost){
const { author, name} = projectDetail;
document.title = `${milepost.name}-里程碑-${author.name}/${name}`;
}
}
// issue
function Init(params){
const url = `/v1/${owner}/${projectsId}/milestones/${mileId}.json`;
axios.get(url,{
params:{
page,
limit,
category,
...params
}
}).then(result=>{
if(result){
const {milestone, issues, total_count, closed_issues_count, opened_issues_count, total_issues_count} = result.data;
if(issues.length === 0 && page>1){
setPage(page-1);
return;
}
setMilepost(milestone);
setIssueList(issues);
setTotal(total_count);
setClosedCount(closed_issues_count);
setOpenedCount(opened_issues_count);
setIssueCount(total_issues_count)
const ids = issues.length>0 ? issues.map(i=>{return i.id}) : [];
setAllIds(ids);
}
}).then(error=>{console.log("error:",error)})
}
//
const menu = (
<Menu selectedKeys={[`${aboutMe}`]} onClick={chooseAboutMe}>
<Menu.Item key={"all"}>全部</Menu.Item>
<Menu.Item key={"aboutme"}><Tooltip title="指我创建的、我负责的和@我的疑修">与我相关</Tooltip></Menu.Item>
<Menu.Item key={"assignedme"}>我负责的</Menu.Item>
<Menu.Item key={"authoredme"}>我创建的</Menu.Item>
<Menu.Item key={"atme"}>@我的</Menu.Item>
</Menu>
)
function chooseAboutMe(e){
setCategory("all");
setAboutMe(e.key);
}
// issue
function chooseAll(e){
setCheckAll(e.target.checked);
if(e.target.checked){
setAllValue(allIds);
}else{
setAllValue([]);
}
}
// issue
function checkIssues(value){
setAllValue(value);
if(value.length === allIds.length){
setCheckAll(true);
}else{
setCheckAll(false);
}
}
//
function cancelUpdate(){
setAllValue([]);
setCheckAll(false);
//
menuRef.current && menuRef.current.clearChoose();
}
// allValue updateIds
function sureUpdate(){
const url = `/v1/${owner}/${projectsId}/issues/batch_update`;
axios.patch(url,{
assigner_ids: updateIds && updateIds.assigner_id && updateIds.assigner_id.split(","),
ids: allValue,
issue_tag_ids: updateIds && updateIds.issue_tag_ids && updateIds.issue_tag_ids.split(","),
milestone_id: updateIds && updateIds.milestone_id,
}).then(result=>{
if(result){
setUpdateIds(undefined);
setAllValue([]);
setTotal(undefined);
Init();
cancelUpdate();
}
}).catch(error=>{})
}
//
function changepage(page){
setPage(page);
}
function chooseFunc(ids){
if(allValue && allValue.length>0){
// ids便
setUpdateIds(ids);
}else{
const {sort_by} = ids;
const params = {...ids, ...sortBy[sort_by]}
Init(params);
}
}
return(
<div className="main milepostDetail">
{milepost && <FlexAJ>
<div>
<p className="font-17 font-bd color-black" style={{width: '900px'}}>{milepost.name}</p>
<p className="mt7">
<span className="mr20">
<i className="iconfont icon-a-31shijian font-14 mr5 color-grey-89"></i>
<span className="color-grey-89">{milepost.effective_date || '暂无截止时间'}</span>
</span>
{(milepost.percent || milepost.percent===0) ? <span style={{color: '#1aaf42'}}> {milepost.percent > 0 ? milepost.percent.toFixed(2)*100:milepost.percent}%完成 </span> :"" }
</p>
</div>
<div className="milepostdiv">
{
(current_user && current_user.login) && (permission && permission !== "Reporter") ?
<Link to={`/${owner}/${projectsId}/milestones/${mileId}/edit`} className="grayButton" style={{ marginRight: 15 }}><i className="iconfont icon-lichengbeiicon1 font-12 mr5"></i>编辑里程碑</Link>
:""
}
{/* {
current_user && current_user.login ?
<Button type='primary' onClick={() => {history.push(`/${owner}/${projectsId}/issues/${mileId}/new`)}} className='createMilepostBtn' style={{width: '83px'}}>+ 创建疑修</Button>
:<Button type='primary' onClick={showLoginDialog} className='createMilepostBtn' style={{width: '83px'}}>+ 创建疑修</Button>
} */}
<CheckProfile {...props} sureFunc={()=>{history.push(`/${owner}/${projectsId}/issues/${mileId}/new`)}} checklogin className="operateButton ml20">+ 创建疑修</CheckProfile>
</div>
</FlexAJ>}
<div className="lists mt25">
<div className="listheader">
<div style={{display:"flex"}}>
<Checkbox value="all" style={{marginRight: "16px", display: (permission && permission !== "Reporter") ?"block":"none"}} checked={checkAll} onChange={chooseAll}></Checkbox>
{
allValue && allValue.length>0 ?
<span>选择{allValue.length}个issue</span>
:
<ul className="statusul">
<li className={category === "all" ?"active":""} onClick={()=>setCategory("all")}>全部<span>{issuesCount}</span></li>
<li className={category === "opened" ?"active":""} onClick={()=>setCategory("opened")}>开启中<span>{openedCount}</span></li>
<li className={category === "closed" ?"active":""} onClick={()=>setCategory("closed")}>已关闭<span>{closedCount}</span></li>
</ul>
}
</div>
<div className="menusul">
<AllMenus
ref={menuRef}
update={allValue && allValue.length>0}
owner={owner}
projectsId={projectsId}
chooseFunc={chooseFunc}
/>
{
allValue && allValue.length>0 ?
<div>
<Button type="primary" ghost onClick={sureUpdate}>确定</Button>
<Button ghost className="ml10 mr10" onClick={cancelUpdate}>取消</Button>
</div>
:""
}
</div>
</div>
{
total === 0 && <div className="milestonesNoDate"><img src={emp} alt=""/></div>
}
{
total > 0 &&
<React.Fragment>
<Checkbox.Group name="issues" onChange={checkIssues} value={allValue} style={{ width: "100%" }}>
<div className="listdatas">
{issueList.map((item,key)=>{
return(
<Datas
key={key}
checkbox={ <Checkbox value={item.id} key={item.id} style={{marginRight: "16px", display: (permission && permission !== "Reporter") ?"block":"none"}}></Checkbox> }
item={item}
owner={owner}
projectsId={projectsId}
/>
)
})
}
</div>
</Checkbox.Group>
{
total > 10 &&
<div className="pt25 pb30" style={{textAlign:"right"}}>
<Pagination total={total} pageSize={limit} current={page} onChange={changepage} showSizeChanger showQuickJumper onShowSizeChange={(current, pageSize)=>{setPage(1);setLimit(pageSize);}}/>
</div>
}
</React.Fragment>
}
{total === undefined && <div style={{height:344,display:"flex",alignItems:"center",justifyContent:"center"}}><Spin /></div> }
</div>
</div>
)
}
export default MilepostDetail;

View File

@ -1,14 +1,15 @@
import React, { Component } from "react";
import { Link } from 'react-router-dom';
import { Dropdown, Menu, Icon, Pagination, Spin } from 'antd';
import { Dropdown, Menu, Icon, Pagination, Spin, Button, Checkbox } from 'antd';
import './order.scss';
import { FlexAJ } from '../Component/layout';
import CheckProfile from '../Component/ProfileModal/Profile';
import AllMenus from "../Issues/Component/allMenus";
import NoneData from '../Nodata';
import OrderItem from './OrderItem';
import axios from 'axios';
import './milepost.scss';
/**
* issue_chosen:下拉的筛选列表,
@ -22,6 +23,7 @@ import axios from 'axios';
* search_count:列表总条数
* issue_type:搜索条件
* status_type: issue的关闭和开启1表示开启中的2表示关闭的
* category: issue状态 全部开启关闭
*/
class MilepostDetail extends Component {
constructor(props) {
@ -47,7 +49,11 @@ class MilepostDetail extends Component {
status_ids: "状态",
done_ratios: '完成度',
paix: '排序',
issueFlag:true
issueFlag:true,
checkAll: false,
allValue: [],
allIds: [],
category: 'all'
}
}
@ -55,7 +61,6 @@ class MilepostDetail extends Component {
this.getSelectList();
const { page } = this.state;
this.getIssueList(page);
//
}
componentDidUpdate=(prevProps)=>{
@ -122,7 +127,7 @@ class MilepostDetail extends Component {
const { projectsId, meilid , owner } = this.props.match.params;
const { limit , order_name , order_type , issue_tag_id , author_id , assigned_to_id , tracker_id , status_id , done_ratio , status_type } = this.state;
const url = `/${owner}/${projectsId}/milestones/${meilid}.json`;
const url = `/v1/${owner}/${projectsId}/milestones/${meilid}.json`;
let params = update ? {
page, limit , order_name:value , order_type:updateValue , issue_tag_id ,
author_id , assigned_to_id , tracker_id , status_id , done_ratio,
@ -138,7 +143,7 @@ class MilepostDetail extends Component {
}).then((result) => {
if (result) {
this.setState({
data: result.data,
data: result.data.milestone,
issues: result.data.issues,
search_count: params.status_type ==="1" ? result.data.open_issues_count : result.data.close_issues_count,
isSpin: false
@ -211,7 +216,7 @@ class MilepostDetail extends Component {
this.getIssueList(page);
}
openorder = (type) => {
category = (type) => {
this.setState({
status_type: type,
issue_tag_id : undefined,
@ -233,9 +238,18 @@ class MilepostDetail extends Component {
}
// 全选所有issue
chooseAll = (e) =>{
const {allIds} = this.state;
this.setState({
checkAll: e.target.checked,
allValue: e.target.checked ? allIds : []
});
}
render() {
const { issue_chosen, issues, limit, page, search_count, data, isSpin , status_type , issueFlag } = this.state;
const { issue_chosen, issues, limit, page, search_count, data, isSpin , status_type , issueFlag, checkAll, allValue, category } = this.state;
const { projectsId, meilid ,owner} = this.props.match.params;
const { current_user , showLoginDialog , projectDetail } = this.props;
const menu = (
@ -247,42 +261,74 @@ class MilepostDetail extends Component {
</Menu>
)
return (
<div className="main" style={{padding:"0px"}}>
<div className="miledetail">
<p className="font-20">{data && data.name}</p>
<FlexAJ>
<span className="mt7">
<span className="mr10">
<i className="iconfont icon-rili font-14 mr5">
<div className="main milepostDetail">
<FlexAJ>
<div>
<p className="font-17 font-bd color-black" style={{width: '900px'}}>{data && data.name}</p>
<p className="mt7">
<span className="mr20">
<i className="iconfont icon-a-31shijian font-14 mr5 color-grey-89">
</i>
{
data && data.effective_date ?
<span >{data && data.effective_date}</span>
<span className="color-grey-89">{data && data.effective_date}</span>
:
<span >暂无截止时间</span>
<span className="color-grey-89">暂无截止时间</span>
}
</span>
{data && (data.percent || data.percent===0) ? <span className="font-weight-bold"> {data.percent > 0 ? data.percent.toFixed(2):data.percent}%完成 </span> :"" }
</span>
<div className="milepostdiv">
{data && (data.percent || data.percent===0) ? <span style={{color: '#1aaf42'}}> {data.percent > 0 ? data.percent.toFixed(2):data.percent}%完成 </span> :"" }
</p>
</div>
<div className="milepostdiv">
{
(current_user && current_user.login) && ( projectDetail && projectDetail.permission && projectDetail.permission !== "Reporter") ?
<Link to={`/${owner}/${projectsId}/milestones/${meilid}/edit`} className="grayButton" style={{ marginRight: 15 }}><i className="iconfont icon-lichengbeiicon1 font-12 mr5"></i></Link>
:""
}
{
issueFlag ? (current_user && current_user.login ?
<Button type='primary' onClick={() => {this.props.history.push(`/${owner}/${projectsId}/issues/${meilid}/new`)}} className='createMilepostBtn' style={{width: '83px'}}>+ 创建疑修</Button>
:<Button type='primary' onClick={showLoginDialog} className='createMilepostBtn' style={{width: '83px'}}>+ 创建疑修</Button>)
:""
}
</div>
</FlexAJ>
<Spin spinning={isSpin}>
<div className="listheader">
<div style={{display:"flex"}}>
<Checkbox value="all" style={{marginRight: "16px"}} checked={checkAll} onChange={this.chooseAll}></Checkbox>
{
(current_user && current_user.login) && ( projectDetail && projectDetail.permission && projectDetail.permission !== "Reporter") ?
<Link to={`/${owner}/${projectsId}/milestones/${meilid}/edit`} className="topWrapper_btn" style={{ marginRight: 15 }} >编辑里程碑</Link>
:""
}
{
issueFlag ? (current_user && current_user.login ?
<CheckProfile {...this.props} sureFunc={()=>{this.props.history.push(`/${owner}/${projectsId}/issues/${meilid}/new`)}} className="topWrapper_btn">创建疑修</CheckProfile>
allValue && allValue.length>0 ?
<span>选择{allValue.length}个issue</span>
:
<a className="topWrapper_btn" onClick={showLoginDialog}>创建疑修</a>
):""
<ul className="statusul">
<li className={category === "all" ?"active":""} onClick={() => this.category("all")}>全部<span>{total}</span></li>
<li className={category === "opened" ?"active":""} onClick={() => this.category("opened")}>开启中<span>{openedCount}</span></li>
<li className={category === "closed" ?"active":""} onClick={() => this.category("closed")}>已关闭<span>{closedCount}</span></li>
</ul>
}
</div>
</FlexAJ>
</div>
<Spin spinning={isSpin}>
<div className="pl20 pr20">
<div className="menusul">
<AllMenus
ref={menuRef}
update={allValue && allValue.length>0}
owner={owner}
projectsId={projectsId}
chooseFunc={chooseFunc}
/>
{
allValue && allValue.length>0 ?
<div>
<Button type="primary" ghost onClick={sureUpdate}>确定</Button>
<Button type="danger" ghost className="ml10" onClick={()=>setVisible(true)}>删除</Button>
<Button ghost className="ml10 mr10" onClick={cancelUpdate}>取消</Button>
</div>
:""
}
</div>
</div>
{/* <div className="pl20 pr20">
<div className="f-wrap-between pt15 pb15 bor-bottom-greyE">
<ul className="topWrapper_type_infos">
<li className={status_type==="1" ? "active" : ""} onClick={() => this.openorder("1")}>{data && data.open_issues_count}个开启中</li>
@ -352,7 +398,7 @@ class MilepostDetail extends Component {
<Pagination simple current={page} total={search_count} pageSize={limit} onChange={this.ChangePage}></Pagination>
</div>:""
}
</div>
</div> */}
</Spin>
</div>
)

View File

@ -92,8 +92,7 @@ class UpdateMilepost extends Component {
}).then(result => {
if (result) {
this.setState({ isSpin: false })
this.props.history.push(`/${owner}/${projectsId}/milestones`);
this.props.history.push(`/${owner}/${projectsId}/milestones/${meilid}`);
}
}).catch(error => {
@ -133,7 +132,7 @@ class UpdateMilepost extends Component {
required: true, message: '请输入标题'
}],
})(
<Input placeholder="标题" />
<Input placeholder="标题" maxLength={30}/>
)}
</Form.Item>
</div>
@ -144,8 +143,7 @@ class UpdateMilepost extends Component {
required: true, message: '请输入描述内容'
}],
})(
<TextArea placeholder="请输入描述内容..." style={{ height: "150px" }} />
<TextArea placeholder="请输入描述内容..." style={{ height: "150px" }} maxLength={500}/>
)}
</Form.Item>
</div>

View File

@ -0,0 +1,155 @@
import React,{ useState , useEffect , forwardRef ,useImperativeHandle , useRef } from 'react';
import Menus from '../../Issues/Component/menus';
import axios from 'axios';
const array =[
{id:1,name:"最新创建"},
{id:2,name:"最早创建"},
{id:3,name:"最新更新"},
{id:4,name:"最早更新"},
{id:5,name:"高优先级"},
{id:6,name:"低优先级"}
]
function AllMenus({owner,projectsId,chooseFunc,update},ref){
//
const [ authorList , setAuthorList ] = useState(undefined);
const [ author , setAuthor ] = useState(undefined);
const [ tagList , setTagList ] = useState(undefined);
const [ tag , setTag ] = useState(undefined);
const [ chargeList , setChargeList ] = useState(undefined);
const [ charge , setCharge ] = useState(undefined);
const [ millstone , setMillstone ] = useState(undefined);
const [ millstoneList , setMillstoneList ] = useState(undefined);
const [ ids , setIds ] = useState({author_id:undefined,issue_priorities_id:undefined,issue_tag_ids:undefined,milestone_id:undefined,sort_by:undefined,status_id:undefined,assigner_id:undefined});
const [ names , setNames ] = useState({author_name:undefined,issue_priorities_name:undefined,issue_tag_name:undefined,milestone_name:undefined,sortby_name:undefined,status_name:undefined,assigner_name:undefined});
useImperativeHandle(ref, () => ({
clearChoose: () => {
// Ids\namesundefined
setIds({author_id:undefined,issue_tag_ids:undefined,issue_priorities_id:undefined,milestone_id:undefined,sort_by:undefined,status_id:undefined,assigner_id:undefined});
setNames({author_name:undefined,issue_tag_name:undefined,issue_priorities_name:undefined,milestone_name:undefined,sortby_name:undefined,status_name:undefined,assigner_name:undefined});
}
}))
//
useEffect(()=>{
getSendPerson();
},[author])
function getSendPerson(){
const url = `/v1/${owner}/${projectsId}/issue_authors`;
axios.get(url,{params:{keyword:author,only_name:true}}).then(result=>{
if(result && result.data){
setAuthorList(result.data.authors);
}
})
}
//
useEffect(()=>{
getSign();
},[tag])
function getSign(){
const url = `/v1/${owner}/${projectsId}/issue_tags`;
axios.get(url,{params:{keyword:tag,only_name:true}}).then(result=>{
if(result && result.data){
setTagList(result.data.issue_tags);
}
})
}
//
useEffect(()=>{
getCharge();
},[charge,update])
function getCharge(){
if(update){
const url = `/v1/${owner}/${projectsId}/collaborators`;
axios.get(url,{params:{keyword:charge,only_name:true}}).then(result=>{
if(result && result.data){
setChargeList(result.data.collaborators);
}
})
}else{
const url = `/v1/${owner}/${projectsId}/issue_assigners`;
axios.get(url,{params:{keyword:charge,only_name:true}}).then(result=>{
if(result && result.data){
setChargeList(result.data.assigners);
}
})
}
}
function choose(id,name){
let copy = {...ids,author_id:id && id.length>0 ? id.join(","):undefined};
let copyname = { ...names,author_name:name};
setIds(copy);setNames(copyname);
chooseFunc(copy,copyname);
}
//
useEffect(()=>{
getMillstone();
},[millstone])
function getMillstone(){
const url = `/v1/${owner}/${projectsId}/milestones`;
axios.get(url,{params:{keyword:millstone,only_name:true}}).then(result=>{
if(result && result.data){
setMillstoneList(result.data.milestones);
}
})
}
return(
<ul className="dropboxul">
{ !update && <Menus
update={update}
ids={ids && ids.author_id}
names={names && names.author_name}
name={"发布人" } size={"large"} imgControl
lists={authorList} searchFunc={(value)=>setAuthor(value)}
chooseFunc={(id,name)=>choose(id,name,'author_name')}
/>
}
<Menus
update={update}
ids={ids && ids.issue_tag_ids}
name={"标记"} size={"large"}
double
names={names && names.issue_tag_name}
lists={tagList} searchFunc={(value)=>setTag(value)}
chooseFunc={(id,name)=>{let copy = {...ids,issue_tag_ids:id && id.length>0 ? id.join(","):undefined};let copyname = { ...names,issue_tag_name:name};setNames(copyname);setIds(copy);chooseFunc(copy,copyname)}}
/>
<Menus
update={update}
ids={ids && ids.assigner_id}
name={"负责人"} size={"large"} imgControl
names={names && names.assigner_name}
lists={chargeList} searchFunc={(value)=>setCharge(value)}
double={update ? true : false }
chooseFunc={(id,name)=>{let copy = {...ids,assigner_id:id && id.length>0 ? id.join(","):undefined};let copyname = { ...names,assigner_name:name};setNames(copyname);setIds(copy);chooseFunc(copy,copyname)}}
/>
{ !update && <Menus
ids={ids && ids.sort_by}
name={"排序"} size={"small"}
lists={array}
names={names && names.sortby_name}
chooseFunc={(id,name)=>{let copy = {...ids,sort_by:id && id.length>0 ? id.join(","):undefined};let copyname = { ...names,sortby_name:name};setNames(copyname);setIds(copy);chooseFunc(copy,copyname)}}
/>
}
{update && <Menus
update={update}
ids={ids && ids.milestone_id}
name={"里程碑"} size={"large"}
names={names && names.milestone_name}
lists={millstoneList} searchFunc={(value)=>setMillstone(value)}
chooseFunc={(id,name)=>{let copy = {...ids,milestone_id:id && id.length>0 ? id.join(","):undefined};let copyname = { ...names,milestone_name:name};setNames(copyname);setIds(copy);chooseFunc(copy,copyname)}}
/>}
</ul>
)
}
export default forwardRef(AllMenus);

View File

@ -0,0 +1,108 @@
.milepostBox{
border: none;
padding: 0;
.createMilepost{
text-align: right;
}
}
.flexSpaceBetween{
display: flex;
justify-content: space-between;
align-items: center;
}
.milepostHead{
background-color:#fafcff;
border:1px solid rgba(42, 97, 255, 0.23);
color:#898d9d;
padding: 15px 15px 15px 20px;
border-radius:4px 4px 0px 0px;
.postStatus.active{
color:$primary-color;
.statusCount{
color: $primary-color;
border: 1px solid $primary-color;
background-color: #fff;
}
}
.statusCount{
background-color:rgba(70, 106, 255, 0.09);
border-radius:10px;
margin-left: 5px;
color:#666666;
font-weight: normal;
height: 19px;
line-height: 19px;
display: inline-block;
min-width: 30px;
text-align: center;
}
}
.pointBox{
cursor: pointer;
}
.milepostSort{
box-shadow:0px 0px 10px rgba(24, 54, 181, 0.17);
}
.milepostList{
border-left: 1px solid #d0d0d0;
border-right: 1px solid #d0d0d0;
.milepostItemBox{
border-bottom: 1px solid #d0d0d0;
padding: 15px 20px;
}
.milepostInfo{
display: inline-block;
width: 640px;
}
.actionMileBox{
width: 550px;
.outTime{
color: red;
}
}
}
.createMilepostBtn{
width: 96px;
padding: 0;
}
.primaryColor, .primaryColor:link, .flexSpaceBetween .primaryColor{
color: $primary-color;
}
.effectiveDate, .flexSpaceBetween .effectiveDate{
color:#40424a;
}
.color-grey-89{
color: #898d9d;
}
.colorRed, .colorRed:hover{
color:#f30000;
}
.milestonesNoDate{
border:1px solid#d0d0d0;
border-top: none;
text-align: center;
min-height: 500px;
line-height: 500px;
}
// 浅灰色按钮
.grayButton{
width:108px;
height:32px;
text-align: center;
background-color:#fafbfc;
border:1px solid #d0d0d0;
border-radius:4px;
&:hover{
background-color: #f3f4f6;
}
}
// 里程碑详情页面
.milepostDetail{
border: none;
padding: 0;
}
.noDataMile{
display: flex;
justify-content: center;
}

View File

@ -29,14 +29,6 @@
justify-content: space-between;
flex-wrap: wrap;
}
.miledetail {
padding:15px 20px;
box-sizing: border-box;
justify-content: space-between;
border-bottom: 1px solid #eeeeee;
flex-wrap: wrap;
}
.topWrapper_nav {
display: flex;
}
@ -232,6 +224,15 @@
border-bottom: 1px solid #eee;
padding: 16px 0px 16px 20px;
}
.issueIndex{
display: inline-block;
height:19px;
line-height:19px;
background-color:rgba(213, 220, 246, 0.36);
border-radius:4px;
padding:0px 8px;
color: #666!important;
}
.issueItem:last-child{
border-bottom: none;
}

View File

@ -47,6 +47,22 @@ const PrivateLetter = Loadable({
loader: () => import("./notice/privateLetter/Index"),
loading: Loading,
});
const ExploitBot = Loadable({
loader: () => import("./bot/exploit"),
loading: Loading,
});
const CreateBot = Loadable({
loader: () => import("./bot/exploit/new"),
loading: Loading,
});
const DispositionDetail = Loadable({
loader: () => import("./bot/disposition/detail"),
loading: Loading,
});
const Disposition = Loadable({
loader: () => import("./bot/disposition"),
loading: Loading,
});
function Index(props){
const { current_user,mygetHelmetapi , checkIfLogin } = props;
@ -54,7 +70,7 @@ function Index(props){
const notice_url = mygetHelmetapi && mygetHelmetapi.common && mygetHelmetapi.common.notice;
const [ avatarVisible , setAvatarVisible ] = useState(false);
useEffect(()=>{
if(checkIfLogin() === false){
props.history.push('/login');
@ -70,8 +86,8 @@ function Index(props){
return(
<div className="newMain clearfix whiteBack">
{
avatarVisible &&
<Avatar
avatarVisible &&
<Avatar
onCancel={onCancelAvatar}
avatarImg={getImageUrl(`/${current_user && current_user.image_url}`)}
login={current_user && current_user.login}
@ -94,13 +110,18 @@ function Index(props){
</ul>
{notice_url && <ul className="securityUl ul-border-buttom">
<li>消息通知</li>
<li className={(pathname.indexOf("/settings/notice")>-1 && pathname.indexOf("/settings/notice/config") == -1) || pathname.indexOf("/settings/notice/privateLetter")>-1 ?"active":""}><Link to={"/settings/notice"}><i className="iconfont icon-wodetongzhi"></i><span className="text-shodow-bold">我的通知</span></Link></li>
<li className={pathname.indexOf("/settings/notice/config")>-1 ?"active":""}><Link to={'/settings/notice/config'}><i className="iconfont icon-tongzhiguanli"></i><span className="text-shodow-bold">通知管理</span></Link></li>
<li className={(pathname.indexOf("/settings/notice")>-1 && pathname.indexOf("/settings/notice/config") == -1) || pathname.indexOf("/settings/notice/privateLetter")>-1 ?"active":""}><Link to={"/settings/notice"}><i className="iconfont icon-wodetongzhi font-14"></i><span className="text-shodow-bold">我的通知</span></Link></li>
<li className={pathname.indexOf("/settings/notice/config")>-1 ?"active":""}><Link to={'/settings/notice/config'}><i className="iconfont icon-tongzhiguanli font-14"></i><span className="text-shodow-bold">通知管理</span></Link></li>
</ul>}
<ul className="securityUl">
<ul className="securityUl ul-border-buttom">
<li>安全设置</li>
<li className={pathname.indexOf("/settings/SSH")>-1 ?"active":""}><Link to={`/settings/SSH`}><i className="iconfont icon-xuanzhongssh_icon mr5 font-14"></i><span className="text-shodow-bold">SSH密钥</span></Link></li>
</ul>
<ul className="securityUl">
<li>Bot开发</li>
<li className={pathname.indexOf("/settings/exploitBot")>-1 ?"active":""}><Link to={`/settings/exploitBot`}><i className="iconfont icon-kaifabot mr5 font-18"></i><span className="text-shodow-bold">我的Bot</span></Link></li>
<li className={pathname.indexOf("/settings/dispositionBot")>-1 ?"active":""}><Link to={`/settings/dispositionBot`}><i className="iconfont icon-BOTpeizhi mr5 font-17"></i><span className="text-shodow-bold">Bot安装</span></Link></li>
</ul>
</div>
<LongWidth>
<Gap>
@ -165,6 +186,34 @@ function Index(props){
<SSHIndex {...props} {...p}/>
)}
></Route>
{/* 创建一个新的bot */}
<Route
path="/settings/exploitBot/new"
render={(p) => (
<CreateBot {...props} {...p}/>
)}
></Route>
{/* 开发bot */}
<Route
path="/settings/exploitBot"
render={(p) => (
<ExploitBot {...props} {...p}/>
)}
></Route>
{/* bot配置页面 */}
<Route
path="/settings/dispositionBot/:id"
render={(p) => (
<DispositionDetail {...props} {...p}/>
)}
></Route>
{/* bot配置 */}
<Route
path="/settings/dispositionBot"
render={(p) => (
<Disposition {...props} {...p}/>
)}
></Route>
</Switch>
</Gap>
</LongWidth>

View File

@ -6,12 +6,12 @@
padding:30px 0px 10px;
.shortW{
width: 198px;
border: 1px solid rgba(153, 153, 153, 0.22);
border: 1px solid rgba(79, 108, 188, 0.21);
border-radius: 4px;
// min-height: 400px;
margin-bottom: 30px;
.userDetail{
background: rgba(153, 153, 153, 0.05);
background: rgba(124, 146, 230, 0.05);
border-radius: 4px 4px 0px 0px;
padding:20px 25px;
text-align: center;
@ -32,52 +32,40 @@
}
}
.securityUl{
padding-left: 17px;
color: #333;
margin-bottom: 0px;
padding-bottom: 12px;
padding-top: 5px;
li{
font-size: 14px;
margin-top: 10px;
margin-bottom: 10px;
height: 27px;
line-height: 27px;
height: 36px;
line-height: 36px;
position: relative;
cursor: default;
padding-left: 17px;
font-weight:500;
color:#142a56;
font-size:16px;
a{
color: #666;
&:hover{
color: #333;
}
color:#202d40;
font-size: 14px;
}
&.active a{
color: #333;
.text-shodow-bold{
font-weight: 400;
text-shadow: 0.5px 0 #333;
}
&:hover{
background-color:rgba(70, 106, 255, 0.05);
}
&.active::before{
position: absolute;
left: -18px;
top:6px;
height: 15px;
width: 3px;
border-radius: 2px;
content: "";
background-color: #466AFF;
&:first-child:hover{
background-color: white;
}
&.active{
background-color:rgba(70, 106, 255, 0.05);
a{color: $primary-color;}
}
i{
font-size: 14px !important;
// font-size: 14px !important;
margin-right: 8px;
}
}
}
.ul-border-buttom{
border-bottom: 1px solid rgba(153, 153, 153, 0.22);
border-bottom: 1px solid #eef3ff;
padding-bottom: 5px;
}
}
@ -220,4 +208,96 @@
display: block!important;
}
}
}
.dangerBorBut, .dangerBorBut:focus{
background-color:rgba(196, 0, 14, 0.09);
border:1px solid #f60011;
border-radius:5px;
color:#f60011;
&::after{display: none;}
&:hover{
background-color:rgba(196, 0, 14, 0.18);
border:1px solid #ff727c;
color:#f60011;
}
&:active{
background-color:rgba(196, 0, 14, 0.22);
border:1px solid #f60011;
color:#f60011;
}
}
.themeCorBorBut, .themeCorBorBut:focus{
border:1px solid $primary-color;
border-radius:5px;
color: $primary-color;
&::after{display: none;}
&:hover{
border:1px solid $primary-color-hover;
}
&:active{
border:1px solid $primary-color-active;
}
}
.grayBorBut, .grayBorBut:focus{
background-color:#fbfbfb;
border:1px solid #d5dbec;
border-radius:5px;
color:#333333;
&::after{display: none;}
&:hover{
background-color:#edf0f6;
border:1px solid #adb8d6;
color:#333333;
}
&:active{
background-color:#e7edfb;
border:1px solid #adb8d6;
color:#333333;
}
&.redFontBut, &.redFontBut:hover, &.redFontBut:active, &.redFontBut:focus{
color:#f60011;
}
&.whiteBackBut, &.whiteBackBut:hover, &.whiteBackBut:active, &.whiteBackBut:focus{
background-color:#ffffff;
}
}
.f8HeadModal{
color:#333333;
.ant-modal-content{border-radius: 0;}
.ant-modal-header{
height: 56px;
padding: 10px 30px;
background-color: #f8f8f8;
}
.ant-modal-title{
text-align: left;
font-weight: normal !important;
}
.ant-modal-close-x{
font-size: 25px;
line-height: 45px;
}
.titleTip{margin-left: 45px;}
.deleteTip{
color: #666666;
margin-top: 20px;
margin-left: 92px;
}
.ant-modal-footer{
border-top: none;
padding: 40px 0 60px;
text-align: center;
}
}
.circleRed{
display: inline-block;
width:36px;
height:36px;
line-height: 36px;
text-align: center;
border-radius: 50%;
background-color:#ca0002;
color: white;
margin-right: 15px;
}

View File

@ -0,0 +1,184 @@
import { Button, Checkbox, message, Radio, Select, Switch } from "antd";
import axios from "axios";
import moment from "moment";
import React, { useEffect } from "react";
import { useState } from "react";
import { Link } from "react-router-dom";
import { getInstallBot, updateInstallBot } from "../../../../softbot/api";
import { getImageUrl } from 'educoder';
import './index.scss';
function DispositionDetail(props){
const {match:{params:{id}}, current_user} = props;
const [checkValue, setCheckValue] = useState(0);
const [selectValue, setSelectValue] = useState([]);
const [botDetail, setBotDetail] = useState(undefined);
const [options, setOptions] = useState(undefined);
const [filterOption, setFilterOption] = useState([]);
// bot
const [botState, setBotState] = useState(true);
// const filterOption = options.filter(item=>!selectValue.includes(item));
// bot
useEffect(()=>{
current_user && getInstallBot({
login: current_user.login,
user_id: current_user.user_id,
bot_id: id
}).then(res=>{
if(res && res.code === 200){
const {state, market_name} = res.data;
document.title = `${market_name}-Bot配置`
setBotState(state);
setBotDetail(res.data);
}
})
//
current_user && axios.get(`/users/${current_user.login}/projects.json`,{
params:{
limit: 1000,
page: 1,
category:"manage"
}
}).then(result=>{
if(result && result.data){
setOptions(result.data.projects);
setFilterOption(result.data.projects);
}
}).catch(error=>{
console.log(error);
})
}, [])
// selectValue
useEffect(()=>{
const ids = selectValue.map(item=>{return item.id});
options && setFilterOption(options.filter(item=>!ids.includes(item.id)));
}, [selectValue])
//
useEffect(()=>{
if(botDetail && options){
if(botDetail.store_id_list.length === options.length){
setCheckValue(0);
}else{
setCheckValue(1);
//
const {store_id_list} = botDetail;
const allStoreId = options.map((item)=>{return item.id});
let newMap = selectValue;
store_id_list.map(item=>{
if(allStoreId.includes(item)){
newMap = newMap.concat(options.filter(i=>{return i.id === item})[0]);
}
})
setSelectValue(newMap);
}
}
}, [options, botDetail])
function selectChange(e){
let newMap = selectValue;
newMap = newMap.concat(options.filter(i=>{return i.id === e})[0]);
setSelectValue(newMap);
}
function deleteSelect(e){
let newMap = selectValue;
newMap = newMap.filter(item=>{return item.id !== e.id});
setSelectValue(newMap);
}
function updateInsBot(){
const repo_map = {};
if(checkValue){
selectValue.map(item=>{
repo_map[item.id+""] = item.identifier;
});
}else{
options.map(item=>{
repo_map[item.id+""] = item.identifier;
});
}
updateInstallBot({
bot_id: id,
password: "",
state: botState ? 1 : 0,
store_list: checkValue ? selectValue.map(item=>{return item.id}) : options.map(item=>{return item.id}),
login: current_user && current_user.login,
user_id: current_user && current_user.user_id,
repo_map
}).then(res=>{
if(res && res.code === 200){
message.success('更改成功');
}
})
}
return <div className="dispositionDetailBox">
<div className="oneLine mb30">
{botDetail && <div className="oneLine">
<img src={getImageUrl(botDetail.logo)} alt="" className="botImg imgBox"/>
<div className="ml40 font-16">
<div className="font-22"><a href={`/softbot/${id}`}>{botDetail && botDetail.market_name}</a></div>
<a className="mr30 color88" href={`/${botDetail.register_login}`}>{botDetail && botDetail.register_name}</a>
<span className="color88">{botDetail && botDetail.create_time}</span>
</div>
</div>}
<Button style={{width: '53px', padding: '0'}} className="grayBorBut"><Link to={`/settings/dispositionBot`}>返回</Link></Button>
</div>
{botDetail && <div className="mb10 greenCol font-16"><i className={`mr8 iconfont ${botDetail.limit_vo.juris_diction_code !== 2 ? 'icon-wanchenggouxuan' : 'icon-tishi1 font-16'}`}></i>代码库{botDetail.limit_vo.juris_diction_code === 2 ? '无' : botDetail.limit_vo.juris_diction_code === 1 ? '读写' : '只读'}权限</div>}
{botDetail && <div className="mb20 greenCol font-16"><i className={`mr8 iconfont ${botDetail.limit_vo.juris_diction_pr !== 2 ? 'icon-wanchenggouxuan' : 'icon-tishi1 font-16'}`}></i>合并请求(PR){botDetail.limit_vo.juris_diction_pr === 2 ? '无' : botDetail.limit_vo.juris_diction_pr === 1 ? '读写' : '只读'}权限</div>}
<div className="detailTil font-16">更改安装位置</div>
<div>
<Radio.Group onChange={(e)=>{setCheckValue(e.target.value)}} value={checkValue}>
<Radio value={0} className="radioBox mt10 font-16">安装到所有仓库<br/><span className="font-15 radioBoxTip">安装到用户拥有的所有仓库中</span></Radio>
<Radio value={1} className="radioBox mt15 font-16">安装到指定仓库<br/><span className="font-15 radioBoxTip">请至少选择一个仓库</span></Radio>
</Radio.Group>
{checkValue === 1 && <Select
placeholder="请选择仓库名称"
value={selectValue.map(item=>{return item.id})}
onChange={selectChange}
className="radioBox selectBox"
>
{filterOption.map(item=>(<Select.Option key={item.id} value={item.id}>{item.name}</Select.Option>))}
</Select>}
{checkValue === 1 && selectValue.map((item, index)=>{return <div key={index} className="selectValueBox font-14">
<i className="iconfont icon-daimakuicon1 font-14 mr5"></i>
<Link className="selectValue" to={`/${item.author && item.author.login}/${item.identifier}`}>{`${item.author && item.author.name}/${item.name}`}</Link>
<i className="iconfont icon-guanbi font-12 close ml30" onClick={()=>{deleteSelect(item)}}></i>
</div>})}
</div>
<div className="detailTil font-16 mt30 mb10">更改Bot状态</div>
{/* {botDetail && botDetail.state === 1 && <div className="disBotItem">
<div>
<div className="font-15">挂起</div>
<span className='changeStatusTip'>暂停停止使用该bot阻塞该bot拥有的仓库访问权限</span>
</div>
<Button className="dangerBorBut" style={{width: '88px', height: '36px'}}>挂起</Button>
</div>}
{botDetail && botDetail.state === 0 && <div className="disBotItem">
<div>
<div className="font-15">启用</div>
<span className='changeStatusTip'>开始使用该Bot授予该Bot所需的仓库访问权限</span>
</div>
<Button className="themeCorBorBut" style={{width: '88px', height: '36px'}}>启用</Button>
</div>} */}
<div className="ml25">
{botDetail && <Switch defaultChecked = {botDetail.state} onChange={(checked)=>{setBotState(checked)}}/>}
<div className="mt10">
<div className="font-15">{botState ? '启用' : '挂起'}</div>
<span className='changeStatusTip'>{botState ? '开始使用该Bot授予该Bot所需的仓库访问权限' : '暂停停止使用该bot阻塞该bot拥有的仓库访问权限'}</span>
</div>
</div>
{/* <div className="disBotItem">
<div>
<div className="font-15">卸载</div>
<span className='changeStatusTip'>将该bot从仓库中卸载回收所有分配给该bot的权限将其从安装列表中删除</span>
</div>
<Button className="dangerBorBut" style={{width: '88px', height: '36px'}}>卸载</Button>
</div> */}
<Button type="primary" style={{width: '100px', height: '36px'}} className="mt30" onClick={updateInsBot}>保存</Button>
</div>
}
export default DispositionDetail;

View File

@ -0,0 +1,86 @@
import React, { useEffect, useState } from "react";
import { Button, message, Modal } from "antd";
import { Link } from "react-router-dom";
import { getImageUrl } from 'educoder';
import nullBot from '../../../../softbot/image/notBot.png';
import { getAllInstallBots, deleteInstallBot } from "../../../../softbot/api";
import './index.scss';
// bot
function DispositionBot(props){
const {current_user} = props;
const [botList, setBotList] = useState(undefined);
const [delBotId, setDelBotId] = useState(undefined);
const [visible, setVisible] = useState(false);
const [reload, setReload] = useState(undefined);
useEffect(()=>{
window.scrollTo(0,0);
document.title='Bot安装';
}, [])
useEffect(()=>{
getAllInstallBots({
user_id: current_user && current_user.user_id
}).then(res=>{
if(res && res.code === 200){
setBotList(res.data.install_bots);
}
})
}, [reload])
function delInstallBot(){
current_user && deleteInstallBot({
login: current_user.login,
user_id: current_user.user_id,
bot_id: delBotId,
password: ""
}).then(res=>{
if(res && res.code === 200){
message.success('卸载成功');
setVisible(false);
setReload(Math.random());
}
})
}
return <div>
<div className="dispositionHead font-18">Bot安装</div>
{/* bot列表显示区域 */}
{botList && (botList.length > 0 ? <div className="setBotListBox">
{botList.map((item, index)=>{return <div className="disBotItem" key={index}>
<div className="botNameLeft">
<img src={getImageUrl(item.logo)} alt="" className="imgBox mr20"/>
<Link className="font-15 botName" to={`/settings/dispositionBot/${item.bot_id}`}>{item.bot_name}</Link>
<span className={`statusBox font-12 ml10 ${item.state === 0 ? '' : 'active'}`}>{item.state === 0 ? '挂起' : '启用'}</span>
</div>
<div>
<Button className="themeCorBorBut" style={{width: '68px', height: '36px'}}><Link to={`/settings/dispositionBot/${item.bot_id}`}>配置</Link></Button>
<Button className="dangerBorBut ml20" style={{width: '68px', height: '36px'}} onClick={()=>{setDelBotId(item.bot_id);setVisible(true)}}>卸载</Button>
</div>
</div>})}
</div> : <div className="nullBotsBox mt80">
<img src={nullBot} alt="" width={62}/>
<p className="font-18 showBigTip">您当前暂未安装任何Bot</p>
<div className="showTip font-14">您可以将Bot配置到仓库来处理一些繁杂的项目任务比如自动化关闭疑修合并请求等以节约时间精力提升开发效率快去<Link to="/softbot"> Bot市场 </Link>中挑选一个适合您项目的Bot吧!</div>
<div className="borTip"></div>
<Button type="primary" style={{width: '112px', height: '36px', padding: 0}}><Link to={'/softbot'}>去Bot市场逛逛</Link></Button>
</div>)}
<Modal
className="f8HeadModal"
width="485px"
title="卸载Bot"
visible={visible}
maskClosable={false}
footer={[
<Button onClick={()=>{setVisible(false)}} className="mr30 grayBorBut whiteBackBut" style={{width:"104px", height: '36px'}}>取消</Button>,
<Button className="redFontBut grayBorBut whiteBackBut" style={{width:"104px", height: '36px'}} onClick={delInstallBot}>确认卸载</Button>
]}
onCancel={()=>{setVisible(false)}}
>
<div className="font-16 titleTip mt20"><span className="circleRed font-18">!</span>您确定要卸载此Bot吗</div>
<div className="deleteTip">卸载后将回收所有分配给该bot的权限</div>
</Modal>
</div>
}
export default DispositionBot;

View File

@ -0,0 +1,124 @@
.disBotItem, .oneLine{
display: flex;
justify-content: space-between;
align-items: center;
}
.dispositionHead{
height: 65px;
line-height: 65px;
border-bottom: 1px solid #e0e6f5;
color:#151d40;
}
.disBotItem{
border-bottom: 1px dashed #e0e6f5;
padding: 20px 0 15px;
.botNameLeft{
display: flex;
align-items: center;
width: 80%;
}
.botName{
display: inline-block;
max-width: 80%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.setBotListBox{
margin-top: 16px;
color:#202d40;
.statusBox{
background-color:rgba(255, 81, 81, 0.08);
border:1px solid#f23737;
color:#f92d2d;
border-radius:4px;
padding: 4px 6px;
line-height: 11px;
&.active{
background-color:rgba(58, 213, 115, 0.08);
border-color:#25c589;
color:#25c589;
}
}
}
.imgBox{
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
}
.dispositionDetailBox{
color:#202d40;
.color88{
color:#8893af;
}
a.color88:hover{color: $primary-color;}
.botImg{
width: 86px;
height: 86px;
}
.greenCol{
color:#202d40;
&>.icon-tishi1{
color:#f60011;
}
&>.icon-wanchenggouxuan{
color:#00bb75;
}
}
.detailTil{
font-weight: 700;
color:#151d40;
padding-bottom: 15px;
border-bottom: 1px solid #e0e6f5;
}
.radioBox{
display: block;
color:#202d40;
.radioBoxTip{
display: inline-block;
margin-top: 5px;
margin-left: 24px;
color:#99a2af;
}
}
.selectBox{
width: 260px;
margin: 10px 0 0 24px;
.ant-select-selection{padding-top: 0;}
.ant-select-selection__placeholder{
display: block !important;
}
.ant-select-selection-selected-value{
display: none !important;
}
}
.selectValueBox{
margin: 13px 0 0px 23px;
color:#333333;
border-bottom: 1px dashed #e0e6f5;
padding-bottom: 10px;
.close{
cursor: pointer;
color: rgba(0, 0, 0, 0.45);
}
}
.changeStatusTip{color:#99a2af;}
}
.nullBotsBox{
border-radius: 4px 4px 0px 0px;
text-align: center;
padding: 25px 158px 65px;
.showBigTip{
color:#333333;
margin-top: 16px;
margin-bottom: 22px !important;
}
.showTip{color: #666;}
.borTip{
width: 82%;
margin: 20px auto 18px;
border-top: 1px solid rgba(90, 117, 193, 0.23)
}
}

View File

@ -0,0 +1,107 @@
import React ,{useEffect, useRef, useState} from 'react';
import { Modal , message } from 'antd';
import Cropper from 'react-cropper';
import 'cropperjs/dist/cropper.css';
import axios from 'axios';
import '../../../../../../forge/users/Avatar/Index.scss';
function Index({onCancel,avatarImg,id, botDetail}){
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/png");
if (!imgUrl) {
message.info('请先上传图片');
}
const params = {
...botDetail,
bot_id: parseInt(id)
}
console.log('params', params);
registerUpdateBot(params).then(res=>{
if(res && res.code === 200){
message.success("更改成功");
setReload(Math.random());
}
})
}
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 = () => {
if(reader.result){
setAvatarPhoto(reader.result);
}
};
reader.readAsDataURL(files[0]);
}
return(
<Modal
visible={true}
width="638px"
footer={null}
centered
maskClosable={false}
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,259 @@
import { AutoComplete, Button, message, Modal } from "antd";
import React from "react";
import { useState, useEffect } from "react";
import { getImageUrl } from 'educoder';
import './index.scss';
import axios from "axios";
import { Link } from "react-router-dom";
import { deleteBot, transferBot, registerUpdateBot, downMarket, getMarketBotById, cancelTransferBot } from "../../../../../../softbot/api";
const { Option } = AutoComplete;
// Bot
function AdvancedInformation(props){
const {match, current_user, match:{params:{id}}, botDetail, setReload} = props;
const [ visibleMakeOver, setVisibleMakeOver] = useState(false);
const [ newId , setNewId] = useState(undefined);
const [ source , setSource ] = useState(undefined);
const [ searchKey , setSearchKey ] = useState(undefined);
// bot012
const [ type, setType] = useState(undefined);
const [ visibleDelete, setVisibleDelete] = useState(false);
// bot
const [makeOverErrortTip, setMakeOverErrortTip] = useState(undefined);
//
const [isMarket, setIsMarket] = useState(false);
//
const [marketReload, setMarketReload] = useState(undefined);
useEffect(()=>{
// bot
getMarketBotById({bot_id: id}).then(res=>{
res && setIsMarket(res.code === 200);
})
}, [marketReload])
useEffect(()=>{
getUserList();
},[searchKey])
function getUserList(e){
const url = `/users/list.json`;
axios.get(url, {
params: {
search: searchKey,
},
}).then((result) => {
if (result) {
sourceOptions(result.data.users);
}
})
.catch((error) => {
console.log(error);
});
};
function changeInputUser(e){
setSearchKey(e);
};
//
function selectInputUser(e, option){
setNewId(option.props);
setSearchKey(option.props.name);
};
function sourceOptions(userDataSource){
const s = userDataSource && userDataSource.map((item, key) => {
return (
<Option
key={key}
value={`${item.user_id}`}
login={`${item.login}`}
name={item.username}
>
<img
className="user_img radius"
width="28"
height="28"
src={getImageUrl(`/${item && item.image_url}`)}
alt=""
/>
<span className="ml10" style={{ verticalAlign: "middle" }}>
{item.username}
<span className="color-grey ml10">({item.login})</span>
</span>
</Option>
);
});
setSource(s);
}
//
function makeOver(){
setMakeOverErrortTip(undefined);
if(newId){
const {value, login} = newId
transferBot({
login: current_user && current_user.login,
user_id: current_user && current_user.user_id,
bot_id: parseInt(id),
transfer_to_user_id: parseInt(value),
to_logn: login
}).then(res=>{
if(res && res.code === 200){
message.success('操作成功');
setNewId(undefined);
setVisibleMakeOver(false);
setReload(Math.random());
}
})
}else{
setMakeOverErrortTip("请选择被转让者");
}
}
// ///Bot
function submitModal(){
if(type === '3'){
//
downMarket(parseInt(id), {}).then(res=>{
if(res && res.code === 200){
message.success('下架成功');
setVisibleDelete(false);
setMarketReload(Math.random());
}
})
}else if(type === '0'){
// bot
const params = {
login: current_user && current_user.login,
user_id: current_user && current_user.user_id,
bot_id: parseInt(id)
}
deleteBot(params).then(res=>{
if(res && res.code === 200){
message.success('删除成功');
setVisibleDelete(false);
props.history.push("/settings/exploitBot/configuration");
}
})
}else{
// /bot
const params = {
...botDetail,
bot_id: parseInt(id),
is_public: type === '1' ? 1 : 0
}
registerUpdateBot(params).then(res=>{
if(res && res.code === 200){
setVisibleDelete(false);
message.success("更改成功");
setReload(Math.random());
}else{
setVisibleDelete(false);
message.error(res.message);
}
})
}
}
function cancelTra(){
current_user && cancelTransferBot({
bot_id: parseInt(id),
login: current_user.login,
user_id: current_user.user_id
}).then(res=>{
if(res && res.code === 200){
message.success("操作成功");
setReload(Math.random());
}
})
}
return <div className="advancedInformationBox">
<div className="informationHead pb15">高级选项</div>
{/* 提示:转让中状态 */}
{botDetail && botDetail.is_transfer_success === 2 && <div className="transferBotStatus mt15"><i className="iconfont icon-yuyue-lishi-shijian font-18 mr5 ml10"></i>此Bot正在等待转移至<a href={`/${botDetail.transfer_to_login}`}>{botDetail.transfer_to_name +' (' + botDetail.transfer_to_login +')'}</a></div>}
<div className="disFlex itemBox">
<div className="font-15">
转让该Bot的所有权至其他用户
<p className="grayBox font-14">转让可能会推迟直到新的所有者批准转让</p>
</div>
{botDetail && (botDetail.is_transfer_success !== 2 ? <Button className="themeCorBorBut" style={{width:"88px", height: '36px', padding: '0'}} onClick={()=>{setVisibleMakeOver(true)}}>转让所有权</Button> :
<Button className="themeCorBorBut" style={{width:"88px", height: '36px', padding: '0'}} onClick={cancelTra}>取消转让</Button>)}
</div>
<div className="disFlex itemBox">
<div className="font-15">
删除Bot
<p className="grayBox font-14">该操作无法撤销请慎重</p>
</div>
<Button className="dangerBorBut" style={{width:"88px", height: '36px', padding: '0'}} onClick={()=>{setType('0');setVisibleDelete(true)}}>删除Bot</Button>
</div>
{botDetail && !botDetail.is_public && <div className="disFlex itemBox">
<div className="font-15">
私有Bot
<p className="grayBox font-14">目前您的bot将不能被其他用户安装</p>
</div>
<Button className="themeCorBorBut" style={{width:"88px", height: '36px', padding: '0'}} onClick={()=>{setType('1');setVisibleDelete(true)}}>公开此Bot</Button>
</div>}
{botDetail && !!botDetail.is_public && !isMarket && <div className="disFlex itemBox">
<div className="font-15">
公开Bot
<p className="grayBox font-14">目前您的Bot所有用户都能安装</p>
</div>
<div>
<Button type="primary" style={{width:"88px", height: '36px', padding: '0'}} className="mr20"><Link to={`/settings/exploitBot/configuration/${id}/advanced/putaway`}>上架市场</Link></Button>
<Button className="themeCorBorBut" style={{width:"88px", height: '36px', padding: '0'}} onClick={()=>{setType('2');setVisibleDelete(true)}}>私有此Bot</Button>
</div>
</div>}
{botDetail && !!botDetail.is_public && isMarket && <div className="disFlex itemBox">
<div className="font-15">
公开Bot
<p className="grayBox font-14">目前您的Bot所有用户都能安装</p>
</div>
<div>
<Button type="primary" style={{width:"88px", height: '36px', padding: '0'}} className="mr20"><Link to={`/settings/exploitBot/configuration/${id}/advanced/putaway/edit`}>编辑</Link></Button>
<Button className="themeCorBorBut" style={{width:"88px", height: '36px', padding: '0'}} onClick={()=>{setType('3');setVisibleDelete(true)}}>下架市场</Button>
</div>
</div>}
{/* 转让bot弹框 */}
<Modal
className="themeHeadModal"
width="430px"
title="转让可能会推迟,直到新的所有者批准转让"
visible={visibleMakeOver}
maskClosable={false}
footer={null}
onCancel={()=>{setVisibleMakeOver(false);setMakeOverErrortTip(undefined)}}>
<div className="font-15 mt5 mb10 makeOverTip">新的所有者账号</div>
{makeOverErrortTip && <div className="font-15 mt5 mb10 makeOverErrorTip">{makeOverErrortTip}</div>}
<AutoComplete
dataSource={source}
value={searchKey}
style={{ width: '100%' }}
onChange={changeInputUser}
onSelect={selectInputUser}
placeholder="请输入账户名称"
allowClear
/>
<Button type="primary" onClick={makeOver} className="makeOverSubmit" style={{width:"104px", height: '36px'}}>确认转让</Button>
</Modal>
{/* 删除/公开/私有Bot弹框 */}
<Modal
className="f8HeadModal"
width="550px"
title={`${type === '0' ? '删除' : type === '1' ? '公开' : type === '2' ? '私有' : '下架'}Bot`}
visible={visibleDelete}
maskClosable={false}
footer={[
<Button onClick={()=>{setVisibleDelete(false)}} className="mr30 grayBorBut whiteBackBut" style={{width:"104px", height: '36px'}}>取消</Button>,
<Button className="redFontBut grayBorBut whiteBackBut" style={{width:"104px", height: '36px'}} onClick={submitModal}>确认{type === '0' ? '删除' : type === '1' ? '公开' : type === '2' ? '私有' : '下架'}</Button>
]}
onCancel={()=>{setVisibleDelete(false)}}
>
<div className="font-16 titleTip mt20"><span className="circleRed font-18">!</span>您确定要{type === '0' ? '删除此Bot' : type === '1' ? `公开` : type === '2' ? '私有' : '下架'}{botDetail && botDetail.bot_name}</div>
<div className="deleteTip">{type === '0' ? `该操作无法撤销,将会永久删除${botDetail && botDetail.bot_name}` : type === '1' ? `公开后任何用户都可以安装此Bot` : type === '2' ? '私有后此Bot将只能被Bot开发者账户安装' : '下架后社区用户将无法在市场搜索到此Bot'}</div>
</Modal>
</div>
}
export default AdvancedInformation;

View File

@ -0,0 +1,167 @@
import React, { useEffect, useState } from "react";
import { Button, Form, Input, message } from "antd";
import MDEditor from '../../../../../../modules/tpm/challengesnew/tpm-md-editor';
import { Box } from "../../../../../Component/layout";
import miyao from '../../../../img/miyao.png';
import './index.scss';
import { Link } from "react-router-dom";
import { getImageUrl } from 'educoder';
import { registerUpdateBot } from "../../../../../../softbot/api";
// import Avatar from './UpdateAvatarModal';
import UploadImage from '../../../../../../forge/Team/Component/UploadImage.jsx';
import {CopyToClipboard} from 'react-copy-to-clipboard';
// Bot
function BasicInformation(props){
const {form, botDetail, match:{params:{id}}, setReload, current_user} = props;
const { getFieldDecorator, validateFields , setFieldsValue } = form;
const [ content , setContent ] = useState(undefined);
const [ avatarVisible , setAvatarVisible ] = useState(false);
const [ image , setImage ] = useState(undefined);
const [ imageFlag , setImageFlag ] = useState(false);
const [ imageId, setImageId] = useState(undefined);
function onCancelAvatar(){
setAvatarVisible(false);
}
function getImage(image){
setImageFlag(true);
setImage(image);
}
function getImageId(id){
setImageId(id);
}
function onContentChange(value){
setContent(value);
};
useEffect(()=>{
if(botDetail){
setContent(botDetail.bot_des);
setImage(botDetail.logo)
}
},[botDetail])
// Bot
function submit(e){
e.preventDefault();
validateFields((err, fieldsValue)=>{
if(err){
return;
}
const params = {
...botDetail,
...fieldsValue,
bot_id: parseInt(id)
}
registerUpdateBot(params).then(res=>{
if(res && res.code === 200){
message.success("更改成功");
setReload(Math.random());
}else{
message.error(res.message);
}
})
})
}
//
function saveUpdateImage(){
if(imageId){
const params = {
...botDetail,
bot_id: parseInt(id),
logo: '/api/attachments/'+imageId
}
registerUpdateBot(params).then(res=>{
if(res && res.code === 200){
message.success("成功修改头像");
setReload(Math.random());
}
})
}else{
message.error('请先上传图片');
}
}
// webhooks
function checkAddr(rule, value, callback) {
let reg = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/;
if(!value){
callback();
}
if (!reg.test(value)){
callback("请输入有效的URL");
}
callback();
}
return <Box>
{botDetail && <div className="basicInformationBox">
<p className="informationHead pb15 mb10">注册信息</p>
<div className="labelBox"><span className="label">Bot所有者</span><Link to={`/${current_user.login}`}>{current_user.username}</Link></div>
<div className="labelBox"><span className="label">Bot ID</span>{id}</div>
<div className="labelBox"><span className="label">客户端ID</span>{botDetail.client_id}</div>
<div className="labelBox"><span className="label">主页URL</span><Link to={`/softbot/${id}`} className="blueSpan">{window.location.origin + `/softbot/${id}`}</Link></div>
<div className="informationHead disFlex pb15 mt20 mb10">
客户端密钥
{/* <Button className="themeCorBorBut">生成密钥</Button> */}
</div>
{/* <div className="tipBox font-15">请确保您复制了客户端密钥,密钥信息将在刷新后隐藏</div> */}
<div className="disFlex miYaoBoxItem">
<div className="disFlex">
<img src={miyao} alt="" className="imgBox"/>
<div>
<p className="font-16">{botDetail.client_secret}</p>
{/* <div className="font-15 grayCol">生成者姓名<span className="ml20">时间</span></div> */}
</div>
</div>
<CopyToClipboard text={botDetail.client_secret} onCopy={()=>{message.success('复制成功')}}>
<Button className="themeCorBorBut">复制</Button>
</CopyToClipboard>
</div>
<p className="informationHead mt30 pb15">Bot信息</p>
<Form className="basicInformationForm mt10" onSubmit={submit}>
<Form.Item label="Bot名称" className="width80 botItem">
{getFieldDecorator("bot_name",{
rules:[{required:true,message:"请输入Bot名称"}],
initialValue: botDetail.bot_name
})(
<Input.TextArea placeholder="请输入Bot名称" maxLength={50} className="height36" autoSize={true}/>
)}
</Form.Item>
<Form.Item label="详细介绍" className="botItem introduce">
{getFieldDecorator("bot_des",{
rules:[]
})(
<MDEditor
placeholder={"请输入详细介绍"}
height={500}
mdID={"order-new-description"}
initValue={content}
onChange={onContentChange}
className="mt20"
isFocus={false}
></MDEditor>
)}
</Form.Item>
<Form.Item label="Webhook地址" className="width80 botItem">
{getFieldDecorator("webhook",{
rules:[{required:true,message:"请输入Webhook地址"},{validator:checkAddr}],
initialValue: botDetail.webhook
})(
<Input.TextArea placeholder="请输入Webhook地址" maxLength={200} className="height36" autoSize={true}/>
)}
</Form.Item>
<Form.Item className="mt30"><Button style={{width:"129px", height: '36px'}} type="primary" htmlType="submit">保存修改</Button></Form.Item>
</Form>
</div>}
<div className="headPortraitBox">
<UploadImage url={image && image.startsWith('/api/attachments/') ? getImageUrl(`${image}`) : image} getImage={getImage} getImageId = {getImageId}/>
<Button className="themeCorBorBut ml5" onClick={saveUpdateImage}>保存头像</Button>
</div>
</Box>
}
export default Form.create()(BasicInformation);

View File

@ -0,0 +1,126 @@
.informationHead{
border-bottom: 1px solid #e0e6f5;
font-weight:700;
color:#151d40;
font-size:16px;
}
.basicInformationBox{
width: 800px;
margin-right: 55px;
padding-bottom: 50px;
.labelBox{
line-height: 40px;
}
.blueSpan{
color: $primary-color;
}
.label{
display: inline-block;
width: 76px;
}
.tipBox{
background-color:rgba(70, 106, 255, 0.07);
border-radius:4px;
color:#151d40;
padding: 8px 13px;
}
.imgBox{
width: 38px;
margin-right: 25px;
}
.grayCol{
color:rgba(13, 22, 71, 0.65);
}
.miYaoBoxItem{
padding: 25px 0;
border-bottom: 1px dashed #e0e6f5;
&:hover{background-color:rgba(125, 148, 246, 0.05);}
}
.basicInformationForm{
.width80{
width: 80%;
}
.botItem{
margin-bottom: 5px;
.ant-form-item-label{
line-height: 30px;
}
&.introduce .ant-form-item-control-wrapper{
margin-top: -15px;
margin-bottom: 0;
}
}
}
}
.jurisdictionBox, .advancedInformationBox{
width: 85%;
padding-bottom: 45px;
}
.advancedInformationBox{
.grayBox{
color:#99a2af;
}
.itemBox{
padding: 15px 0;
border-bottom: 1px dashed #e0e6f5;
}
.transferBotStatus{
background-color:rgba(70, 106, 255, 0.09);
border:1px solid $primary-color;
border-radius:4px;
color:rgba(0, 0, 0, 0.65);
display: flex;
align-items: center;
}
.icon-yuyue-lishi-shijian{
color: $primary-color;
}
}
.themeHeadModal{
.ant-modal-header{
height: 55px;
padding: 8px 25px;
background-color:$primary-color;
}
.ant-modal-title{
text-align: left;
color:#ffffff;
font-weight: normal !important;
}
.ant-modal-close-x{
line-height: 38px;
font-size: 18px;
color:#ffffff;
}
.makeOverTip{color:#202d40;}
.makeOverSubmit{margin: 70px 145px;}
.ant-modal-body{
background-image:linear-gradient(42.46deg,#ffffff 0%,#ffffff 48.54%,#ebf0ff 100%);
}
.makeOverErrorTip{color:#f60011;}
}
.putawayBox{
width: 100%;
.putaHead{
color:#151d40;
border-bottom: 1px solid #e0e6f5;
}
.oneItem.ant-form-item{
margin-bottom: 6px;
&.introduce .ant-form-item-control-wrapper{
margin-top: -15px;
}
}
.selectBox{
display: flex;
justify-content: space-between;
.selectOne{
width: 48%;
}
}
.putawayBut[disabled]{
background-color:rgba(70, 106, 255, 0.56);
border: none;
color:#ffffff;
}
}

View File

@ -0,0 +1,120 @@
import React from "react";
import { Button, Checkbox, Form, Input, message, Select } from "antd";
import { useState } from "react";
import '../../new/index.scss';
import './index.scss';
import { registerUpdateBot } from "../../../../../../softbot/api";
import { useEffect } from "react";
const {Option} = Select;
// Bot
function Jurisdiction(props){
const {form, botDetail, setReload, match:{params:{id}}} = props;
const {getFieldDecorator, validateFields , setFieldsValue, getFieldsValue } = form;
const resource = [{value: 2, name: '无权限'}, {value: 0, name: '只读'}, {value: 1, name: '读写'}];
const [codeSelectValue , setCodeSelectValue ] = useState(undefined);
const [prSelectValue , setPrSelectValue ] = useState(undefined);
useEffect(()=>{
if(botDetail){
const {limit_and_events:{juris_diction_code, juris_diction_pr}} = botDetail
setCodeSelectValue(juris_diction_code);
setPrSelectValue(juris_diction_pr);
}
}, [botDetail])
function codeControl(e){
setCodeSelectValue(e);
return e;
}
function prControl(e){
setPrSelectValue(e);
return e;
}
// bot
function submit(e){
e.preventDefault();
validateFields((err, fieldsValue)=>{
if(err){
return;
}
const {juris_diction_code, juris_diction_pr, event_code, event_pr} = fieldsValue;
const params = {
...botDetail,
bot_id: parseInt(id),
limit_and_events: {
event_code: event_code ? event_code.toString() : "",
event_pr: event_pr ? event_pr.toString() : "",
juris_diction_code,
juris_diction_pr
},
}
registerUpdateBot(params).then(res=>{
if(res && res.code === 200){
message.success("更改成功");
setReload(Math.random());
}
})
})
}
// 访
function jurisDictionPrValidator(rule, value, callback){
const {juris_diction_code} = getFieldsValue();
if(juris_diction_code === 2 && value === 2){
callback('请至少选择一个权限');
return;
}
callback();
}
return <Form className="createExploitForm jurisdictionBox" onSubmit={submit}>
{botDetail && <div>
<div className="resourceTitle font-16 pb15 mb10">仓库访问权限</div>
<Form.Item label="代码库权限" className="resourceBox botItem dashedBorder">
<div className="color-99">代码库git推送分支的创建与删除</div>
{getFieldDecorator("juris_diction_code",{initialValue: botDetail.limit_and_events.juris_diction_code, getValueFromEvent: codeControl})(
<Select>
{resource.map(item=>{return <Option value={item.value} key={item.value}>{item.name}</Option>})}
</Select>
)}
</Form.Item>
<div className="dashedBor"></div>
<Form.Item label="合并请求(PR)权限" className="resourceBox botItem">
<div className="color-99">合并请求的打开关闭编辑分配</div>
{getFieldDecorator("juris_diction_pr",{rules:[{validator: jurisDictionPrValidator}],initialValue: botDetail.limit_and_events.juris_diction_pr, getValueFromEvent: prControl})(
<Select>
{resource.map(item=>{return <Option value={item.value} key={item.value}>{item.name}</Option>})}
</Select>
)}
</Form.Item>
{(codeSelectValue !== resource[0].value || prSelectValue !== resource[0].value) && <div className="resourceTitle font-16 pb15 mb10">订阅事件</div>}
{codeSelectValue !== resource[0].value && <Form.Item label="代码库事件" className="botItem width50 checkBox three dashedBorder">
{getFieldDecorator("event_code", {
initialValue: botDetail.limit_and_events.event_code,
rules:[{required:codeSelectValue !== resource[0].value,message:"请选择订阅事件!"}]
})(
<Checkbox.Group>
<Checkbox value={0}>推送<br/><span className="color-99">git推送到存储库</span></Checkbox>
<Checkbox value={1}>创建<br/><span className="color-99">创建分支或标签</span></Checkbox>
<Checkbox value={2}>删除<br/><span className="color-99">删除分支或标签</span></Checkbox>
</Checkbox.Group>
)}
</Form.Item>}
{(codeSelectValue !== resource[0].value && prSelectValue !== resource[0].value) && <div className="dashedBor"></div>}
{prSelectValue !== resource[0].value && <Form.Item label="合并请求事件" className="botItem width50 checkBox">
{getFieldDecorator("event_pr", {
initialValue: botDetail.limit_and_events.event_pr,
rules:[{required:prSelectValue !== resource[0].value,message:"请选择订阅事件!"}]
})(
<Checkbox.Group>
<Checkbox value={3}>合并请求<br/><span className="color-99">合并请求被打开被关闭被重新打开或被编辑</span></Checkbox>
<Checkbox value={4}>合并请求分配<br/><span className="color-99">合并请求被分配或取消分配</span></Checkbox>
</Checkbox.Group>
)}
</Form.Item>}
<Form.Item className="mt30"><Button style={{width:"129px", height: '36px'}} type="primary" htmlType="submit">保存修改</Button></Form.Item></div>}
</Form>
}
export default Form.create()(Jurisdiction);

View File

@ -0,0 +1,194 @@
import React, {forwardRef, useState, useEffect} from "react";
import { Button, Checkbox, Form, Input, message, Select } from "antd";
import { Link } from "react-router-dom";
import './index.scss';
import MdEditor from "../../../../../../modules/tpm/challengesnew/tpm-md-editor";
import { marketBot, getAllBotCategory, getMarketBotById, updateMarketBot } from "../../../../../../softbot/api";
import moment from "moment";
const {Option} = Select;
function Putaway(props){
const {form, current_user, match:{params:{id}}, botDetail, location:{pathname}} = props;
const { getFieldDecorator, validateFields , setFieldsValue } = form;
const [ resource, setResource] = useState([]);
const [ resourceFirst, setResourceFirst] = useState([]);
const [ resourceSecond, setResourceSecond] = useState([]);
const [ content , setContent ] = useState(undefined);
const [ disabled , setDisabled ] = useState(undefined);
const [ detail, setDetail] = useState(undefined);
const [ isEdit, setIsEdit] = useState(undefined);
useEffect(()=>{
//
getAllBotCategory().then(res=>{
if(res && res.code === 200){
setResource(res.data.category_list);
setResourceFirst(res.data.category_list);
setResourceSecond(res.data.category_list);
}
})
if(pathname && pathname.endsWith("/putaway/edit")){
setIsEdit(true);
setDisabled(true);
getMarketBotById({bot_id: id}).then(res=>{
if(res && res.code === 200){
setDetail({
...res.data
});
setContent(res.data.market_intro);
}
})
}else{
setIsEdit(false);
if(botDetail){
const {bot_des, bot_name, webhook} = botDetail;
setDetail({
bot_id: id,
category: "",
first_func: undefined,
logo: "",
market_desc: undefined,
market_name: bot_name,
market_time: undefined,
second_func: undefined,
webhook: webhook
})
setContent(bot_des);
}
}
}, [pathname, botDetail])
//
function onChange(e){
setDisabled(e.target.checked);
}
function onContentChange(value){
setContent(value);
};
// bot
function submit(e){
e.preventDefault();
validateFields((err, fieldsValue)=>{
if(err){
return;
}
const {logo} = botDetail;
if(isEdit){
// bot
updateMarketBot({
...fieldsValue,
category: "",
is_receive_ag1: 1,
is_receive_ag2: 0,
logo: logo,
market_time: "",
login: current_user && current_user.login,
user_id: current_user && current_user.user_id,
bot_id: parseInt(id),
}).then(res=>{
if(res && res.code === 200){
message.success("修改成功");
}else{
message.error(res.data);
}
})
}else{
// bot
marketBot({
...fieldsValue,
login: current_user && current_user.login,
user_id: current_user && current_user.user_id,
bot_id: parseInt(id),
category: "",
is_receive_ag1: disabled ? 1 : 0,
is_receive_ag2: 0,
logo: logo,
market_time: "",
}).then(res=>{
if(res && res.code === -1){
message.error(res.data);
}else{
message.success("成功上架市场");
props.history.push('/softbot/'+id);
}
})
}
})
}
return <div className="putawayBox">
<div className="putaHead pb10 font-18 mb5">
<a href="#" className="font-16">高级选项&gt;</a><b>上架Bot市场</b>
</div>
{detail && <Form className="putawayForm" onSubmit={submit}>
<Form.Item label="上架Bot市场名称" className="width50 oneItem">
{getFieldDecorator("market_name",{
rules:[{required:true,message:"请输入上架Bot市场名称"}],
initialValue: detail.market_name
})(
<Input.TextArea placeholder="请输入上架Bot市场名称" maxLength={50} className="height36" autoSize={true}/>
)}
</Form.Item>
<Form.Item label="简要介绍" className="oneItem">
{getFieldDecorator("market_desc",{
rules:[{required:true,message:"请输入简要介绍"}],
initialValue: detail.market_desc
})(
<Input.TextArea placeholder="请输入简要介绍的内容" maxLength={200} className="height36" autoSize={true}/>
)}
</Form.Item>
<div className="selectBox">
<Form.Item label="主要功能" className="selectOne oneItem">
{getFieldDecorator("first_func",{
rules:[{required:true,message:"请选择主要功能"}],
initialValue: detail.first_func
})(
<Select placeholder="请选择" onChange={(value)=>{setResourceSecond(resource.filter(item => item !== value))}}>
{resourceFirst.map(item=>{return <Option value={item} key={item}>{item}</Option>})}
</Select>
)}
</Form.Item>
<Form.Item label="次要功能" className="selectOne oneItem">
{getFieldDecorator("second_func",{
initialValue: detail.second_func
})(
<Select placeholder="请选择" onChange={(value)=>{setResourceFirst(resource.filter(item => item !== value))}}>
{resourceSecond.map(item=>{return <Option value={item} key={item}>{item}</Option>})}
</Select>
)}
</Form.Item>
</div>
<Form.Item label="详细介绍" className="oneItem introduce">
{getFieldDecorator("market_intro",{
rules:[{required:true,message:"请输入详细介绍"}],
initialValue: content
})(
<MdEditor
placeholder={"请输入详细介绍"}
height={500}
mdID={"order-new-description"}
initValue={content}
onChange={onContentChange}
className="mt20"
imageExpand={false}
></MdEditor>
)}
</Form.Item>
<Form.Item label="订阅接收Webhook地址" className="width50 oneItem">
{getFieldDecorator("webhook",{
rules:[{required:true,message:"请输入Webhook地址"}],
initialValue: detail.webhook
})(
<Input.TextArea placeholder="请输入Webhook地址" maxLength={200} className="height36" autoSize={true}/>
)}
</Form.Item>
{!isEdit && <Form.Item>
<Checkbox onChange={onChange}>我已阅读并接受<a className="login-form-forgot" href="https://forum.trustie.net/forums/5029/detail" target="_blank">GitLink服务协议条款</a></Checkbox>
</Form.Item>}
<Form.Item><Button style={{width:"129px", height: '36px'}} type="primary" htmlType="submit" disabled={!disabled} className="putawayBut mt20">{isEdit ? '保存修改' : '申请上架'}</Button></Form.Item>
</Form>}
</div>
}
export default Form.create()(forwardRef(Putaway));

View File

@ -0,0 +1,96 @@
import React, {useEffect, useState} from "react";
import { Route, Switch } from "react-router-dom";
import Loadable from "react-loadable";
import Loading from "../../../../../Loading";
import { Link } from "react-router-dom";
import { TPMIndexHOC } from "../../../../../modules/tpm/TPMIndexHOC";
import { Box } from "../../../../Component/layout";
import './index.scss';
import { Button, message } from "antd";
import { getRegisterBot } from "../../../../../softbot/api";
const BasicInformation = Loadable({
loader: () => import("./compenent/basicInformation"),
loading: Loading,
});
const Jurisdiction = Loadable({
loader: () => import("./compenent/jurisdiction"),
loading: Loading,
});
const AdvancedInformation = Loadable({
loader: () => import("./compenent/advancedInformation"),
loading: Loading,
});
const Putaway = Loadable({
loader: () => import("./compenent/putaway"),
loading: Loading,
});
// bot
function ConfigurateBot(props){
const {match:{params:{id}}, current_user, location:{pathname}, history} = props;
const [reload, setReload] = useState(undefined);
const [botDetail, setBotDetail] = useState(undefined);
useEffect(()=>{
getRegisterBot({
user_id: current_user && current_user.user_id,
bot_id: id
}).then(res=>{
if(res && res.code === 200){
const {bot_name} = res.data.bot_output_vo;
document.title = `${bot_name}-Bot开发者配置`
setBotDetail(res.data.bot_output_vo);
}else if(res && res.code === 500){
message.error("您没有权限访问该bot");
history.push('/settings/exploitBot/configuration');
}
})
}, [reload])
return <div className="configurateBotBox">
<div className="configurateBotHead mb30">
<span className="font-18 configurateBotTitle">Bot开发者配置</span>
<Button style={{width: '53px', padding: '0'}} className="grayBorBut"><Link to={`/settings/exploitBot/configuration`}>返回</Link></Button>
</div>
<Box>
<div className="leftBox mr30">
<Link to={`/settings/exploitBot/configuration/${id}/basic`} className={pathname.lastIndexOf("/jurisdiction") === -1 && pathname.lastIndexOf("/advanced") === -1 ?"active itemBox":"itemBox"}>基本信息</Link>
<Link to={`/settings/exploitBot/configuration/${id}/jurisdiction`} className={pathname.lastIndexOf("/jurisdiction") > -1 ?"active itemBox":"itemBox"}>权限&订阅</Link>
<Link to={`/settings/exploitBot/configuration/${id}/advanced`} className={pathname.lastIndexOf("/advanced") > -1 ?"active itemBox":"itemBox"}>高级选项</Link>
</div>
<Switch>
{/* bot上架市场 */}
<Route
path="/settings/exploitBot/configuration/:id/advanced/putaway"
render={(p) => (
<Putaway {...props} {...p} botDetail={botDetail} setReload={setReload}/>
)}
></Route>
<Route
path="/settings/exploitBot/configuration/:id/advanced/putaway/edit"
render={(p) => (
<Putaway {...props} {...p} botDetail={botDetail} setReload={setReload}/>
)}
></Route>
<Route
path="/settings/exploitBot/configuration/:id/basic"
render={(p)=>(<BasicInformation {...props} {...p} botDetail={botDetail} setReload={setReload}/>)}
></Route>
<Route
path="/settings/exploitBot/configuration/:id/jurisdiction"
render={(p)=>(<Jurisdiction {...props} {...p} botDetail={botDetail} setReload={setReload}/>)}
></Route>
<Route
path="/settings/exploitBot/configuration/:id/advanced"
render={(p)=>(<AdvancedInformation {...props} {...p} botDetail={botDetail} setReload={setReload}/>)}
></Route>
<Route
path="/settings/exploitBot/configuration/:id"
render={(p)=>(<BasicInformation {...props} {...p} botDetail={botDetail} setReload={setReload}/>)}
></Route>
</Switch>
</Box>
</div>
}
export default TPMIndexHOC(ConfigurateBot);

View File

@ -0,0 +1,30 @@
.configurateBotBox{
width: 1200px;
margin: 0 auto;
color:#202d40;
}
.configurateBotHead, .disFlex{
display: flex;
justify-content: space-between;
align-items: center;
}
.configurateBotHead{
padding: 25px 0 15px;
border-bottom: 1px solid #e0e6f5;
.configurateBotTitle{font-weight: bold;}
}
.leftBox{
width: 150px;
min-height: 317px;
background-color: #f8faff;
border-radius: 4px;
.itemBox{
display: block;
color:#202d40;
padding: 4px 0 4px 15px;
&.active{
background-color:rgba(70, 106, 255, 0.06);
color: $primary-color;
}
}
}

View File

@ -0,0 +1,128 @@
import React, { useState, useEffect } from "react";
import { Button, Icon, message, Modal } from "antd";
import './index.scss';
import { Link } from "react-router-dom";
import { getImageUrl } from 'educoder';
import { getMyBot, getTransferToBot, receiveTransferBot, refuseTransferBot } from "../../../../softbot/api";
import nullBot from '../../../../softbot/image/notBot1.png';
import moment from "moment";
function ExploitBot(props){
const {current_user} = props;
const [makeOverList, setMakeOverList] = useState([]);
const [botList, setBotList] = useState(undefined);
const [visible, setVisible] = useState(false);
const [refuseBotDetail, setRefuseBotDetail] = useState(undefined);
const [reload, setReload] = useState(undefined);
useEffect(()=>{
window.scrollTo(0,0);
document.title='我的Bot';
}, [])
useEffect(()=>{
getMyBot({
user_id: current_user && current_user.user_id,
page_no: 1,
page_size: 1000
}).then(res=>{
if(res && res.code === 200){
setBotList(res.data.list);
}
})
getTransferToBot({
state: "2",
user_id: current_user && current_user.user_id,
login: current_user && current_user.login
}).then(res=>{
if(res && res.code === 200){
setMakeOverList(res.data.bot_list);
}
})
}, [reload])
// bot
function receiveBot(info){
receiveTransferBot({
bot_id: info.bot_id,
transfer_from_id: info.transfer_id,
transfer_to_login: current_user && current_user.login,
transfer_to_id: current_user && current_user.user_id
}).then(res=>{
if(res && res.code === 200){
message.success('操作成功');
setReload(Math.random());
}
})
}
// bot
function refuseBot(){
refuseTransferBot({
bot_id: refuseBotDetail.bot_id,
transfer_from_bot: refuseBotDetail.transfer_id,
transfer_to_bot: current_user && current_user.user_id
}).then(res=>{
if(res && res.code === 200){
setVisible(false);
setRefuseBotDetail(undefined);
message.success('操作成功');
setReload(Math.random());
}
})
}
return <div>
<div className="exploitHead font-18">
<span>我的Bot</span>
<Button type="primary" style={{width: '100px', height: '36px'}}><Link to={`/settings/exploitBot/new`}>Bot注册</Link></Button>
</div>
{/* bot转让接收按钮显示区域 */}
{makeOverList.length > 0 && <div className="makeOverBox">
{makeOverList.map((item, index)=>{return <div className="makeOverItem" key={index}>
<div>
<p className="botName font-15">{item.bot_name}</p>
<p className="userName"><a href={`/${item.fromlogin}`} className="fromUserNameBot">{item.fromname}</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{moment(item.transfer_date).format('YYYY-MM-DD HH:mm')}</p>
</div>
<div>
<Button className="themeCorBorBut" style={{width: '68px', height: '36px'}} onClick={()=>{receiveBot(item)}}>接收</Button>
<Button className="dangerBorBut ml20" style={{width: '68px', height: '36px'}} onClick={()=>{setRefuseBotDetail(item); setVisible(true)}}>拒绝</Button>
</div>
</div>})}
</div>}
{/* bot列表显示区域 */}
{botList && (botList.length > 0 ? <div className="softBotListBox">
{botList.map((item, index)=>{return <div className="softBotItem" key={index}>
<div className="botOneLine botNameWarpBox">
<img src={getImageUrl(item.logo)} alt="" className="imgBox mr20" object-fit="fill"/>
<Link className="font-15 botOneLine botNameBox" to={`/settings/exploitBot/configuration/${item.bot_id}`}>{item.bot_name}</Link>
<span className={`font-12 ml10 statusBot ${item.is_public ? item.is_market ? 'market' : 'public' : 'private'}`}>{item.is_public ? item.is_market ? '上架' : '公开' : '私有'}</span>
</div>
<Button className="themeCorBorBut ml30" style={{width: '68px', height: '36px'}}><Link to={`/settings/exploitBot/configuration/${item.bot_id}`}>编辑</Link></Button>
</div>})}
</div> : <div className="nullBotsBox mt80">
<img src={nullBot} alt="" width={62}/>
<p className="font-18 showBigTip">您当前暂未注册任何Bot</p>
<div className="showTip font-14">Bot可以帮助您自动化处理一些繁杂的项目任务欢迎您<Link to="/settings/exploitBot/new"> 注册一个Bot </Link>基于GitLink API进行开发<br/>此外您还可以在<Link to="/softbot"> Bot市场 </Link>中共享或安装他人的Bot</div>
</div>)}
<Modal
className="f8HeadModal"
width="550px"
title={`拒绝${refuseBotDetail && refuseBotDetail.bot_name}Bot`}
visible={visible}
maskClosable={false}
footer={[
<Button onClick={()=>{setVisible(false)}} className="mr30 grayBorBut whiteBackBut" style={{width:"104px", height: '36px'}}>取消</Button>,
<Button className="redFontBut grayBorBut whiteBackBut" style={{width:"104px", height: '36px'}} onClick={refuseBot}>确认</Button>
]}
onCancel={()=>{setVisible(false)}}
>
<div className="font-16 titleTip mt20"><span className="circleRed font-18">!</span>您确定要拒绝此Bot吗</div>
<div className="deleteTip">拒绝后开发Bot权限将返回至转让人</div>
</Modal>
</div>
}
export default ExploitBot;

View File

@ -0,0 +1,114 @@
.makeOverResultItem, .makeOverItem, .softBotItem{
display: flex;
justify-content: space-between;
align-items: center;
}
.exploitHead{
height: 65px;
border-bottom: 1px solid #e0e6f5;
display: flex;
justify-content: space-between;
align-items: center;
color:#151d40;
}
.makeOverResultItem{
background-color:rgba(255, 0, 0, 0.04);
border:1px solid #f60011;
border-radius:4px;
margin-top: 16px;
padding: 0 15px;
height: 32px;
color:rgba(0, 0, 0, 0.65);
.circle{
display: inline-block;
width:14px;
height:14px;
text-align: center;
line-height: 14px;
border-radius: 50%;
background-color:#cc0209;
color: white;
font-size: 12px;
margin-right: 5px;
}
}
.makeOverBox{
background-color:rgba(59, 105, 255, 0.03);
border:1px solid #ebf1ff;
margin-top: 16px;
padding: 12px 30px 0px 12px;
.makeOverItem{
border-bottom: 1px dashed #e0e6f5;
padding: 10px 0;
color:#99a2af;
&:first-child{
margin-top: -12px;
}
.fromUserNameBot{
color:#99a2af;
&:hover{
color: $primary-color;
}
}
}
.botName{
color:#202d40;
}
}
.softBotListBox{
margin-top: 16px;
color:#202d40;
.softBotItem{
border-bottom: 1px dashed #e0e6f5;
padding: 20px 0 15px;
}
.imgBox{
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
}
}
.nullBotsBox{
border-radius: 4px 4px 0px 0px;
text-align: center;
padding: 25px 158px 65px;
.showBigTip{
color:#333333;
margin-top: 16px;
margin-bottom: 22px !important;
}
.showTip{color: #666;}
}
.botOneLine{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.botNameWarpBox{
display: flex;
align-items: center;
}
}
.botNameBox{
max-width: 80%;
display: inline-block;
}
.statusBot{
border: 1px solid $primary-color;
border-radius: 5px;
color: $primary-color;
padding: 4px 6px 4px;
background-color: rgba(70, 106, 255, 0.09);
line-height: 12px;
&.public{
background-color: rgba(58, 213, 115, 0.08);
border-color: #25c589;
color: #25c589;
}
&.private{
background-color: rgba(230, 126, 34, 0.08);
border-color: rgba(230, 126, 34, 1);
color: rgba(230, 126, 34, 1);
}
}

View File

@ -0,0 +1,179 @@
import React, { forwardRef, useState, useEffect } from "react";
import { Button, Checkbox, Form, Input, message, Radio, Select } from "antd";
import { Link } from "react-router-dom";
import MDEditor from '../../../../../modules/tpm/challengesnew/tpm-md-editor';
import './index.scss';
import { registerBot } from "../../../../../softbot/api";
const {Option} = Select;
function Disposition(props){
const {form, current_user} = props;
const { getFieldDecorator, validateFields , setFieldsValue, getFieldsValue } = form;
const resource = [{value: 2, name: '无权限'}, {value: 0, name: '只读'}, {value: 1, name: '读写'}];
const [ content , setContent ] = useState(undefined);
const [ codeSelectValue , setCodeSelectValue ] = useState(resource[0].value);
const [ prSelectValue , setPrSelectValue ] = useState(resource[0].value);
useEffect(()=>{
document.title='注册新的Bot';
}, [])
function onContentChange(value){
setContent(value);
};
function codeControl(e){
setCodeSelectValue(e);
return e;
}
function prControl(e){
setPrSelectValue(e);
return e;
}
// webhooks
function checkAddr(rule, value, callback) {
let reg = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/;
if(!value){
callback();
}
if (!reg.test(value)){
callback("请输入有效的URL");
}
callback();
}
// 访
function jurisDictionPrValidator(rule, value, callback){
const {juris_diction_code} = getFieldsValue();
if(juris_diction_code === 2 && value === 2){
callback('请至少选择一个权限');
return;
}
callback();
}
// bot
function submit(e){
e.preventDefault();
validateFields((err, fieldsValue)=>{
if(err){
return;
}
const {bot_name, bot_des, webhook, is_public, juris_diction_code, juris_diction_pr, event_code, event_pr} = fieldsValue;
if(juris_diction_code === 2 && juris_diction_pr === 2){
form.setFields({juris_diction_pr: {value:juris_diction_pr,errors:[new Error('请至少选择一个权限')]}});
return;
}
const param = {
bot_name, bot_des, webhook, is_public,
login: current_user && current_user.login,
user_id: current_user && current_user.user_id,
logo: '/api/attachments/347246',
limit_and_events: {
event_code: event_code ? event_code.toString() : "",
event_pr: event_pr ? event_pr.toString() : "",
juris_diction_code,
juris_diction_pr
},
}
registerBot(param).then(res=>{
if(res && res.code === 200){
message.success('注册成功');
props.history.push('/settings/exploitBot/configuration/'+res.data.bot_id);
}else{
message.success('注册失败: '+res.data);
}
})
})
}
return <div className="createExploitBox">
<div className="ceHead pb5 font-16 mb10">
<Link to={`/settings/exploitBot`} className="blue-Purple">我的Bot</Link><span className="ceTitle font-18"><i className="iconfont icon-youjiantou ml5 mr5 font-12"></i>注册新的Bot</span>
</div>
<Form className="createExploitForm" onSubmit={submit}>
<Form.Item label="Bot名称" className="width50 botItem">
{getFieldDecorator("bot_name",{
rules:[{required:true,message:"请输入Bot名称"}]
})(
<Input.TextArea placeholder="请输入Bot名称" maxLength={50} className="height36" autoSize={true}/>
)}
</Form.Item>
<Form.Item label="详细介绍" className="botItem introduce">
{getFieldDecorator("bot_des",{
rules:[]
})(
<MDEditor
placeholder={"请输入详细介绍"}
height={500}
mdID={"order-new-description"}
initValue={content}
onChange={onContentChange}
className="mt20"
></MDEditor>
)}
</Form.Item>
<Form.Item label="Webhook地址" className="width50 botItem">
{getFieldDecorator("webhook",{
rules:[{required:true,message:"请输入Webhook地址"},{validator:checkAddr}]
})(
<Input.TextArea placeholder="请输入Webhook地址" maxLength={200} className="height36" autoSize={true}/>
)}
</Form.Item>
<div className="resourceTitle font-16 pb15 mb10">仓库访问权限</div>
<Form.Item label="代码库权限" className="resourceBox botItem dashedBorder">
<div className="color-99">代码库git推送分支的创建与删除</div>
{getFieldDecorator("juris_diction_code",{initialValue: resource[0].value, getValueFromEvent: codeControl})(
<Select>
{resource.map(item=>{return <Option value={item.value} key={item.value}>{item.name}</Option>})}
</Select>
)}
</Form.Item>
<div className="dashedBor"></div>
<Form.Item label="合并请求(PR)权限" className="resourceBox botItem">
<div className="color-99">合并请求的打开关闭编辑分配</div>
{getFieldDecorator("juris_diction_pr",{rules:[{validator: jurisDictionPrValidator}],initialValue: resource[0].value, getValueFromEvent: prControl})(
<Select>
{resource.map(item=>{return <Option value={item.value} key={item.value}>{item.name}</Option>})}
</Select>
)}
</Form.Item>
{(codeSelectValue !== resource[0].value || prSelectValue !== resource[0].value) && <div className="resourceTitle font-16 pb15 mb10">订阅事件</div>}
{codeSelectValue !== resource[0].value && <Form.Item label="代码库事件" className="botItem width50 checkBox three dashedBorder">
{getFieldDecorator("event_code",{
rules:[{required:codeSelectValue !== resource[0].value,message:"请选择订阅事件!"}]
})(
<Checkbox.Group>
<Checkbox value={0}>推送<br/><span className="color-99">git推送到存储库</span></Checkbox>
<Checkbox value={1}>创建<br/><span className="color-99">创建分支或标签</span></Checkbox>
<Checkbox value={2}>删除<br/><span className="color-99">删除分支或标签</span></Checkbox>
</Checkbox.Group>
)}
</Form.Item>}
{(codeSelectValue !== resource[0].value && prSelectValue !== resource[0].value) && <div className="dashedBor"></div>}
{prSelectValue !== resource[0].value && <Form.Item label="合并请求事件" className="botItem width50 checkBox">
{getFieldDecorator("event_pr",{
rules:[{required:prSelectValue !== resource[0].value,message:"请选择订阅事件!"}]
})(
<Checkbox.Group>
<Checkbox value={3}>合并请求<br/><span className="color-99">合并请求被打开被关闭被重新打开或被编辑</span></Checkbox>
<Checkbox value={4}>合并请求分配<br/><span className="color-99">合并请求被分配或取消分配</span></Checkbox>
</Checkbox.Group>
)}
</Form.Item>}
<Form.Item label="公/私有设置" className="botItem resourceTitleItem">
{getFieldDecorator("is_public",{initialValue: 0})(
<Radio.Group>
<Radio value={1}>公开</Radio>
<Radio value={0}>私有</Radio>
</Radio.Group>
)}
</Form.Item>
<Form.Item><Button style={{width:"129px", height: '36px'}} type="primary" htmlType="submit">注册Bot</Button></Form.Item>
</Form>
</div>
}
export default Form.create()(forwardRef(Disposition));

View File

@ -0,0 +1,86 @@
.ceHead{
border-bottom: 1px solid #e0e6f5;
.ceTitle{
font-weight:700;
color:#151d40;
}
}
#resource1, #resource2{
position: relative;
top: -15px;
width: 88px;
}
.resourceBox .ant-form-item-children{
display: flex;
justify-content: space-between;
position: relative;
}
.resourceBox .ant-select{
width: auto;
}
.resourceBox .has-error .ant-select{
color: #f5222d;
}
.width50{
width: 60%;
&.checkBox{
width: 68%;
}
.height36{
height: 36px;
}
}
.botItem{
.ant-form-item-label{
line-height: 30px;
}
label{
color:#202d40;
font-size:15px;
&.ant-checkbox-wrapper{
line-height: 30px;
margin-top: 10px;
}
&::after{
display: none;
}
}
&.introduce .ant-form-item-control-wrapper{
margin-top: -15px;
margin-bottom: 0;
}
&.checkBox .ant-checkbox-group{
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.color-99{padding-left: 24px;}
}
&.three .ant-checkbox-wrapper{
width: 34%;
margin-left: 0px;
padding-left: 12px;
&:first-child, &:last-child{
padding-left: 0px;
}
}
}
.dashedBor{
border-bottom: 1px dashed #e0e6f5;
margin-bottom: 12px;
}
.resourceBox + .dashedBor{
margin-top: -15px;
}
.resourceTitle, .resourceTitleItem .ant-form-item-label{
font-weight:700;
color:#151d40;
border-bottom: 1px solid #e0e6f5;
width: 100%;
text-align: left;
label{font-size: 16px;}
}
.resourceTitleItem .ant-form-item-label{padding-bottom: 10px;}
.resourceTitleItem .ant-checkbox-wrapper{margin-right: 40px;}
.color-99{
color:#99a2af;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,10 +1,10 @@
import React , { forwardRef, useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Button, Form , Input } from 'antd';
import { Form , Input } from 'antd';
import Axios from 'axios';
const { TextArea } = Input;
function New({ form , showNotification , history }) {
function New({ form , showNotification , history },ref) {
const { getFieldDecorator, validateFields , setFieldsValue } = form;
const [ msg , setMsg ] = useState(undefined);

View File

@ -44,6 +44,10 @@ const ManageNew = Loadable({
loader: () => import("./ManageWebNew"),
loading: Loading,
});
const SoftBot = Loadable({
loader: () => import("./softBot"),
loading: Loading,
});
class Index extends Component {
render() {
const { projectsId , owner } = this.props.match.params;
@ -97,7 +101,7 @@ class Index extends Component {
</Link>
</p>
</li>
<li
{/* <li
className={pathname.indexOf("settings/labels") > -1 ? "active" : ""}
>
<p>
@ -106,6 +110,16 @@ class Index extends Component {
项目标记
</Link>
</p>
</li> */}
<li
className={pathname.indexOf("settings/softbot") > -1 ? "active" : ""}
>
<p>
<Link to={`/${owner}/${projectsId}/settings/softbot`} className="w-100">
<i className="iconfont icon-a-jiqirenzhineng-02 font-18 mr10 color-grey-6"></i>
已安装Bot
</Link>
</p>
</li>
{/* <li
@ -151,13 +165,13 @@ class Index extends Component {
<Collaborator {...this.props} {...props} {...this.state} />
)}
></Route>
{/* 修改仓库信息 */}
<Route
{/* 修改仓库标记 */}
{/* <Route
path="/:owner/:projectsId/settings/labels"
render={(props) => (
<Tags {...this.props} {...props} {...this.state} />
)}
></Route>
></Route> */}
<Route
path="/:owner/:projectsId/settings/branches/:branch"
render={(props) => (
@ -182,6 +196,12 @@ class Index extends Component {
<Manage {...this.props} {...props} {...this.state} />
)}
></Route>
<Route
path="/:owner/:projectsId/settings/softbot"
render={(props) => (
<SoftBot {...this.props} {...props} {...this.state} />
)}
></Route>
{/* 修改仓库信息 */}
<Route
path="/:owner/:projectsId/settings"

View File

@ -6,6 +6,7 @@ import DivertModal from '../Divert/DivertModal';
import { Link } from 'react-router-dom';
import axios from "axios";
import "./setting.scss";
import { unInstallMarketBot } from "../../softbot/api";
const { TextArea } = Input;
const { Option } = Select;
@ -194,17 +195,25 @@ class Setting extends Component {
deleteProject = () => {
const { projectsId , owner } = this.props.match.params;
const { projectName } = this.state;
const {current_user:{login, user_id}, projectDetail:{project_id}} = this.props;
this.props.confirm({
content: <span style={{display:"block",textAlign:"left"}}>该操作无法撤销且将会一并删除相关的疑修合并请求工作流里程碑动态等数据<br/>是否确认删除 <span style={{fontWeight:"bold"}}>{owner}/{projectName}({projectsId})</span></span>,
onOk: () => {
const url = `/${owner}/${projectsId}.json`;
axios.delete(url).then((result) => {
this.props.showNotification("仓库删除成功!");
this.props.history.push(`/${owner}`);
// 卸载bot判断bot是否安装后端做了判断
unInstallMarketBot({
login,
user_id,
store_id: project_id,
store_identifier: projectsId
})
.catch((error) => {
console.log(error);
});
this.props.showNotification("仓库删除成功!");
this.props.history.push(`/${owner}`);
})
.catch((error) => {
console.log(error);
});
},
});
};

View File

@ -283,4 +283,69 @@
border-bottom-left-radius: 0;
}
}
}
.softBotClass{
.softBoxHeadTitle{
color:#151d40;
padding: 8px 0;
border-bottom: 1px solid #e0e6f5;
}
.botListBox{
margin-top: 16px;
color:#202d40;
.statusBox{
background-color:rgba(255, 81, 81, 0.08);
border:1px solid#f23737;
color:#f92d2d;
border-radius:4px;
padding: 4px 6px;
line-height: normal;
&.active{
background-color:rgba(58, 213, 115, 0.08);
border-color:#25c589;
color:#25c589;
}
}
.disBotItem{
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px dashed #e0e6f5;
padding: 20px 0 15px;
}
.flexCenter{
display: inline-flex;
align-items: center;
}
.botName{
display: inline-block;
max-width: 600px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.imgBox{
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
}
}
.nullBotBox{
background-color: #fafcff;
border-radius: 4px 4px 0px 0px;
text-align: center;
padding: 25px 158px 65px;
.showBigTip{
color:#333333;
margin-top: 16px;
margin-bottom: 22px !important;
}
.showTip{color: #666;}
.borTip{
width: 82%;
margin: 20px auto 18px;
border-top: 1px solid rgba(90, 117, 193, 0.23)
}
}
}

View File

@ -0,0 +1,46 @@
import React, {useState} from "react";
import { Link } from "react-router-dom";
import { Button } from "antd";
import nullBot from '../../softbot/image/notBot.png';
import './setting.scss';
import { useEffect } from "react";
import { getStoreAllInstallBots } from "../../softbot/api";
import { getImageUrl } from 'educoder';
function SoftBot(props){
const {projectDetail} = props;
const [botList, setBotList] = useState(undefined);
useEffect(()=>{
// bot
projectDetail && getStoreAllInstallBots({
store_id: projectDetail.project_id
}).then(res=>{
if(res && res.code === 200){
setBotList(res.data.install_bot_infos);
}
})
}, [projectDetail])
return <div className="softBotClass">
<div className="softBoxHeadTitle font-18">Bot安装</div>
{/* bot列表显示区域 */}
{botList && (botList.length > 0 ? <div className="botListBox">
{botList.map((item, index)=>{return <div className="disBotItem" key={index}>
<div className="flexCenter">
<img src={getImageUrl(item.logo)} alt="" className="imgBox mr20"/>
<span className="font-15 botName">{item.bot_name}</span>
<span className={`statusBox font-12 ml10 ${item.state === 0 ? '' : 'active'}`}>{item.state === 0 ? '挂起' : '启用'}</span>
</div>
<Button className="themeCorBorBut" style={{width: '68px', height: '36px'}}><Link to={`/settings/dispositionBot/${item.bot_id}`}>配置</Link></Button>
</div>})}
</div> : <div className="nullBotBox mt20">
<img src={nullBot} alt="" width={62}/>
<p className="font-18 showBigTip">您当前暂未安装任何Bot</p>
<div className="showTip font-14">Bot可以帮助项目贡献者处理繁杂的项目任务比如关闭疑修合并请求等以节约时间精力提升开发效率快去市场中挑选一个适合您项目的Bot吧!</div>
<div className="borTip"></div>
<Button type="primary" style={{width: '112px', height: '36px', padding: 0}}><Link to={'/softbot'}>去Bot市场逛逛</Link></Button>
</div>)}
</div>
}
export default SoftBot

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { Upload , Icon , message } from "antd";
import { getUploadActionUrl } from 'educoder';
function UploadImage({ getImage , url }){
function UploadImage({ getImage , url, getImageId }){
const [ imageUrl , setImageUrl ] = useState(undefined);
useEffect(()=>{
if(url){
@ -33,6 +33,8 @@ function UploadImage({ getImage , url }){
//
function handleChange(info){
if(info && info.file && info.file.status === "done"){
console.log('info', info);
getImageId && getImageId(info.file.response.id);
getBase64(info.file.originFileObj, imageUrl =>
setImageUrl(imageUrl)
);

View File

@ -12,6 +12,19 @@ class Index extends Component {
}
}
componentDidMount=()=>{
const {defaultFileList} = this.props;
if(defaultFileList){
const files = [];
defaultFileList.map(i=>{
files.push({
uid: i.id,
name: i.title,
status: 'done',
url: i.url
})
})
this.setState({fileList: files})
}
this.checkInitFile();
}
componentDidUpdate=(prevProps)=>{
@ -70,10 +83,12 @@ class Index extends Component {
fileIdList = (fileList) => {
let array = [];
fileList && fileList.length > 0 && fileList.map((item) => {
return array.push(item.response && (item.response.id || (item.response.data && item.response.data.id)));
})
array && this.props.load && this.props.load(array);
if(fileList && fileList.length > 0){
fileList.map((item) => {
return item.response && item.status==="done" && array.push((item.response.id || (item.response.data && item.response.data.id)));
})
array && array.length>0 && this.props.load(array);
}
}
checkFile=(str)=>{

View File

@ -76,6 +76,7 @@ class Attachment extends Component {
};
deleteAttachment = (id) => {
const { deleteLoad } = this.props;
const url = `/attachments/${id}.json`;
axios
.delete(url, {})
@ -86,6 +87,7 @@ class Attachment extends Component {
Deleted: this.state.Deleted.concat(id),
});
this.props.showNotification("附件删除成功");
deleteLoad && deleteLoad(id);
} else {
this.props.showNotification(response.data.message);
}

View File

@ -13,6 +13,9 @@
.menuPanels{
width: 295px;
.ant-popover-arrow{
bottom: 10.2px!important;
}
&.maxWidth{
width: 400px;
}
@ -60,11 +63,13 @@
flex-wrap: wrap;
padding-bottom: 2px;
a{
margin: 0px 10px 10px 0px;
margin: 0px 2px 0px 0px;
height: 22px;
line-height: 20px;
img{
border-radius: 50%;
width: 40px;
height: 40px;
width: 30px;
height: 30px;
}
&:nth-child(4n){
margin-right: 0px;

View File

@ -173,21 +173,21 @@ class claims extends React.Component {
const renderClaim = () => {
return (
<div className="claimNote_input" style={{ width: "400" }}>
<div className="mb5"> 声明留言</div>
<div className="mb10"> 声明留言</div>
<TextArea
rows={6}
rows={4}
type="text"
placeholder="例如可以留言你的解决思路......"
onChange={this.changeClaimNote}
value={claimNoteBody}
className={claimNoteBodyError ? 'error' : ''}
/>
<div className="mt10" style={{color: 'red'}}>{claimNoteBodyError}</div>
<div style={{ display: "flex", justifyContent: "center" }} className="mt10">
<Button onClick={() => this.cancelClaim()}> </Button>
<div style={{color: 'red',height:"20px",lineHeight:"20px"}}>{claimNoteBodyError}</div>
<div style={{ display: "flex", justifyContent: "center" }}>
<Button style={{width:"67px" }} onClick={() => this.cancelClaim()}> </Button>
<Button
onClick={() => this.yesClaim()}
style={{ marginLeft: "20px" }}
style={{ marginLeft: "20px",width:"67px" }}
type={"primary"}
>
@ -211,26 +211,19 @@ class claims extends React.Component {
className="radius"
src={getImageUrl(`/${item && item.user_picture}`)}
alt=""
width="30"
height="30"
width="46"
height="46"
/>
</Link>
<Link
to={`/${item && item.user_login}`}
className="show-user-link color-black ml10 fwb"
className="show-user-link color-black ml10"
>
<div>
{current_user.login == item.user_login ? (
<div></div>
) : (
<div>{item && item.user_name}</div>
)}
<div className="font-16" style={{fontWeight:500,color:"#1e1d1d"}}>{current_user.login === item.user_login ? "你": item.user_name} </div>
<span className="color-grey-8 font-12"> 声明于 {item.created_at}</span>
</div>
</Link>
<div style={{ marginLeft: "10px" }}>
{" "}
<span className="color-grey-8"> 声明于 {item.created_at}</span>
</div>
</AlignCenter>
</FlexAJ>
<div className="claimNote">
@ -245,6 +238,7 @@ class claims extends React.Component {
justifyContent: "center",
paddingBottom: "10px",
paddingTop: "10px",
width:"83px"
}}
>
<Button
@ -293,8 +287,6 @@ class claims extends React.Component {
</Button>}
</Popover>
<div className="ant-divider ant-divider-horizontal"> </div>
{claimerData && claimerData.length > 0 ? (
<div>
<div className="mt15">

View File

@ -4,6 +4,9 @@ ul,ol,dl{
.markdown-body{
overflow: inherit;
}
.markdown-body .editormd-tex .katex-display>.katex{
white-space: pre-wrap;
}
.markdown-body a{
color: #466AFF;
}

View File

@ -68,7 +68,6 @@ export default function javaFetch(actionUrl){
}
},
error => {
console.log(error);
let res = error.response||{};
if (res.status === 400) {
message.error(res.data.message || '操作失败');

View File

@ -6,6 +6,7 @@ import 'cropperjs/dist/cropper.css';
import axios from 'axios';
function Index({onCancel,avatarImg,login}){
console.log('avatarImg,login', avatarImg,login);
const [ avatarPhoto , setAvatarPhoto ] = useState(avatarImg);

View File

@ -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>

View File

@ -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});
}
@ -251,6 +258,10 @@ class Infos extends Component {
axios.put(url,params).then(result=>{
if(result && result.data){
this.fetchUser();
if(flag){
const { username } = this.props.match.params;
this.props.history.push(`/${username}`);
}
}
})
}
@ -330,7 +341,10 @@ class Infos extends Component {
<div className="usersDesc">
<p>想全方位展示自己试试点击下方按钮定制化您的专属个人简介吧</p>
{
user.show_super_description ? <a className="emptyBtn" onClick={()=>this.aboutDescFunc(false)}>隐藏个人简介</a> : <a className="fullBtn" onClick={()=>this.aboutDescFunc(true)}></a>
user.show_super_description ?
<a className="emptyBtn" onClick={()=>this.aboutDescFunc(false)}>隐藏个人简介</a>
:
<a className="fullBtn" onClick={()=>this.aboutDescFunc(true)}>展示个人简介</a>
}
</div>
}
@ -383,17 +397,21 @@ class Infos extends Component {
</Menu.Item>
:""
}
{
{/* {
current_user && current_user.login && current_user.login === username ?
<Menu.Item key="4"><Link to={`/${user && user.login}/devops/CIService`}><i className="iconfont icon-gongzuoliu1"></i>DevOps</Link></Menu.Item>
:""
}
} */}
<Menu.Item key="5">
<Link to={`/${user && user.login}/organizes`}>
<i className="iconfont icon-zuzhi"></i>
{ 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 && (
@ -416,7 +434,7 @@ class Infos extends Component {
return <FanUser {...this.props} {...this.state} userType="fan_users" fetchUser={this.fetchUser}/>;
}}
></Route>
<Route
{/* <Route
path="/:username/devops/CDService"
render={() => {
return <InfosDevOpsCD {...this.props} {...this.state} />;
@ -427,7 +445,7 @@ class Infos extends Component {
render={() => {
return <InfosDevOps {...this.props} {...this.state} />;
}}
></Route>
></Route> */}
<Route
path="/:username/projects" // /:project_type
render={() => {
@ -440,6 +458,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={() => {

View File

@ -20,7 +20,7 @@ export default Form.create()(
function checkPsd(rule, value, callback){
const map = passMap;
if(value){
current_user && Axios.post(`/v1/${current_user.login}/check_password.json`,{
current_user && value.length > 7 && Axios.post(`/v1/${current_user.login}/check_password.json`,{
password: value
}).then(res=>{
if(res && !res.data.status){
@ -50,7 +50,7 @@ export default Form.create()(
callback("请先输入登录密码");
}
if(value){
current_user && value && value.length > 4 && Axios.post(`/v1/${current_user.login}/check_phone_verify_code.json`,{
current_user && value && value.length > 5 && Axios.post(`/v1/${current_user.login}/check_phone_verify_code.json`,{
code_type: 4,
phone: phoneValue,
code: value
@ -192,7 +192,7 @@ export default Form.create()(
{required:true,message:"请输入登录密码"},
{validator:(rule, value, callback)=>checkPsd(rule, value, callback)}
],
validateTrigger:"onBlur",
validateTrigger:"onChange",
})(
<Input.Password placeholder="请输入登录密码" autoComplete={"new-password"} style={{width:"400px"}}/>
)}
@ -203,7 +203,7 @@ export default Form.create()(
{required:true,message:"请输入新手机号码"},
{validator:(rule, value, callback)=>checkPhone(rule, value, callback)}
],
validateTrigger:"onBlur",
validateTrigger:"onChange",
})(
<Input placeholder="请输入新手机号码" style={{width:"400px"}} autoComplete={"off"}/>
)}
@ -215,7 +215,7 @@ export default Form.create()(
{required:true,message:"请输入短信验证码"},
{validator:(rule, value, callback)=>checkCode(rule, value, callback)}
],
validateTrigger:"onBlur",
validateTrigger:"onChange",
})(
<Input placeholder="请输入短信验证码" style={{width:"400px"}}/>
)}

View File

@ -0,0 +1,170 @@
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 = 10;
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 {current_user:{user_id, login, open_blockchain}, current_user} = props;
const {user} = props;
const username = props.match.params.username;
useEffect(()=>{
if((username && current_user && (current_user.login !== username)) || !open_blockchain){
props.history.push(`/${username}`);
}
},[current_user,username])
useEffect(()=>{
// token
axios.get(`/users/blockchain/balance.json`,{params: {
user_id,
limit,
page
}}).then(res=>{
if(res && res.data){
const {projects} = res.data;
const newProjects = [];
let deleteProjectCount = 0;
projects.map(item=>{
if(item.id){
newProjects.unshift(item);
}else{
deleteProjectCount++;
}
})
deleteProjectCount && newProjects.push({
'balance': deleteProjectCount
})
setList(newProjects);
setTotal(res.data.total_count);
}
})
}, [reload, page])
useEffect(()=>{
if(user){
//
const {username, login} = user;
document.title = `贡献确权-${username}/${login}`;
}
},[user])
//
function getUser(id, login){
// 使JSON
const userLoginObject = JSON.parse(JSON.stringify(userLogin));
userLoginObject[id] = login;
setUserLogin(userLoginObject);
}
// token
function setToken(id, token){
const tokenCountObject = JSON.parse(JSON.stringify(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{
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=>{
if(res && res.data && res.data.status === 2){
getUser(id, undefined);
setToken(id, undefined);
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 item.id ? <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="搜索转账目标用户" value={userLogin[item.id]}/>
{proId === item.id && error1 && <div className='errorTipTri'>{error1}</div>}
</div>
<div className='tableColumn4'>
<Input value={tokenCount[item.id]} 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 className='flexCenterCont transferAccount deleteRepo'>{item.balance}个项目被删除</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 mb50'>
<Pagination simple current={page} pageSize={limit} total={total} onChange={(page)=>{setPage(page);window.scrollTo(0,0);}}/>
</div>}
</div>
)
}
export default Contribution;

View File

@ -0,0 +1,82 @@
.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;
}
.deleteRepo{
color: slategrey;
}
.tableColumn3, .tableColumn4{
width: 150px;
}
.tableColumn1{
width: 200px;
}
.tableColumn5{
width: 100px;
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;
}

View File

@ -74,8 +74,8 @@ function md_elocalStorage(editor, mdu, id) {
return tid
}
export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, className = '', noStorage = false, imageExpand = true, placeholder = '', width = '100%', height = 400, initValue = '', emoji, watch, showNullButton = false, showResizeBar = false, startInit = true , forMember = true , isCanAtme = false , changeAtWhoLoginList, owner, projectsId }) => {
// isFocus是否聚焦到md编译器
export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, className = '', noStorage = false, imageExpand = true, placeholder = '', width = '100%', height = 400, initValue = '', emoji, watch, showNullButton = false, showResizeBar = false, startInit = true , forMember = true , isCanAtme = false , changeAtWhoLoginList, owner, projectsId, isFocus = true }) => {
const editorEl = useRef();
const resizeBarEl = useRef();
const [editorInstance, setEditorInstance] = useState();
@ -114,7 +114,7 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
if (entry.target.offsetHeight > 0 || entry.target.offsetWidth > 0) {
editorInstance.resize();
editorInstance.cm.refresh();
editorInstance.cm.focus();
isFocus && editorInstance.cm.focus();
}
}
})
@ -161,8 +161,8 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
//获取光标位置
const cssStyle = document.getElementsByClassName("CodeMirror cm-s-default CodeMirror-wrap")[0].firstChild.style;
//设置弹框位置
const newTop = placeholder === "添加评论..." ? 159: placeholder === "请输入合并请求的描述..." ? 172:62;
const newLeft = placeholder === "添加评论..." ? 80: 20;
const newTop = 62
const newLeft = 20;
document.getElementById("at_who_list").style.top = parseInt(cssStyle.getPropertyValue("top").replace("px","")) + newTop +"px";
document.getElementById("at_who_list").style.left = parseInt(cssStyle.getPropertyValue("left").replace("px",""))+newLeft+"px";
}

Some files were not shown because too many files have changed in this diff Show More