Merge branch 'pre_gitlink_ssr' of https://gitlink.org.cn/Gitlink/forgeplus-react into pre_gitlink_ssr
|
|
@ -1,4 +1,5 @@
|
|||
@charset "utf-8";
|
||||
|
||||
/* 头部 */
|
||||
.header {
|
||||
width: 100%;
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 20 KiB |
|
|
@ -198,6 +198,12 @@ form .ant-cascader-picker, form .ant-select {
|
|||
.linesContent .lines > span:first-child{
|
||||
margin-right: 0px;
|
||||
}
|
||||
.linesContent .lines,.linesContent .linetype,.no-select,.filesInfo{
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE/Edge */
|
||||
user-select: none; /* 标准语法 */
|
||||
}
|
||||
.linesContent .lines > span{
|
||||
width: 50%;
|
||||
text-align: right;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@ const Pages = Loadable({
|
|||
loader: () => import('./pages'),
|
||||
loading: Loading,
|
||||
})
|
||||
// 开源度量工具
|
||||
const Measurement = Loadable({
|
||||
loader: () => import('./measurement'),
|
||||
loading: Loading,
|
||||
})
|
||||
const MeasurementDetail = Loadable({
|
||||
loader: () => import('./measurement/dimension'),
|
||||
loading: Loading,
|
||||
})
|
||||
function ServerIndex(props){
|
||||
const { projectDetail } = props;
|
||||
const { owner , projectsId } = props.match.params;
|
||||
|
|
@ -37,11 +46,21 @@ function ServerIndex(props){
|
|||
return(
|
||||
<div className="panels">
|
||||
<Switch {...props}>
|
||||
<Route path="/:owner/:projectsId/service/measurement/:type"
|
||||
render={
|
||||
(p) => (<MeasurementDetail {...props} {...p}/>)
|
||||
}
|
||||
></Route>
|
||||
<Route path="/:owner/:projectsId/service/reposyncer"
|
||||
render={
|
||||
() => (<Reposyncer {...props}/>)
|
||||
}
|
||||
></Route>
|
||||
<Route path="/:owner/:projectsId/service/measurement"
|
||||
render={
|
||||
(p) => (<Measurement {...props} {...p}/>)
|
||||
}
|
||||
></Route>
|
||||
<Route path="/:owner/:projectsId/service/codeanalysis"
|
||||
render={
|
||||
() => (<Data {...props}/>)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ function Main(props){
|
|||
const [ has_trace_user , setHas_trace_user ] = useState(false);
|
||||
|
||||
const { current_user , resetUserInfo, projectDetail, showNotification, isDeveloper, isManager, isReporter } = props;
|
||||
const privateStatus = projectDetail && projectDetail.private;
|
||||
|
||||
useEffect(()=>{
|
||||
// 更新网页标题
|
||||
|
|
@ -79,6 +80,19 @@ function Main(props){
|
|||
<Link to={`/${owner}/${projectsId}/service/pages`} className="btnhover">查看详情</Link>
|
||||
</span>
|
||||
</li>}
|
||||
{
|
||||
!privateStatus &&
|
||||
<li>
|
||||
<span className="servername">
|
||||
<img src={require('./img/logo4.png')} alt=""/>
|
||||
<a onClick={openDetail} style={{marginLeft:"-8px"}}>开源软件健康度量服务</a>
|
||||
</span>
|
||||
<p className="task-hide-2 serverdesc">支持项目健康度量分析一键生成,协助维护项目健康生态</p>
|
||||
<span className="serverbtn">
|
||||
<Link to={`/${owner}/${projectsId}/service/measurement`} className="btnhover">查看详情</Link>
|
||||
</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 633 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 460 B |
|
After Width: | Height: | Size: 451 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -461,4 +461,35 @@
|
|||
.timeBox{
|
||||
color: rgba(70, 106, 255, 1)
|
||||
}
|
||||
}
|
||||
.nullStoreBox{
|
||||
background-color:#fafcff;
|
||||
border-radius:4px 4px 0px 0px;
|
||||
text-align: center;
|
||||
color:#333333;
|
||||
padding-bottom: 65px;
|
||||
.loBox{width: 68px;}
|
||||
.introBox{
|
||||
color:#666666;
|
||||
width: 57%;
|
||||
margin: 15px auto;
|
||||
word-break: break-all;
|
||||
}
|
||||
.borBox{
|
||||
width: 45%;
|
||||
margin: 0 auto 20px;
|
||||
border-bottom: 1px solid rgba(90, 117, 193, 0.23);
|
||||
}
|
||||
}
|
||||
.headBox{
|
||||
height:60px;
|
||||
line-height: 60px;
|
||||
background-color:#fafcff;
|
||||
border:1px solid rgba(42, 97, 255, 0.23);
|
||||
border-radius:3px 3px 0px 0px;
|
||||
color:#333333;
|
||||
position: relative;
|
||||
em{
|
||||
font-family: YouSheBiaoTiHei;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import StackImg from './stackImg';
|
||||
import Pie from './pie';
|
||||
import { Select } from 'antd';
|
||||
import Nodata from '../../../Nodata';
|
||||
|
||||
function CompanyData({ data }){
|
||||
|
||||
|
||||
const [ keys , setKeys ] = useState([]);
|
||||
const [ values , setValues ] = useState([]);
|
||||
const [ userDrop , setUserDrop ] = useState([]);
|
||||
const [ userValue , setUserValue ] = useState([]);
|
||||
|
||||
|
||||
const [ c1keys , setC1Keys ] = useState([]);
|
||||
const [ c1values , setC1Values ] = useState([]);
|
||||
const [ c1userDrop , setC1UserDrop ] = useState([]);
|
||||
const [ c1userValue , setC1UserValue ] = useState([]);
|
||||
|
||||
const [ c3keys , setC3Keys ] = useState([]);
|
||||
const [ c3values , setC3Values ] = useState([]);
|
||||
const [ c3userDrop , setC3UserDrop ] = useState([]);
|
||||
const [ c3userValue , setC3UserValue ] = useState([]);
|
||||
|
||||
const [ pieValues , setPieValues ] = useState([]);
|
||||
|
||||
|
||||
const { c1 ,c2,c3,c4 } = data;
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(c1 && c1.length > 0){
|
||||
const values = Array.from(new Set(c1.map(i => i.Module || "")));
|
||||
setC1UserDrop(values);
|
||||
setC1UserValue(values[0]);
|
||||
}
|
||||
},[c1])
|
||||
|
||||
useEffect(()=>{
|
||||
if(c1userValue && c1 && c1.length > 0){
|
||||
let fil = c1.filter(i=> i.Module === c1userValue);
|
||||
const companyArray = Array.from(new Set(fil.map(i => i.Company)));
|
||||
let monthList = fil.map(obj => obj.Month);
|
||||
let valueList = fil.map(obj => obj['Weight Ratio']);
|
||||
setC1Keys(monthList);
|
||||
if(companyArray && companyArray.length>0 && fil.length>0){
|
||||
const arr = companyArray.map(item=>{
|
||||
let v = fil.filter(i=> i.Company == item);
|
||||
let valueList = v.map(obj => obj['Weight Ratio']);
|
||||
return {name :item , type: 'line',stack: 'Total',data:valueList}
|
||||
})
|
||||
setC1Values(arr);
|
||||
}else{
|
||||
setC1Values(valueList);
|
||||
}
|
||||
}
|
||||
},[c1,c1userValue])
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(c2 && c2.length > 0){
|
||||
const values = Array.from(new Set(c2.map(i => i.Company || "")));
|
||||
setUserDrop(values);
|
||||
setUserValue(values[0]);
|
||||
}
|
||||
},[c2])
|
||||
|
||||
useEffect(()=>{
|
||||
if(userValue && c2 && c2.length > 0){
|
||||
let fil = c2.filter(i=>i.Company === userValue);
|
||||
let monthList = fil.map(obj => obj.Month);
|
||||
let valueList = fil.map(obj => obj['Total Weight']);
|
||||
setKeys(monthList);
|
||||
setValues(valueList);
|
||||
}
|
||||
},[c2,userValue])
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(c3 && c3.length > 0){
|
||||
const values = Array.from(new Set(c3.map(i => i.Company || "")));
|
||||
setC3UserDrop(values);
|
||||
setC3UserValue(values[0]);
|
||||
}
|
||||
},[c3])
|
||||
useEffect(()=>{
|
||||
if(c3userValue && c3 && c3.length > 0){
|
||||
let fil = c3.filter(i=>i.Company === c3userValue);
|
||||
let monthList = fil.map(obj => obj.Month);
|
||||
let valueList = fil.map(obj => parseInt(obj['Percentage']));
|
||||
setC3Keys(monthList);
|
||||
setC3Values(valueList);
|
||||
}
|
||||
},[c3,c3userValue])
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(c4 && c4.length > 0){
|
||||
let pieValues = c4.map(i=>{return {name:i['Company'],value:i["Commit Count"]}});
|
||||
setPieValues(pieValues)
|
||||
}
|
||||
},[c4])
|
||||
|
||||
return(
|
||||
(!c1 && !c2 && !c3 && !c4) ? <div>
|
||||
<div className='dropEchartPanel'>
|
||||
{
|
||||
c1userValue && c1userDrop && c1userDrop.length>0 &&
|
||||
<div style={{textAlign:"left",paddingLeft:20}}>
|
||||
<span className='mr10'>公司名:</span>
|
||||
<Select
|
||||
value={c1userValue}
|
||||
style={{ width: 220 }}
|
||||
onChange={(e)=>{setC1UserValue(e);}}
|
||||
>
|
||||
{c1userDrop.map(value => {
|
||||
return <Option key={value}>{value}</Option>
|
||||
})}
|
||||
</Select>
|
||||
</div>
|
||||
}
|
||||
<StackImg x={c1keys} values={c1values} title={'每月公司贡献占比'} id={"c1"}/>
|
||||
</div>
|
||||
<div className='dropEchartPanel'>
|
||||
{
|
||||
userValue && userDrop && userDrop.length>0 &&
|
||||
<div style={{textAlign:"left",paddingLeft:20}}>
|
||||
<span className='mr10'>公司名:</span>
|
||||
<Select
|
||||
value={userValue}
|
||||
style={{ width: 220 }}
|
||||
onChange={(e)=>{setUserValue(e);}}
|
||||
>
|
||||
{userDrop.map(value => {
|
||||
return <Option key={value}>{value}</Option>
|
||||
})}
|
||||
</Select>
|
||||
</div>
|
||||
}
|
||||
<StackImg x={keys} values={[{name:"", type: 'line',stack: 'Total',data:values}]} title={'每月公司总贡献'} id={"c2"}/>
|
||||
</div>
|
||||
<div className='dropEchartPanel'>
|
||||
{
|
||||
c3userValue && c3userDrop && c3userDrop.length>0 &&
|
||||
<div style={{textAlign:"left",paddingLeft:20}}>
|
||||
<span className='mr10'>公司名:</span>
|
||||
<Select
|
||||
value={c3userValue}
|
||||
style={{ width: 220 }}
|
||||
onChange={(e)=>{setC3UserValue(e);}}
|
||||
>
|
||||
{c3userDrop.map(value => {
|
||||
return <Option key={value}>{value}</Option>
|
||||
})}
|
||||
</Select>
|
||||
</div>
|
||||
}
|
||||
<StackImg x={c3keys} values={[{name:"", type: 'line',stack: 'Total',data:c3values}]} title={'每月公司贡献最高模块中总贡献'} id={"c3"}/>
|
||||
</div>
|
||||
<div className='dropEchartPanel'>
|
||||
<Pie title={'公司总提交数'} values={pieValues}/>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<Nodata _html="暂无数据~"/>
|
||||
)
|
||||
}
|
||||
export default CompanyData;
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import StackImg from './stackImg';
|
||||
import { Select } from 'antd';
|
||||
|
||||
function DataStack({ data }){
|
||||
const [ keys , setKeys ] = useState([]);
|
||||
const [ values , setValues ] = useState([]);
|
||||
|
||||
|
||||
const [ userDrop , setUserDrop ] = useState([]);
|
||||
const [ userValue , setUserValue ] = useState([]);
|
||||
useEffect(()=>{
|
||||
if(data){
|
||||
let drop = Object.keys(data);
|
||||
setUserDrop(drop);
|
||||
setUserValue(drop[0]);
|
||||
}
|
||||
},[data])
|
||||
|
||||
useEffect(()=>{
|
||||
if(data && userValue){
|
||||
let arr = data[userValue];
|
||||
let ks = arr && Object.keys(arr);
|
||||
let vs = arr && Object.values(arr);
|
||||
setKeys(ks);
|
||||
setValues(vs);
|
||||
}
|
||||
},[data,userValue])
|
||||
|
||||
return(
|
||||
<div>
|
||||
{
|
||||
userValue && userDrop && userDrop.length>0 &&
|
||||
<div style={{textAlign:"left",paddingLeft:20}}>
|
||||
<span className='mr10'>公司名:</span>
|
||||
<Select
|
||||
value={userValue}
|
||||
style={{ width: 220 }}
|
||||
onChange={(e)=>{setUserValue(e);}}
|
||||
>
|
||||
{userDrop.map(value => {
|
||||
return <Option key={value}>{value}</Option>
|
||||
})}
|
||||
</Select>
|
||||
</div>
|
||||
}
|
||||
<StackImg x={keys} values={[{name:"", type: 'line',stack: 'Total',data:values}]} id={"companyNum"}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default DataStack;
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import React, { useEffect , useState } from 'react';
|
||||
import { Table } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import "../index.scss";
|
||||
import { fieldData , thirdData , subField } from '../config';
|
||||
|
||||
function Indicator({owner , projectsId , data , subCardIndex}){
|
||||
const [ dataSource , setDataSource ] = useState([]);
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(data && data.length > 0 && subCardIndex){
|
||||
let name = thirdData[subField[subCardIndex]];
|
||||
let s = data.filter(i=>name.includes(i.name));
|
||||
setDataSource(s);
|
||||
}
|
||||
},[subCardIndex,data])
|
||||
|
||||
const column = [
|
||||
{
|
||||
title: "指标名称",
|
||||
dataIndex: "name",
|
||||
render:(value)=>{
|
||||
return(
|
||||
(value !=="branchNum" && value !== "browseNum") ?
|
||||
<Link to={`/${owner}/${projectsId}/service/measurement/${value}`} className='color-blue'>{fieldData[value]}</Link>
|
||||
:
|
||||
<span>{fieldData[value]}</span>
|
||||
)
|
||||
// return fieldData[value]
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "度量得分",
|
||||
dataIndex: "InitialScore",
|
||||
align:"center",
|
||||
render:(v)=>{
|
||||
return `${Math.round(v)}`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "权重占比",
|
||||
align:"center",
|
||||
dataIndex: "Weight",
|
||||
render:(v)=>{
|
||||
return `${v ? parseFloat(v*100).toFixed(1) : 0}%`
|
||||
}
|
||||
},
|
||||
]
|
||||
return(
|
||||
<Table columns={column} dataSource={dataSource} pagination={false} size='small' className='smalltable'/>
|
||||
)
|
||||
}
|
||||
export default Indicator;
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import * as echarts from 'echarts';
|
||||
// title:公司总提交数
|
||||
function Pie({values,title}){
|
||||
useEffect(()=>{
|
||||
if(values && values.length>0){
|
||||
Init(values)
|
||||
}
|
||||
},[values])
|
||||
|
||||
function Init(data){
|
||||
var huan_val = document.getElementById("Pie");
|
||||
var myEcharts = echarts.init(huan_val);
|
||||
let option = {
|
||||
title: {
|
||||
text: title || "",
|
||||
left: 20,
|
||||
top:20
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
color:['#5871FF',"#FFBE38","#00bf9a"],
|
||||
legend: {
|
||||
orient: 'horizontal',
|
||||
right: 20,
|
||||
top:20,
|
||||
formatter: function (name) {
|
||||
// 查找对应系列的值
|
||||
var data = option.series[0].data;
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (data[i].name == name) {
|
||||
return data[i].value === 0 ? name + ' (0)' : name;
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '',
|
||||
type: 'pie',
|
||||
radius: '50%',
|
||||
data: data,
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
},
|
||||
center: ['50%', '60%']
|
||||
}
|
||||
]
|
||||
};
|
||||
myEcharts.setOption(option);
|
||||
}
|
||||
return(
|
||||
<div id="Pie" style={{height:"300px"}}></div>
|
||||
)
|
||||
}
|
||||
export default Pie;
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import React, { useEffect , useState } from 'react';
|
||||
import * as echarts from 'echarts';
|
||||
import { fieldData } from '../config';
|
||||
|
||||
function Radar({data, subCardIndex}){
|
||||
const [ values , setValues ] = useState([]);
|
||||
const [ titles , setTitles ] = useState([]);
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(data && data.length > 0 && subCardIndex){
|
||||
let d = [...data];
|
||||
let v = [];
|
||||
d = d.map(i=>{
|
||||
v = v.concat(i.InitialScore)
|
||||
return {name:fieldData[i.name],max:100}
|
||||
})
|
||||
setValues(v);
|
||||
setTitles(d);
|
||||
}
|
||||
},[subCardIndex,data])
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(titles && values && values.length>0 &&titles.length>0)
|
||||
Init();
|
||||
},[values,titles])
|
||||
|
||||
function Init(){
|
||||
var huan_val = document.getElementById("radar");
|
||||
var myEcharts = echarts.init(huan_val);
|
||||
let option = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
title: {
|
||||
text: ''
|
||||
},
|
||||
color:['#5871FF',"#FFBE38"],
|
||||
radar: {
|
||||
// shape: 'circle',
|
||||
indicator: titles,
|
||||
radius:'50%',
|
||||
// splitLine:{
|
||||
// lineStyle:{
|
||||
// color:'#11B0D3',
|
||||
// opacity: 0.2
|
||||
// },
|
||||
// },
|
||||
// axisLine:{
|
||||
// lineStyle:{
|
||||
// color:'#11B0D3',
|
||||
// opacity: 0.2
|
||||
// },
|
||||
// },
|
||||
// splitArea:{
|
||||
// areaStyle:{
|
||||
// color: 'none',
|
||||
// }
|
||||
// },
|
||||
nameGap: 3,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '',
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
value: values,
|
||||
name: ''
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
myEcharts.setOption(option);
|
||||
}
|
||||
return(
|
||||
<div id="radar" style={{height:"300px",width:"45%"}}></div>
|
||||
)
|
||||
}
|
||||
export default Radar;
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import * as echarts from 'echarts';
|
||||
import { detailByName } from '../config';
|
||||
import Pie from './pie';
|
||||
import { Select } from 'antd';
|
||||
|
||||
function Stack({name,data , type , pieData}){
|
||||
const [ obj , setObj ] = useState({});
|
||||
const [ list , setList ] = useState(data);
|
||||
|
||||
const [ userDrop , setUserDrop ] = useState([]);
|
||||
const [ userValue , setUserValue ] = useState([]);
|
||||
|
||||
const [pieEchartData,setPieEchartData] = useState([]);
|
||||
|
||||
useEffect(()=>{
|
||||
if(name){
|
||||
let obj = detailByName[name.replace(/\s/g, '')];
|
||||
setObj(obj);
|
||||
}
|
||||
},[name])
|
||||
|
||||
useEffect(()=>{
|
||||
if(data && data.length > 0){
|
||||
if(obj && obj.user_login){
|
||||
const values = Array.from(new Set(data.map(i => i.user_login || "")));
|
||||
setUserDrop(values);
|
||||
setUserValue(values[0]);
|
||||
}else{
|
||||
setList(data);
|
||||
}
|
||||
}
|
||||
},[data,obj && obj.user_login])
|
||||
|
||||
useEffect(()=>{
|
||||
if(userValue){
|
||||
let valueData = data && data.filter(i=>i.user_login === userValue);
|
||||
setList(valueData);
|
||||
}
|
||||
},[userValue])
|
||||
|
||||
useEffect(()=>{
|
||||
if(list && list.length > 0){
|
||||
let values = [];
|
||||
const monthList = list.map(obj => obj.month);
|
||||
values = list.map(obj => Math.round(obj[type] || obj["count"] || obj['commits_number'] || 0));
|
||||
Init(monthList,values);
|
||||
}
|
||||
},[list])
|
||||
|
||||
function Init(x,values){
|
||||
var huan_val = document.getElementById("line");
|
||||
var myEcharts = echarts.init(huan_val);
|
||||
let option = {
|
||||
title: {
|
||||
text: ''
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
color:['#5871FF',"#FFBE38"],
|
||||
// legend: {
|
||||
// data: ['Email', 'Union Ads']
|
||||
// },
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
// toolbox: {
|
||||
// feature: {
|
||||
// saveAsImage: {}
|
||||
// }
|
||||
// },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: x
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: name,
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
data: values
|
||||
}
|
||||
]
|
||||
};
|
||||
myEcharts.setOption(option);
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
if(pieData){
|
||||
let newData = new Array;
|
||||
for (var i in pieData) {
|
||||
var json = {};
|
||||
if (pieData[i] != "") {
|
||||
json.name = i;
|
||||
json.value = pieData[i];
|
||||
newData.push(json);
|
||||
}
|
||||
}
|
||||
setPieEchartData(newData)
|
||||
}
|
||||
},[pieData])
|
||||
|
||||
return(
|
||||
<div>
|
||||
<div className='dropEchartPanel'>
|
||||
{
|
||||
obj && obj.user_login && userValue && userDrop && userDrop.length>0 &&
|
||||
<div style={{textAlign:"left",paddingLeft:20}}>
|
||||
<span className='mr10'>贡献者:</span>
|
||||
<Select
|
||||
value={userValue}
|
||||
style={{ width: 220 }}
|
||||
onChange={(e)=>{setUserValue(e);}}
|
||||
>
|
||||
{userDrop.map(value => {
|
||||
return <Option key={value}>{value}</Option>
|
||||
})}
|
||||
</Select>
|
||||
</div>
|
||||
}
|
||||
<div id="line" style={{height:"350px"}}></div>
|
||||
</div>
|
||||
{
|
||||
pieData &&
|
||||
<div className='dropEchartPanel'>
|
||||
<Pie values={pieEchartData} title="Commit Message质量分布"/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Stack;
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
function StackImg({x,values,title,id}){
|
||||
useEffect(()=>{
|
||||
if(x && values && x.length>0 && values.length>0 && id){
|
||||
Init(x,values)
|
||||
}
|
||||
},[x,values,id])
|
||||
|
||||
function Init(xData,data){
|
||||
var huan_val = document.getElementById(id);
|
||||
var myEcharts = echarts.init(huan_val);
|
||||
let option = {
|
||||
title: {
|
||||
text: title || '',
|
||||
textStyle:{
|
||||
fontSize: 17 ,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
left:20,
|
||||
top:15
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
color:['#5871FF',"#FFBE38"],
|
||||
// legend: {
|
||||
// data: ['Email', 'Union Ads']
|
||||
// },
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
// toolbox: {
|
||||
// feature: {
|
||||
// saveAsImage: {}
|
||||
// }
|
||||
// },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: xData
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: data
|
||||
};
|
||||
myEcharts.setOption(option);
|
||||
}
|
||||
return(
|
||||
<div id={id} style={{height:"350px"}}></div>
|
||||
)
|
||||
}
|
||||
export default StackImg;
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
export const firstData = ['开发活动', '社区协作','项目影响']
|
||||
export const secondData = {
|
||||
开发活动:["开发质量","开发效率"],
|
||||
社区协作:['个体开发者','商业公司'],
|
||||
项目影响:["项目影响"]
|
||||
}
|
||||
export const field={
|
||||
社区协作:"communityCooperation",
|
||||
开发活动:"developmentActivity",
|
||||
项目影响:"influence"
|
||||
}
|
||||
export const subField={
|
||||
开发质量:"developmentQuality",
|
||||
开发效率:"developmentEfficiency",
|
||||
个体开发者:"individual",
|
||||
商业公司:"company",
|
||||
项目影响:"influence"
|
||||
}
|
||||
export const thirdData = {
|
||||
developmentEfficiency:["PRNum","PRTime","commitNum","issueTime","ProjectActivity"],
|
||||
developmentQuality:["commitMessageQuality","issueNum"],
|
||||
individual:["individualLoss","individualNum","longTermIndividualNum"],
|
||||
company:["companyLead","companyLoss","companyNum","longTermCompanyNum"],
|
||||
influence:["branchNum","browseNum","forkNum","praiseNum","storeNum"]
|
||||
}
|
||||
export const fieldData = {
|
||||
PRNum:"PR数量",
|
||||
PRTime:"PR处理间隔",
|
||||
commitNum:"Commit数量",
|
||||
issueTime:"Issue处理间隔",
|
||||
commitMessageQuality:"Commit message质量",
|
||||
issueNum:"Issue数量",
|
||||
ProjectActivity:"项目活跃度",
|
||||
individualLoss:"长期贡献者稳定性",
|
||||
individualNum:"活跃开发者占比",
|
||||
longTermIndividualNum:"长期贡献者占比",
|
||||
companyLead:"公司主导",
|
||||
companyLoss:"公司贡献者流失预测",
|
||||
companyNum:"公司贡献者数量",
|
||||
longTermCompanyNum:"长期公司贡献者数量",
|
||||
|
||||
browseNum:"浏览次数",
|
||||
forkNum:"Fork次数",
|
||||
praiseNum:"点赞次数",
|
||||
storeNum:"关注数量",//followNum
|
||||
branchNum:"分支数量"
|
||||
}
|
||||
|
||||
export const detailByName={
|
||||
Commitmessage质量:{desc:"可以通过每月新增Poor Message数量和Commit Message质量分布这两个维度对Commit message质量进行度量",radar:true},
|
||||
Issue数量:{desc:"通过分析项目每月提交的Issue数量对Issue数量指标进行度量"},
|
||||
Commit数量:{desc:"通过分析项目每月提交的三种Commit数量对Commit数量指标进行度量"},
|
||||
PR数量:{desc:"通过分析项目每月提交的PR数量对PR数量指标进行度量"},
|
||||
PR处理间隔:{desc:"通过分析每个月新增PR的平均处理间隔对PR处理间隔(小时)指标进行度量"},
|
||||
Issue处理间隔:{desc:"通过分析每个月新增Issue的平均处理间隔对Issue处理间隔(小时)指标进行度量"},
|
||||
|
||||
活跃开发者占比:{desc:"通过分析每个月活跃的个体贡献者数量对个体贡献者数量指标进行度量",descTitle:"个体贡献者数量"},//个体贡献者数量
|
||||
项目活跃度:{desc:"不同类型的贡献者每月/周给master分支的贡献次数",month:true,user_login:true,descTitle:"所有个体开发者贡献历史"},//所有个体开发者贡献历史
|
||||
长期贡献者占比:{desc:"通过分析每个月活跃的长期个体贡献者数量对长期个体贡献者数量指标进行度量",descTitle:"长期个体贡献者数量"},//长期个体贡献者数量
|
||||
长期贡献者稳定性:{desc:"通过分析具体的即将流失的长期贡献者流失概率对个体贡献者流失预测指标进行度量",bar:true,descTitle:"个体贡献者流失预测"},//个体贡献者流失预测
|
||||
|
||||
公司贡献者数量:{desc:"通过分析每月做出了贡献的公司贡献者数量对公司贡献者数量指标进行度量"},
|
||||
长期公司贡献者数量:{desc:"通过分析每月的长期对项目做出贡献的公司贡献者数量对长期公司贡献者数量指标进行度量"},
|
||||
公司主导:{desc:"从每月公司贡献占比、每月公司总贡献、每月公司贡献最高模块中总贡献、公司总提交数四个方面对公司主导指标进行度量",radar:true},
|
||||
公司贡献者流失预测:{desc:"通过分析各个公司的流失概率对公司贡献者流失预测指标进行度量"},
|
||||
|
||||
浏览次数:{desc:"通过分析每月项目的浏览次数对浏览次数指标进行度量"},
|
||||
点赞次数:{desc:"通过分析每月项目的点赞次数对点赞次数指标进行度量"},
|
||||
关注数量:{desc:"通过分析每月项目的关注数量对关注数量指标进行度量"},
|
||||
分支数量:{desc:"通过分析每月项目的分支数量对分支数量指标进行度量"},
|
||||
Fork次数:{desc:"通过分析每月项目的新增Fork数量对Fork数量指标进行度量"},
|
||||
}
|
||||
|
||||
export const scoreComp = (score)=>{
|
||||
let value = Math.round(score);
|
||||
return <span className="faYou" style={{color:value >= 80 ? "#466aff" : (value<80 && value>=60) ? "#E18419" :"#F70202"}}>{value}</span>
|
||||
}
|
||||
|
||||
export const colorsField={
|
||||
社区协作:"#F58400",
|
||||
开发活动:"#466AFF",
|
||||
项目影响:"#00BF9A"
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
import React ,{useEffect, useState} from 'react';
|
||||
import { Breadcrumb , Select } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import "./index.scss";
|
||||
import LineStack from './componet/stack';
|
||||
import DataStack from './componet/dataStack';
|
||||
import CompanyData from './componet/companyData';
|
||||
import { detailByName , secondData , thirdData , subField ,fieldData , scoreComp} from './config';
|
||||
import axios from 'axios';
|
||||
import Nodata from '../../Nodata';
|
||||
|
||||
const dimensionData = {
|
||||
开发质量: ['Commit message质量', 'Issue数量'],
|
||||
开发效率: ['PR数量', 'PR处理间隔', 'Commit数量', 'Issue处理间隔','项目活跃度'],
|
||||
个体开发者:['活跃开发者占比','长期贡献者占比','长期贡献者稳定性'],
|
||||
商业公司:['公司主导','公司贡献者流失预测','公司贡献者数量','长期公司贡献者数量'],
|
||||
项目影响:['Fork次数','点赞次数','关注数量']//'分支数量','浏览次数',
|
||||
};
|
||||
|
||||
function Dimension(props){
|
||||
const { type , owner , projectsId } = props.match.params;
|
||||
const [ data , setData ] = useState([]);
|
||||
const [ pieData , setPieData ] = useState([]);
|
||||
const [ score , setScore ] = useState(0);
|
||||
|
||||
const [title, setTitle] = useState('');
|
||||
|
||||
const [seconds, setSeconds] = useState([]);
|
||||
const [sValue, setSValue] = useState('');
|
||||
|
||||
const [ dimensions , setDimensions] = useState([]);
|
||||
const [dValue, setDValue] = useState('');
|
||||
|
||||
const findObjKeyByValue = (obj, value) => {
|
||||
return Object.keys(obj).find(key => obj[key].includes(value));
|
||||
};
|
||||
const findKeyByValue = (obj, value) => {
|
||||
return Object.keys(obj).find(key => obj[key].includes(value));
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
if(type){
|
||||
Init(type==="storeNum" ?"followNum":type);
|
||||
let find = findObjKeyByValue(thirdData,type);
|
||||
if(find){
|
||||
let sub = findKeyByValue(subField,find);
|
||||
setSValue(sub);
|
||||
}else{
|
||||
setSValue(seconds[0])
|
||||
}
|
||||
setDValue(fieldData[type])
|
||||
}
|
||||
},[type])
|
||||
|
||||
|
||||
function Init(t){
|
||||
const url = `/v1/${owner}/${projectsId}/oss_health_measuring/keyid`;
|
||||
axios(url,{params:{
|
||||
key_id:t
|
||||
}}).then(res=>{
|
||||
if(!res){
|
||||
props.history.push(`/${owner}/${projectsId}/service/measurement`)
|
||||
return;
|
||||
}
|
||||
let data = res && res.data;
|
||||
if(data){
|
||||
let d = data[t] ? data[t].value : data.value;
|
||||
let s = data.score !== undefined ? data.score : data[t].score;
|
||||
setPieData(data.distribution);
|
||||
setData(d);
|
||||
if(t==="companyLoss"){
|
||||
d = d.map(i=>{return { month:i.Company , count : (i["Predicted State"] || 0.0)}});
|
||||
setData(d);
|
||||
}else if(t === "companyNum" || t === "longTermCompanyNum" || t==="companyLead"){
|
||||
d = Object.keys(data.value).length === 0 ?[]:[Object.assign(data.value)];
|
||||
setData(d);
|
||||
}
|
||||
setScore(s);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
if(sValue){
|
||||
let arr = dimensionData[sValue];
|
||||
setDimensions(arr);
|
||||
let find = findObjKeyByValue(secondData,sValue);
|
||||
setTitle(find);
|
||||
let subList = secondData[find];
|
||||
setSeconds(subList);
|
||||
}
|
||||
},[sValue])
|
||||
|
||||
|
||||
return(
|
||||
<div>
|
||||
<Breadcrumb separator=">">
|
||||
<Breadcrumb.Item><Link className="color-blue" to={`/${owner}/${projectsId}/service/measurement`}>首页</Link></Breadcrumb.Item>
|
||||
<Breadcrumb.Item>{title}</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className='headBox font-16 pl15 mt20 df pr30 mb30'>
|
||||
{title}
|
||||
<span style={{marginLeft:"auto"}}>
|
||||
<span>度量值:<em className='color-blue font-26'>{scoreComp(score)}</em></span>
|
||||
{/* <span className='ml30'>健康度权重比:<em className='color-blue font-26'>20%</em></span> */}
|
||||
</span>
|
||||
</div>
|
||||
<div className='dropTeam'>
|
||||
<Select
|
||||
value={sValue}
|
||||
style={{ width: 120 }}
|
||||
onChange={(e)=>{setSValue(e);props.history.push(`/${owner}/${projectsId}/service/measurement/${findObjKeyByValue(fieldData,dimensionData[e][0])}`)}}
|
||||
border={null}
|
||||
>
|
||||
{seconds.map(province => (
|
||||
<Option key={province}>{province}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<Select
|
||||
value={dValue}
|
||||
style={{ width: 190 }}
|
||||
onChange={(e)=>{setDValue(e);props.history.push(`/${owner}/${projectsId}/service/measurement/${findObjKeyByValue(fieldData,e)}`)}}
|
||||
border={null}
|
||||
>
|
||||
{dimensions.map(province => (
|
||||
<Option key={province}>{province}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div className='dropEcharts'>
|
||||
<p className='dropName'>{dValue && (detailByName[dValue.replace(/\s/g, '')].descTitle ? detailByName[dValue.replace(/\s/g, '')].descTitle : dValue)}</p>
|
||||
<p className='dropDesc'>{dValue && detailByName[dValue.replace(/\s/g, '')].desc}</p>
|
||||
{
|
||||
data && data.length > 0 ?
|
||||
<div>
|
||||
{
|
||||
type === "companyNum" || type === "longTermCompanyNum" ?
|
||||
<div className='dropEchartPanel'><DataStack data={data[0]} /></div>
|
||||
:
|
||||
type === "companyLead" ?
|
||||
<CompanyData data={data[0]} />
|
||||
:
|
||||
<LineStack name={dValue} data={data} type={type==="storeNum" ?"followNum":type} pieData={pieData}/>
|
||||
}
|
||||
</div>
|
||||
:<Nodata _html={`暂无数据~`}/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Dimension;
|
||||
|
|
@ -0,0 +1,260 @@
|
|||
import React, {useState, useEffect , useRef} from "react";
|
||||
import './index.scss';
|
||||
import Logo from '../img/logo4.png';
|
||||
import { Button , Spin } from 'antd';
|
||||
import Large from '../img/large.png';
|
||||
import Middle from '../img/middle.png';
|
||||
import Small from '../img/small.png';
|
||||
import team from '../img/team.png';
|
||||
import person from '../img/person.png';
|
||||
import Rader from './componet/radar';
|
||||
import Indicator from './componet/indicator';
|
||||
import { scoreComp , field , firstData , colorsField, secondData , subField , thirdData } from './config';
|
||||
import axios from "axios";
|
||||
import cookie from 'react-cookies';
|
||||
// import res from './xiuos.json';
|
||||
|
||||
|
||||
function Index(props){
|
||||
const [ dataLoading , setDataLoading ] = useState(true);
|
||||
const [cardIndex, setCardIndex] = useState(firstData[0]);
|
||||
const [ subCardIndex, setSubCardIndex] = useState(1);
|
||||
const [ loading , setLoading ] = useState(false);
|
||||
|
||||
const { owner , projectsId } = props.match.params;
|
||||
const [ datas , setDatas ] = useState();
|
||||
const [ scoreNormal , setScoreNormal ] = useState();
|
||||
|
||||
const [ score , setScore ] = useState([]);
|
||||
const [ scoreNum , setScoreNum ] = useState(0);
|
||||
|
||||
const [ subMenus , setSubMenus ] = useState([]);
|
||||
const [ subDatas , setSubDatas ] = useState([]);
|
||||
|
||||
const [ fieldDatas , setFieldDatas ] = useState([]);
|
||||
let interval = undefined;
|
||||
const count = useRef();
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(owner && projectsId){
|
||||
console.log(cookie.load("opensource"));
|
||||
if(cookie.load("opensource") && cookie.load("opensource")===`${owner}_${projectsId}`){
|
||||
getReportStart();
|
||||
}else{
|
||||
checkData();
|
||||
}
|
||||
}
|
||||
},[owner,projectsId])
|
||||
|
||||
// 根据接口判断是否已经生成数据报告
|
||||
async function checkData(){
|
||||
const url = `/v1/${owner}/${projectsId}/oss_health_measuring/can_get`;
|
||||
axios(url).then(res=>{
|
||||
if(res && res.data && res.data.statue === "YES"){
|
||||
Init();
|
||||
clearInterval(interval);
|
||||
cookie.remove("opensource");
|
||||
}else if(res && res.data && res.data.statue === "NO"){
|
||||
setDataLoading(false);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取数据接口
|
||||
function Init(){
|
||||
const url = `/v1/${owner}/${projectsId}/oss_health_measuring/keyid`;
|
||||
axios(url,{params:{
|
||||
key_id:"score"
|
||||
}}).then(res=>{
|
||||
if(res && res.data){
|
||||
let data = res.data;
|
||||
setTimeout(()=>{setDataLoading(false)},300);
|
||||
setDatas(data);
|
||||
setScoreNormal(data);
|
||||
getlist(data);
|
||||
}
|
||||
})
|
||||
}
|
||||
// 开始体验,定时调用接口
|
||||
function getReportStart(){
|
||||
|
||||
setLoading(true);
|
||||
count.current = 20;
|
||||
checkData();
|
||||
interval = setInterval(() => {
|
||||
if (count.current > 1) {
|
||||
let old = count.current;
|
||||
count.current = old - 1;
|
||||
console.log(count);
|
||||
checkData();
|
||||
} else {
|
||||
//倒计时结束,提示繁忙
|
||||
clearInterval(interval);
|
||||
setLoading(false);
|
||||
props.showNotification("当前服务繁忙,请稍后再试");
|
||||
}
|
||||
}, 30000)
|
||||
}
|
||||
|
||||
// 切换一级维度tab
|
||||
useEffect(()=>{
|
||||
if(score && cardIndex){
|
||||
// 获取到二级维度名称以及其效率总分和健康度权重比,
|
||||
let sub = secondData[cardIndex];
|
||||
sub && sub.length > 0 && setSubCardIndex(sub[0]);
|
||||
let subData = score.filter(i=>i.name === cardIndex);
|
||||
setSubMenus(sub);
|
||||
// 以及一级维度的开发活动总分、加权得分
|
||||
let value = subData && subData.length > 0 && subData[0];
|
||||
if(value){
|
||||
setSubDatas(value);
|
||||
let name = value.name;
|
||||
let subname = secondData[name];
|
||||
let list = subname.map(i=>{
|
||||
return subField[i]
|
||||
})
|
||||
let listData = [];
|
||||
for(var i =0;i<list.length;i++){
|
||||
let l = value[list[i]] || value;
|
||||
let d = thirdData[list[i]];
|
||||
if(d){
|
||||
let items = d.map(j=>{
|
||||
return {name:j,...l[j],InitialScore:l[j] ? Math.round(l[j].InitialScore) : 0};
|
||||
})
|
||||
listData = listData.concat(items);
|
||||
}
|
||||
}
|
||||
setFieldDatas(listData);
|
||||
}
|
||||
}
|
||||
},[cardIndex,score])
|
||||
|
||||
|
||||
// 将一级维度的值转为数组
|
||||
function getlist(values){
|
||||
let d = firstData.map(i=>{
|
||||
return {name:i,...values[field[i]],color:colorsField[i]};
|
||||
})
|
||||
setScore(d);
|
||||
let sn = d.reduce((sum, item) => Math.round(sum + Math.round(item.WeightedScore)), 0);
|
||||
setScoreNum(sn);
|
||||
}
|
||||
console.log(subDatas);
|
||||
|
||||
|
||||
return(
|
||||
<div>
|
||||
<div class="headBox font-16 pl15">开源软件健康度量服务</div>
|
||||
{ dataLoading && <Spin><div style={{minHeight:400,width:"100%"}}></div></Spin>}
|
||||
{
|
||||
!dataLoading && !datas &&
|
||||
<div class="nullStoreBox mt25">
|
||||
<img src={Logo} class="loBox mt50" />
|
||||
<p class="font-22 mt10">欢迎使用开源软件健康度量服务</p>
|
||||
<div class="introBox font-15">开源软件健康度量服务是一种代码库分析工具,旨在通过深入分析代码库的关键活动数据来评估开源项目的健康状况。该服务通过收集和分析代码库中的贡献者、提交(commit)、合并请求(pullRequest)、疑修(issue)等数据指标,为项目开发者、维护者提供了一个全面的项目健康报告</div>
|
||||
<div class="borBox"></div>
|
||||
{!loading && <Button type="primary" onClick={()=>{cookie.save("opensource",`${owner}_${projectsId}`,{ expires: new Date(new Date().getTime() + 24 * 3600 * 1000),path:`/` });getReportStart();}}>开始体验</Button> }
|
||||
{ loading && <div><Spin /><span className="ml5 color-blue">服务报告生成中...</span></div> }
|
||||
</div>
|
||||
}
|
||||
{
|
||||
!dataLoading && datas &&
|
||||
<div className="scorePanel">
|
||||
{
|
||||
scoreNormal &&
|
||||
<div className="scoreMain">
|
||||
<div className="scoreimg">
|
||||
<span className="scoreValue">
|
||||
<img src={scoreNum >= 80 ? Large : scoreNum < 60 ? Small : Middle} alt="" width={"100%"}/>
|
||||
<div className="s_content">
|
||||
<span>健康值</span>
|
||||
<span className="s_num">{scoreComp(scoreNum)}</span>
|
||||
</div>
|
||||
</span>
|
||||
<p className="font-18 fw500 mt10 mb15">本项目开源生态健康度</p>
|
||||
<p className="s_detail">开源生态健康度由“开发活动”、“社区协作”、“项目影响”三个总体维度对开源项目的健康状况进行分析并加权求和,用于反馈项目的总体健康状况。您可点击查询每一个维度的度量指标及可视化图表。</p>
|
||||
</div>
|
||||
{
|
||||
score &&
|
||||
<ul className="scoreProgress">
|
||||
{
|
||||
<li>
|
||||
<span className="index">{Math.round(scoreNum)}</span>
|
||||
<span className="name">开源生态健康度</span>
|
||||
<div className="progress">
|
||||
{
|
||||
score.map((i,k)=>{
|
||||
return <p style={{width:`${i.WeightedScore}%`,backgroundColor:`${i.color}`}}></p>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<span><span className="color-blue">{Math.round(scoreNum)}</span><span className="ml3 mr3">/</span>100</span>
|
||||
</li>
|
||||
}
|
||||
{
|
||||
score.map((i,k)=>{
|
||||
return(
|
||||
<li>
|
||||
<span className="index">{Math.round(i.WeightedScore)}</span>
|
||||
<span className="name">{i.name}</span>
|
||||
<div className="progress"><span style={{width:`${i.WeightedScore}%`,backgroundColor:`${i.color}`}}></span></div>
|
||||
<span><span className="color-blue">{Math.round(i.WeightedScore)}</span><span className="ml3 mr3">/</span>{i.Weight*100}</span>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div className="scoreDetail">
|
||||
<ul className="typeLi">
|
||||
{
|
||||
firstData.map((i,k)=>{
|
||||
return <li className={cardIndex === i ?"active":""} onClick={()=>{setCardIndex(i)}}><span>{k<10 && `0${k+1}`}</span>{i}</li>
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
{
|
||||
subDatas &&
|
||||
<div className="typeContent">
|
||||
<p className="typeValue fw500">
|
||||
{cardIndex}总分:{scoreComp(subDatas.InitialScore)}<span className="mr40 faYou">/100</span>
|
||||
{cardIndex}加权得分:{scoreComp(subDatas.WeightedScore)}<span className="mr40 faYou">/{subDatas.Weight*100}</span>
|
||||
</p>
|
||||
<div className="typeEchart">
|
||||
<ul className="echartCate">
|
||||
{
|
||||
subMenus && subMenus.length > 0 &&
|
||||
subMenus.map((i,k)=>{
|
||||
let detail = subDatas[subField[i]];
|
||||
return(
|
||||
<li className={`${subCardIndex === i ?"active":""}`} onClick={()=>setSubCardIndex(i)}>
|
||||
<div className="echartNum">
|
||||
<span className="fw500 font-16">{i}</span>
|
||||
<span><span className="font-17">{i}总分:</span>{scoreComp(detail ? detail.InitialScore : subDatas.InitialScore)}</span>
|
||||
<span><span className="font-17">健康度权重比:</span><span className="faYou">{(detail ? detail.Weight : subDatas.Weight) *100}%</span></span>
|
||||
</div>
|
||||
<img src={team} width={76} alt="" />
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
<div className="echartData">
|
||||
<Rader data={fieldDatas} subCardIndex={subCardIndex}/>
|
||||
<div style={{width:"50%"}}>
|
||||
<Indicator data={fieldDatas} subCardIndex={subCardIndex} owner={owner} projectsId={projectsId}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Index;
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
.scorePanel{
|
||||
.scoreMain{
|
||||
display: flex;
|
||||
padding:35px 0px;
|
||||
.scoreimg{
|
||||
width: 47%;
|
||||
padding-right: 80px;
|
||||
text-align: center;
|
||||
.s_detail{
|
||||
font-weight: 400;
|
||||
font-size: 15px;
|
||||
color: #131415;
|
||||
line-height: 30px;
|
||||
text-align: left;
|
||||
}
|
||||
.scoreValue{
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 270px;
|
||||
text-align: center;
|
||||
margin:0px auto;
|
||||
.s_content{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0px;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
color: #B2C6D9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.s_num{
|
||||
font-family: "YouSheBiaoTiHei";
|
||||
font-weight: 400;
|
||||
font-size: 41px;
|
||||
color: #466AFF;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.scoreProgress{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding:20px 0px;
|
||||
li{
|
||||
height: 36px;
|
||||
width: 100%;
|
||||
background: #F8F8F8;
|
||||
padding-right: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #61708B;
|
||||
position: relative;
|
||||
&::before{
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 26px solid;
|
||||
border-color: transparent transparent transparent #F8F8F8;
|
||||
transform: rotate(45deg);
|
||||
position: absolute;
|
||||
left: -26px;
|
||||
content: "";
|
||||
top: 10px;
|
||||
}
|
||||
.index{
|
||||
width: 56px;
|
||||
background-image: url(../img/index.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
display: block;
|
||||
text-align: center;
|
||||
line-height: 36px;
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #466AFF;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.name{
|
||||
width: 120px;
|
||||
margin-right: 14px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #181818;
|
||||
}
|
||||
.progress{
|
||||
width: 330px;
|
||||
height: 16px;
|
||||
background: #F3F3F3;
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin-right: 15px;
|
||||
&>span{
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
left: 0px;
|
||||
top:0px;
|
||||
}
|
||||
&>p{
|
||||
border-right: 2px solid #fff;
|
||||
&:last-child{
|
||||
border:none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.typeLi{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding:44px 0px;
|
||||
li{
|
||||
height: 62px;
|
||||
background: #F5F6F7;
|
||||
border-radius: 8px 8px 8px 8px;
|
||||
border: 1px solid #DBE0E9;
|
||||
width: 280px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #212F54;
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
margin:0px 25px;
|
||||
cursor: pointer;
|
||||
span{
|
||||
font-family: YouSheBiaoTiHei;
|
||||
font-weight: 400;
|
||||
font-size: 25px;
|
||||
margin-right: 15px;
|
||||
position: relative;
|
||||
&::after{
|
||||
content: "";
|
||||
right: -8px;
|
||||
bottom: -3px;
|
||||
width: 19px;
|
||||
height: 15px;
|
||||
background-image: url('../img/trans1.png');
|
||||
background-repeat: no-repeat;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
&.active{
|
||||
background-image: url('../img/blue.png');
|
||||
border:none;
|
||||
line-height: 62px;
|
||||
color: #fff;
|
||||
span{
|
||||
&::after{
|
||||
background-image: url('../img/trans.png');
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover{
|
||||
border-color: #6F93D4;
|
||||
}
|
||||
}
|
||||
}
|
||||
.fw500{
|
||||
font-weight: 500;
|
||||
}
|
||||
.typeContent{
|
||||
background: rgba(102,102,102,0.03);
|
||||
border-radius: 0px 0px 0px 0px;
|
||||
border: 1px solid rgba(70,106,255,0.13);
|
||||
padding:40px 30px;
|
||||
.typeValue{
|
||||
font-size: 17px;
|
||||
color: #181818;
|
||||
text-align: center;
|
||||
margin-bottom: 30px!important;
|
||||
span{
|
||||
font-weight: 400;
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
.typeEchart{
|
||||
display: flex;
|
||||
}
|
||||
.echartCate{
|
||||
width: 298px;
|
||||
margin-right: 30px;
|
||||
.echartNum{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex:1;
|
||||
font-weight: 400;
|
||||
font-size: 22px;
|
||||
color: #212F54;
|
||||
span{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
li{
|
||||
background-image: url('../img/back.png');
|
||||
background-size: 100% 100%;
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
box-shadow: 0px 0px 10px 1px rgba(28,48,175,0.04);
|
||||
border-radius: 8px 8px 8px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 30px;
|
||||
font-weight: 400;
|
||||
font-size: 15px;
|
||||
color: #181818;
|
||||
line-height: 40px;
|
||||
border: 1px solid #fff;
|
||||
cursor: pointer;
|
||||
&.active{
|
||||
border-color: #466AFF;
|
||||
position: relative;
|
||||
&::after{
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
background-image: url("../img/true.png");
|
||||
width: 56px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
&:first-child{
|
||||
margin-bottom: 30px!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.echartData{
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0px 0px 10px 1px rgba(28,48,175,0.08);
|
||||
border-radius: 0px 0px 0px 0px;
|
||||
flex: 1;
|
||||
min-height: 370px;
|
||||
padding:25px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
.smalltable{
|
||||
border-color: rgba(65,84,241,0.04)!important;
|
||||
.ant-table-body{
|
||||
margin:0px!important;
|
||||
.ant-table-thead th {
|
||||
background-color:rgba(65,84,241,0.04)!important;
|
||||
}
|
||||
.ant-table-tbody tr td{
|
||||
background: #F5F6F7;
|
||||
}
|
||||
.ant-table-tbody tr:hover{
|
||||
background: #F5F6FA;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dropTeam{
|
||||
background-image: url('../img/blue-large.png');
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
width: 330px;
|
||||
height: 50px;
|
||||
margin:40px auto;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.ant-select-selection{
|
||||
border:none;
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
.ant-select-arrow .ant-select-arrow-icon svg{
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.ant-select{
|
||||
position: relative;
|
||||
&:last-child::before{
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
border: 1px solid rgba(255,255,255,0.36);
|
||||
left: 0px;
|
||||
top:9px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dropEcharts{
|
||||
background-image: url('../img/echartBack.png');
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
.dropName{
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
color: #181818;
|
||||
}
|
||||
.dropDesc{
|
||||
font-weight: 400;
|
||||
font-size: 15px;
|
||||
color: #212F54;
|
||||
width: 75%;
|
||||
margin:0px auto;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.dropEchartPanel{
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0px 0px 10px 1px rgba(28,48,175,0.08);
|
||||
border-radius: 6px 6px 6px 6px;
|
||||
padding:25px;
|
||||
width: 75%;
|
||||
margin:0px auto;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
.faYou{
|
||||
font-family: YouSheBiaoTiHei;
|
||||
}
|
||||
|
|
@ -30,15 +30,6 @@
|
|||
border-color:#1a47ff;
|
||||
}
|
||||
}
|
||||
.reposyncerBox .headBox, .storeListBox .headBox{
|
||||
height:60px;
|
||||
line-height: 60px;
|
||||
background-color:#fafcff;
|
||||
border:1px solid rgba(42, 97, 255, 0.23);
|
||||
border-radius:3px 3px 0px 0px;
|
||||
color:#333333;
|
||||
position: relative;
|
||||
}
|
||||
.reposyncerBox{
|
||||
padding-bottom: 60px;
|
||||
.helpBox1{
|
||||
|
|
@ -53,24 +44,6 @@
|
|||
align-items: center;
|
||||
cursor: default;
|
||||
}
|
||||
.nullStoreBox{
|
||||
background-color:#fafcff;
|
||||
border-radius:4px 4px 0px 0px;
|
||||
text-align: center;
|
||||
color:#333333;
|
||||
padding-bottom: 65px;
|
||||
.loBox{width: 68px;}
|
||||
.introBox{
|
||||
color:#666666;
|
||||
width: 57%;
|
||||
margin: 15px auto;
|
||||
}
|
||||
.borBox{
|
||||
width: 45%;
|
||||
margin: 0 auto 20px;
|
||||
border-bottom: 1px solid rgba(90, 117, 193, 0.23);
|
||||
}
|
||||
}
|
||||
.rightContentBox{
|
||||
display: flex;
|
||||
.leftNav{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
|
||||
|
||||
ul,ol,dl{
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
|
|
|||