diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json index 47f342e..10dd5d3 100644 --- a/api/.vscode/launch.json +++ b/api/.vscode/launch.json @@ -56,8 +56,8 @@ // "--qndmn http://resources.anxinyun.cn", // "--qndmn http://rhvqdivo5.hn-bkt.clouddn.com", // click 开发 - // "--clickHouseUrl http://10.8.30.95", - // "--clickHousePort 30123", + "--clickHouseUrl http://10.8.30.95", + "--clickHousePort 30123", // click 测试 // "--clickHouseUrl http://10.8.30.161", // "--clickHousePort 30123", @@ -71,20 +71,20 @@ // // // click 商用 - "--clickHouseUrl http://218.3.126.49", - "--clickHousePort 18123", - "--clickHouseUser default", - "--clickHousePassword Wbo@hi1I", - "--clickHouseAnxincloud anxinyun", - "--clickHousePepEmis pepca", - "--clickHouseProjectManage peppm", - "--clickHouseVcmp video_access", - "--clickHouseDataAlarm alarm", - "--clickHouseIot iota", - "--clickHouseCamworkflow camundaworkflow", - "--confirmAlarmAnxinUserId 1", - "--vcmpAppId 5048b08d-c449-4d7f-b1ec-f741012aefe8", - "--vcmpAppSecret 5ba8c0ab-9fbd-4f07-9817-c48017c3cbad", + // "--clickHouseUrl http://218.3.126.49", + // "--clickHousePort 18123", + // "--clickHouseUser default", + // "--clickHousePassword Wbo@hi1I", + // "--clickHouseAnxincloud anxinyun", + // "--clickHousePepEmis pepca", + // "--clickHouseProjectManage peppm", + // "--clickHouseVcmp video_access", + // "--clickHouseDataAlarm alarm", + // "--clickHouseIot iota", + // "--clickHouseCamworkflow camundaworkflow", + // "--confirmAlarmAnxinUserId 1", + // "--vcmpAppId 5048b08d-c449-4d7f-b1ec-f741012aefe8", + // "--vcmpAppSecret 5ba8c0ab-9fbd-4f07-9817-c48017c3cbad", // // // 似乎不能传空 先注释 * 2 @@ -98,17 +98,17 @@ // "--clickHouseDataAlarm default", // "--clickHouseIot iot", // 测试 - // "--clickHouseAnxincloud anxinyun", - // "--clickHousePepEmis pepca", - // "--clickHouseProjectManage peppm", - // "--clickHouseVcmp video_access_dev", - // "--clickHouseDataAlarm default", - // "--clickHouseDataAlarmLocal default", - // "--clickHouseIot iota", - // "--clickHouseCamworkflow camworkflow", - // "--confirmAlarmAnxinUserId 1", - // "--vcmpAppId 5048b08d-c449-4d7f-b1ec-f741012aefe8", - // "--vcmpAppSecret 5ba8c0ab-9fbd-4f07-9817-c48017c3cbad", + "--clickHouseAnxincloud anxinyun", + "--clickHousePepEmis pepca", + "--clickHouseProjectManage peppm", + "--clickHouseVcmp video_access_dev", + "--clickHouseDataAlarm alarm", + "--clickHouseDataAlarmLocal default", + "--clickHouseIot iota", + "--clickHouseCamworkflow camworkflow", + "--confirmAlarmAnxinUserId 1", + "--vcmpAppId 5048b08d-c449-4d7f-b1ec-f741012aefe8", + "--vcmpAppSecret 5ba8c0ab-9fbd-4f07-9817-c48017c3cbad", "--apiCrawUrl http://218.3.126.49:30555/v1" //设备升级接口 ] }, diff --git a/api/app/lib/controllers/dataCacl/index.js b/api/app/lib/controllers/dataCacl/index.js new file mode 100644 index 0000000..21eca58 --- /dev/null +++ b/api/app/lib/controllers/dataCacl/index.js @@ -0,0 +1,636 @@ + +const moment = require('moment'); +//获取异常识别算法 +async function findAbnMethods(ctx, next) { + let rslt = null; + let error = { name: 'FindError', message: '异常识别算法获取失败' }; + try { + const models = ctx.fs.dc.models; + let abnMethods = await models.AbnTypes.findAll(); + rslt = abnMethods.map(s => ({ + id: s.id, + name: s.name, + des: s.des + })); + error = null; + } catch (err) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${err}`); + } + if (error) { + ctx.status = 400; + ctx.body = error; + } else { + ctx.status = 200; + ctx.body = rslt; + } +} +//获取异常参数配置 +async function findAbnParamList(ctx) { + const { sensorId } = ctx.query + const id=sensorId.split(',') + let rslt = null; + let error = { name: 'FindError', message: '异常参数配置获取失败' }; + try { + const models = ctx.fs.dc.models; + let abnParamList = await models.AbnReportParams.findAll({ + where: { sensorId:{$in:id} } + }) + rslt = abnParamList.map(s => ({ + key: s.id, + id: s.id, + sensorId: s.sensorId, + sensorName: s.sensorLocationDescription, + abnType: s.abnTypeId, + enabled: s.enabled, + factorId: s.factorId, + factorName: s.factor, + params: s.params, + itemIndex:s.itemIndex + })); + error = null; + } catch (err) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${err}`); + } + if (error) { + ctx.status = 400; + ctx.body = error; + } else { + ctx.status = 200; + ctx.body = rslt; + } +} + +//新增异常参数配置 +async function createAbnParam(ctx) { + let error = { name: 'CreateError', message: '异常参数配置新增失败' }; + const models = ctx.fs.dc.models + const data = ctx.request.body + + try { + for (let i = 0; i < data.length; i++) { + let dataItem = data[i]; + if (dataItem && dataItem.params && dataItem.abnType && dataItem.enabled != null && dataItem.sensorId && dataItem.factorId) { + let dataToSave = { + sensorId: dataItem.sensorId, + sensorLocationDescription:dataItem.sensorName, + enabled: dataItem.enabled, + abnTypeId: dataItem.abnType, + factorId: dataItem.factorId, + factor: dataItem.factorName, + itemIndex: dataItem.itemId, + params: dataItem.params + }; + await models.AbnReportParams.create(dataToSave) + + error = null + // // 日志信息 + // ctx.fs.api = ctx.fs.api || {} + // ctx.fs.api.actionParameter = JSON.stringify(data) + // ctx.fs.api.actionParameterShow = `新增异常推送配置id:${newId}` + } + } + } + catch (err) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${err}`) + } + if (error) { + ctx.status = 400 + ctx.body = error + } else { + ctx.status = 204 + } +} +// +async function batchSwitch(ctx, next) { + const ids = ctx.params.ids.split(',') + const data = ctx.request.body + let error = { name: 'UpdateError', message: data ? '批量启用异常参数配置失败' : '批量禁用异常参数配置失败' }; + try { + for (let i = 0; i < ids.length; i++) { + let id = ids[i]; + const models = ctx.fs.dc.models; + let abnParam = await models.AbnReportParams.findOne({ where: { id: id } }); + if (abnParam) { + let dataToSave = {}; + if (data.use == 'switch') { + dataToSave.enabled = data.enabled;//批量启用or禁用 + } else { + dataToSave.params = data.paramJson;//批量改参数 + } + if (Object.keys(dataToSave).length) { + await models.AbnReportParams.update(dataToSave, { where: { id } }); + } + error = null; + // // 日志信息 + // ctx.fs.api = ctx.fs.api || {}; + // ctx.fs.api.actionParameter = JSON.stringify(data); + // ctx.fs.api.actionParameterShow = `异常参数配置id:${id}`; + } else { + error = { name: 'NotFound', message: `不存在{id=${id}}的异常参数配置` }; + } + } + } catch (err) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${err}`); + } + if (error) { + ctx.status = 400; + ctx.body = error; + } else { + ctx.status = 204; + } +} + +//删除异常参数配置 +async function deleteAbnParam(ctx, next) { + let error = { name: 'DeleteError', message: '异常参数配置删除失败' }; + const ids = ctx.params.ids.split(','); + //const { id } = ctx.params; + try { + for (let i = 0; i < ids.length; i++) { + let id = ids[i]; + const models = ctx.fs.dc.models; + let abnParam = await models.AbnReportParams.findOne({ where: { id } }); + if (abnParam) { + await models.AbnReportParams.destroy({ where: { id } }); + error = null; + } else { + error = { name: 'NotFound', message: `不存在{id=${id}}的异常参数配置` }; + } + } + } catch (err) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${err}`); + } + if (error) { + ctx.status = 400; + ctx.body = error; + } else { + ctx.status = 204; + } +} + +//修改异常推送配置 +async function updateAbnParam(ctx) { + let error = { name: 'UpdateError', message: '异常参数配置修改失败' } + const ids = ctx.params.ids.split(',') + const data = ctx.request.body + + if (data && Object.keys(data).length) { + try { + for (let i = 0; i < ids.length; i++) { + let id = ids[i]; + const models = ctx.fs.dc.models; + let abnParam = await models.AbnReportParams.findOne({ where: { id: id } }); + if (abnParam) { + let dataToSave = {}; + const { abnType, params, enabled } = data; + if (enabled != null && enabled != abnParam.enabled) + dataToSave.enabled = enabled; + //中断 + if (abnType == 1) { + if (params != null && params.thr_int !== abnParam.params.thr_int) { + dataToSave.params = params; + } + } + //毛刺 + if (abnType == 2) { + if (params != null && params.thr_burr !== abnParam.params.thr_burr) { + dataToSave.params = params; + } + } + //趋势 + if (abnType == 3) { + if (params != null && + (params.thr_burr !== abnParam.params.thr_burr || params.win_med !== abnParam.params.win_med + || params.win_avg !== abnParam.params.win_avg || params.win_grad !== abnParam.params.win_grad + || params.thr_grad !== abnParam.params.thr_grad || params.thr_der !== abnParam.params.thr_der + || params.days_Last !== abnParam.params.days_Last)) { + dataToSave.params = params; + } + } + if (Object.keys(dataToSave).length) { + await models.AbnReportParams.update(dataToSave, { where: { id } }); + } + error = null; + } else { + error = { name: 'NotFound', message: `不存在{id=${id}}的异常参数配置` }; + } + } + } catch (err) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${err}`); + } + } + if (error) { + ctx.status = 400; + ctx.body = error; + } else { + ctx.status = 204; + } +} +//异常数据对比 +async function getAbnTaskResult(ctx, next) { + let error = { name: 'TaskError', message: '异常数据对比失败' } + const models = ctx.fs.dc.models + const structId = ctx.params.id + const startTime = ctx.params.start + const endTime = ctx.params.end + const data = ctx.request.body + const stationId = data.station + let factorProto = await models.Factor.findOne({ + where: { id: data.factorId }, + attributes: ['id', 'proto'] + }); + let protoItems = await models.FactorProtoItem.findAll({ + where: { proto: factorProto.proto }, + attributes: ['id', 'name'] + }); + let itemName = await models.FactorProtoItem.findOne({ + where: { id: data.itemId ? data.itemId : protoItems[0].id }, + attributes: ['id', 'field_name', 'name'] + }); + try { + const itemsObj = await findThemeItems(models, data.factorId) + const filter = { + query: { + bool: { + must: [ + { match: { "sensor": stationId } } + ] + } + } + } + if (startTime && endTime) { + filter.query.bool.must.push({ range: { "collect_time": { gte: moment(startTime).toISOString(), lte: moment(endTime).toISOString() } } }); + } + const esThemeData = await findThemeDataFromES(ctx.app.fs.esclient, filter) + const stationsData = esThemeData.reduce((p, c) => { + const { sensor, data, collect_time } = c._source; + p.unshift(Object.assign({}, data, { time: moment(collect_time).format('YYYY-MM-DD HH:mm:ss') })); + return p; + }, []); + //获取前一天的最后一条数据 + const preFilter = { + query: { + bool: { + must: [ + { match: { "sensor": stationId } }, + { range: { "collect_time": { gte: moment(startTime).add('days', -1).toISOString(), lte: moment(startTime).toISOString() } } } + ] + } + } + } + const esPreData = await findThemeDataFromES(ctx.app.fs.esclient, preFilter); + const preOneData = esPreData.reduce((p, c) => { + const { data, collect_time } = c._source; + p.unshift(Object.assign({}, data, { time: moment(collect_time).format('YYYY-MM-DD HH:mm:ss') })); + return p; + }, []); + let one = preOneData && preOneData.length > 0 ? preOneData[preOneData.length - 1] : null; + + let itemKey = itemName.get({ plain: true }).field_name;//监测项名称 + let itemn = itemName.get({ plain: true }).name; + let calcResult = calcAlgorithm(one, stationsData, data, itemKey);//计算 + ctx.status = 200; + ctx.body = { + unit: itemsObj[itemKey].unit, + method: data.abnType, + itemKey: itemKey, + itemName: itemn, + stationData: stationsData, + resultArray: calcResult + }; + } catch (err) { + ctx.fs.logger.error(err); + ctx.status = 400; + ctx.body = { + name: "FindError", + message: "异常数据识别-数据对比失败" + } + } +} + +let calcAlgorithm = function (dataOne, dataSource, params, itemKey) { + let result; + switch (params.abnType) { + case "interrupt": + result = interrupt(dataOne, dataSource, params, itemKey); + break; + case "burr": + result = burr(dataOne, dataSource, params, itemKey).result; + break; + case "trend": + result = trend(null, dataSource, params, itemKey); + break; + } + return result; +}; +//中断 +let interrupt = function (dataOne, dataSource, params, key) { + let result = []; + if (dataSource.length != 0) { + if (dataOne == null) { + result.push({ + type: "interrupt", + hour: 24.00, + time: dataSource[0].time, + value: dataSource[0][key] + });//第一个点中断 + } else { + dataSource.unshift(dataOne); + } + for (let i = 0; i < dataSource.length - 1; i++) { + if (dataSource[i] == null || dataSource[i + 1] == null) continue; + let hour = getHour(dataSource[i + 1].time, dataSource[i].time); + if (hour >= params.params.thr_int) { + result.push({ + type: "interrupt", + hour: hour, + time: dataSource[i + 1].time, + value: dataSource[i + 1][key] + }); + } + } + } + return result; +} +//毛刺 +let burr = function (dataOne, dataSource, params, key) { + let burrTv = params.params.thr_burr; + let result = [];//毛刺点 + let dataSAfterBurr = [];//去掉毛刺点的数组 + if (dataSource.length != 0) { + if (dataOne != null) { + dataSource.unshift(dataOne); + } + for (let i = 1; i < dataSource.length - 1; i++) { + if (dataSource[i - 1] == null || dataSource[i] == null || dataSource[i + 1] == null) continue + let gap1 = dataSource[i][key] - dataSource[i - 1][key] + let gap2 = dataSource[i][key] - dataSource[i + 1][key] + + let gap3 = dataSource[i - 1][key] - dataSource[i][key] + let gap4 = dataSource[i + 1][key] - dataSource[i][key] + + let result1 = (gap1 > burrTv && gap2 > burrTv) + let result2 = (gap3 > burrTv && gap4 > burrTv) + + if (i == 1) {//第一个点 + dataSAfterBurr.push(dataSource[0]) + } + if (result1 || result2) { + result.push({ + type: "burr", + burr: result1 ? Math.min(gap1, gap2) : Math.min(gap3, gap4), + time: dataSource[i].time, + value: dataSource[i][key] + }) + } else { + dataSAfterBurr.push(dataSource[i]) + } + if (i == dataSource.length - 2) {//最后一个点 + dataSAfterBurr.push(dataSource[dataSource.length - 1]) + } + } + } + return { result: result, dataSAfterBurr: dataSAfterBurr } +} +//异常趋势 +let trend = function (dataOne, dataSource, params, key) { + let result; + if (dataSource.length != 0) { + //去完毛刺的新数组 + let afterBurr = burr(dataOne, dataSource, params, key).dataSAfterBurr; + //滑动中值 + let arrAfterMedian = []; + for (let i = 0; i < afterBurr.length; i += parseInt(params.params.win_med)) { + let arr = afterBurr.slice(i, i + parseInt(params.params.win_med)) + let oneMedian = calcMedian(arr, key) + arrAfterMedian.push(oneMedian) + } + //滑动均值 + let arrAfterAvg = calcMeanValue(arrAfterMedian, params.params.win_avg, key) + //错位相减,相当于求导 + let arrAfterDe = [] + for (let j = 0; j < arrAfterAvg.length - 1; j++) { + let one = { + value: arrAfterAvg[j + 1].value - arrAfterAvg[j].value, + time: arrAfterAvg[j + 1].time + } + arrAfterDe.push(one); + } + //最后判断 + let finalArray = finalJudge(arrAfterDe, arrAfterMedian, params) + result = { + calcFinal: finalArray, + calcPreprocess: arrAfterAvg//要画预处理+滑动均值完了的曲线 + }; + } + return result +} +let getHour = function (s1, s2) { + s1 = new Date(s1.replace(/-/g, '/')) + s2 = new Date(s2.replace(/-/g, '/')) + let ms = Math.abs(s1.getTime() - s2.getTime()) + return ms / 1000 / 60 / 60; +} +//计算一组数据的中值 +let calcMedian = function (array, key) { + let result; + if (array != null || array.length > 0) { + array.sort((a, b) => { return a[key] - b[key]}) + if (array.length % 2 == 0) {//偶数 + let index1 = array.length / 2; + result = { + value: (array[index1][key] + array[index1 - 1][key]) / 2, + time: array[index1].time + } + + } else {//奇数 + let index = (array.length - 1) / 2 + result = { + value: array[index][key], + time: array[index].time + } + } + } + return result; +} +//计算一组数据的均值 +let calcMeanValue = function (array, coef, key) { + let result = []; + let sum = 0; + if (array != null || array.length > 0) { + for (let i = 0; i < array.length; i++) { + let value; + if (i < parseInt(coef)) { + sum = sum + array[i].value + value = sum / (i + 1) + } else { + let arr = array.slice(i - parseInt(coef) + 1, i + 1) + let ssum = 0; + for (let s = 0; s < arr.length; s++) { + ssum = ssum + arr[s].value + } + value = ssum / parseInt(coef) + } + let one = { + value: value, + time: array[i].time + } + result.push(one) + } + } + return result +} + +let finalJudge = function (array, original, params) { + let ups = 1, downs = 1; + let tempUp = [], tempDown = []; + let point = params.params.win_grad;//渐变点个数 + let deTv = params.params.thr_der;//导数阈值 + let greTv = params.params.thr_grad;//渐变阈值 + let finalArray = []; + for (let i = 0; i < array.length; i++)//对最新数组作阈值判断 + { + if (array[i].value > deTv) { + ups = ups + 1 + if (ups == 2) { + tempUp.push(original[i]) + } + tempUp.push(original[i + 1]) + if (tempDown.length >= point) { + let bbb = tempDown[tempDown.length - 1].value - tempDown[0].Value + if (downs >= point && bbb < -greTv) { + let one = { + startTime: tempDown[0].time, + endTime: tempDown[tempDown.length - 1].time, + startValue: tempDown[0].value, + endValue: tempDown[tempDown.length - 1].value, + value: bbb, + des: "异常下降" + }; + finalArray.push(one) + } + } + downs = 1 + tempDown = [] + } else if (array[i].value < -deTv) { + downs = downs + 1; + if (downs == 2) { + tempDown.push(original[i]) + } + tempDown.push(original[i + 1]) + if (tempUp.length >= point) { + let aaa = tempUp[tempUp.length - 1].value - tempUp[0].value + if (ups >= point && aaa > greTv) { + let one = { + startTime: tempUp[0].time, + endTime: tempUp[tempUp.length - 1].time, + startValue: tempUp[0].value, + endValue: tempUp[tempUp.length - 1].value, + value: aaa, + des: "异常上升" + }; + finalArray.push(one) + } + } + ups = 1; + tempUp = [] + } + } + if (ups >= point) { + let ccc = tempUp[tempUp.length - 1].value - tempUp[0].value + if (ccc > greTv) { + let one = { + startTime: tempUp[0].time, + endTime: tempUp[tempUp.length - 1].time, + startValue: tempUp[0].value, + endValue: tempUp[tempUp.length - 1].value, + value: ccc, + des: "异常上升" + }; + finalArray.push(one) + } + } + if (downs >= point) { + let ddd = tempDown[tempDown.length - 1].value - tempDown[0].value + if (ddd < -greTv) { + let one = { + startTime: tempDown[0].time, + endTime: tempDown[tempDown.length - 1].time, + startValue: tempDown[0].value, + endValue: tempDown[tempDown.length - 1].value, + value: ddd, + des: "异常下降" + }; + finalArray.push(one); + } + } + return finalArray +} +async function findThemeItems(models, factor) { + try { + let factorProto = await models.Factor.findOne({ + where: { id: factor }, + attributes: ['id', 'proto'] + }); + let protoItems = await models.FactorProtoItem.findAll({ + where: { proto: factorProto.proto }, + attributes: ['name', 'fieldName'], + include: [{ + model: models.ItemUnit, + where: { default: true }, + required: true, + attributes: ['name'] + }] + }); + let itemsObj = protoItems.reduce((p, c) => { + p[c.fieldName] = { + name: c.name, + unit: c.itemUnits[0] ? c.itemUnits[0].name : null + } + return p + }, {}) + return itemsObj + } catch (err) { + throw err + } +}; +async function findThemeDataFromES(esclient, filter, limit, _source) { + try { + let rslt = [] + const client = esclient[THEME_DATA] + let params = { + index: client.config.index, + type: client.config.type, + body: filter + } + + params.size = limit + if (limit == null) { + const countRes = await client.count(params) + params.size = countRes.count > 10000 ? 10000 : countRes.count + } + + params._source = _source || ["sensor", "collect_time", "data"] + params.body.sort = { "collect_time": { "order": "desc" } } + let res = await client.search(params) + rslt = res.hits.hits + return rslt + } catch (err) { + throw err + } +} + + + + + + +module.exports = { + findAbnMethods, + findAbnParamList, + createAbnParam, + updateAbnParam, + batchSwitch, + deleteAbnParam, + getAbnTaskResult +}; \ No newline at end of file diff --git a/api/app/lib/controllers/monitor/index.js b/api/app/lib/controllers/monitor/index.js new file mode 100644 index 0000000..fff7966 --- /dev/null +++ b/api/app/lib/controllers/monitor/index.js @@ -0,0 +1,192 @@ + +const moment = require('moment'); + +async function getStructures (ctx) { + try { + const { models } = ctx.fs.dc; + const { clickHouse } = ctx.app.fs + const { pomsProjectId } = ctx.query + let bindRes=[] + //选择全局就是查询所有项目下的结构物,有选择项目就只查询对应项目的结构物 + if(pomsProjectId){ + bindRes = await models.ProjectCorrelation.findAll({where:{id:{ $in: pomsProjectId.split(',') }}}) + }else{ + bindRes = await models.ProjectCorrelation.findAll() + } + let anxinProjectIds = new Set() + for (let b of bindRes) { + if (b.anxinProjectId.length) { + for (let aid of b.anxinProjectId) { + anxinProjectIds.add(aid) + } + } + } + let undelStrucRes=[] + // if (bindRes) { + // undelStrucRes = anxinProjectIds.size ? + // await clickHouse.anxinyun.query( + // ` + // SELECT + // t_structure.id AS strucId, + // t_structure.name AS strucName + + // FROM + // t_project + // LEFT JOIN + // t_project_structure + // ON t_project_structure.project = t_project.id + // LEFT JOIN + // t_project_structuregroup + // ON t_project_structuregroup.project = t_project.id + // LEFT JOIN + // t_structuregroup_structure + // ON t_structuregroup_structure.structuregroup = t_project_structuregroup.structuregroup + // LEFT JOIN + // t_project_construction + // ON t_project_construction.project = t_project.id + // LEFT JOIN + // t_structure_site + // ON t_structure_site.siteid = t_project_construction.construction + // RIGHT JOIN + // t_structure + // ON t_structure.id = t_project_structure.structure + // OR t_structure.id = t_structuregroup_structure.structure + // OR t_structure.id = t_structure_site.structid + + // WHERE + // project_state != -1 + // AND + // t_project.id IN (${[...anxinProjectIds].join(',')}, -1) + // ORDER BY strucId + // ` + // ).toPromise() : + // [] + // } + undelStrucRes.push({strucId:4036,strucName:'象山港大桥'}) + undelStrucRes.push({strucId:1,strucName:'象山港大'}) + ctx.status = 200; + ctx.body = undelStrucRes + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +async function getFactors (ctx) { + try { + const { models } = ctx.fs.dc; + const { clickHouse } = ctx.app.fs + const { structId,cacl } = ctx.query + let list=[] + if(cacl){ + const factorList=await clickHouse.alarmLocal.query(`select distinct SafetyFactorTypeId, SafetyFactorTypeName from sensors where PlatformStructureId =${structId}`).toPromise() + let fList=factorList.map(m=>m.SafetyFactorTypeId) + list=await clickHouse.alarmLocal.query(`select distinct FactorID, Name,Items,ItemNames from factors where FactorID in (${[...fList,-1].join(',')})`).toPromise() + }else{ + list=await clickHouse.alarmLocal.query(`select distinct SafetyFactorTypeId, SafetyFactorTypeName from sensors where PlatformStructureId =${structId}`).toPromise() + } + ctx.body=list + ctx.status=200 + }catch(error){ + ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} +//查询设备 +async function getSensors (ctx) { + try { + const { models } = ctx.fs.dc; + const { clickHouse } = ctx.app.fs + const { structId,SafetyFactorTypeId } = ctx.query + const list=await clickHouse.alarmLocal.query(` + select distinct SensorId,SensorLocationDescription,Project + from sensors where PlatformStructureId =${structId} + and SafetyFactorTypeId=${SafetyFactorTypeId}`).toPromise() + ctx.body=list + ctx.status=200 + }catch(error){ + ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} +//根据设备id和监测因素id查询监测数据 +async function getMonitorData (ctx) { + try{ + const { clickHouse } = ctx.app.fs + const { factorId,sensorId,startTime, endTime,limit,page } = ctx.query + const offset=page*limit + const factorsList=await clickHouse.alarmLocal.query(`SELECT FactorID,Items,ItemNames,ItemUnits FROM factors + WHERE FactorID=${factorId} + `).toPromise()||[] + const monitorData=await clickHouse.alarmLocal.query(`SELECT SensorId,CollectTime,Values FROM themes + WHERE SensorId in ('${sensorId}') + AND CollectTime >= toDate('${startTime}') + AND CollectTime <= toDate('${endTime}') + LIMIT ${limit} OFFSET ${offset} + `).toPromise()||[] + console.log('vmonitorDatamonitorDatamonitorData',monitorData) + const id=sensorId&&sensorId.replace('-',':') + const sensor=await clickHouse.alarmLocal.query(`SELECT distinct SensorId,SensorLocationDescription FROM sensors WHERE ID='${id}'`).toPromise() + //监测项 + let items={} + if(factorsList&&factorsList.length>0){ + //因素解释 + let factors=[] + //因素名词 + let factorNames=[] + factorsList.map(item=>{ + factors=item.ItemNames.split(',') + factorNames=item.Items.split(',') + factors.map(child=>{ + factorNames.map(p=>{ + items[p]={name:child,unit:item.ItemUnits} + }) + }) + }) + } + //设备数据+数据 + let sensors=[] + if(monitorData&&monitorData.length>0){ + monitorData.map(data => { + const values = {}; + Object.keys(items).forEach(key => { + const index = Object.keys(items).indexOf(key); + values[key] = data.Values[index]; + }); + sensors.push({ values, time: moment(data.CollectTime).format('YYYY-MM-DD HH:mm:ss') }); + }); + + } + if(sensor&&sensor.length){ + ctx.body={items,sensors:[{data:sensors,id:sensor[0].SensorId,name:sensor[0].SensorLocationDescription}]} + ctx.status=200 + }else{ + ctx.body={items,sensors:[]} + ctx.status=200 + } + }catch(error){ + ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + + + + + + +module.exports = { + getStructures,getFactors,getSensors,getMonitorData +} \ No newline at end of file diff --git a/api/app/lib/index.js b/api/app/lib/index.js index b8c4758..c5401bf 100644 --- a/api/app/lib/index.js +++ b/api/app/lib/index.js @@ -69,7 +69,7 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq AppInspection, ProjectApp, ProjectCorrelation, AppAlarm, App, AlarmAppearRecord, AlarmConfirmLog, EmailSendLog, LatestDynamicList, AlarmPushConfig, MaintenanceRecord, MaintenanceRecordExecuteUser, MaintenancePlanExecuteUser, MaintenancePlan, EquipmentMaintenanceRecord, EquipmentMaintenanceRecordProject, EquipmentMaintenanceRecordExecuteUser, ServerMaintenanceRecordRepairman, ServerMaintenanceRecord, - AlarmDataContinuityType, AlarmDataContinuity, + AlarmDataContinuityType, AlarmDataContinuity,AbnTypes,AbnReportParams } = dc.models; AppInspection.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' }); @@ -135,5 +135,6 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq ServerMaintenanceRecordRepairman.belongsTo(ServerMaintenanceRecord, { foreignKey: 'serverMaintenanceRecordId', targetKey: 'id' }) ServerMaintenanceRecord.hasMany(ServerMaintenanceRecordRepairman, { foreignKey: 'serverMaintenanceRecordId', targetKey: 'id' }) - + AbnReportParams.belongsTo(AbnTypes, { foreignKey: 'abnTypeId', targetKey: 'id' }) + AbnTypes.hasMany(AbnReportParams, { foreignKey: 'abnTypeId', targetKey: 'id' }) }; diff --git a/api/app/lib/models/abn_report_params.js b/api/app/lib/models/abn_report_params.js new file mode 100644 index 0000000..04647f8 --- /dev/null +++ b/api/app/lib/models/abn_report_params.js @@ -0,0 +1,101 @@ +/* eslint-disable*/ + +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const AbnReportParams = sequelize.define("abnReportParams", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true + }, + sensorId: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "sensor_id", + autoIncrement: false + }, + sensorLocationDescription: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "sensor_location_description", + autoIncrement: false + }, + factor: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "factor", + autoIncrement: false + }, + factorId: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "factor_id", + autoIncrement: false + }, + abnTypeId: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "abn_type", + autoIncrement: false, + references: { + key: "id", + model: "abnType" + } + }, + params: { + type: DataTypes.JSONB, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "params", + autoIncrement: false + }, + enabled: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "enabled", + autoIncrement: false + }, + itemIndex: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "item_index", + autoIncrement: false + } + }, { + tableName: "abn_report_params", + comment: "", + indexes: [] + }); + dc.models.AbnReportParams = AbnReportParams; + return AbnReportParams; +}; \ No newline at end of file diff --git a/api/app/lib/models/abn_type.js b/api/app/lib/models/abn_type.js new file mode 100644 index 0000000..e67070d --- /dev/null +++ b/api/app/lib/models/abn_type.js @@ -0,0 +1,31 @@ +'use strict'; + +module.exports = (dc) => { + const AbnTypes = dc.orm.define('abnTypes', + { + id: { + field: 'id', + type: dc.ORM.INTEGER, + primaryKey: true, + unique: true, + allowNull: false, + autoIncrement: true + }, + name: { + field: 'name', + type: dc.ORM.STRING, + allowNull: false + }, + des: { + field: 'description', + type: dc.ORM.STRING, + allowNull: false + } + }, { + tableName: 'abn_type' + }); + + dc.models.AbnTypes = AbnTypes; + + return AbnTypes; +}; \ No newline at end of file diff --git a/api/app/lib/routes/dataCacl/index.js b/api/app/lib/routes/dataCacl/index.js new file mode 100644 index 0000000..f93ba74 --- /dev/null +++ b/api/app/lib/routes/dataCacl/index.js @@ -0,0 +1,28 @@ + +'use strict'; + +const dataCacl = require('../../controllers/dataCacl'); + +module.exports = function (app, router, opts) { + app.fs.api.logAttr['GET/abnormal/methods'] = { content: '获取异常识别算法', visible: true } + router.get('/abnormal/methods', dataCacl.findAbnMethods) + + app.fs.api.logAttr['GET/struct/abnormal/params'] = { content: '获取结构物下异常识别参数配置列表', visible: false } + router.get('/struct/abnormal/params', dataCacl.findAbnParamList) + + app.fs.api.logAttr['POST/struct/abnormal/params'] = { content: '新增异常识别参数配置', visible: true } + router.post('/struct/abnormal/params', dataCacl.createAbnParam) + + app.fs.api.logAttr['PUT/batch/switch/:ids'] = { content: '异常参数批量配置', visible: true } + router.put('/batch/switch/:ids', dataCacl.batchSwitch) + + app.fs.api.logAttr['DELETE/delete/abnormal/params/:ids'] = { content: '删除异常识别参数配置', visible: true } + router.del('delete/abnormal/params/:ids', dataCacl.deleteAbnParam) + + app.fs.api.logAttr['PUT/edit/abnormal/params/:ids'] = { content: '修改异常识别参数配置', visible: true } + router.put('/edit/abnormal/params/:ids', dataCacl.updateAbnParam) + + app.fs.api.logAttr['POST/struct/:id/abnTask/result/:start/:end'] = { content: '获取异常数据识别任务结果', visible: true } + router.post('/struct/:id/abnTask/result/:start/:end', dataCacl.getAbnTaskResult) + +} \ No newline at end of file diff --git a/api/app/lib/routes/monitor/index.js b/api/app/lib/routes/monitor/index.js new file mode 100644 index 0000000..84ae094 --- /dev/null +++ b/api/app/lib/routes/monitor/index.js @@ -0,0 +1,20 @@ + +'use strict'; + +const monitor = require('../../controllers/monitor'); + +module.exports = function (app, router, opts) { + + app.fs.api.logAttr['GET/project/allStructures'] = { content: '获取对应项目的结构物', visible: true }; + router.get('/project/allStructures', monitor.getStructures); + + + app.fs.api.logAttr['GET/structure/factors'] = { content: '获取对应的监测因素', visible: true }; + router.get('structure/factors', monitor.getFactors); + + app.fs.api.logAttr['GET/structure/factors/sensors'] = { content: '获取对应的点位', visible: true }; + router.get('structure/factors/sensors', monitor.getSensors); + + app.fs.api.logAttr['GET/structure/factors/sensors/data'] = { content: '获取对应点位的设备', visible: true }; + router.get('structure/factors/sensors/data', monitor.getMonitorData); +} \ No newline at end of file diff --git a/api/config.js b/api/config.js index 372508b..af91bcd 100644 --- a/api/config.js +++ b/api/config.js @@ -49,7 +49,7 @@ args.option('clickHousePepEmis', 'clickHouse 项企数据库名称'); args.option('clickHouseProjectManage', 'clickHouse 项目管理数据库名称'); args.option('clickHouseVcmp', 'clickHouse 视频平台数据库名称'); args.option('clickHouseDataAlarm', 'clickHouse 视频平台数据告警库名称'); -// args.option('clickHouseDataAlarmLocal', 'clickHouse 本地化告警相关数据'); +args.option('clickHouseDataAlarmLocal', 'clickHouse 本地化告警相关数据'); args.option('clickHouseIot', 'clickHouse IOT平台设备信息库名称'); args.option('clickHouseCamworkflow', 'clickHouse 工作流数据库名称'); @@ -125,7 +125,7 @@ const CLICKHOUST_VCMP = process.env.CLICKHOUST_VCMP || flags.clickHouseVcmp const CLICKHOUST_DATA_ALARM = process.env.CLICKHOUST_DATA_ALARM || flags.clickHouseDataAlarm const CLICKHOUST_IOT = process.env.CLICKHOUST_IOT || flags.clickHouseIot const CLICKHOUST_CAM_WORKFLOW = process.env.CLICKHOUST_CAM_WORKFLOW || flags.clickHouseCamworkflow -// const CLICKHOUST_DATA_ALARM_LOCAL=process.env.CLICKHOUST_DATA_ALARM_LOCAL|| flags.clickHouseDataAlarmLocal +const CLICKHOUST_DATA_ALARM_LOCAL=process.env.CLICKHOUST_DATA_ALARM_LOCAL|| flags.clickHouseDataAlarmLocal const CONFIRM_ALARM_ANXIN_USER_ID = process.env.CONFIRM_ALARM_ANXIN_USER_ID || flags.confirmAlarmAnxinUserId @@ -171,7 +171,7 @@ const requireParams = { CONFIRM_ALARM_ANXIN_USER_ID, VCMP_APP_ID, VCMP_APP_SECRET, API_CRAW_URL, - // CLICKHOUST_DATA_ALARM_LOCAL, + CLICKHOUST_DATA_ALARM_LOCAL, TYPES } @@ -341,10 +341,10 @@ const product = { name: 'camWorkflow', db: CLICKHOUST_CAM_WORKFLOW }, - // { - // name: 'alarmLocal', - // db: CLICKHOUST_DATA_ALARM_LOCAL - // } + { + name: 'alarmLocal', + db: CLICKHOUST_DATA_ALARM_LOCAL + } ] }, es: { diff --git a/script/3.7/schema/1.create_abn_type.sql b/script/3.7/schema/1.create_abn_type.sql new file mode 100644 index 0000000..6e274a6 --- /dev/null +++ b/script/3.7/schema/1.create_abn_type.sql @@ -0,0 +1,12 @@ +create table abn_type +( + id serial + primary key, + name varchar(50) not null, + description varchar(50) not null +); +INSERT INTO abn_type (id, name, description) VALUES (DEFAULT, 'interrupt'::varchar(50), '数据中断'::varchar(50)); +INSERT INTO abn_type (id, name, description) VALUES (DEFAULT, 'burr'::varchar(50), '毛刺'::varchar(50)); +INSERT INTO abn_type (id, name, description) VALUES (DEFAULT, 'trend'::varchar(50), '趋势'::varchar(50)) + + diff --git a/script/3.7/schema/2.create_abn_report_params.sql b/script/3.7/schema/2.create_abn_report_params.sql new file mode 100644 index 0000000..f07268d --- /dev/null +++ b/script/3.7/schema/2.create_abn_report_params.sql @@ -0,0 +1,15 @@ +create table abn_report_params +( + id serial + primary key, + sensor_id varchar(300) not null, + sensor_location_description varchar(300) not null, + factor varchar(30) not null, + factor_id varchar(30) not null, + abn_type integer + constraint t_abn_report_params_t_abn_type_id_fk + references abn_type, + params jsonb not null, + enabled boolean default true not null, + item_index integer not null +); diff --git a/web/client/src/sections/data/actions/index.js b/web/client/src/sections/data/actions/index.js index 66a9a99..8b92c7b 100644 --- a/web/client/src/sections/data/actions/index.js +++ b/web/client/src/sections/data/actions/index.js @@ -1,7 +1,7 @@ 'use strict'; import * as dataQuery from './dataQuery' - +import * as monitor from './monitor' export default { - ...dataQuery + ...dataQuery,...monitor } \ No newline at end of file diff --git a/web/client/src/sections/data/actions/monitor.js b/web/client/src/sections/data/actions/monitor.js new file mode 100644 index 0000000..4035ec2 --- /dev/null +++ b/web/client/src/sections/data/actions/monitor.js @@ -0,0 +1,54 @@ +/* + * @Author: zhaobing + * @Date: 2023-11-02 11:31:56 + */ +import { ApiTable, basicAction } from '$utils' + +export function getProjectAllStructures (query) {//获取对应项目下所有结构物 + return (dispatch) => basicAction({ + type: "get", + dispatch: dispatch, + actionType: "GET_PROJECT_ALL_STRUCTURES", + query: query, + url: `${ApiTable.getProjectAllStructures}`, + msg: { option: "获取对应项目下的所有结构物" }, + reducer: { name: "ProjectAllStructures", params: { noClear: true } }, + }); + } + + + export function getFactors (query) {//获取对应结构物下的检测因素 + return (dispatch) => basicAction({ + type: "get", + dispatch: dispatch, + actionType: "GET_FACTORS", + query: query, + url: `${ApiTable.getFactors}`, + msg: { option: "获取对应结构物下的检测因素" }, + reducer: { name: "Factors", params: { noClear: true } }, + }); + } + + export function getSensors (query) {//获取对应检测因素下的设备 + return (dispatch) => basicAction({ + type: "get", + dispatch: dispatch, + actionType: "GET_SENSORS", + query: query, + url: `${ApiTable.getSensors}`, + msg: { option: "获取对应检测因素下的设备" }, + reducer: { name: "Sensors", params: { noClear: true } }, + }); + } + + export function getMonitorData (query) {//获取对应检测因素下的设备的数据(最终的数据) + return (dispatch) => basicAction({ + type: "get", + dispatch: dispatch, + actionType: "GET_MONITOR_DATA", + query: query, + url: `${ApiTable.getMonitorData}`, + msg: { option: "获取对应检测因素下的设备的数据(最终的数据)" }, + reducer: { name: "SensorsData", params: { noClear: true } }, + }); +} \ No newline at end of file diff --git a/web/client/src/sections/data/components/lineTimeChartTemplate.jsx b/web/client/src/sections/data/components/lineTimeChartTemplate.jsx new file mode 100644 index 0000000..e5c52ae --- /dev/null +++ b/web/client/src/sections/data/components/lineTimeChartTemplate.jsx @@ -0,0 +1,359 @@ + +'use strict'; +import React, { Component } from 'react'; +// 引入 echarts 主模块。 +import * as echarts from 'echarts/lib/echarts'; +// 引入折线图 +import 'echarts/lib/chart/line'; +//引入标签组件 +import ReactEcharts from 'echarts-for-react'; + +let chartDownload = ""; +class LineTimeChart extends Component { + constructor(props) { + super(props); + this.sliderId = 'sliderId' + Math.floor(Math.random() * 1000000000); + this.chartId = 'chartId' + Math.floor(Math.random() * 1000000000); + this.legendId = 'legendId' + Math.floor(Math.random() * 1000000000); + this.legendListId = 'legendListId' + Math.floor(Math.random() * 1000000000); + this.tooltipId = 'tooltipId' + Math.floor(Math.random() * 1000000000); + this.state = { + option: {}, + } + } + + componentDidMount() { + let that = this; + this.renderChart(); + this.reSetLegendStyle(that) + window.addEventListener('resize', function () { + let a = setInterval(function () { that.reSetLegendStyle(that) }, 100); + setTimeout(function () { clearInterval(a) }, 3500) + }); + } + + reSetLegendStyle = (that) => { + // Ps.initialize(document.getElementById(that.legendId)); + // Ps.initialize(document.getElementById(that.tooltipId)); + // let legendListDom = document.getElementById(that.legendListId) + // legendListDom.style.textAlign = 'left'; + // legendListDom.style.maxWidth = '115px' + // document.getElementById(that.legendId).style.left = '10px' + } + + download = () => { + this.chartDownload.downloadImage(); + } + + renderChart = () => { + const { options } = this.props; + const props = this.props; + let height = props.height || 300; + let showSlider = options && options.sliderStart; + let padding = [20, 135, 30, 140] + let startTime, endTime, ds, dv, view; + + // let chart = new G2.Chart({ + // container: this.chartId, + // forceFit: true, + // height: height, + // padding: padding, + // }); + // chart.source(props.data); + // if (showSlider) { + // startTime = options.sliderStart; + // endTime = options.sliderEnd; + // ds = new DataSet({ + // state: { + // start: startTime, + // end: endTime, + // } + // }); + // dv = ds.createView(); + // dv.source(props.data) + // .transform({ // !!! 根据状态量设置数据过滤规则, + // type: 'filter', + // callback: obj => { + // return obj.time <= ds.state.end && obj.time >= ds.state.start; + // } + // }); + // } + // chart.scale({ + // 'time': { + // range: [0, 1], + // type: 'time', + // alias: '时间', + // mask: 'YYYY-MM-DD HH:mm:ss', + // }, + // }); + // chart.on('tooltip:change', function (ev) { + // let item = ev.items[0]; + // item.title = item.title; + // }); + // chart.axis('value', { + // position: 'right' + // }); + // chart.tooltip(true, { + // enterable: true, + // offset: 3, + // crosshairs: { + // type: 'cross', + // }, + // inPlot: false, + // containerTpl: ` + //
+ //
+ //
+ // + //
+ //
`, + // }); + // chart.legend(true, { + // marker: 'circle', + // useHtml: true, + // position: 'left', + // containerTpl: `
+ //

+ // + //
`, + // }) + // let guideOption = { + // position: ['min', 'max'], + // content: options.yAlias, + // offsetY: -10 + // } + // if (showSlider) { + // view = chart.view(); + // view.source(dv); // !!! 注意数据源是 ds 创建 DataView 对象 + // view.guide().text(guideOption); + // view.line().position('time*value').color('name').shape('line').size(2); + // } else { + // chart.guide().text(guideOption); + // chart.line().position('time*value').color('name').shape('line').size(2); + // } + // chart.render(); + // if (showSlider) { + // // 创建滑动条 + // let slider = new Slider({ + // container: this.sliderId, + // padding: [35, 135, 30, 140], + // xAxis: 'time', + // yAxis: 'value', + // start: startTime, + // end: endTime, + // data: props.data, + // scales: { + // 'time': { + // type: 'time', + // mask: "YYYY-MM-DD HH:mm:ss" + // } + // }, + // onChange: ({ startValue, endValue, startText, endText }) => { + // ds.setState('start', startText); + // ds.setState('end', endText); + // } // 更新数据状态量的回调函数 + // }); + // slider.render(); + // } + // chart.changeData(props.data); + + //组织dataset数据 + // let data = this.props.data; + // let dataset = [], name = [], x = ['product']; + // data.map(d => { + // x.push(d.time); + // let value = []; + // if (!name.includes(d.name)) { + // name.push(d.name); + // value.push(d.name); + // value.push(d.value); + // dataset.push(value); + // } else { + // dataset.map(set => { + // if (set[0] == d.name) { + // set.push(d.value); + // } + // }) + // } + + // }); + // dataset.unshift(x); + //组织后端数据组织data + let data = this.props.data; + let dataArray = [], types = {}; + let xAxisValue = props.xAxis || 'time'; + let yAxisValue = props.yAxis || 'value'; + data.map(dd => { + if (!types[dd.name]) { + types[dd.name] = Object.assign({ + name: dd.name, data: [] + }, { + type: 'line', + //smooth: true + }); + } + types[dd.name].data.push({ + name: dd.time, + value: [dd[xAxisValue], dd[yAxisValue]] + }) + + }); + for (let t in types) { + dataArray.push(types[t]); + } + // //根据dataset数据数量给出足够的线空间 + // let line, lines = []; + // line = { type: 'line', smooth: true, seriesLayoutBy: 'row' } + // dataset.map(data => { + // lines.push(line); + // }); + + let eOption = {} + //解决只有一条数据时图例和线颜色不同 + // if (dataset.length == 2) { + // eOption.color = ['#000000'] + // } + //鼠标移入提示 + eOption.tooltip = { + // axisPointer:{ + // axis:{} + // } + trigger: 'axis', + //格式化 tooltip + formatter: function (params, ticket, callback) { + let htmlStr = ""; + for (let i = 0; i < params.length; i++) { + let param = params[i]; + let xName = param.name;//x轴的名称 + let seriesName = param.seriesName.split("series");//图例名称 + let value = param.value;//y轴值 + let color = param.color;//图例颜色 + let data = param.value[1] ? param.value[1] : '无数据'; + if (value[1] && seriesName.length == 1) { + htmlStr += '
' + xName + ' ' + seriesName[0] + ' ' + data + '
'; + } + } + return htmlStr; + } + } + //X轴 + eOption.xAxis = { + //type: 'category', + type: 'time', + // min: new Date(xxxxx), + // //结束时间 + // max: new Date(xxxxx), + // axisLabel: { + // interale: 0, + // rotate: -40, + // formatter: function (value) {//在这里写你需要的时间格式 + // var t_date = new Date(value); + // return [t_date.getFullYear(), t_date.getMonth() + 1, t_date.getDate()].join('-') + " " + // + [t_date.getHours(), t_date.getMinutes()].join(':'); + + // } + // } + + + } + //Y轴 + eOption.yAxis = { + type: 'value', + min: 'dataMin', + max: 'dataMax', + name: options.yAlias, + axisLabel: { + formatter: function (value, index) { + if (value < 0.01) { + if(value<0.001){ + return value?value.toFixed(4):0; + } + return value.toFixed(3); + } + return value.toFixed(2); + } + } + + } + //toolbox 设置导出图片 + eOption.toolbox = { + feature: { + saveAsImage: {} + } + } + //缩放 + eOption.dataZoom = [ + { + show: true, + realtime: true, + start: 0, + end: 100 + }, + { + type: 'inside', + realtime: true, + start: 0, + end: 100 + } + ] + //颜色标识 + eOption.legend = { + left: '0', + orient: 'vertical', + type: 'scroll', + icon: 'circle', + //文字过长可以使用下面方法截取字符串 + formatter: function (name) { + if (!name) return ''; + if (name.length > 8) { + if (name.length > 16) { + name = name.slice(0, 8) + `\n` + name.slice(0, 7) + "..."; + } else { + name = name.slice(0, 8) + `\n` + name.slice(8); + } + return name; + } else { + return name + } + }, + tooltip: { + show: true + } + } + // //数据源 + // eOption.dataset = { + // source: dataset + // } + //props.data; + eOption.series = dataArray + this.setState({ option: eOption }); + + //this.chartDownload = chart; + } + + render() { + const { data, width, height, options, slider } = this.props; + const showSlider = options && options.sliderStart; + return ( +
+ {/*
+ +
*/} +
+ {/*
*/} + + {/*
*/} + {/* { + showSlider ? [ +
+ ] : null + } */} +
+
+ ); + } +} +export default LineTimeChart; \ No newline at end of file diff --git a/web/client/src/sections/data/containers/dataComponent.jsx b/web/client/src/sections/data/containers/dataComponent.jsx new file mode 100644 index 0000000..0e97599 --- /dev/null +++ b/web/client/src/sections/data/containers/dataComponent.jsx @@ -0,0 +1,122 @@ +'use strict' + + +import React from 'react'; +import { connect } from 'react-redux'; +import moment from 'moment'; +import { Form, Button, Row, Col, DatePicker, Cascader, Select, Spin, Banner } from '@douyinfe/semi-ui'; +import LineTimeChart from '../components/lineTimeChartTemplate'; + + +const FormItem = Form.Item; +const { RangePicker } = DatePicker; +const Option = Select.Option; +const createForm = Form.create; + +const DataComponent =(props)=>{ + const {isRequesting,dataList}=props + const {items,sensors}=dataList + + const renderChart = () => { + // const { dataAcquisitionPointList, dataAcquisitionList, isRequesting, dataType, aggregationData } =props; + + if (isRequesting) { + return + } + if (JSON.stringify(dataList) == "{}") { + return + } + let chartContent = []; + let deviceItems ={pitstate: {name: "坑位状态", unit: null}} + let deviceSensors =[{id: "8d59eeef-147f-40e4-9516-768f8270b3fd", name: "女坑1",data:[{values: {pitstate: "1"}, time: "2023-11-06 03:03:58"},{values: {pitstate: "1"}, time: "2022-11-05 03:03:58"}]}]; + for (let deviceItem in items) { + let chartDataD = []; + let startD = ""; + let endD = ""; + for (let d = 0; d < sensors.length; d++) { + let isHas = false; + for (let s = 0; s < sensors[d].data.length; s++) { + let cdataR = {}; + let dataDS = sensors[d].data[s]; + cdataR.time = moment(dataDS.time).format('YYYY-MM-DD HH:mm:ss'); + if (startD == "") { + startD = dataDS.time; + endD = dataDS.time; + } else { + if (moment(startD) >= moment(dataDS.time)) + startD = dataDS.time; + if (moment(endD) <= moment(dataDS.time)) + endD = dataDS.time; + } + cdataR.name = sensors[d].name; + let deviceItemValue = Number(dataDS.values[deviceItem]); + if (!isNaN(deviceItemValue)) { + cdataR.value = deviceItemValue; + chartDataD.push(cdataR); + } + // if (deviceItemValue !== undefined) { + // cdataR.value = typeof deviceItemValue == 'number' ? Number(deviceItemValue) : null; + // chartDataD.push(cdataR); + // } + } + } + chartDataD.reverse(); + if (chartDataD.length > 0) { + const optionsD = { + "xArray": [], + "yAlias": deviceItems[deviceItem].name + '(' + deviceItems[deviceItem].unit + ')', + "sliderStart": moment(startD).format('YYYY-MM-DD HH:mm:ss'),//默认数据源第一个,时间毫秒数 + "sliderEnd": moment(endD).format('YYYY-MM-DD HH:mm:ss'),//默认数据源最后一个,时间毫秒数 + "mask": "yyyy-mm-dd HH:MM:ss",//时间格式 + } + chartContent.push( + ) + } + } + if (chartContent.length == 0) { + return + } else { + return chartContent.map(p => { + return p; + }) + } + } + + //渲染图表 + return (
+ {renderChart()} +
) + + + + +} + +const mapStateToProps = (state) => { + const { auth, global, dataContinuityType, dataContinuity,pepProjectId,SensorsData} = state; + return { + user: auth.user, + actions: global.actions, + dataContinuityType: dataContinuityType.data || [], + dataContinuity: dataContinuity.data || [], + pepProjectId:global.pepProjectId, + isRequesting:SensorsData.isRequesting + }; +} +export default connect(mapStateToProps)(DataComponent); + + + + + diff --git a/web/client/src/sections/data/containers/dataDetail.jsx b/web/client/src/sections/data/containers/dataDetail.jsx new file mode 100644 index 0000000..62dc2e7 --- /dev/null +++ b/web/client/src/sections/data/containers/dataDetail.jsx @@ -0,0 +1,284 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { connect } from 'react-redux'; +import moment from 'moment'; +import { exportWord } from 'mhtml-to-word' +import { saveAs } from 'file-saver'; +import { SkeletonScreen, } from "$components"; +import DataTableComponent from './dataTableComponent'; +import DataComponent from './dataComponent.jsx'; +import { IconSearch,IconLineChartStroked,IconStar,IconUser,IconBookOpenStroked} from '@douyinfe/semi-icons'; +import { Divider,Form, Button, Skeleton, Table, Pagination, Space, Row, Col, DatePicker, Cascader, Select, Spin, Alert, message, Menu, Icon,Nav,Tabs, TabPane} from '@douyinfe/semi-ui'; +import '../style.less' + +const DataDetail = (props) => { + const { dispatch, actions, user, dataContinuityType, dataContinuity,pepProjectId } = props + const { data } = actions + const [queryPage, setQueryPage] = useState({ limit: 10, page: 0 }); //页码信息 + const [limits, setLimits] = useState(0)//每页实际条数 + const [loading, setLoading] = useState(true); + const [params, setParams] = useState({}) + const [checkVis, setCheckVis] = useState(false) + const [checkData, setCheckData] = useState({}) + const [structList,setStructList]=useState([])//结构物筛选列表 + const [structId,setStructId]=useState(null)//结构物id(根据结构物下拉框赋值) + const [factosList,setFactorsList]=useState([])//监测因素列表 + const [factorId,setFactorId]=useState(null) + const [sensorList,setSensorList]=useState([])//设备列表 + const [sensorId,setSensorId]=useState([])//设备数组id + const [project,setProject]=useState(null)//project(eg:{projcet:'nbjj'}) + const form = useRef(); + //初始化 + useEffect(() => { + if(factorId&&project){ + let query={ + factorId, + sensorId:sensorId&&sensorId.length>0?sensorId.map(item=>`${project}-${item}`).join(','):[-11], + startTime:form.current.getValue('createTimes')[0], + endTime:form.current.getValue('createTimes')[1], + limit:queryPage.limit, + page:queryPage.page + } + queryData(query) + } + + // dispatch(sectionData.getContinuityType()) + // form.current.setValue('createTimes',[moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'), moment().format('YYYY-MM-DD HH:mm:ss')]) + + }, [factorId,project,sensorId]) + + //监听变化 + useEffect(() => { + setStructList([]) + form.current.reset() + getData() + }, [pepProjectId]) + //监听结构物变化,查询监测因素 + useEffect(()=>{ + setLoading(true); + let queryParams = {structId} + if(structId){ + dispatch(data.getFactors({...queryParams})).then(res => { + if(res.success){ + const list=res.payload.data?.map(item=>{ + return { + value: item.SafetyFactorTypeId, + label: item.SafetyFactorTypeName, + } + }) + form.current.setValue('factor',list[0]?.value) + setFactorsList(list) + setFactorId(list[0]?.value) + setLoading(false); + } + }) + } + + },[structId]) + useEffect(()=>{ + let queryParams = {structId,SafetyFactorTypeId:factorId} + if(factorId){ + dispatch(data.getSensors({...queryParams})).then(res => { + if(res.success){ + const list=res.payload.data?.map(item=>{ + return { + value: item.SensorId, + label: item.SensorLocationDescription, + } + }) + form.current.setValue('point',[list[0]?.value]) + setSensorList(list) + setLoading(false) + setProject(res.payload.data[0].Project) + setSensorId([list[0]?.value]) + } + }) + } + },[factorId]) + const getData = (queryParams = {pomsProjectId:pepProjectId}) => { + setLoading(true); + dispatch(data.getProjectAllStructures({...queryParams})).then(res => + { + if(res.success){ + const uniqueIds = new Set(); + const list=res.payload.data?.filter(item => { + const duplicate = uniqueIds.has(item.strucId); + uniqueIds.add(item.strucId); + return !duplicate + })?.map(item => ({ + value: item.strucId, + label: item.strucName, + project: item.Project, + })) + form.current.setValue('struct',list[0]?.value) + setStructId(list[0]?.value) + setStructList(list) + setLoading(false) + } + } + + ) + } + + const downloadWord = (html = '', name = '数据监测 ' + moment().format('YYYY-MM-DD HH:mm:ss')) => { + exportWord({ + mhtml: html, + filename: name, + }) + // let blob = new Blob([html], { type: "application/msword;charset=utf-8" }); + // saveAs(blob, name + '.doc'); + } + //结构物筛选变化回调函数 + const structChange = (value) => { + setStructId(value) + form.current.setValue('point','') + form.current.setValue('factor','') + + // setStructName(structList.find(item=>item.value===value)?.label) + } + //结构物筛选发生变化回调函数 + const factorChange=(value)=>{ + form.current.setValue('point','') + setFactorId(value) + } + //点位筛选发生改变的回调函数 + const pointChange=(value)=>{ + setSensorId(value) + // setProject(structList.find(item=>item.value===structId)?.project) + } + //查询按钮回调函数 + const queryData=(query)=>{ + dispatch(data.getMonitorData(query)).then(res=>{ + if(res.success){ + setCheckData(res.payload.data) + } + }) + } + const searchHandler=(values) => { + form.current.validate().then(rs=>{ + let query={ + factorId, + sensorId:sensorId&&sensorId.length>0?sensorId.map(item=>`${project}-${item}`).join(','):[-11], + startTime:moment(rs.createTimes[0]).format('YYYY-MM-DD HH:mm:ss'), + endTime:moment(rs.createTimes[1]).format('YYYY-MM-DD HH:mm:ss'), + limit:queryPage.limit, + page:queryPage.page + } + queryData(query) + }) + } + + + return ( + <> +
+
+
+
+
数据详情
+
DATA DETAIL
+
+
+ +
{ + form.current = formApi + }} + labelPosition='left' + layout="horizontal" + style={{ position: "relative", width: "100%", flex: 1 }} + // onSubmit={formSubmit} + > + + + + + + + + + +
+
+ + + + + + + + + +
+
+
+ + + + 趋势图} itemKey="1"> + + + + 数据} itemKey="2"> + + + + + + + +
+ + ) +} + +function mapStateToProps(state) { + const { auth, global, dataContinuityType, dataContinuity,pepProjectId} = state; + return { + user: auth.user, + actions: global.actions, + dataContinuityType: dataContinuityType.data || [], + dataContinuity: dataContinuity.data || [], + pepProjectId:global.pepProjectId + }; +} + +export default connect(mapStateToProps)(DataDetail); diff --git a/web/client/src/sections/data/containers/dataQuery.jsx b/web/client/src/sections/data/containers/dataQuery.jsx index 8dc134d..036a7f0 100644 --- a/web/client/src/sections/data/containers/dataQuery.jsx +++ b/web/client/src/sections/data/containers/dataQuery.jsx @@ -33,7 +33,7 @@ const DataQuery = (props) => { ]) } - const downloadWord = (html = '', name = '数据查询 ' + moment().format('YYYY-MM-DD HH:mm:ss')) => { + const downloadWord = (html = '', name = '数据监测 ' + moment().format('YYYY-MM-DD HH:mm:ss')) => { exportWord({ mhtml: html, filename: name, @@ -48,7 +48,7 @@ const DataQuery = (props) => {
-
数据查询
+
数据监测
DATA QUERY
@@ -73,9 +73,9 @@ const DataQuery = (props) => { /> diff --git a/web/client/src/sections/data/containers/dataTableComponent.jsx b/web/client/src/sections/data/containers/dataTableComponent.jsx new file mode 100644 index 0000000..25cd0df --- /dev/null +++ b/web/client/src/sections/data/containers/dataTableComponent.jsx @@ -0,0 +1,137 @@ +/* + * @Author: zhaobing + * @Date: 2023-11-02 09:32:25 + */ +import React, { useState, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { Table, Button, Alert, Spin } from '@douyinfe/semi-ui'; +// import { ApiTable } from '../../../../utils/webapi'; +import FileSaver from 'file-saver'; +import moment from 'moment'; + +const DataTableComponent = (props) => { + const {dataList}=props + const [currentPage, setCurrentPage] = useState(1); + const [exportingFlag, setExportingFlag] = useState(false); + const [isRequesting, setIsRequesting] = useState(false); + const [arr,setArr]=useState(dataList.items) + const [data,setData]=useState([]) + const [columns,setColumns]=useState([]) + //初始化 + + useEffect(()=>{ + + // let arr=[{readingNumber:{name: '电表示数', unit: 'kWh'}}] + // let dataSource = [{values: {total: 24138.97}, time: "2023-11-03 14:18:27.000"},{values: {total: 24138.97}, time: "2023-11-03 14:18:27.000"}] + let columns = [] + columns.push({ + title: '设备位置', + dataIndex: 'position', + key: 'position', + }); + for (let index in arr) { + columns.push({ + title: arr[index].name + '(' + arr[index].unit + ')', + dataIndex: index, + sorter: (a, b) => b[index] - a[index], + key: index, + }) + }; + columns.push({ + title: '采集时间', + dataIndex: 'acqTime', + sorter: (a, b) => b['realTime'] - a['realTime'], + key: 'acqTime', + }); + //设备 + const data1=[] + for (let i = 0; i < dataList.length; i++) { + for (let k = 0; k < dataList[i].data.length; k++) { + let cdataT={} + let startT = ""; + let endT = ""; + let dataTS = dataList[i].data[k]; + cdataT.key = `station-${dataList[i].id}-${k}`; + cdataT.position = dataList[i].name; + cdataT.acqTime = moment(dataTS.time).format('YYYY-MM-DD HH:mm:ss'); + cdataT.realTime = moment(dataTS.time).valueOf(); + if (startT == "") { + startT = dataTS.time; + endT = dataTS.time; + } else { + if (moment(startT) >= moment(dataTS.time)) + startT = dataTS.time; + if (moment(endT) <= moment(dataTS.time)) + endT = dataTS.time; + } + //动态列的值 + for (let themeItem in arr) { + cdataT[themeItem] = dataTS.values[themeItem]; + } + data1.push(cdataT) + } + } + setData(data1) + setColumns(columns) + },[]) + + + + + + + + const exporting = () => { + setIsRequesting(true); + }; + + const pageChange = (page, pageSize) => { + setCurrentPage(page); + }; + + const exportCsv = (data, title, filename) => { + setExportingFlag(true); + }; + + const exportVibrationCsv = (data, title, filename) => { + setExportingFlag(true); + }; + + const renderViberationChart = () => { + }; + + useEffect(() => { + setCurrentPage(1); + }, [props]); + + + + + return ( +
+ +
+ {/*

+ { + data.length ? : null + } +

*/} + + + + + ); +}; + +const mapStateToProps = (state) => { + const { auth, global, dataContinuityType, dataContinuity,pepProjectId} = state; + return { + user: auth.user, + actions: global.actions, + dataContinuityType: dataContinuityType.data || [], + dataContinuity: dataContinuity.data || [], + pepProjectId:global.pepProjectId + }; +}; + +export default connect(mapStateToProps)(DataTableComponent); \ No newline at end of file diff --git a/web/client/src/sections/data/containers/index.js b/web/client/src/sections/data/containers/index.js index 4c93e75..51d84bf 100644 --- a/web/client/src/sections/data/containers/index.js +++ b/web/client/src/sections/data/containers/index.js @@ -3,4 +3,5 @@ import DataQuery from './dataQuery'; import DataComparison from './dataComparison'; import DataAssociation from './dataAssociation'; import Notebook from './notebook'; -export { DataQuery, DataComparison, DataAssociation,Notebook}; \ No newline at end of file +import DataDetail from './dataDetail'; +export { DataQuery, DataComparison, DataAssociation,Notebook,DataDetail}; \ No newline at end of file diff --git a/web/client/src/sections/data/nav-item.jsx b/web/client/src/sections/data/nav-item.jsx index 0a5118c..82e325d 100644 --- a/web/client/src/sections/data/nav-item.jsx +++ b/web/client/src/sections/data/nav-item.jsx @@ -1,3 +1,4 @@ + import React from 'react'; import { IconCode } from '@douyinfe/semi-icons'; @@ -15,7 +16,9 @@ export function getNavItem (user, dispatch) { icon: , to: '/data/dataMonitoring/dataQuery', items: [{ - itemKey: 'dataQuery', to: '/data/dataMonitoring/dataQuery', text: '数据查询' + itemKey: 'dataQuery', to: '/data/dataMonitoring/dataQuery', text: '数据监测' + },{ + itemKey: 'dataDetail', to: '/data/dataMonitoring/dataDetail', text: '数据详情' }] }, { itemKey: 'dataAnalysis', diff --git a/web/client/src/sections/data/routes.js b/web/client/src/sections/data/routes.js index b497cdd..a93c65d 100644 --- a/web/client/src/sections/data/routes.js +++ b/web/client/src/sections/data/routes.js @@ -1,4 +1,5 @@ -import { DataQuery, DataComparison, DataAssociation, Notebook } from './containers'; + +import { DataQuery, DataComparison, DataAssociation, Notebook,DataDetail,DataTableComponent } from './containers'; export default [{ type: 'inner', @@ -15,7 +16,13 @@ export default [{ path: '/dataQuery', key: 'dataQuery', component: DataQuery, - breadcrumb: '数据查询', + breadcrumb: '数据监测', + },{ + path: '/dataDetail', + key: 'dataDetail', + component: DataDetail, + breadcrumb: '数据详情', + }] }, { path: '/dataAnalysis', diff --git a/web/client/src/sections/data/style.less b/web/client/src/sections/data/style.less index e69de29..5604f9f 100644 --- a/web/client/src/sections/data/style.less +++ b/web/client/src/sections/data/style.less @@ -0,0 +1,10 @@ +.semi-form-horizontal{ + display: block; +} +.fontStyle{ + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; +} +.semi-col-12 { + display: flex; + +} \ No newline at end of file diff --git a/web/client/src/sections/install/actions/dataCacl.js b/web/client/src/sections/install/actions/dataCacl.js new file mode 100644 index 0000000..f4aba77 --- /dev/null +++ b/web/client/src/sections/install/actions/dataCacl.js @@ -0,0 +1,117 @@ + +'use strict'; + +import { ApiTable, basicAction } from '$utils' + +export function getAbnMethods (query) {//获取异常识别算法 + return (dispatch) => basicAction({ + type: "get", + dispatch: dispatch, + actionType: "GET_ABN_METHODS", + query: query, + url: `${ApiTable.getAbnMethods}`, + msg: { option: "获取异常识别算法" }, + reducer: { name: "abnMethods", params: { noClear: true } }, + }); +} + + +export function getAbnParamList (query) {//获取异常参数配置 + return (dispatch) => basicAction({ + type: "get", + dispatch: dispatch, + actionType: "GET_ABN_PARAM_LIST", + query: query, + url: `${ApiTable.getAbnParamList}`, + msg: { option: "获取异常参数配置" }, + reducer: { name: "abnParam", params: { noClear: true } }, + }); +} + +export function addAbnParam (data) {//新增异常参数配置 + return (dispatch) => basicAction({ + type: "post", + dispatch: dispatch, + actionType: "ADD_ABN_PARAM", + data, + url: `${ApiTable.addAbnParamList}`, + msg: { option: "新增异常参数配置" }, + + }) +} + +export function batchCfgAbnParams (ids, enable) {//批量启用or禁用 + return (dispatch) => basicAction({ + type: "put", + dispatch: dispatch, + actionType: "BATCH_CFG_ABNPARAMS", + data:enable, + url: `${ApiTable.batchSwitch.replace("{ids}", ids)}`, + msg: { option: enable?.enabled?"批量启用异常参数配置" : "批量禁用异常参数配置" }, + + }) +} + +export function deleteAbnParams (id) {//删除配置 + return (dispatch) => basicAction({ + type: "delete", + dispatch: dispatch, + actionType: "DELETE_ABN_PARAMS", + url: `${ApiTable.deleteAbnParams.replace("{id}", id)}`, + msg: { option: '删除异常参数配置' }, + + }); +} + +export function editAbnParams (id, pushData) {//编辑配置 + return (dispatch) => basicAction({ + type: "put", + data:pushData, + dispatch: dispatch, + actionType: "EDIT_ABN_PARAMS", + url: `${ApiTable.editAbnParams.replace("{id}", id)}`, + msg: { option: '修改异常参数配置' }, + + }) +} + +//中断数据对比 +export function getItemAbnResult_int (structId, start, end, pushData) { + return (dispatch) => basicAction({ + type: "post", + data:pushData, + dispatch: dispatch, + actionType: "GET_ITEM_ABN_RESULT_INT", + url: `${ApiTable.getAbnTaskResult.replace("{structId}", structId).replace("{start}", start).replace("{end}", end)}`, + msg: { option: '中断数据对比' }, + reducer: { name: "abnItemState_int", params: { noClear: true } }, + }) +} + + +//异常趋势数据对比 +export function getItemAbnResult_tr (structId, start, end, pushData) {// + return (dispatch) => basicAction({ + type: "post", + data:pushData, + dispatch: dispatch, + actionType: "GET_ITEM_ABN_RESULT_TR", + url: `${ApiTable.getAbnTaskResult.replace("{structId}", structId).replace("{start}", start).replace("{end}", end)}`, + msg: { option: '异常趋势数据对比' }, + reducer: { name: "abnItemState_tr", params: { noClear: true } }, + }) +} + + +//毛刺数据对比 +export function getItemAbnResult_burr (structId, start, end, pushData) {// + return (dispatch) => basicAction({ + type: "post", + data:pushData, + dispatch: dispatch, + actionType: "GET_ITEM_ABN_RESULT_BURR", + url: `${ApiTable.getAbnTaskResult.replace("{structId}", structId).replace("{start}", start).replace("{end}", end)}`, + msg: { option: '毛刺数据对比' }, + reducer: { name: "abnItemState_burr", params: { noClear: true } }, + }) +} diff --git a/web/client/src/sections/install/actions/index.js b/web/client/src/sections/install/actions/index.js index 93e6a8a..4cacf08 100644 --- a/web/client/src/sections/install/actions/index.js +++ b/web/client/src/sections/install/actions/index.js @@ -2,7 +2,9 @@ import * as roles from './roles' import * as system from './system' +import * as dataCacl from './dataCacl' export default { ...roles, - ...system + ...system, + ...dataCacl } \ No newline at end of file diff --git a/web/client/src/sections/install/components/TimeAbnValueLineChart.jsx b/web/client/src/sections/install/components/TimeAbnValueLineChart.jsx new file mode 100644 index 0000000..b717c5d --- /dev/null +++ b/web/client/src/sections/install/components/TimeAbnValueLineChart.jsx @@ -0,0 +1,168 @@ +import React, { Component } from 'react'; +import createG2 from 'g2-react'; + +import G2, { Plugin } from 'g2'; +//import G2 from '@antv/g2' +//import DataSet from '@antv/data-set'; + +import 'g2-plugin-slider'; +//import Slider from '@antv/g2-plugin-slider'; + +import './theme'; +import { Button } from '@douyinfe/semi-ui'; +import FileSaver from 'file-saver'; +/** + * props: + * + * data: [{ + * name: 图例名称, + * value: y轴数值, + * time: x轴时间值 + * }], // 数组或封装后的frame + * height: 图表高度, + * configs: { + * slider: { + * start: 初始开始点, + * end: 初始结束点 + * } + * }, + * yAxis: 修改默认y轴值对应的键 + */ + +let chartDownload = ""; +class TimeAbnValueLineChart extends Component { + constructor(props) { + super(props); + + this.sliderId = 'range' + Math.floor(Math.random() * 1000000000); + const Chart = createG2((chart, configs) => { + chart.source(props.data, { + 'value': { + formatter: val => { + if (configs.unit) { + return val + configs.unit + } else { + return val + } + } + }, + 'time': { + range: [0, 1], + type: 'time', + alias: '时间', + mask: 'yyyy-mm-dd HH:MM:ss' + }, + 'days': { + type: 'linear', + alias: '', + formatter: val => { + return val + '天' + } + } + }); + chart.axis(props.xAxis || 'time', { + title: null, + line: { + //stroke: '#C0C0C0', + }, label: { + textStyle: { + //fill: '#C0C0C0', // 文本的颜色 + } + } + //mask: 'yyyy-mm-dd HH:MM:ss' + }); + chart.axis(props.yAxis || 'value', { + title: null, + }); + chart.legend({ + title: null, + position: 'right', // 设置图例的显示位置 + itemWrap: true, + //dx: -30, + height: 120, + }); + chart.tooltip(true, { + map: { + + }, + }); + chart.on('tooltipchange', function (ev) { + //let items = ev.items; + + }); + chart.line().position(`${props.xAxis || 'time'}*${props.yAxis || 'value'}`).color('name').shape('line').size(2); + // chart.point().position(`${props.xAxis || 'time'}*${props.yAxis || 'value'}`).color('name', (name) => name != this.props.itemName).size(4).shape('circle').style({ + // stroke: '#fff', + // lineWidth: 1 + // }); + if (this.props.contentType != 'trend') { + chart.point().position(`${props.xAxis || 'time'}*${props.yAxis || 'value'}`).color('name').size(4).shape('circle').style({ + stroke: '#fff', + lineWidth: 1 + }); + } + + if (!configs || !configs.slider) { + chart.render(); + } else { + // 创建滑动条 + //Plugin.slider.ATTRS.textAttr = { fill: '#fff' }; + var slider = new Plugin.slider({ + domId: this.sliderId, + height: 30, + charts: chart, + xDim: props.xAxis || 'time', + yDim: props.yAxis || 'value', + start: configs.slider.start, // 滑块开始值,用户未定义则使用默认值 + end: configs.slider.end // 滑块结束值,用户未定义则使用默认值 + }); + slider.render(); + } + this.chartDownload = chart; + }); + this.Chart = Chart; + } + download = () => { + //this.chartDownload.downloadImage(); + const dataurl = this.chartDownload.toImage(); + let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], + bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + let blob = new Blob([u8arr], { type: mime }); + + FileSaver.saveAs(blob, "chart.png"); + } + + render() { + const { unit, data, width, height, configs, xAxis } = this.props; + + let margin = [20, 350, 30, 60]; + const showSlider = configs && configs.slider; + if (showSlider) { + //margin = [20, 145, 30, 138]; + margin = [20, 335, 80, 138]; + } + return ( +
+
+ +
+
+ + { + showSlider ? [ +
, +
+ ] : null + } +
+
+ ); + } +} + +export default TimeAbnValueLineChart; \ No newline at end of file diff --git a/web/client/src/sections/install/components/TrendModal.jsx b/web/client/src/sections/install/components/TrendModal.jsx new file mode 100644 index 0000000..6bda8f3 --- /dev/null +++ b/web/client/src/sections/install/components/TrendModal.jsx @@ -0,0 +1,288 @@ + +import React, { useState, useEffect,useRef } from "react"; +import { connect } from "react-redux"; +import moment from 'moment'; +import { Form, Tooltip, Select, Checkbox, Modal, Input, Button, Switch, DatePicker, Row } from "@douyinfe/semi-ui"; +import {IconLineChartStroked} from "@douyinfe/semi-icons" +import { TimeAbnValueLineChart } from '../components/TimeAbnValueLineChart'; +import PropTypes from 'prop-types'; + + + + + +const TrendModal = (props) => { + const { modalData,dispatch,actions,sensorId, closeModal, visible,project } = props + const{install}=actions + const [currStartTime, setCurrStartTime] = useState(moment().add('days', -1).format('YYYY-MM-DD HH:mm:ss')) + const [currEndTime, setcurrEndTime] = useState(moment().format('YYYY-MM-DD HH:mm:ss')) + const form = useRef() + //初始化 + useEffect(() => { + }, []) + const checkPoint = (rule, value) => { + const pattern = /^[1-9]*[1-9][0-9]*$/; + if (pattern.test(value) && value != 1) { + return true + } + return false + } + const checkNumber = (rule, val) => { + const strRegex = /^-?\d+(\.\d+)?$/ + if (strRegex.test(val)) { + return true + } + return false + + + + } + const btnFormSubmit = () => { + let data = modalData + form.current.validate().then(value=>{ + let paramJson = { + "thr_burr": Number(value.burr),//毛刺阈值 + "win_med": Number(value.ws),//滑动中值 + "win_avg": Number(value.rc),//滑动均值 + "thr_der": Number(value.dv), //导数阈值 + "win_grad": Number(value.pn),//渐变点个数 + "thr_grad": Number(value.gv),//渐变阈值 + "days_Last": Number(value.timeRange),//分析时长 + } + let pushData = { + station: data.sensorId,//测点 + factorId: data.factorId, + itemId: data.itemIndex, + abnType: 3,//异常趋势 + enabled: value.checked, + params: paramJson + }; + dispatch(install.editAbnParams(data.id,pushData)).then(res => { + if (res.success) { + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id})) + closeModal() + } + }) + }) + } + const checkInterger = (rule, val) => { + // if(val){ + const strRegex = /^[1-9]*[1-9][0-9]*$/ + if (strRegex.test(val)) { + return true + } + return false + // } + + }; + const timeOnChange = (dates, dateStrings) => { + if (dates.length == 0) { + setCurrStartTime(null) + setCurrEndTime(null) + return Notification.warning({ + content: `请选择时间`, + duration: 3, + }) + } + setCurrStartTime(dates[0].format('YYYY-MM-DD HH:mm:ss')) + setCurrEndTime(dates[1].format('YYYY-MM-DD HH:mm:ss')) + } + //数据对比 + // const dataCompare = () => { + // this.props.form.validateFields((err, values) => { + // if (!err) { + // let paramJson = { + // "thr_burr": values.burr,//毛刺阈值 + // "win_med": values.ws,//滑动中值 + // "win_avg": values.rc,//滑动均值 + // "thr_der": values.dv, //导数阈值 + // "win_grad": values.pn,//渐变点个数 + // "thr_grad": values.gv,//渐变阈值 + // "days_Last": values.timeRange,//分析时长 + // } + // let data = { + // station: this.props.modalData.stationId, + // factorId: this.props.modalData.factorId, + // itemId: this.props.modalData.itemId, + // abnType: 'trend',//算法类型 + // enabled: true, + // params: paramJson + // }; + // let start = this.state.currStartTime; + // let end = this.state.currEndTime; + // this.props.dispatch(getItemAbnResult_tr(this.props.structId, start, end, data)).then(res => { + // }) + // } + // }); + // } + + // let originalData = ['还没查询'], calcArray = [], key, itemName, start, end; + // let stationsData = []; + // if (abnItemCompData) { + // originalData = abnItemCompData.stationData; + // calcArray = abnItemCompData.resultArray; + // key = abnItemCompData.itemKey; + // itemName = abnItemCompData.itemName; + // start = originalData && originalData.length > 0 ? originalData[0].time : ''; + // end = originalData && originalData.length > 0 ? originalData[originalData.length - 1].time : ''; + + // for (let i = 0; i < originalData.length; i++) { + // let one = { name: itemName + "(单位:" + abnItemCompData.unit + ")", value: originalData[i][key], time: originalData[i].time }; + // stationsData.push(one); + // } + // if (calcArray) { + // let preLineData = calcArray.calcPreprocess; + // let abnTrends = calcArray.calcFinal; + // for (let j = 0; j < preLineData.length; j++) { + // let one = { name: "预处理+滑动均值后数据", value: preLineData[j].value, time: preLineData[j].time }; + // stationsData.push(one); + // } + // for (let t = 0; t < abnTrends.length; t++) { + // let name = abnTrends[t].startTime + "至" + abnTrends[t].endTime + abnTrends[t].des + ",渐变差值:" + abnTrends[t].value.toFixed(2) + abnItemCompData.unit; + // let start = { name: name, value: abnTrends[t].startValue, time: abnTrends[t].startTime }; + // let end = { name: name, value: abnTrends[t].endValue, time: abnTrends[t].endTime }; + // stationsData.push(start); + // stationsData.push(end); + // } + // } + // } + TrendModal.propTypes = { + visible: PropTypes.bool.isRequired, + closeModal: PropTypes.func.isRequired + }; + + return
+ + + +
]}> + { + form.current = formApi + }} labelPosition='left' + + > + + + + + + + + + + 1个月 + 2个月 + 3个月 + + + + + + + + + + + + + + + + + + + {/* */} + + + {/*
+ {originalData && originalData[0] == '还没查询' ? +
输入参数,点击数据对比展示数据对比图
+ : + originalData && originalData[0] != '还没查询' && originalData.length > 0 ? + + : +
没有查询到任何有效数据!
} +
*/} + + + + ; +} + +function mapStateToProps(state) { + const { abnItemState_tr, global} = state; + return { + abnItemCompData: abnItemState_tr.items, + actions:global.actions + } +} + +export default connect(mapStateToProps)(TrendModal) diff --git a/web/client/src/sections/install/components/burrModal.jsx b/web/client/src/sections/install/components/burrModal.jsx new file mode 100644 index 0000000..bab3140 --- /dev/null +++ b/web/client/src/sections/install/components/burrModal.jsx @@ -0,0 +1,214 @@ +import React, { useRef,useState,useEffect } from "react"; +import { connect } from "react-redux"; +import moment from 'moment'; +import { Form, Tooltip, Select, Checkbox, Modal, Input, Button, Switch, DatePicker, Row, IconLineChartStroked } from "@douyinfe/semi-ui"; +import {IconInfoCircle} from "@douyinfe/semi-icons" +import { TimeAbnValueLineChart } from '../components/TimeAbnValueLineChart'; +import PropTypes from 'prop-types'; + +const BurrModal = (props) => { + const form = useRef() + const {dispatch, modalData, closeModal, visible, structId,item,actions,sensorId,project } = props + const [currStartTime, setCurrStartTime] = useState(moment().add('days', -1).format('YYYY-MM-DD HH:mm:ss')) + const [currEndTime, setcurrEndTime] = useState(moment().format('YYYY-MM-DD HH:mm:ss')) + const{install}=actions + useEffect(()=>{ + // form.current.setValues({stationName: modalData.sensorName, + // factorName: modalData.factorName + "/" + modalData.item, + // burr: modalData.params.thr_burr, + // isEnable: modalData.enabled}) + },[]) + //表单类 + const btnFormSubmit = () => { + let data = modalData; + form.current.validate().then(value=>{ + let paramJson = { "thr_burr": Number(value.burr) }; + let pushData = { + station: data.sensorId,//测点 + factorId: data.factorId, + itemId: data.itemIndex, + abnType: 2,//毛刺 + enabled: value.checked, + params: paramJson + } + dispatch(install.editAbnParams(data.id,pushData)).then(res => { + if (res.success) { + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id})) + closeModal() + } + }) + // this.props.dispatch(editAbnParams(data.id, pushData)).then(_ => { + // this.props.dispatch(getAbnParamList(this.props.structId)).then(() => { + // this.props.closeModal(); + // }); + // }); + + }) + } +const checkInterger = (rule, value, callback) => { + if (!value) { + callback(); + } else { + const pattern = /^[1-9]*[1-9][0-9]*$/; + if (pattern.test(value)) { + callback(); + } else { + callback(new Error('请输入正整数')); + } + } + }; +const timeOnChange = (dates, dateStrings) => { + if (dates.length == 0) { + setCurrStartTime(null) + setcurrEndTime(null) + return message.warning('请选择时间'); + } + setCurrStartTime(dates[0].format('YYYY-MM-DD HH:mm:ss')) + setcurrEndTime(dates[1].format('YYYY-MM-DD HH:mm:ss')) + } + //数据对比 +// const dataCompare = () => { +// form.current.validate().then(res=>{ +// if (res.success) { +// let paramJson = { "thr_burr": values.burr } +// let data = { +// station: modalData.stationId, +// factorId: modalData.factorId, +// itemId: modalData.itemId, +// abnType: 'burr',//算法类型 +// enabled: true, +// params: paramJson +// }; +// let start = this.state.currStartTime +// let end = this.state.currEndTime + +// // this.props.dispatch(getItemAbnResult_burr(this.props.structId, start, end, data)).then(res => { +// // }) +// } +// }) +// } +const checkNumber = (rule, val) => { + const strRegex = /^-?\d+(\.\d+)?$/ + if (strRegex.test(val)) { + return true + } + return false + + + +} + // let originalData = ['还没查询'], calcArray = [], key, itemName, start, end; + // let stationsData = []; + // if (abnItemCompData) { + // originalData = abnItemCompData.stationData; + // calcArray = abnItemCompData.resultArray; + // key = abnItemCompData.itemKey; + // itemName = abnItemCompData.itemName; + // start = originalData && originalData.length > 0 ? originalData[0].time : ''; + // end = originalData && originalData.length > 0 ? originalData[originalData.length - 1].time : ''; + // for (let i = 0; i < originalData.length; i++) { + // let one = { name: itemName + "(单位:" + abnItemCompData.unit + ")", value: originalData[i][key], time: originalData[i].time }; + // stationsData.push(one); + // } + // for (let j = 0; j < calcArray.length; j++) { + // let one = { + // name: "毛刺点:" + calcArray[j].time + "(突变大小:" + calcArray[j].burr.toFixed(2) + abnItemCompData.unit + ")" + // , value: calcArray[j].value, time: calcArray[j].time + // }; + // stationsData.push(one); + // } + // } + BurrModal.propTypes = { + visible: PropTypes.bool.isRequired, + closeModal: PropTypes.func.isRequired + } + return (<> + + + + ]}> +
{ form.current = formApi}}> + + + + + + + + + + + + + + {/* */} + + + {/*
+ {originalData && originalData[0] == '还没查询' ? +
输入参数,点击数据对比展示数据对比图
+ : + originalData && originalData[0] != '还没查询' && originalData.length > 0 ? + + : +
没有查询到任何有效数据!
} +
*/} + + +
+ + ) + +} + + + + + + + +function mapStateToProps(state) { + const { abnItemState_burr,global } = state; + return { + abnItemCompData: abnItemState_burr.items, + actions:global.actions, + } +} + +export default connect(mapStateToProps)(BurrModal) diff --git a/web/client/src/sections/install/components/interruptModal.jsx b/web/client/src/sections/install/components/interruptModal.jsx new file mode 100644 index 0000000..775c25b --- /dev/null +++ b/web/client/src/sections/install/components/interruptModal.jsx @@ -0,0 +1,187 @@ +import React, { useRef,useState,useEffect } from "react"; +import { connect } from "react-redux"; +import moment from 'moment'; +import { Form, Tooltip, Select, Checkbox, Modal, Input, Button, Switch, DatePicker, Row } from "@douyinfe/semi-ui"; +import { IconLineChartStroked } from "@douyinfe/semi-icons" +import { TimeAbnValueLineChart } from '../components/TimeAbnValueLineChart' +import PropTypes from 'prop-types'; + +const InterruptModal = (props) => { + const form = useRef() + const {modalData,actions,dispatch, closeModal, visible, structId,item,sensorId,project}=props + const {install}=actions + const [currStartTime, setCurrStartTime] = useState(moment().add('days', -1).format('YYYY-MM-DD HH:mm:ss')) + const [currEndTime, setcurrEndTime] = useState(moment().format('YYYY-MM-DD HH:mm:ss')) + + + //表单类 + const btnFormSubmit = () => { + let data = modalData + form.current.validate().then(value=>{ + let paramJson = { "thr_int": Number(value.thr_int) } + let pushData = { + station: data.sensorId,//测点 + factorId: data.factorId, + itemId: null, + abnType: 1,//中断 + enabled: value.checked, + params: paramJson + } + dispatch(install.editAbnParams(data.id,pushData)).then(res => { + if (res.success) { + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id})) + closeModal() + } + }) + // this.props.dispatch(editAbnParams(data.id, pushData)).then(_ => { + // this.props.dispatch(getAbnParamList(this.props.structId)).then(() => { + // this.props.closeModal(); + // }); + // }); + + }) + // this.props.form.validateFieldsAndScroll((err, values) => { + + // }); + } + + + const checkInterger = (rule, val) => { + // if(val){ + const strRegex = /^[1-9]*[1-9][0-9]*$/ + if (strRegex.test(val)) { + return true + } + return false + // } + +}; + const timeOnChange = (dates, dateStrings) => { + if (dates.length == 0) { + setCurrStartTime(null) + setcurrEndTime(null) + return message.warning('请选择时间'); + } + setCurrStartTime(dates[0].format('YYYY-MM-DD HH:mm:ss')) + setcurrEndTime(dates[1].format('YYYY-MM-DD HH:mm:ss')) + } + //数据对比 + // const dataCompare = () => { + // form.current.validate().then(_=>{ + // let paramJson = { "thr_int": values.interrupt } + // let data = { + // station: modalData.stationId, + // factorId: modalData.factorId, + // itemId: modalData.itemId, + // abnType: 'interrupt',//算法类型 + // enabled: true, + // params: paramJson + // }; + // let start = this.state.currStartTime; + // let end = this.state.currEndTime; + // this.props.dispatch(getItemAbnResult_int(this.props.structId, start, end, data)).then(res => { + // }) + // }) + // } + // let originalData = ['还没查询'], calcArray = [], key, itemName, start, end; + // let stationsData = []; + // if (abnItemCompData) { + // originalData = abnItemCompData.stationData; + // calcArray = abnItemCompData.resultArray; + // key = abnItemCompData.itemKey; + // itemName = abnItemCompData.itemName; + // start = originalData && originalData.length > 0 ? originalData[0].time : ''; + // end = originalData && originalData.length > 0 ? originalData[originalData.length - 1].time : ''; + // for (let i = 0; i < originalData.length; i++) { + // let one = { name: itemName + "(单位:" + abnItemCompData.unit + ")", value: originalData[i][key], time: originalData[i].time }; + // stationsData.push(one); + // } + // for (let j = 0; j < calcArray.length; j++) { + // let one = { name: "中断点:" + calcArray[j].time + "(中断时长:" + calcArray[j].hour.toFixed(2) + "h)", value: calcArray[j].value, time: calcArray[j].time }; + // stationsData.push(one); + // } + // } + +// InterruptModal.propTypes = { +// visible: PropTypes.bool.isRequired, +// closeModal: PropTypes.func.isRequired +// } + return
+ + + +
]}> + { + form.current = formApi + }}> + + + + + + + + + + + + {/* */} + + {/* */} + + {/*
+ {originalData && originalData[0] == '还没查询' ? +
输入参数,点击数据对比展示数据对比图(中断仅展示第一个监测项)
+ : + originalData && originalData[0] != '还没查询' && originalData.length > 0 ? + + : +
没有查询到任何有效数据!
} +
*/} +
+ + + + +} + + +function mapStateToProps(state) { + const { global } = state; + return { + // abnItemCompData: abnItemState_int.items, + // abnItemCompData:[], + actions:global.actions + } +} + +export default connect(mapStateToProps)(InterruptModal) diff --git a/web/client/src/sections/install/components/theme.jsx b/web/client/src/sections/install/components/theme.jsx new file mode 100644 index 0000000..0298e64 --- /dev/null +++ b/web/client/src/sections/install/components/theme.jsx @@ -0,0 +1,474 @@ +import G2 from 'g2'; + +const DEFAULT_COLOR = '#ffea00'; +const FONT_FAMILY = '"Microsoft YaHei", "微软雅黑", "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", SimSun, "sans-serif"'; +const theme = { + defaultColor: DEFAULT_COLOR, // 默认主题色 + plotCfg: { + margin: [20, 80, 60, 80] + }, + facetCfg: { + type: 'rect', + margin: 10, + facetTitle: { + titleOffset: 16, + colDimTitle: { + title: { + fontSize: 14, + textAlign: 'center', + fill: '#999' + } + }, + colTitle: { + title: { + fontSize: 12, + textAlign: 'center', + fill: '#999' + } + }, + rowTitle: { + title: { + fontSize: 12, + textAlign: 'center', + rotate: 90, + fill: '#999' + } + }, + rowDimTitle: { + title: { + fontSize: 12, + textAlign: 'center', + rotate: 90, + fill: '#999' + } + } + } + }, + binWidth: 0.03, // bin 统计的默认值 + fontFamily: FONT_FAMILY, + colors: { + 'default': ['#ffea00', '#0de3ff', '#fe8501', '#00ff36', '#0097ff', '#fe4c9b', '#9bff00', '#bd67ff', '#f6ba33', '#978cff'], + intervalStack: ['#ffea00', '#0de3ff', '#fe8501', '#00ff36', '#0097ff', '#fe4c9b', '#9bff00', '#bd67ff', '#f6ba33', '#978cff'] + }, + shapes: { + point: ['hollowCircle', 'hollowSquare', 'hollowDiamond', 'hollowBowtie', 'hollowTriangle', + 'hollowHexagon', 'cross', 'tick', 'plus', 'hyphen', 'line'], + line: ['line', 'dash', 'dot'], + area: ['area'] + }, + hues: ['red', 'yellow', 'green', 'blue', 'orange', 'purple', 'pink', 'brown', 'white', 'gray', 'black'], + axis: { + top: { + position: 'top', + titleOffset: 30, + title: { + fontSize: 12, + fill: '#999' + }, + labels: { + label: { + fill: '#a2adc2', + fontSize: 12 + } + }, + tickLine: { + lineWidth: 1, + stroke: '#ccc', + value: 5 + } + }, + bottom: { + position: 'bottom', + titleOffset: 45, + labelOffset: 20, + title: { + fontSize: 12, + textAlign: 'center', + fill: '#999' + }, + labels: { + label: { + fill: '#a2adc2', + fontSize: 12 + } + }, + line: { + lineWidth: 1, + stroke: '#ccc' + }, + tickLine: { + lineWidth: 1, + stroke: '#ccc', + value: 5 + } + }, + left: { + position: 'left', + titleOffset: 60, + labelOffset: 13, + title: { + fontSize: 12, + fill: '#999' + }, + labels: { + label: { + fill: '#a2adc2' + } + }, + line: { + lineWidth: 1, + stroke: '#ccc' + }, + tickLine: { + lineWidth: 1, + stroke: '#ccc', + value: 5 + }, + grid: { + line: { + stroke: '#495f7a', + lineWidth: 1, + lineDash: [2, 2] + } + } + }, + right: { + position: 'right', + titleOffset: 60, + labelOffset: 13, + title: { + fontSize: 12, + fill: '#999' + }, + labels: { + label: { + fill: '#a2adc2' + } + }, + line: { + lineWidth: 1, + stroke: '#ccc' + }, + tickLine: { + lineWidth: 1, + stroke: '#ccc', + value: 5 + } + }, + circle: { + labelOffset: 5, + line: { + lineWidth: 1, + stroke: '#ccc' + }, + grid: { + line: { + stroke: '#d9d9d9', + lineWidth: 1, + lineDash: [1, 3] + } + }, + labels: { + label: { + fill: '#a2adc2' + } + } + }, + gauge: { + grid: null, + labelOffset: 5, + tickLine: { + lineWidth: 1, + value: -20, + stroke: '#ccc' + }, + subTick: 5, + labels: { + label: { + fill: '#a2adc2' + } + } + }, + clock: { + grid: null, + labelOffset: 5, + tickLine: { + lineWidth: 1, + value: -20, + stroke: '#C0D0E0' + }, + subTick: 5, + labels: { + label: { + fill: '#a2adc2' + } + } + }, + radius: { + titleOffset: 45, + labels: { + label: { + fill: '#a2adc2' + } + }, + line: { + lineWidth: 1, + stroke: '#ccc' + }, + grid: { + line: { + stroke: '#d9d9d9', + lineWidth: 1, + lineDash: [2, 2] + }, + type: 'circle' + } + }, + helix: { + grid: null, + labels: { + label: null + }, + line: { + lineWidth: 1, + stroke: '#ccc' + }, + tickLine: { + lineWidth: 1, + value: 5, + stroke: '#ccc' + } + } + }, + labels: { + offset: 14, + label: { + fill: '#666', + fontSize: 12 + } + }, + treemapLabels: { + offset: 10, + label: { + fill: '#fff', + fontSize: 14, + textBaseline: 'top', + fontStyle: 'bold' + } + }, + innerLabels: { + label: { + fill: '#fff', + fontSize: 12 + } + }, // 在theta坐标系下的饼图文本内部的样式 + thetaLabels: { + labelLine: { + lineWidth: 1 + }, + labelHeight: 14, + offset: 30 + }, // 在theta坐标系下的饼图文本的样式 + legend: { + right: { + position: 'right', + back: null, + title: { + fill: '#a2adc2' + }, + spacingX: 10, + spacingY: 12, + markerAlign: 'center', + wordSpaceing: 12, + word: { + fill: '#a2adc2' + }, + width: 20, + height: 156 + }, + left: { + position: 'left', + back: null, + title: { + fill: '#a2adc2' + }, + spacingX: 10, + spacingY: 12, + markerAlign: 'center', + wordSpaceing: 12, + word: { + fill: '#a2adc2' + }, + width: 20, + height: 156 + }, + top: { + position: 'top', + title: null, + back: null, + spacingX: 16, + spacingY: 10, + markerAlign: 'center', + wordSpaceing: 12, + word: { + fill: '#a2adc2' + }, + width: 156, + height: 20 + }, + bottom: { + position: 'bottom', + title: null, + back: null, + spacingX: 16, + spacingY: 10, + markerAlign: 'center', + wordSpaceing: 12, + word: { + fill: '#a2adc2' + }, + width: 156, + height: 20 + } + }, + tooltip: { + crosshairs: false, + offset: 15, + crossLine: { + stroke: '#666' + }, + wordSpaceing: 6, + markerCfg: { + symbol: 'circle', + radius: 3 + } + }, + activeShape: { + point: { + radius: 5, + fillOpacity: 0.7 + }, + hollowPoint: { + lineWidth: 2, + radius: 4 + }, + interval: { + fillOpacity: 0.7 + }, + hollowInterval: { + lineWidth: 2 + }, + area: { + fillOpacity: 0.85 + }, + hollowArea: { + lineWidth: 2 + }, + line: { + lineWidth: 2 + }, + polygon: { + fillOpacity: 0.75 + } + }, // 图形激活时,鼠标移动到上面 + shape: { + point: { + lineWidth: 1, + fill: DEFAULT_COLOR, + radius: 4 + }, + hollowPoint: { + fill: '#fff', + lineWidth: 1, + stroke: DEFAULT_COLOR, + radius: 3 + }, + interval: { + lineWidth: 0, + fill: DEFAULT_COLOR, + fillOpacity: 0.85 + }, + pie: { + lineWidth: 1, + stroke: '#fff' + }, + hollowInterval: { + fill: '#fff', + stroke: DEFAULT_COLOR, + fillOpacity: 0, + lineWidth: 1 + }, + area: { + lineWidth: 0, + fill: DEFAULT_COLOR, + fillOpacity: 0.6 + }, + polygon: { + lineWidth: 0, + fill: DEFAULT_COLOR, + fillOpacity: 1 + }, + hollowPolygon: { + fill: '#fff', + stroke: DEFAULT_COLOR, + fillOpacity: 0, + lineWidth: 1 + }, + hollowArea: { + fill: '#fff', + stroke: DEFAULT_COLOR, + fillOpacity: 0, + lineWidth: 1 + }, + line: { + stroke: DEFAULT_COLOR, + lineWidth: 1, + fill: null + } + }, + guide: { + text: { + fill: '#666', + fontSize: 12 + }, + line: { + stroke: DEFAULT_COLOR, + lineDash: [0, 2, 2] + }, + rect: { + lineWidth: 0, + fill: DEFAULT_COLOR, + fillOpacity: 0.1 + }, + tag: { + line: { + stroke: DEFAULT_COLOR, + lineDash: [0, 2, 2] + }, + text: { + fill: '#666', + fontSize: 12, + textAlign: 'center' + }, + rect: { + lineWidth: 0, + fill: DEFAULT_COLOR, + fillOpacity: 0.1 + } + }, + html: { + align: 'cc' + } + }, + tooltipMarker: { + fill: '#fff', + symbol: 'circle', + lineWidth: 2, + stroke: DEFAULT_COLOR, + radius: 4 + } // 提示信息在折线图、区域图上形成点的样式 +}; + +var Theme = G2.Util.mix(true, {}, G2.Theme, theme); + +G2.Global.setTheme(Theme); \ No newline at end of file diff --git a/web/client/src/sections/install/containers/AbnRecognize.jsx b/web/client/src/sections/install/containers/AbnRecognize.jsx new file mode 100644 index 0000000..86b647c --- /dev/null +++ b/web/client/src/sections/install/containers/AbnRecognize.jsx @@ -0,0 +1,106 @@ +'use strict'; + +import React, { } from 'react'; +import { connect } from 'react-redux'; +import { Row,Col, Card,Icon } from '@douyinfe/semi-ui'; +import { IconSearch,IconLineChartStroked,IconStar,IconUser} from '@douyinfe/semi-icons'; +import Trend from './trend'; +import Burr from './Burr'; +import Interrupt from './Interrupt'; + +const AbnRecognize = (props) => { + // const {abnCompareData}=props + const {calcMethod,abnCompareData,factorId,structId,strunctChange,itName,sensorId,project}=props + // let originalData = ['还没查询'], calcArray = [], key, itemName, start, end; + // let stationsData = []; + // if (abnCompareData &&calcMethod == abnCompareData.method) { + // originalData = abnCompareData.stationData; + // calcArray = abnCompareData.resultArray; + // key = abnCompareData.itemKey; + // itemName = abnCompareData.itemName; + // start = originalData && originalData.length > 0 ? originalData[0].time : ''; + // end = originalData && originalData.length > 0 ? originalData[originalData.length - 1].time : ''; + // for (let i = 0; i < originalData.length; i++) { + // let one = { name: itemName + "(单位:" + abnCompareData.unit + ")", value: originalData[i][key], time: originalData[i].time }; + // stationsData.push(one); + // } + // if (calcMethod == 'interrupt') { + // for (let j = 0; j < calcArray.length; j++) { + // let one = { name: "中断点:" + calcArray[j].time + "(中断时长:" + calcArray[j].hour.toFixed(2) + "h)", value: calcArray[j].value, time: calcArray[j].time }; + // stationsData.push(one); + // } + // } else if (calcMethod == 'burr') { + // for (let j = 0; j < calcArray.length; j++) { + // let one = { + // name: "毛刺点:" + calcArray[j].time + "(突变大小:" + calcArray[j].burr.toFixed(2) + abnCompareData.unit + ")", + // value: calcArray[j].value, time: calcArray[j].time + // }; + // stationsData.push(one); + // } + // } else { + // if (calcArray) { + // let preLineData = calcArray.calcPreprocess; + // let abnTrends = calcArray.calcFinal; + // for (let j = 0; j < preLineData.length; j++) { + // let one = { name: "预处理+滑动均值后数据", value: preLineData[j].value, time: preLineData[j].time }; + // stationsData.push(one); + // } + // for (let t = 0; t < abnTrends.length; t++) { + // let name = abnTrends[t].startTime + "至" + abnTrends[t].endTime + abnTrends[t].des + ",渐变差值:" + abnTrends[t].value.toFixed(2) + abnCompareData.unit; + // let start = { name: name, value: abnTrends[t].startValue, time: abnTrends[t].startTime }; + // let end = { name: name, value: abnTrends[t].endValue, time: abnTrends[t].endTime }; + // stationsData.push(start); + // stationsData.push(end); + // } + // } + // } + // } + + +const renderContent=()=> { + const SubContent = { + 'interrupt': , + 'burr': , + 'trend': , + }; + return SubContent[calcMethod]; +} + + + +return (<> +
+ {/* +
+ 数据对比}> +
+
+ {originalData && originalData[0] == '还没查询' ? +
输入参数,点击数据对比展示数据对比图(默认显示选择的第一个传感器)
+ : + originalData && originalData[0] != '还没查询' && originalData.length > 0 ? + + : +
没有查询到任何有效数据!
} +
+
+
+ + */} + {renderContent()} + +) +} + + + +function mapStateToProps(state) { + const { auth, global, OrganizationDeps } = state; + return { + // loading: OrganizationDeps.isRequesting, + // user: auth.user, + // actions: global.actions, + }; +} +export default connect(mapStateToProps)(AbnRecognize) \ No newline at end of file diff --git a/web/client/src/sections/install/containers/burr.jsx b/web/client/src/sections/install/containers/burr.jsx new file mode 100644 index 0000000..6ad9459 --- /dev/null +++ b/web/client/src/sections/install/containers/burr.jsx @@ -0,0 +1,267 @@ +'use strict'; + +import React, { useState,useRef } from 'react'; +import { connect } from 'react-redux'; +import { Row, Col, Button, Input, Table, Card, Switch, Popconfirm, Tag,Notification, Form } from '@douyinfe/semi-ui'; +import BurrModal from '../components/burrModal'; +import { useEffect } from 'react'; + +const Burr = (props) => { + const {factorId,abnParam,structId,dispatch,actions,itName,sensorId,project}=props + const form = useRef() + const [selectedRowKeys, setSelectedRowKeys] = useState([]) + const [filterFunc, setFilterFunc] = useState({}) + const [modalVisible, setModalVisible] = useState(false) + const [bvBatch, setBvBatch] = useState('') + const [modalData, setModalData] = useState(null) + const {install}=actions + const filterSet = { sensorName: null, factorName: null } + + useEffect(()=>{ + },[]) + const onSelectChange = (selectedRowKeys) => { + setSelectedRowKeys(selectedRowKeys); + } + const compareAndEdit = (e, record) => { + setModalVisible(true) + setModalData(record) + } + const modalCancel = e => { + setModalVisible(false) + }; + const removeItem = e => { + dispatch(install.deleteAbnParams(e)).then(res=>{ + if(res.success){ + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id})) + } + }) + }; + const handleInputChange = e => { + if (e != null) { + const value = e + let func = (ep => (s => (s.sensorName).search(ep) > -1))(value); + filterSet.sensorName = func; + func = (ep => (s => (s.factorName).search(ep) > -1))(value); + filterSet.factorName = func; + } else { + filterSet.sensorName = null; + filterSet.factorName = null; + } + setFilterFunc(filterSet) + } + //批量启用or禁用 + const onSwitchChange = (e) => { + if (selectedRowKeys.length != 0) { + let ids = selectedRowKeys.join(','); + let data = { + enabled: e, + use: 'switch' + }; + //批量启用or禁用逻辑 + dispatch(install.batchCfgAbnParams(ids,data)).then(res=>{ + if(res.success){ + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id })) + } + }) + } + } + //批量删除 + const batchDelete = () => { + if (selectedRowKeys.length != 0) { + let ids = selectedRowKeys.join(','); + dispatch(install.deleteAbnParams(ids)).then(res=>{ + if(res.success){ + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id })) + } + }) + + // this.props.dispatch(deleteAbnParams(ids)).then(_ => { + // this.props.dispatch(getAbnParamList(this.props.structId)); + // }); + } else { + Notification.warning({ + content: '您尚未勾选任何参数配置!', + duration: 3, + }) + } + } + //批量保存 + const batchSave = () => { + form.current.validate().then(res=>{ + let dataSource = abnParam.filter(a => a.abnType == 2 && a.factorId == factorId) + if (selectedRowKeys.length != 0) { + let ids = selectedRowKeys.join(','); + let data = { + paramJson: { "thr_burr": bvBatch }, + use: 'notSwitch', + }; + //批量保存逻辑 + dispatch(install.batchCfgAbnParams(ids,data)).then(res=>{ + if(res.success){ + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id })) + } + }) + } else if (dataSource.length != 0) { + Notification.warning({ + content: '您尚未勾选任何参数配置!', + duration: 3, + }) + } + }) + } + const batchBurr = (value) => { + setBvBatch(value) + } + const checkNumber = (rule, val) => { + const strRegex = /^-?\d+(\.\d+)?$/ + if (strRegex.test(val)) { + return true + } + return false + + + + } + let dataSource = abnParam.filter(a => a.abnType == 2 && a.factorId == factorId) + const rowSelection = { + selectedRowKeys: selectedRowKeys, + onChange: onSelectChange, + }; + let filterData = dataSource + let flag = false + let keyArr = [] + let tmpds = [] + let dataPush = [] + Object.keys(filterFunc).forEach(key => { + const filter = filterFunc[key] + filterData = dataSource + if (filter != null) { + flag = true + filterData = filterData.filter(filter) + if (filterData.length > 0) { + filterData.map(s => { + if (keyArr.indexOf(s.id) == -1) { + keyArr.push(s.id) + dataPush.push(s) + } + }) + } + } + }) + tmpds = flag ? dataPush.sort((a, b) => a.id - b.id) : dataSource.sort((a, b) => a.id - b.id) + let columns = [ + { + title: "测点位置", + dataIndex: "sensorName", + key: "sensorName", + width: '20%', + }, + { + title: "监测项", + dataIndex: "factorName", + key: "factorName", + width: '20%', + render: (text, record) => ( + record.factorName + "/" + itName + ), + }, + { + title: "毛刺阈值", + dataIndex: "params", + key: "params", + width: '20%', + render: text => text.thr_burr + }, + { + title: "启用状态", + dataIndex: "enabled", + key: "enabled", + width: '20%', + render: (text, record) => ( + record.enabled ? 已启用 : 已禁用 + ) + }, + { + title: "操作", + key: "action", + width: '20%', + render: (text, record) => ( + + + + {removeItem(record.id) }}> + + + + ), + } + ] + + return ( +
{ + form.current = formApi + }} + labelPosition='left' + + > + +
+ + + + + + + + + batchBurr(e)} placeholder="毛刺阈值:数字"> + + + + + + + + + + + + +
+ {modalVisible ? : ''} + + ) + +} + + + +function mapStateToProps(state) { + const { abnParam ,global} = state; + // let isRequesting = abnParamState?.isRequesting; + + return { + isRequesting: false,//请求状态 + abnParam: abnParam?.data||[], + actions:global.actions + } +} +export default connect(mapStateToProps)(Burr) + diff --git a/web/client/src/sections/install/containers/cacl.jsx b/web/client/src/sections/install/containers/cacl.jsx new file mode 100644 index 0000000..c035f37 --- /dev/null +++ b/web/client/src/sections/install/containers/cacl.jsx @@ -0,0 +1,496 @@ +'use strict' + +import React, { useEffect, useState, useRef } from 'react'; +import { connect } from 'react-redux'; +import { Row, Form, Col, Select, Button, Notification } from '@douyinfe/semi-ui'; +import moment from 'moment'; +import AbnRecognize from './AbnRecognize' +const methodType = { + 'interrupt': 1, + 'burr': 2, + 'trend': 3, +} +const methodDes = { + 'interrupt': "中断", + 'burr': "毛刺", + 'trend': "异常趋势", +} + +const Calc = (props) => { + const { actions, dispatch, pepProjectId, abnMethods, abnParam } = props + const { install, data } = actions + const [structId, setStructId] = useState(null) + const [methodType, setMethodType] = useState('interrupt') + const [loading, setLoading] = useState(false) + const [structList, setStructList] = useState([])//结构物筛选列表 + const [project, setProject] = useState('')//project(eg:{projcet:'nbjj'}) + const [reCalcFactorId, setReCalcFactorId] = useState(null); + const [msg, setMsg] = useState('') + const [factosList, setFactorsList] = useState([])//监测因素列表 + const [factorId, setFactorId] = useState([]) + const [sensorList, setSensorList] = useState([])//设备列表 + const [sensorId, setSensorId] = useState([])//设备id' + const [itemId, setItemId] = useState(null)//监测项索引 + const [itemName, setItemName] = useState('')//检测项name + const form = useRef() + const form2 = useRef() + const method = { + 'interrupt': 1, + 'burr': 2, + 'trend': 3, + } + //初始化 + useEffect(() => { + // getData() + dispatch(install.getAbnMethods()) + }, []) + useEffect(() => { + if(project){ + const id = [...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId: id })) + } + }, [sensorId, factorId,project]) + + //监听变化 + useEffect(() => { + setStructList([]) + form.current.reset() + form2.current.reset() + form2.current.setValue('method', methodType) + getData() + }, [pepProjectId]) + const checkInterger = (rule, val) => { + // if(val){ + const strRegex = /^[1-9]*[1-9][0-9]*$/ + if (strRegex.test(val)) { + return true + } + + return false + // } + + }; + const checkNumber = (rule, val) => { + const strRegex = /^-?\d+(\.\d+)?$/ + if (strRegex.test(val)) { + return true + } + return false + + + + } + const checkPoint = (rule, value) => { + const pattern = /^[1-9]*[1-9][0-9]*$/; + if (pattern.test(value) && value != 1) { + return true + } + return false + } + const getJson = (values) => { + let paramJson; + switch (methodType) { + case 'interrupt': + return paramJson = { "thr_int": Number(values.iv) }; + case 'burr': + return paramJson = { "thr_burr": Number(values.bv1) } + case 'trend': + return paramJson = { + "thr_burr": Number(values.bv2),//毛刺阈值 + "win_med": Number(values.ws),//滑动中值窗口 + "win_avg": Number(values.rc),//滑动均值窗口 + "win_grad": Number(values.pn),//渐变点个数 + "thr_grad": Number(values.gv),//渐变阈值 + "thr_der": Number(values.dv),//导数阈值 + "days_Last": Number(values.time),//时间跨度 + } + } + } + const getStationstoSave = () => { + let toSave = [], toSaveName = [], notToSave = [], notToSaveName = [] + let selectIds = sensorId + for (let i = 0; i < selectIds.length; i++) { + let seStaName = sensorList.find(a => a.value == selectIds[i]).label + let cfg + if (methodType == "interrupt") { + cfg = abnParam.find(a => a.abnType == 1 && a.factorId == factorId + && a.sensorId == selectIds[i]) + } else { + let type = methodType == "burr" ? 2 : 3 + cfg = abnParam.find(a => a.abnType == type && a.factorId == factorId + && a.sensorId == selectIds[i]) + } + if (!cfg) { + toSave.push(selectIds[i]) + toSaveName.push(seStaName) + } else { + notToSave.push(selectIds[i]) + notToSaveName.push(seStaName) + } + } + return { + toSave: toSave, toSaveName: toSaveName, + notToSave: notToSave, notToSaveName: notToSaveName + }; + } + //保存配置 + const handleSave = () => { + setMsg('') + form2.current.validate().then(res => { + if (sensorId.length != 0) { + let ids = getStationstoSave() + if (ids.toSave.length != 0) { + let paramJson = getJson(res) + let data = { + sensorId:ids.toSave,//测点 + sensorName: ids.toSaveName, + factorId: factorId, + factorName: factosList.find(a => a.value == factorId).label, + abnType: method[methodType],//算法类型 + enabled: true, + params: paramJson, + itemId: itemId, + + } + let pushData = data.sensorId.map(d => { + return { + sensorId: project+'-'+d,//测点 + sensorName: sensorList.find(a => a.value == d).label, + factorId: factorId, + factorName: factosList.find(a => a.value == factorId).label, + abnType: method[methodType],//算法类型 + enabled: true, + params: paramJson, + itemId: methodType == 'interrupt' ? null : itemId + + } + }) + dispatch(install.addAbnParam(pushData)).then(res => { + if (res.success) { + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId: id })) + } + }) + if (ids.notToSave.length != 0) { + let fact = methodType == 'interrupt' ? "因素" : "子项" + Notification.warning({ + content: `已存在传感器: ${ids.notToSaveName.join(',')} 在当前监测${fact}下的${methodDes[methodType]}参数配置,本次保存的传感器为:${ids.toSaveName.join(',')}`, + duration: 3, + }) + } + } else { + let sg = methodType == 'interrupt' ? "因素下的中断" + : methodType == 'burr' ? "子项下的毛刺" : "子项下的异常趋势" + Notification.warning({ + content: `已存在选定测点在当前监测 ${sg} 参数配置,本次无可保存的参数配置`, + duration: 3, + }) + } + } else { + Notification.warning({ + content: `请选择测点`, + duration: 3, + }) + return false + } + + }) + } + //数据对比 + const dataCompare = () => { + // this.setState({ msg: '' }); + // this.props.form.validateFields((err, values) => { + // if (!err) { + // if (this.props.stationsValue.length != 0) { + // let paramJson = this.getJson(values); + // let data = { + // station: this.props.stationsValue[0],//第一个测点 + // factorId: this.props.factorId, + // itemId: this.props.itemId, + // abnType: this.props.methodType,//算法类型 + // enabled: true, + // params: paramJson + // }; + // this.props.dataCompara(data); + // } else { + // message.error('请选择测点!'); + // return false; + // } + // } + // }); + } + + //结构物筛选发生变化回调函数 + const reCalcFactorChange = (value) => { + setItemName(factosList?.find(item => item.value === value[0])?.children?.find(p => p.value === value[1])?.label) + setItemId(value[1]) + // form2.current.setValue('point', '') + setFactorId(value[0]) + } + //点位筛选发生改变的回调函数 + const pointChange = (value) => { + setSensorId(value) + // setProject(structList.find(item => item.value == structId)?.project) + } + //监听结构物变化,查询监测因素 + useEffect(() => { + setLoading(true); + let queryParams = { structId, cacl: 1 } + if (structId) { + dispatch(data.getFactors({ ...queryParams })).then(res => { + if (res.success) { + const list = res.payload.data?.map(item => { + const itemList = item.ItemNames && item.ItemNames.split(',') || [] + let child = [] + itemList && itemList.length && itemList.forEach((i, index) => { + child.push({ label: i, value: index }) + }) + return { + label: item.Name, + value: item.FactorID, + children: child + + } + + }) + form2.current.setValue('factor', [list[0]?.value, list[0]?.children[0]?.value]) + setFactorsList(list) + setFactorId(list[0]?.value) + setItemId(list[0]?.children[0]?.value) + setItemName(list[0]?.children[0]?.label) + setLoading(false) + } + }) + } + + }, [structId]) + useEffect(() => { + let queryParams = { structId, SafetyFactorTypeId: factorId } + if (factorId && structId) { + dispatch(data.getSensors({ ...queryParams })).then(res => { + if (res.success) { + const list = res.payload.data?.map(item => { + return { + value: item.SensorId, + label: item.SensorLocationDescription, + } + }) + form2.current.setValue('point', [list[0]?.value]) + setSensorId([list[0]?.value]) + setSensorList(list) + setLoading(false); + setProject(res.payload.data[0].Project) + } + }) + } + }, [factorId]) + + + + + //获取结构物函数 + const getData = (queryParams = { pomsProjectId: pepProjectId }) => { + setLoading(true); + dispatch(data.getProjectAllStructures({ ...queryParams })).then(res => { + if (res.success) { + const uniqueIds = new Set(); + const list = res.payload.data?.filter(item => { + const duplicate = uniqueIds.has(item.strucId); + uniqueIds.add(item.strucId); + return !duplicate + })?.map(item => ({ + value: item.strucId, + label: item.strucName, + project: item.Project, + })) + form.current.setValue('struct', list[0]?.value) + setStructId(list[0]?.value) + setStructList(list) + setLoading(false) + } + } + + ) + } + const changeMethod = (value) => { + setMethodType(value) + } + const clearFactorAndSensor = () => { + form2.current.setValue('point', '') + form2.current.setValue('factor', '') + } + const structChange = (value) => { + setStructId(value) + clearFactorAndSensor() + } + + + const renderParamsConfig = () => { + switch (methodType) { + case 'interrupt': + return ( + + + + + ); + case 'burr': + return ( + + + + + ); + case 'trend': + return ( + + + + + + + + + + + + + + + + + + + + + + ); + } + } + + return (<> +
+
+
+
数据计算
+
DATA CACL
+
+
+
{ + form.current = formApi + }} style={{ textAlign: 'left', position: "relative", width: "100%", flex: 1 }}> + +
+ + + + {/* + + + */} + + + + + + +
+
+
{ form2.current = formApi }}> + +
+ + + + + + + + + + {abnMethods?.map(item => { + return ; + })} + + + + {methodType === 'trend' ? + + + + : ''} + + + + {renderParamsConfig()} + + {/* */} + + + + + + + + + + + + + + + + + + + ) +} + + + +function mapStateToProps(state) { + const { auth, global, OrganizationDeps, abnMethods, abnParam } = state; + return { + loading: OrganizationDeps.isRequesting, + user: auth.user, + actions: global.actions, + pepProjectId: global.pepProjectId, + abnMethods: abnMethods?.data || [], + abnParam: abnParam?.data || [] + }; +} +export default connect(mapStateToProps)(Calc); + + diff --git a/web/client/src/sections/install/containers/index.js b/web/client/src/sections/install/containers/index.js index afbe8a0..4cd2313 100644 --- a/web/client/src/sections/install/containers/index.js +++ b/web/client/src/sections/install/containers/index.js @@ -4,5 +4,5 @@ import Roles from './roles'; import System from './system'; import Setup from './setup'; import Set from './set'; - -export { Roles, System, Setup, Set }; \ No newline at end of file +import Cacl from './cacl' +export { Roles, System, Setup, Set,Cacl } \ No newline at end of file diff --git a/web/client/src/sections/install/containers/interrupt.jsx b/web/client/src/sections/install/containers/interrupt.jsx new file mode 100644 index 0000000..7ca3832 --- /dev/null +++ b/web/client/src/sections/install/containers/interrupt.jsx @@ -0,0 +1,266 @@ +'use strict'; + +import React, { useRef, useState, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { Row, Col, Button, Input, Table, Card, Switch, Popconfirm, Tag, Notification, Form } from '@douyinfe/semi-ui'; +import InterruptModal from '../components/InterruptModal'; +import '../style.less' + + +const Interrupt = (props) => { + const {dispatch, abnParam,structId,factorId,actions,sensorId,project} = props + const form = useRef() + const [selectedRowKeys, setSelectedRowKeys] = useState([]) + const [filterFunc, setFilterFunc] = useState({}) + const [modalVisible, setModalVisible] = useState(false) + const [ivBatch, setIvBatch] = useState('') + const [modalData, setModalData] = useState(null) + const filterSet = { sensorName: null, factorName: null } + const {install}=actions + const onSelectChange = (selectedRowKeys) => { + setSelectedRowKeys(selectedRowKeys); + } + const compareAndEdit = (e, record) => { + setModalVisible(true) + setModalData(record) + } + const modalCancel = e => { + setModalVisible(false) + } + const removeItem = e => { + dispatch(install.deleteAbnParams(e)).then(res=>{ + if(res.success){ + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id})) + } + }) + } + const handleInputChange = e => { + if (e!= null) { + const value = e + let func = (ep => (s => (s.sensorName).search(ep) > -1))(value); + filterSet.sensorName = func + func = (ep => (s => (s.factorName).search(ep) > -1))(value); + filterSet.factorName = func + } else { + filterSet.sensorName = null + filterSet.factorName = null + } + setFilterFunc(filterSet) + } + //批量启用or禁用 + const onSwitchChange = (e) => { + if (selectedRowKeys.length != 0) { + let ids = selectedRowKeys.join(',') + let data = { + enabled: e, + use: 'switch' + } + dispatch(install.batchCfgAbnParams(ids,data)).then(res=>{ + if(res.success){ + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id })) + } + }) + + } + } + //批量删除 + const batchDelete = () => { + if (selectedRowKeys.length != 0) { + let ids = selectedRowKeys.join(',') + dispatch(install.deleteAbnParams(ids)).then(res=>{ + if(res.success){ + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id})) + } + }) + } else { + Notification.warning({ + content: '您尚未勾选任何参数配置!', + duration: 3, + }) + } + } + //批量保存 + const batchSave = () => { + form.current.validate().then(_ => { + let dataSource = abnParam.filter(a => a.abnType == 1 && a.factorId == factorId) + if (selectedRowKeys.length != 0) { + let ids = selectedRowKeys.join(','); + let data = { + paramJson: { "thr_int": ivBatch }, + use: 'notSwitch', + } + + dispatch(install.batchCfgAbnParams(ids,data)).then(res=>{ + if(res.success){ + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id })) + } + }) + } else if (dataSource.length != 0) { + Notification.warning({ + content: '您尚未勾选任何参数配置!', + duration: 3, + }) + } + }) + } + const batchInterrupt = (value) => { + setIvBatch(value) + } + const checkInterger = (rule, val) => { + // if(val){ + const strRegex = /^[1-9]*[1-9][0-9]*$/ + if (strRegex.test(val)) { + return true + } + return false + // } + + }; + let dataSource = abnParam.filter(a => a.abnType == 1 && a.factorId == factorId) + const rowSelection = { + selectedRowKeys: selectedRowKeys, + onChange: onSelectChange, + }; + let filterData = dataSource + let flag = false + let keyArr = [] + let tmpds = [] + let dataPush = [] + Object.keys(filterFunc).forEach(key => { + const filter = filterFunc[key] + filterData = dataSource + if (filter != null) { + flag = true + filterData = filterData.filter(filter) + if (filterData.length > 0) { + filterData.map(s => { + if (keyArr.indexOf(s.id) == -1) { + keyArr.push(s.id) + dataPush.push(s) + } + }) + } + } + }) + tmpds = flag ? dataPush.sort((a, b) => a.id - b.id) : dataSource.sort((a, b) => a.id - b.id); + let columns = [ + { + title: "测点位置", + dataIndex: "sensorName", + key: "stationName", + width: '20%', + }, + { + title: "监测因素", + dataIndex: "factorName", + key: "factorName", + width: '20%', + }, + { + title: "中断阈值", + dataIndex: "params", + key: "params", + width: '20%', + render: text => text.thr_int + // render: text => { + // return () + // } + }, + { + title: "启用状态", + dataIndex: "enabled", + key: "enabled", + width: '20%', + render: (text, record) => ( + record.enabled ? 已启用 : 已禁用 + ) + }, + { + title: "操作", + key: "action", + width: '20%', + render: (text, record) => ( + + + + { removeItem(record.id) }}> + + + + ), + } + ] + + return ( +
{ + form.current = formApi + }} + labelPosition='left' + > + +
+ + + + + + + + + batchInterrupt(e)} placeholder="毛中断阈值:正整数"> + + + + + + + + + + + + +
+ {modalVisible ? : ''} + + ) +} + + + + + + + + + + + + +function mapStateToProps(state) { + const { abnParam,global } = state; + // let isRequesting = abnParamState?.isRequesting; + + return { + isRequesting: false,//请求状态 + abnParam: abnParam?.data || [], + actions:global.actions, + } +} +export default connect(mapStateToProps)(Interrupt) diff --git a/web/client/src/sections/install/containers/trend.jsx b/web/client/src/sections/install/containers/trend.jsx new file mode 100644 index 0000000..3fb64a5 --- /dev/null +++ b/web/client/src/sections/install/containers/trend.jsx @@ -0,0 +1,425 @@ +'use strict'; + +import React, { Component, useRef,useState } from 'react'; +import { connect } from 'react-redux'; +import { Row, Col, Button, Input, Table, Card, Switch, Popconfirm, Tag, Notification, Form, Select } from '@douyinfe/semi-ui' +import {IconPlus} from "@douyinfe/semi-icons" +import TrendModal from '../components/TrendModal'; + + + +const Trend = (props) => { + const { abnParam, factorId,itName,actions,dispatch,sensorId,project } = props + const form = useRef() + const [selectedRowKeys, setSelectedRowKeys] = useState([]) + const [filterFunc, setFilterFunc] = useState({}) + const [modalVisible, setModalVisible] = useState(false) + const [showBatchConfig, setShowBatchConfig] = useState(false) + const [timeRange, setTimeRange] = useState('') + const [bvBatch, setBvBatch] = useState('') + const [winSize, setWinSize] = useState('') + const [reCoef, setReCoef] = useState('') + const [deValue, setDeValue] = useState('') + const [graPoint, setGraPoint] = useState('') + const [graValue, setGraValue] = useState('') + const [modalData, setModalData] = useState(null) + const filterSet = { sensorName: null, factorName: null } + const {install}=actions + const onSelectChange = (selectedRowKeys) => { + setSelectedRowKeys(selectedRowKeys) + } + const compareAndEdit = (e, record) => { + setModalVisible(true) + setModalData(record) + } + const modalCancel = e => { + setModalVisible(false) + }; + const removeItem = e => { + dispatch(install.deleteAbnParams(e)).then(res=>{ + if(res.success){ + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id})) + } + }) + } + const handleInputChange = e => { + if (e != null) { + const value = e + let func = (ep => (s => (s.sensorName).search(ep) > -1))(value) + filterSet.sensorName = func + func = (ep => (s => (s.factorName).search(ep) > -1))(value) + filterSet.factorName = func + } else { + filterSet.sensorName = null + filterSet.factorName = null + } + setFilterFunc(filterSet) + } + //批量启用or禁用 + const onSwitchChange = (e) => { + if (selectedRowKeys.length != 0) { + let ids = selectedRowKeys.join(','); + let data = { + enabled: e, + use: 'switch' + }; + //启用和禁用接口 + dispatch(install.batchCfgAbnParams(ids,data)).then(res=>{ + if(res.success){ + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id})) + } + }) + } + } + //批量删除 + const batchDelete = () => { + if (selectedRowKeys.length != 0) { + let ids = selectedRowKeys.join(','); + //删除接口 + dispatch(install.deleteAbnParams(ids)).then(res=>{ + if(res.success){ + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id })) + } + }) + } else { + Notification.warning({ + content: '您尚未勾选任何参数配置!', + duration: 3, + }) } + } + //批量保存 + const batchSave = () => { + form.current.validate().then(value=>{ + let dataSource = abnParam.filter(a => a.abnType == 3 && a.factorId == factorId) + if (selectedRowKeys.length != 0) { + let ids = selectedRowKeys.join(',') + let data = { + paramJson: { + "thr_der": deValue, //导数阈值 + "win_avg": reCoef,//滑动均值 + "win_med": winSize,//滑动中值 + "thr_burr": bvBatch,//毛刺阈值 + "thr_grad": graValue,//渐变阈值 + "win_grad": graPoint,//渐变点个数 + "days_Last": timeRange//分析时长 + }, + use: 'notSwitch', + }; + + dispatch(install.batchCfgAbnParams(ids,data)).then(res=>{ + if(res.success){ + const id=[...sensorId?.map(item=>project+'-'+item), -1].join(',') + dispatch(install.getAbnParamList({ sensorId:id})) + } + }) + } else if (dataSource.length != 0) { + Notification.warning({ + content: '您尚未勾选任何参数配置!', + duration: 3, + }) + } + }) + } + const iconClick = () => { + if (!showBatchConfig) { + setShowBatchConfig(true); + } else { + setShowBatchConfig(false); + } + } + const rangeChange = (value) => { + setTimeRange(value) + } + const bvValueBatch = (value) => { + setBvBatch(value) + } + const winSizeBatch = (value) => { + setWinSize(value) + } + const reCoefBatch = (value) => { + setReCoef(value) + } + const deValueBatch = (value) => { + setDeValue(value) + } + const graPointBatch = (value) => { + setGraPoint(value) + } + const graValueBatch = (value) => { + setGraValue(value) + } + const checkInterger = (rule, val) => { + // if(val){ + const strRegex = /^[1-9]*[1-9][0-9]*$/ + if (strRegex.test(val)) { + return true + } + return false + // } + + }; + const checkPoint = (rule, value) => { + const pattern = /^[1-9]*[1-9][0-9]*$/; + if (pattern.test(value) && value != 1) { + return true + } + return false + } + const checkNumber = (rule, val) => { + const strRegex = /^-?\d+(\.\d+)?$/ + if (strRegex.test(val)) { + return true + } + return false + + + + } + + let dataSource = abnParam.filter(a => a.abnType == 3 && a.factorId == factorId) + const rowSelection = { + selectedRowKeys: selectedRowKeys, + onChange: onSelectChange, + }; + let filterData = dataSource; + let flag = false; + let keyArr = []; + let tmpds = []; + let dataPush = []; + Object.keys(filterFunc).forEach(key => { + const filter = filterFunc[key]; + filterData = dataSource; + if (filter != null) { + flag = true; + filterData = filterData.filter(filter) + if (filterData.length > 0) { + filterData.map(s => { + if (keyArr.indexOf(s.id) == -1) { + keyArr.push(s.id); + dataPush.push(s); + } + }) + } + } + }) + tmpds = flag ? dataPush.sort((a, b) => a.id - b.id) : dataSource.sort((a, b) => a.id - b.id); + let columns = [ + { + title: "测点位置", + dataIndex: "sensorName", + key: "sensorName", + width: '9%' + }, + { + title: "监测项", + dataIndex: "factorName", + key: "factorName", + width: '10%', + render: (text, record) => ( + record.factorName + "/" + itName + ), + }, + { + title: "分析时段", + dataIndex: "params", + key: "params1", + width: '9%', + render: text => text.days_Last + "个月" + }, + { + title: "毛刺阈值", + dataIndex: "params", + key: "params2", + width: '9%', + render: text => text.thr_burr + }, + { + title: "窗口数", + dataIndex: "params", + key: "params3", + width: '9%', + render: text => text.win_med + }, + { + title: "回归系数", + dataIndex: "params", + key: "params4", + width: '9%', + render: text => text.win_avg + }, + { + title: "导数阈值", + dataIndex: "params", + key: "params5", + width: '9%', + render: text => text.thr_der + }, + { + title: "渐变点个数", + dataIndex: "params", + key: "params6", + width: '9%', + render: text => text.win_grad + }, + { + title: "渐变阈值", + dataIndex: "params", + key: "params7", + width: '9%', + render: text => text.thr_grad + }, + { + title: "启用状态", + dataIndex: "enabled", + key: "enabled", + width: '9%', + render: (text, record) => ( + record.enabled ? 已启用 : 已禁用 + ) + }, + { + title: "操作", + key: "action", + width: '9%', + render: (text, record) => ( + + + + { removeItem(record.id) }}> + + + + ), + } + ] + return ( +
{ + form.current = formApi + }}> + +
+ + + + + + + + + + + + + + + + + {showBatchConfig ? + + + + 1个月 + 2个月 + 3个月 + + + + bvValueBatch(e)} + rules={[{ + required: true, message: "请输入正整数", validator: checkInterger + }]} trigger='blur'/> + + + winSizeBatch(e)} + trigger='blur'/> + + + reCoefBatch(e)} + trigger='blur'/> + + + deValueBatch(e)} + trigger='blur'/> + + + graPointBatch(e)} + trigger='blur'/> + + + graValueBatch(e)} + trigger='blur'/> + + + + + : ''} + +
+ {modalVisible ? : ''} + + ); +} + +function mapStateToProps(state) { + const { abnParam,global } = state + // let isRequesting = abnParamState?.isRequesting; + + return { + isRequesting: false,//请求状态 + abnParam: abnParam?.data||[], + actions:global.actions + } +} +export default connect(mapStateToProps)(Trend) + diff --git a/web/client/src/sections/install/nav-item.jsx b/web/client/src/sections/install/nav-item.jsx index ea09e55..fa4eddd 100644 --- a/web/client/src/sections/install/nav-item.jsx +++ b/web/client/src/sections/install/nav-item.jsx @@ -25,6 +25,15 @@ export function getNavItem (user, dispatch) { items: [{ itemKey: 'system', to: '/install/mapping/system', text: '系统映射' }] + }, + { + itemKey: 'data', + text: '数据计算', + icon: , + to: '/install/data/cacl', + items: [{ + itemKey: 'cacl', to: '/install/data/cacl', text: '异常数据识别' + }] }, // { // itemKey: 'order ', diff --git a/web/client/src/sections/install/routes.js b/web/client/src/sections/install/routes.js index e983d58..c7eae43 100644 --- a/web/client/src/sections/install/routes.js +++ b/web/client/src/sections/install/routes.js @@ -1,5 +1,6 @@ + 'use strict'; -import { Roles, System, Setup, Set } from './containers'; +import { Roles, System, Setup, Set,Cacl } from './containers/index.js'; export default [ { @@ -30,7 +31,17 @@ export default [ component: System, breadcrumb: '系统映射', }] - }, { + },{ + path: '/data', + key: 'data', + breadcrumb: '数据计算', + childRoutes: [{ + path: '/cacl', + key: 'cacl', + component: Cacl, + breadcrumb: '异常数据识别', + }] + }, { path: '/order', key: 'order', breadcrumb: '工单管理', diff --git a/web/client/src/sections/install/style.less b/web/client/src/sections/install/style.less index aa91f88..3e338a7 100644 --- a/web/client/src/sections/install/style.less +++ b/web/client/src/sections/install/style.less @@ -23,4 +23,5 @@ .separator{ display: none; } -} \ No newline at end of file +} + diff --git a/web/client/src/utils/webapi.js b/web/client/src/utils/webapi.js index 2597668..8d3f82f 100644 --- a/web/client/src/utils/webapi.js +++ b/web/client/src/utils/webapi.js @@ -64,7 +64,7 @@ export const ApiTable = { getVcmpAuth: 'vcmp/auth', // 获取视频平台应用鉴权token getAlarmVideoExceptionType: 'alarm/video/exceptionType', //查询视频设备类型 - // 数据查询 + // 数据监测 getDataContinuityType: 'data/continuity/type_list', getDataContinuity: 'data/continuity', getDataContinuityDetail: 'data/continuity/{continuityId}/detail', @@ -174,7 +174,21 @@ export const ApiTable = { //获取设备信息 getThingMessages: 'getThingMessages', //下发配置(批量单个) - distributeConfiguration: 'distributeConfiguration' + distributeConfiguration: 'distributeConfiguration', + //监测数据相关接口 + getProjectAllStructures: "project/allStructures", //获取绑定项目下结构物 + getFactors:'structure/factors' ,//结构物下的监测因素 + getSensors:'structure/factors/sensors', //检测因素下的设备数据 + getMonitorData:'structure/factors/sensors/data',//获取监测数据 + //异常数据配置相关接口 + getAbnMethods:'abnormal/methods' ,//获取计算方式 + getAbnParamList:'struct/abnormal/params',//获取异常参数配置 + addAbnParamList:'struct/abnormal/params',//新增异常参数配置 + batchSwitch:'batch/switch/{ids}',//批量启用or禁用 + deleteAbnParams: 'delete/abnormal/params/{id}',//删除配置 + editAbnParams: 'edit/abnormal/params/{id}',//修改配置 + getAbnTaskResult: 'struct/{structId}/abnTask/result/{start}/{end}',// + }; // 项企的接口 diff --git a/web/package.json b/web/package.json index 57d43b7..ba23dff 100644 --- a/web/package.json +++ b/web/package.json @@ -66,6 +66,9 @@ "file-saver": "^2.0.5", "fs-attachment": "^1.0.0", "fs-web-server-scaffold": "^1.0.6", + "g2": "^2.3.13", + "g2-plugin-slider": "^1.2.1", + "g2-react": "^1.3.2", "koa-better-http-proxy": "^0.2.5", "koa-proxy": "^1.0.0-alpha.3", "koa-view": "^2.1.4",