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
+
: <> + })} +
-
- +
+ {/*
*/} +