Merge branch 'xingyun' of https://gitlink.org.cn/Gitlink/forgeplus-react into xingyun
|
|
@ -5,7 +5,7 @@ import 'echarts/lib/component/tooltip';
|
|||
import axios from 'axios';
|
||||
import { Icon, Popover } from 'antd';
|
||||
|
||||
function Radar({owner,projectsId}) {
|
||||
function Radar({owner,projectsId, history}) {
|
||||
|
||||
useEffect(()=>{
|
||||
const url = `/v1/${owner}/${projectsId}/portrait.json`;
|
||||
|
|
@ -39,13 +39,14 @@ function Radar({owner,projectsId}) {
|
|||
{ name: '社区\n影响力', max: 120},
|
||||
{ name: '项目\n成熟度', max: 120},
|
||||
{ name: '项目\n健康度', max: 120},
|
||||
{ name: '团队\n影响度', max: 120},
|
||||
{ name: '团队\n健康度', max: 120},
|
||||
{ name: '开发\n活跃度', max: 120},
|
||||
],
|
||||
center:["50%","55%"]
|
||||
center:["50%","55%"],
|
||||
triggerEvent: true
|
||||
},
|
||||
series: [{
|
||||
name: "社区影响力,项目成熟度,项目健康度,团队影响度,开发活跃度",
|
||||
name: "社区影响力,项目成熟度,项目健康度,团队健康度,开发活跃度",
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
|
|
@ -56,6 +57,30 @@ function Radar({owner,projectsId}) {
|
|||
}]
|
||||
};
|
||||
myEcharts.setOption(option);
|
||||
myEcharts.on('click', function (params) {
|
||||
// 确保是点击在标签上
|
||||
if (params.targetType && params.targetType === 'axisName') {
|
||||
switch (params.name) {
|
||||
case '社区\n影响力':
|
||||
history.push(`/${owner}/${projectsId}/portrait/community_impact`);
|
||||
break;
|
||||
case '项目\n成熟度':
|
||||
history.push(`/${owner}/${projectsId}/portrait/project_maturity`);
|
||||
break;
|
||||
case '项目\n健康度':
|
||||
history.push(`/${owner}/${projectsId}/portrait/project_health`);
|
||||
break;
|
||||
case '团队\n健康度':
|
||||
history.push(`/${owner}/${projectsId}/portrait/team_impact`);
|
||||
break;
|
||||
case '开发\n活跃度':
|
||||
history.push(`/${owner}/${projectsId}/portrait/develop_activity`);
|
||||
break;
|
||||
default:
|
||||
console.log('点击了其他区域:', params);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addTwenty(number){
|
||||
|
|
@ -68,7 +93,7 @@ function Radar({owner,projectsId}) {
|
|||
社区影响力:从项目点赞、关注、fork角度生成评分<br/>
|
||||
项目成熟度:从项目疑修、合并请求、提交总数量生成评分<br/>
|
||||
项目健康度:从项目疑修、合并请求、许可证完成度生成评分<br/>
|
||||
团队影响度:从项目团队稳定性、新人增长率生成评分<br/>
|
||||
团队健康度:从项目团队稳定性、新人增长率生成评分<br/>
|
||||
开发活跃度:从代码提交、疑修及合并请求等开发活动频率生成评分
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -720,8 +720,8 @@ function CoderDepot(props){
|
|||
{/* {__CLIENT__ && projectDetail && projectDetail.project_id && !projectDetail.private && <ProjectCompass owner={owner} projectsId={projectsId} />} */}
|
||||
{/* 贡献者 */}
|
||||
{mirror_status ===0 && <Contributors owner={owner} projectsId={projectsId} /> }
|
||||
{/* 项目画像: 导入/fork项目不展示, open_portrait后台配置是否开启仓库 */}
|
||||
{__CLIENT__ && projectDetail && !projectDetail.type && !projectDetail.forked_from_project_id && project && project.open_portrait && <ProjectPortrait owner={owner} projectsId={projectsId}/>}
|
||||
{/* 项目画像: 导入/fork项目不展示 */}
|
||||
{__CLIENT__ && projectDetail && !projectDetail.type && !projectDetail.forked_from_project_id && <ProjectPortrait owner={owner} projectsId={projectsId} history={props.history}/>}
|
||||
{/* 语言 */}
|
||||
{ mirror_status ===0 && <LanguagePower owner={owner} projectsId={projectsId}/> }
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -147,6 +147,10 @@ const Review = Loadable({
|
|||
loader: () => import('../Newfile/codeReview'),
|
||||
loading: Loading,
|
||||
});
|
||||
const Portrait = Loadable({
|
||||
loader: () => import('../Portrait/projectPortraitDetail.jsx'),
|
||||
loading: Loading,
|
||||
});
|
||||
/**
|
||||
* permission:Manager:管理员,Reporter:报告人员(只有读取权限),Developer:开发人员(除不能设置仓库信息外)
|
||||
*/
|
||||
|
|
@ -953,6 +957,12 @@ class Detail extends Component {
|
|||
(props) => (<CoderDepot {...this.props} {...props} {...this.state} {...common} />)
|
||||
}
|
||||
></Route>
|
||||
{/* 项目画像详情 */}
|
||||
<Route path="/:owner/:projectsId/portrait/:type"
|
||||
render={
|
||||
(props) => (<Portrait {...this.props} {...props} {...this.state} {...common} />)
|
||||
}
|
||||
></Route>
|
||||
{/* 邀请 */}
|
||||
<Route path="/:owner/:projectsId/invite"
|
||||
render={
|
||||
|
|
|
|||
|
|
@ -0,0 +1,272 @@
|
|||
.portrait-bg {
|
||||
background-image: url(./img/bj.png);
|
||||
background-size: 100% auto;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.portrait-main {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
.headBox {
|
||||
height:60px;
|
||||
line-height: 60px;
|
||||
background:rgba(176, 176, 255, 0.16);
|
||||
border:1px solid;
|
||||
border-color:#ffffff;
|
||||
border-radius:3px 3px 0px 0px;
|
||||
position: relative;
|
||||
|
||||
font-family:Alibaba PuHuiTi;
|
||||
color:#333333;
|
||||
font-size:16px;
|
||||
|
||||
img {
|
||||
height: 59px;
|
||||
position: relative;
|
||||
top: -5px;
|
||||
}
|
||||
}
|
||||
|
||||
.scoreMain{
|
||||
display: flex;
|
||||
padding:35px 0px;
|
||||
background-image: url(./img/total_bj.png);
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.scoreimg{
|
||||
width: 600px;
|
||||
text-align: center;
|
||||
background-image: url(./img/score_bj.png);
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
.scoreTitle {
|
||||
height:26px;
|
||||
line-height:normal;
|
||||
font-family:Alibaba PuHuiTi;
|
||||
font-weight:500;
|
||||
color:#ffffff;
|
||||
font-size:19px;
|
||||
text-align:center;
|
||||
}
|
||||
.scoreText{
|
||||
font-weight: 400;
|
||||
color:#ffffff;
|
||||
font-size:15px;
|
||||
line-height: 30px;
|
||||
text-align: left;
|
||||
padding: 20px 20px 20px 40px;
|
||||
border:1px solid;
|
||||
border-color:#ffffff;
|
||||
border-radius:3px 3px 0px 0px;
|
||||
}
|
||||
.scoreValue{
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 400px;
|
||||
height: 200px;
|
||||
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 20px 20px;
|
||||
|
||||
li{
|
||||
height: 36px;
|
||||
width: 100%;
|
||||
background:rgba(255, 255, 255, 0.16);
|
||||
border:1px solid;
|
||||
border-color:#ffffff;
|
||||
|
||||
padding-right: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: rgba(97, 112, 139, 1);
|
||||
position: relative;
|
||||
.index{
|
||||
width: 58px;
|
||||
background-image: url(./img/score_total_bj.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: #ffffff;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.name{
|
||||
width: 140px;
|
||||
margin-right: 14px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #181818;
|
||||
}
|
||||
.progress{
|
||||
width: 280px;
|
||||
height: 16px;
|
||||
background: rgba(255, 255, 255, 0.33);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.subDetail {
|
||||
margin-top: 90px;
|
||||
padding-bottom: 150px;
|
||||
}
|
||||
.subScore {
|
||||
background-image: url(./img/total_bj.png);
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
width:1200px;
|
||||
.typeValue{
|
||||
line-height:40px;
|
||||
font-size: 22px;
|
||||
color: #181818;
|
||||
text-align: center;
|
||||
padding: 45px 0 30px;
|
||||
font-family:Alibaba PuHuiTi;
|
||||
span{
|
||||
font-weight: 400;
|
||||
font-size: 22px;
|
||||
font-family:YouSheBiaoTiHei;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.subContent {
|
||||
background-image: url(./img/sub_content_bj.png);
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
padding-top: 40px;
|
||||
.subTitle {
|
||||
line-height:30px;
|
||||
font-family:Alibaba PuHuiTi;
|
||||
color:#181818;
|
||||
font-size:19px;
|
||||
padding-bottom: 20px;
|
||||
padding-left: 30px;
|
||||
background-image: url(./img/star.png);
|
||||
background-size: 24px 24px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: left 5px;
|
||||
}
|
||||
.subRemark{
|
||||
width: 600px;
|
||||
padding-bottom: 20px;
|
||||
line-height:30px;
|
||||
font-family:Alibaba PuHuiTi;
|
||||
color:#181818;
|
||||
font-size:15px;
|
||||
}
|
||||
}
|
||||
|
||||
.typeLi{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border:1px solid;
|
||||
border-color:#ffffff;
|
||||
backdrop-filter:blur(30px);
|
||||
|
||||
li{
|
||||
flex: 1;
|
||||
height: 90px;
|
||||
// background: #F5F6F7;
|
||||
// border-radius: 8px 8px 8px 8px;
|
||||
// border: 1px solid #DBE0E9;
|
||||
// width: 280px;
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
line-height: 90px;
|
||||
text-align: center;
|
||||
color:#181818;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// 底部子分类图标
|
||||
.sub_icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.community_impact_watcher {
|
||||
background-image: url('./img/community_impact_watcher.png');
|
||||
}
|
||||
.community_impact_praise {
|
||||
background-image: url('./img/community_impact_praise.png');
|
||||
}
|
||||
.community_impact_fork {
|
||||
background-image: url('./img/community_impact_fork.png');
|
||||
}
|
||||
|
||||
&.active{
|
||||
background-image: url('./img/btn_active_bj.png');
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
color: #fff;
|
||||
.community_impact_watcher {
|
||||
background-image: url('./img/community_impact_watcher_active.png');
|
||||
}
|
||||
.community_impact_praise {
|
||||
background-image: url('./img/community_impact_praise_active.png');
|
||||
}
|
||||
.community_impact_fork {
|
||||
background-image: url('./img/community_impact_fork_active.png');
|
||||
}
|
||||
}
|
||||
&:hover{
|
||||
background-image: url('./img/btn_active_bj.png');
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
import { key } from "localforage"
|
||||
|
||||
// 项目画像五个分类
|
||||
export const typeName = {
|
||||
community_impact: '社区影响力',
|
||||
project_maturity: '项目成熟度',
|
||||
project_health: '项目健康度',
|
||||
team_impact: '团队健康度',
|
||||
develop_activity: '开发活跃度',
|
||||
}
|
||||
|
||||
// 项目画像分类描述文字
|
||||
export const typeDetail = {
|
||||
community_impact: '社区影响力分析旨在量化评估开源项目在开源社区中的受欢迎程度和传播能力,为项目管理者、开发者和社区组织者提供决策依据。通过项目关注度、项目收藏度、项目派生度三个关键指标,综合评估项目的社区影响力,帮助项目团队了解项目在社区中的地位和发展趋势,从而采取相应的策略提升项目的社区影响力。',
|
||||
project_maturity: '项目成熟度旨在通过量化项目的开发活动和任务管理数据,为项目管理者、开发者和社区组织者提供一个客观、全面的评估框架,综合反映项目的稳定性和可靠性。通过对项目解决任务量、累计开发活动量、项目有效开发年龄等指标的评估,不仅有助于项目团队内部的管理和优化,还能为社区资源的合理分配提供重要依据,确保有限的资源能够优先投入到那些具有潜力和成熟度的项目中。',
|
||||
project_health: '项目健康度统计通过评估项目源代码质量和开发过程效率,如任务过程效率分析、代码过程效率分析、代码质量分析、项目许可证分析等指标,展示项目的健康状况,帮助用户及时发现潜在问题,采取相应的改进措施,确保项目的顺利进行。',
|
||||
team_impact: '团队健康度统计分析功能通过对团队稳定性、新人增长率、任务均衡度等指标的评估,展示项目团队的健康状况和协作能力,为团队管理和优化提供参考,帮助提升团队的整体效能和可持续发展能力。',
|
||||
develop_activity: '开发活跃度统计分析功能通过评估代码提交度、缺陷反馈度、代码合并请求率、版本发布率等指标,展示项目的开发活跃度和团队的活跃程度,帮助用户了解项目的开发动态,及时发现开发过程中的问题,采取相应的措施进行调整。',
|
||||
}
|
||||
|
||||
// 项目画像子分类标识
|
||||
export const subType = {
|
||||
community_impact: ['community_impact_watcher', 'community_impact_praise', 'community_impact_fork'],
|
||||
project_maturity: ['project_maturity_complete_issue', 'project_maturity_pullrequest', 'project_maturity_project_create_day'],
|
||||
project_health: ['health_issue', 'health_pullrequest', 'health_code', 'health_license'],
|
||||
team_impact: ['team_impact_member', 'team_impact_recent_member', 'team_impact_issue_assigner'],
|
||||
develop_activity: ['develop_activity_commit', 'develop_activity_issue', 'develop_activity_pullrequest', 'develop_activity_release'],
|
||||
}
|
||||
|
||||
// 项目画像子分类名称
|
||||
export const subTypeDetail = {
|
||||
community_impact: {
|
||||
community_impact_watcher: {
|
||||
name: '项目关注度',
|
||||
// 权重百分比
|
||||
WeightedScore: 30,
|
||||
remark: '项目关注度反映了项目在社区中的受欢迎程度和关注度。当用户对一个项目感兴趣时,他们会通过平台的“关注”功能来标记该项目,以便后续能够及时获取项目的最新动态,如代码更新、版本发布、问题讨论等。关注数越高,表明项目在社区中的影响力越大,潜在的用户群体和贡献者也越多。'
|
||||
},
|
||||
community_impact_praise: {
|
||||
name: '项目收藏度',
|
||||
// 权重百分比
|
||||
WeightedScore: 30,
|
||||
remark: '项目收藏度反映了用户对项目的认可程度和长期关注意愿。用户点赞收藏项目通常是因为他们认为该项目具有较高的价值,值得长期跟踪和参考。项目收藏度不仅体现了项目的受欢迎程度,还表明用户有较高的可能性在未来参与项目的贡献或使用项目的成果。收藏数多的项目往往在社区中具有较高的口碑和用户粘性。'
|
||||
},
|
||||
community_impact_fork: {
|
||||
name: '项目派生度',
|
||||
// 权重百分比
|
||||
WeightedScore: 40,
|
||||
remark: '项目派生度反映了项目的可扩展性和影响力。当用户认为一个项目具有良好的基础和潜力时,他们会基于该项目派生新的项目,进行进一步的开发和创新。派生数多的项目不仅在原项目的基础上得到了扩展和改进,还能够激发社区的创新活力,推动相关技术的发展。派生数是衡量项目在开源社区中影响力和生态构建能力的重要指标之一。'
|
||||
}
|
||||
},
|
||||
project_maturity: {
|
||||
project_maturity_complete_issue: {
|
||||
name: '解决任务量',
|
||||
// 权重百分比
|
||||
WeightedScore: 40,
|
||||
remark: '项目解决任务量是指项目中所有已解决和未解决的issue比例及issue总数在当前社区中的一个加权值。这反映了项目在任务管理和社区反馈处理方面的表现。项目issue高总数表明项目具有良好的问题跟踪机制,项目的高issue解决率能够及时响应用户和开发者的需求。通过计算issue的相关数据,来跟踪项目问题修复的效率,优化问题解决流程。'
|
||||
},
|
||||
project_maturity_pullrequest: {
|
||||
name: '累计开发活动量',
|
||||
// 权重百分比
|
||||
WeightedScore: 40,
|
||||
remark: '累计开发活动量是指项目中所有合并和未合并的Pull Request(PR)总数相对于当前社区对应水平的比例加权。这一指标反映了项目的开发活跃度、协作效率以及社区贡献规模。较高的 PR 总数表明项目吸引了广泛的开发者参与,具备良好的代码贡献机制;而 PR 的持续增长则说明项目保持健康的开发节奏和开放的协作文化。通过分析 PR 的提交、评审和合并数据,可以优化代码审查流程,提升项目开发效率,并激励更多贡献者参与。'
|
||||
},
|
||||
project_maturity_project_create_day: {
|
||||
name: '项目有效开发年龄',
|
||||
// 权重百分比
|
||||
WeightedScore: 20,
|
||||
remark: '项目有效开发年龄是指项目自首次提交以来,具有实际代码贡献的有效天数相对于社区同类项目水平的比例加权。该指标衡量项目的持续开发能力和长期维护稳定性,较高的有效开发天数表明项目具备持久的生命力,能够持续迭代优化。通过分析代码提交的时间分布,可以评估项目的开发节奏,优化维护策略,并确保项目的可持续发展。'
|
||||
}
|
||||
},
|
||||
project_health: {
|
||||
health_issue: {
|
||||
name: '任务过程效率分析',
|
||||
// 权重百分比
|
||||
WeightedScore: 10,
|
||||
remark: '任务过程效率分析衡量项目在 Issue 处理上的效能,通过已解决 Issue 数量与 Issue 总数的相对比例来评估团队响应问题和修复缺陷的效率。较高的解决率表明项目具备良好的问题跟踪和响应机制,能够有效管理用户反馈和开发任务;而持续优化的解决流程则有助于提升社区参与度和项目可靠性。通过监控 Issue 的解决周期和分布,可以识别协作瓶颈,优化任务分配和优先级策略。'
|
||||
},
|
||||
health_pullrequest: {
|
||||
name: '代码过程效率分析',
|
||||
// 权重百分比
|
||||
WeightedScore: 10,
|
||||
remark: '代码过程效率析是指项目中已合并的代码提交请求数量与总代码提交请求数量的比例加权,评估项目处理代码贡献的效率水平。该指标既反映了团队审查和合并PR的及时性,也体现了项目对代码质量的把控能力。较高的合并率表明项目建立了流畅的协作机制,能够有效整合社区开发力量,同时合理的PR筛选机制也保障了代码质量。通过持续跟踪这一指标,项目可以优化代码审查流程,在开发效率和质量控制之间取得更好平衡。'
|
||||
},
|
||||
health_code: {
|
||||
name: '代码质量分析',
|
||||
// 权重百分比
|
||||
WeightedScore: 40,
|
||||
remark: '代码质量分析是评估项目代码健壮性和可维护性的核心指标,通过静态代码分析直接反映项目的代码质量水平和长期可维护性,高质量的代码表现为较低的缺陷密度、以及一致的编码风格。持续监控代码质量有助于及时发现潜在问题,优化架构设计,并确保项目在快速迭代过程中保持技术可靠性。'
|
||||
},
|
||||
health_license: {
|
||||
name: '项目许可证分析',
|
||||
// 权重百分比
|
||||
WeightedScore: 40,
|
||||
remark: '开源许可证分析是评估项目合规性与协作开放性的关键指标,通过检测项目是否包含标准化开源许可证来判定其法律完备性。该指标反映了项目团队对知识产权管理的专业程度,以及项目参与商业生态的合规能力。通过该分析可识别项目在合规治理方面的成熟度,为后续生态化发展奠定法律基础。'
|
||||
}
|
||||
},
|
||||
team_impact: {
|
||||
team_impact_member: {
|
||||
name: '团队稳定性',
|
||||
// 权重百分比
|
||||
WeightedScore: 40,
|
||||
remark: '团队稳定性是评估开源项目可持续发展能力的重要指标,通过当前活跃贡献者数量与社区最大团队规模的比例加权进行量化。该指标不仅反映项目当前的协作规模,更揭示了核心开发者的留存情况、项目吸引人才的能力,以及应对人员流动的韧性。较高的稳定性比值表明项目已形成良性的开发者生态,为项目管理提供了重要参考,帮助识别团队建设中的薄弱环节,优化人才发展策略。'
|
||||
},
|
||||
team_impact_recent_member: {
|
||||
name: '新人增长率',
|
||||
// 权重百分比
|
||||
WeightedScore: 40,
|
||||
remark: '新人增长率反映项目吸引新贡献者的能力,通过近一个月新增成员数与社区最高增长值的比例加权进行评估。该指标直接体现社区活力和扩展潜力,较高的增长率较高的增长率表明项目具备良好的技术影响力和传播效应,能够持续吸引新鲜血液加入。定期监测可帮助优化新人引导和社区运营策略。'
|
||||
},
|
||||
team_impact_issue_assigner: {
|
||||
name: '任务均衡度',
|
||||
// 权重百分比
|
||||
WeightedScore: 20,
|
||||
remark: '任务均衡度衡量团队成员间的工作量分配合理性,通过分析每位成员在特定周期内的任务承担数量及参与深度来评估。该指标反映了团队协作的公平性和资源利用率,均衡的任务分配既能避免核心成员过载,又能确保新成员获得适当参与机会。理想的均衡状态有助于维持团队积极性,促进知识共享,是项目可持续发展的关键因素之一。'
|
||||
}
|
||||
},
|
||||
develop_activity: {
|
||||
develop_activity_commit: {
|
||||
name: '代码提交度',
|
||||
// 权重百分比
|
||||
WeightedScore: 40,
|
||||
remark: '代码提交度是衡量项目开发活跃度的核心指标之一,通过近一月提交次数与社区最高值的比值来量化评估。该指标直接反映项目的迭代速度和开发强度,较高的比值表明团队保持稳定的产出节奏,能够持续推进功能改进和优化。这一指标为项目管理提供了直观的开发效能参考。'
|
||||
},
|
||||
develop_activity_issue: {
|
||||
name: '缺陷反馈度',
|
||||
// 权重百分比
|
||||
WeightedScore: 20,
|
||||
remark: '缺陷报告数量因素反映了项目在质量管理和社区参与方面的活跃程度,通过比较近一个月的缺陷反馈数量与社区最高值进行评估,帮助开发者了解项目的问题发现能力和用户参与热度。适中的指标值表明项目既保持着良好的质量反馈机制,又能有效处理问题,避免积压,是评估项目健康度的重要参考。'
|
||||
},
|
||||
develop_activity_pullrequest: {
|
||||
name: '代码合并请求率',
|
||||
// 权重百分比
|
||||
WeightedScore: 20,
|
||||
remark: '代码合并请求数量因素衡量项目接收和处理社区代码贡献的活跃程度,通过比较近一个月的PR数量与社区最高值的比例来评估。该指标既反映了项目对外部开发者贡献的开放性和吸引力,也体现了社区成员的技术参与热情。较高的PR活跃度通常意味着项目具备良好的协作生态和健康的开发循环,是评估项目可持续发展能力的重要参考指标之一。'
|
||||
},
|
||||
develop_activity_release: {
|
||||
name: '版本发布率',
|
||||
// 权重百分比
|
||||
WeightedScore: 20,
|
||||
remark: '版本发布数量因素衡量项目的迭代更新能力,通过近一月正式版本发布次数与社区最高值的比例来评估加权。该指标既反映项目团队的开发规划执行力,也体现对功能改进和问题修复的响应速度。稳定的版本发布节奏表明项目保持健康的开发周期管理,既能持续交付价值,又能确保版本质量,是评估项目成熟度的重要参考。'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const colors = ['#466aff', '#c566fe', '#fdcd00']
|
||||
|
After Width: | Height: | Size: 502 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 416 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 933 KiB |
|
After Width: | Height: | Size: 314 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
|
@ -0,0 +1,271 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Spin } from 'antd';
|
||||
import echarts from 'echarts';
|
||||
import axios from "axios";
|
||||
|
||||
import HeaderTitle from './img/header_title.png'
|
||||
import Star from './img/star.png'
|
||||
import Large from '../Server/img/large.png';
|
||||
import Middle from '../Server/img/middle.png';
|
||||
import Small from '../Server/img/small.png';
|
||||
|
||||
import { typeName, typeDetail, subType, subTypeDetail, colors } from './config'
|
||||
|
||||
import './Index.scss';
|
||||
|
||||
|
||||
function PortraitDetail(props) {
|
||||
const [dataLoading, setDataLoading] = useState(false);
|
||||
const { owner, projectsId, type } = props.match.params;
|
||||
|
||||
const [cardIndex, setCardIndex] = useState(0);
|
||||
|
||||
const [score, setScore] = useState([]);
|
||||
const [scoreNum, setScoreNum] = useState(0);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
// 组织、项目存在且项目画像类型存在
|
||||
if (owner && projectsId && type && typeName[type]) {
|
||||
getData()
|
||||
}
|
||||
}, [owner, projectsId, type])
|
||||
|
||||
// 根据类型请求接口
|
||||
async function getData() {
|
||||
// 社区影响力: /v1/${owner}/${projectsId}/portrait/community_impact.json
|
||||
// 项目成熟度: /v1/${owner}/${projectsId}/portrait/project_maturity.json
|
||||
// 项目健康度: /v1/${owner}/${projectsId}/project_health.json
|
||||
// 团队健康度: /v1/${owner}/${projectsId}/team_impact.json
|
||||
// 开发活跃度: /v1/${owner}/${projectsId}/develop_activity.json
|
||||
const url = `/v1/${owner}/${projectsId}/portrait/${type}`;
|
||||
axios(url).then(res => {
|
||||
if (res && res.status == '200' && res.data) {
|
||||
const _score = []
|
||||
for (let index = 0; index < subType[type].length; index++) {
|
||||
let sub = subType[type][index];
|
||||
let percent = parseInt((res.data[sub] / subTypeDetail[type][sub].WeightedScore) * 100)
|
||||
// 按照画像类型的各个子类型拼装数据
|
||||
_score.push({
|
||||
key: sub,
|
||||
...subTypeDetail[type][sub],
|
||||
score: res.data[sub] ? res.data[sub].toFixed(2) : res.data[sub],
|
||||
percent: percent < 101 ? percent : 100
|
||||
})
|
||||
}
|
||||
setScoreNum(res.data[type])
|
||||
setScore(_score)
|
||||
setDataLoading(false);
|
||||
InitGauge(res.data[type])
|
||||
} else {
|
||||
console.log(`请求${url}出错,返回为:`, res);
|
||||
}
|
||||
})
|
||||
// 模拟数据
|
||||
// const res = {
|
||||
// data: {
|
||||
// "community_impact_praise": 10,
|
||||
// "community_impact_watcher": 20,
|
||||
// "community_impact_fork": 30,
|
||||
// "community_impact": "60.00"
|
||||
// },
|
||||
// status: 200
|
||||
// }
|
||||
}
|
||||
|
||||
// 初始化仪表盘
|
||||
function InitGauge(num) {
|
||||
var huan_val = document.getElementById("scoreValue");
|
||||
var myEcharts = echarts.init(huan_val);
|
||||
let option = {
|
||||
series: [
|
||||
{
|
||||
type: 'gauge',
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
center: ['50%', '90%'],
|
||||
radius: '160px',
|
||||
min: 0,
|
||||
max: 100,
|
||||
// 显示刻度数
|
||||
splitNumber: 5,
|
||||
axisLine: {
|
||||
show: false,
|
||||
lineStyle: {
|
||||
width: 10,
|
||||
color: [
|
||||
[0.25, '#da6fe6'],
|
||||
[0.5, '#d8c064'],
|
||||
[0.75, '#85d586'],
|
||||
[1, '#3ec7d7']
|
||||
]
|
||||
}
|
||||
},
|
||||
pointer: {
|
||||
show: true,
|
||||
// icon: 'path://M12.8,0.7l12,40.1H0.7L12.8,0.7z',
|
||||
length: '50%',
|
||||
width: 5,
|
||||
itemStyle: {
|
||||
color: 'auto'
|
||||
}
|
||||
},
|
||||
// 小刻度线
|
||||
axisTick: {
|
||||
length: 10,
|
||||
lineStyle: {
|
||||
color: 'auto',
|
||||
width: 2
|
||||
}
|
||||
},
|
||||
// 大刻度线
|
||||
splitLine: {
|
||||
length: 15,
|
||||
lineStyle: {
|
||||
color: 'auto',
|
||||
width: 3
|
||||
}
|
||||
},
|
||||
// 刻度
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: '#FFF',
|
||||
fontSize: 12,
|
||||
distance: 0,
|
||||
rotate: 'tangential'
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
show: false,
|
||||
offsetCenter: [0, '-25%'],
|
||||
fontSize: 16,
|
||||
color: '#FFF'
|
||||
},
|
||||
// 分数
|
||||
detail: {
|
||||
fontSize: 32,
|
||||
offsetCenter: [0, 0],
|
||||
valueAnimation: true,
|
||||
color: '#FFF'
|
||||
},
|
||||
progress: {
|
||||
show: true,
|
||||
roundCap: true,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: num,
|
||||
name: typeName[type]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
myEcharts.setOption(option);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div class="portrait-bg">
|
||||
<div class="portrait-main pt20">
|
||||
<div class="headBox">
|
||||
<img src={HeaderTitle} alt="" />
|
||||
<span>项目画像-{typeName[type]}
|
||||
<img src={Star} style={{ width: '14px', height: '14px', position: 'relative', top: '-10px' }} />
|
||||
</span>
|
||||
</div>
|
||||
{dataLoading && <Spin><div style={{ minHeight: 400, width: "100%" }}></div></Spin>}
|
||||
{
|
||||
!dataLoading &&
|
||||
<div className="pt20">
|
||||
{
|
||||
<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>{typeName[type]}</span>
|
||||
<span className="s_num">{scoreNum}</span>
|
||||
</div>
|
||||
</span> */}
|
||||
<div className="scoreValue" id="scoreValue"></div>
|
||||
<p className="scoreTitle mt15 mb15">本项目{typeName[type]}</p>
|
||||
<p className="scoreText">{typeDetail[type]}</p>
|
||||
</div>
|
||||
{
|
||||
score &&
|
||||
<ul className="scoreProgress">
|
||||
{
|
||||
<li>
|
||||
<span className="index">{scoreNum}</span>
|
||||
<span className="name">{typeName[type]}</span>
|
||||
<div className="progress">
|
||||
{
|
||||
score.map((i, k) => {
|
||||
if (i.score > 0) {
|
||||
return <p key={k} style={{ width: `${i.score}%`, backgroundColor: colors[k] }}></p>
|
||||
} else {
|
||||
return <></>
|
||||
}
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<span><span className="color-blue">{scoreNum}</span><span className="ml3 mr3">/</span>100</span>
|
||||
</li>
|
||||
}
|
||||
{
|
||||
score.map((i, k) => {
|
||||
return (
|
||||
<li key={k}>
|
||||
<span className="index">{i.score}</span>
|
||||
<span className="name">{i.name}</span>
|
||||
<div className="progress"><span style={{ width: `${i.percent}%`, backgroundColor: `${colors[k]}` }}></span></div>
|
||||
<span><span className="color-blue">{i.score}</span><span className="ml3 mr3">/</span>{i.WeightedScore}</span>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div className="subDetail">
|
||||
{
|
||||
score && score[cardIndex] &&
|
||||
<div className="subScore">
|
||||
<p className="typeValue fw500">
|
||||
{score[cardIndex].name}总分:<span style={{ color: '#466aff' }}>{score[cardIndex].score}</span><span className="mr40"></span>
|
||||
{score[cardIndex].name}权重:<span style={{ color: '#f70202' }}>{score[cardIndex].WeightedScore}%</span>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
<div className="subContent">
|
||||
{
|
||||
score && score[cardIndex] &&
|
||||
<>
|
||||
<div className="subTitle">{score[cardIndex].name}</div>
|
||||
<div className="subRemark">{score[cardIndex].remark}</div>
|
||||
</>
|
||||
}
|
||||
<ul className="typeLi">
|
||||
{
|
||||
score.map((i, k) => {
|
||||
// 通过className切换不同的图片显示
|
||||
return (
|
||||
<li key={k} className={cardIndex === k ? "active" : ""} onClick={() => { setCardIndex(k) }}>
|
||||
<div className={'sub_icon ' + i.key}></div>
|
||||
<span>{i.name}</span>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default PortraitDetail;
|
||||