修改iframe及左侧菜单代码

This commit is contained in:
何童崇 2022-03-15 10:58:29 +08:00
parent dfcecc1f2d
commit c3ce2cc6a0
14 changed files with 1434 additions and 58 deletions

View File

@ -0,0 +1,152 @@
import React, { useEffect } from 'react';
import * as echarts from 'echarts';
let fontSizeText = 16;
let clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
fontSizeText = 16
} else if (clientWidth >= 1000 && clientWidth <= 2000) {
fontSizeText = 14 + (clientWidth - 1000) / 500;
} else if (clientWidth > 2000) {
fontSizeText = 16 + (clientWidth - 2000) / 500;
} else if (clientWidth > 3000) {
fontSizeText = 18 + (clientWidth - 3000) / 2000;
}
let fontSizeTitle = 1.25 * fontSizeText;
export default ({ id,className, title, xData, yData }) => {
useEffect(() => {
let newEchartBar = document.getElementById(id) && echarts.init(document.getElementById(id));
let textColor = "#7988a5";
let normalColor = "#e8e8ed";
let option = {
grid: {
left: "3%",
top: "15%",
right: "8%",
bottom: 0,
containLabel: true
},
title: {
text: title,
fontSize: fontSizeTitle,
position: 'inside',
textStyle: {
color: textColor
}
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
textStyle: {
color: "#fff"
}
},
},
xAxis: [{
type: "category",
data: xData,
axisPointer: {
type: "shadow"
},
axisLabel: {
textStyle: {
color: textColor,
fontSize: .75 * fontSizeText
},
interval: 0,
rotate: 40
},
axisLine: {
lineStyle: {
color: normalColor
}
},
axisTick: {
show: false
},
splitLine: {
show: false
}
}],
yAxis: [{
type: "value",
minInterval: 1,
nameTextStyle: {
color: textColor,
fontSize: fontSizeText
},
axisLabel: {
formatter: "{value}",
textStyle: {
color: textColor,
fontSize: fontSizeText
}
},
axisLine: {
lineStyle: {
color: normalColor
}
},
axisTick: {
show: false
},
splitLine: {
lineStyle: {
color: normalColor
}
},
}],
series: [{
type: "bar",
data: yData,
barWidth: "60%",
itemStyle: {
normal: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: "#deeefe"
},
{
offset: 1,
color: "#2c8bff"
}
],
globalCoord: false
}
}
},
label: {
show: true,
// position: ['-20%', -1.2 * fontSizeText],
position: "top",
// color: "#2c8bff",
color: '#555',
fontSize: fontSizeText,
formatter: function (params) {
// var percent = 0;
// percent = ((params.value / allStaff.num) * 100).toFixed(0);
return params.value || ''
// + '\n' + percent + '%';
},
}
}]
};
newEchartBar && newEchartBar.setOption(option);
}, [id, title, xData, yData])
return (
<div id={id} key={id} className={className} style={{minHeight:"20vh"}}>
</div>
)
}

View File

@ -0,0 +1,198 @@
import React, { useEffect, useState } from 'react';
import * as echarts from 'echarts';
let fontSizeText = 16;
let clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
fontSizeText = 16
} else if (clientWidth >= 1000 && clientWidth <= 2000) {
fontSizeText = 14 + (clientWidth - 1000) / 500;
} else if (clientWidth > 2000) {
fontSizeText = 16 + (clientWidth - 2000) / 500;
} else if (clientWidth > 3000) {
fontSizeText = 18 + (clientWidth - 3000) / 2000;
}
const colorList = ["#27e8b4", '#77b4fd', '#ffd55f', '#ac90ef', '#9E87FF'];
export default ({ id = "uid", className, title, xData, seriesArr }) => {
useEffect(() => {
let newEchartBar = document.getElementById(id) && echarts.init(document.getElementById(id));
let option = {
backgroundColor: '#fff',
legend: {
icon: 'circle',
top: '5%',
itemWidth: 6,
itemGap: 20,
textStyle: {
color: '#556677'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
color: "#DCE2E8"
}
},
backgroundColor: '#fff',
textStyle: {
color: '#5c6c7c'
},
padding: [10, 10],
extraCssText: 'box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)'
},
grid: {
top: '15%'
},
xAxis: [{
type: 'category',
data: xData,
axisLine: {
lineStyle: {
color: '#DCE2E8'
}
},
axisTick: {
show: false
},
axisLabel: {
// interval: 0,
textStyle: {
color: '#556677'
},
// x
fontSize: 0.75*fontSizeText,
// margin:x
margin: fontSizeText,
rotate: 40
},
axisPointer: {
label: {
padding: [0, 0, 10, 0],
/*
除了padding[0]建议必须是0之外其他三项可随意设置
和CSSpadding相同[]
如果需要下边线超出文字设左右padding即可左右padding最好相同
padding[2]的10:
10 = 文字距下边线的距离 + 下边线的宽度
UI图中文字距下边线距离为7 下边线宽度为2
则padding: [0, 0, 9, 0]
*/
// marginaxisLabelmargin!
margin: fontSizeText,
//
fontSize: 0.75*fontSizeText,
backgroundColor: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: '#fff' // 0%
}, {
// offset: 0.9,
offset: 0.86,
//0.86 = + 线/ + 线 + 线
color: '#fff' // 0%
}, {
offset: 0.86,
color: '#33c0cd' // 0%
}, {
offset: 1,
color: '#33c0cd' // 100%
}],
global: false // false
}
}
},
boundaryGap: false
}],
yAxis: [{
name: "(个)",
minInterval: 1,
type: 'value',
axisTick: {
show: false
},
axisLine: {
show: false,
lineStyle: {
color: '#556677'
}
},
axisLabel: {
textStyle: {
color: '#556677'
}
},
splitLine: {
lineStyle: {
color: '#e8e8ed'
}
}
}],
series: [],
};
for (let i = 0; i < seriesArr.length; i++) {
if (seriesArr[i].data) {
option.series.push({
name: seriesArr[i].name,
type: 'line',
data: seriesArr[i].data,
symbolSize: 1,
symbol: 'circle',
smooth: true,
yAxisIndex: 0,
showSymbol: false,
itemStyle: {
normal: {
color: colorList[i],
borderColor: colorList[i]
}
}
});
}
if(seriesArr[i].name=='新增代码数'&&seriesArr[i].data){
option.yAxis.push({
name: "(行)",
type: 'value',
position: 'right',
axisTick: {
show: false
},
axisLabel: {
textStyle: {
color: '#556677'
},
formatter: '{value}'
},
axisLine: {
show: false,
lineStyle: {
color: '#556677'
}
},
splitLine: {
show: false
}
});
}
}
newEchartBar && newEchartBar.setOption(option);
}, [xData,seriesArr])
return (
<div id={id} key={id} className={className} style={{ minHeight: "400px" }}>
</div>
)
}

View File

@ -10,6 +10,49 @@
*/
export default [
{
key: "statistics",
title: "概览",
location: "",
icon: "icon-yonghuguanli",
target: "_self",
urltype: "self",
children: [
{
key: "global",
title: "全局统计",
location: "/managements/statistics/global",
menustatus: "Y",
parentKey: "statistics",
icon: "",
target: "",
urltype: "self",
children: [],
},
// {
// key: "member",
// title: "成员工作统计",
// location: "/managements/statistics/member",
// menustatus: "Y",
// parentKey: "statistics",
// icon: "",
// target: "",
// urltype: "self",
// children: [],
// },
// {
// key: "activity",
// title: "项目活跃度统计",
// location: "/managements/statistics/activity",
// menustatus: "Y",
// parentKey: "statistics",
// icon: "",
// target: "",
// urltype: "self",
// children: [],
// },
],
},
{
key: "task",
title: "创客空间",
@ -396,4 +439,26 @@ export default [
},
],
},
{
key: "settings",
title: "网站配置",
location: "",
icon: "icon-yonghuguanli",
target: "",
urltype: "current_main_site_url",
children: [
{
key: "platform_communicates",
title: "社区动态管理",
location: "/managements/admins/platform_communicates",
menustatus: "Y",
parentKey: "settings",
icon: "",
target: "",
urltype: "current_main_site_url",
params:'layout=none',
children: [],
},
],
},
];

View File

@ -9,11 +9,13 @@ const titleObj = {};
const locationObj = {};
const parentKeyObj = {};
const allRouter = [];
const rootSubmenuKeys =[];
function titleFun(menus) {
menus.forEach(i => {
titleObj[i.key] = i.title;
locationObj[i.location] = i;
i.location && allRouter.push(i.location);
!i.parentKey && rootSubmenuKeys.push(i.key);
if (Array.isArray(i.children) && i.children.length > 0) {
titleFun(i.children);
i.parentKey && (parentKeyObj[i.key] = i.parentKey);
@ -31,7 +33,13 @@ export default (props) => {
let initCurrent = locationObj[pathname] ? [locationObj[pathname].key] : [];
let initKeyPath = locationObj[pathname] ? [locationObj[pathname].parentKey, locationObj[pathname].key] : [];
const defaultOpenKeys = [];
const myKey = pathname && locationObj[pathname] && locationObj[pathname].parentKey;
defaultOpenKeys.push(myKey ? myKey : "task");
parentKeyObj[myKey]&&defaultOpenKeys.push(parentKeyObj[myKey]);
const [openKeys, setOpenKeys] =useState(defaultOpenKeys);
const [current, setCurrent] = useState(initCurrent);
const [keyPath, setKeyPath] = useState(initKeyPath);
const [acitve, setActive] = useState(locationObj[pathname] || {});
@ -84,6 +92,16 @@ export default (props) => {
}
}
function onOpenChange(newOpenKeys){
const latestOpenKey = newOpenKeys.find(key=>openKeys.indexOf(key) === -1);
if(rootSubmenuKeys.indexOf(latestOpenKey) === -1){
setOpenKeys(newOpenKeys);
}else{
setOpenKeys(latestOpenKey?[latestOpenKey]:[]);
}
console.log(newOpenKeys);
}
useEffect(() => {
//
history.listen(historyLocation => {
@ -95,19 +113,16 @@ export default (props) => {
})
}, [history]);
const defaultOpenKeys = [];
const myKey = pathname && locationObj[pathname] && locationObj[pathname].parentKey;
defaultOpenKeys.push(myKey ? myKey : "task");
defaultOpenKeys.push(parentKeyObj[myKey]);
return (
<Fragment>
<div className="layouts">
<Menu
mode="inline"
defaultOpenKeys={defaultOpenKeys}
// defaultOpenKeys={defaultOpenKeys}
selectedKeys={current}
onClick={handleClick}
onOpenChange={onOpenChange}
openKeys={openKeys}
>
{getMenuList(urlConfig)}
</Menu>
@ -115,7 +130,7 @@ export default (props) => {
<div className="managements">
<div className="head-title">{head(keyPath)}</div>
{acitve.urltype == "self" ? children :
<iframe id="iframe" className="iframe-item" src={`${setting[acitve.urltype] + acitve.location}?${acitve.params}`} onLoad={iframeLoad}></iframe>
<iframe id="iframe" className="iframe-item" src={`${setting[acitve.urltype]}${acitve.location.startsWith('/managements')?acitve.location.replace(/\/managements/,""):acitve.location}?${acitve.params}`} onLoad={iframeLoad}></iframe>
}
</div>
</Fragment>

View File

@ -1,5 +1,4 @@
import React, { useEffect, useState } from "react";
import { Route, Switch } from "react-router-dom";
import { withRouter } from "react-router";
import { SnackbarHOC } from "educoder";
@ -11,6 +10,12 @@ import { ImageLayerOfCommentHOC } from "../modules/page/layers/ImageLayerOfComme
import Layouts from "./components/layouts";
import './index.scss';
// 概览
const Statistics = Loadable({
loader: () => import("./statistics/"),
loading: Loading,
});
{/* 任务管理审核 */ }
const TaskManage = Loadable({
loader: () => import("../military/task/taskManage"),
@ -93,23 +98,8 @@ const CompetitionList = Loadable({
});
// 公告管理
const NoticeList = Loadable({
loader: () => import("./notice/noticeList"),
loading: Loading,
});
// 公告管理详情
const NoticeDetail = Loadable({
loader: () => import("./notice/noticeDetail"),
loading: Loading,
});
// 公告管理编辑
const NoticeEdit = Loadable({
loader: () => import("./notice/noticeEdit"),
loading: Loading,
});
// 公告reader
const NoticeReader = Loadable({
loader: () => import("./notice/noticeReader"),
const NoticeManage = Loadable({
loader: () => import("./notice"),
loading: Loading,
});
@ -136,6 +126,15 @@ const Managements = (propsF) => {
<div className="newMain clearfix">
<Layouts {...propsF} >
<Switch {...propsF}>
{/* 概览 */}
<Route
path="/managements/statistics"
render={(props) => (
<Statistics {...propsF} {...props} />
)}
></Route>
{/* 任务管理审核 */}
<Route
path="/managements/task/taskManage/:publishMode"
@ -208,8 +207,6 @@ const Managements = (propsF) => {
)}
></Route>
{/* 专家审核 */}
<Route
path="/managements/expert/register"
@ -274,39 +271,11 @@ const Managements = (propsF) => {
)}
></Route>
{/* 公告列表 */}
{/* 公告管理 */}
<Route
path="/managements/notice/list/:isChecked"
path="/managements/notice"
render={(props) => (
<NoticeList {...propsF} {...props} />
)}
></Route>
{/* 公告详情 */}
<Route
path="/managements/notice/detail/:noticeId"
render={(props) => (
<NoticeDetail {...propsF} {...props} />
)}
></Route>
{/* 公告编辑 */}
<Route
path="/managements/notice/edit/:noticeId"
render={(props) => (
<NoticeEdit {...propsF} {...props} />
)}
></Route>
{/* 公告新增 */}
<Route
path="/managements/notice/edit"
render={(props) => (
<NoticeEdit {...propsF} {...props} />
)}
></Route>
{/* 公告预览 */}
<Route
path="/managements/notice/reader/:noticeId"
render={(props) => (
<NoticeReader {...propsF} {...props} />
<NoticeManage {...propsF} {...props} />
)}
></Route>
</Switch>

View File

@ -3,6 +3,9 @@
padding:0 1.875rem 1.6rem;
background: #f6f9fe;
min-height: 80vh;
.content{
margin-top:1rem;
}
}
.management-content-head{
background-color: #fff;

View File

@ -0,0 +1,67 @@
import React from "react";
import { Route, Switch } from "react-router-dom";
import Loadable from "react-loadable";
import Loading from "../../Loading";
//
const NoticeList = Loadable({
loader: () => import("./noticeList"),
loading: Loading,
});
//
const NoticeDetail = Loadable({
loader: () => import("./noticeDetail"),
loading: Loading,
});
//
const NoticeEdit = Loadable({
loader: () => import("./noticeEdit"),
loading: Loading,
});
// reader
const NoticeReader = Loadable({
loader: () => import("./noticeReader"),
loading: Loading,
});
export default (propsF)=>{
return (
<Switch {...propsF}>
{/* 公告列表 */}
<Route
path="/managements/notice/list/:isChecked"
render={(props) => (
<NoticeList {...propsF} {...props} />
)}
></Route>
{/* 公告详情 */}
<Route
path="/managements/notice/detail/:noticeId"
render={(props) => (
<NoticeDetail {...propsF} {...props} />
)}
></Route>
{/* 公告编辑 */}
<Route
path="/managements/notice/edit/:noticeId"
render={(props) => (
<NoticeEdit {...propsF} {...props} />
)}
></Route>
{/* 公告新增 */}
<Route
path="/managements/notice/edit"
render={(props) => (
<NoticeEdit {...propsF} {...props} />
)}
></Route>
{/* 公告预览 */}
<Route
path="/managements/notice/reader/:noticeId"
render={(props) => (
<NoticeReader {...propsF} {...props} />
)}
></Route>
</Switch>
)
}

View File

@ -0,0 +1,175 @@
import React, { useEffect, useState } from 'react';
import { Table, Pagination, notification, Input } from 'antd';
import { paramToUrl } from '../../common/utils';
import fetch from 'military/notice/fetch';
const { Search } = Input;
export default function Activity(props) {
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [orderBy, setOrderBy] = useState('');
const [dataList, setDataList] = useState([]);
const [loading, setLoading] = useState(false);
const [projectName, setProjectName] = useState('');
useEffect(() => {
// table
let params = {
orderBy,
curPage,
projectName,
pageSize: 10,
};
let url = encodeURI(paramToUrl('/api/ProjectActivityData/', params));
setLoading(true);
fetch({
url,
method: 'get',
}).then(res => {
if (res.code === '1') {
setDataList(res.data.rows);
setTotal(res.data.total);
} else {
setDataList([]);
setTotal(0);
notification.open({
message: "错误",
description: res.message,
});
}
setLoading(false);
});
}, [curPage, orderBy, projectName]);
function handleTableChange(pagination, filters, sorter) {
if (sorter.order) {
if (sorter.order === 'ascend') {
setOrderBy(sorter.field + 'Asc');
} else {
setOrderBy(sorter.field + 'Desc');
}
} else {
setOrderBy('');
}
}
function onCell(record) {
return {
onClick: event => { onCellClick(record) }, //
}
}
function onCellClick(record) {
let url = encodeURI(paramToUrl('/api/ProjectActivityData/getUrl', { projectId: record.projectId }));
fetch({
url,
method: 'get',
}).then(res => {
if (res.code === '1') {
window.open(res.data);
} else {
notification.open({
message: "错误",
description: res.message,
});
}
});
}
const columns = [
{
title: '项目名',
dataIndex: 'name',
key: 'name',
className: 'link',
onCell,
},
{
title: '活跃度',
key: 'activityScore',
dataIndex: 'activityScore',
sorter: true,
},
{
title: '浏览数',
key: 'visits',
dataIndex: 'visits',
sorter: true,
},
{
title: '点赞数',
key: 'praisesCount',
dataIndex: 'praisesCount',
sorter: true,
},
{
title: '关注数',
key: 'watchersCount',
dataIndex: 'watchersCount',
sorter: true,
},
{
title: '任务数',
key: 'issuesCount',
dataIndex: 'issuesCount',
sorter: true,
},
{
title: 'PR数',
key: 'pullRequestsCount',
dataIndex: 'pullRequestsCount',
sorter: true,
},
{
title: '版本数',
key: 'versionsCount',
dataIndex: 'versionsCount',
sorter: true,
},
];
function searchFun(val) {
setProjectName(val);
setCurPage(1);
}
return (
<div className="content">
<Search
style={{ width: '300px', margin: '1em' }}
placeholder="请输入项目关键字"
enterButton="搜索"
size="large"
onSearch={searchFun}
className="global-search"
maxLength={20}
/>
<div className="table-detail">
<Table
loading={loading}
rowKey={(row) => row.id}
dataSource={dataList}
columns={columns}
pagination={false}
onChange={handleTableChange} //
/>
{dataList.length > 0 &&
<Pagination
onChange={(page) => { setCurPage(page) }}
current={curPage}
total={total}
/>}
</div>
</div>
);
}

View File

@ -0,0 +1,134 @@
import React, { useEffect, useState } from 'react';
import { Icon, Col, Row, Tabs, notification } from 'antd';
import EchartBar from '../../components/EchartBar';
import fetch from 'military/notice/fetch';
import './index.scss';
const { TabPane } = Tabs;
let length = 13;
let nowMonth = new Date().getMonth() + 1;
let year = new Date().getFullYear() - 1;
let monthArr = [];
if (nowMonth === 13) {
nowMonth = 1
}
for (let i = 0; i < length; i++) {
monthArr.push(year + '.' + nowMonth);
nowMonth++;
if (nowMonth === 13) {
nowMonth = 1;
year++;
}
}
export default function Global(props){
const [type, setType] = useState(1);
const [totalProjects, setTotalProjects] = useState(0);
const [totalMembers, setTotalMembers] = useState(0);
const [newAddedProjects, setNewAddedProjects] = useState([]);
const [newAddedMembers, setNewAddedMembers] = useState([]);
const [newCodeSubmissionTimes, setNewCodeSubmissionTimes] = useState([]);
const [taskCompletion, setTaskCompletion] = useState([]);
useEffect(() => {
fetch({
url: '/api/GlobalResourcesData/',
method: 'get',
}).then(res => {
if (res.code === '1') {
const data = res.data;
setTotalProjects(data.totalProjects);
setTotalMembers(data.totalMembers);
setNewAddedProjects(data.newAddedProjects);
setNewAddedMembers(data.newAddedMembers);
setNewCodeSubmissionTimes(data.newCodeSubmissionTimes);
setTaskCompletion(data.taskCompletion);
} else {
notification.open({
message: "错误",
description: res.message,
});
}
});
}, []);
return (
<div className="content">
<Row>
<Col className="box-item" xs={12} lg={6}>
<Icon type="folder" />
<div>
<p className="box-item-tit">项目总数</p>
<p className="box-item-num">{totalProjects}</p>
<p className="box-item-describe">当前项目总数</p>
</div>
</Col>
<Col className="box-item" xs={12} lg={6}>
<Icon type="user" />
<div>
<p className="box-item-tit">用户总数</p>
<p className="box-item-num">{totalMembers}</p>
<p className="box-item-describe">当前用户总数</p>
</div>
</Col>
</Row>
<Tabs defaultActiveKey="1" onChange={(key) => { setType(key) }}>
<TabPane tab="开源统计" key="1">
<Row>
<Col xs={24} lg={12}>
<EchartBar
id="newAddedProjects"
className="echart-box"
title="新项目统计"
xData={monthArr}
yData={newAddedProjects}
/>
</Col>
<Col xs={24} lg={12}>
<EchartBar
id="newAddedMembers"
className="echart-box"
title="新用户统计"
xData={monthArr}
yData={newAddedMembers}
/>
</Col>
<Col xs={24} lg={12}>
<EchartBar
id="newCodeSubmissionTimes"
className="echart-box"
title="代码提交次数"
xData={monthArr}
yData={newCodeSubmissionTimes}
/>
</Col>
<Col xs={24} lg={12}>
<EchartBar
id="taskCompletion"
className="echart-box"
title="任务完成量"
xData={monthArr}
yData={taskCompletion}
/>
</Col>
</Row>
</TabPane>
{/* <TabPane tab="" key="2">
</TabPane>
<TabPane tab="其他" key="4">
</TabPane> */}
</Tabs>
</div>
);
}

View File

@ -0,0 +1,50 @@
.box-item{
display: flex;
max-width: 300px;
justify-content: space-around;
margin: .75em;
padding: .5em;
background: #fff;
border-radius: .5em;
box-shadow: 0 1px 1px #d9d9d9;
.anticon{
font-size: 4em;
color: #1890ff;
}
.box-item-num{
font-size: 2em;
margin-bottom: 0;
}
.box-item-describe{
font-size: .9em;
color: #999;
}
}
.ant-tabs-bar{
border: 0;
}
.ant-tabs-tabpane {
min-height: 25vh;
}
.ant-tabs-tab-active {
color: #000;
font-weight: 600;
}
.ant-tabs-nav .ant-tabs-tab:hover {
color: #000;
font-weight: 600;
}
.echart-box{
height: 300px;
margin: .75em;
padding: 1em;
border-radius: .5em;
background: #fff;
border:1px solid #d9d9d9;
}

View File

@ -0,0 +1,49 @@
import React from "react";
import { Route, Switch } from "react-router-dom";
import Loadable from "react-loadable";
import Loading from "../../Loading";
//
const Global = Loadable({
loader: () => import("./global"),
loading: Loading,
});
//
const Member = Loadable({
loader: () => import("./member"),
loading: Loading,
});
//
const Activity = Loadable({
loader: () => import("./activity"),
loading: Loading,
});
export default (propsF)=>{
return (
<Switch {...propsF}>
{/* 全局统计 */}
<Route
path="/managements/statistics/global"
render={(props) => (
<Global {...propsF} {...props} />
)}
></Route>
{/* 成员工作统计 */}
<Route
path="/managements/statistics/member"
render={(props) => (
<Member {...propsF} {...props} />
)}
></Route>
{/* 活跃度统计 */}
<Route
path="/managements/statistics/activity"
render={(props) => (
<Activity {...propsF} {...props} />
)}
></Route>
</Switch>
)
}

View File

@ -0,0 +1,280 @@
import React, { useEffect, useState } from 'react';
import { Col, Row, Select, Button, DatePicker, notification } from 'antd';
import moment from 'moment';
import EchartLine from '../../components/EchartLine';
import { beforeDayArr, paramToUrl } from '../../common/utils';
import TableDetail from './table-detail';
import fetch from 'military/notice/fetch';
import './index.scss';
const { Option } = Select;
const ButtonGroup = Button.Group;
const { RangePicker } = DatePicker;
const monthArr = beforeDayArr();
const nowDate = moment(new Date()).format('YYYY-MM-DD');
const weekAgo = moment(new Date().setDate(new Date().getDate() - 6)).format('YYYY-MM-DD');
console.log(weekAgo);
const monthAgo = moment(new Date().setDate(new Date().getDate() - 30)).format('YYYY-MM-DD');
const threeMonthAgo = moment(new Date().setDate(new Date().getDate() - 90)).format('YYYY-MM-DD');
export default function Member(props) {
const [member, setMember] = useState(undefined);
const [memberList, setMemberList] = useState([]);
const [timeType, setTimeType] = useState(2);
const [dayNum, setDayNum] = useState(30);
const [timeArr, setTimeArr] = useState(monthArr);
const [startTime, setStartTime] = useState(monthAgo); //
const [endTime, setEndTime] = useState(nowDate);
const [newTask, setNewTask] = useState(null);
const [completedTask, setCompletedTask] = useState(null);
const [newPrNum, setNewPrNum] = useState(null);
const [newCommitNum, setNewCommitNum] = useState(null);
const [newCodeNum, setNewCodeNum] = useState(null);
// 线
function getAllDataLine() {
let params = {
startTime,
endTime,
}
let url = encodeURI(paramToUrl('/api/DeveloperData/total', params));
fetch({
url,
method: 'get',
}).then(res => {
if (res.code === '1') {
const data = res.data;
setNewTask(data.newTask);
setCompletedTask(data.completedTask);
setNewPrNum(data.newPrNum);
setNewCommitNum(data.newCommitNum);
setNewCodeNum(data.newCodeNum);
}
});
}
//
function getPersonData() {
let params = {
startTime,
endTime,
curPage: 1,
pageSize: 31,
userId: member.key,
userName:member.login,
};
if (timeType == 1) {
params.pageSize = 7;
} else if (timeType == 3) {
params.pageSize = 91;
} else if (timeType == 0) {
params.pageSize = dayNum;
}
let url = encodeURI(paramToUrl('/api/DeveloperData/detail/search', params));
fetch({
url,
method: 'get',
}).then(res => {
let newTask = [];
let completedTask = [];
let newPrNum = [];
let newCommitNum = [];
let newCodeNum = [];
if (res.code === '1' && res.data.rows) {
if (res.data.rows.length) {
for (const item of res.data.rows) {
newTask.unshift(item.newTask);
completedTask.unshift(item.completedTask);
newPrNum.unshift(item.newPrNum);
newCommitNum.unshift(item.newCommitNum);
newCodeNum.unshift(item.newCodeNum);
}
} else {
newCodeNum.fill(0, 0,);
}
} else {
notification.open({
message: "错误",
description: res.message,
});
}
setNewTask(newTask);
setCompletedTask(completedTask);
setNewPrNum(newPrNum);
setNewCommitNum(newCommitNum);
setNewCodeNum(newCodeNum);
});
}
function onSearch(value) {
if (value) {
getMemberList(value);
}
}
//
let timeout;
function getMemberList(value) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
function fake() {
fetch({
url: '/api/DeveloperData/detail/searchInput?userName=' + value,
method: 'get',
}).then(res => {
if (res.code === '1') {
setMemberList(res.data);
} else {
notification.open({
message: "错误",
description: res.message,
});
}
});
}
timeout = setTimeout(fake, 1000);
}
function searchInTime(timeType) {
setTimeType(timeType);
if (timeType === 1) {
setTimeArr(beforeDayArr(7));
setStartTime(weekAgo);
setEndTime(nowDate);
} else if (timeType === 2) {
setTimeArr(monthArr);
setStartTime(monthAgo);
setEndTime(nowDate);
} else if (timeType === 3) {
setTimeArr(beforeDayArr(91));
setStartTime(threeMonthAgo);
setEndTime(nowDate);
}
}
function changeDate(dates, dateStrings) {
if (dateStrings && dateStrings[0]) {
setStartTime(dateStrings[0]);
setEndTime(dateStrings[1]);
let dayNum = (dates[1] - dates[0]) / 86400000 + 1;
setTimeArr(beforeDayArr(dayNum, new Date(dates[1])));
setTimeType(0);
setDayNum(dayNum);
} else {
setTimeType(2);
setTimeArr(monthArr);
setStartTime(monthAgo);
setEndTime(nowDate);
}
}
useEffect(() => {
if (member) {
getPersonData();
} else {
getAllDataLine();
}
}, [startTime, endTime, member]);
function changeMember(val){
if(val){
for(const item of memberList){
if(val.key==item.id){
val.login=item.login
}
}
}
setMember(val);
}
return (
<div className="content">
<Row className="search-box">
<Col xs={24} sm={12} lg={10} >
<Select
showSearch
value={member}
placeholder={'请选择成员或输入成员查询'}
defaultActiveFirstOption={false}
filterOption={false}
onSearch={onSearch}
onChange={(val) => { changeMember(val) }}
notFoundContent={null}
allowClear={true}
labelInValue
>
{
memberList.map(d => <Option key={d.id}>{d.nickname || d.login}</Option>)
}
</Select>
</Col>
<Col xs={24} sm={12} lg={14} className="choose-time">
<ButtonGroup>
<Button type={timeType === 1 ? "primary" : ""} onClick={() => { searchInTime(1) }}>
近1周
</Button>
<Button type={timeType === 2 ? "primary" : ""} onClick={() => { searchInTime(2) }}>
近1月
</Button>
<Button type={timeType === 3 ? "primary" : ""} onClick={() => { searchInTime(3) }}>
近3月
</Button>
</ButtonGroup>
<div className="custom-time">
<div className="time-label">自定义时间段</div>
<RangePicker
placeholder={['--/--', '--/--']}
onChange={changeDate}
/>
</div>
</Col>
</Row>
<EchartLine
id="memberStatistics"
xData={timeArr}
seriesArr={
[{
name: '新增任务数',
data: newTask
}, {
name: '完成任务数',
data: completedTask
}, {
name: '新增PR数',
data: newPrNum
}, {
name: '新增提交数',
data: newCommitNum
}, {
name: '新增代码数',
data: newCodeNum
}]
}
/>
<h4 className="mt20">详情列表</h4>
<TableDetail
member={member}
startTime={startTime}
endTime={endTime}
/>
</div>
);
}

View File

@ -0,0 +1,44 @@
.search-box {
margin-bottom: .75rem;
.ant-select {
min-width: 280px;
}
.ant-btn:active, .ant-btn.active{
color: #fff;
background-color: #096dd9;
border-color: #096dd9;
}
.choose-time{
display: flex;
justify-content: flex-end;
}
.ant-btn-group{
margin-right: 10px;
}
.custom-time{
display: flex;
justify-content: flex-end;
align-items: center;
}
.ant-calendar-picker{
max-width: 250px;
}
.time-label{
background: #fafbfc;
line-height: 30px;
padding: 0 10px;
border: 1px solid #d9d9d9;
border-right: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.ant-calendar-picker-input{
text-align: left;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
}

View File

@ -0,0 +1,175 @@
import React, { useEffect, useState } from 'react';
import { Table, Pagination, notification } from 'antd';
import { paramToUrl } from '../../common/utils';
import fetch from 'military/notice/fetch';
export default ({ member, startTime, endTime }) => {
const [curPage, setCurPage] = useState(1);
const [total, setTotal] = useState(0);
const [orderBy, setOrderBy] = useState('');
const [dataList, setDataList] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
getTableData();
}, [member, startTime, endTime, orderBy, curPage]);
// table
function getTableData() {
let params = {
startTime,
endTime,
orderBy,
curPage,
pageSize: 10,
};
setLoading(true);
let url;
if (member) {
//
params = {
...params,
userId: member.key,
userName: member.login,
};
url = encodeURI(paramToUrl('/api/DeveloperData/detail/search', params));
} else {
//
url = encodeURI(paramToUrl('/api/DeveloperData/detail', params));
}
fetch({
url,
method: 'get',
data: params
}).then(res => {
if (res.code === '1') {
setDataList(res.data.rows);
setTotal(res.data.total);
} else {
setDataList([]);
setTotal(0);
notification.open({
message: "错误",
description: res.message,
});
}
setLoading(false);
});
}
function handleTableChange(pagination, filters, sorter) {
if (sorter.order) {
if (sorter.order === 'ascend') {
setOrderBy(sorter.field + 'Asc');
setCurPage(1);
} else {
setOrderBy(sorter.field + 'Desc');
setCurPage(1);
}
} else {
setOrderBy('');
setCurPage(1);
}
}
const columns = [
{
title: '成员姓名',
dataIndex: 'nickName',
key: 'nickName',
width: '20%',
className: 'link',
onCell,
render: (text, record) => {
return (text || record.userName)
}
},
{
title: '新增任务数',
key: 'newTask',
dataIndex: 'newTask',
sorter: true,
},
{
title: '完成任务数',
key: 'completedTask',
dataIndex: 'completedTask',
sorter: true,
},
{
title: '新增PR数',
key: 'newPrNum',
dataIndex: 'newPrNum',
sorter: true,
},
{
title: '新增提交数',
key: 'newCommitNum',
dataIndex: 'newCommitNum',
sorter: true,
},
// {
// title: '',
// key: 'newCodeNum',
// dataIndex: 'newCodeNum',
// },
];
function onCell(record) {
return {
onClick: event => { onCellClick(record) }, //
}
}
function onCellClick(record) {
let url = encodeURI(paramToUrl('/api/DeveloperData/getUrl', { login: record.userName }));
fetch({
url,
method: 'get',
}).then(res => {
if (res.code === '1') {
window.open(res.data);
} else {
notification.open({
message: "错误",
description: res.message,
});
}
});
}
if (member) {
columns.splice(0, 1, {
title: '日期',
dataIndex: 'date',
key: 'date',
width: '20%',
})
}
return (
<div className="table-detail">
<Table
loading={loading}
rowKey={(row, i) => row.id || i}
dataSource={dataList}
columns={columns}
pagination={false}
onChange={handleTableChange}
/>
{dataList.length > 0 &&
<Pagination
onChange={(page) => { setCurPage(page) }}
current={curPage}
total={total}
loading={loading}
/>}
</div>
)
}