diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json
index 1735f54..1d718a8 100644
--- a/api/.vscode/launch.json
+++ b/api/.vscode/launch.json
@@ -76,8 +76,8 @@
//
//
// 似乎不能传空 先注释 * 2
- // "--clickHouseUser ",
- // "--clickHousePassword ",
+ // "--clickHouseUser default",
+ // "--clickHousePassword Wbo@hi1I",
// 研发
// "--clickHouseAnxincloud anxinyun",
// "--clickHousePepEmis pepca",
diff --git a/api/app/lib/controllers/project/group.js b/api/app/lib/controllers/project/group.js
index c9f0fbb..9ade271 100644
--- a/api/app/lib/controllers/project/group.js
+++ b/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
};
\ No newline at end of file
diff --git a/api/app/lib/models/structure_off.js b/api/app/lib/models/structure_off.js
index 153f6a5..a923f4d 100644
--- a/api/app/lib/models/structure_off.js
+++ b/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,
diff --git a/api/app/lib/routes/project/index.js b/api/app/lib/routes/project/index.js
index 545d829..bb0ffa6 100644
--- a/api/app/lib/routes/project/index.js
+++ b/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);
};
\ No newline at end of file
diff --git a/script/3.1/schema/3.updata_struc_off .sql b/script/3.1/schema/3.updata_struc_off .sql
new file mode 100644
index 0000000..8f61563
--- /dev/null
+++ b/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 '离线个数';
+
diff --git a/web/client/assets/images/projectGroup/chart.png b/web/client/assets/images/projectGroup/chart.png
new file mode 100644
index 0000000..b575c3c
Binary files /dev/null and b/web/client/assets/images/projectGroup/chart.png differ
diff --git a/web/client/assets/images/projectGroup/first.png b/web/client/assets/images/projectGroup/first.png
new file mode 100644
index 0000000..9b8a315
Binary files /dev/null and b/web/client/assets/images/projectGroup/first.png differ
diff --git a/web/client/assets/images/projectGroup/second.png b/web/client/assets/images/projectGroup/second.png
new file mode 100644
index 0000000..6f8af3b
Binary files /dev/null and b/web/client/assets/images/projectGroup/second.png differ
diff --git a/web/client/assets/images/projectGroup/third.png b/web/client/assets/images/projectGroup/third.png
new file mode 100644
index 0000000..996d8fd
Binary files /dev/null and b/web/client/assets/images/projectGroup/third.png differ
diff --git a/web/client/src/layout/components/header/components/customProjGroupModal.jsx b/web/client/src/layout/components/header/components/customProjGroupModal.jsx
index 41afab4..7969055 100644
--- a/web/client/src/layout/components/header/components/customProjGroupModal.jsx
+++ b/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 }) => (
<>
-
+
{
pomsList.map((item, index) => {
return (
diff --git a/web/client/src/sections/projectGroup/actions/group.js b/web/client/src/sections/projectGroup/actions/group.js
index f60a956..72e47b5 100644
--- a/web/client/src/sections/projectGroup/actions/group.js
+++ b/web/client/src/sections/projectGroup/actions/group.js
@@ -55,4 +55,32 @@ export function groupStatisticOnline (query = {}) {
msg: { error: "获取项目分组在线率统计信息失败" },
reducer: { name: "groupStatisticOnline", params: { noClear: true } },
});
-}
\ No newline at end of file
+}
+
+
+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 } },
+ });
+}
+
diff --git a/web/client/src/sections/projectGroup/containers/bigscreen.jsx b/web/client/src/sections/projectGroup/containers/bigscreen.jsx
index 9eea52a..b405e56 100644
--- a/web/client/src/sections/projectGroup/containers/bigscreen.jsx
+++ b/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) {
+ //数据大于20的话,取前20
+ 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 (
@@ -27,72 +190,167 @@ const Bigscreen = ({ dispatch, actions, user, match, history, clientHeight, grou
-
+
+
+
{
+ setFormatter(values?.data)
+ }
+ // `${values.seriesName}
${values.marker} ${values.name} ${values.value}个(${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 }}
+ />
+
+
+
{formatter?.value}
+
{formatter?.name}
+
+
+
+ {proportion?.map((v, index) => {
+ let color = ['rgb(53 100 209)', 'rgb(138 201 15)', 'rgb(239 204 77)', 'rgb(233 107 107)']
+ return
+
+
+ {v.name}
+
{v.value}次
+
+
+ })}
+
-
-
-
+
+
+
+ {groupProject?.map((c, index) => {
+ return index < 10 ?
+
+ NO.{index + 1}
+
+ {c.name || c.pepProjectName}(售后工单)
+
+ {c.value}h
+
: <>>
+ })}
+
-
-
+
+ {/*
*/}
+
*/}
v.name) || [],
- right: '10%',
- textStyle: {
- color: '#FFF',
- },
+ trigger: 'axis',
+ formatter: function (params) {
+ // 自定义提示框内容
+ // console.log(params);
+ let title = params[0].data[0] + '
' + '
'
+ 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 + ":" + " " + " " + v.data[1] + "%" + "(" + find?.online + "/" + find?.total + ")" + '
'
+ })
+ 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'}
/>
+
-
-
+
+ {mockData && mockData.length > 0 ? (
+
+
+ {mockData && mockData[0] ? (
+
+
+
+
{mockData[0]?.name?.length > 5 ? {mockData[0]?.name.substring(0, 5) + '...'} : mockData[0]?.name}
+
+
+
0? ((mockData[0].dealAlarmCount / biggest) * 100 + '%'):0), height: '100%' }}>
+
+
+
0? ((mockData[0].alarmCount / biggest) * 100 + '%'):0), height: '100%' }}>
+
+
+
) : ''
+ }
+ {mockData && mockData[1] ? (
+
+
+
+
{mockData[1]?.name?.length > 5 ? {mockData[1]?.name.substring(0, 5) + '...'} : mockData[0]?.name}
+
+
+
0? ((mockData[1].dealAlarmCount / biggest) * 100 + '%'):0), height: '100%' }}>
+
+
+
0? ((mockData[1].alarmCount / biggest) * 100 + '%'):0), height: '100%' }}>
+
+
+
) : ''
+ }
+ {mockData && mockData[2] ? (
+
+
+
+
{mockData[2]?.name?.length > 5 ? {mockData[2]?.name.substring(0, 5) + '...'} : mockData[0]?.name}
+
+
+
0? ((mockData[2].dealAlarmCount / biggest) * 100 + '%'):0), height: '100%' }}>
+
+
+
0? ((mockData[2].alarmCount / biggest) * 100 + '%'):0), height: '100%' }}>
+
+
+
) : ''
+ }
+ {alarmData && alarmData.length ?
+ alarmData.map((item, index) => {
+ return (
+
+ {index + 4}
+
+
{item.name?.length > 5 ? {item.name.substring(0, 5) + '...'} : item.name}
+
+
+
0?((item.dealAlarmCount / biggest) * 100 + '%'):0), height: '100%' }}>
+
+
+
0? ((item.alarmCount / biggest) * 100 + '%'):0), height: '100%' }}>
+
+
-
+
)
+ }
+ )
+ : ''
+ }
+
+
+ {xData?.map(item => {
+ return
{item}
+ })}
+
+ ) : ''}
-
-
+
+
+
+ {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
+
{c.name}
+
{title}
+
{c.offnum + '/' + c.totnum}
+
+ })}
+
-
+
-