资助激励功能合并 #1

Closed
qyzh1996 wants to merge 128 commits from qyzh1996/forgeplus-react:sponsor into newVersion_forge
115 changed files with 6632 additions and 1435 deletions

16
.babelrc Normal file
View File

@ -0,0 +1,16 @@
{
"presets": [
"es2015",
"react",
"stage-2"
],
"plugins": [[
"transform-runtime",
{
"helpers": false,
"polyfill": false,
"regenerator": true,
"moduleName": "babel-runtime"
}
]]
}

1
.gitignore vendored
View File

@ -86,3 +86,4 @@ typings/
.DS_Store
.idea/*
.forgeplus-react.sublime-workspace

BIN
build.tar.gz Normal file

Binary file not shown.

View File

@ -0,0 +1,317 @@
{
"auto_complete":
{
"selected_items":
[
[
"samp",
"samp2_method"
],
[
"modu",
"module_method"
],
[
"cu",
"currentTerm"
],
[
"req",
"requestVote"
],
[
"reque",
"RequestVote"
],
[
"App",
"AppendEntries"
],
[
"hea",
"heartBeatInterval"
],
[
"time",
"timeOut"
],
[
"ele",
"electionTimeout"
],
[
"R",
"Raft"
],
[
"vote",
"votedFor"
],
[
"Ra",
"RaftState"
],
[
"is",
"isleader"
]
]
},
"buffers":
[
{
"file": "src/redux/actions/actionTypes.js",
"settings":
{
"buffer_size": 5688,
"encoding": "UTF-8",
"line_ending": "Unix"
}
}
],
"build_system": "",
"build_system_choices":
[
],
"build_varint": "",
"command_palette":
{
"height": 0.0,
"last_filter": "",
"selected_items":
[
],
"width": 0.0
},
"console":
{
"height": 171.0,
"history":
[
"ls"
]
},
"distraction_free":
{
"menu_visible": true,
"show_minimap": false,
"show_open_files": false,
"show_tabs": false,
"side_bar_visible": false,
"status_bar_visible": false
},
"expanded_folders":
[
"/home/qyzh/new_trustie/forgeplus-react"
],
"file_history":
[
"/home/qyzh/OSS/forgeplus/app/models/user.rb",
"/home/qyzh/OSS/forgeplus/db/structure.sql",
"/home/qyzh/OSS/forgeplus/app/controllers/settings_controller.rb",
"/home/qyzh/OSS/forgeplus/app/controllers/application_controller.rb",
"/home/qyzh/OSS/Trustie/app/controllers/projects_controller.rb",
"/home/qyzh/temp.sh",
"/home/qyzh/OSS/Trustie/Gemfile",
"/home/qyzh/.npm/_logs/2020-06-07T02_42_06_886Z-debug.log",
"/home/qyzh/OSS/forgeplus/app/controllers/users_controller.rb",
"/home/qyzh/OSS/forgeplus/app/controllers/accounts_controller.rb",
"/home/qyzh/custom/conf/app.ini",
"/home/qyzh/OSS/Trustie_old/Gemfile.lock",
"/home/qyzh/OSS/Trustie/Gemfile.lock",
"/home/qyzh/ruby_study/test.rb",
"/home/qyzh/RubymineProjects/myblog/app/controller/users_controller.rb",
"/home/qyzh/OSS/forgeplus/app/libs/limit_forbid_control.rb",
"/home/qyzh/OSS/forgeplus/log/development.log",
"/home/qyzh/NJU-DisSys-2017/src/raft/raft.go",
"/home/qyzh/OSS/forgeplus/config/application.rb",
"/home/qyzh/RubymineProjects/demo/app/helpers/welcome_helper.rb",
"/home/qyzh/RubymineProjects/demo/app/controllers/welcome_controller.rb",
"/home/qyzh/RubymineProjects/demo/config/routes.rb",
"/home/qyzh/RubymineProjects/demo/app/views/welcome/index.html.erb",
"/home/qyzh/trustie/Gemfile",
"/home/qyzh/trustie/Gemfile.lock",
"/home/qyzh/ruby_study/test",
"/home/qyzh/NJU-DisSys-2017/src/labrpc/test_test.go",
"/home/qyzh/NJU-DisSys-2017/src/labrpc/labrpc.go",
"/home/qyzh/NJU-DisSys-2017/src/raft/test_test.go",
"/home/qyzh/NJU-DisSys-2017/src/raft/persister.go",
"/home/qyzh/NJU-DisSys-2017/src/raft/util.go",
"/home/qyzh/NJU-DisSys-2017/src/raft/config.go"
],
"find":
{
"height": 29.0
},
"find_in_files":
{
"height": 0.0,
"where_history":
[
]
},
"find_state":
{
"case_sensitive": true,
"find_history":
[
"current_user",
"user_set",
"login",
"platform",
"Platform不包含于列表中",
"find_by_",
"find_by",
"3.2.22",
"bug",
"persister",
"currentTerm",
"currentTern",
"term",
"your",
"electionInterval",
"your",
"if",
"your",
"Persister",
"make",
"Your code here",
"Call",
"labrpc",
"main"
],
"highlight": true,
"in_selection": false,
"preserve_case": false,
"regex": false,
"replace_history":
[
],
"reverse": false,
"show_context": true,
"use_buffer2": true,
"whole_word": false,
"wrap": true
},
"groups":
[
{
"selected": 0,
"sheets":
[
{
"buffer": 0,
"file": "src/redux/actions/actionTypes.js",
"semi_transient": false,
"settings":
{
"buffer_size": 5688,
"regions":
{
},
"selection":
[
[
0,
0
]
],
"settings":
{
"syntax": "Packages/JavaScript/JavaScript.sublime-syntax",
"tab_size": 2,
"translate_tabs_to_spaces": true
},
"translation.x": 0.0,
"translation.y": 0.0,
"zoom_level": 1.0
},
"stack_index": 0,
"type": "text"
}
]
}
],
"incremental_find":
{
"height": 29.0
},
"input":
{
"height": 0.0
},
"layout":
{
"cells":
[
[
0,
0,
1,
1
]
],
"cols":
[
0.0,
1.0
],
"rows":
[
0.0,
1.0
]
},
"menu_visible": true,
"output.find_results":
{
"height": 0.0
},
"pinned_build_system": "",
"project": "forgeplus-react.sublime-project",
"replace":
{
"height": 54.0
},
"save_all_on_build": true,
"select_file":
{
"height": 0.0,
"last_filter": "",
"selected_items":
[
],
"width": 0.0
},
"select_project":
{
"height": 0.0,
"last_filter": "",
"selected_items":
[
],
"width": 0.0
},
"select_symbol":
{
"height": 0.0,
"last_filter": "",
"selected_items":
[
],
"width": 0.0
},
"selected_group": 0,
"settings":
{
},
"show_minimap": true,
"show_open_files": false,
"show_tabs": true,
"side_bar_visible": true,
"side_bar_width": 288.0,
"status_bar_visible": true,
"template_settings":
{
}
}

995
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,8 +9,7 @@
"antd": "^3.26.15",
"array-flatten": "^2.1.2",
"autoprefixer": "7.1.6",
"axios": "^0.18.1",
"babel-core": "6.26.0",
"axios": "^0.21.1",
"babel-eslint": "7.2.3",
"babel-jest": "20.0.3",
"babel-loader": "7.1.2",
@ -24,7 +23,7 @@
"classnames": "^2.2.5",
"clipboard": "^2.0.6",
"code-prettify": "^0.1.0",
"codemirror": "^5.53.0",
"codemirror": "^5.61.0",
"connected-react-router": "4.4.1",
"css-loader": "^3.5.2",
"dotenv": "4.0.0",
@ -47,7 +46,7 @@
"jest": "20.0.4",
"js-base64": "^2.5.2",
"katex": "^0.11.1",
"lodash": "^4.17.15",
"lodash": "^4.17.21",
"loglevel": "^1.6.8",
"marked": "^1.0.0",
"material-ui": "^1.0.0-beta.40",
@ -75,6 +74,7 @@
"rc-upload": "^2.9.4",
"react": "^16.13.1",
"react-beautiful-dnd": "^10.0.4",
"react-charts": "^2.0.0-beta.7",
"react-codemirror": "^1.0.0",
"react-codemirror2": "^6.0.1",
"react-color": "^2.18.0",
@ -94,6 +94,7 @@
"react-router-dom": "^4.2.2",
"react-split-pane": "^0.1.91",
"react-url-query": "^1.5.0",
"react-vis": "^1.11.7",
"react-zmage": "^0.8.5-beta.31",
"redux": "^4.0.5",
"redux-thunk": "2.3.0",
@ -108,7 +109,7 @@
"sw-precache-webpack-plugin": "0.11.4",
"url-loader": "0.6.2",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"webpack-dev-server": "^3.11.0",
"webpack-manifest-plugin": "^2.2.0",
"whatwg-fetch": "2.0.3",
"wrap-md-editor": "^0.2.20"
@ -182,7 +183,13 @@
"port": "3007",
"devDependencies": {
"@babel/runtime": "7.0.0-beta.51",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-plugin-import": "^1.13.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"compression-webpack-plugin": "^1.1.12",
"concat": "^1.0.3",
"happypack": "^5.0.1",

View File

@ -2325,6 +2325,9 @@ input::-ms-clear {
background-color: #F5F5F5;
}
.ant-modal-close{
top:8px!important;
}
.newContainer {
min-height: 100%;
@ -2658,7 +2661,7 @@ a.color-green:hover {
/*百分比宽度*/
.width100 {
width: 100%;
width: 100% !important;
}
.width89 {

111
public/css/merge.css Executable file
View File

@ -0,0 +1,111 @@
.CodeMirror-merge {
position: relative;
white-space: pre;
}
.CodeMirror-merge, .CodeMirror-merge .CodeMirror {
min-height:50px;
}
.CodeMirror-merge-2pane .CodeMirror-merge-pane { width: 48%; }
.CodeMirror-merge-2pane .CodeMirror-merge-gap { width: 4%; }
.CodeMirror-merge-3pane .CodeMirror-merge-pane { width: 31%; }
.CodeMirror-merge-3pane .CodeMirror-merge-gap { width: 3.5%; }
.CodeMirror-merge-pane {
display: inline-block;
white-space: normal;
vertical-align: top;
}
.CodeMirror-merge-pane-rightmost {
position: absolute;
right: 0px;
z-index: 1;
}
.CodeMirror-merge-gap {
z-index: 2;
display: inline-block;
height: 100%;
-moz-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
position: relative;
background: #515151;
}
.CodeMirror-merge-scrolllock-wrap {
position: absolute;
bottom: 0; left: 50%;
}
.CodeMirror-merge-scrolllock {
position: relative;
left: -50%;
cursor: pointer;
color: #d8d8d8;
line-height: 1;
}
.CodeMirror-merge-copybuttons-left, .CodeMirror-merge-copybuttons-right {
position: absolute;
left: 0; top: 0;
right: 0; bottom: 0;
line-height: 1;
}
.CodeMirror-merge-copy {
position: absolute;
cursor: pointer;
color: #ce374b;
z-index: 3;
}
.CodeMirror-merge-copy-reverse {
position: absolute;
cursor: pointer;
color: #44c;
}
.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { left: 2px; }
.CodeMirror-merge-copybuttons-right .CodeMirror-merge-copy { right: 2px; }
.CodeMirror-merge-r-inserted, .CodeMirror-merge-l-inserted {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12MwuCXy3+CWyH8GBgYGJgYkAABZbAQ9ELXurwAAAABJRU5ErkJggg==);
background-position: bottom left;
background-repeat: repeat-x;
}
.CodeMirror-merge-r-deleted, .CodeMirror-merge-l-deleted {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12M4Kyb2/6yY2H8GBgYGJgYkAABURgPz6Ks7wQAAAABJRU5ErkJggg==);
background-position: bottom left;
background-repeat: repeat-x;
}
.CodeMirror-merge-r-chunk { background: #9a6868; }
.CodeMirror-merge-r-chunk-start { /*border-top: 1px solid #ee8; */}
.CodeMirror-merge-r-chunk-end {/* border-bottom: 1px solid #ee8; */}
.CodeMirror-merge-r-connect { fill:#9a6868;}
.CodeMirror-merge-l-chunk { background: #eef; }
.CodeMirror-merge-l-chunk-start { border-top: 1px solid #88e; }
.CodeMirror-merge-l-chunk-end { border-bottom: 1px solid #88e; }
.CodeMirror-merge-l-connect { fill: #eef; stroke: #88e; stroke-width: 1px; }
.CodeMirror-merge-l-chunk.CodeMirror-merge-r-chunk { background: #dfd; }
.CodeMirror-merge-l-chunk-start.CodeMirror-merge-r-chunk-start { border-top: 1px solid #4e4; }
.CodeMirror-merge-l-chunk-end.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #4e4; }
.CodeMirror-merge-collapsed-widget:before {
content: "(...)";
}
.CodeMirror-merge-collapsed-widget {
cursor: pointer;
color: #88b;
background: #eef;
border: 1px solid #ddf;
font-size: 90%;
padding: 0 3px;
border-radius: 4px;
}
.CodeMirror-merge-collapsed-line .CodeMirror-gutter-elt { display: none; }

View File

@ -8,18 +8,9 @@
<meta name=”Description” Content=”持续构建协同、共享、可信的软件创建生态开源创作与软件生产相结合,支持大规模群体开展软件协同创新活动”>
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<!-- <script type="text/javascript">
window.__isR = true;
if (
(navigator.userAgent.indexOf('MSIE 9') != -1
|| navigator.userAgent.indexOf('MSIE 10') != -1)
&&
location.pathname.indexOf("/compatibility") == -1) {
location.href = '/compatibility.html'
}
</script> -->
<link rel=" stylesheet" type="text/css" href="%PUBLIC_URL%css/iconfont.css">
<link rel=" stylesheet" type="text/css" href="%PUBLIC_URL%css/edu-purge.css">
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/iconfont.css">
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/edu-purge.css">
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/editormd.min.css">
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/merge.css">
<%= htmlWebpackPlugin.tags.headTags %>
@ -30,6 +21,7 @@
<div id="root" class="page -layout-v -fit widthunit"></div>
<div id="picture_display" style="display: none;"></div>
<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/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

@ -9,13 +9,7 @@ import {
} from 'react-router-dom';
import axios from 'axios';
import LoginDialog from './modules/login/LoginDialog';
import Notcompletedysl from './modules/user/Notcompletedysl';
import Trialapplicationysl from './modules/login/Trialapplicationysl';
import Trialapplicationreview from './modules/user/Trialapplicationreview';
import AccountProfile from "./modules/user/AccountProfile";
import Accountnewprofile from './modules/user/Accountnewprofile';
import Certifiedprofessional from './modules/modals/Certifiedprofessional';
import 'babel-polyfill';
import Loading from './Loading'
import Loadable from 'react-loadable';
@ -30,6 +24,7 @@ import { SnackbarHOC } from 'educoder'
import { initAxiosInterceptors } from './AppConfig'
import { Provider } from 'react-redux';
import configureStore from './redux/stores/configureStore';
import { load } from 'react-cookies';
const store = configureStore();
window.marked = marked;
const theme = createMuiTheme({
@ -76,6 +71,20 @@ const OrganizeIndex = Loadable({
loader: () => import('./forge/Team/Index'),
loading: Loading,
})
// Sponsor
const Sponsor = Loadable({
loader: () => import('./forge/sponsor/Sponsor'),
loading: Loading,
})
// Sponsor Confirmation
const SponsorConfirmation = Loadable({
loader: () => import('./forge/sponsor/SponsorConfirmation'),
loading: Loading,
})
const EducoderLogin = Loadable({
loader: () => import('./modules/login/EducoderLogin'),
loading: Loading,
})
class App extends Component {
constructor(props) {
@ -213,18 +222,12 @@ class App extends Component {
<Provider store={store}>
<ConfigProvider locale={zhCN}>
<MuiThemeProvider theme={theme}>
{/* <Accountnewprofile {...this.props}{...this.state} /> */}
<LoginDialog {...this.props} {...this.state} Modifyloginvalue={() => this.Modifyloginvalue()}></LoginDialog>
{/* <Notcompletedysl {...this.props} {...this.state}></Notcompletedysl> */}
{/* <Trialapplicationysl {...this.props} {...this.state}></Trialapplicationysl> */}
{/* <Trialapplicationreview {...this.props} {...this.state}></Trialapplicationreview> */}
{/* <AccountProfile {...this.props} {...this.state} /> */}
{/* <Certifiedprofessional {...this.props} {...this.state} ModalCancelsy={this.ModalCancelsy} ModalshowCancelsy={this.ModalshowCancelsy} /> */}
<Router>
<Switch>
{/*项目*/}
<Route
path={"/projects/:projectId/ops/:opsId/detail"}
path={"/projects/:owner/:projectId/devops/:opsId/detail"}
render={
(props) => {
return (<OpsDetail {...this.props} {...props} {...this.state} />)
@ -240,7 +243,15 @@ class App extends Component {
}
}>
</Route>
<Route
path="/register"
render={
(props) => {
return (<EducoderLogin {...this.props} {...props} {...this.state} />)
}
}
/>
{/*403*/}
<Route path="/403" component={Shixunauthority} />
@ -260,7 +271,17 @@ class App extends Component {
(props) => {
return (<InfosIndex {...this.props} {...this.state} />)
}
}></Route>
}></Route>
<Route exact path="/sponsor_confirmation/:username/:tier_id"
render={
(props) => (<SponsorConfirmation {...this.props}{...this.state}></SponsorConfirmation>)
}>
</Route>
<Route exact path="/sponsor/:username"
render={
(props) => (<Sponsor {...this.props}{...this.state}></Sponsor>)
}>
</Route>
<Route exact path="/"
render={
(props) => (

View File

@ -28,7 +28,7 @@ if (isDev) {
debugType = window.location.search.indexOf('debug=t') !== -1 ? 'teacher' :
window.location.search.indexOf('debug=s') !== -1 ? 'student' :
window.location.search.indexOf('debug=a') !== -1 ? 'admin' : parsed.debug || 'admin'
}
}
function clearAllCookie() {
cookie.remove('_educoder_session', { path: '/' });
cookie.remove('autologin_trustie', { path: '/' });
@ -38,7 +38,6 @@ clearAllCookie();
function setpostcookie() {
const str = window.location.pathname;
if (str.indexOf("/wxcode") !== -1) {
console.log("123")
cookie.remove('_educoder_session', { path: '/' });
cookie.remove('autologin_trustie', { path: '/' });
const _params = window.location.search;
@ -57,9 +56,10 @@ window._debugType = debugType;
export function initAxiosInterceptors(props) {
initOnlineOfflineListener()
// TODO 避免重复的请求 https://github.com/axios/axios#cancellation
var
proxy = "http://localhost:3000"
proxy = "https://testforgeplus.trustie.net"
var
// proxy = "http://172.27.144.138:3000/"
proxy = "http://localhost:3000"
// proxy = "https://testforgeplus.trustie.net"
const requestMap = {};
window.setfalseInRequestMap = function (keyName) {
@ -71,7 +71,7 @@ export function initAxiosInterceptors(props) {
setpostcookie()
clearAllCookie()
if (config.url.indexOf(proxy) !== -1 || config.url.indexOf(':') !== -1) {
if (config.url.indexOf(proxy) !== -1) {
return config
}
requestProxy(config)

View File

@ -1,7 +1,4 @@
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import { Spin } from 'antd';
class Loading extends Component {
@ -12,7 +9,6 @@ class Loading extends Component {
}
render() {
// Loading
return (
<div className="App" style={{ minHeight: '800px', width: "100%" }}>
<style>

View File

@ -11,15 +11,11 @@ export function getImageUrl(path) {
// https://www.educoder.net
// https://testbdweb.trustie.net
// const local = 'http://localhost:3000'
const local = 'https://testforgeplus.trustie.net/';
if(path.indexOf("http://")===-1){
if (isDev) {
return `${local}/${path}`
}
return `/${path}`;
}else{
return path;
const local = 'https://testforgeplus.trustie.net';
if (isDev) {
return `${local}/${path}`
}
return `/${path}`;
}
export function getImage(path) {

View File

@ -38,13 +38,11 @@ export const formatDelta = (deltas) => {
alt="${alt}"
/>
`;
// text = "<img src="+url+" width='60px' height='30px' onclick='' alt="+alt+"/>";
}
}
formatted.push(text);
});
console.log(formatted);
return formatted.join('');
}

View File

@ -71,7 +71,7 @@ function Index(props){
<div className="aboutContent">
<AlignCenterBetween style={{padding:"14px 0px"}}>
<span className="font-16"><i className="iconfont icon-xiangmujianjie mr5 font-16 color-blue"></i>项目简介</span>
{ editOpration && <a onClick={editContent} className="color-blue">编辑</a> }
{ editOpration && !edit && <a onClick={editContent} className="color-blue">编辑</a> }
</AlignCenterBetween>
{
edit ?

View File

@ -177,7 +177,7 @@ class Activity extends Component{
}
</div>
:
<NoneData _html="暂时还没有相关数据!" />
<NoneData _html="暂时还没有相关数据!" />
}
</Spin>
{

View File

@ -4,7 +4,7 @@ import './branch.css';
import { getBranch , getTag } from '../GetData/getData';
export default (({ projectsId , repo_id , changeBranch , branch , owner })=>{
export default (({ projectsId , branch , owner , history , changeBranch })=>{
const [ showValue , setShowValue ] = useState(branch);
const [ inputValue , setInputValue] = useState(undefined);
const [ nav , setNav ] = useState(0);
@ -17,10 +17,11 @@ export default (({ projectsId , repo_id , changeBranch , branch , owner })=>{
useEffect(()=>{
setShowValue(branch);
},[branch])
useEffect(()=>{
document.body.addEventListener('click', e => {
let name = e.target.className;
let turn = name == "ant-input OptionsInput" || name == "navli active"|| name == "navli" || name == "padding10 bor-bottom-greyE";
let turn = name === "ant-input OptionsInput" || name === "navli active"|| name === "navli" || name === "padding10 bor-bottom-greyE";
if(turn){
return;
}else{
@ -31,7 +32,7 @@ export default (({ projectsId , repo_id , changeBranch , branch , owner })=>{
useEffect(()=>{
getBranchs(projectsId,owner);
},[projectsId])
},[owner])
async function getBranchs(id,owner){
@ -63,7 +64,7 @@ export default (({ projectsId , repo_id , changeBranch , branch , owner })=>{
}
}
function chooseitem(value){
setShowValue(value);
// setShowValue(value);
changeBranch(value);
}

View File

@ -51,7 +51,7 @@
display: flex;
align-items: center;
cursor: pointer;
width: 240px;
min-width: 240px;
}
.branch-tagBox-list .ant-popover-arrow{
display: none;

View File

@ -8,6 +8,10 @@ li.ant-menu-item{
margin:0px!important;
border-bottom: 1px solid #eee;
}
.flags{
border: 1px solid red;
border-radius: 5px;
}
// Cards
.cards{
display: flex;
@ -72,6 +76,7 @@ li.ant-menu-item{
border-radius:11px;
color: #fff;
margin-left: 5px;
font-size: 12px;
&.running{
background:#5091FF;
color: #F1F8FF;
@ -88,6 +93,10 @@ li.ant-menu-item{
background:#F73030;
color:#FCEEEE ;
}
&.killed{
background:#eee;
color:#999 ;
}
}
.handleBox{
position: fixed;
@ -95,6 +104,7 @@ li.ant-menu-item{
right:240px;
z-index: 10000;
}
@media screen and (max-width: 1800px){
.handleBox{
right:190px;

View File

@ -1,12 +1,10 @@
import React from 'react';
import styled from 'styled-components';
export default ({title , value , className})=>{
const Keys = styled.span`
display:flex;
align-item:center;
align-items:center;
& span{
display:block;
height:20px;

View File

@ -0,0 +1,24 @@
import React from 'react';
import { Modal , Button } from 'antd';
import './Component.scss';
function Modals({visible,title,content,onOk,onCancel}){
return(
<Modal
className="modalsStyle"
visible={visible}
title={title}
onCancel={onCancel}
closable={true}
footer={
<div>
<Button onClick={onCancel}>取消</Button>
<Button type={"primary"} style={{marginLeft:"20px"}} onClick={onOk}>确定</Button>
</div>
}
>
<div style={{fontSize:"16px"}}>{content}</div>
</Modal>
)
}
export default Modals;

View File

@ -0,0 +1,61 @@
import React , { useState , useEffect } from 'react';
import { Select } from 'antd';
import { getUrl } from 'educoder';
import axios from 'axios';
const Option = Select.Option;
export default (({ language , select_language })=>{
const [ six , setSix ] = useState(undefined);
const [ languages , setLanguage ] = useState(undefined);
useEffect(()=>{
const url = '/ci/languages.json';
axios.get(url).then(result=>{
if(result){
setLanguage(result.data);
}
}).catch(error=>{
console.log(error);
})
},[])
function changelanguage(value){
let array = value ? languages.filter(item=>item.name === value) :undefined;
select_language(value,array && array[0]);
}
useEffect(()=>{
const url = '/ci/languages/common.json';
axios.get(url).then(result=>{
if(result){
setSix(result.data);
}
}).catch(error=>{
console.log(error);
})
},[])
return(
<React.Fragment>
{
six &&
<ul className="language">
{
six.map((item,key)=>{
return(
key < 6 ? <li className={language ===item.name ? "active":""} onClick={()=>changelanguage(item.name)}><img alt="" src={item.cover_url && getUrl(item.cover_url)} /></li> : ""
)
})
}
</ul>
}
<Select showSearch={true} placeholder={"请选择文本语言"} style={{ width: 200 }} value={language} onChange={changelanguage}>
<Option value={undefined}>请选择文本语言</Option>
{languages && languages.map((item, key) => {
return <Option value={item.name}>{item.name}</Option>;
})}
</Select>
</React.Fragment>
)
})

View File

@ -3,42 +3,54 @@ import './Component.scss';
export const Tags = (status)=>{
switch(status){
case 1:
case "running":
return(
<span className="statusColor running">运行中</span>
);
case 2:
case "failure":case"error":
return (
<span className="statusColor failed">未通过</span>
);
case 3:
case "success":
return (
<span className="statusColor pass">已通过</span>
);
default:
case "pending":
return (
<span className="statusColor Preparing">准备中</span>
);
case 'killed':
return (
<span className="statusColor killed">已撤销</span>
);
}
}
export const TagsLine = (status)=>{
switch(status){
case 1:
case "running":
return(
<span className="statuslineColor running">运行中</span>
);
case 2:
case "failure":case "error":
return (
<span className="statuslineColor failed">未通过</span>
);
case 3:
case "success":
return (
<span className="statuslineColor pass">已通过</span>
);
default:
case "pending":
return (
<span className="statuslineColor Preparing">准备中</span>
);
case 'killed':
return (
<span className="statuslineColor killed">已撤销</span>
);
case 'skipped':
return (
<span className="statuslineColor skipped">已跳过</span>
);
}
}
}

View File

@ -0,0 +1,45 @@
import React ,{ useState } from 'react';
import { Modal , Input , Spin } from 'antd';
import { AlignCenter } from "./layout";
import axios from 'axios';
import './Component.scss';
function PasswordAuthority({ authorityValBox , successFunc , cancelFunc }){
const [ authorityVal , setAuthorityVal ] = useState(undefined);
const [ authorityValFlag , setAuthorityValFlag ] = useState(false);
const [ isSpin , setIsSpin ] = useState(false);
// -
function cancelAuthority(){
cancelFunc();
}
//
function okAuthority(){
if(!authorityVal){
setAuthorityValFlag(true);
return;
}
setIsSpin(true);
const url = `/users/ci/oauth_grant.json`;
axios.get(url,{
params:{password:authorityVal}
}).then(result=>{
setIsSpin(false);
if(result){
successFunc(result.data.step);
}
}).catch(error=>{setIsSpin(false);});
}
return(
<Modal visible={authorityValBox} centered={true} title="授权" onCancel={cancelAuthority} onOk={okAuthority}>
<Spin spinning={isSpin}>
<p style={{textAlign:"center"}}>请输入您的登录密码确认授权DevOps应用</p>
<AlignCenter style={{justifyContent:"center",marginTop:"20px"}}>
<span>密码</span>
<Input.Password value={authorityVal} className={authorityValFlag===true && "flags"} onChange={(e)=>setAuthorityVal(e.target.value)} style={{width:"220px"}}></Input.Password>
</AlignCenter>
</Spin>
</Modal>
)
}
export default PasswordAuthority;

View File

@ -1,27 +1,66 @@
import React , { forwardRef , useCallback } from 'react';
import activate from '../Images/activate.png';
import { Blueback } from '../Component/layout';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { Form , Input } from 'antd';
import axios from 'axios';
import React, { forwardRef, useCallback, useState , useEffect } from "react";
import activate from "../Images/activate.png";
import { Blueback , AlignCenter } from "../Component/layout";
import PasswordAuthority from "../Component/PasswordAuthority";
import styled from "styled-components";
import { Form, Input , Spin , Modal } from "antd";
import axios from "axios";
const P = styled.p`{
width:200px;
line-height:30px;
font-size:16px;
color:#333;
text-align:center;
margin-top:30px;
margin-bottom:30px!important;
}`;
function About( props , ref){
const { form: { getFieldDecorator , validateFields } } = props;
const P = styled.p`
{
width: 230px;
line-height: 30px;
font-size: 16px;
color: #333;
text-align: center;
margin-top: 30px;
margin-bottom: 30px !important;
}
`;
function About(props, ref) {
const { form: { getFieldDecorator, validateFields , setFieldsValue } } = props;
const [isSpining, setIsSpining] = useState(true);
const [ authorityValBox , setAuthorityValBox ] = useState(false);
const [ authorityVal , setAuthorityVal ] = useState(undefined);
const [ authorityValFlag , setAuthorityValFlag ] = useState(false);
//0: devops
//1:
const [step, setStep] = useState(undefined);
// step1true
const owner = props.match.params.owner;
const projectsId = props.match.params.projectsId;
const AuthorLogin = props.author && props.author.login;
const CurrentLogin = props.current_user && props.current_user.login;
useEffect(()=>{
if(CurrentLogin === AuthorLogin){
auth('get');
}else{
setIsSpining(false);
}
},[AuthorLogin,CurrentLogin])
function auth(type){
const url = `/${owner}/${projectsId}/ci_authorize.json`;
axios({
method:`${type}`,
url
}).then(result=>{
if(result && result.data ){
setIsSpining(false);
setStep(result.data.step);
setFieldsValue({...result.data.cloud_account});
}
}).catch(error=>{
console.log(error);
})
}
const helper = useCallback(
(label, name, rules, widget, isRequired) => (
<React.Fragment>
<span className={isRequired?"required":""}>{label}</span>
<span className={isRequired ? "required" : ""}>{label}</span>
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
</Form.Item>
@ -29,52 +68,122 @@ function About( props , ref){
),
[]
);
function startActive(){
let projectsId = props.match.params.projectsId;
validateFields((error,values)=>{
if(!error){
const url = `/dev_ops/cloud_accounts.json`;
axios.post(url,{
...values,
project_id:projectsId
}).then(result=>{
if(result && result.data.redirect_url){
window.location.href = result.data.redirect_url;
}
}).catch(error=>{
console.log(error);
})
//
function goStep() {
validateFields((error, values) => {
if (!error) {
if(step > 0){
setAuthorityValBox(true);
}else{
setIsSpining(true);
const url = `/${owner}/${projectsId}/cloud_accounts.json`;
axios.post(url, {...values,ip_num:values.ip}).then((result) => {
setIsSpining(false);
if (result && result.data.redirect_url) {
props.showNotification("服务器信息配置完成!");
setStep(1);
setAuthorityValBox(true);
}
})
.catch((error) => {
console.log(error);
setIsSpining(false);
});
}
}
});
}
//
function startActive(){
setIsSpining(true);
const url = `/${owner}/${projectsId}/activate.json`;
axios.post(url).then(result=>{
setIsSpining(false);
if(result && result.data.status === 0){
props.history.push(`/projects/${owner}/${projectsId}/devops/dispose`);
// open_devops
let { changeOpenDevops } = props;
changeOpenDevops && changeOpenDevops(true);
}
}).catch(error=>{
console.log(error);
setIsSpining(false);
})
}
return(
<div className="activatePanel">
<img src={activate} alt="" width="250px"/>
<P>定义DevOps工作流帮助您检测bug发布代码</P>
<Link to={""} style={{color:"#5091FF",marginBottom:"20px"}}>了解什么是DevOps</Link>
<Form>
{helper(
"服务器IP地址",
"ip_num",
[{ required: true, message: "请输入服务器IP地址" }],
<Input placeholder="请输入服务器IP地址" style={{width:"368px"}} size="large" />,true
)}
{helper(
"服务器用户名:",
"account",
[{ required: true, message: "请输入服务器用户名" }],
<Input placeholder="请输入服务器用户名" size="large" />,true
)}
{helper(
"服务器密码:",
"secret",
[{ required: true, message: "请输入服务器密码" }],
<Input.Password placeholder="请输入服务器密码" size="large" />,true
)}
</Form>
<Blueback onClick={startActive}>开始激活</Blueback>
</div>
)
// -
function cancelAuthority(){
setAuthorityValBox(false);
}
//
function okAuthority(step){
setAuthorityValBox(false);
setStep(step);
}
return (
<Spin spinning={isSpining}>
<PasswordAuthority authorityValBox={authorityValBox} successFunc={okAuthority} cancelFunc={cancelAuthority}></PasswordAuthority>
<div className="activatePanel">
<img src={activate} alt="" width="250px" />
<P>定义DevOps工作流帮助您检测bug发布代码</P>
{
CurrentLogin !== AuthorLogin && (step === undefined || (step && step < 1)) &&
<div className="noOperation">DevOps开启功能暂未对项目创建者以外的角色开放可以联系项目创建者进行开启开启后便可查看构建信息</div>
}
<a href={"https://forum.trustie.net/forums/3080/detail"} target="_blank" style={{ color: "#5091FF"}}>
了解什么是DevOps
</a>
<a href={"https://forum.trustie.net/forums/3110/detail"} target="_blank" style={{ color: "#5091FF"}}>
如何使用DevOps
</a>
{
AuthorLogin === CurrentLogin ?
<React.Fragment>
{ step <=1 ?
<React.Fragment>
<Input.Password style={{display:'none'}} size="large" />
<Form style={{marginTop:"20px"}}>
<p className="mb20" style={{width:"370px"}}>请仔细核对您的服务器信息一旦确认提交将无法修改</p>
{helper(
"服务器IP地址",
"ip",
[{ required: true, message: "请输入服务器IP地址" }],
<Input
placeholder="请输入服务器IP地址"
style={{ width: "368px" }}
size="large"
disabled={step > 0}
/>,
true
)}
{helper(
"服务器用户名:",
"account",
[{ required: true, message: "请输入服务器用户名" }],
<Input placeholder="请输入服务器用户名" size="large" disabled={step > 0}/>,
true
)}
{helper(
"服务器密码:",
"secret",
[{ required: true, message: "请输入服务器密码" }],
<Input.Password placeholder="请输入服务器密码" size="large" disabled={step > 0}/>,
true
)}
</Form>
<Blueback onClick={goStep}>下一步</Blueback>
</React.Fragment>
:""}
{step > 1 &&
<div style={{textAlign:'center',marginTop:"20px"}}>
<Blueback onClick={startActive} className="mt20">开始激活</Blueback>
</div>
}
</React.Fragment>
:""
}
</div>
</Spin>
);
}
export default Form.create()(forwardRef(About));
export default Form.create()(forwardRef(About));

View File

@ -1,53 +1,45 @@
import React , { useState , useEffect } from 'react';
import { Spin } from 'antd';
import { Blueback } from '../Component/layout';
import Editor from "react-monaco-editor";
import Modals from './DisposeModal';
import FileLanguage from '../Component/FileLanguage';
import FileLanguage from '../Component/OpsFileLanguage';
import axios from 'axios';
function Dispose(props){
const [ info , setInfo ] = useState(undefined);
const [ spining , setSpining ] = useState(true);
const [ info , setInfo ] = useState('.trustie-pipeline.yml');
const [ visible , setVisible ] = useState(false);
const [ ymlValue , setYmlValue ] = useState("");
const [ six , setSix ] = useState(undefined);
const [ sha , setSha ] = useState(undefined);
const [ fileLanguage , setFileLanguage ] = useState(undefined);
const [ first , setFirst ] = useState(false);
let projectsId = props.match.params.projectsId;
let owner = props.match.params.owner;
useEffect(()=>{
if(projectsId){
const url = '/dev_ops/builds/get_trustie_pipeline.json';
const url = `/${owner}/${projectsId}/get_trustie_pipeline.json`;
axios.get(url,{
params:{
project_id:projectsId
}
}).then(result=>{
if(result && result.data.content){
setInfo(result.data);
setInfo(result.data.name);
setYmlValue(result.data.content);
setFirst(true);
setSha(result.data.sha);
}else{
setFirst(false);
}
setSpining(false);
}).catch(error=>{
console.log(error);
})
}
},[])
useEffect(()=>{
const url = '/dev_ops/languages/common.json';
axios.get(url).then(result=>{
if(result){
setSix(result.data);
}
}).catch(error=>{
console.log(error);
})
},[])
},[projectsId])
//
function changeEditor(value){
@ -57,25 +49,25 @@ function Dispose(props){
//
function select_language(value,array){
setFileLanguage(value);
// console.log(array);
setYmlValue( array && array.content);
}
//
function submit(){
let url = '';
const { defaultBranch } = props;
let params = {
branch: "master",
branch: defaultBranch,
content:ymlValue,
filepath:info && info.name,
filepath:info,
message:''
}
if(first){
// true
url = `/${owner}/${projectsId}/update_file.json`;
url = `/${owner}/${projectsId}/update_trustie_pipeline.json`;
axios.put(url,{
...params,
sha:info && info.sha
sha
}).then(result=>{
if(result){
setVisible(true);
@ -95,37 +87,24 @@ function Dispose(props){
}
}
function suresubmit(){
props.history.push(`/projects/${owner}/${projectsId}/ops/list`);
setVisible(false);
props.history.push(`/projects/${owner}/${projectsId}/devops/list`);
}
return(
<React.Fragment>
<Spin spinning={spining}>
<Modals visible={visible} closeFunc={(flag)=>setVisible(flag)} sureFunc={suresubmit}></Modals>
<p>编程语言</p>
{
six &&
<ul className="language">
{
six && six.map((item,key)=>{
return(
key < 6 ? <li><img alt="" src={item.cover_url} /></li> : ""
)
})
}
</ul>
}
<div className="mt20 mb20">
<FileLanguage language={fileLanguage} select_language={select_language}/>
</div>
<p>配置脚本</p>
<div className="editorBody">
<p className="editorHead">
<span>{info && info.name}</span>
<a><i className="iconfont icon-bianji6 font-14"></i></a>
</p>
<p className="editorHead">{info}</p>
<Editor
height="300px"
language={"java"}
language={"yml"}
theme={"vs-grey"}
defaultValue="请输入内容"
value={ymlValue}
@ -134,7 +113,7 @@ function Dispose(props){
></Editor>
</div>
<Blueback onClick={submit}>确定提交</Blueback>
</React.Fragment>
</Spin>
)
}
export default Dispose;

View File

@ -8,7 +8,7 @@ const Div = styled.div`{
text-align:center;
color:#333;
}`
export default (({visible , closeFunc , suresubmit})=>{
export default (({visible , closeFunc , sureFunc})=>{
return(
<Modal
title="提示"
@ -17,7 +17,7 @@ export default (({visible , closeFunc , suresubmit})=>{
footer={
<div>
<Button onClick={()=>closeFunc(false)}>取消</Button>
<Button type={"primary"} onClick={suresubmit} style={{marginLeft:"20px"}}>确定</Button>
<Button type={"primary"} onClick={()=>sureFunc()} style={{marginLeft:"20px"}}>确定</Button>
</div>
}
onCancel={()=>closeFunc(false)}

View File

@ -18,17 +18,17 @@ export default ((props)=>{
return(
<WhiteBack className="opsPanel">
<Switch {...props}>
<Route path="/projects/:projectsId/ops/dispose"
<Route path="/projects/:owner/:projectsId/devops/dispose"
render={
() => (<Infos {...props} />)
}
></Route>
<Route path="/projects/:projectsId/ops/list"
<Route path="/projects/:owner/:projectsId/devops/list"
render={
() => (<Infos {...props} />)
}
></Route>
<Route path="/projects/:projectsId/ops"
<Route path="/projects/:owner/:projectsId/devops"
render={
() => (<About {...props} />)
}

View File

@ -1,4 +1,4 @@
import React , { useEffect , useState } from 'react';
import React , { useEffect , useState , useRef } from 'react';
import { Banner } from '../Component/layout';
import { Link } from 'react-router-dom';
@ -11,27 +11,46 @@ const Div = styled.div`{
padding:24px 30px;
}`;
export default ((props)=>{
const [ menu , setMenu ] = useState(undefined);
const [ menu , setMenu ] = useState(false);
const [ permission , setPermission ] = useState("");
const childRef = useRef();
const path = props.location.pathname;
const owner = props.match.params.owner;
const projectsId = props.match.params.projectsId;
const projectDetail = props.projectDetail;
useEffect(()=>{
// console.log(props.match.params.projectsId)
if(path === `/projects/${props.match.params.projectsId}/ops/list`){
if(path === `/projects/${owner}/${projectsId}/devops/list`){
setMenu(true);
}else{
setMenu(false);
}
},[path])
useEffect(()=>{
if(projectDetail){
setPermission(props.projectDetail.permission);
}
},[projectDetail])
const updateChildState = () => {
// changeVal
if (childRef.current) {
childRef.current.changeVal();
}
}
return(
<div className="disposePanel">
<Banner>
<Link to={`/projects/${props.match.params.projectsId}/ops/dispose`}>工作流配置</Link>
{ menu ? <Link to={`/projects/${props.match.params.projectsId}/ops/list`} style={{ marginLeft:"66px",color:"#5091FF" }}>构建列表</Link>:""}
{ permission !=="Reporter" && <Link to={`/projects/${owner}/${props.match.params.projectsId}/devops/dispose`} className={menu===false && "color-blue"} style={{ marginRight:"66px"}}>工作流配置</Link>}
<Link to={`/projects/${owner}/${props.match.params.projectsId}/devops/list`}className={menu===true && "color-blue"}>构建列表</Link>
{ menu===true && <a onClick={updateChildState} style={{float:"right",fontSize:"14px",color:"#FF6E21",marginTop:"5px"}}>刷新</a>}
</Banner>
<Div>
{ menu && menu === true && <Structure {...props}/> }
{ menu && menu === false && <Dispost {...props}/> }
{ menu === true && <Structure {...props} wrappedComponentRef={(form) => childRef.current = form} ref={childRef}/> }
{ menu === false && permission !=="Reporter" && <Dispost {...props}/> }
</Div>
</div>
)

View File

@ -1,42 +1,127 @@
import React from 'react';
import { FlexAJ , Blueline , AlignCenter } from '../Component/layout';
import styled from 'styled-components';
import { Menu } from 'antd';
import { TagsLine } from '../Component/OpsStatus';
import React, { useEffect, useState } from "react";
import { FlexAJ, Blueline, AlignCenter } from "../Component/layout";
import styled from "styled-components";
import { Menu, Popconfirm } from "antd";
import { TagsLine } from "../Component/OpsStatus";
import { Time } from "../Utils/Time";
import { truncateCommitId } from "../common/util";
import { getUrl } from 'educoder';
const SubMenu = Menu.SubMenu;
const Img = styled.img`{
width:25px;
height:25px;
border-radius:50%;
margin-right:10px;
}`
export default (()=>{
return(
const Img = styled.img`
{
width: 25px;
height: 25px;
border-radius: 50%;
margin-right: 10px;
}
`;
export default ({ data, repeatSet , chooseSteps }) => {
const [tamp, setTamp] = useState(undefined);
const [sha, setSha] = useState(undefined);
useEffect(() => {
if (data && data.started) {
let t = parseInt(data.started) * 1000;
let time = Time(t);
setTamp(time);
}
if (data && data.after) {
setSha(truncateCommitId(data.after));
}
}, [data]);
function renderStatusBtn() {
let status = data && data.status;
let number = data && data.number;
if (status === "failure" || status === "error" || status === "success") {
return "";
}else if(status === "killed"){
return(
<Popconfirm
title="确认重新构建?"
onConfirm={(e) => repeatSet(e,'repeat',number)}
onCancel={(e)=>{e.stopPropagation()}}
cancelText="取消"
okText="确定"
>
<Blueline onClick={(e)=>{e.stopPropagation()}}>重新构建</Blueline>
</Popconfirm>
);
} else {
return (
<Popconfirm
title="确认撤销构建?"
onConfirm={(e) => repeatSet(e,'cancel',number)}
onCancel={(e)=>{e.stopPropagation()}}
cancelText="取消"
okText="确定"
>
<Blueline onClick={(e)=>{e.stopPropagation()}}>撤销构建</Blueline>
</Popconfirm>
);
}
}
function clickSub(e,stageN,stepN){
chooseSteps(stageN,stepN);
}
return (
<div>
<FlexAJ className="leftheader">
<AlignCenter>
<Img src="https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2091711702,2468700162&fm=111&gp=0.jpg"/>
<span className="nest">开始时间<span>2020.07.10 15:30</span></span>
<span className="nest">运行时间<span>20s</span></span>
<Img src={getUrl(`/images/${data && data.author && data.author.image_url}`)} />
{data && data.started &&
<span className="nest">
开始时间<span> {data.started}</span>
</span>
}
{
data && data.duration_time &&
<span className="nest">
运行时间<span>{data.duration_time}</span>
</span>
}
</AlignCenter>
<Blueline>重新创建</Blueline>
{renderStatusBtn()}
</FlexAJ>
<div className="leftMainContent">
<AlignCenter className="contentBranch">
<i className="iconfont icon-fenzhi1"></i>
<span>分支</span>
<span className="branchname">master</span>
<span className="branchsha">8b3476f5</span>
<span className="branchname">{data && data.branch_target}</span>
<span className="branchsha">{data && truncateCommitId(data.build_after_sha)}</span>
</AlignCenter>
</div>
<Menu mode='inline' className="leftMenu">
<SubMenu title={<div><i className="iconfont icon-gongzuoliu font-14 mr4"></i><span>CI</span></div>}>
<Menu.Item>
<FlexAJ><span>Build setup 01 {TagsLine(1)}</span><span>20s</span></FlexAJ>
</Menu.Item>
</SubMenu>
<Menu mode="inline" className="leftMenu" defaultOpenKeys={[`0`]} defaultSelectedKeys={[`0`]}>
{data && data.stages ? data.stages.map((item, key) => {
return item.steps && item.steps.length > 0 ?
<SubMenu
title={
<div>
<i className="iconfont icon-gongzuoliu font-14 mr4"></i>
<span>{item.name}</span>
</div>
}
key={`${key}`}
>
{item.steps.map((i, k) => {
return (
<Menu.Item key={`${k}`} onClick={(e)=>clickSub(e,item.number,i.id)}>
<FlexAJ>
<span>
{i.name} {i.status ? TagsLine(i.status) : ""}
</span>
<span>{i.duration_time}</span>
</FlexAJ>
</Menu.Item>
);
})}
</SubMenu>
: "";
})
: ""}
</Menu>
</div>
)
})
);
};

View File

@ -1,21 +1,107 @@
import React from 'react';
import { FlexAJ , AlignCenter } from '../Component/layout';
import React, { useState, useEffect } from "react";
import { Spin } from "antd";
import { FlexAJ, AlignCenter } from "../Component/layout";
import axios from "axios";
export default (()=>{
return(
<div className="rightMainContent">
<div>
<FlexAJ className="items">
<span>Build setup 01</span>
<AlignCenter>20<i className="iconfont icon-triangle"></i></AlignCenter>
</FlexAJ>
export default ({
data,
stepN,
stageN,
projectId,
owner,
opsId,
rightSpin,
}) => {
const [coders, setCoders] = useState(undefined);
const [empty, setEmpty] = useState(false);
const [spining, setSpining] = useState(true);
const [stage, setStage] = useState(undefined);
const [step, setStep] = useState(undefined);
useEffect(() => {
setSpining(rightSpin);
}, [rightSpin]);
useEffect(() => {
if (data) {
let stages = data.stages;
if (stages && stages.length > 0) {
let pre = stageN
? stages.filter((item) => item.number === stageN)[0]
: stages[0];
setStage(pre);
let p = pre && pre.steps;
let sub = stepN
? p && p.length > 0 && p.filter((item) => item.id === stepN)[0]
: p[0];
setStep(sub);
if (sub && sub.status !== "skipped") {
getStep(pre.number, sub.number);
}
// skippedout
if(sub.status === "skipped"){
setCoders(undefined);
setEmpty(true);
setSpining(false);
}
} else {
setSpining(false);
}
}
}, [data, stageN, stepN]);
function getStep(stageN, stepN) {
if (stageN && stepN) {
const url = `/${owner}/${projectId}/builds/${opsId}/logs/${stageN}/${stepN}.json`;
axios.get(url).then((result) => {
if (result) {
setCoders(result.data);
setSpining(false);
}
})
.catch((error) => {
console.log(error);
});
}
}
return (
<Spin spinning={spining}>
<div className="rightMainContent">
{data && data.status !== "error" ? (
<div>
<FlexAJ className="items">
<span>{step && step.name}</span>
<AlignCenter>
{step && step.duration_time}
<i className="iconfont icon-sanjiaoxing-down"></i>
</AlignCenter>
</FlexAJ>
<div>
{coders && coders.length > 0 ? (
coders.map((item, key) => {
return (
<div className="opsDetailOut">
<span>{key + 1}</span>
<p>{item.out}</p>
</div>
);
})
) : empty ? (
<div className="opsDetailOut">
<span>1</span>
<p>
{stage && stage.name} {step && step.name}: Skipped
</p>
</div>
) : (
""
)}
</div>
</div>
) : (
<div style={{ color: "red" }}>error:{data && data.error}</div>
)}
</div>
<div>
<FlexAJ className="items"></FlexAJ>
</div>
<div>
<FlexAJ className="items"></FlexAJ>
</div>
</div>
)
})
</Spin>
);
};

View File

@ -1,240 +1,320 @@
import React , { useState , useEffect } from 'react';
import { FlexAJ , AlignCenter , Blueback } from '../Component/layout';
import { Table , Pagination } from 'antd';
import { truncateCommitId } from '../common/util';
import styled from 'styled-components';
import axios from 'axios';
import React, { useState, useEffect , useImperativeHandle ,forwardRef } from "react";
import { FlexAJ, AlignCenter } from "../Component/layout";
import { Table, Pagination, Popconfirm } from "antd";
import { truncateCommitId } from "../common/util";
import {getUrl} from 'educoder';
import axios from "axios";
const STATUS = [
{name:"所有",value:"1"},
{name:"准备中",value:"2"},
{name:"运行中",value:"3"},
{name:"已完成",value:"4"}
]
const LIMIT = 15;
const datasource = [
{
status:2,
author:"caishi",
message:{
branch:"master",
sha:"8b3476f5",
image:"https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2034740944,4251903193&fm=26&gp=0.jpg",
message:"将分支“ 221063-improve-buy-ci-minutes-link”"
},
begin:"2020-07-08",
run:"20s"
},
{
status:1,
author:"caishi",
message:{
branch:"master",
sha:"8b3476f5",
image:"https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2034740944,4251903193&fm=26&gp=0.jpg",
message:"将分支“ 221063-improve-buy-ci-minutes-link”"
},
begin:"2020-07-08",
run:""
}
{ name: "所有"},
{ name: "运行中", value: "running" },
{ name: "已撤销", value: "killed" },
{ name: "构建失败", value: "failure" },
{ name: "已完成", value: "success" },
];
const LIMIT = 15;
function Structure(props,ref){
const [status, setStatus] = useState(undefined);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [data, setData] = useState(undefined);
const [tableLoading, setTableLoading] = useState(true);
const Img = styled.img`{
border-radius:50%;
margin-rigth:10px;
width:25px;
height:25px;
}`
export default ((props)=>{
const [ status ,setStatus ] = useState("1");
const [ page ,setPage ] = useState(1);
const [ total ,setTotal ] = useState(10);
const [ data , setData ] = useState(undefined);
let projectsId = props.match.params.projectsId;
let owner = props.match.params.owner;
const permission = props.projectDetail && props.projectDetail.permission;
let projectsId = props.match.params.projectsId;
useEffect(()=>{
if(projectsId){
const url ='/dev_ops/builds.json';
axios.get(url,{
params:{
project_id:projectsId
}
}).then(result=>{
if(result){
let list = result.data && result.data.map((item,key)=>{
return {
status:item.status,
author:item.sender,
message:{
branch:item.source,
image:item.author_avatar,
message:item.message,
sha:truncateCommitId(item.after)
},
started:item.started,
timestamp:item.timestamp
}
})
setData(list);
}
}).catch(error=>{
console.log(error);
})
useImperativeHandle(ref, () => ({
changeVal: () => {
setTableLoading(true);
Init();
}
},[])
}))
function ChangeStatus(value){
setStatus(value)
useEffect(() => {
if (projectsId) {
Init();
}
}, [page]);
let current_user = props.current_user;
function Init(status) {
const url = `/${owner}/${projectsId}/builds.json`;
axios.get(url,{
params:{
search:status,
page,limit:LIMIT
}
}).then((result) => {
if (result && result.data) {
let list = result.data.builds && result.data.builds.map((item, key) => {
return {
...item,
author:item.author && item.author.name,
message: {
branch: item.branch_target,
message: item.message,
sha: truncateCommitId(item.build_after_sha),
},
started: item.started || "--"
};
});
setTotal(result.data.total_count);
setData(list);
setTableLoading(false);
}
})
.catch((error) => {
console.log(error);
});
}
function ChangeStatus(value) {
setStatus(value);
Init(value);
}
//
function ChangePage(page){
setPage(page)
function ChangePage(page) {
setPage(page);
}
function renderStatus() {
return(
return (
<ul className="listNav">
{
STATUS.map((item,key)=>{
return <li onClick={()=>ChangeStatus(item.value)} className={ status === item.value ? "active":""}>{item.name}</li>
})
}
{STATUS.map((item, key) => {
return (
<li
onClick={() => ChangeStatus(item.value)}
className={status === item.value ? "active" : ""}
>
{item.name}
</li>
);
})}
</ul>
)
);
}
function renderStatusBtn(status){
if(status === "failure" || status ==="success"){
function renderStatusBtn(status, number) {
if (status === "error" || status === "success") {
return "";
}else if(status === "killed" || status === "failure"){
return(
<a className="color-blue">重新构建</a>
)
}else{
return(
<a className="color-red">撤销构建</a>
)
<Popconfirm
title="确认重新构建?"
onConfirm={(e) => repeatSet(e,number)}
onCancel={(e)=>{e.stopPropagation()}}
cancelText="取消"
okText="确定"
>
<a className="color-blue" onClick={(e)=>{e.stopPropagation()}}>重新构建</a>
</Popconfirm>
);
} else {
return (
<Popconfirm
title="确认撤销构建?"
onConfirm={(e) => cancelSet(e,number)}
onCancel={(e)=>{e.stopPropagation()}}
cancelText="取消"
okText="确定"
>
<a className="color-red" onClick={(e)=>{e.stopPropagation()}}>撤销构建</a>
</Popconfirm>
);
}
}
function renderTableStatus (status){
switch (status){
//
function repeatSet(e,number) {
e.stopPropagation();
setTableLoading(true);
const url = `/${owner}/${projectsId}/builds/${number}/restart.json`;
axios.post(url).then((result) => {
if (result) {
props.showNotification("工作流正在重新构建!");
Init();
}
})
.catch((error) => {
console.log(error);
});
}
//
function cancelSet(e,number) {
e.stopPropagation();
setTableLoading(true);
const url = `/${owner}/${projectsId}/builds/${number}/stop.json`;
axios.delete(url).then((result) => {
if (result) {
props.showNotification("撤销构建成功!");
Init(projectsId);
}
})
.catch((error) => {
console.log(error);
});
}
function renderTableStatus(status) {
switch (status) {
case "running":
return(
<span className="statusTag running"><i className="iconfont icon-yunhangzhong"></i>运行中</span>
);
case "failure":
return (
<span className="statusTag failed"><i className="iconfont icon-weitongguo"></i>未通过</span>
<span className="statusTag running">
<i className="iconfont icon-yunhangzhong"></i>运行中
</span>
);
case "failure": case 'error':
return (
<span className="statusTag failed">
<i className="iconfont icon-weitongguo"></i>未通过
</span>
);
case "success":
return (
<span className="statusTag pass"><i className="iconfont icon-yitongguo"></i>已通过</span>
<span className="statusTag pass">
<i className="iconfont icon-yitongguo"></i>已通过
</span>
);
default:
case 'killed':
return (
<span className="statusTag Preparing"><i className="iconfont icon-zhunbeizhong"></i>准备中</span>
<span className="statusTag killed">
<i className="iconfont icon-weitongguo"></i>已撤销
</span>
);
case 'pending':
return (
<span className="statusTag Preparing">
<i className="iconfont icon-zhunbeizhong"></i>准备中
</span>
);
}
}
function clickRows(event,e){
props.history.push(`/projects/${owner}/${projectsId}/devops/${e.number}/detail`);
}
const column = [
{
title:'序号',
dataIndex:"No",
key:"No",
width:"8%",
render:(item,value,key)=>{
return(
<span>#{key+1}</span>
)
}
title: "序号",
dataIndex: "number",
key: "number",
width: "8%",
render: ( value, item, key) => {
return <span>#{value}</span>;
},
},
{
title:'状态',
dataIndex:"status",
key:"status",
width:"12%",
render:(value,item,key)=>{
return(renderTableStatus(value))
}
title: "状态",
dataIndex: "status",
key: "status",
width: "12%",
render: (value, item, key) => {
return renderTableStatus(value);
},
},
{
title:'构建人',
dataIndex:"author",
key:"author",
width:"12%",
align:"center"
title: "构建人",
dataIndex: "author",
key: "author",
width: "12%",
align: "center",
},
{
title:'提交信息',
dataIndex:"message",
key:"message",
width:"30%",
render:(value,item,key)=>{
title: "提交信息",
dataIndex: "message",
key: "message",
width: "30%",
render: (value, item, key) => {
let meg = item.message;
return (
return (
<React.Fragment>
<div>
{ meg.branch && <span className="mr10 color-grey-8"><i className="iconfont icon-fenzhi1 font-16 mr5"></i>分支{meg.branch}</span>}
{ meg.sha && <span className="color-orange">{meg.sha}</span>}
{meg.branch && (
<span className="mr10 color-grey-8">
<i className="iconfont icon-fenzhi1 font-16 mr5"></i>分支
{meg.branch}
</span>
)}
{meg.sha && <span className="color-orange">{meg.sha}</span>}
</div>
<AlignCenter>
<Img src={meg.image} />
<div className="task-hide ml5" style={{maxWidth:"300px"}}>{meg.message}</div>
<img style={{borderRadius:"50%",marginRight:"10px",width:"25px",height:"25px"}} src={`${current_user && getUrl(`/images/${current_user.image_url}`)}`} />
<div className="task-hide ml5" style={{ maxWidth: "300px" }}>
{meg.message}
</div>
</AlignCenter>
</React.Fragment>
)
}
);
},
},
{
title:'开始时间',
dataIndex:"started",
key:"started",
width:"15%",
render:(value,item,key)=>{
return (
<span>{value || "--"}</span>
)
}
title: "开始时间",
dataIndex: "started",
key: "started",
width: "15%",
render: (value, item, key) => {
return <span>{value || "--"}</span>;
},
},
{
title:'运行时间',
dataIndex:"timestamp",
key:"timestamp",
width:"15%",
render:(value,item,key)=>{
return (
<span>{value || value === 0 ? `${value}s` : "--"}</span>
)
}
title: "运行时间",
dataIndex: "duration_time",
key: "duration_time",
width: "15%",
render: (value, item, key) => {
return <span>{value || "--"}</span>;
},
},
{
title:'操作',
dataIndex:"operation",
key:"operation",
render:(value,item,key)=>{
return(renderStatusBtn(item.status));
}
}
]
return(
title: "操作",
dataIndex: "operation",
key: "operation",
render: (value, item, key) => {
if(permission === "Admin" || permission === "Owner"){
return renderStatusBtn(item.status, item.number);
}else{
return "--";
}
},
},
];
return (
<div className="listPart">
<FlexAJ>
{renderStatus()}
<span>
<Blueback className="mr30">手动创建</Blueback>
<span className="mr30"><i className="iconfont icon-fenzhi1 font-16 mr5 color-blue"></i>分支</span>
<span><i className="iconfont icon-biaoqian3 font-16 mr5 color-blue"></i>标签</span>
</span>
{/* <Blueback>手动创建</Blueback> */}
{/* <span className="mr30">
<i className="iconfont icon-fenzhi1 font-16 mr5 color-blue"></i>分支
</span>
<span>
<i className="iconfont icon-biaoqian3 font-16 mr5 color-blue"></i>
标签
</span> */}
</FlexAJ>
<Table
onRow={(record,index)=>{
return{
onClick:(event)=>clickRows(event,record)
}
}}
columns={column}
className="normalTable"
dataSource={data}
pagination={false}
loading={tableLoading}
></Table>
{
total > LIMIT ?
<div style={{textAlign:'center',margin:"30px 50px"}}>
<Pagination showQuickJumper defaultCurrent={page} total={total} pageSize={LIMIT} onChange={ChangePage}></Pagination>
</div>:""
}
{total > LIMIT ?
<div style={{ textAlign: "center", margin: "30px 50px" }}>
<Pagination
showQuickJumper
defaultCurrent={page}
total={total}
pageSize={LIMIT}
onChange={ChangePage}
></Pagination>
</div>
:
"" }
</div>
)
})
);
};
export default forwardRef(Structure);

View File

@ -12,8 +12,14 @@
.disposePanel{
.language{
display: flex;
margin-bottom: 20px;
li{
margin-right: 20px;
cursor: pointer;
border:1px solid #fff;
&.active{
border:1px solid #5091FF;
}
}
}
.editorBody{
@ -36,6 +42,7 @@
top:7px;
}
.listPart{
min-height: 400px;
.listNav{
display: flex;
li{
@ -63,6 +70,7 @@
.ant-table-thead > tr > th, .ant-table-tbody > tr > td{
padding:10px 5px;
color:#333;
cursor: pointer;
}
.ant-table-thead{
border:1px solid rgba(238,238,238,1)
@ -70,6 +78,9 @@
}
}
}
.ant-modal-close{
top:7px;
}
// 列表
.listPart{
.statusTag{
@ -103,8 +114,13 @@
border:1px solid #F73030;
color:#F73030 ;
}
&.killed{
background:#eee;
border:1px solid #999;
color:#999 ;
}
}
}
}
@ -205,6 +221,14 @@
border:1px solid rgba(255,110,33,1);
color:rgba(255,110,33,1);
}
&.killed{
border:1px solid #999;
color:#999;
}
&.skipped{
border:1px solid #d4d9de;
color:#798390;
}
}
}
&.rightSection{
@ -212,6 +236,8 @@
background-color: #081930;
.rightMainContent{
padding:24px 30px;
height:100vh;
overflow-y: auto;
& > div{
margin-bottom: 12px;
.items{
@ -264,4 +290,27 @@
cursor: col-resize;
}
}
}
.opsDetailOut{
display: flex;
color: #fff;
line-height: 22px;
align-items: flex-start;
margin-top: 5px;
&>span{
margin-right: 10px;
min-width: 20px;
text-align: left;
padding-left: 3px;
}
&>p{
margin-bottom: 0px;
}
}
.noOperation{
margin-bottom:20px;
width:380px;
text-align:center;
line-height:22px;
color:#666;
}

View File

@ -1,33 +1,114 @@
import React from 'react';
import './ops.scss';
import { FlexAJ, AlignCenter } from '../Component/layout';
import { Tags } from '../Component/OpsStatus';
import SplitPane from 'react-split-pane';
import LeftPanel from './OpsDetailLeftpanel';
import RightPanel from './OpsDetailRightpanel';
import React, { useEffect, useState, useRef, useContext } from "react";
import "./ops.scss";
import { FlexAJ, AlignCenter } from "../Component/layout";
import { Tags } from "../Component/OpsStatus";
import SplitPane from "react-split-pane";
import LeftPanel from "./OpsDetailLeftpanel";
import RightPanel from "./OpsDetailRightpanel";
import axios from "axios";
import { Spin } from "antd";
import { Link } from "react-router-dom";
export default (props) => {
const [data, setData] = useState(undefined);
const [stageN, setStageN] = useState(undefined);
const [stepN, setStepN] = useState(undefined);
const [rightSpin, setRightSpin] = useState(false);
const [spinning, setSpinning] = useState(true);
export default (()=>{
return(
<div className="opsDetailPanel">
<FlexAJ className="opsInfos">
<AlignCenter>
<span>#1</span>
<span className="ml10">将分支221063-improve-buy-ci-minutes-link合并到221063-improve-buy-ci-minutes-link"</span>
{Tags(1)}
</AlignCenter>
<a style={{color:"#ddd"}}><i className="iconfont icon-yiguanbi font-15 mr5"></i>退出</a>
</FlexAJ>
<div className="opsSection">
<SplitPane className="outer-split-pane" split="vertical" minSize={468} maxSize={-350} defaultSize="40%">
<section className="leftSection">
<LeftPanel />
</section>
<section className="rightSection">
<RightPanel />
</section>
</SplitPane>
let projectId = props.match.params.projectId;
let owner = props.match.params.owner;
let opsId = props.match.params.opsId;
useEffect(() => {
if (opsId && projectId) {
Init();
}
}, [opsId]);
function Init() {
const url = `/${owner}/${projectId}/builds/${opsId}.json`;
axios.get(url).then((result) => {
if (result && result.data) {
setSpinning(false);
setData(result.data);
}
})
.catch((error) => {
console.log(error);
setSpinning(false);
});
}
//
function repeatSet(e,type,number) {
if(type==="repeat"){
//
const url = `/${owner}/${projectId}/builds/${number}/restart.json`;
axios.post(url).then((result) => {
if (result && result.data) {
props.showNotification("工作流正在重新构建!");
props.history.push(`/projects/${owner}/${projectId}/devops/${result.data.number}/detail`);
}
})
.catch((error) => {
console.log(error);
});
}else{
//
const url = `/${owner}/${projectId}/builds/${number}/stop.json`;
axios.delete(url).then((result) => {
if (result) {
props.showNotification("撤销构建成功!");
Init();
}
})
.catch((error) => {
console.log(error);
});
}
}
function chooseSteps(pre,sub){
if(pre && sub){
setStepN(sub);
setStageN(pre);
setRightSpin(true);
}
}
return (
<Spin spinning={spinning}>
<div className="opsDetailPanel">
<FlexAJ className="opsInfos">
<AlignCenter>
<span>#{data && data.number}</span>
<span className="ml10">{data && data.message}</span>
{Tags(`${data && data.status}`)}
</AlignCenter>
<Link
style={{ color: "#ddd" }}
to={`/projects/${owner}/${projectId}/devops/list`}
>
<i className="iconfont icon-yiguanbi font-15 mr5"></i>退出
</Link>
</FlexAJ>
<div className="opsSection">
<SplitPane
className="outer-split-pane"
split="vertical"
minSize={468}
maxSize={-350}
defaultSize="40%"
>
<section className="leftSection">
<LeftPanel data={data} repeatSet={repeatSet} chooseSteps={chooseSteps} />
</section>
<section className="rightSection">
<RightPanel data={data} rightSpin={rightSpin} stepN={stepN} stageN={stageN} owner={owner} projectId={projectId} opsId={opsId} />
</section>
</SplitPane>
</div>
</div>
</div>
)
});
</Spin>
);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -32,9 +32,9 @@ export default ((props)=>{
return(
<li key={key}>
<div>
<Link to={`/projects/${owner}/${projectsId}?branch=${item.name}`} className="color-blue font-15" style={{"maxWidth":"100px"}}>{item.name}</Link>
<Link to={`/projects/${owner}/${projectsId}/branch/${item.name}`} className="color-blue font-15" style={{"maxWidth":"100px"}}>{item.name}</Link>
<p className="f-wrap-alignCenter mt15">
<span className="mr5 commitKey" style={{marginLeft:0}}>{item.last_commit && truncateCommitId(item.last_commit.sha)}</span>
<Link to={`/projects/${owner}/${projectsId}/commits/${truncateCommitId(`${item.last_commit.sha}`)}`} className="mr5 commitKey" style={{marginLeft:0}}>{item.last_commit && truncateCommitId(item.last_commit.sha)}</Link>
<span className="color-grey-3 hide-1 messages leftPoint">{item.last_commit && item.last_commit.message}</span>
<span className="color-grey-8 ml30">最后更新于{item.last_commit && item.last_commit.time_from_now}</span>
</p>

View File

@ -1,7 +1,7 @@
import React , { Component } from 'react';
import { Spin , Pagination } from 'antd';
import { getImageUrl } from 'educoder';
import { truncateCommitId } from '../common/util'
import { truncateCommitId } from '../common/util';
import SelectBranch from '../Branch/Select';
import Nodata from '../Nodata';
@ -12,7 +12,6 @@ class CoderRootCommit extends Component{
constructor(props){
super(props)
this.state={
branch:"master",
commitDatas:undefined,
dataCount:undefined,
limit:20,
@ -22,11 +21,23 @@ class CoderRootCommit extends Component{
}
componentDidMount=()=>{
const { branch , page , limit } = this.state;
this.Init();
}
componentDidUpdate=(prevProps)=>{
const { location } = this.props;
const prevlocation = prevProps && prevProps.location;
if (location !== prevlocation) {
this.Init();
}
}
Init =()=>{
const { branchName } = this.props.match.params;
const { page , limit } = this.state;
this.setState({
isSpining:true
})
this.getCommitList( branch , page , limit );
this.getCommitList( branchName , page , limit );
}
getCommitList=(branch , page , limit)=>{
@ -68,26 +79,28 @@ class CoderRootCommit extends Component{
// 切换分支 search:tag为根据标签搜索
changeBranch=(value)=>{
const { page , limit } = this.state;
const { getTopCount } = this.props;
this.setState({
isSpining:true,
branch:value,
})
this.getCommitList(value , page , limit);
getTopCount && getTopCount(value);
// const { page , limit } = this.state;
// const { getTopCount } = this.props;
// this.setState({
// isSpining:true,
// branch:value,
// })
// this.getCommitList(value , page , limit);
// getTopCount && getTopCount(value);
const { projectsId , owner } = this.props.match.params;
this.props.history.push(`/projects/${owner}/${projectsId}/commits/branch/${value}`);
}
ChangePage=(page)=>{
const { branch , limit } = this.state;
this.getCommitList(branch , page , limit);
const { branchName } = this.props.match.params;
const { limit } = this.state;
this.getCommitList(branchName , page , limit);
}
render(){
const { branch , commitDatas , dataCount , limit , page , isSpining } = this.state;
const { branchs , projectDetail, commit_class } = this.props;
const { projectsId , owner } = this.props.match.params;
const { commitDatas , dataCount , limit , page , isSpining } = this.state;
const { projectDetail, commit_class , defaultBranch } = this.props;
const { projectsId , owner , branchName } = this.props.match.params;
let branch = branchName || defaultBranch;
return(
<React.Fragment>
<div className={commit_class}>
@ -98,6 +111,7 @@ class CoderRootCommit extends Component{
branch={branch}
changeBranch={this.changeBranch}
owner={owner}
history={this.props.history}
></SelectBranch>
</div>
<Spin spinning={isSpining}>
@ -113,8 +127,8 @@ class CoderRootCommit extends Component{
return(
<div key={k}>
<p className="f-wrap-alignCenter">
<span className="commitKey" style={{marginLeft:0}}>{truncateCommitId(`${item.sha}`)}</span>
<span className="flex1 ml20 font-16 color-grey-3">{item.message}</span>
<Link to={`/projects/${owner}/${projectsId}/commits/${truncateCommitId(`${item.sha}`)}`} className="commitKey" style={{marginLeft:0}}>{truncateCommitId(`${item.sha}`)}</Link>
<Link to={`/projects/${owner}/${projectsId}/commits/${truncateCommitId(`${item.sha}`)}`} className="flex1 ml20 font-16 color-grey-3">{item.message}</Link>
</p>
<p className="f-wrap-alignCenter mt15">
<Link to={`/users/${item.login}`} className="show-user-link">

View File

@ -15,7 +15,6 @@ import Nodata from '../Nodata';
import axios from "axios";
/**
* address:http和SSHhttp_url(对应git地址)
* branch:当前分支
* filePath:点击目录时当前目录的路径
* subfileType:保存当前点击目录的文件类型显示目录列表时才显示新建文件如果点击的是文件就不显示新建文件按钮
* readMeContent:根目录下面的readme文件内容
@ -34,7 +33,6 @@ class CoderRootDirectory extends Component {
super(props);
this.state = {
address: "http",
branch: "master",
filePath: undefined,
subFileType: undefined,
readMeContent: undefined,
@ -79,32 +77,27 @@ class CoderRootDirectory extends Component {
Init = () => {
let { search } = this.props.history.location;
let branchName = undefined;
if (search && search.indexOf("?branch=") > -1) {
branchName = search.split("?branch=")[1];
this.setState({
branch: branchName,
});
}
if (search && search.indexOf("?url=") > -1) {
let url = search.split("?url=")[1];
this.setState({
filePath: decodeURI(url),
});
this.getFileDetail(decodeURI(url));
const { branchName } = this.props.match.params;
const { defaultBranch } = this.props;
let branch = branchName || defaultBranch;
if (search && (search.indexOf("?url=") > -1 || search.indexOf("&url=") > -1)) {
let url = search.split("url=")[1];
if(url && decodeURI(url).indexOf("&")){
url=decodeURI(url).split("&")[0];
}
this.getFileDetail(decodeURI(url),branch);
} else {
const { branch } = this.state;
this.getProjectRoot(branchName || branch);
this.getProjectRoot(branch);
}
};
// 页面地址返回到主目录
returnMain = (branch) => {
const { projectsId , owner } = this.props.match.params;
const { projectsId , owner , branchName } = this.props.match.params;
this.setState({
readOnly:true
})
this.props.history.push(`/projects/${owner}/${projectsId}`);
this.props.history.push(`/projects/${owner}/${projectsId}${branchName?`/branch/${branchName}`:""}`);
this.getProjectRoot(branch);
};
@ -182,48 +175,55 @@ class CoderRootDirectory extends Component {
// 获取子目录
getFileDetail = (path, ref) => {
const { projectsId ,owner } = this.props.match.params;
const { branch } = this.state;
this.setState({
filePath: decodeURI(path),
});
const { projectsId , owner , branchName } = this.props.match.params;
const { chooseType } = this.state;
const url = `/${owner}/${projectsId}/sub_entries.json`;
axios.get(url,{
params:{
filepath:path,
ref:ref || branch
ref:ref || branchName,
type:chooseType
}
}).then((result)=>{
let entries = result.data && result.data.entries;
this.setState({
isSpin:false
})
if( entries && entries.length > 0){
let { chooseType } = this.state;
// 当前返回的子目录只有一条数据,且这条数据返回的是文件类型
if(entries.length === 1 && entries[0].type === "file" && chooseType ==="file"){
this.setState({
fileDetail:entries,
rootList:undefined,
subFileType:false
})
if(result){
if(entries){
// 返回对象entries.type则是文件类型否则是文件夹
if(entries.type){
this.setState({
fileDetail:[entries],
rootList:undefined,
subFileType:false
})
}else{
this.setState({
fileDetail:undefined,
rootList:entries,
branchLastCommit:result.data.last_commit && result.data.last_commit.commit,
lastCommitAuthor:result.data.last_commit && (result.data.last_commit.author || (result.data.last_commit.commit && result.data.last_commit.commit.author))
})
this.renderData(entries);
}
}else{
this.setState({
fileDetail:undefined,
rootList:entries,
branchLastCommit:result.data.last_commit && result.data.last_commit.commit,
lastCommitAuthor:result.data.last_commit && (result.data.last_commit.author || (result.data.last_commit.commit && result.data.last_commit.commit.author))
rootList:undefined,
isSpin:false,
subFileType:false
})
this.renderData(entries);
}
}else{
this.setState({
fileDetail:undefined,
rootList:undefined,
isSpin:false,
subFileType:false
})
}
})
.catch((error) => {
this.setState({
isSpin:false
})
console.log(error);
});
};
@ -255,8 +255,8 @@ class CoderRootDirectory extends Component {
this.setState({
chooseType:type
})
const { projectsId, owner } = this.props.match.params;
this.props.history.push(`/projects/${owner}/${projectsId}?url=${path}`);
const { projectsId, owner , branchName } = this.props.match.params;
this.props.history.push(`/projects/${owner}/${projectsId}${branchName?`/branch/${branchName}`:""}?url=${path}`);
if(filename.substring(filename.length - 3) === ".md"){
this.setState({
md:true
@ -309,24 +309,18 @@ class CoderRootDirectory extends Component {
// 选择分支
changeBranch = (value) => {
this.setState({
branch: value,
isSpin: true,
readOnly:true
});
const { getTopCount } = this.props;
getTopCount && getTopCount(value);
let { search } = this.props.history.location;
if (search && search.indexOf("?url=") > -1) {
let url = search.split("?url=")[1];
this.setState({
filePath: decodeURI(url),
});
this.getFileDetail(decodeURI(url), value);
} else {
this.getProjectRoot(value);
const { projectsId , owner } = this.props.match.params;
let url = `/projects/${owner}/${projectsId}${value && `/branch/${value}`}`;
if (search && (search.indexOf("?url=") > -1 || search.indexOf("&url=") > -1)) {
let u = search.split("url=")[1];
if(u && decodeURI(u).indexOf("&")){
u=decodeURI(u).split("&")[0];
}
url += `?url=${u}`;
}
this.props.history.push(url);
}
// 子目录路径返回链接
@ -364,6 +358,7 @@ class CoderRootDirectory extends Component {
title = (branchLastCommit,lastCommitAuthor) => {
if (branchLastCommit) {
const { projectsId , owner } = this.props.match.params;
return (
<div className="f-wrap-alignCenter">
{lastCommitAuthor ? (
@ -389,13 +384,13 @@ class CoderRootDirectory extends Component {
) : (
""
)}
<span className="color-blue flex-1 hide-1">
<Link to={`/projects/${owner}/${projectsId}/commits/${truncateCommitId(`${branchLastCommit.sha}`)}`} className="color-blue flex-1 hide-1">
{branchLastCommit.message}
</span>
</Link>
<span>{branchLastCommit.time_from_now}</span>
<span className="commitKey">
<Link to={`/projects/${owner}/${projectsId}/commits/${truncateCommitId(`${branchLastCommit.sha}`)}`} className="commitKey">
{truncateCommitId(branchLastCommit.sha)}
</span>
</Link>
</div>
);
}else{
@ -404,31 +399,36 @@ class CoderRootDirectory extends Component {
}
render(){
const { branchLastCommit , lastCommitAuthor , rootList , branch ,filePath , fileDetail , subFileType , readMeContent, isSpin , zip_url , tar_url} = this.state;
const { isManager , isDeveloper , projectDetail } = this.props;
const { projectsId , owner } = this.props.match.params;
const { branchLastCommit , lastCommitAuthor , rootList ,filePath , fileDetail , subFileType , readMeContent, isSpin , zip_url , tar_url} = this.state;
const { isManager , isDeveloper , projectDetail , platform , defaultBranch } = this.props;
const { projectsId , owner , branchName } = this.props.match.params;
let branch = branchName || defaultBranch;
const columns = [
{
key:"name",
dataIndex: 'name',
width:"30%",
render: (text,item) => (
<a onClick={()=>this.goToSubRoot(item.path,item.type,text)} className="ml12 task-hide" style={{ display: "block", maxWidth: "345px" }}>
<i className={ item.type === "file" ? "iconfont icon-wenjia font-15 color-green-file mr5":"iconfont icon-wenjianjia1 color-green-file font-15 mr5"}></i>{text}
<i className={ item.type === "file" ? "iconfont icon-wenjia font-15 color-green-file mr5" : "iconfont icon-wenjianjia1 color-green-file font-15 mr5"}></i>{text}
</a>
),
},
{
dataIndex: "commit",
key:"message",
dataIndex: "message",
width: "60%",
render: (text, item) =>
item.commit && item.commit.message ?
<span title={item.commit.message} className="task-hide" style={{ display: "block", maxWidth: "670px" }} >
<Link to={`/projects/${owner}/${projectsId}/commits/${truncateCommitId(`${item.commit.sha}`)}`} title={item.commit.message} className="task-hide" style={{ display: "block", maxWidth: "670px" }} >
{item.commit.message}
</span>
</Link>
: ""
},
{
dataIndex: "commit",
key:"time_from_now",
dataIndex: "time_from_now",
width: "10%",
className: "edu-txt-right",
render: (text, item) =>
@ -447,13 +447,20 @@ class CoderRootDirectory extends Component {
<div className="main">
<div className="f-wrap-between mb20">
<div className="f-wrap-alignCenter">
<SelectBranch
repo_id={projectDetail && projectDetail.repo_id}
projectsId={projectsId}
branch={branch}
changeBranch={this.changeBranch}
owner={owner}
></SelectBranch>
{
platform ?
<SelectBranch
repo_id={projectDetail && projectDetail.repo_id}
projectsId={projectsId}
branch={branch}
changeBranch={this.changeBranch}
owner={owner}
history={this.props.history}
></SelectBranch>
:
<span>分支<span className="color-grey-6">master</span></span>
}
{filePath && (
<span className="ml20 font-16">
@ -480,7 +487,7 @@ class CoderRootDirectory extends Component {
)}
</div>
<div className="f-wrap-alignCenter">
{subFileType && (projectDetail && parseInt(projectDetail.type)) !== 2 && (isManager || isDeveloper) && (
{subFileType && (projectDetail && parseInt(projectDetail.type)) !== 2 && (isManager || isDeveloper) && platform && (
<div>
<span>
<Link to={`/projects/${owner}/${projectsId}/${branch}/uploadfile${urlRoot}`} >
@ -527,7 +534,6 @@ class CoderRootDirectory extends Component {
{
!rootList && !fileDetail && <Nodata _html="暂未发现当前文件!"/>
}
{/* readme.txt (isManager || isDeveloper)*/}
{ rootList && this.renderReadMeContent(readMeContent, isManager || isDeveloper)}
</div>
</Spin>

View File

@ -165,12 +165,12 @@ class CoderRootFileDetail extends Component {
isManager,
isDeveloper,
md,
currentBranch
currentBranch,
platform
} = this.props;
const { language, languages, description } = this.state;
let flag = current_user && current_user.login && (isManager || isDeveloper);
const Option = Select.Option;
return (
<div className="mb20">
<div className="grid-item branchTitle">
@ -180,7 +180,7 @@ class CoderRootFileDetail extends Component {
</span>
</div>
<p className="text-right">
{flag && (
{flag && platform && (
<div>
{readOnly ? (
<span>

View File

@ -55,12 +55,20 @@ class CoderRootIndex extends Component{
}
componentDidMount=()=>{
let { search } = this.props.history.location;
let branchName = undefined;
if (search && search.indexOf("?branch=") > -1) {
branchName = search.split("?branch=")[1];
this.Init();
}
componentDidUpdate=(prevProps)=>{
const { location } = this.props;
const prevlocation = prevProps && prevProps.location;
if (location !== prevlocation) {
this.Init();
}
this.getTopCount(branchName);
}
Init=()=>{
const { branchName } = this.props.match.params;
const { defaultBranch } = this.props;
this.getTopCount(branchName || defaultBranch);
}
getTopCount=(branch)=>{
@ -97,17 +105,23 @@ class CoderRootIndex extends Component{
(props) => (<FileNew {...this.props} {...props} {...this.state} getTopCount={this.getTopCount} />)
}
></Route>
{/* diff */}
<Route path="/projects/:owner/:projectsId/commits/branch/:branchName"
render={
(props) => (<CoderRootCommit {...this.props} {...props} {...this.state} commit_class="main" getTopCount={this.getTopCount} />)
}
></Route>
<Route path="/projects/:owner/:projectsId/commits/:sha"
render={
(props) => (<Diff {...this.props} {...props} {...this.state}/>)
}
></Route>
<Route path="/projects/:owner/:projectsId/commits"
render={
() => (<CoderRootCommit {...this.props} {...this.state} commit_class="main" getTopCount={this.getTopCount} />)
}
></Route>
{/* diff */}
<Route path="/projects/:owner/:projectsId/diff/:sha"
render={
() => (<Diff {...this.props} {...this.state}/>)
}
></Route>
<Route path="/projects/:owner/:projectsId/releases/:versionId/update"
render={
@ -125,12 +139,18 @@ class CoderRootIndex extends Component{
() => (<CoderRootVersion {...this.props} {...this.state} />)
}
></Route>
<Route path="/projects/:owner/:projectsId/tag"
render={
() => (<CoderRootTag {...this.props} {...this.state} />)
}
></Route>
<Route path="/projects/:owner/:projectsId/branch"
<Route path="/projects/:owner/:projectsId/branch/:branchName"
render={
(props) => (<CoderRootDirectory {...this.props} {...this.state} getTopCount={this.getTopCount} />)
}
></Route>
<Route path="/projects/:owner/:projectsId/branchs"
render={
() => (<CoderRootBranch {...this.props} {...this.state} />)
}

View File

@ -3,13 +3,12 @@ import axios from 'axios';
import { Spin } from 'antd';
import { truncateCommitId } from '../common/util';
import Nodata from '../Nodata';
import { Link } from 'react-router-dom'
export default (( props, { projectDetail }) => {
const [isSpin, setSpin] = useState(true);
const [data, setData] = useState(undefined);
const repo_id = projectDetail && projectDetail.repo_id;
const { projectsId , owner } = props.match.params;
useEffect(() => {
@ -49,7 +48,7 @@ export default (( props, { projectDetail }) => {
<span className="font-16">{item.name}</span>
</span>
<span className="ul_tbody_third">
<span className="commitKey" style={{ "marginLeft": 0 }}>{truncateCommitId(`${item.id}`)}</span>
<Link to={`/projects/${owner}/${projectsId}/commits/${truncateCommitId(`${item.id}`)}`} className="commitKey" style={{ "marginLeft": 0 }}>{truncateCommitId(`${item.id}`)}</Link>
</span>
<span className="ul_tbody_forth">
<a href={item.tarball_url} style={{ color: "#4CC1DA" }} className="mr30"><i className="iconfont icon-TAR font-18 mr5"></i>TAR</a>

View File

@ -140,6 +140,8 @@ function checkPathname(pathname){
name="activity"
}else if(pathname.indexOf("/setting")>-1){
name="setting"
}else if(pathname.indexOf("/devops")>-1){
name="devops"
}
}
return name;
@ -165,6 +167,12 @@ class Detail extends Component {
project: null,
firstSync:false,
secondSync:false,
open_devops:false,
// 默认分支
defaultBranch:undefined,
// 非本平台项目
platform:false
}
}
@ -186,8 +194,22 @@ class Detail extends Component {
axios.get(url).then((result) => {
if (result && result.data) {
this.setState({
project: result.data
project: result.data,
open_devops:result.data.open_devops,
platform:result.data.platform && result.data.platform !== 'educoder'
})
// 工作流:两种状态进入的链接不同
const pathname = this.props.history.location.pathname;
let p = checkPathname(pathname);
if(p==="devops"){
if(result.data.open_devops && pathname === `/projects/${owner}/${projectsId}/devops`){
this.props.history.push(`/projects/${owner}/${projectsId}/devops/list`);
}else if(result.data.open_devops===false && pathname !== `/projects/${owner}/${projectsId}/devops`){
this.props.history.push(`/projects/${owner}/${projectsId}/devops`);
}
}
if (result.data.type !== 0 && result.data.mirror_status === 1) {
console.log("--------start channel --------");
// 是镜像项目,且未完成迁移
@ -213,6 +235,13 @@ class Detail extends Component {
}
})
}
// 工作流激活后修改状态
changeOpenDevops=(flag)=>{
this.setState({
open_devops:flag
})
}
canvasChannel = () => {
const name = window.location.hostname === "localhost" ? "testforgeplus.trustie.net" : window.location.hostname;
const actioncable = require("actioncable");
@ -240,11 +269,11 @@ class Detail extends Component {
const { projectsId , owner } = this.props.match.params;
const url = `/${owner}/${projectsId}.json`;
axios.get(url).then((result) => {
if (result) {
if (result && result.data) {
this.setState({
projectDetail: result.data,
project_id: result.data.project_id,
isManager: result.data.permission && (result.data.permission === "Manager"|| result.data.permission === "Admin" || result.data.permission === "Owner"),
isManager: result.data.permission && (result.data.permission === "Manager" || result.data.permission === "Admin" || result.data.permission === "Owner"),
isReporter: result.data.permission && result.data.permission === "Reporter",
isDeveloper: result.data.permission && result.data.permission === "Developer",
http_url: result.data.clone_url,
@ -254,6 +283,7 @@ class Detail extends Component {
watchers_count: result.data.watchers_count,
praises_count: result.data.praises_count,
forked_count: result.data.forked_count,
defaultBranch:result.data.default_branch
})
}
}).catch((error) => { })
@ -261,8 +291,10 @@ class Detail extends Component {
// 关注和取消关注
focusFunc = (flag) => {
const { project_id } = this.state;
const { platform } = this.state;
if(!platform)return;
const { project_id } = this.state;
axios({
method: flag ? 'delete' : 'post',
url: `/watchers/${flag ? 'unfollow' : 'follow'}.json`,
@ -282,6 +314,8 @@ class Detail extends Component {
// 点赞和取消点赞
pariseFunc = (flag) => {
const { platform } = this.state;
if(!platform)return;
const { project_id } = this.state;
axios({
method: flag ? 'delete' : 'post',
@ -312,6 +346,8 @@ class Detail extends Component {
// fork项目
forkFunc = () => {
const { platform } = this.state;
if(!platform)return;
const { current_user } = this.props
const { projectsId , owner } = this.props.match.params;
const url = `/${owner}/${projectsId}/forks.json`;
@ -327,6 +363,8 @@ class Detail extends Component {
// 同步镜像
synchronismMirror = () => {
const { platform } = this.state;
if(!platform)return;
const { projectsId , owner } = this.props.match.params;
const url = `/${owner}/${projectsId}/sync_mirror.json`;
axios.post(url).then(result => {
@ -343,7 +381,10 @@ class Detail extends Component {
render() {
const { projectDetail, watchers_count, praises_count, forked_count, firstSync , secondSync , isManager, watched, praised, project } = this.state;
const { projectDetail, watchers_count, praises_count,
forked_count, firstSync , secondSync ,
isManager, watched, praised,
project , open_devops , platform , defaultBranch } = this.state;
const url = this.props.history.location.pathname;
const urlArr = url.split("/");
const urlFlag = (urlArr.length === 3);
@ -352,8 +393,6 @@ class Detail extends Component {
const { projectsId , owner } = this.props.match.params;
const { state } = this.props.history.location;
const { current_user } = this.props;
const checkLogin = current_user && current_user.login;
const text = (
projectDetail && projectDetail.forked_from_project_id && projectDetail.fork_info ?
@ -365,9 +404,10 @@ class Detail extends Component {
</React.Fragment> : ""
);
const common = {
getDetail: this.getDetail
getDetail: this.getDetail,
changeOpenDevops:this.changeOpenDevops,
defaultBranch
}
return (
<div>
@ -397,10 +437,11 @@ class Detail extends Component {
projectDetail.type === 2 ?
<Tooltip title={"镜像自: " + projectDetail.mirror_url} className="ml5" placement={'right'}>
<i className="iconfont icon-banbenku font-18 mt6" style={{ color: "#8D90E3" }}/>
</Tooltip>:
</Tooltip>
:
<Tooltip title={"镜像自: " + projectDetail.mirror_url} className="ml5" placement={'right'}>
<i className="iconfont icon-jingxiang font-18 color-green mt6" />
</Tooltip>
<i className="iconfont icon-jingxiang font-18 color-green mt6" />
</Tooltip>
:""
}
</span>
@ -413,30 +454,45 @@ class Detail extends Component {
<a className="synchronism ml30" onClick={this.synchronismMirror}>同步镜像</a> : ""
}
<span className="detail_tag_btn">
<a className="detail_tag_btn_name" onClick={() => this.focusFunc(watched)}>
<a className="detail_tag_btn_name" style={{cursor:platform?"pointer":"default"}} onClick={() => this.focusFunc(watched)}>
<i className={watched ? "iconfont icon-shixing color-orange font-16 mr3":"iconfont icon-kongxing color-grey-9 font-16 mr3"}></i>
<span>{watched ? '取消关注' : '关注'}</span>
</a>
<Link className="detail_tag_btn_count" style={{color:`${watched?"#2878FF":"#666"}`}} to={{ pathname: `/projects/${owner}/${projectsId}/watchers`, state }}>
{watchers_count}
</Link>
{
platform ?
<Link className="detail_tag_btn_count" style={{color:`${watched?"#2878FF":"#666"}`}} to={platform?{ pathname: `/projects/${owner}/${projectsId}/watchers`, state }:""}>
{watchers_count}
</Link>
:
<span className="detail_tag_btn_count">{watchers_count}</span>
}
</span>
<span className="detail_tag_btn">
<a className="detail_tag_btn_name" onClick={() => this.pariseFunc(praised)}>
<a className="detail_tag_btn_name" style={{cursor:platform?"pointer":"default"}} onClick={() => this.pariseFunc(praised)}>
<i className={praised ? "iconfont icon-weibiaoti105 color-orange font-14 mr3":"iconfont icon-guanzhu color-grey-9 font-14 mr3"}></i>
<span>{praised ? '取消点赞' : '点赞'}</span>
</a>
<Link className="detail_tag_btn_count" style={{color:`${praised?"#2878FF":"#666"}`}} to={{ pathname: `/projects/${owner}/${projectsId}/stargazers`, state }}>
{praises_count}
</Link>
{
platform ?
<Link className="detail_tag_btn_count" style={{color:`${praised?"#2878FF":"#666"}`}} to={{ pathname: `/projects/${owner}/${projectsId}/stargazers`, state }}>
{praises_count}
</Link>:
<span className="detail_tag_btn_count">{praises_count}</span>
}
</span>
<span className="detail_tag_btn">
<a className="detail_tag_btn_name" onClick={this.forkFunc}>
<a className="detail_tag_btn_name" style={{cursor:platform?"pointer":"default"}} onClick={this.forkFunc}>
<i className="iconfont icon-fork color-grey-9 mr3"></i>Fork
</a>
<Link className="detail_tag_btn_count" to={{ pathname: `/projects/${owner}/${projectsId}/fork_users`, state }}>
{forked_count}
</Link>
{
platform ?
<Link className="detail_tag_btn_count" to={{ pathname: `/projects/${owner}/${projectsId}/fork_users`, state }}>
{forked_count}
</Link>
:
<span className="detail_tag_btn_count">{praises_count}</span>
}
</span>
</span>
}
@ -465,7 +521,7 @@ class Detail extends Component {
</Link>
</li>
{
projectDetail && parseInt(projectDetail.type) !== 2 &&
projectDetail && parseInt(projectDetail.type) !== 2 && platform &&
<li className={pathname==="pulls" ? "active" : ""}>
<Link to={{ pathname: `/projects/${owner}/${projectsId}/pulls`, state }}>
<i className={pathname==="pulls" ? "iconfont icon-hebingqingqiu1 color-grey-3 mr5 font-14":"iconfont icon-hebingqingqiu1 color-grey-6 font-14 mr5"}></i>
@ -474,12 +530,16 @@ class Detail extends Component {
</Link>
</li>
}
{/* <li className={url.indexOf("/ops") > -1 ? "active" : ""}>
<Link to={{ pathname: `/projects/${owner}/${projectsId}/ops`, state }}>
<i className="iconfont icon-gongzuoliu font-13 mr8"></i>
{projectDetail && projectDetail.ops_count ? <span>{projectDetail.ops_count}</span> : ""}
</Link>
</li> */}
{
platform &&
<li className={pathname==="devops" ? "active" : ""}>
<Link to={{ pathname: `/projects/${owner}/${projectsId}/devops${open_devops ? `/list`:""}`, state }}>
<i className="iconfont icon-gongzuoliu font-13 mr8"></i>(beta)
{projectDetail && projectDetail.ops_count ? <span>{projectDetail.ops_count}</span> : ""}
</Link>
</li>
}
<li className={pathname==="milestones" ? "active" : ""}>
<Link to={{ pathname: `/projects/${owner}/${projectsId}/milestones`, state }}>
<i className={pathname==="milestones" ? "iconfont icon-lichengbei color-grey-3 mr5 font-14":"iconfont icon-lichengbei color-grey-6 font-14 mr5"}></i>
@ -494,7 +554,7 @@ class Detail extends Component {
</Link>
</li>
{
isManager &&
isManager && platform &&
<li className={url.indexOf("/setting") > 0 ? "active" : ""}>
<Link to={`/projects/${owner}/${projectsId}/setting`}>
<i className={url.indexOf("/setting") > 0 ? "iconfont icon-cangku color-grey-3 mr5 font-14":"iconfont icon-cangku color-grey-6 font-14 mr5"}></i>
@ -523,7 +583,7 @@ class Detail extends Component {
}
></Route>
{/* 工作流 */}
<Route path="/projects/:owner/:projectsId/ops"
<Route path="/projects/:owner/:projectsId/devops"
render={
() => (<DevIndex {...this.props} {...this.state} {...common} />)
}
@ -652,6 +712,16 @@ class Detail extends Component {
(props) => (<ForkUsers {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
<Route path="/projects/:owner/:projectsId/commits/branch/:branchName"
render={
(props) => (<CoderRootIndex {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
<Route path="/projects/:owner/:projectsId/branch/:branchName"
render={
(props) => (<CoderRootIndex {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
<Route path="/projects/:owner/:projectsId"
render={
(props) => (<CoderRootIndex {...this.props} {...props} {...this.state} {...common} />)

View File

@ -3,27 +3,53 @@ import { Link } from 'react-router-dom';
class DetailTop extends Component {
render() {
const { coderCount } = this.props;
const { coderCount , platform } = this.props;
const { projectsId, owner } = this.props.match.params;
const { pathname } = this.props.location;
return (
<p className="branch-wrapper">
<Link to={`/projects/${owner}/${projectsId}/commits`} className={pathname.indexOf("/commits") > 0 ? "active" : ""}>
<i className="iconfont icon-tijiaojilu font-20 mr3 font-bd"></i>
<span>{(coderCount && coderCount.commits_count) || 0}</span>
</Link>
<Link to={`/projects/${owner}/${projectsId}/branch`} className={pathname.indexOf("/branch") > 0 ? "active" : ""}>
<i className="iconfont icon-fenzhi1 font-18 mr3"></i>
<span>{(coderCount && coderCount.branches_count) || 0}</span>
</Link>
<Link to={`/projects/${owner}/${projectsId}/tag`} className={pathname.indexOf("/tag") > 0 ? "active" : ""}>
<i className="iconfont icon-biaoqian3 font-18 mr3"></i>
<span>{(coderCount && coderCount.tags_count) || 0}</span>
</Link>
<Link to={`/projects/${owner}/${projectsId}/releases`} className={pathname.indexOf("/releases") > 0 ? "active" : ""}>
<i className="iconfont icon-fahangban font-18 mr3"></i>
<span>{(coderCount && coderCount.version_releasesed_count) || 0}</span>
</Link>
{
platform ?
<React.Fragment>
<Link to={`/projects/${owner}/${projectsId}/commits`} className={pathname.indexOf("/commits") > 0 ? "active" : ""}>
<i className="iconfont icon-tijiaojilu font-20 mr3 font-bd"></i>
<span>{(coderCount && coderCount.commits_count) || 0}</span>
</Link>
<Link to={`/projects/${owner}/${projectsId}/branchs`} className={pathname.indexOf("/branchs") > 0 ? "active" : ""}>
<i className="iconfont icon-fenzhi1 font-18 mr3"></i>
<span>{(coderCount && coderCount.branches_count) || 0}</span>
</Link>
<Link to={`/projects/${owner}/${projectsId}/tag`} className={pathname.indexOf("/tag") > 0 ? "active" : ""}>
<i className="iconfont icon-biaoqian3 font-18 mr3"></i>
<span>{(coderCount && coderCount.tags_count) || 0}</span>
</Link>
<Link to={`/projects/${owner}/${projectsId}/releases`} className={pathname.indexOf("/releases") > 0 ? "active" : ""}>
<i className="iconfont icon-fahangban font-18 mr3"></i>
<span>{(coderCount && coderCount.version_releasesed_count) || 0}</span>
</Link>
</React.Fragment>
:
<React.Fragment>
<a href="javscript:void(0)" style={{cursor:"default"}}>
<i className="iconfont icon-tijiaojilu font-20 mr3 font-bd"></i>
<span>{(coderCount && coderCount.commits_count) || 0}</span>
</a>
<a href="javscript:void(0)" style={{cursor:"default"}}>
<i className="iconfont icon-fenzhi1 font-18 mr3"></i>
<span>{(coderCount && coderCount.branches_count) || 0}</span>
</a>
<a href="javscript:void(0)" style={{cursor:"default"}}>
<i className="iconfont icon-biaoqian3 font-18 mr3"></i>
<span>{(coderCount && coderCount.tags_count) || 0}</span>
</a>
<a href="javscript:void(0)" style={{cursor:"default"}}>
<i className="iconfont icon-fahangban font-18 mr3"></i>
<span>{(coderCount && coderCount.version_releasesed_count) || 0}</span>
</a>
</React.Fragment>
}
<a href="javscript:void(0)" style={{cursor:"default"}}>
<i className="iconfont icon-cangku font-18 mr3"></i>
仓库 <span className="ml3">{(coderCount && coderCount.size) || 0}</span>

View File

@ -2,7 +2,8 @@ import React, { useEffect, useState } from "react";
import styled from "styled-components";
import { Button ,Spin } from "antd";
import { truncateCommitId } from '../common/util';
import Nodata from '../Nodata';
import { getImageUrl } from 'educoder';
import Files from '../Merge/Files';
import User from "../Component/User";
import Keys from "../Component/Keys";
@ -11,65 +12,34 @@ import axios from "axios";
const Infos = styled.div`
border: 1px solid #dddddd;
margin-bottom:15px;
& .commitinfos {
background-color: #f1f8ff;
border-bottom: 1px solid #ddd;
padding: 20px;
}
& > .f-wrap-between {
padding: 10px 24px;
padding: 10px 20px;
}
`;
const Operation = styled.p`
border-bottom: 1px solid #eee;
padding: 12px 0px;
margin-top: 10px;
display: flex;
justify-content: space-between;
align-items: center;
`;
const FileUl = styled.ul`
padding-top: 10px;
& li {
display: flex;
justify-content: space-between;
align-items: center;
height: 20px;
line-height: 20px;
margin-bottom: 10px;
}
`;
const DetailP = styled.p`
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 20px 12px 12px;
background-color: #fafafa;
border: 1px solid #dddddd;
`;
export default ({ projectDetail, match }) => {
export default ({ match , history }) => {
const [data, setData] = useState({undefined});
const [commit, setCommit] = useState(undefined);
const [files, setFiles] = useState(undefined);
const [parents, setParents] = useState(undefined);
const [committer, setCommitter] = useState(undefined);
const [fileflag , setFileflag] = useState(false);
const [isSpin, setIsSpin] = useState(true);
const repo_id = projectDetail && projectDetail.repo_id;
const { sha } = match.params;
const { sha , projectsId, owner } = match.params;
useEffect(() => {
if (repo_id && sha) {
const url = `/repositories/${repo_id}/commits/${sha}.json`;
if (projectsId && owner && sha) {
const url = `/${owner}/${projectsId}/commits/${sha}.json`;
axios
.get(url)
.then(result => {
if (result) {
setData(result.data);
setCommit(result.data.commit);
setFiles(result.data.files);
setParents(result.data.parents);
setCommitter(result.data.committer || (result.data.commit && result.data.commit.committer));
setIsSpin(false);
@ -79,78 +49,32 @@ export default ({ projectDetail, match }) => {
console.log(error);
});
}
}, [repo_id, sha]);
const renderFiles = () => {
return (
<FileUl>
{
files && files.length > 0 && files.map((item,key)=>{
return(
<li>
<span className="mr30 task-hide flex1">
<i className="iconfont icon-wenjia color-grey-3 font-16 mr8"></i>
{item.Name}
</span>
<span>
<label className="color-green mr20">+{item.Addition}</label>
<label className="color-red">-{item.Deletion}</label>
</span>
</li>
)
})
}
</FileUl>
);
};
function changeFlag(){
let flag = !fileflag;
setFileflag(flag);
}
const filesDetail = () => {
return (
<div>
<DetailP>
<span>
<i className="iconfont icon-youjiantou mr4 font-15 color-grey-9"></i>
<span className="font-16 color-grey-3">README.md</span>
</span>
<Button>查看文件</Button>
</DetailP>
</div>
);
};
}, [projectsId , owner, sha]);
return (
<div className="main">
<Spin spinning={isSpin}>
<Infos>
<div className="commitinfos">
<p className="f-wrap-between">
<span className="font-20 color-grey-3">
{ commit && commit.title }
</span>
<Button type="primary">浏览代码</Button>
</p>
{commit && commit.message ? (
<pre className="mt10">{commit.message}</pre>
) : (
""
)}
<div className="f-wrap-between">
{commit && commit.message &&
<pre className="task-hide" style={{marginBottom:"0px",height:"28px",whiteSpace:"pre-wrap"}}>{commit.message}</pre>
}
<Button type="primary" onClick={()=>{history.push(`/projects/${owner}/${projectsId}/branch/${truncateCommitId(sha)}`)}} className="ml30">浏览代码</Button>
</div>
</div>
<div className="f-wrap-between" style={{ alignItems: "center" }}>
<ul className="df">
<User
url={(committer && committer.image_url)|| "https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3025493530,1989042357&fm=26&gp=0.jpg"}
url={(committer && getImageUrl(`images/${committer.image_url}`))|| "https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3025493530,1989042357&fm=26&gp=0.jpg"}
name={committer && committer.name}
/>
{committer && committer.time_from_now && <li className="ml20 mt2">{committer.time_from_now}</li>}
</ul>
<li className="df">
{
parents && parents.length > 0 && parents.map((item,key)=>{
return(
<Keys title="父节点" value={truncateCommitId(item.sha) } className="mr20"></Keys>
<Keys title="父节点" value={truncateCommitId(item.sha)} key={key} className="mr20"></Keys>
)
})
}
@ -158,35 +82,12 @@ export default ({ projectDetail, match }) => {
</li>
</div>
</Infos>
{
files && files.length > 0 ?
<React.Fragment>
<Operation>
<span className="files_info" onClick={changeFlag}>
<i className={fileflag ? "iconfont icon-sanjiaoxing-down mr8 color-grey-9 font-16":"iconfont icon-triangle mr8 color-grey-9 font-16"}></i>
<span className="color-grey-9">
共有<span>{files && files.length}个文件被更改</span>包括
{data && data.additions ? (
<span className="color-green">{data.additions}次插入</span>
) : (
""
)}
{data && data.additions && data.deletions ? "和" : ""}
{data && data.deletions ? (
<span className="color-red">{data.deletions}次删除</span>
) : (
""
)}
</span>
</span>
<Button>双栏查看</Button>
</Operation>
{fileflag && renderFiles()}
{filesDetail()}
</React.Fragment>
:
<Nodata _html="暂无文件修改信息!"/>
}
<Files
history={history}
data={data}
owner={owner}
projectsId={projectsId}
/>
</Spin>
</div>
);

View File

@ -277,7 +277,7 @@ class Index extends Component {
</div>
<div className="baseInfo">
<span className="look"><i className="iconfont icon-dianjiliang font-12"></i>{item.visits}</span>
<span className="type">项目类型{item.category && item.category.name}</span>
<span className="type">{item.category && item.category.name}</span>
</div>
</div>
)

View File

@ -68,7 +68,7 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
text-align: right;
}
}
}

View File

@ -20,9 +20,16 @@ class IndexItem extends Component {
projects && projects.length > 0 ? projects.map((item, key) => {
return (
<div className="p-r-Item" key={key}>
<Link to={`/users/${item.author.login}`} className="show-user-link">
<img className="p-r-photo" alt="" src={getImage(`${item.author && item.author.image_url}`)} ></img>
</Link>
{
item.platform === "educoder" ?
<a href="javascript:void(0)" style={{cursor:"default"}} className="show-user-link">
<img className="p-r-photo" alt="" src={item.author && item.author.image_url} ></img>
</a>
:
<Link to={`/users/${item.author.login}`} className="show-user-link">
<img className="p-r-photo" alt="" src={getImageUrl(`${item.author && item.author.image_url}`)} ></img>
</Link>
}
<div className="p-r-Infos">
<div className="p-r-name">
<Link to={`/projects/${item.author.login}/${item.identifier}`} className="hide-1 color-grey-3 font-18 task-hide " style={{ whiteSpace: "wrap", display: 'flex', width: 400 }}>
@ -46,16 +53,16 @@ class IndexItem extends Component {
}
</Link>
<span className="p-r-tags">
<span className="pariseTag"><img src={img_parise} alt="" className="pariseImg" /> ({item.praises_count})</span>
<span><i className="iconfont icon-fork mr3 font-16" style={{ color: "#1B8FFF" }} />fork ({item.forked_count})</span>
<span className="pariseTag"><img src={img_parise} alt="" className="pariseImg" /> {item.praises_count}</span>
<span><i className="iconfont icon-fork mr3 font-16" style={{ color: "#1B8FFF" }} />fork {item.forked_count}</span>
</span>
</div>
<p className="break_word task-hide-2 mt10" style={{ maxHeight: "44px",lineHeight:"22px" }}>{item.description}</p>
<div className="p-r-about">
<span className="p-r-detail">
<span><label>浏览量</label>{item.visits}</span>
{item.category && item.category.id && <span><label>项目类别</label>{item.category.name}</span>}
{/* <span><label>浏览量:</label>{item.visits}</span> */}
{/* {item.category && item.category.id && <span>{item.category.name}</span>} */}
{item.last_update_time ? <span ><label>更新于</label>{item.time_ago}</span> : ""}
{item.language && item.language.id ? <span className="color-grey-3">{item.language.name}</span> : ""}
</span>

View File

@ -261,7 +261,7 @@
}
.detail_tag_btn_name{
padding:0px 10px;
color: #666;
color: #666!important;
}
.detail_tag_btn_name img{
margin-right: 10px;
@ -294,6 +294,7 @@
.branch-wrapper > a >i{
color: #5091FF;
margin-right: 5px;
cursor: default;
}
.branch-wrapper a{
display: flex;
@ -411,6 +412,7 @@
padding: 13px 16px!important;
}
.commitKey{
cursor: pointer;
border:1px solid #FD7700;
background-color:#FFF3DC;
color: #FD7700!important;

View File

@ -0,0 +1,35 @@
import React from 'react';
import { AlignCenter , FlexAJ } from '../Component/layout';
import User from '../Component/User';
import { truncateCommitId } from '../common/util';
import { getImageUrl } from 'educoder';
import { Link } from 'react-router-dom';
function Commits({ commits , projectsId , owner }){
return(
<div className="pb20">
{
commits.map((item,key)=>{
return(
<div className="prCommits">
<p className="prCreate">{item.created_at}</p>
<div className="prInfo">
<FlexAJ>
<AlignCenter>
<Link to={`/projects/${owner}/${projectsId}/commits/${truncateCommitId(`${item.sha}`)}`} className="commitKey" style={{marginLeft:0}}>{truncateCommitId(`${item.sha}`)}</Link>
<p className="ml15 font-16 color-grey-3 task-hide" style={{maxWidth:"700px"}}>{item.message}</p>
</AlignCenter>
<Link to={`/projects/${owner}/${projectsId}/commits/${truncateCommitId(item.sha)}`} className="color-blue">浏览代码</Link>
</FlexAJ>
<AlignCenter className="mt15">
<User url={getImageUrl(`images/${item.committer && item.committer.image_url}`)} name={`${item.committer && item.committer.name}`}></User><span>提交于{item.time_from_now}</span>
</AlignCenter>
</div>
</div>
)
})
}
</div>
)
}
export default Commits;

86
src/forge/Merge/Files.jsx Normal file
View File

@ -0,0 +1,86 @@
import React ,{useEffect,useState } from 'react';
import { truncateCommitId } from '../common/util';
import { AlignCenter , FlexAJ } from '../Component/layout';
import { Button } from 'antd';
import './merge.css';
function Files({data,history,owner,projectsId}){
const [ files , setFiles ] = useState(data && data.files);
useEffect(()=>{
if(data){
setFiles(data.files);
}
},[data])
function showDown(flag,index,isBin){
if(!isBin){
var lists = files.concat();
lists[index].flag = !flag ? true : false;
lists.splice();
setFiles(lists);
}
}
return(
<div>
<AlignCenter className="color-grey-9 pb10" style={{borderBottom:"1px solid #eee"}}>
<i className="iconfont icon-sanjiaoxing-down mr5"></i>
<span>
共有<span className="color-grey-3"> {data && data.files_count} 个文件被更改</span>包括
{ data && data.total_addition ? <span className="color-green"> {data && data.total_addition} 次插入</span>:"" }
{ data && data.total_addition && data.total_deletion ? " 和 ":""}
{ data && data.total_deletion ? <span className="color-red"> {data && data.total_deletion} 次删除</span>:""}
</span>
</AlignCenter>
{
files && files.length>0 &&
<div>
{
files.map((item,key)=>{
return(
<div className="files" key={key}>
<FlexAJ className="filesInfo" style={{cursor:item.isBin ? "default":"pointer"}} onClick={()=>showDown(item.flag,key,item.isBin)}>
<AlignCenter>
{!item.isBin ? <i className={!item.flag?"iconfont icon-xiajiantou font-16 mr15 color-grey-9":"iconfont icon-youjiantou font-16 mr15 color-grey-9"}></i>:""}
<i className="iconfont icon-wenjia font-16 mr8 color-grey-9"></i>
<span>{item.name}</span>
</AlignCenter>
<span>
<Button className="mr20" onClick={()=>{history.push(`/projects/${owner}/${projectsId}${item.sha ? `/branch/${truncateCommitId(item.sha)}?`:"?"}url=${item.name}`)}}>查看文件</Button>
<span className="color-green">+{item.addition}</span>
<span className="color-red ml20">-{item.deletion}</span>
</span>
</FlexAJ>
{
item.sections && item.sections.length >= 1 && !item.flag &&
<div className="filesContent">
{
item.sections.map((i,k)=>{
return(
i.lines && i.lines.length>0 && i.lines.map((item,key)=>{
return(
<div key={k+key} className={(item.type === 2) ? "linesContent add" : item.type === 3 ? "linesContent reduce": item.type===4?"linesContent translate":"linesContent"}>
<span className="lines">
<span>{item.leftIdx && item.leftIdx !=="0" ? item.leftIdx :"" }</span>
<span>{item.rightIdx && item.rightIdx !=="0" ? item.rightIdx :"" }</span>
</span>
<p>{item.content}</p>
</div>
)
})
)
})
}
</div>
}
</div>
)
})
}
</div>
}
</div>
)
}
export default Files;

View File

@ -214,7 +214,7 @@ class MergeDetail extends Component {
)
} else {
return (
<NoneData _html="暂时还没有相关数据!" />
<NoneData _html="暂时还没有相关数据!" />
)
}
}

View File

@ -98,7 +98,7 @@ class MergeItem extends Component {
<span className="ml15">
<Tag className="pr-branch-tag">
<Link
to={`/projects/${item.is_original ? item.fork_project_user : owner}/${ item.is_original ? item.fork_project_identifier : projectsId }?branch=${item.pull_request_head}`}
to={`/projects/${item.is_original ? item.fork_project_user : owner}/${ item.is_original ? item.fork_project_identifier : projectsId }/branch/${item.pull_request_head}`}
className="maxW200px hide-1 ver-middle"
>
{item.is_original
@ -116,7 +116,7 @@ class MergeItem extends Component {
</span>
<Tag className="pr-branch-tag">
<Link
to={`/projects/${owner}/${projectsId}?branch=${item.pull_request_base}`}
to={`/projects/${owner}/${projectsId}/branch/${item.pull_request_base}`}
className="maxW200px hide-1 ver-middle"
>
{/* {item.is_fork ? item.pull_request_base : `${item.author_name}:${item.pull_request_base}`} */}

View File

@ -5,6 +5,7 @@ import axios from 'axios';
import{ Form , Table , Spin } from 'antd'
import { getImageUrl } from 'educoder';
import {Link} from "react-router-dom";
import { truncateCommitId} from '../common/util';
class MergeSubmit extends Component{
constructor(props){
super(props);
@ -36,7 +37,6 @@ class MergeSubmit extends Component{
}
getDetail=()=>{
const { projectsId , mergeId, owner} = this.props.match.params;
const url = `/projects/${owner}/${projectsId}/pull_requests/${mergeId}.json`;
axios.get(url).then((result)=>{
@ -83,8 +83,16 @@ class MergeSubmit extends Component{
}).catch((error)=>{console.log(error)})
}
title =()=>{
return(
<div className="f-wrap-between" style={{alignItems:"center"}}>
<span className="font-16">提交列表</span>
</div>
)
}
render(){
const { projectsId , owner } = this.props.match.params;
const { titledata } = this.state;
const columns=[{
title:"作者",
@ -102,7 +110,7 @@ class MergeSubmit extends Component{
title:"SHA",
dataIndex: 'sha',
render: (text) => (
<span className="commitKey">{text}</span>
<Link to={`/projects/${owner}/${projectsId}/commits/${truncateCommitId(`${text}`)}`} className="commitKey">{text}</Link>
)
},{
title:"备注",
@ -118,14 +126,6 @@ class MergeSubmit extends Component{
<span>{text}</span>
)
}]
const title =()=>{
return(
<div className="f-wrap-between" style={{alignItems:"center"}}>
<span className="font-16">提交列表</span>
</div>
)
}
return(
<Spin spinning={this.state.isSpin}>
@ -136,11 +136,9 @@ class MergeSubmit extends Component{
showHeader={false}
size="small"
pagination={false}
title={() => title()}
title={() => this.title()}
/>
</Spin>
// </div>
// </div>
)
}
}

View File

@ -1,6 +1,7 @@
import React, { Component } from "react";
import { Tabs } from 'antd';
import { Link } from "react-router-dom";
import { AlignCenter } from '../Component/layout';
import axios from "axios";
import { getImageUrl } from "educoder";
import {
@ -39,6 +40,8 @@ class MessageCount extends Component {
SpinMerge: false,
edit_spin: false,
pr_status: undefined,
copyVisible:false,
};
}
@ -47,7 +50,18 @@ class MessageCount extends Component {
SpinFlag: true,
});
this.getDetail();
// this.clickBody();
};
clickBody=()=>{
document.body.addEventListener('click', e => {
let name = e.target.className;
if(name.indexOf("notHide")>-1 || name.indexOf("ant-tabs-tab")>-1 || name==="ant-tabs-nav-scroll"){return;}
this.setState({
copyVisible:false
})
})
}
getDetail = () => {
const { projectsId, mergeId, owner } = this.props.match.params;
@ -59,7 +73,7 @@ class MessageCount extends Component {
this.setState({
data: result.data,
SpinFlag: false,
pr_status: result.data.pull_request.status,
pr_status: result.data && result.data.pull_request && result.data.pull_request.status,
});
} else {
this.setState({ SpinFlag: false });
@ -198,6 +212,33 @@ class MessageCount extends Component {
return <RenderHtml className="break_word_comments imageLayerParent" value={v} />;
};
setCopyVisible=(e)=>{
e.stopPropagation();
this.setState({
copyVisible:true
})
}
copyItem =()=>{
return(
<div className="copyTab notHide">
<Tabs defaultActiveKey="1" className="notHide" animated={false} size={"small"}>
<Tabs.TabPane key="1" tab={<span className="notHide">HTTPS</span>}>{this.returnCopyUrl("https://gitee.com/44886/polhttp.git")}</Tabs.TabPane>
<Tabs.TabPane key="2" tab={<span className="notHide">SSH</span>}>{this.returnCopyUrl("https://gitee.com/44886/polssh.git")}</Tabs.TabPane>
</Tabs>
</div>
)
}
returnCopyUrl=(url)=>{
return(
<div className="df notHide">
<Input value={url} className="notHide" disabled={true}/>
<Button type="primary" ghost className="ml15 notHide">复制</Button>
</div>
)
}
render() {
const { projectsId, mergeId , owner } = this.props.match.params;
@ -208,6 +249,7 @@ class MessageCount extends Component {
isSpin,
ismesrge,
SpinFlag,
copyVisible
} = this.state;
const { current_user, projectDetail } = this.props;
const menu = (
@ -258,7 +300,7 @@ class MessageCount extends Component {
<div className="mt15">
<Tag className="pr-branch-tag">
<Link
to={`/projects/${owner}/${data.pull_request.is_original?data.pull_request.fork_project_id:projectsId}?branch=${data.pull_request.head}`}
to={`/projects/${owner}/${data.pull_request.is_original?data.pull_request.fork_project_id:projectsId}/branch/${data.pull_request.head}`}
className="ver-middle"
>
{data.pull_request.is_original ? data.pull_request.fork_project_user : data.issue.project_author_name}:{data.pull_request.head}
@ -273,7 +315,7 @@ class MessageCount extends Component {
</span>
<Tag className="pr-branch-tag">
<Link
to={`/projects/${owner}/${projectsId}?branch=${data.pull_request.base}`}
to={`/projects/${owner}/${projectsId}/branch/${data.pull_request.base}`}
className="ver-middle"
>
{/* {data.pull_request.is_fork ? data.pull_request.base : `${data.pull_request.pull_request_user}:${data.pull_request.base}`} */}
@ -352,13 +394,20 @@ class MessageCount extends Component {
</div>
</div>
<div className="ml10">
<div className="mt15 text-right">
<div className="mt15 text-right" style={{display:"flex",justifyContent:"flex-end"}}>
{/* <span className="composeButton">
<Dropdown overlay={this.copyItem()} visible={copyVisible} onClick={(e)=>this.setCopyVisible(e)}>
<span>复制</span>
</Dropdown>
<span>下载为<i className="iconfont icon-sanjiaoxing-down color-blue"></i></span>
</span> */}
{current_user && projectDetail &&
pr_status !== 2 &&
pr_status === 0 &&
projectDetail.permission !=="Reporter" && (
<Button
type="success"
type="green"
ghost
className="ml20"
onClick={()=>{this.props.history.push(`/projects/${owner}/${projectsId}/pulls/${mergeId}/UpdateMerge`);}}
>
编辑
@ -369,7 +418,7 @@ class MessageCount extends Component {
type="danger"
ghost
onClick={() => this.closedetail()}
className="ml15"
className="ml20"
loading={isSpin}
>
拒绝
@ -471,14 +520,12 @@ class MessageCount extends Component {
</div>
</div>
<div className=" main">
<MergeFooter
footer_type="show"
order_id={data && data.issue.id}
{...this.props}
{...this.state}
></MergeFooter>
</div>
<MergeFooter
footer_type={true}
order_id={data && data.issue.id}
{...this.props}
{...this.state}
></MergeFooter>
</div>
) : (
""

View File

@ -1,5 +1,5 @@
import React, { Component } from "react";
import { Input, Select, Button, Spin, Alert } from "antd";
import { Input, Select , Spin, Alert } from "antd";
import axios from "axios";
import "../Order/order.css";
import "./merge.css";
@ -24,21 +24,42 @@ class NewMerge extends Component {
merge_head: false, // 是否向fork后的源项目发起合并请求
default_message: "必须选择不同的分支",
project_id: undefined, // 当前项目的id也即开始发送合并请求的源项目id
merge_project_user: undefined
merge_project_user: undefined,
oldProject:undefined,//保存选择目标分支、跳转页面后会刷新的project
comparesData:undefined,//提交和文件的内容保存compare接口返回的数据
};
}
componentDidMount = () => {
const { projectsId } = this.props.match.params;
this.getmergelist(projectsId);
// 监听回退事件
if (window.history && window.history.pushState) {
window.addEventListener('popstate', this.handleBack, false);
}
};
//获取新建分枝数据
componentDidUpdate=(preProps)=>{
const { project } = this.props;
let oldProject = preProps.project;
if(project && oldProject && (oldProject.id !== project.id)){
this.compareProject(this.state.id,"master","master");
}
}
// 页面销毁取消监听
componentWillUnmount () {
window.removeEventListener('popstate', this.handleBack, false);
};
handleBack=()=>{
const { projectsId } = this.props.match.params;
this.getmergelist(projectsId);
}
//获取新建分支数据
getmergelist = (projectsId) => {
this.setState({isSpin: true})
const { owner } = this.props.match.params;
const url = `/${owner}/${projectsId}/pulls/new.json`;
axios
.get(url)
@ -56,6 +77,7 @@ class NewMerge extends Component {
this.set_default_pull(result.data.branches);
this.set_default_merge(result.data.merge_projects);
}
this.compareProject(result.data.id,"master","master");
this.setState({isSpin: false})
})
.catch((error) => {
@ -64,6 +86,32 @@ class NewMerge extends Component {
});
};
// 获取compare接口
compareProject=(baseid , localBranch , mergeBranch)=>{
const { project } = this.props;
const { owner , projectsId } = this.props.match.params;
let url = `/${owner}/${projectsId}/compare`;
if(project){
if(baseid === project.id){
this.setState({
oldProject:project
})
url += `/${localBranch}...${mergeBranch}.json`;
}else{
const { oldProject } = this.state;
const { author , identifier } =oldProject;
url += `/${mergeBranch}...${author && author.login}/${identifier}:${localBranch}.json`;
}
axios.get(url).then(result=>{
if(result){
this.setState({
comparesData:result.data
})
}
}).catch(error=>{})
}
}
set_default_pull = (branches) => {
if(branches && branches.length>0){
let default_pull = branches.filter((e) => e.name === "master")
@ -117,12 +165,16 @@ class NewMerge extends Component {
selectBrach = (type, value) => {
this.state[type] = value;
this.ischeckmerge();
let { id ,merge , pull } = this.state;
if(type==="pull"){
this.compareProject(id,value,merge);
}else{
this.compareProject(id,pull,value);
}
};
selectProjectName = (value) => {
console.log("value",value)
const { project_id, projects_names,id } = this.state;
const { owner } = this.props.match.params;
const { projects_names,id } = this.state;
let arr = projects_names && projects_names.filter(item=>item.id===value);
let identifier = arr && arr[0].project_id;
let login = arr && arr[0].project_user_login;
@ -138,7 +190,6 @@ class NewMerge extends Component {
})
this.props.history.push(`/projects/${login}/${identifier}/pulls/new`);
this.newMergelist(login,identifier);
};
//判断2分支是否可以合并
@ -146,7 +197,7 @@ class NewMerge extends Component {
ischeckmerge = () => {
this.setState({ isSpin: true });
const { projectsId , owner } = this.props.match.params;
const { pull, merge, project_id, merge_head, id } = this.state;
const { pull, merge , merge_head, id } = this.state;
const url = `/${owner}/${projectsId}/pulls/check_can_merge.json`;
axios.post(url, {
head: pull,
@ -194,9 +245,9 @@ class NewMerge extends Component {
show_message,
default_message,
merge_head,
projects_names,
projects_names,id,comparesData
} = this.state;
const { projectsId } = this.props.match.params;
const renderBrances = (list, type) => {
if (list && list.length > 0) {
return list.map((item, key) => {
@ -228,29 +279,26 @@ class NewMerge extends Component {
return <div dangerouslySetInnerHTML={{ __html: html }}></div>;
};
let { project } = this.props;
console.log("222 ",project);
console.log("222111 ",projects_names);
return (
<div>
<div className="main">
<Spin spinning={isSpin}>
<div className="merge-header width100 inline-block">
<div className="width45 pull-left">
<div className="width40 pull-left">
<div className="color-grey-3 mb10 fwb">源分支:</div>
<Input.Group compact className="display-flex">
<Select
defaultValue={project && project.id}
class=" maxW50 hide-1 task-hide"
value={id}
className="hide-1 task-hide flex1"
disabled
style={{maxWidth:"200px"}}
>
{renderProjectNames(projects_names)}
</Select>
<Select
defaultValue={pull}
value={pull}
onSelect={(e) => this.selectBrach("pull", e)}
showSearch
className="minW50 merge-flex1"
className="merge-flex1 flex1"
>
{renderBrances(branches, false)}
</Select>
@ -261,24 +309,22 @@ class NewMerge extends Component {
className={"iconfont icon-youjiang color-grey-c font-32"}
></i>
</div>
<div className="width45 pull-left">
<div className="width40 pull-left">
<div>
<div className="color-grey-3 mb10 fwb">目标分支:</div>
<Input.Group compact className="display-flex">
<Select
defaultValue={project && project.id}
class=" maxW50 hide-1 task-hide"
value={project && project.id}
className="hide-1 task-hide flex1"
onSelect={(e) => this.selectProjectName(e)}
style={{maxWidth:"200px"}}
>
{renderProjectNames(merge_projects)}
</Select>
<Select
defaultValue={merge}
value={merge}
onSelect={(e) => this.selectBrach("merge", e)}
showSearch
className="minW50 merge-flex1"
className="merge-flex1 flex1"
>
{renderBrances(merge_branches, merge_head)}
</Select>
@ -297,10 +343,20 @@ class NewMerge extends Component {
data={data}
merge={merge}
pull={pull}
files_count={comparesData && comparesData.diff && comparesData.diff.files_count}
commits_count={comparesData && comparesData.commits_count}
></MergeForm>
)}
</Spin>
</div>
<MergeFooter
order_id={data && data.issue && data.issue.id}
{...this.props}
{...this.state}
merge={merge}
pull={pull}
comparesData={comparesData}
></MergeFooter>
</div>
);
}

View File

@ -37,11 +37,15 @@ class UpdateMerge extends Component {
merge: result.data.base,
});
} else {
this.setState({ isSpin: false });
this.setState({
isSpin: false,
});
}
})
.catch((error) => {
this.setState({ isSpin: false });
this.setState({
isSpin: false,
});
console.log(error);
});
};
@ -52,6 +56,7 @@ class UpdateMerge extends Component {
<div>
<div className="main">
<Spin spinning={isSpin}>
{" "}
{data ? (
<div>
<div className="merge-header width100 inline-block">
@ -66,17 +71,17 @@ class UpdateMerge extends Component {
defaultValue={data.is_original ? `${data.fork_project_user}:${pull}` : `${pull}`}
className="minW50 merge-flex1"
disabled
></Select>
</Input.Group>
</div>
></Select>{" "}
</Input.Group>{" "}
</div>{" "}
<div className="width10 pull-left text-center mt25">
<i
className={"iconfont icon-youjiang color-grey-c font-32"}
></i>
</div>
></i>{" "}
</div>{" "}
<div className="width45 pull-left">
<div>
<div className="color-grey-3 mb10 fwb">目标分支:</div>
<div className="color-grey-3 mb10 fwb"> 目标分支 : </div>{" "}
<Input.Group compact className="display-flex">
<Button className="merge-header-button maxW50 hide-1 task-hide">
{`${data.project_author}/${data.project_name}`}
@ -85,19 +90,18 @@ class UpdateMerge extends Component {
defaultValue={data.is_original ? `${data.project_login}:${merge}` : `${merge}`}
className="minW50 merge-flex1"
disabled
></Select>
</Input.Group>
</div>
</div>
></Select>{" "}
</Input.Group>{" "}
</div>{" "}
</div>{" "}
</div>
<MergeForm
{...this.props}
merge_type="edit"
data={data}
merge={merge}
pull={pull}
></MergeForm>
></MergeForm>{" "}
</div>
) : (
""

View File

@ -40,4 +40,161 @@ form .ant-cascader-picker, form .ant-select {
}
.merge-header-button{
background:rgba(241,248,255,1);
}
.width70{
width:70%;
}
.width30{
width:30%;
}
.width40{
width:40%;
}
.display-flex{
display: flex!important;
}
.w120{
width: 120px;
}
.w240{
width: 240px;
}
.custom-commit-tabs .ant-tabs-nav .ant-tabs-tab{
padding:14px 0px!important;
}
.custom-commit-tabs .ant-tabs-bar{
border-bottom: none;
}
.custom-commit-tabs .ant-tabs-nav .ant-tabs-tab-active .tabNum{
background-color: #EBF4FE;
}
.custom-commit-tabs .ant-tabs-ink-bar{
width: 34px!important;
}
.tabNum{
display: inline-block;
margin-left:8px ;
border-radius: 10px;
height: 18px;
line-height: 18px;
background-color: #eee;
padding:0px 9px;
font-size: 12px;
}
/* 复制-下载为,组合按钮 */
.composeButton{
display: flex;
border:1px solid #5091FF;
border-radius: 5px;
height: 34px;
line-height: 34px;
}
.composeButton > span{
display: block;
padding:0px 18px;
color: #5091FF;
display: flex;
align-items: center;
border-radius: 5px;
cursor: pointer;
}
.composeButton > span:hover{
background-color: #F1F8FF;
}
.composeButton > span:first-child{
border-radius: 5px 0px 0px 5px;
border-right: 1px solid #5091FF;
}
.ant-btn{
border-radius: 5px;
}
/* 绿色按钮-type="green" */
.ant-btn.ant-btn-green{
border:1px solid #28BD6C;
color: #28BD6C;
}
.copyTab{
min-width: 370px;
padding:0px 18px 22px 18px;
box-shadow: 0px 2px 10px rgba(0,0,0,0.2);
background-color: #fff;
}
.copyTab .ant-tabs-tab{
padding:12px 0px!important;
}
/* pr--提交tab页面 */
.prCommits{
border:1px solid #ddd;
border-top: none;
}
.prCommits:first-child{
border-top: 1px solid #ddd;
}
.prCommits .prCreate{
height: 50px;
line-height: 50px;
padding:0px 25px;
background-color:#fafafa;
color:#333;
font-size: 16px;
border-bottom: 1px solid #ddd;
}
.prCommits .prInfo{
padding:20px 25px;
}
/* pr-文件修改信息页面 */
.files{
border:1px solid #ddd;
margin-top: 15px;
border-radius: 2px;
}
.filesInfo{
padding:10px 15px;
background-color: #fafafa;
}
.filesContent{
border-top: 1px solid #ddd;
}
.linesContent{
display: flex;
min-height: 30px;
line-height: 30px;
color: #333;
flex-wrap: wrap;
}
.linesContent > p{
flex:1;
}
.linesContent .lines{
display: flex;
border-right: 1px solid #ddd;
width: 100px;
padding: 0px 8px;
box-sizing: border-box;
margin-right: 8px;
}
.linesContent .lines > span:first-child{
margin-right: 0px;
}
.linesContent .lines > span{
width: 50%;
text-align: right;
display: block;
color: #888;
}
.linesContent.translate{
background-color:#F1F8FF;
}
.linesContent.translate .lines > span{
border-right: 1px solid transparent;
}
.linesContent.reduce{
background-color:rgba(247, 48, 48, 0.15);;
}
.linesContent.add{
background: rgba(48, 232, 132, 0.15);
}

View File

@ -423,7 +423,7 @@ class merge extends Component {
</Spin>
</div>
) : (
<NoneData _html="暂时还没有相关数据!" projectsId={projectsId} owner={owner} />
<NoneData _html="暂时还没有相关数据!" projectsId={projectsId} owner={owner} />
)}
</div>
);

View File

@ -1,45 +1,169 @@
import React, { Component } from "react";
import { Tabs, Empty } from "antd";
import { Tabs, Spin } from "antd";
import "../Order/order.css";
import "./merge.css";
import CodesCommit from "../Main/CoderRootCommit";
import Commits from "./Commits";
import Comments from "../comments/comments";
import Files from "./Files";
import axios from 'axios';
const { TabPane } = Tabs;
class MergeFooter extends Component {
constructor(props) {
constructor(props){
super(props);
this.state = {};
this.state={
pageData:undefined,
commitsData:undefined,
filesData:undefined,
isSpin:false,
activeKey:"1",
commitCount:0,
filesCount:0
}
}
componentDidMount=()=>{
const { footer_type ,data } = this.props;
if(footer_type){
const { projectsId , owner , mergeId } = this.props.match.params;
this.getCommit(owner,projectsId,mergeId);
this.getFile(owner,projectsId,mergeId);
}
this.setState({
activeKey:footer_type ? "1" : "2",
commitCount:data && data.commits_count,
filesCount:data && data.files_count
})
}
componentDidUpdate=(prevProps)=>{
const { comparesData } = this.props;
const { footer_type } = this.props;
if(footer_type){
const { data } = this.props;
if(data !== prevProps.data){
this.setState({
commitCount:data && data.commits_count,
filesCount:data && data.files_count
})
}
}
if(comparesData !== prevProps.comparesData){
this.setState({
activeKey:footer_type ? "1" : "2"
})
this.changeTab(footer_type ? "1" : "2");
}
}
changeTab=(index)=>{
this.setState({
isSpin:true
})
this.setState({
activeKey:index
})
const { footer_type , comparesData } = this.props;
const { projectsId , owner , mergeId } = this.props.match.params;
if(footer_type){
if(index === "2"){
this.getCommit(owner,projectsId,mergeId);
}else if(index === "3"){
this.getFile(owner,projectsId,mergeId);
}else{
this.setState({
isSpin:false
})
}
}else{
this.setState({
commitsData:comparesData.commits,
filesData:comparesData.diff,
commitCount:comparesData.commits_count,
filesCount:comparesData.diff && comparesData.diff.files_count,
isSpin:false
})
}
}
getCommit =(owner,projectsId,mergeId)=>{
const url = `/${owner}/${projectsId}/pulls/${mergeId}/commits.json`;
axios.get(url).then(result=>{
if(result){
this.setState({
commitsData:result.data.commits,
isSpin:false,
commitCount:result.data.commits_count
})
}
}).catch(error=>{})
}
getFile =(owner,projectsId,mergeId)=>{
const url = `/${owner}/${projectsId}/pulls/${mergeId}/files.json`;
axios.get(url).then(result=>{
if(result){
this.setState({
filesData:result.data,
isSpin:false,
filesCount:result.data.files_count,
})
}
}).catch(error=>{})
}
render() {
const { footer_type, order_id } = this.props;
const { projectsId , owner } = this.props.match.params;
const { footer_type, order_id, data , comparesData } = this.props;
let { isSpin , activeKey , filesCount, commitCount , filesData , commitsData } = this.state;
return (
<div>
<Tabs
defaultActiveKey={footer_type === "show" ? "1" : "2"}
className="custom-commit-tabs"
>
{
footer_type === "show" &&
<TabPane tab={<span className="ml-3 font-16">评论</span>} key="1">
<Comments
order_id={order_id}
showNotification={this.props.showNotification}
only_show_content={true}
{...this.props}
/>
</TabPane>
}
{/* <TabPane tab={<span className="ml-3 font-16"></span>} key="2">
<CodesCommit {...this.props} main_class="pd10"></CodesCommit>
</TabPane> */}
{/* <TabPane tab={<span className="ml-3 font-16"></span>} key="3">
<Empty />
</TabPane> */}
</Tabs>
!footer_type && !comparesData || (comparesData && ((comparesData.commits && comparesData.commits.length===0)||(comparesData && !comparesData.diff)) )?"":
<div className="main" style={{paddingTop:"0px"}}>
<Spin spinning={isSpin}>
<Tabs
activeKey={activeKey}
className="custom-commit-tabs"
animated={false}
onChange={this.changeTab}
>
{
footer_type &&
<TabPane
tab={
<span><span className="font-16">评论</span>
{data && parseInt(data.comments_count) > 0 && <span className="tabNum">{data.comments_count}</span>}
</span>
} key="1">
<Comments
order_id={order_id}
showNotification={this.props.showNotification}
only_show_content={true}
{...this.props}
/>
</TabPane>
}
{
commitsData && commitsData.length > 0 &&
<TabPane tab={<span><span className="font-16">提交</span>
{commitCount > 0 && <span className="tabNum">{commitCount}</span>}
</span>} key="2">
<Commits {...this.props} commits={commitsData} projectsId={projectsId} owner={owner}></Commits>
</TabPane>
}
{
filesData && filesData.files && filesData.files.length>0 &&
<TabPane tab={
<span><span className="font-16">文件</span>
{filesCount > 0 && <span className="tabNum">{filesCount}</span>}
</span>
} key="3">
<Files {...this.props} data={filesData} projectsId={projectsId} owner={owner}/>
</TabPane>
}
</Tabs>
</Spin>
</div>
);
}

View File

@ -138,7 +138,7 @@ class MergeForm extends Component {
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
const { projectsId, mergeId , owner } = this.props.match.params;
const { merge, pull, merge_type, data } = this.props;
const { merge, pull, merge_type, data , commits_count , files_count } = this.props;
if (values.issue_tag_ids && values.issue_tag_ids.length > 0) {
values.issue_tag_ids = [parseInt(values.issue_tag_ids)];
} else {
@ -154,7 +154,9 @@ class MergeForm extends Component {
base: merge,
is_original: data && data.is_original,
fork_project_id: data && data.fork_project_id,
merge_user_login: data && data.merge_user_login
merge_user_login: data && data.merge_user_login,
files_count,
commits_count
})
.then((result) => {
if (result) {
@ -221,7 +223,7 @@ class MergeForm extends Component {
};
render() {
const { merge_type, data } = this.props;
const { merge_type } = this.props;
const { getFieldDecorator } = this.props.form;
const { projectsId, mergeId ,owner } = this.props.match.params;
const {

View File

@ -51,9 +51,7 @@ class Index extends Component {
<div className="grid-item">
<div className="setInputAddon">
<Input
addonBefore={`/${
projectDetail && projectDetail.identifier
}${urlroot}/`}
addonBefore={`/${ projectDetail && projectDetail.identifier }${urlroot}/`}
value={filename}
onChange={this.changeFileName}
placeholder="命名文件..."

View File

@ -54,9 +54,7 @@ class UserSubmitComponent extends Component {
const { getTopCount } = this.props;
getTopCount && getTopCount(values.branchname);
}
let url = values.branchname
? `/projects/${owner}/${projectsId}?branch=${values.branchname}`
: `/projects/${owner}/${projectsId}`;
let url = `/projects/${owner}/${projectsId}${values.branchname ? `/branch/${values.branchname}`: (branch ? `/branch/${branch}` : "")}`;
this.props.history.push(url);
}
})
@ -73,7 +71,7 @@ class UserSubmitComponent extends Component {
// 确认修改文件
UpdateFile = () => {
this.setState({ isSpin: true });
const { branch, detail, content, filepath } = this.props;
const { branch, detail, content , currentBranch } = this.props;
const { projectsId , owner } = this.props.match.params;
const { submitType } = this.state;
const url = `/${owner}/${projectsId}/update_file.json`;
@ -82,7 +80,7 @@ class UserSubmitComponent extends Component {
axios
.put(url, {
filepath: detail.path,
branch: branch,
branch: submitType === "1" ? undefined : (currentBranch || branch),
new_branch: submitType === "1" ? values.branchname : undefined,
content: content,
sha: detail.sha,
@ -91,12 +89,10 @@ class UserSubmitComponent extends Component {
.then((result) => {
this.setState({ isSpin: false });
if (result.data && result.data.status === 1) {
let url = values.branchname
? `/projects/${owner}/${projectsId}?branch=${values.branchname}`
: `/projects/${owner}/${projectsId}`;
debugger;
let url = `/projects/${owner}/${projectsId}${(values.branchname ? `/branch/${values.branchname}` : ((currentBranch || branch) ? `/branch/${currentBranch || branch}`:""))}`;
this.props.history.push(url);
this.props.showNotification("修改成功!");
this.props.showNotification("文件修改成功!");
}
})
.catch((error) => {

View File

@ -309,8 +309,10 @@ class Detail extends Component {
<div className="list-left list-left-padding">
<div className="list-right-item-padding background-f boder-4">
<p className="grid-item-left pb15">
<span className="issue_detail_info">分支:</span>
<span>{data && data.branch_name ? data.branch_name : "--"}</span>
<span className="issue_detail_info">负责人:</span>
<span>
{data && data.assign_user_name ? data.assign_user_name : "--"}
</span>
</p>
<p className="grid-item-left pb15">
<span className="issue_detail_info">标签:</span>
@ -344,12 +346,6 @@ class Detail extends Component {
<span className="issue_detail_info">分类:</span>
<span>{data && data.tracker ? data.tracker : "--"}</span>
</p>
<p className="grid-item-left pb15">
<span className="issue_detail_info">负责人:</span>
<span>
{data && data.assign_user_name ? data.assign_user_name : "--"}
</span>
</p>
<p className="grid-item-left pb15">
<span className="issue_detail_info">开始日期:</span>
<span>{data && data.start_date ? data.start_date : "--"}</span>
@ -363,6 +359,10 @@ class Detail extends Component {
<span className="issue_detail_info">完成度:</span>
<span>{data && data.done_ratio ? data.done_ratio : "--"}</span>
</p>
<p className="grid-item-left pb15">
<span className="issue_detail_info">分支:</span>
<span>{data && data.branch_name ? data.branch_name : "--"}</span>
</p>
</div>
</div>
</div>

View File

@ -251,7 +251,7 @@ class Milepost extends Component {
})
}
</div>
: <NoneData _html="暂时还没有相关数据!" />
: <NoneData _html="暂时还没有相关数据!" />
}
{
data && data.versions_count > limit ?

View File

@ -271,7 +271,7 @@ class MilepostDetail extends Component {
<div className="setItemStyle">
{
search_count === 0 ?
<NoneData _html="暂时还没有相关数据!" />
<NoneData _html="暂时还没有相关数据!" />
:
issues && issues.length>0 && issues.map((item,key)=>{
return(

View File

@ -1,9 +1,7 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { Popconfirm, Tag } from 'antd'
import { Popconfirm } from 'antd'
import { TagInfo } from '../Utils/TagColor';
class OrderItem extends Component {
constructor(props) {
super(props);
@ -54,7 +52,7 @@ class OrderItem extends Component {
{current_user && current_user.login && checkbox}
<div className="flex-1">
<p className="mb10 df" style={{alignItems:"center"}}>
<Link to={`/projects/${owner}/${projectsId}/issues/${item.id}/detail`} target="_blank" title={item.name} className="hide-1 font-16 color-grey-3 lineh-30 mr10" style={{maxWidth:"300px"}}>{item.name}</Link>
<Link to={`/projects/${owner}/${projectsId}/issues/${item.id}/detail`} target="_blank" title={item.name} className="hide-1 font-16 color-grey-3 lineh-30 mr10" style={{maxWidth:"370px"}}>{item.name}</Link>
{TagInfo(item.priority,"mr10")}
</p>
<p className="color-grey-6 font-12">

View File

@ -362,7 +362,7 @@ class Tags extends Component {
</div>
</div>
:
<NoneData _html="暂时还没有相关数据!" />
<NoneData _html="暂时还没有相关数据!" />
}
{
data && data.issue_tags_count > limit ?

View File

@ -239,6 +239,8 @@ class order extends Component {
this.setState({
search: value,
isSpin: true,
checkedValue:[],
all:undefined
});
const { status_type } = this.state;
@ -258,7 +260,10 @@ class order extends Component {
assigned_to_ids: "负责人",
status_ids: "状态",
done_ratios: "完成度",
fixed_version_ids:"里程碑",
paix: "排序",
checkedValue:[],
all:undefined
});
this.state.select_params = {
search: undefined,
@ -324,19 +329,19 @@ class order extends Component {
id: id,
},
})
.then((result) => {
if (result) {
const { status_type } = this.state;
.then((result) => {
if (result) {
const { status_type } = this.state;
this.getIssueList(status_type);
}
})
.catch((error) => {
console.log(error);
});
this.getIssueList(status_type);
}
})
.catch((error) => {
console.log(error);
});
};
islogin() {
islogin=()=>{
this.props.showLoginDialog();
}
renderNew =()=>{
@ -349,9 +354,7 @@ class order extends Component {
)
}else{
return(
<a className="topWrapper_btn ml10" onClick={this.islogin}>
+&nbsp;创建任务
</a>
<a className="topWrapper_btn ml10" onClick={this.islogin}>+&nbsp;创建任务</a>
)
}
}
@ -802,7 +805,7 @@ class order extends Component {
</div>
{search_count === 0 ? (
<NoneData _html="暂时还没有相关数据!" />
<NoneData _html="暂时还没有相关数据!" />
) : (
<div style={{ minHeight: "500px" }}>
<Checkbox.Group name="issues" onChange={this.checkIssues} value={checkedValue} style={{ width: "100%" }}>

View File

@ -34,6 +34,8 @@ class order_form extends Component {
subject: "",
get_attachments: undefined,
show_token: false,
cannot_edit: false,
issue_current_user: true
};
}
componentDidUpdate=(prevPros)=>{
@ -263,6 +265,7 @@ class order_form extends Component {
});
this.setState({
done_ratio: "0",
issue_current_user: true,
});
} else if (e === "3") {
this.props.form.setFieldsValue({
@ -270,6 +273,11 @@ class order_form extends Component {
});
this.setState({
done_ratio: "100",
issue_current_user: true,
});
}else if (e === "5") {
this.setState({
issue_current_user: false,
});
}
};
@ -371,19 +379,13 @@ class order_form extends Component {
</div>
<div className="list-left list-left-padding issue-edit-form-right">
<div className="pd20 background-f issue-form-right">
<Form.Item>
{getFieldDecorator("branch_name", {
rules: []
})(
<Form.Item label="指派成员">
{getFieldDecorator("assigned_to_id", {rules: []})(
<Select>
<Option value={""}>分支未指定</Option>
{branches && branches.length > 0 && branches.map((item, key) => {
return (
<Option value={item} key={key}>
{item}
</Option>
);
})}
<Option value={""}>未指派成员</Option>
{this.renderSelect(
issue_chosen && issue_chosen.assign_user
)}
</Select>
)}
</Form.Item>
@ -452,16 +454,6 @@ class order_form extends Component {
)}
</Form.Item>
<Form.Item label="指派成员">
{getFieldDecorator("assigned_to_id", {rules: []})(
<Select>
<Option value={""}>未指派成员</Option>
{this.renderSelect(
issue_chosen && issue_chosen.assign_user
)}
</Select>
)}
</Form.Item>
<Form.Item label="开始日期">
<DatePicker
value={start_date ? moment(start_date, "YYYY-MM-DD") : null}
@ -485,28 +477,22 @@ class order_form extends Component {
</Select>
)}
</Form.Item>
{/* <Form.Item label="">
<Switch
checkedChildren="是"
unCheckedChildren="否"
defaultChecked={false}
checked={issue_type && issue_type === "2"}
onChange={this.change_issue_type}
/>
<Form.Item label="指定分支">
{getFieldDecorator("branch_name", {
rules: []
})(
<Select>
<Option value={""}>分支未指定</Option>
{branches && branches.length > 0 && branches.map((item, key) => {
return (
<Option value={item} key={key}>
{item}
</Option>
);
})}
</Select>
)}
</Form.Item>
{(show_token || (issue_type && issue_type === "2")) && (
<Form.Item label="token">
{getFieldDecorator("token", {
rules: [
{
required: true,
message: "请填写token值",
},
],
initialValue: token,
})(<Input placeholder="请填写token值" />)}
</Form.Item>
)} */}
</div>
</div>
</Form>

View File

@ -1,9 +1,9 @@
import React , { useState } from 'react';
import React , { useEffect, useState } from 'react';
import SelectBranch from '../Branch/Select';
import Title from '../Component/Title';
import styled from 'styled-components';
import { Blueline , FlexAJ , NumUl , GreenUnder , AlignCenter , WhiteBack } from '../Component/layout';
import axios from 'axios';
const Div = styled.div`{
padding:20px 30px;
min-height:500px;
@ -14,8 +14,30 @@ export default ((props)=>{
const { projectsId , owner } = props.match.params;
const projectDetail = props.projectDetail;
function resetSetting(){
const { defaultBranch } = props;
useEffect(()=>{
if(defaultBranch){
setBranch(defaultBranch);
}
},[defaultBranch]);
function resetSetting(){
const url = `/${owner}/${projectsId}.json`;
axios.put(url, {
default_branch:branch
})
.then((result) => {
if (result) {
props.showNotification(`分支设置成功!`);
}
})
.catch((error) => {
console.log(error);
});
}
function settingRule(){
props.history.push(`/projects/${owner}/${projectsId}/setting/branch/new`);
}
return(
<WhiteBack>
@ -31,17 +53,18 @@ export default ((props)=>{
projectsId={projectsId}
changeBranch={setBranch}
owner={owner}
history={props.history}
/>
<a className="color-blue ml20" onClick={resetSetting()}>设为默认分支</a>
<a className="color-blue ml20" onClick={()=>resetSetting()}>设为默认分支</a>
</AlignCenter>
</div>
<div>
<FlexAJ className="pt20">
<span className="color-grey-3">保护分支规则</span>
<Blueline>+&nbsp;新建规则</Blueline>
<Blueline onClick={()=>settingRule()}>+&nbsp;新建规则</Blueline>
</FlexAJ>
<NumUl>
<li>限制分支的推送合并强制推送相关请去<GreenUnder>仓库设置</GreenUnder></li>
<li>限制分支的推送合并强制推送相关请去<GreenUnder onClick={()=>{props.history.push(`/projects/${owner}/${projectsId}/setting`)}}>仓库设置</GreenUnder></li>
<li>一个分支同时只能有一个保护分支规则生效越早创建的规则优先级越高</li>
<li>保护分支规则只影响状态是保护分支的分支常规分支只读分支都不影响</li>
</NumUl>

View File

@ -1,20 +1,55 @@
import React , { forwardRef , useCallback } from 'react';
import { Form , Input , Select , Button } from 'antd';
import Title from '../Component/Title';
import { WhiteBack , Cancel } from '../Component/layout';
import styled from 'styled-components';
import React, { forwardRef, useCallback, useState, useEffect } from "react";
import { Form, Input, Select, Button } from "antd";
import Title from "../Component/Title";
import { WhiteBack, Cancel } from "../Component/layout";
import styled from "styled-components";
import axios from "axios";
const {Option} = Select;
const Div = styled.div`{
padding:20px 30px;
}`
const { Option } = Select;
const Div = styled.div`
{
padding: 20px 30px;
}
`;
export default Form.create()(
forwardRef(( { form })=>{
const { getFieldDecorator } = form;
forwardRef(({ form, match, history }) => {
const [ list, setList ] = useState(undefined);
const { projectsId, owner } = match.params;
const { getFieldDecorator, validateFields } = form;
useEffect(() => {
const url = `/${owner}/${projectsId}/collaborators.json`;
axios.get(url).then((result) => {
setList(result.data.members);
})
.catch((error) => {});
}, []);
function getMember(list) {
return (
list &&
list.length > 0 &&
list.map((item, key) => {
return (
item.role !== "Manager" && (
<Option value={item.id}>{item.name}</Option>
)
);
})
);
}
function saveBranchRule() {
validateFields((error, values) => {
if (!error) {
}
});
}
const helper = useCallback(
(label, name, rules, widget , className , isRequired ) => (
(label, name, rules, widget, className, isRequired) => (
<div className={className}>
<span className={isRequired?"required":""}>{label}</span>
<span className={isRequired ? "required" : ""}>{label}</span>
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
</Form.Item>
@ -22,7 +57,7 @@ export default Form.create()(
),
[]
);
return(
return (
<WhiteBack>
<Title>新建保护分支规则</Title>
<Div>
@ -30,31 +65,48 @@ export default Form.create()(
"设置分支/通配符",
"sign",
[{ required: true, message: "请输入分支/通配符" }],
<Input placeholder="请输入分支名称或通配符规则" />,'setStyleRule'
<Input placeholder="请输入分支名称或通配符规则" />,
"setStyleRule"
)}
<p className="color-grey-8 mb20">例如设置为master则对名称为master的分支生效设置为*-stable release*则对名称符合此通配符的所有保护分支生效</p>
<p className="color-grey-8 mb20">
例如设置为master则对名称为master的分支生效设置为*-stable
release*则对名称符合此通配符的所有保护分支生效
</p>
{helper(
"可推送代码成员",
"psuhmember",
[],
<Select placeholder="请选择仓库成员" >
[{ required: true, message: "请选择可推送代码成员" }],
<Select placeholder="请选择仓库成员">
<Option value="0">请选择仓库成员</Option>
</Select>,'setSelectWidth'
{getMember(list)}
</Select>,
"setSelectWidth"
)}
{helper(
"可合并Pull Request成员",
"pullmember",
[],
<Select placeholder="请选择仓库成员" >
[{ required: true, message: "请选择可合并Pull Request成员" }],
<Select placeholder="请选择仓库成员">
<Option value="0">请选择仓库成员</Option>
</Select>,'setSelectWidth'
{getMember(list)}
</Select>,
"setSelectWidth"
)}
<div className="df pb30">
<Button type="primary">保存</Button>
<Cancel className="ml30">取消</Cancel>
<Button type="primary" onClick={saveBranchRule}>
保存
</Button>
<Cancel
className="ml30"
onClick={() => {
history.push(`/projects/${owner}/${projectsId}/setting/branch`);
}}
>
取消
</Cancel>
</div>
</Div>
</WhiteBack>
)
);
})
)
);

View File

@ -360,6 +360,11 @@ class Collaborator extends Component {
dataIndex: "email",
render: (text) => <span>{text}</span>,
},
{
title: "Token值",
dataIndex: "token",
render: (text) => <span>{text}</span>,
},
{
title: roleTitle,
dataIndex: "role_name",
@ -493,7 +498,7 @@ class Collaborator extends Component {
rowKey={(record) => record.id}
></Table>
) : (
<NoneData _html="暂时还没有相关数据!" />
<NoneData _html="暂时还没有相关数据!" />
)}
</div>
</Spin>

View File

@ -71,7 +71,7 @@ class Index extends Component {
}
>
<p>
<Link to={`/projects/${projectsId}/setting/branch`} className="w-100">
<Link to={`/projects/${owner}/${projectsId}/setting/branch`} className="w-100">
<i className="iconfont icon-fenzhi font-20 mr10"></i>
分支设置
</Link>
@ -128,7 +128,7 @@ class Index extends Component {
<Route
path="/projects/:owner/:projectsId/setting/branch"
render={(props) => (
<Branch {...this.props} {...props} {...this.state} />
<Branch {...this.props} {...props } {...this.state} />
)}
></Route>
<Route

View File

@ -19,7 +19,6 @@ class Setting extends Component {
}
componentDidUpdate=(prevPros)=>{
console.log("dddd",this.props.checkIfLogin());
if(prevPros && this.props && !this.props.checkIfLogin()){
this.props.history.push("/403")
return
@ -145,7 +144,6 @@ class Setting extends Component {
});
};
changePrivate = (e) => {
console.log(e);
this.setState({
private_check: e.target.checked,
});

View File

@ -410,7 +410,7 @@ class NewTags extends Component {
</div>
);
} else {
return <NoneData _html="暂时还没有相关数据!" />;
return <NoneData _html="暂时还没有相关数据!" />;
}
};

View File

@ -91,7 +91,7 @@ class CommonUsers extends Component {
<Spin spinning={isSpin}>
<div className="w-100 inline-block">
{count === 0 ? (
<NoneData _html="暂时还没有相关数据!" />
<NoneData _html="暂时还没有相关数据!" />
) : (
<UserList users={users} userClass={'w-25'} {...this.props}></UserList>
)}

View File

@ -125,7 +125,7 @@ class ForkUsers extends Component {
<Spin spinning={isSpin}>
<div className="w-100 inline-block">
{count === 0 ? (
<NoneData _html="暂时还没有相关数据!" />
<NoneData _html="暂时还没有相关数据!" />
) : (
this.renderList(users)
)}

View File

@ -26,7 +26,7 @@ export function getDateTime(value, dataformat) {
return date.format(dataformat);
}
export default function Time(UTCtiem) {
export function Time(UTCtiem) {
var dateTime = new Date(UTCtiem);
var year = dateTime.getFullYear();

View File

@ -0,0 +1,191 @@
import React , { Component } from "react";
import { Form , Input , Select,Divider,Button,Checkbox,Dropdown,Menu} from 'antd';
import {Link} from 'react-router-dom';
import UploadComponent from '../Upload/Index';
import '../Order/order.css';
import './version.css';
import axios from 'axios';
const Option = Select.Option;
const TextArea = Input.TextArea;
class NewVersion extends Component{
constructor(props){
super(props);
this.state={
branch_name:"",
issue_tag_ids:"",
fixed_version_id:"",
issue_chosen:undefined,
fileList:undefined,
ischeck:undefined,
pull:undefined,
tag_name:'',
data:undefined
}
}
componentDidMount=()=>{
this.getSelectList();
}
getSelectList=()=>{
const { projectsId,versionId} = this.props.match.params;
const url = `/projects/${projectsId}/version_releases/${versionId}/edit.json`;
axios.get(url).then((result)=>{
if(result){
this.setState({
data:result.data,
pull:result.data.target_commitish
})
}
}).catch((error)=>{
console.log(error);
})
}
//delete
deleteversion=()=>{
const { projectsId , versionId} = this.props.match.params;
const url = `/projects/${projectsId}/version_releases/${versionId}.json`;
axios.delete(url,{ data: {
project_id: projectsId,
id:versionId
}
}).then((result)=>{
if(result){
this.props.history.push(`/projects/${projectsId}/version`);
}
}).catch((error)=>{
console.log(error);
})
}
// 创建
handleSubmit=()=>{
this.props.form.validateFieldsAndScroll((err, values) => {
if(!err){
const { projectsId,versionId} = this.props.match.params;
const { pull,ischeck } = this.state;
const url = `/projects/${projectsId}/version_releases/${versionId}.json`;
// if(values.issue_type==="普通"){
// values.issue_type="1"
// }
axios.put(url,{
...values,
tag_name:this.state.data&&this.state.data.tag_name,
draft:false,
prerelease:ischeck,
target_commitish:pull
}).then(result=>{
if(result){
this.props.history.push(`/projects/${projectsId}/version`);
}
}).catch(error=>{
console.log(error);
})
}
})
}
RedieonChange=(e)=>{
this.setState({
ischeck:e.target.checked
})
}
Preservation=()=>{
alert(this.state.data.tag_name)
}
renderMenu =(array,id)=>{
return(
<Menu>
{
array && array.length > 0 && array.map((item,key)=>{
return(
<Menu.Item key={item} onClick={()=>this.getOption(item)}>{item}</Menu.Item>
)
})
}
</Menu>
)
}
getOption=(name)=>{
this.setState({
pull:name
})
}
changmodelname=(e)=>{
this.setState({
tag_name:e.target.value
})
}
render(){
const { getFieldDecorator } = this.props.form;
const { projectsId } = this.props.match.params;
return(
<div className="main">
<Form>
<h1 style={{marginLeft:15,marginTop:20}}>发布新版</h1>
<h5 style={{marginLeft:15}}>版本发布组织项目的版本</h5>
<Divider/>
<div style={{display:'flex',marginLeft:15}}>
{this.state.data&&this.state.data.tag_name}@{this.state.data&&this.state.data.target_commitish}
</div>
<div style={{display:'flex'}}>
<div className="versionmilepostleft">
<h1>标题</h1>
<div>
<Form.Item>
{getFieldDecorator('name', {
rules: [{
required: true, message: '请输入标题'
}],
initialValue:this.state.data&&this.state.data.name
})(
<Input placeholder="标题"/>
)}
</Form.Item>
</div>
<h1>内容</h1>
<Form.Item>
{getFieldDecorator('body', {
rules: [{
required: true, message: '请输入描述内容'
}],
initialValue:this.state.data&&this.state.data.body
})(
<TextArea placeholder="添加一个可选的扩展描述。。。" style={{height:"300px"}}/>
)}
</Form.Item>
{/* <UploadComponent load={this.UploadFunc} style={{width:80,marginLeft:15}}></UploadComponent> */}
</div>
</div>
<Divider/>
<div className="fr">
<Checkbox onChange={this.RedieonChange}>标记为预行版</Checkbox>
<p>标记此版本不适合生产使用</p>
</div>
<div className="clearfix mt15" style={{marginTop:5}} >
<a className='topWrapper_btn_delete fr' onClick={()=>this.deleteversion()} style={{marginLeft:15}}>删除发布</a>
<a className='topWrapper_btn fr' onClick={()=>this.handleSubmit()} style={{marginRight:15}}>编辑发布信息</a>
<Link to={`/projects/${projectsId}/version`} style={{marginRight:30}} className='topWrapper_btn fr'>取消</Link>
</div>
</Form>
</div>
)
}
}
const WrappedNewVersionForm = Form.create({ name: 'NewVersionForm' })(NewVersion);
export default WrappedNewVersionForm;

View File

@ -177,7 +177,7 @@
}
.versionTips{
width:30%;
padding-right: 15px;
padding-left: 15px;
box-sizing: border-box;
}
.infosTip{

View File

@ -97,7 +97,7 @@ class version extends Component {
)
} else {
return (
<NoneData _html="暂时还没有相关数据!" />
<NoneData _html="暂时还没有相关数据!" />
)
}
}
@ -107,7 +107,7 @@ class version extends Component {
const { data , releases } = this.state
return (
<div className="main">
<div className="main" style={{paddingTop:"0px"}}>
<div className="topWrapper">
<span className="font-18 color-grey-3">版本发布</span>
{

View File

@ -523,7 +523,7 @@ class comments extends Component {
{new_comment(is_reply, undefined)}
</div>
) : (
<div className="children-comment-bg pd20 grid-item">
<div className="children-comment-bg pd20 grid-item mb20">
<img
className="radius"
src={

View File

@ -3,6 +3,6 @@ export function truncateCommitId(str) {
if (str && str.length > 11) {
return str.substring(0, 10)
}else{
return undefined
return str
}
}

View File

@ -135,7 +135,24 @@ form{
vertical-align: middle;
}
.pd510{padding: 5px 10px;}
.modalsStyle{
.ant-modal-content{
&>.ant-modal-header{
text-align: left!important;
}
}
.ant-modal-body{
max-width: 400px;
margin:0px auto;
text-align: left;
min-height: 140px;
display: flex;
align-items: center;
}
.ant-modal-close-x{
margin-top:8px;
}
}
.list-l-Menu{
margin-bottom: 12px;
border-radius:2px;

View File

@ -0,0 +1,348 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Avatar, Tag, Button, Spin, Input, Form, message, Divider} from "antd";
import { Route, Switch } from "react-router-dom";
import { withRouter } from "react-router";
import { SnackbarHOC } from "educoder";
import { CNotificationHOC } from "../../modules/courses/common/CNotificationHOC";
import { TPMIndexHOC } from "../../modules/tpm/TPMIndexHOC";
import axios from "axios";
import { getImageUrl } from "educoder";
import Loadable from "react-loadable";
import Loading from "../../Loading";
import { ImageLayerOfCommentHOC } from "../../modules/page/layers/ImageLayerOfCommentHOC";
import SponsorTierItem from "./sponsor_tier_item";
import "../users/new_user.css";
import "../css/index.scss";
import '../users/Index.scss';
import "./sponsor.css";
const tempData = [
{id:1, tier: 5, description: "这能让我感到很开心"},
{id:2, tier: 10, description: "如果您更加慷慨的话就选择这个"},
{id:3, tier: 150, description: "这能为我带来相当多的额外收入"},
{id:4, tier: 250, description: "这能为我带来相当多的额外收入"},
{id:5, tier: 500, description: "这能为我带来相当多的额外收入"},
{id:6, tier: 1000, description: "这能为我带来相当多的额外收入"},
{id:7, tier: 5000, description: "这能为我带来相当多的额外收入"},
];
const tempDesc = "我长期从事开源社区相关工作...目前已经开发了多个项目如xxx, xxxx, ...\n我希望通过资助机制成为全职开源开发者如果您对我的项目感兴趣请资助我"
const tempSponsors = [
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
{image_url: "avatars/User/b", username: "万山川", user_id: 1369, login:"alpc104"},
];
const FormItem = Form.Item;
const TextArea = Input.TextArea;
const DescriptionForm = Form.create()(
class extends Component{
getItemsValue = ()=>{
const val= this.props.form.getFieldsValue(); // 获取from表单的值
return val;
}
render(){
const { form, initValue} = this.props;
const { getFieldDecorator } = form; // 校验控件
return(
<Form >
<FormItem>
{getFieldDecorator(`description`,{
rules: [],
initialValue: initValue ? initValue : undefined
})(
// <Input placeholder="请输入"/>
<TextArea rows={4} placeholder="请输入"></TextArea>
)}
</FormItem>
</Form>
)
}
}
);
class Sponsor extends Component {
constructor(props) {
super(props);
this.state = {
isSpin: false,
user: undefined,
description: undefined,
sponsors: undefined,
edit: false,
tiers: [],
};
}
componentDidMount = () => {
this.fetchUser();
this.fetchData();
};
fetchUser = () => {
this.setState({
isSpin: true,
});
const { current_user } = this.props;
const { username } = this.props.match.params;
let url = `/users/${username || (current_user && current_user.login)}.json`;
axios
.get(url)
.then((result) => {
// TODO: 在这里添加了对用户自我介绍的处理,前后端交互实现后需要删掉
// result.data.description = tempDesc;
this.setState({
user: result.data,
isSpin: false,
}, () => {this.fetchSponsors()});
// console.log(result.data);
})
.catch((error) => {
console.log(error);
this.setState({
isSpin: false,
});
});
};
fetchSponsors = () => {
// console.log("fetch sponsors");
const user_id = this.state.user.user_id;
let url = `/sponsorships/sponsored.json`;
axios.get(url, {params: {id: user_id}})
.then((result) => {
// console.log(result);
this.setState({
sponsors: result.data.sponsorships,
isSpin: false,
})
})
.catch((error) => {
console.log(error);
this.setState({
isSpin: false,
});
});
// this.setState({
// sponsors: tempData,
// displayList: tempData,
// })
// this.setState({
// sponsors: tempSponsors,
// });
}
fetchData = () => {
let url = `/sponsor_tiers.json`;
const user_login = this.props.match.params.username;
axios
.get(url, {params:{login: user_login}})
.then((result) => {
// console.log(result.data);
// console.log(tempData)
this.setState({
tiers: result.data
});
})
.catch((error) => {
console.log(error);
})
}
editDescription = () => {
this.setState({
edit: true,
});
}
cancelEdit = () => {
this.setState({
edit: false,
})
}
confirmEdit = () => {
//TODO: 增加和后端交互
let value = this.formRef.getItemsValue().description;
// console.log(value);
let user = this.state.user;
let url = `/users/${user.user_id}/update_description.json`
axios.put(url, {description: value})
.then((result) => {
user.description = value;
this.setState({
user: user,
edit: false
})
this.props.showNotification("修改成功")
})
.catch(error => {
console.log(error);
this.props.showNotification("修改失败")
})
// if (user){
// user.description = value;
// }
// this.setState({
// user: user,
// edit: false,
// });
}
visitorTemplate = () => {
const user = this.state.user;
return (
<div>
{user && user.description?
(<p>{user.description}</p>):(<p></p>)}
</div>
);
}
manageTemplate = () => {
const user = this.state.user;
return (
<div>
<div>
<i className="iconfont icon-bianji3 font-15" onClick={() => this.editDescription()}></i>
</div>
{user && user.description? (<div>{user.description}</div>):(<div></div>)}
</div>
);
}
editTemplate = () => {
const user = this.state.user;
return (
<div>
<DescriptionForm
initValue={user && user.description}
wrappedComponentRef={(form) => this.formRef = form}
/>
<Button onClick={() => this.confirmEdit()}>确认</Button>
<Button onClick={() => this.cancelEdit()}>取消</Button>
</div>
);
}
sponsorList(){
const {sponsors} = this.state;
const sponsorList = (sponsors && sponsors.length>0 ? sponsors.map((item, key)=>{
return (
<Link to={`/users/${item.login}`} className="show-user-link">
<img className="sponsor-page-sponsors" alt="" src={getImageUrl(`images/${item && item.image_url}`)} title={item.username}></img>
</Link>);
})
:"")
return (<p>{sponsorList}</p>);
}
render(){
const { current_user} = this.props;
const { user, isSpin, tiers} = this.state;
const sponsorList = this.sponsorList();
// console.log(tiers);
var content = "";
if (current_user && user && user.login !== current_user.login){
content = this.visitorTemplate();
}
else if(current_user && user && user.login === current_user.login && this.state.edit){
content = this.editTemplate();
}
else if(current_user && user && user.login === current_user.login && !this.state.edit){
content = this.manageTemplate();
}
return (
<div className="newMain clearfix">
<Spin spinning={isSpin}>
<div className="new-content-flex">
<div className="sponsor-page-left">
<div className="list-l-Menu pd20 ">
<div className="text-center mt15 font-16 fwb"> 成为{user && user.username}的赞助者吧</div>
<div className="text-center"><Avatar
size={110}
src={getImageUrl(`images/${user && user.image_url}`)}
/>
{user && user.user_identity && (
<div className="mt-n15 position-relative">
<Tag color="#FF6E21" style={{marginRight:"0px"}}>{user && user.user_identity}</Tag>
</div>
)}
</div>
<Divider />
{content}
<Divider />
<div>
赞助者:
{sponsorList}
</div>
</div>
<div className="bgcF">
{/* <div className="list-l-Menu pd20 ">
{content}
</div>
<div className="list-l-Menu pd20 ">
赞助者:
{sponsorList}
</div> */}
</div>
</div>
<div className="sponsor-page-right">
<SponsorTierItem
{...this.props}
{...this.state}
tiers={tiers}>
</SponsorTierItem>
</div>
</div>
</Spin>
</div>
);
}
}
export default withRouter(
ImageLayerOfCommentHOC({
imgSelector: ".imageLayerParent img, .imageLayerParent .imageTarget",
parentSelector: ".newMain",
})(CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(Sponsor))))
);

View File

@ -0,0 +1,325 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Avatar, Tag, Button, Spin, Input, Divider, Form, Radio, Alert, message, Modal} from "antd";
import { Route, Switch } from "react-router-dom";
import { withRouter } from "react-router";
import { SnackbarHOC } from "educoder";
import { CNotificationHOC } from "../../modules/courses/common/CNotificationHOC";
import { TPMIndexHOC } from "../../modules/tpm/TPMIndexHOC";
import axios from "axios";
import { getImageUrl } from "educoder";
import Loadable from "react-loadable";
import Loading from "../../Loading";
import { ImageLayerOfCommentHOC } from "../../modules/page/layers/ImageLayerOfCommentHOC";
import "../users/new_user.css";
import "../css/index.scss";
import '../users/Index.scss';
import "./sponsor.css";
import { submitUserCode } from "../../redux/actions/ojForUser";
const tempData = {amount:100, description: "如果您更加慷慨的话就选择这个"};
const tempBalance = 999;
const FormItem = Form.Item;
const { confirm } = Modal;
const SponsorConfirmation = Form.create()(
class extends Component{
constructor(props) {
super(props);
this.state = {
isSpin: false,
user: undefined,
tier: undefined,
single: false,
balance: undefined,
is_sponsoring: false,
visible: false,
sponsorship_id: undefined,
};
}
componentDidMount = () => {
this.fetchUser();
this.fetchTierInformation();
this.fetchBalance();
// this.fetchData();
};
//组件刚刚挂载时TPMINDEXHOC还没从后端获取current_user需要在获取后再使用current_user的内容
componentWillReceiveProps = () => {
if (this.props.current_user)
this.fetchBalance();
}
fetchUser = () => {
this.setState({
isSpin: true,
});
const { current_user } = this.props;
const { username } = this.props.match.params;
let url = `/users/${username || (current_user && current_user.login)}.json`;
axios
.get(url)
.then((result) => {
this.setState({
user: result.data,
isSpin: false,
});
})
.catch((error) => {
console.log(error);
this.setState({
isSpin: false,
});
});
};
fetchTierInformation = () => {
const { current_user } = this.props;
const { username, tier_id } = this.props.match.params;
let url = `/sponsor_tiers/${tier_id}.json`;
axios
.get(url)
.then((result) => {
this.setState({
tier: result.data.tier,
is_sponsoring: result.data.is_sponsoring,
sponsorship_id: result.data.sponsorship_id,
})
})
.catch((error) => {
console.log(error);
this.setState({
isSpin: false,
});
});
}
fetchBalance = () => {
const { current_user } = this.props;
let url = `/wallets/balance.json`;
if (current_user){
axios
.get(url, {params: {id: current_user.user_id}})
.then((result) => {
this.setState({
balance: result.data.balance,
})
})
.catch((error) => {
console.log(error);
this.setState({
isSpin: false,
});
});
}
}
reSelect = () => {
const {user} = this.state;
this.setState({
route_type: undefined
})
this.props.history.push(`/sponsor/${user && user.login}`)
}
changeSingle = (e) => {
this.setState({
single: e.target.value
})
}
updateSponsorship = () => {
const value = this.props.form.getFieldsValue();
const {tier, user, sponsorship_id} = this.state;
const {current_user} = this.props;
let url = `/sponsorships/${sponsorship_id}.json`;
axios.put(url, {
amount: tier.tier,
visible: value.visible
})
.then((result) => {
if (result && result.data.status == 1)
this.props.showNotification(result.data.message);
})
.catch((error) => {
console.log(error);
})
}
submit = () => {
const value = this.props.form.getFieldsValue();
const {tier, user} = this.state;
const {current_user} = this.props;
// console.log(value);
let url = `/sponsorships.json`;
axios.post(url, {
amount: tier.tier,
visible: value.visible,
single: value.single,
developer_id: user.user_id,
})
.then((result) => {
// console.log(result);
if (result && result.data.status == 1)
this.props.showNotification(result.data.message);
})
.catch((error) => {
console.log(error);
})
}
handleCancel = e => {
this.setState({
visible: false,
});
};
showModal = () => {
this.setState({
visible: true,
});
};
showConfirm = () => {
const {user} = this.state;
const fun = this.state.is_sponsoring?this.updateSponsorship:this.submit;
let text = this.state.is_sponsoring?`确认要修改对 ${user && user.username} 的赞助吗?若修改金额,将在下次自动扣款生效。`
:`确认要赞助 ${user && user.username} 吗?`;
confirm({
// title: '确认赞助',
// icon: <ExclamationCircleOutlined />,
content: text,
onOk() {
fun()
},
onCancel() {
},
});
}
render(){
const {tier_id, username} = this.props.match.params;
const {current_user, form} = this.props;
const {user, isSpin, tier, balance, is_sponsoring, single} = this.state;
const {getFieldDecorator} = form;
const current_username = current_user && current_user.username;
const alert_text = this.state.single? `您选择了单次付费,本次扣除${tier && tier.tier}硬币`
:`您选择了按月付费每月20日将扣除${tier && tier.tier}硬币`;
// console.log("sponsoring", is_sponsoring);
// console.log("tier: ", tier);
// console.log("current user", current_user);
const radioStyle = {
display: 'block',
height: '30px',
lineHeight: '30px',
};
return (
<div className="newMain clearfix">
<Spin spinning={isSpin}>
<div className="new-content-flex">
<div className="sponsor-confirmation-left">
<div className="list-l-Menu pd20 ">
<p>
<Avatar
size={50}
src={getImageUrl(`images/${user && user.image_url}`)}
/>
<a href={`/sponsor/${user && user.login}`}>赞助{user && user.username}</a>/
</p>
<p>
赞助者为
<Avatar
size={50}
src={getImageUrl(`images/${current_user && current_user.image_url}`)}
/>
{current_user && current_user.username}
</p>
<Divider></Divider>
<p>所选赞助等级</p>
<p>{tier && tier.tier}硬币</p>
<p>{tier && tier.description}</p>
<Button onClick={() => this.reSelect()}>重新选择</Button>
</div>
</div>
<div className="sponsor-confirmation-right">
<div className="list-l-Menu pd20 ">
<Form>
<FormItem >
{getFieldDecorator(`single`,{
rules: [{
required: true
}],
initialValue: single
})(
<Radio.Group onChange={(e) => this.changeSingle(e)} disabled={is_sponsoring}>
<Radio style={radioStyle} value={true}>单次付费</Radio>
<Radio style={radioStyle} value={false}>按月支付</Radio>
</Radio.Group>
)}
</FormItem>
<p>本次支付金额{tier && tier.tier}硬币</p>
<p>{this.state.single? "单次付费": "即日起-次月20日"}</p>
<Divider></Divider>
<div>支付信息</div>
<div>
<div>
<Avatar
size={50}
src={getImageUrl(`images/${current_user && current_user.image_url}`)}
/>
</div>
<div>
<div>{current_user && current_user.username}</div>
<div>硬币余额{balance}</div>
</div>
</div>
<Alert message={alert_text} type="warning"/>
<Divider></Divider>
<span>赞助关系可见性</span>
<FormItem >
{getFieldDecorator(`visible`,{
rules: [{
required: true
}],
initialValue: 1
})(
<Radio.Group >
<Radio style={radioStyle} value={1}>所有人可见</Radio>
<Radio style={radioStyle} value={0}>私密</Radio>
</Radio.Group>
)}
</FormItem>
<Button type='primary' className="confirm-btn" onClick={() => this.showConfirm()}>赞助 {user && user.username}</Button>
</Form>
</div>
</div>
</div>
</Spin>
</div>
);
}
});
export default withRouter(
ImageLayerOfCommentHOC({
imgSelector: ".imageLayerParent img, .imageLayerParent .imageTarget",
parentSelector: ".newMain",
})(CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(SponsorConfirmation))))
);

View File

@ -0,0 +1,80 @@
.sponsor-page-left{
width: 65%;
padding-right: 20px;
box-sizing: border-box;
margin-bottom: 20px;
}
.sponsor-page-right{
width: 35%;
padding-right: 20px;
box-sizing: border-box;
margin-bottom: 20px;
}
.sponsor-page-sponsors{
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 11px;
}
.sponsor-confirmation-left{
width: 35%;
padding-right: 20px;
box-sizing: border-box;
margin-bottom: 20px;
}
.sponsor-confirmation-right{
width: 65%;
padding-right: 20px;
box-sizing: border-box;
margin-bottom: 20px;
}
.sponsor-inline{
display: inline-block;
}
.tier-Item:last-child{
border-bottom: none !important;
}
.tier-Item{
border-bottom:1px solid rgba(238,238,238,1);
padding:9px 12px;
}
.right{
float: right
}
.btn-border-5{
border-radius: 5px;
}
.description-edit{
min-height:28px;
border:true;
}
.tier-title{
font-size: 16px;
font-weight: 600;
}
.mr10{
margin-right: 10px;
}
.confirm-btn{
width: 100%;
border-radius: 5px;
}
.sponsor-item-btn{
display: flex;
flex-direction: column;
}

View File

@ -0,0 +1,422 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Avatar, Tag, Button, Spin, Icon, Modal, Input, Form, message, Popconfirm, Divider, InputNumber} from "antd";
import { Route, Switch } from "react-router-dom";
import { withRouter } from "react-router";
// import { EditOutlined } from "@ant-design/icons";
import { SnackbarHOC } from "educoder";
import { CNotificationHOC } from "../../modules/courses/common/CNotificationHOC";
import { TPMIndexHOC } from "../../modules/tpm/TPMIndexHOC";
import axios from "axios";
import { getImageUrl } from "educoder";
import Loadable from "react-loadable";
import Loading from "../../Loading";
import { ImageLayerOfCommentHOC } from "../../modules/page/layers/ImageLayerOfCommentHOC";
import "../users/new_user.css";
import "../css/index.scss";
import '../users/Index.scss';
import "./sponsor.css";
import "../Main/list.css";
import Item from "antd/lib/list/Item";
const FormItem = Form.Item;
const EditTierForm = Form.create()(
class extends Component{
getItemsValue = ()=>{
const val= this.props.form.getFieldsValue(); // 获取from表单的值
return val;
}
render(){
const { form, initValue1, initValue2, initValueList } = this.props;
const { getFieldDecorator } = form; // 校验控件
return(
<Form >
<FormItem label={`金额`} >
{getFieldDecorator(`tier`,{
rules: [{
message: '请输入数字!',
pattern: /^[0-9]+$/,
required: true
}],
initialValue: initValue1 ? initValue1 : undefined
})(
<InputNumber placeholder="请输入数字"/>
)}
</FormItem>
<FormItem label={`描述`} >
{getFieldDecorator(`description`,{
rules: [{
message: '必填字段!',
required: true,
}, {
max: 255,
message: "长度不能超过255个字符"
}],
initialValue: initValue2 ? initValue2 : undefined
})(
<Input placeholder="请输入"/>
)}
</FormItem>
</Form>
)
}
});
class SponsorTierItem extends Component{
constructor(props) {
super(props)
var edit = new Array();
if (props.tiers){
for(var i=0; i<props.tiers.length; i++) edit.push(false);
}
this.state = {
tiers: props.tiers,
new_tier: undefined,
edit: edit,
isSpin: false,
}
// this.state = {
// tiers: undefined,
// new_tier: undefined,
// edit: undefined,
// isSpin: false,
// }
}
componentDidMount = () => {
}
// 如果服务器较慢组件会先被挂载此时父组件传来的props数据是空的但是获得数据后,有新的props需要更新。
// 如果用户点击了修改自我描述等父组件的方法,父组件会重新渲染,这个方法又会被调用,有可能原有的编辑框会丢失。
// 因此先判断一下父组件传过来的数据是否有变化。没有变化则不需要修改state
componentWillReceiveProps = () => {
if (this.props.tiers !== this.state.tiers){
var edit = new Array();
for(var i=0; i<this.props.tiers.length; i++) edit.push(false);
this.setState({
tiers: this.props.tiers,
edit: edit,
})
}
}
fetchData = () => {
let url = `/sponsor_tiers.json`;
axios
.get(url)
.then((result) => {
console.log(result.data);
// console.log(tempData);
var edit = new Array();
for(var i=0; i<result.data.length; i++) edit.push(false);
this.setState({
tiers: result.data,
edit: edit,
})
})
.catch((error) => {
console.log(error);
})
}
fetchUser = () => {
this.setState({
isSpin: true,
});
const { current_user } = this.props;
const { username } = this.props.match.params;
let url = `/users/${username || (current_user && current_user.login)}.json`;
axios
.get(url)
.then((result) => {
this.setState({
user: result.data,
isSpin: false,
});
})
.catch((error) => {
console.log(error);
this.setState({
isSpin: false,
});
});
};
editTier = (item, key, e) => {
var edit = this.state.edit;
var editing = false;
for(var i=0; i<edit.length; i++)
editing = editing || edit[i];
if(editing){
this.props.showNotification("请先完成当前编辑");
return;
}
else{
edit[key] = true;
this.setState({
edit:edit
})
}
}
confirmEdit = async (item, key, e) => {
let url;
try {
const values = await this.formRef.props.form.validateFields();
// console.log('Success:', values);
} catch (errorInfo) {
this.props.showNotification('提交失败,数据不合法');
return false;
}
let values = this.formRef.getItemsValue();
// console.log(this.formRef);
const {current_user} = this.props;
if (this.state.new_tier == key){
console.log("new tier");
url = `/sponsor_tiers.json`;
axios
.post(url, {
...values,
user_id: current_user.user_id
})
.then((result) => {
var {tiers, edit} = this.state;
tiers[key] = result.data.tier;
edit[key] = false;
this.setState({
tiers: tiers,
edit: edit,
new_tier: undefined
})
// message.success("新增成功");
this.props.showNotification(`新增成功!`);
})
.catch((error) => {
console.log(error);
this.props.showNotification("新增失败");
})
}
else{
url = `/sponsor_tiers/${item.id}.json`
axios
.put(url, {
...values,
})
.then((result) => {
var {tiers, edit} = this.state;
tiers[key] = result.data.tier;
edit[key] = false;
this.setState({
tiers: tiers,
edit: edit,
new_tier: undefined
})
// message.success("修改成功");
this.props.showNotification(`修改成功!`);
})
.catch((error) => {
console.log(error);
this.props.showNotification("修改失败");
})
}
// TODO: 在此处加入对后端的修改请求
// TODO: 修改的请求实现
}
cancelEdit = (item, key, e) => {
let {tiers, edit, new_tier} = this.state;
if (new_tier){
tiers.pop();
edit.pop();
}
else {
edit[key] = false;
}
this.setState({
tiers: tiers,
edit: edit,
new_tier: undefined
})
}
newTier = () => {
if (this.state.new_tier !== undefined){
this.props.showNotification("请先完成当前编辑")
return;
}
let {tiers, edit} = this.state;
tiers.push({tier: 1, description: ""})
edit.push(true);
this.setState({
tiers: tiers,
edit: edit,
new_tier: tiers.length - 1,
})
}
deleteTier = (item, key, e) => {
// TODO: 此处向后端发送请求删除
let {tiers, edit} = this.state;
let url = `/sponsor_tiers/${item.id}.json`;
axios
.delete(url)
.then((result) => {
tiers.splice(key, 1)
edit.splice(key, 1)
this.setState({
tiers: tiers,
edit: edit,
})
this.props.showNotification("删除成功");
})
.catch((error) => {
console.log(error);
this.props.showNotification("删除失败");
})
}
toConformation = (item, key, e) => {
const {user} = this.props;
this.setState({
route_type: undefined
})
this.props.history.push(`/sponsor_confirmation/${user && user.login}/${item.id}`)
}
visitorTemplate = (item, key) => {
return (
<ul className="tier-Item">
<div className="tier-title">
{item.tier}&nbsp;&nbsp;硬币
<Button className="right btn-border-5" onClick={(e) => this.toConformation(item, key, e)}>选择</Button>
</div>
<div>
{item.description}
</div>
</ul>
);
}
manageTemplate = (item, key) => {
return (
<div className="tier-Item">
<div>
<span className="tier-title">{item.tier}&nbsp;&nbsp;硬币</span>
<Popconfirm
title="确认删除?"
onConfirm={e => this.deleteTier(item, key, e)}
// onCancel={cancel}
okText="是"
cancelText="否"
>
<i className="iconfont icon-shanchu font-15 right"></i>
</Popconfirm>
<i className="iconfont icon-bianji3 font-15 mr10 right" onClick={e => this.editTier(item, key, e)}></i>
</div>
<div>
{item.description}
</div>
</div>
);
}
editTemplate = (item, key) => {
return (
<div className="tier-Item">
<EditTierForm
initValue1={item.tier}
initValue2={item.description}
wrappedComponentRef={(form) => this.formRef = form}
/>
<Button onClick={e => this.confirmEdit(item, key, e)}>确定</Button>
<Button onClick={e => this.cancelEdit(item, key, e)}>取消</Button>
</div>
);
}
render() {
const {tiers} = this.state;
const { current_user} = this.props;
const {user} = this.props;
// console.log("item state tiers:", tiers);
// console.log("item props tiers:", this.props.tiers);
const renderList = (
tiers && tiers.length > 0 ? tiers.map((item, key) => {
const tier = this.state.edit[key]? (<Input></Input>):item.tier;
const description = this.state.edit[key]? (<Input></Input>):item.description;
var content = "";
if (current_user && user && user.login !== current_user.login){
content = this.visitorTemplate(item, key);
}
else if(current_user && user && user.login === current_user.login && this.state.edit[key]){
content = this.editTemplate(item, key);
}
else if(current_user && user && user.login === current_user.login && !this.state.edit[key]){
content = this.manageTemplate(item, key);
}
// return (
// <ul className="list-l-Menu">
// <div className="font-15 mr5">
// 金额:{amount}
// {current_user && user && user.login === current_user.login ?
// // (<Icon type="edit" onClick={item => this.editTier(item)}/>
// (<i className="iconfont icon-bianji3 font-15 mr5" onClick={e => this.editTier(item, key, e)}></i>):
// <Button >选择</Button>}
// </div>
// <div className="font-15 mr5">
// 描述:{description}
// {current_user && user && user.login === current_user.login ?
// (<i className="iconfont icon-shanchu font-15"></i>):""}
// </div>
// </ul>
// )
return content;
}) : ""
);
return (
<div>
<div className="list-l-Menu">
<p className="tier-Item tier-title">选择您的赞助等级</p>
{/* <Divider/> */}
{renderList}
</div>
{current_user && user && user.login === current_user.login ?
( <Button type="primary" onClick={() => this.newTier()}>新增赞助等级</Button>):
""
}
</div>
)
}
}
export default SponsorTierItem;

100
src/forge/users/CIList.jsx Normal file
View File

@ -0,0 +1,100 @@
import React , { useEffect , useState } from 'react';
import { Button ,Spin , Pagination , Popconfirm } from 'antd';
import { Link } from 'react-router-dom';
import Nodata from '../Nodata';
import axios from 'axios';
const limit = 10;
function CIList(props){
const [ page , setPage ] = useState(1);
const [ total , setTotal ] = useState(0);
const [ isSpining , setIsSpining] = useState(true);
const [ list , setList ] = useState(undefined);
let user = props.current_user;
useEffect(()=>{
setIsSpining(true);
if(user && user.login){
getInfo();
}
},[user,page])
function getInfo(){
const url = `/users/${user.login}/projects.json`;
axios.get(url,{
params:{
limit , page , category:"manage"
}
}).then(result=>{
if(result && result.data){
setList(result.data.projects);
setTotal(result.data.count);
setIsSpining(false);
}
}).catch(error=>{
console.log(error);
})
}
function changePage(page){
setPage(page);
}
//
function cancelActivate(author,identifier){
setIsSpining(true);
const url =`/${author && author.login}/${identifier}/deactivate.json`;
axios.delete(url).then(result=>{
if(result){
getInfo();
}else{
setIsSpining(false);
}
}).catch(error=>{setIsSpining(false);})
}
return(
<div style={{minHeight:"400px"}}>
<Spin spinning={isSpining}>
{
list && list.length > 0 ?
<ul className="CIList">
{list.map((item,key)=>{
return(
<li key={key}>
<span>
<Link to={`/projects/${item.author && item.author.login}/${item.identifier}${ item.open_devops ? "/devops/list":""}`}>{item.name}</Link>
{ item.open_devops ?
<span className="authTag green ml20">已激活</span>
:
<span className="authTag red ml20">未激活</span>
}
</span>
{ item.open_devops ?
<Popconfirm
title="确定取消激活?"
onConfirm={()=>cancelActivate(item.author,item.identifier)}
okText="确定"
cancelText="取消"
>
<Button>取消激活</Button>
</Popconfirm>
:
<Button type={"primary"} onClick={()=>{props.history.push(`/projects/${item.author && item.author.login}/${item.identifier}/devops`)}}>马上激活</Button>
}
</li>
)
})}
</ul>
:<Nodata _html="暂无数据"/>
}
{
total > limit &&
<div className="mt20 mb30" style={{textAlign:"center"}}>
<Pagination simple onChange={changePage} pageSize={limit} current={page} total={total}></Pagination>
</div>
}
</Spin>
</div>
)
}
export default CIList;

View File

@ -0,0 +1,169 @@
import React , { useEffect , useState , forwardRef , useCallback } from 'react';
import { Button , Modal , Form , Input } from 'antd';
import { AlignCenter } from '../Component/layout';
import axios from 'axios';
import Modals from '../Component/Modal';
import PasswordAuthority from '../Component/PasswordAuthority';
import { func } from 'prop-types';
const TIPS = "解除CI服务器绑定后您所有的项目构建数据将被清空。确定解除绑定";
function CIdispose(props){
const [ CIData , setCIData ] = useState(undefined);
const [ step , setStep ] = useState(0);
const [ visible, setVisible ] = useState(false);
const [ bindVisible, setBindVisible ] = useState(false);
const [ ci_certification, setCi_certification ] = useState(false);
const [ btnLoading , setBtnLoading ] = useState(false);
const [ authorityValBox , setAuthorityValBox] = useState(false);
const { form: { getFieldDecorator, validateFields , setFieldsValue } } = props;
const helper = useCallback(
(label, name, rules, widget, isRequired) => (
<React.Fragment>
<span className={isRequired ? "required" : ""}>{label}</span>
<Form.Item>
{getFieldDecorator(name, { rules, validateFirst: true })(widget)}
</Form.Item>
</React.Fragment>
),
[]
);
useEffect(()=>{
getInit();
},[])
function getInit(){
const url = `/users/ci/cloud_account.json`;
axios.get(url).then(result=>{
if(result && result.data){
setCIData(result.data.cloud_account);
setStep(result.data.step);
setCi_certification(result.data.ci_certification);
}
}).catch(error=>{
console.log(error);
})
}
//
function cancelBind(){
const url = `/users/ci/cloud_account/unbind.json`;
axios.delete(url).then(result=>{
if(result && result.data){
props.showNotification("成功解除绑定!");
setVisible(false);
getInit();
}
}).catch(error=>{
setBindVisible(false);
})
}
function onCancel(){
setVisible(false);
}
//
function sureBindInfo(){
validateFields((error, values) => {
if (!error) {
setBtnLoading(true);
const url = `/users/ci/cloud_account/bind.json`;
axios.post(url,{
...values
}).then(result=>{
setBtnLoading(false);
if(result && result.data){
setBindVisible(false);
setCIData(result.data.cloud_account);
setStep(result.data.step);
props.showNotification("服务器绑定成功!");
}
})
}
})
}
//
function cancelBindInfo(){
setBindVisible(false);
setFieldsValue({
ip_num:undefined,
secret:undefined,
account:undefined
})
}
function okAuthority(){
setAuthorityValBox(false);
getInit();
}
function cancelAuthority(){
setAuthorityValBox(false);
}
return(
<div className="disposeInfo">
<PasswordAuthority authorityValBox={authorityValBox} successFunc={okAuthority} cancelFunc={cancelAuthority}></PasswordAuthority>
<Modal
className="modalsStyle"
visible={bindVisible}
title={"绑定CI服务器地址"}
onCancel={cancelBindInfo}
closable={true}
footer={
<div>
<Button onClick={cancelBindInfo}>取消</Button>
<Button loading={btnLoading} type={"primary"} style={{marginLeft:"20px"}} onClick={sureBindInfo}>确定</Button>
</div>
}
>
<Form style={{marginTop:"20px"}}>
{helper(
"服务器IP地址",
"ip_num",
[{ required: true, message: "请输入服务器IP地址" }],
<Input
placeholder="请输入服务器IP地址"
style={{ width: "368px" }}
size="large"
/>,
true
)}
{helper(
"服务器用户名:",
"account",
[{ required: true, message: "请输入服务器用户名" }],
<Input placeholder="请输入服务器用户名" size="large" />,
true
)}
{helper(
"服务器密码:",
"secret",
[{ required: true, message: "请输入服务器密码" }],
<Input.Password placeholder="请输入服务器密码" size="large" />,
true
)}
</Form>
</Modal>
<Modals visible={visible} title={"解除绑定"} content={TIPS} onOk={cancelBind} onCancel={onCancel}/>
<div className="disposeItem">
<AlignCenter>
<span>CI服务器地址</span>
{CIData && step >= 1 && <span className="ml10">{CIData.ip}</span>}
{ step === 0 && <span className="ml10 authTag red">未绑定</span> }
{ step > 0 && !ci_certification && <span className="ml10 authTag red">未认证</span> }
{ ci_certification && <span className="ml10 authTag green">已认证</span> }
</AlignCenter>
<AlignCenter>
{ step === 0 && <Button type={'primary'} onClick={()=>setBindVisible(true)} >马上绑定</Button> }
{ step > 0 && !ci_certification && <Button type={'primary'} style={{color:"#fff"}} onClick={()=>setAuthorityValBox(true)} target="_blank">马上认证</Button> }
{ step >= 1 && <Button type={'danger'} className="ml20" onClick={()=>setVisible(true)}>解除绑定</Button> }
</AlignCenter>
</div>
</div>
)
}
export default Form.create()(forwardRef(CIdispose));;

View File

@ -70,4 +70,52 @@ $flex:flex;
margin: 8px 0 0 0 !important;
}
}
}
ul.ant-menu.menuStyle{
padding:0px 30px;
font-size: 16px;
li{
height: 70px;
line-height: 70px;
padding:0px;
margin-right: 30px!important;
}
}
.disposeInfo{
padding:0px 30px;
min-height: 400px;
.disposeItem{
display: flex;
justify-content: space-between;
padding:30px 0px;
border-bottom: 1px solid #eee;
}
}
.authTag{
display: inline-block;
padding:0px 10px;
border-radius: 12px;
font-size: 12px;
height: 22px;
line-height: 22px;
&.red{
border:1px solid #F73030;
color: #F73030;
}
&.green{
border:1px solid #28BD6C;
color: #28BD6C;
}
}
.CIList{
padding:0px 30px;
min-height: 400px;
li{
display: flex;
justify-content: space-between;
padding:28px 0px;
border-bottom: 1px solid #eee;
}
}

View File

@ -14,6 +14,15 @@ import './Index.scss';
import Loadable from "react-loadable";
import Loading from "../../Loading";
const InfosDevOps = Loadable({
loader: () => import("./devOpsCI"),
loading: Loading,
});
const InfosDevOpsCD = Loadable({
loader: () => import("./devOpsCD"),
loading: Loading,
});
const InfosUser = Loadable({
loader: () => import("./InfosUser"),
loading: Loading,
@ -36,6 +45,19 @@ const UndoEvents = Loadable({
loader: () => import("./undo_events"),
loading: Loading,
})
const Wallet = Loadable({
loader: () => import("./sponsor/wallet"),
loading: Loading,
});
const SponsorManagement = Loadable({
loader: () => import("./sponsor/SponsorManagement"),
loading: Loading,
});
const SponsorDataCharts = Loadable({
loader: () => import("./sponsor/communityData"),
loading: Loading,
});
class Infos extends Component {
constructor(props) {
super(props);
@ -43,7 +65,10 @@ class Infos extends Component {
isSpin: false,
user: undefined,
project_type: undefined,
route_type: undefined
route_type: undefined,
c: undefined,
sponsor_management: undefined,
choose_chart: undefined,
};
}
@ -78,7 +103,10 @@ class Infos extends Component {
change_project_type = (type) => {
const {user} = this.state
this.setState({
project_type: type ,
project_type: type,
choose_wallet: false,
sponsor_management: undefined,
choose_chart: undefined,
route_type: undefined
})
let url = `/users/${user && user.login}`
@ -89,6 +117,15 @@ class Infos extends Component {
};
change_devops_type=(type)=>{
const {user} = this.state;
this.setState({
project_type: type ,
route_type: undefined
})
this.props.history.push(`/users/${user && user.login}/devops/${type}`);
}
undo_link = () => {
const {user} = this.state
this.setState({
@ -111,10 +148,63 @@ class Infos extends Component {
this.props.history.push(`/users/${user && user.login}/organizes`)
}
sponsor_link = () => {
const {user} = this.state
this.setState({
route_type: undefined
})
this.props.history.push(`/sponsor/${user && user.login}`)
}
sponsor_management_link = () => {
const {user} = this.state
this.setState({
route_type: undefined,
sponsor_management: true,
project_type: undefined,
choose_wallet: undefined,
choose_chart: undefined,
})
this.props.history.push(`/users/${user && user.login}/sponsor_management`)
}
wallet_link = () => {
const {user} = this.state
this.setState({
project_type: undefined,
choose_wallet: true,
route_type: undefined,
sponsor_management: undefined,
choose_chart: undefined,
})
this.props.history.push(`/users/${user && user.login}/wallet`)
}
sponsor_setting_link = () => {
const {user} = this.state
this.setState({
route_type: undefined
})
this.props.history.push(`/users/${user && user.login}/sponsor_setting`)
}
sponsor_statistic = () => {
const {user} = this.state
this.setState({
project_type: undefined,
choose_wallet: undefined,
route_type: undefined,
sponsor_management: undefined,
choose_chart: true
})
this.props.history.push(`/users/${user && user.login}/sponsor_community_data`)
}
render() {
const { current_user, mygetHelmetapi } = this.props;
const { user, isSpin, project_type, route_type } = this.state;
const { user, isSpin, project_type, route_type, choose_wallet, choose_chart, sponsor_management } = this.state;
// console.log("current", current_user)
// console.log("user", user)
return (
<div className="newMain clearfix">
<Spin spinning={isSpin}>
@ -137,10 +227,10 @@ class Infos extends Component {
<div>
{user && current_user && user.login === current_user.login && (
<div className="user-info-star-button ">
<div className="user-info-star-button">
<Button
block
className="text-button-grey"
className="text-button-grey mr5"
href={`${
mygetHelmetapi &&mygetHelmetapi.new_course&&
mygetHelmetapi.new_course.edit_account
@ -148,10 +238,19 @@ class Infos extends Component {
target="_blank"
>
{" "}
<i className="iconfont icon-shezhi4 font-15 mr5"></i>
<i className="iconfont icon-shezhi4 font-15"></i>
修改资料
</Button>
<Button
block
className="text-button-grey"
href={`/sponsor/${user && user.login}`}
>
赞助信息修改
</Button>
</div>
)}
{current_user && user && user.login !== current_user.login && (
<div className="user-info-star-button ">
@ -162,6 +261,13 @@ class Infos extends Component {
fontClass={"font-14 ml5"}
starText={"关注TA"}
/>
<Button
block
className="text-button-grey ml-5"
href={`/sponsor/${user && user.login}`}
>
赞助
</Button>
</div>
)}
</div>
@ -200,6 +306,7 @@ class Infos extends Component {
</div>
</div>
)} */}
<div className="bgcF">
<ul className="list-l-Menu">
<li className="MenuTitle" onClick={() => this.change_project_type(undefined)}>
@ -233,6 +340,33 @@ class Infos extends Component {
</li>
</ul>
</div>
{
current_user && current_user.login &&
<div className="bgcF">
<ul className="list-l-Menu">
<li className="MenuTitle">
<i className="iconfont icon-gongzuoliu font-15 mr5"></i>
DevOps工作流
<i className="iconfont icon-youjiantou font-15 mr20 color-grey-9 pull-right"></i>
</li>
<li className={project_type && project_type === "CIService" ? "active" : ""} onClick={() => this.change_devops_type("CIService")}>
<p>
<span className="font-16 color-grey-3">CI服务</span>
</p>
</li>
{/* <li className={project_type && project_type === "CDService" ? "active" : ""} onClick={() => this.change_devops_type("CDService")}>
<p>
<span className="font-16">CD服务</span>
<span className="color-blue">
{user && user.common_projects_count}
</span>
</p>
</li> */}
</ul>
</div>
}
{/* <div className="bgcF">
<div className="list-l-Menu">
<p className="list-l-p pd20" onClick={() => this.organize_link()} >
@ -245,7 +379,56 @@ class Infos extends Component {
</span>
</p>
</div>
</div> */}
</div> */}
{current_user && user && user.login == current_user.login && (
<div className="bgcF">
<ul className="list-l-Menu">
<li className="MenuTitle">
<i className="iconfont icon-bianzu1 font-15 mr5"></i>
赞助管理
<i className="iconfont icon-youjiantou font-15 mr20 color-grey-9 pull-right"></i>
</li>
<li className={choose_wallet ? "active" : ""} onClick={() => this.wallet_link()}>
<p>
<span className="font-16">我的钱包</span>
<span className="color-blue">
{/* {user && user.common_projects_count} */}
</span>
</p>
</li>
<li className={sponsor_management ? "active" : ""} onClick={() => this.sponsor_management_link()}>
<p>
<span className="font-16">赞助管理</span>
<span className="color-blue">
{/* {user && user.sync_mirror_projects_count} */}
</span>
</p>
</li>
</ul>
</div>
)}
{current_user && user && user.login == current_user.login && current_user.admin &&(
<div className="bgcF">
<ul className="list-l-Menu">
<li className="MenuTitle">
<i className="iconfont icon-dashujucunchu font-15 mr5"></i>
社区统计信息
<i className="iconfont icon-youjiantou font-15 mr20 color-grey-9 pull-right"></i>
</li>
<li className={choose_chart ? "active" : ""} onClick={() => this.sponsor_statistic()}>
<p>
<span className="font-16">赞助信息统计</span>
<span className="color-blue">
{/* {user && user.common_projects_count} */}
</span>
</p>
</li>
</ul>
</div>
)}
</div>
<div className="list-right">
{user && (
@ -263,12 +446,42 @@ class Infos extends Component {
return <UndoEvents {...this.props} {...this.state} />;
}}
></Route>
<Route
path="/users/:username/wallet"
render={(props) => {
return <Wallet {...this.props} {...this.state} />;
}}
></Route>
<Route
path="/users/:username/sponsor_management"
render={(props) => {
return <SponsorManagement {...this.props} {...this.state} />;
}}
></Route>
<Route
path="/users/:username/sponsor_community_data"
render={(props) => {
return <SponsorDataCharts {...this.props} {...this.state} is_sponsor={true}/>;
}}
></Route>
<Route
path="/users/:username/fan_users"
render={() => {
return <FanUser {...this.props} {...this.state} userType="fan_users"/>;
}}
></Route>
<Route
path="/users/:username/devops/CDService"
render={() => {
return <InfosDevOpsCD {...this.props} {...this.state} />;
}}
></Route>
<Route
path="/users/:username/devops/CIService"
render={() => {
return <InfosDevOps {...this.props} {...this.state} />;
}}
></Route>
<Route
path="/users/:username/projects/:project_type"
render={() => {

View File

@ -0,0 +1,8 @@
import React , { useEffect , useState } from 'react';
function DevOpsCD(){
return(
<div>222</div>
)
}
export default DevOpsCD;

View File

@ -0,0 +1,30 @@
import React , { useEffect , useState } from 'react';
import { Menu } from 'antd';
import './Index.scss';
import CIdispose from './CIdispose';
import CIList from './CIList';
function DevOpsCI(props){
const [ selectKey , setSelectKey ] = useState("dispose");
function changeKeys(e){
setSelectKey(e.key);
}
return(
<div>
<Menu mode="horizontal" className="menuStyle" onClick={changeKeys} selectedKeys={[selectKey]}>
<Menu.Item key="dispose">服务器配置</Menu.Item>
<Menu.Item key="list">项目列表</Menu.Item>
</Menu>
{
selectKey === "dispose" ?
<CIdispose {...props}/>
:
<CIList {...props}/>
}
</div>
)
}
export default DevOpsCI;

View File

@ -28,10 +28,15 @@
.mt-n15{margin-top: -15px;}
.position-relative{position: relative;}
.mr-5{margin-right: 5px;}
.mt-5{margin-top: 5px;}
.ml-5{margin-left: 5px;}
a.text-button-grey{
color: rgba(0, 0, 0, 0.65)
}
.user-info-star-button{margin: 20px 50px 0 50px}
.user-info-star-button{
display: flex;
margin: 20px 20px 0 20px
}
.list-l-p{
width: 100%;
display: -ms-flexbox;

View File

@ -0,0 +1,45 @@
import React, { Component } from 'react';
import { Tooltip } from 'antd';
import '../../css/index.scss'
import '../../Main/list.css';
class CoinChangeItem extends Component {
TurnToDetail = (login, url) => {
this.props.history.push({
pathname: url,
state: login
})
}
render() {
const { coinChanges, current_user } = this.props;
const renderList = (
coinChanges && coinChanges.length > 0 ? coinChanges.map((item, key) => {
let url = current_user && item.to_user_login == current_user.login? `/users/${item.from_user_login}`:`/users/${item.to_user_login}`;
return (
<div className="p-r-Item" key={key}>
<div className="p-r-Infos">
<p className="break_word task-hide-2 mt10" style={{ maxHeight: "44px",lineHeight:"22px" }}>金额{current_user && item.to_user_login == current_user.login? "收入":"支出"}{item.amount}硬币</p>
<p className="break_word task-hide-2 mt10" style={{ maxHeight: "44px",lineHeight:"22px" }}>变动原因{item.reason}</p>
{/* <p className="break_word task-hide-2 mt10" style={{ maxHeight: "44px",lineHeight:"22px" }}>备注:{item.description}</p> */}
<div className="p-r-about">
<span className="p-r-detail">
<span><label>时间</label>{item.date}</span>
{current_user && item.to_user_login == current_user.login?
(<span><label>金额来源</label><a href={url}>{item.from_user}</a></span>):
(<span><label>金额去向</label><a href={url}>{item.to_user}</a></span>)}
</span>
</div>
</div>
</div>
)
}) : ""
)
return (
<div className="project-list minH-670">
{renderList}
</div>
)
}
}
export default CoinChangeItem;

View File

@ -0,0 +1,309 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Menu, Input, Spin, Pagination, Popover, Button, Divider } from "antd";
import SponsorBarCharts from "./charts/sponsorBarChart";
import SponsorPieCharts from "./charts/sponsorPieChart";
import axios from "axios";
import CoinChangeItem from "./CoinChangeItem";
import Nodata from "../../Nodata";
import img_array from "../../Images/array.png";
import SponsorList from "./sponsor_list";
import "../../sponsor/sponsor.css";
// import "../new_user.css";
// const Search = Input.Search;
// const tempData = [
// {image_url: "avatars/User/b", username: "万山川", user_id: 1369, visible: true, sponsor: false, login:"alpc104", start_time: "2020-08-01", stop_time: "-", amount: 100},
// {image_url: "avatars/User/b", username: "万山川", user_id: 1369, visible: true, sponsor: true, login:"alpc104", start_time: "2020-08-01", stop_time: "-", amount: 100},
// {image_url: "avatars/User/b", username: "万山川", user_id: 1369, visible: false, sponsor: false, login:"alpc104", start_time: "2020-08-01", stop_time: "-", amount: 100},
// {image_url: "avatars/User/b", username: "万山川", user_id: 1369, visible: false, sponsor: true, login:"alpc104", start_time: "2020-08-01", stop_time: "-", amount: 100},
// {image_url: "avatars/User/b", username: "万山川", user_id: 1369, visible: false, sponsor: false, login:"alpc104", start_time: "2020-08-01", stop_time: "-", amount: 100},
// {image_url: "avatars/User/b", username: "万山川", user_id: 1369, visible: false, sponsor: false, login:"alpc104", start_time: "2020-08-01", stop_time: "2020-09-05", amount: 100},
// {image_url: "avatars/User/b", username: "万山川", user_id: 1369, visible: false, sponsor: true, login:"alpc104", start_time: "2020-08-01", stop_time: "2020-09-05", amount: 100},
// {image_url: "avatars/User/b", username: "万山川", user_id: 1369, visible: false, sponsor: false, login:"alpc104", start_time: "2020-08-01", stop_time: "2020-09-05", amount: 100},
// {image_url: "avatars/User/b", username: "万山川", user_id: 1369, visible: false, sponsor: false, login:"alpc104", start_time: "2020-08-01", stop_time: "2020-09-05", amount: 100},
// {image_url: "avatars/User/b", username: "万山川", user_id: 1369, visible: false, sponsor: false, login:"alpc104", start_time: "2020-08-01", stop_time: "2020-09-05", amount: 100},
// {image_url: "avatars/User/b", username: "万山川", user_id: 1369, visible: false, sponsor: false, login:"alpc104", start_time: "2020-08-01", stop_time: "2020-09-05", amount: 100},
// ];
class SponsorManagement extends Component{
constructor(props) {
super(props);
this.state = {
page: 1,
limit: 15,
sort_by: undefined,
totalCount: undefined,
isSpin: false,
total: undefined,
category: "sponsoring",
is_sponsor: false,
sponsors: undefined,
displayList: undefined,
chartsData: {income:[], outcome:[]},
};
}
changeCategory = (cate) => {
this.setState({
category: cate.target.value,
page: 1,
}, ()=>{
if(this.state.category === "chart")
this.get_chart_data();
else
this.get_sponsors();
});
// if(cate.target.value === "chart"){
// this.setState({
// category: cate.target.value,
// page: 1,
// });
// }
// else{
// this.setState({
// category: cate.target.value,
// page: 1,
// }, ()=>{this.get_sponsors();});
// }
};
componentDidMount = () => {
this.get_sponsors();
}
get_chart_data = () => {
this.setState({
isSpin: true
})
let url = `/wallets/balance_chart.json`;
const {current_user} = this.props;
let user_id = current_user && current_user.user_id;
if (current_user){
axios.get(url, {
params: {
id: user_id,
sponsor: true,
}})
.then((result) => {
this.setState({
chartsData: result.data,
isSpin: false,
})
})
.catch((error) => {
console.log(error);
this.setState({
isSpin: false,
});
});
}
};
get_sponsors = ()=>{
const user_id = this.props.current_user.user_id;
let url = this.state.is_sponsor? `sponsoring`:`sponsored`;
url = this.state.category === "stopped"? `stopped_` + url: url;
url = `/sponsorships/` + url + `.json`;
const {page, limit} = this.state;
this.setState({
isSpin: true
})
axios.get(url, {
params: {
id: user_id,
page: page,
limit: limit,
}})
.then((result) => {
// console.log(result);
this.setState({
displayList: result.data.sponsorships,
total: result.data.count,
isSpin: false,
})
})
.catch((error) => {
console.log(error);
this.setState({
isSpin: false,
});
});
// this.setState({
// sponsors: tempData,
// displayList: tempData,
// })
}
//切换页数
changePage = (page) => {
this.setState({
page: page
}, ()=>{this.get_sponsors()})
};
// 排序
ChangeSoryBy = (e) => {
console.log(e.key)
};
changeSearchValue = (e) => {
this.setState({
search: e.target.value,
});
};
menu =()=> (
<Menu onClick={this.ChangeSoryBy}>
<Menu.Item key="time_order">时间从近到远排序</Menu.Item>
<Menu.Item key="reverse_time_order">时间从远到近排序</Menu.Item>
</Menu>
);
category_button=(category)=>{
const { current_user, user } = this.props;
const button_lists =
user && current_user && user.login === current_user.login
? [
{ type: "sponsoring", name: "赞助中" },
{ type: "stopped", name: "已终止" },
{ type: "chart", name: "统计图表" },
]
:
[];
let list = button_lists.map((item, key) => {
return (
<span key={key} className="pr15">
<Button
type={
(category && category === item.type) || (!category && !item.type)
? "primary"
: "default"
}
ghost={
(category && category === item.type) || (!category && !item.type)
}
value={item.type}
onClick={this.changeCategory}
>
{item.name}
</Button>
</span>
);
})
return list;
}
changeStatus=(check_is_sponsor, e)=>{
// const {is_sponsor} = this.state
// const new_is_sponsor = is_sponsor === check_is_sponsor ? undefined : check_is_sponsor
// this.state.is_sponsor = check_is_sponsor;
this.setState({
is_sponsor: check_is_sponsor,
page: 1,
}, ()=>{
if(this.state.category === "chart")
this.get_chart_data();
else
this.get_sponsors();
});
}
render() {
const { current_user, user } = this.props;
const { category , is_sponsor, sponsors, displayList, chartsData} = this.state;
const {isSpin, total, search, limit, page }= this.state;
// console.log(displayList);
const sponsorList = displayList && displayList.length > 0 ?
<SponsorList
{...this.props}
userClass={"w-33"}
current_user={current_user}
users={displayList}
rerender={this.get_sponsors}
history={this.props.history}
></SponsorList>
:
<Nodata _html={`暂时没有数据`} />
const chartComponent = <div>
<p>七天内赞助{this.state.is_sponsor?"支出":"收入"}</p>
<SponsorBarCharts is_sponsor={this.state.is_sponsor} data={chartsData}></SponsorBarCharts>
{/* <p>{this.state.is_sponsor?"":""}</p>
<SponsorPieCharts is_sponsor={this.state.is_sponsor}></SponsorPieCharts> */}
</div>
const displayComponent = category==="chart"?chartComponent
:
sponsorList;
return (
// <div className="list-right boxShandow radius-2" style={{padding:0}}>
<Spin spinning={isSpin}>
{/* <div className="list-r-operation"> */}
{/* <Search
placeholder="输入关键字进行搜索"
enterButton="搜索"
size="large"
onSearch={this.get_sponsors}
className="list-r-Search"
value={search}
onChange={this.changeSearchValue}
/> */}
{/* <div>
<Popover content={this.menu()} trigger={["click"]} placement="bottom">
<a className="ant-dropdown-link">
<span className="color-blue font-16">
排序 <img src={img_array} alt="" width="10px" />
</span>
</a>
</Popover>
</div> */}
{/* </div> */}
<div className="infosType">
<div>{this.category_button(category)}</div>
{
user && current_user && user.login === current_user.login ?
<div className="infoStatus">
<span className={is_sponsor === true ? "active" : "" } onClick={(e)=>this.changeStatus(true, e)}>我赞助的</span>
{
!is_sponsor ? (<Divider type={"vertical"} className="statusDivider" ></Divider>):""
}
<span className={is_sponsor === false ? "active" : "" } onClick={(e)=>this.changeStatus(false, e)}>赞助我的</span>
</div>
:""
}
</div>
{user && current_user && user.login === current_user.login?
displayComponent:<Nodata _html={`暂时没有项目`} />
}
{
total && total > limit && category!="chart"?
<div className="edu-txt-center pt30 mb30 border-top-grey">
<Pagination
simple
defaultCurrent={page}
total={total}
pageSize={limit}
onChange={this.changePage}
></Pagination>
</div>
:
""
}
</Spin>
// </div>
);
}
};
export default SponsorManagement;

View File

@ -0,0 +1,77 @@
import React, { Component } from 'react';
import {XYPlot, VerticalBarSeries, VerticalGridLines, HorizontalGridLines, XAxis, YAxis, Crosshair, DiscreteColorLegend} from 'react-vis';
// import DiscreteColorLegend from 'legends/discrete-color-legend';
import "./style.scss";
// const ONE_DAY = 86400000;
// const d = new Date().getTime()-ONE_DAY*7;
// const data = [
// {x: new Date(d+ONE_DAY).toLocaleDateString(), y: 8},
// {x: new Date(d+ONE_DAY*2).toLocaleDateString(), y: 5},
// {x: new Date(d+ONE_DAY*3).toLocaleDateString(), y: 4},
// {x: new Date(d+ONE_DAY*4).toLocaleDateString(), y: 9},
// {x: new Date(d+ONE_DAY*5).toLocaleDateString(), y: 2},
// {x: new Date(d+ONE_DAY*6).toLocaleDateString(), y: 7},
// {x: new Date(d+ONE_DAY*7).toLocaleDateString(), y: 2},
// ];
// const data2 = [
// {x: new Date(d+ONE_DAY).toLocaleDateString(), y: 2},
// {x: new Date(d+ONE_DAY*2).toLocaleDateString(), y: 3},
// {x: new Date(d+ONE_DAY*3).toLocaleDateString(), y: 1},
// {x: new Date(d+ONE_DAY*4).toLocaleDateString(), y: 5},
// {x: new Date(d+ONE_DAY*5).toLocaleDateString(), y: 4},
// {x: new Date(d+ONE_DAY*6).toLocaleDateString(), y: 0},
// {x: new Date(d+ONE_DAY*7).toLocaleDateString(), y: 2},
// ];
const ITEMS = [
{title: '收入', strokeWidth: 13},
{title: '支出', strokeWidth: 13}
];
class CoinChangeCharts extends Component {
constructor(props) {
super(props);
this.state = {
crosshairValues: []
};
// console.log(this.props);
}
render() {
const {chartsData} = this.props;
if(chartsData == undefined){
return (<div className="App"></div>);
}
var max = 0;
for(var i=0; i<chartsData.income.length; i++){
max = Math.max(max, chartsData.income[i].y, chartsData.outcome[i].y);
}
return (
<div className="App">
<DiscreteColorLegend height={80} width={1300} items={ITEMS} />
<XYPlot xType="ordinal" height={400} width= {700} yDomain={[-0.5, max]}>
<VerticalGridLines />
<HorizontalGridLines />
<XAxis style={{
line: {stroke: '#ADDDE1'},
ticks: {stroke: '#ADDDE1'},
text: {stroke: 'none', fill: '#6b6b76', fontWeight: 600}
}}/>
<YAxis style={{
line: {stroke: '#ADDDE1'},
ticks: {stroke: '#ADDDE1'},
text: {stroke: 'none', fill: '#6b6b76', fontWeight: 600}
}}/>
<VerticalBarSeries data={chartsData.income} />
<VerticalBarSeries data={chartsData.outcome} />
</XYPlot>
</div>
);
}
}
export default CoinChangeCharts;

View File

@ -0,0 +1,79 @@
import React, { Component } from 'react';
import {XYPlot, VerticalBarSeries, VerticalGridLines, HorizontalGridLines, XAxis, YAxis, Crosshair, DiscreteColorLegend} from 'react-vis';
// import DiscreteColorLegend from 'legends/discrete-color-legend';
import "./style.scss";
const ONE_DAY = 86400000;
const d = new Date().getTime()-ONE_DAY*7;
const data = [
{x: new Date(d+ONE_DAY).toLocaleDateString(), y: 6},
{x: new Date(d+ONE_DAY*2).toLocaleDateString(), y: 3},
{x: new Date(d+ONE_DAY*3).toLocaleDateString(), y: 2},
{x: new Date(d+ONE_DAY*4).toLocaleDateString(), y: 7},
{x: new Date(d+ONE_DAY*5).toLocaleDateString(), y: 0},
{x: new Date(d+ONE_DAY*6).toLocaleDateString(), y: 5},
{x: new Date(d+ONE_DAY*7).toLocaleDateString(), y: 0},
];
const data2 = [
{x: new Date(d+ONE_DAY).toLocaleDateString(), y: 3},
{x: new Date(d+ONE_DAY*2).toLocaleDateString(), y: 4},
{x: new Date(d+ONE_DAY*3).toLocaleDateString(), y: 2},
{x: new Date(d+ONE_DAY*4).toLocaleDateString(), y: 6},
{x: new Date(d+ONE_DAY*5).toLocaleDateString(), y: 5},
{x: new Date(d+ONE_DAY*6).toLocaleDateString(), y: 1},
{x: new Date(d+ONE_DAY*7).toLocaleDateString(), y: 3},
];
const ITEMS = [
{title: '收入', strokeWidth: 13},
{title: '支出', strokeWidth: 13}
];
class SponsorBarCharts extends Component {
constructor(props) {
super(props);
this.state = {
crosshairValues: []
};
console.log(this.props)
}
componentWillReceiveProps = () => {
this.setState({
crosshairValues: []
})
}
render() {
const chartsData = this.props.is_sponsor?this.props.data.outcome:this.props.data.income;
var max = 0;
for(var i=0; i<chartsData.length; i++){
max = Math.max(max, chartsData[i].y);
}
return (
<div className="App">
{/* <DiscreteColorLegend height={20} width={1300} items={ITEMS} /> */}
<XYPlot animation={true} xType="ordinal" height={400} width= {700} yDomain={[-0.5, max]}>
<VerticalGridLines />
<HorizontalGridLines />
<XAxis style={{
line: {stroke: '#ADDDE1'},
ticks: {stroke: '#ADDDE1'},
text: {stroke: 'none', fill: '#6b6b76', fontWeight: 600}
}}/>
<YAxis style={{
line: {stroke: '#ADDDE1'},
ticks: {stroke: '#ADDDE1'},
text: {stroke: 'none', fill: '#6b6b76', fontWeight: 600}
} }/>
<VerticalBarSeries data={chartsData} />
</XYPlot>
</div>
);
}
}
export default SponsorBarCharts;

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