CODE 1 year ago
parent
commit
d13f8669fa
  1. 4
      api/.vscode/launch.json
  2. 62
      api/app/lib/controllers/project/group.js
  3. 18
      api/app/lib/models/structure_off.js
  4. 3
      api/app/lib/routes/project/index.js
  5. 11
      script/3.1/schema/3.updata_struc_off .sql
  6. BIN
      web/client/assets/images/projectGroup/chart.png
  7. BIN
      web/client/assets/images/projectGroup/first.png
  8. BIN
      web/client/assets/images/projectGroup/second.png
  9. BIN
      web/client/assets/images/projectGroup/third.png
  10. 4
      web/client/src/layout/components/header/components/customProjGroupModal.jsx
  11. 30
      web/client/src/sections/projectGroup/actions/group.js
  12. 460
      web/client/src/sections/projectGroup/containers/bigscreen.jsx
  13. 108
      web/client/src/sections/projectGroup/style.less
  14. 2
      web/client/src/utils/webapi.js

4
api/.vscode/launch.json

@ -76,8 +76,8 @@
//
//
// * 2
// "--clickHouseUser ",
// "--clickHousePassword ",
// "--clickHouseUser default",
// "--clickHousePassword Wbo@hi1I",
//
// "--clickHouseAnxincloud anxinyun",
// "--clickHousePepEmis pepca",

62
api/app/lib/controllers/project/group.js

@ -116,7 +116,7 @@ async function groupStatistic (ctx) {
}
})
// 获取全部的 poms 项目id
// 获取全部的 poms 项目id 并构建关系
let pomsProjectIds = new Set()
for (let group of progectGroupList) {
for (let projectId of group.pomsProjectIds) {
@ -255,6 +255,9 @@ async function groupStatisticOnline (ctx) {
// 查在线率
const strucOnlineClient = ctx.app.fs.esclient.strucOnline
console.log('es参数,', strucOnlineClient.config)
console.log('strucIdArr', strucIdArr)
const onlineRes = await strucOnlineClient.search({
index: strucOnlineClient.config.index,
type: strucOnlineClient.config.type,
@ -285,9 +288,11 @@ async function groupStatisticOnline (ctx) {
"order": "asc"
}
}
]
],
"size": 10000
}
})
console.log('es数据,', onlineRes.hits.hits.length, onlineRes.hits.hits)
for (let struc of strucRes) {
let curOnline =
@ -360,7 +365,7 @@ async function groupStatisticAlarm (ctx) {
).toPromise() : []
// 查一周内超阈值告警的个数
strucIdArr = [1]
// strucIdArr = [1]
const alarmRes = strucIdArr.length ? await clickHouse.dataAlarm.query(
`
SELECT StructureId,count(StructureId) AS alarmCount
@ -409,6 +414,56 @@ async function groupStatisticAlarm (ctx) {
}
}
async function groupProject (ctx) {
try {
const { models } = ctx.fs.dc;
const { groupId } = ctx.query
const { clickHouse } = ctx.app.fs
const findOne = await models.ProjectGroup.findOne({ where: { id: groupId } })
const proRes = await models.ProjectCorrelation.findAll({ where: { id: { $in: findOne.pomsProjectIds } } })
let pepProjectIds = new Set()
for (let p of proRes) {
if (p.pepProjectId) {
pepProjectIds.add(p.pepProjectId)
}
}
const pepProjectRes = pepProjectIds.size ?
await clickHouse.projectManage.query(
`
SELECT
t_pim_project.id AS id,
t_pim_project.project_name AS project_name,
t_pim_project.isdelete AS isdelete,
t_pim_project_construction.construction_status_id AS construction_status_id,
t_pim_project_state.construction_status AS construction_status
FROM t_pim_project
LEFT JOIN t_pim_project_construction
ON t_pim_project.id = t_pim_project_construction.project_id
LEFT JOIN t_pim_project_state
ON t_pim_project_construction.construction_status_id = t_pim_project_state.id
WHERE id IN (${[...pepProjectIds].join(',')}, -1)
`
).toPromise() :
[]
for (let p of proRes) {
const corPro = pepProjectRes.find(pp => pp.id == p.pepProjectId) || {}
p.dataValues.pepProjectName = corPro.project_name
}
ctx.status = 200;
ctx.body = proRes;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
module.exports = {
groupList,
editGroup,
@ -416,4 +471,5 @@ module.exports = {
groupStatistic,
groupStatisticOnline,
groupStatisticAlarm,
groupProject
};

18
api/app/lib/models/structure_off.js

@ -34,6 +34,24 @@ module.exports = dc => {
field: "offline",
autoIncrement: false
},
totnum: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "测点总数",
primaryKey: false,
field: "totnum",
autoIncrement: false
},
offnum: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "离线个数",
primaryKey: false,
field: "offnum",
autoIncrement: false
},
offtime: {
type: DataTypes.STRING,
allowNull: false,

3
api/app/lib/routes/project/index.js

@ -48,4 +48,7 @@ module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/project/group/statistic/alarm'] = { content: '获取项目分组告警统计信息', visible: true };
router.get('/project/group/statistic/alarm', projectGroup.groupStatisticAlarm);
app.fs.api.logAttr['GET/project/group/list'] = { content: '获取分组项目信息', visible: true };
router.get('/project/group/list', projectGroup.groupProject);
};

11
script/3.1/schema/3.updata_struc_off .sql

@ -0,0 +1,11 @@
alter table t_structure_off
add totnum integer;
comment on column t_structure_off.totnum is '测点总数';
alter table t_structure_off
add offnum integer;
comment on column t_structure_off.offnum is '离线个数';

BIN
web/client/assets/images/projectGroup/chart.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
web/client/assets/images/projectGroup/first.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

BIN
web/client/assets/images/projectGroup/second.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
web/client/assets/images/projectGroup/third.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

4
web/client/src/layout/components/header/components/customProjGroupModal.jsx

@ -27,7 +27,7 @@ const CustomProjGroupModal = (props) => {
if (editData) {
stoData.id = editData.id
}
dispatch(actions.projectGroupAC.editProjectGroup(stoData)).then((res) => {
dispatch(actions.projectGroup.editProjectGroup(stoData)).then((res) => {
if (res.success) {
cancel({ refresh: true })
form.current?.reset()
@ -53,7 +53,7 @@ const CustomProjGroupModal = (props) => {
{({ formState, values, formApi }) => (
<>
<Form.Input field='name' label='名称' rules={[{ required: true, message: '请填写自定义分组名称' }]} maxLength={15} />
<Form.Select multiple field="pomsProjectIds" label={{ text: '运维项目' }} style={{ width: '100%' }} rules={[{ required: true, message: '请选择运维项目' }]} >
<Form.Select filter multiple field="pomsProjectIds" label={{ text: '运维项目' }} style={{ width: '100%' }} rules={[{ required: true, message: '请选择运维项目' }]} >
{
pomsList.map((item, index) => {
return (

30
web/client/src/sections/projectGroup/actions/group.js

@ -55,4 +55,32 @@ export function groupStatisticOnline (query = {}) {
msg: { error: "获取项目分组在线率统计信息失败" },
reducer: { name: "groupStatisticOnline", params: { noClear: true } },
});
}
}
export function groupStatisticAlarm (query = {}) {
return (dispatch) => basicAction({
type: "get",
dispatch: dispatch,
query,
actionType: "GET_STATISTICALARM",
url: `${ApiTable.groupStatisticAlarm}`,
msg: { error: "获取项目分组告警统计信息失败" },
reducer: { name: "groupStatisticAlarm",
params: { noClear: true } },
});
}
export function groupProject (query = {}) {
return (dispatch) => basicAction({
type: "get",
dispatch: dispatch,
query,
actionType: "GET_GROUP_PROJECT",
url: `${ApiTable.groupProject}`,
msg: { error: "获取分组项目信息失败" },
reducer: { name: "groupProject",
params: { noClear: true } },
});
}

460
web/client/src/sections/projectGroup/containers/bigscreen.jsx

@ -1,4 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import { Skeleton, Button, Pagination, Select, Popconfirm, Table, Tooltip } from '@douyinfe/semi-ui';
import { connect } from 'react-redux';
import Header from '../components/header';
import Body from '../components/body'
@ -6,18 +7,180 @@ import Card from '../components/card'
import '../style.less'
import ReactECharts from 'echarts-for-react';
import moment from 'moment'
import PerfectScrollbar from "perfect-scrollbar";
let interrupt
let repair
let overviewScrollbar;
const Bigscreen = ({ dispatch, actions, user, match, history, clientHeight, groupStatisticOnline }) => {
const [InterruptRank, setInterruptRank] = useState([])
const [online, setOnline] = useState([])
const [value, setValue] = useState([])
const [time, setTime] = useState([])
const [groupProject, setGroupProject] = useState([])
const [proportion, setProportion] = useState([])
const [formatter, setFormatter] = useState({})
const [alarmData, setAlarmData] = useState()//
const [biggest, setBiggest] = useState()//
const [mockData, setMockData] = useState()//
const [xData, setXData] = useState([])//
const self = useRef({ myChart: null });
useEffect(() => {
let groupId = JSON.parse(localStorage.getItem('project_group'))?.find(v => user?.id == v.userId)?.projectGroupId
console.log();
dispatch(actions.projectGroup.groupStatisticOnline({ groupId }))
statisticOnline(groupId)
//
timeRequest(groupId)
dispatch(actions.projectGroup.groupStatisticAlarm({ groupId })).then(res => {
if (res.success) {
setMockData(res.payload.data)
}
})
dispatch(actions.projectGroup.groupProject({ groupId })).then(res => {
if (res.success) {
setGroupProject(res.payload.data?.map(v => ({ ...v, value: (Math.random() * 20).toFixed(0) })) || [])
setProportion([...res.payload.data?.slice(0, 3)?.map(v => ({ name: v.name || v.pepProjectName, value: (Math.random() * 20).toFixed(0) })), { value: 20, name: '其它' }])
}
})
const interruptDom = document.getElementById("interrupt");
if (interruptDom) {
interrupt = new PerfectScrollbar("#interrupt", {
suppressScrollX: true,
});
}
const repairDom = document.getElementById("repair");
if (repairDom) {
repair = new PerfectScrollbar("#repair", {
suppressScrollX: true,
});
}
}, [])
console.log(groupStatisticOnline);
useEffect(() => {
const overview = document.getElementById("alarmRank");
if (overview) {
overviewScrollbar = new PerfectScrollbar("#alarmRank", {
suppressScrollX: true,
});
}
if (overviewScrollbar && overview) {
overviewScrollbar.update();
}
const interruptDom = document.getElementById("interrupt");
if (interrupt && interruptDom) {
interrupt.update();
}
const repairDom = document.getElementById("repair");
if (repair && repairDom) {
interrupt.update();
}
})
useEffect(() => {
const maxCombinedValue = mockData?.reduce((max, item) => {
const combinedMax = Math.max(item.alarmCount, item.dealAlarmCount);
if (combinedMax > max) {
return combinedMax;
}
return max;
}, -Infinity)
const bigD = Math.ceil(maxCombinedValue / 50) * 50
if (bigD == 0) {
setXData([5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5])//0
} else {
setXData([bigD, (bigD - bigD / 5), (bigD - bigD * 2 / 5), (bigD - bigD * 3 / 5), (bigD - bigD * 4 / 5), 0, (bigD - bigD * 4 / 5), (bigD - bigD * 3 / 5), (bigD - bigD * 2 / 5), (bigD - bigD / 5), bigD])
}
setBiggest(bigD)
if (mockData && mockData.length > 3 && mockData.length < 21) {
const newArray = mockData.slice(3)
setAlarmData(newArray)
}
if (mockData && mockData.length > 21) {
//2020
const newArray = mockData.slice(3, 20)
setAlarmData(newArray)
}
}, [mockData])
let statisticOnline = (groupId) => {
dispatch(actions.projectGroup.groupStatisticOnline({ groupId })).then(res => {
if (res.success) {
let Interrupt = []
res.payload.data?.forEach(v => {
if (v.offline?.id) {
Interrupt.push({ name: v.name, ...v.offline })
}
})
Interrupt = Interrupt?.sort((a, b) => b.offline - a.offline)
setInterruptRank(Interrupt)
setOnline(res.payload.data?.slice(0, 10) || [])
setValue(res.payload.data?.map(v => v.id)?.slice(0, 10) || [])
}
})
}
const timeRequest = (groupId) => {
//
const currentTime = moment();
//
const nextHour = moment().add(1, 'hour').startOf('hour');
//
const minuteDifference = nextHour.diff(currentTime, 'minutes');
setTimeout(function () {
statisticOnline(groupId)
timeRequest(groupId)
}, 1000 * 60 * (minuteDifference + 1 || 61));
}
useEffect(() => {
let count = 0;
let currentIndex = -1;
if (!self.current.cityChart) return;
const timer = setInterval(() => {
count++;
if (count == 8) {
count = 1;
}
//
self.current.cityChart.dispatchAction({
type: "downplay",
seriesIndex: 0,
dataIndex: currentIndex,
});
currentIndex =
(currentIndex + 1) % proportion?.length
//
self.current.cityChart.dispatchAction({
type: "highlight",
seriesIndex: 0,
dataIndex: currentIndex,
});
// label
self.current.cityChart.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex: currentIndex,
});
}, 3000);
return () => {
clearInterval(timer);
};
}, [proportion]);
return (
<div className='project-group'>
@ -27,72 +190,167 @@ const Bigscreen = ({ dispatch, actions, user, match, history, clientHeight, grou
<div style={{ width: '100%', height: "45%", display: 'flex' }}>
<div style={{ width: "calc(50% - 8px)", height: "100%", marginRight: 16, display: 'flex' }}>
<Card title='项目工单占比' style={{ width: "calc(50% - 8px)", height: "100%", marginRight: 16 }}>
<div style={{ height: '100%' }}>
<div style={{ height: '100%', position: 'relative' }}>
<div style={{ height: clientHeight * 0.55 - 300, display: 'flex', justifyContent: "center", position: 'relative' }}>
<ReactECharts
option={{
tooltip: {
show: true,
trigger: "item",
position: "right",
backgroundColor: "rgba(0,0,0,0.7)",
textStyle: {
color: "#fff",
},
formatter: (values) => {
setFormatter(values?.data)
}
// `${values.seriesName}<br /> ${values.marker} ${values.name} <b>${values.value}</b>(${values.percent}%)`,
},
series: [
{
// name: 'Access From',
type: 'pie',
radius: ['60%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
// emphasis: {
// label: {
// show: true,
// fontSize: 44,
// fontWeight: 'bold'
// }
// },
labelLine: {
show: false
},
data: proportion || [],
}
]
}}
notMerge
onChartReady={(instance) => {
self.current.cityChart = instance;
}}
lazyUpdate
style={{ width: clientHeight * 0.55 - 300, height: clientHeight * 0.55 - 300 }}
/>
<img src='/assets/images/projectGroup/chart.png' style={{ height: clientHeight * 0.55 - 330, position: 'absolute', top: 16 }} />
<div style={{ position: 'absolute', top: 58, left: "auto", width: 100, textAlign: "center" }}>
<div style={{ fontFamily: 'DIN-Regular', fontWeight: 400, fontSize: 28, color: '#2F2F2F' }}>{formatter?.value}</div>
<div style={{
width: 100, fontFamily: 'SourceHanSansCN-Regular', fontWeight: 400,
fontSize: 14, color: '#2C66F3', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis'
}}>{formatter?.name}</div>
</div>
</div>
<div style={{ width: "100%", fontFamily: 'SourceHanSansCN-Regular', fontWeight: 400, fontSize: 14, position: 'absolute', bottom: 10 }}>
{proportion?.map((v, index) => {
let color = ['rgb(53 100 209)', 'rgb(138 201 15)', 'rgb(239 204 77)', 'rgb(233 107 107)']
return <div style={{ width: "50%", display: 'inline-block' }}>
<div style={{ display: "flex", justifyContent: "space-between", padding: '0 6px' }}>
<div title={v.name} style={{ width: 'calc(100% - 40px)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<span style={{ background: color[index], display: 'inline-block', marginRight: 6, width: 6, height: 6 }}></span>{v.name}</div>
<div style={{ width: 34 }}>{v.value}</div>
</div>
</div>
})}
</div>
</div>
</Card>
<Card title='修复排名' style={{ width: "calc(50% - 8px)", height: "100%" }}>
<div style={{ height: '100%' }}>
<div style={{ height: '100%', fontFamily: 'SourceHanSansCN-Regular', fontWeight: 400, fontSize: 14, }}>
<div style={{ display: "flex", background: '#F6F9FF', height: 40, alignItems: 'center' }}>
<div style={{ textAlign: 'center', width: '25%' }}>序号</div>
<div style={{ textAlign: 'center', width: '49%' }}>工单名称</div>
<div style={{ textAlign: 'center', width: '25%' }}>修复时长</div>
</div>
<div id="repair" style={{ position: 'relative', height: clientHeight * 0.55 - 220 }}>
{groupProject?.map((c, index) => {
return index < 10 ? <div style={{ display: "flex", background: index % 2 == 1 ? "#F6F9FF" : '', padding: 6, height: 50, alignItems: 'center' }}>
<div style={{ textAlign: 'center', width: '25%', fontFamily: 'SourceHanSansCN-Regular', color: '#2C66F3', fontWeight: 400 }}>
NO.{index + 1}</div>
<div title={c.name || c.pepProjectName + "(售后工单)"} style={{ textAlign: 'center', padding: '0 6px', width: '49%', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{c.name || c.pepProjectName}售后工单</div>
<div style={{ textAlign: 'center', width: '25%', fontFamily: 'SourceHanSansCN-Regular', fontWeight: 400 }}>
{c.value}h</div>
</div> : <></>
})}
</div>
</div>
</Card>
</div>
<Card title='数据在线率' style={{ width: "calc(50% - 8px)", height: "100%", }}>
<div style={{ height: '100%' }}>
<div style={{ height: '100%', position: 'relative' }}>
{/* <div > */}
<Select
showClear
filter
value={value}
multiple={true}
maxTagCount={1}
style={{ width: 300, position: 'absolute', top: 0, right: 0, zIndex: 99 }}
optionList={groupStatisticOnline?.map(v => ({ value: v.id, label: v.name })) || []}
onChange={v => {
setValue(v)
setOnline(groupStatisticOnline?.filter(s => v.includes(s.id)))
}}
/>
{/* </div> */}
<ReactECharts
option={{
title: {
// text: v.name,
},
grid: {
// width: 300,
// height: 200
left: 27,
right: 10,
bottom: 20,
},
// dataZoom: [
// {
// type: 'slider',
// showDetail: false
// },
// {
// type: 'inside',
// },
// ],
tooltip: {
trigger: 'axis'
},
legend: {
data: groupStatisticOnline?.map(v => v.name) || [],
right: '10%',
textStyle: {
color: '#FFF',
},
trigger: 'axis',
formatter: function (params) {
//
// console.log(params);
let title = params[0].data[0] + '<br/>' + '<br/>'
params.forEach(v => {
let find = online?.find(s => s.name == v.seriesName)?.online?.find(d => moment(d.collect_time).format('YYYY-MM-DD HH') == v.data[0]) || {}
title = title + v.seriesName + ":" + "&nbsp" + "&nbsp" + v.data[1] + "%" + "(" + find?.online + "/" + find?.total + ")" + '<br/>'
})
return title
}
},
xAxis: {
type: 'time',
// name: "",
boundaryGap: false,
minInterval: 1000 * 60,
minInterval: 1000 * 60 * 60,
},
yAxis: {
type: 'value',
name: "单位:A",
name: "单位%",
areaStyle: {
color: '#FFF',
},
},
series: groupStatisticOnline?.map(v => ({
series: online?.map(v => ({
type: 'line',
name: v.name,
smooth: true,
areaStyle: {
color: '#0e9cff26',
},
data: v.online?.map(f => [moment(f.collect_time).format('YYYY-MM-DD HH'), f.rate]) || []
data: v.online?.map(f => [moment(f.collect_time).format('YYYY-MM-DD HH'), f.rate.toFixed(1)]) || []
})) || []
}}
notMerge={true}
@ -100,28 +358,142 @@ const Bigscreen = ({ dispatch, actions, user, match, history, clientHeight, grou
style={{ width: "100%", height: "100%" }}
theme={'ReactEChart'}
/>
</div>
</Card>
</div>
<div style={{ width: '100%', height: "calc(55% - 24px)", display: 'flex', marginTop: 24 }}>
<Card title='告警排名TOP20' style={{ width: "calc(50% - 8px)", height: "100%", marginRight: 16, }}>
<div style={{ height: '100%' }}>
<Card title='告警排名TOP20' style={{
width: "calc(50% - 8px)", height: "100%", marginRight: 16
}} >
{mockData && mockData.length > 0 ? (<div style={{ height: '100%' }}>
<div style={{ display: "flex", justifyContent: 'flex-end' }}>
<div style={{ display: "flex", alignItems: 'center' }}>
<div class='alarmDiv'></div><div class='alarm'>超阈值个数</div>
</div>
<div style={{ display: "flex", alignItems: 'center' }}>
<div class='dealAlarmCountDiv'></div><div class='alarmCount'>手动恢复个数</div>
</div>
</div>
<div id='alarmRank' style={{ height: clientHeight * 0.55 - 150, position: 'relative' }}>
{mockData && mockData[0] ? (<div style={{ display: 'flex', marginTop: 15, alignItems: 'center' }}>
<div class='rankDiv'>
<img src='/assets/images/projectGroup/first.png'></img>
</div>
<div class='structDiv'>{mockData[0]?.name?.length > 5 ? <Tooltip content={mockData[0]?.name}>{mockData[0]?.name.substring(0, 5) + '...'}</Tooltip> : mockData[0]?.name}</div>
<div class='barChartDiv'>
<div style={{ width: '50%', display: 'flex', justifyContent: 'flex-end' }}>
<div class='alarms' style={{ width:(biggest>0? ((mockData[0].dealAlarmCount / biggest) * 100 + '%'):0), height: '100%' }}></div>
</div>
<div style={{ width: '50%', display: 'flex', }}>
<div class='dealAlarms' style={{ width: (biggest>0? ((mockData[0].alarmCount / biggest) * 100 + '%'):0), height: '100%' }}></div>
</div>
</div>
</div>) : ''
}
{mockData && mockData[1] ? (<div style={{ display: 'flex', marginTop: 5, alignItems: 'center' }}>
<div class='rankDiv'>
<img src='/assets/images/projectGroup/second.png'></img>
</div>
<div class='structDiv'>{mockData[1]?.name?.length > 5 ? <Tooltip content={mockData[1]?.name}>{mockData[1]?.name.substring(0, 5) + '...'}</Tooltip> : mockData[0]?.name}</div>
<div class='barChartDiv'>
<div style={{ width: '50%', display: 'flex', justifyContent: 'flex-end' }}>
<div class='alarms' style={{ width: (biggest>0? ((mockData[1].dealAlarmCount / biggest) * 100 + '%'):0), height: '100%' }}></div>
</div>
<div style={{ width: '50%', display: 'flex', }}>
<div class='dealAlarms' style={{ width: (biggest>0? ((mockData[1].alarmCount / biggest) * 100 + '%'):0), height: '100%' }}></div>
</div>
</div>
</div>) : ''
}
{mockData && mockData[2] ? (<div style={{ display: 'flex', marginTop: 5, alignItems: 'center' }}>
<div class='rankDiv'>
<img src='/assets/images/projectGroup/third.png'></img>
</div>
<div class='structDiv'>{mockData[2]?.name?.length > 5 ? <Tooltip content={mockData[2]?.name}>{mockData[2]?.name.substring(0, 5) + '...'}</Tooltip> : mockData[0]?.name}</div>
<div class='barChartDiv'>
<div style={{ width: '50%', display: 'flex', justifyContent: 'flex-end' }}>
<div class='alarms' style={{ width: (biggest>0? ((mockData[2].dealAlarmCount / biggest) * 100 + '%'):0), height: '100%' }}></div>
</div>
<div style={{ width: '50%', display: 'flex', }}>
<div class='dealAlarms' style={{ width:(biggest>0? ((mockData[2].alarmCount / biggest) * 100 + '%'):0), height: '100%' }}></div>
</div>
</div>
</div>) : ''
}
{alarmData && alarmData.length ?
alarmData.map((item, index) => {
return (<div style={{ display: 'flex', marginTop: 5, alignItems: 'center' }}>
<div class='rankDiv'>
<span>{index + 4}</span>
</div>
<div class='structDiv'>{item.name?.length > 5 ? <Tooltip content={item.name}>{item.name.substring(0, 5) + '...'}</Tooltip> : item.name}</div>
<div class='barChartDiv'>
<div style={{ width: '50%', display: 'flex', justifyContent: 'flex-end' }}>
<div class='alarms' style={{ width:(biggest>0?((item.dealAlarmCount / biggest) * 100 + '%'):0), height: '100%' }}></div>
</div>
<div style={{ width: '50%', display: 'flex', }}>
<div class='dealAlarms' style={{ width:(biggest>0? ((item.alarmCount / biggest) * 100 + '%'):0), height: '100%' }}></div>
</div>
</div>
</div>
</div>)
}
)
: ''
}
</div>
<div class="scale">
{xData?.map(item => {
return <div >{item}</div>
})}
</div>
</div>) : ''}
</Card>
<Card title='中断排名' style={{ width: "calc(50% - 8px)", height: "100%", }}>
<div style={{ height: '100%' }}>
<div style={{ height: '100%', fontFamily: 'SourceHanSansCN-Regular', fontWeight: 400, fontSize: 14, }}>
<div style={{ display: "flex", background: '#F6F9FF', height: 40, alignItems: 'center' }}>
<div style={{ textAlign: 'center', width: '33%' }}>结构物</div>
<div style={{ textAlign: 'center', width: '33%' }}>中断时长</div>
<div style={{ textAlign: 'center', width: '33%' }}>中断个数</div>
</div>
<div id="interrupt" style={{ position: 'relative', height: clientHeight * 0.55 - 170 }}>
{InterruptRank?.map((c, index) => {
let title
if (c.offline) {
if (c.offline >= 1440 && Math.floor(c.offline / 1440)) title = Math.floor(c.offline / 1440) + "天"
if ((c.offline % 1440) >= 60 && Math.floor(c.offline % 1440 / 60)) {
if (title) {
title = title + Math.floor(c.offline % 1440 / 60) + "时"
} else {
title = Math.floor(c.offline % 1440 / 60) + "时"
}
}
if (c.offline % 1440 % 60) {
if (title) {
title = title + c.offline % 1440 % 60 + "分"
} else {
title = c.offline % 1440 % 60 + "分"
}
}
}
return <div style={{ display: "flex", background: index % 2 == 1 ? "#F6F9FF" : '', height: 40, alignItems: 'center' }}>
<div style={{ textAlign: 'center', width: '33%', fontFamily: 'SourceHanSansCN-Regular', color: '#2C66F3', fontWeight: 400 }}>{c.name}</div>
<div style={{ textAlign: 'center', width: '33%' }}>{title}</div>
<div style={{ textAlign: 'center', width: '33%', fontFamily: 'SourceHanSansCN-Regular', color: '#F33B3B', fontWeight: 400 }}>{c.offnum + '/' + c.totnum}</div>
</div>
})}
</div>
</div>
</Card>
</div>
</div>
</div >
</Body>
</div>
</Body >
</div >
)
}
@ -135,4 +507,4 @@ function mapStateToProps (state) {
};
}
export default connect(mapStateToProps)(Bigscreen);
export default connect(mapStateToProps)(Bigscreen);

108
web/client/src/sections/projectGroup/style.less

@ -1,3 +1,109 @@
.project-group {
font-family: PangMenZhengDaoBiaoTiTi;
}
}
.alarmDiv{
width: 8px;
height: 8px;
background-image: linear-gradient(180deg, #fbac3200 1%, #FBAC32 100%);
box-shadow: inset 0 2px 0 0 #FBAC32;
}
.alarm{
width: 73px;
height: 12px;
font-family: SourceHanSansCN-Regular;
font-weight: 400;
font-size: 12px;
color: #A0A0A0;
letter-spacing: 0;
line-height: 12px;
// margin-left: 2!important;
}
.dealAlarmCountDiv{
width: 8px;
height: 8px;
background-image: linear-gradient(180deg, #c2d2ff00 1%, #2A62FC 100%);
box-shadow: inset 0 2px 0 0 #2C66F3;
}
.alarmCount{
width: 73px;
height: 12px;
font-family: SourceHanSansCN-Regular;
font-weight: 400;
font-size: 12px;
color: #A0A0A0;
letter-spacing: 0;
text-align: right;
line-height: 12px;
// margin-left: 2 !important;
}
//柱状图外层的div
.barChartDiv{
width: 740px;
height: 12px;
background: #F0F5FF;
border-radius: 2px;
display: flex;
}
.structDiv{
width: 100px;
height: 14px;
font-family: SourceHanSansCN-Regular;
font-weight: 400;
font-size: 14px;
color: #3E434E;
letter-spacing: 0;
line-height: 14px;
text-align: center;
}
.rankDiv{
width: 34.48px;
height: 17.67px;
text-align: center;
span{
width: 13px;
height: 16px;
font-family: D-DIN-Italic;
font-weight: Italic;
font-size: 16px;
color: #7C8DB6;
letter-spacing: 0;
}
}
.rankBackDiv{
// width: 34.48px;
// height: 9px;
// background-image: linear-gradient(270deg, #f957571f 0%, #f32c2c57 93%);
// margin-bottom: 0;
}
.alarms{
width: 282px;
height: 10px;
background-image: linear-gradient(116deg, #2c66f329 8%, #2C66F3 100%);
box-shadow: inset 2px 0 0 0 #2C66F3;
}
.dealAlarms{
width: 108px;
height: 10px;
background-image: linear-gradient(270deg, #fbac3229 1%, #FBAC32 100%);
box-shadow: inset -2px 0 0 0 #FBAC32;
}
.scale{
display: flex;
width: 740px;
height: 18px;
font-family: DIN-Regular;
font-weight: 400;
font-size: 12px;
color: #5A6685;
letter-spacing: 0;
text-align: center;
justify-content: space-between;
margin-left: 134.48px;
}

2
web/client/src/utils/webapi.js

@ -40,6 +40,8 @@ export const ApiTable = {
projectGroup: 'project/group',
groupStatistic: 'project/group/statistic',
groupStatisticOnline: 'project/group/statistic/online',
groupStatisticAlarm: 'project/group/statistic/alarm',
groupProject: "project/group/list",
//告警
getProjectPoms: 'project/poms', //获取已绑定项目

Loading…
Cancel
Save