Browse Source

feat:数据计算+数据详情展示

dev
zhaobing’ 1 year ago
parent
commit
9f47b9e578
  1. 54
      api/.vscode/launch.json
  2. 636
      api/app/lib/controllers/dataCacl/index.js
  3. 192
      api/app/lib/controllers/monitor/index.js
  4. 5
      api/app/lib/index.js
  5. 101
      api/app/lib/models/abn_report_params.js
  6. 31
      api/app/lib/models/abn_type.js
  7. 28
      api/app/lib/routes/dataCacl/index.js
  8. 20
      api/app/lib/routes/monitor/index.js
  9. 14
      api/config.js
  10. 12
      script/3.7/schema/1.create_abn_type.sql
  11. 15
      script/3.7/schema/2.create_abn_report_params.sql
  12. 4
      web/client/src/sections/data/actions/index.js
  13. 54
      web/client/src/sections/data/actions/monitor.js
  14. 359
      web/client/src/sections/data/components/lineTimeChartTemplate.jsx
  15. 122
      web/client/src/sections/data/containers/dataComponent.jsx
  16. 284
      web/client/src/sections/data/containers/dataDetail.jsx
  17. 8
      web/client/src/sections/data/containers/dataQuery.jsx
  18. 137
      web/client/src/sections/data/containers/dataTableComponent.jsx
  19. 3
      web/client/src/sections/data/containers/index.js
  20. 5
      web/client/src/sections/data/nav-item.jsx
  21. 11
      web/client/src/sections/data/routes.js
  22. 10
      web/client/src/sections/data/style.less
  23. 117
      web/client/src/sections/install/actions/dataCacl.js
  24. 4
      web/client/src/sections/install/actions/index.js
  25. 168
      web/client/src/sections/install/components/TimeAbnValueLineChart.jsx
  26. 288
      web/client/src/sections/install/components/TrendModal.jsx
  27. 214
      web/client/src/sections/install/components/burrModal.jsx
  28. 187
      web/client/src/sections/install/components/interruptModal.jsx
  29. 474
      web/client/src/sections/install/components/theme.jsx
  30. 106
      web/client/src/sections/install/containers/AbnRecognize.jsx
  31. 267
      web/client/src/sections/install/containers/burr.jsx
  32. 496
      web/client/src/sections/install/containers/cacl.jsx
  33. 4
      web/client/src/sections/install/containers/index.js
  34. 266
      web/client/src/sections/install/containers/interrupt.jsx
  35. 425
      web/client/src/sections/install/containers/trend.jsx
  36. 9
      web/client/src/sections/install/nav-item.jsx
  37. 15
      web/client/src/sections/install/routes.js
  38. 3
      web/client/src/sections/install/style.less
  39. 18
      web/client/src/utils/webapi.js
  40. 3
      web/package.json

54
api/.vscode/launch.json

@ -56,8 +56,8 @@
// "--qndmn http://resources.anxinyun.cn", // "--qndmn http://resources.anxinyun.cn",
// "--qndmn http://rhvqdivo5.hn-bkt.clouddn.com", // "--qndmn http://rhvqdivo5.hn-bkt.clouddn.com",
// click // click
// "--clickHouseUrl http://10.8.30.95", "--clickHouseUrl http://10.8.30.95",
// "--clickHousePort 30123", "--clickHousePort 30123",
// click // click
// "--clickHouseUrl http://10.8.30.161", // "--clickHouseUrl http://10.8.30.161",
// "--clickHousePort 30123", // "--clickHousePort 30123",
@ -71,20 +71,20 @@
// //
// //
// click // click
"--clickHouseUrl http://218.3.126.49", // "--clickHouseUrl http://218.3.126.49",
"--clickHousePort 18123", // "--clickHousePort 18123",
"--clickHouseUser default", // "--clickHouseUser default",
"--clickHousePassword Wbo@hi1I", // "--clickHousePassword Wbo@hi1I",
"--clickHouseAnxincloud anxinyun", // "--clickHouseAnxincloud anxinyun",
"--clickHousePepEmis pepca", // "--clickHousePepEmis pepca",
"--clickHouseProjectManage peppm", // "--clickHouseProjectManage peppm",
"--clickHouseVcmp video_access", // "--clickHouseVcmp video_access",
"--clickHouseDataAlarm alarm", // "--clickHouseDataAlarm alarm",
"--clickHouseIot iota", // "--clickHouseIot iota",
"--clickHouseCamworkflow camundaworkflow", // "--clickHouseCamworkflow camundaworkflow",
"--confirmAlarmAnxinUserId 1", // "--confirmAlarmAnxinUserId 1",
"--vcmpAppId 5048b08d-c449-4d7f-b1ec-f741012aefe8", // "--vcmpAppId 5048b08d-c449-4d7f-b1ec-f741012aefe8",
"--vcmpAppSecret 5ba8c0ab-9fbd-4f07-9817-c48017c3cbad", // "--vcmpAppSecret 5ba8c0ab-9fbd-4f07-9817-c48017c3cbad",
// //
// //
// * 2 // * 2
@ -98,17 +98,17 @@
// "--clickHouseDataAlarm default", // "--clickHouseDataAlarm default",
// "--clickHouseIot iot", // "--clickHouseIot iot",
// //
// "--clickHouseAnxincloud anxinyun", "--clickHouseAnxincloud anxinyun",
// "--clickHousePepEmis pepca", "--clickHousePepEmis pepca",
// "--clickHouseProjectManage peppm", "--clickHouseProjectManage peppm",
// "--clickHouseVcmp video_access_dev", "--clickHouseVcmp video_access_dev",
// "--clickHouseDataAlarm default", "--clickHouseDataAlarm alarm",
// "--clickHouseDataAlarmLocal default", "--clickHouseDataAlarmLocal default",
// "--clickHouseIot iota", "--clickHouseIot iota",
// "--clickHouseCamworkflow camworkflow", "--clickHouseCamworkflow camworkflow",
// "--confirmAlarmAnxinUserId 1", "--confirmAlarmAnxinUserId 1",
// "--vcmpAppId 5048b08d-c449-4d7f-b1ec-f741012aefe8", "--vcmpAppId 5048b08d-c449-4d7f-b1ec-f741012aefe8",
// "--vcmpAppSecret 5ba8c0ab-9fbd-4f07-9817-c48017c3cbad", "--vcmpAppSecret 5ba8c0ab-9fbd-4f07-9817-c48017c3cbad",
"--apiCrawUrl http://218.3.126.49:30555/v1" // "--apiCrawUrl http://218.3.126.49:30555/v1" //
] ]
}, },

636
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
};

192
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
}

5
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, AppInspection, ProjectApp, ProjectCorrelation, AppAlarm, App, AlarmAppearRecord, AlarmConfirmLog, EmailSendLog, LatestDynamicList, AlarmPushConfig,
MaintenanceRecord, MaintenanceRecordExecuteUser, MaintenancePlanExecuteUser, MaintenancePlan, EquipmentMaintenanceRecord, EquipmentMaintenanceRecordProject, MaintenanceRecord, MaintenanceRecordExecuteUser, MaintenancePlanExecuteUser, MaintenancePlan, EquipmentMaintenanceRecord, EquipmentMaintenanceRecordProject,
EquipmentMaintenanceRecordExecuteUser, ServerMaintenanceRecordRepairman, ServerMaintenanceRecord, EquipmentMaintenanceRecordExecuteUser, ServerMaintenanceRecordRepairman, ServerMaintenanceRecord,
AlarmDataContinuityType, AlarmDataContinuity, AlarmDataContinuityType, AlarmDataContinuity,AbnTypes,AbnReportParams
} = dc.models; } = dc.models;
AppInspection.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' }); 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' }) ServerMaintenanceRecordRepairman.belongsTo(ServerMaintenanceRecord, { foreignKey: 'serverMaintenanceRecordId', targetKey: 'id' })
ServerMaintenanceRecord.hasMany(ServerMaintenanceRecordRepairman, { 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' })
}; };

101
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;
};

31
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;
};

28
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)
}

20
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);
}

14
api/config.js

@ -49,7 +49,7 @@ args.option('clickHousePepEmis', 'clickHouse 项企数据库名称');
args.option('clickHouseProjectManage', 'clickHouse 项目管理数据库名称'); args.option('clickHouseProjectManage', 'clickHouse 项目管理数据库名称');
args.option('clickHouseVcmp', 'clickHouse 视频平台数据库名称'); args.option('clickHouseVcmp', 'clickHouse 视频平台数据库名称');
args.option('clickHouseDataAlarm', 'clickHouse 视频平台数据告警库名称'); args.option('clickHouseDataAlarm', 'clickHouse 视频平台数据告警库名称');
// args.option('clickHouseDataAlarmLocal', 'clickHouse 本地化告警相关数据'); args.option('clickHouseDataAlarmLocal', 'clickHouse 本地化告警相关数据');
args.option('clickHouseIot', 'clickHouse IOT平台设备信息库名称'); args.option('clickHouseIot', 'clickHouse IOT平台设备信息库名称');
args.option('clickHouseCamworkflow', 'clickHouse 工作流数据库名称'); 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_DATA_ALARM = process.env.CLICKHOUST_DATA_ALARM || flags.clickHouseDataAlarm
const CLICKHOUST_IOT = process.env.CLICKHOUST_IOT || flags.clickHouseIot const CLICKHOUST_IOT = process.env.CLICKHOUST_IOT || flags.clickHouseIot
const CLICKHOUST_CAM_WORKFLOW = process.env.CLICKHOUST_CAM_WORKFLOW || flags.clickHouseCamworkflow 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 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, CONFIRM_ALARM_ANXIN_USER_ID,
VCMP_APP_ID, VCMP_APP_SECRET, VCMP_APP_ID, VCMP_APP_SECRET,
API_CRAW_URL, API_CRAW_URL,
// CLICKHOUST_DATA_ALARM_LOCAL, CLICKHOUST_DATA_ALARM_LOCAL,
TYPES TYPES
} }
@ -341,10 +341,10 @@ const product = {
name: 'camWorkflow', name: 'camWorkflow',
db: CLICKHOUST_CAM_WORKFLOW db: CLICKHOUST_CAM_WORKFLOW
}, },
// { {
// name: 'alarmLocal', name: 'alarmLocal',
// db: CLICKHOUST_DATA_ALARM_LOCAL db: CLICKHOUST_DATA_ALARM_LOCAL
// } }
] ]
}, },
es: { es: {

12
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))

15
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
);

4
web/client/src/sections/data/actions/index.js

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import * as dataQuery from './dataQuery' import * as dataQuery from './dataQuery'
import * as monitor from './monitor'
export default { export default {
...dataQuery ...dataQuery,...monitor
} }

54
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 } },
});
}

359
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: `
// <div class="g2-tooltip">
// <div id="${this.tooltipId}" style="max-height:${height - 30}px;min-width:130px;overflow:auto;position:relative;padding-right:12px">
// <div class="g2-tooltip-title" style="margin-bottom: 4px;"></div>
// <ul class="g2-tooltip-list"></ul>
// </div>
// </div>`,
// });
// chart.legend(true, {
// marker: 'circle',
// useHtml: true,
// position: 'left',
// containerTpl: `<div class="g2-legend" id="${this.legendId}" style="word-wrap:break-word;position:absolute;top:20px;left:0">
// <h4 class="g2-legend-title"></h4>
// <ul class="g2-legend-list" id="${this.legendListId}" style="word-wrap:break-word;text-align: left;height:${height - 50}px;width:${130}px"></ul>
// </div>`,
// })
// 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 += '<div><span>' + xName + '</span> ' + seriesName[0] + ' <span style="margin-right:5px;display:inline-block;width:10px;height:10px;border-radius:5px;'
+ 'background-color:' + color + ';"></span>' + data + '</div>';
}
}
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 (
<div style={{ position: 'relative', paddingTop: 20, marginBottom: 24 }}>
{/* <div style={{ position: 'absolute', right: '20px', top: '20px', zIndex: 2000 }} >
<Button onClick={this.download} size="default">导出</Button>
</div> */}
<div>
{/* <div id={this.chartId} style={{ textAlign: 'left' }}></div> */}
<ReactEcharts
option={this.state.option}
/>
{/* <div id="echarts" style={{ textAlign: 'left' }}></div> */}
{/* {
showSlider ? [
<div style={{ width: '100%' }} id={this.sliderId}></div>
] : null
} */}
</div>
</div>
);
}
}
export default LineTimeChart;

122
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 <Banner
type="info"
description="查询数据中,请稍候..."/>
}
if (JSON.stringify(dataList) == "{}") {
return <Banner description="无数据" type="info"/>
}
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(
<LineTimeChart
key={"slider-device-" + deviceItem}
data={chartDataD}
width={1000}
height={300}
slider={"slider-device-" + deviceItem}
options={optionsD}
/>)
}
}
if (chartContent.length == 0) {
return <Banner message="无数据"
type="info"
/>
} else {
return chartContent.map(p => {
return p;
})
}
}
//
return (<div>
<span>{renderChart()}</span>
</div>)
}
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);

284
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 (
<>
<div style={{ background: '#FFFFFF', margin: '8px 12px', padding: '20px 20px 0px 20px' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ width: 0, height: 20, borderLeft: '3px solid #005ABD', borderTop: '3px solid transparent', borderBottom: '3px solid transparent' }}></div>
<div style={{ fontFamily: "YouSheBiaoTiHei", fontSize: 24, color: '#101531', marginLeft: 8 }}>数据详情</div>
<div style={{ marginLeft: 6, fontSize: 12, color: '#969799', fontFamily: "DINExp", }}>DATA DETAIL</div>
</div>
</div>
<Form
getFormApi={formApi => {
form.current = formApi
}}
labelPosition='left'
layout="horizontal"
style={{ position: "relative", width: "100%", flex: 1 }}
// onSubmit={formSubmit}
>
<Row>
<Col span={12} >
<Form.Select
value={structId}
filter
labelWidth="75px"
label="结构物"
field="struct"
placeholder="结构物"
optionList={structList}
style={{ width: 260 }}
showClear
onChange={structChange}
></Form.Select>
</Col>
<Col span={12} >
<Form.Select
filter
label="监测因素"
field="factor"
placeholder="监测因素"
style={{ width: 260, marginLeft: 12, marginRight: 12 }}
showClear
optionList={factosList}
onChange={factorChange}
></Form.Select>
<Form.Select
filter
label="点位"
field="point"
placeholder="点位"
multiple
optionList={sensorList}
style={{ width: 260, marginLeft: 12, marginRight: 12 }}
showClear
onChange={pointChange}
></Form.Select>
</Col>
</Row>
<br/>
<br/>
<Row>
<Col span={12}>
<Form.DatePicker
// labelWidth="75px"
field="createTimes"
label="查询时间"
initValue={[moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'), moment().format('YYYY-MM-DD HH:mm:ss')]}
// style={{ width: 268 }}
type="dateTimeRange" density="compact"
/>
<Col span={6}>
<Button theme='solid' type="primary" htmlType="submit" onClick={searchHandler}>
查询
</Button>
</Col>
</Col>
</Row>
</Form>
<br/>
<br/>
<Row>
<Col>
<Tabs type="button">
<TabPane tab={<span><IconLineChartStroked />趋势图</span>} itemKey="1">
<Divider></Divider>
<DataComponent dataList={checkData}/>
</TabPane>
<TabPane tab={<span><IconBookOpenStroked />数据</span>} itemKey="2">
<Divider></Divider>
<DataTableComponent dataList={checkData}/>
</TabPane>
</Tabs>
</Col>
</Row>
</div>
</>
)
}
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);

8
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({ exportWord({
mhtml: html, mhtml: html,
filename: name, filename: name,
@ -48,7 +48,7 @@ const DataQuery = (props) => {
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ width: 0, height: 20, borderLeft: '3px solid #005ABD', borderTop: '3px solid transparent', borderBottom: '3px solid transparent' }}></div> <div style={{ width: 0, height: 20, borderLeft: '3px solid #005ABD', borderTop: '3px solid transparent', borderBottom: '3px solid transparent' }}></div>
<div style={{ fontFamily: "YouSheBiaoTiHei", fontSize: 24, color: '#101531', marginLeft: 8 }}>数据查询</div> <div style={{ fontFamily: "YouSheBiaoTiHei", fontSize: 24, color: '#101531', marginLeft: 8 }}>数据监测</div>
<div style={{ marginLeft: 6, fontSize: 12, color: '#969799', fontFamily: "DINExp", }}>DATA QUERY</div> <div style={{ marginLeft: 6, fontSize: 12, color: '#969799', fontFamily: "DINExp", }}>DATA QUERY</div>
</div> </div>
<div style={{ marginRight: 20, display: 'flex', alignItems: 'center' }} className='myempush'> <div style={{ marginRight: 20, display: 'flex', alignItems: 'center' }} className='myempush'>
@ -73,9 +73,9 @@ const DataQuery = (props) => {
/> />
<Form.Select <Form.Select
pure pure
label="数据查询类型" label="数据监测类型"
field="typeNo" field="typeNo"
placeholder="数据查询类型" placeholder="数据监测类型"
style={{ width: 260, marginLeft: 12, marginRight: 12 }} style={{ width: 260, marginLeft: 12, marginRight: 12 }}
showClear showClear
> >

137
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 (
<div>
<Spin spinning={isRequesting}>
<div>
{/* <p style={{ textAlign: 'right', margin: 8 }}>
{
data.length ? <Button type="ghost" onClick={exporting}>导出</Button> : null
}
</p> */}
<Table dataSource={data} columns={columns} pagination={{ current: currentPage, onChange:pageChange }} />
</div>
</Spin>
</div>
);
};
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);

3
web/client/src/sections/data/containers/index.js

@ -3,4 +3,5 @@ import DataQuery from './dataQuery';
import DataComparison from './dataComparison'; import DataComparison from './dataComparison';
import DataAssociation from './dataAssociation'; import DataAssociation from './dataAssociation';
import Notebook from './notebook'; import Notebook from './notebook';
export { DataQuery, DataComparison, DataAssociation,Notebook}; import DataDetail from './dataDetail';
export { DataQuery, DataComparison, DataAssociation,Notebook,DataDetail};

5
web/client/src/sections/data/nav-item.jsx

@ -1,3 +1,4 @@
import React from 'react'; import React from 'react';
import { IconCode } from '@douyinfe/semi-icons'; import { IconCode } from '@douyinfe/semi-icons';
@ -15,7 +16,9 @@ export function getNavItem (user, dispatch) {
icon: <iconpark-icon style={{ width: 20, height: 20 }} name="iconsjjiankong"></iconpark-icon>, icon: <iconpark-icon style={{ width: 20, height: 20 }} name="iconsjjiankong"></iconpark-icon>,
to: '/data/dataMonitoring/dataQuery', to: '/data/dataMonitoring/dataQuery',
items: [{ items: [{
itemKey: 'dataQuery', to: '/data/dataMonitoring/dataQuery', text: '数据查询' itemKey: 'dataQuery', to: '/data/dataMonitoring/dataQuery', text: '数据监测'
},{
itemKey: 'dataDetail', to: '/data/dataMonitoring/dataDetail', text: '数据详情'
}] }]
}, { }, {
itemKey: 'dataAnalysis', itemKey: 'dataAnalysis',

11
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 [{ export default [{
type: 'inner', type: 'inner',
@ -15,7 +16,13 @@ export default [{
path: '/dataQuery', path: '/dataQuery',
key: 'dataQuery', key: 'dataQuery',
component: DataQuery, component: DataQuery,
breadcrumb: '数据查询', breadcrumb: '数据监测',
},{
path: '/dataDetail',
key: 'dataDetail',
component: DataDetail,
breadcrumb: '数据详情',
}] }]
}, { }, {
path: '/dataAnalysis', path: '/dataAnalysis',

10
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;
}

117
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 } },
})
}

4
web/client/src/sections/install/actions/index.js

@ -2,7 +2,9 @@
import * as roles from './roles' import * as roles from './roles'
import * as system from './system' import * as system from './system'
import * as dataCacl from './dataCacl'
export default { export default {
...roles, ...roles,
...system ...system,
...dataCacl
} }

168
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 (
<div style={{ position: 'relative', paddingTop: 20, marginBottom: 24 }}>
<div style={{ position: 'absolute', right: '20px', top: '20px', zIndex: 2 }} >
<Button onClick={this.download} size="default">导出</Button>
</div>
<div>
<this.Chart
data={data} width={width} height={height || 500} xAxis={xAxis}
plotCfg={{ margin: margin }} forceFit={true} ref="myChart" configs={configs} />
{
showSlider ? [
<div className="chart-inner-divider"></div>,
<div className="chart-slider" id={this.sliderId}></div>
] : null
}
</div>
</div>
);
}
}
export default TimeAbnValueLineChart;

288
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 <div>
<Modal title="异常参数编辑" maskClosable={false} visible={visible} onCancel={closeModal} width={1000}
footer={[<div>
<Button type="primary" theme='borderless' htmlType="submit" onClick={btnFormSubmit}>保存</Button>
<Button key="cancel" onClick={closeModal}>关闭</Button>
</div>]}>
<Form getFormApi={formApi => {
form.current = formApi
}} labelPosition='left'
>
<Row>
<Form.Input label='测点位置' field='stationName'
rules={[{ required: true, message: '测点位置' }]}
disabled={true}
initValue={modalData.sensorName}
>
</Form.Input>
<Form.Input label='监测因素' field='factorName'
rules={[{ required: true, message: '监测因素' }]}
disabled={true}
initValue={modalData.factorName}
>
</Form.Input>
<Form.Switch label='是否启用' field='checked'
rules={[{ required: true, message: '是否启用' }]}
initValue={modalData.enabled}
>
</Form.Switch>
<Form.Select showSearch filter label="分析时长" field="timeRange"
placeholder="请选择分析时长" style={{ width: 127, marginBottom: 0 }}
initValue={modalData.params.days_Last} rules={[{
required: true, message: "请选择分析时长"
}]}
>
<Select.Option value="1">1个月</Select.Option>
<Select.Option value="2">2个月</Select.Option>
<Select.Option value="3">3个月</Select.Option>
</Form.Select>
</Row>
<Row style={{ marginTop: 18 }}>
<Form.Input
initValue={ modalData.params.thr_burr}
field='burr'
placeholder="毛刺阈值:数字" label="毛刺阈值"
rules={[{
required: true, message: "请输入正整数", validator: checkInterger
}]} t
trigger='blur' />
<Form.Input
initValue={ modalData.params.win_med}
field='ws'
placeholder="滑动中值:正值" label="滑动中值"
rules={[{
required: true, message: "请输入正整数", validator: checkInterger
}]}
trigger='blur' />
<Form.Input
field='rc'
initValue={ modalData.params.win_avg}
placeholder="滑动均值:正值" label="滑动均值"
rules={[{
required: true, message: "请输入正整数", validator: checkInterger
}]}
trigger='blur' />
</Row>
<Row style={{ marginTop: 18 }}>
<Form.Input
field='dv'
initValue={modalData.params.thr_der}
placeholder="导数阈值:数字" label="导数阈值"
rules={[{
required: true, message: "请输入数字", validator: checkNumber
}]}
trigger='blur' />
<Form.Input
field='pn'
initValue={modalData.params.win_grad}
placeholder="渐变点个数:正值" label="渐变点数"
rules={[{
required: true, message: "请输入大于1的正整数", validator: checkPoint
}]}
trigger='blur' />
<Form.Input
field='gv'
initValue={modalData.params.thr_grad}
placeholder="渐变阈值:数字" label="渐变阈值"
rules={[{
required: true, message: "请输入数字", validator: checkNumber
}]} trigger='blur'
/>
</Row>
<Row style={{ marginTop: 28 }}>
<Form.DatePicker
placeholder={['开始时间', '结束时间']}
field="timeSelected"
label="查询时间"
initValue={[moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'), moment().format('YYYY-MM-DD HH:mm:ss')]}
type="dateTimeRange" density="compact"
onChange={timeOnChange}
/>
{/* <Button size='default' type="primary" onClick={dataCompare}>数据对比</Button> */}
</Row>
{/* <div className="data-chart-container">
{originalData && originalData[0] == '还没查询' ?
<div style={{ margin: '30px 0' }}><IconLineChartStroked/> 输入参数点击数据对比展示数据对比图</div>
:
originalData && originalData[0] != '还没查询' && originalData.length > 0 ?
<TimeAbnValueLineChart contentType={'trend'} data={stationsData} width={300} height={300}
itemName={itemName} configs={{ slider: { start: start, end: end } }} />
:
<div style={{ margin: '30px 0' }}><IconLineChartStroked/> 没有查询到任何有效数据</div>}
</div> */}
</Form>
</Modal>
</div>;
}
function mapStateToProps(state) {
const { abnItemState_tr, global} = state;
return {
abnItemCompData: abnItemState_tr.items,
actions:global.actions
}
}
export default connect(mapStateToProps)(TrendModal)

214
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 (<>
<Modal maskClosable={false} title="异常参数编辑" visible={visible} onCancel={closeModal} width={900}
footer={[<div>
<Button type="primary" theme='borderless' htmlType="submit" onClick={btnFormSubmit}>保存</Button>
<Button key="cancel" onClick={closeModal}>关闭</Button>
</div>]}>
<Form
labelPosition='left'
// initialValues={{'stationName': modalData.sensorName,
// 'factorName': modalData.factorName + "/" + item,
// 'burr': modalData.params.thr_burr,
// 'checked': modalData.enabled}}
getFormApi={formApi => { form.current = formApi}}>
<Row>
<Form.Input label='测点位置' field='stationName'
initValue={modalData.sensorName}
rules={[{ required: true, message: '测点位置' }]}
disabled={true}
>
</Form.Input>
<Form.Input label='监测因素' field='factorName'
initValue={ modalData.factorName + "/" + item}
rules={[{ required: true, message: '监测因素' }]}
disabled={true}
>
</Form.Input>
<Form.Switch label='是否启用' field='checked'
initValue={modalData.enabled}
rules={[{ required: true, message: '是否启用' }]}
>
</Form.Switch>
<Form.Input
field='burr'
placeholder="毛刺阈值:数字" label="毛刺阈值"
initValue={modalData.params.thr_burr}
rules={[{
required: true, message: "请输入数字", validator: checkNumber
}]}
trigger='blur' />
</Row>
<Row style={{ marginTop: 15 }}>
<Form.DatePicker
placeholder={['开始时间', '结束时间']}
field="timeSelected"
label="查询时间"
initValue={[moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'), moment().format('YYYY-MM-DD HH:mm:ss')]}
type="dateTimeRange" density="compact"
onChange={timeOnChange}
/>
{/* <Button size='default' type="primary" onClick={dataCompare}>数据对比</Button> */}
</Row>
{/* <div className="data-chart-container">
{originalData && originalData[0] == '还没查询' ?
<div style={{ margin: '30px 0' }}><IconInfoCircle /> 输入参数点击数据对比展示数据对比图</div>
:
originalData && originalData[0] != '还没查询' && originalData.length > 0 ?
<TimeAbnValueLineChart contentType={'burr'} data={stationsData} width={300} height={300}
itemName={itemName} configs={{ slider: { start: start, end: end } }} />
:
<div style={{ margin: '30px 0' }}><IconInfoCircle/> 没有查询到任何有效数据</div>}
</div> */}
</Form>
</Modal>
</>)
}
function mapStateToProps(state) {
const { abnItemState_burr,global } = state;
return {
abnItemCompData: abnItemState_burr.items,
actions:global.actions,
}
}
export default connect(mapStateToProps)(BurrModal)

187
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 <div>
<Modal maskClosable={false} title="异常参数编辑" visible={visible} onCancel={closeModal} width={900}
footer={[<div>
<Button type="primary" theme='borderless' htmlType="submit" onClick={btnFormSubmit}>保存</Button>
<Button key="cancel" onClick={closeModal}>关闭</Button>
</div>]}>
<Form labelPosition="left" getFormApi={formApi => {
form.current = formApi
}}>
<Row>
<Form.Input label='测点位置' field='stationName'
rules={[{ required: true, message: '测点位置' }]}
disabled={true}
initValue={modalData.sensorName}
>
</Form.Input>
<Form.Input label='监测因素' field='factorName'
initValue={modalData.factorName}
rules={[{ required: true, message: '监测因素' }]}
disabled={true}
>
</Form.Input>
<Form.Switch label='是否启用' field='checked'
initValue={modalData.enabled}
rules={[{ required: true, message: '是否启用' }]}
>
</Form.Switch>
<Form.Input
field='thr_int'
placeholder="中断阈值:正整数" label="中断阈值"
rules={[{
required: true, message: "请输入正整数", validator: checkInterger
}]} trigger='blur'
initValue={modalData.params.thr_int}
/>
<Row style={{ marginTop: 15 }}>
<Form.DatePicker
placeholder={['开始时间', '结束时间']}
field="timeSelected"
label="查询时间"
initValue={[moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'), moment().format('YYYY-MM-DD HH:mm:ss')]}
type="dateTimeRange" density="compact"
onChange={timeOnChange}
/>
{/* <Button size='default' type="primary" onClick={dataCompare}>数据对比</Button> */}
</Row>
{/* <Button size='default' type="primary" onClick={dataCompare}>数据对比</Button> */}
{/* <div className="data-chart-container">
{originalData && originalData[0] == '还没查询' ?
<div style={{ margin: '30px 0' }}><IconLineChartStroked /> 输入参数点击数据对比展示数据对比图中断仅展示第一个监测项</div>
:
originalData && originalData[0] != '还没查询' && originalData.length > 0 ?
<TimeAbnValueLineChart contentType={'interrupt'} data={stationsData} width={300} height={300}
itemName={itemName} configs={{ slider: { start: start, end: end } }} />
:
<div style={{ margin: '30px 0' }}><IconLineChartStroked /> 没有查询到任何有效数据</div>}
</div> */}
</Row>
</Form>
</Modal>
</div>
}
function mapStateToProps(state) {
const { global } = state;
return {
// abnItemCompData: abnItemState_int.items,
// abnItemCompData:[],
actions:global.actions
}
}
export default connect(mapStateToProps)(InterruptModal)

474
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);

106
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': <Interrupt project={project} sensorId={sensorId} structId={structId} factorId={factorId} />,
'burr': <Burr project={project} sensorId={sensorId} structId={structId} factorId={factorId} itName={itName} />,
'trend': <Trend project={project} sensorId={sensorId} structId={structId} factorId={factorId} itName={itName}/>,
};
return SubContent[calcMethod];
}
return (<>
<hr style={{ borderTopWidth: 0, marginBottom: 18 }} />
{/* <Row>
<Col>
<Card title={<div><IconLineChartStroked/><span style={{ marginLeft: 6 }}>数据对比</span></div>}>
<div bodyStyle={{ padding: 0 }}>
<div className="data-chart-container">
{originalData && originalData[0] == '还没查询' ?
<div style={{ margin: '20px 0' }}><IconLineChartStroked/> 输入参数点击数据对比展示数据对比图默认显示选择的第一个传感器</div>
:
originalData && originalData[0] != '还没查询' && originalData.length > 0 ?
<timeChart contentType={calcMethod} data={stationsData} width={300} height={300}
itemName={itemName} configs={{ slider: { start: start, end: end } }} />
:
<div style={{ margin: '20px 0' }}><IconLineChartStroked/> 没有查询到任何有效数据</div>}
</div>
</div>
</Card>
</Col>
</Row> */}
{renderContent()}
</>)
}
function mapStateToProps(state) {
const { auth, global, OrganizationDeps } = state;
return {
// loading: OrganizationDeps.isRequesting,
// user: auth.user,
// actions: global.actions,
};
}
export default connect(mapStateToProps)(AbnRecognize)

267
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 ? <Tag color="blue">已启用</Tag> : <Tag>已禁用</Tag>
)
},
{
title: "操作",
key: "action",
width: '20%',
render: (text, record) => (
<span>
<Button theme='borderless' type='primary' onClick={(e) => compareAndEdit(e, record)}>编辑</Button>
<span className="ant-divider"></span>
<Popconfirm title="确认删除该参数配置?" id={record.id} onConfirm={() => {removeItem(record.id) }}>
<Button theme='borderless' type='danger'>删除</Button>
</Popconfirm>
</span>
),
}
]
return (<Card style={{ marginTop: 15 }}>
<Form style={{ marginBottom: 15 }}
getFormApi={formApi => {
form.current = formApi
}}
labelPosition='left'
>
<Row style={{ display: 'flex',alignItems:'center'}}>
<Col span={5}>
<Form.Input field='keywords' noLabel={true} style={{ marginBottom: 0,width: '95%' }} placeholder="关键词:测点位置、监测项" onChange={handleInputChange}>
</Form.Input>
</Col>
<Col span={2}>
<Form.Switch field='switch' label='批量启用' style={{ marginBottom: 0 }} checked onChange={onSwitchChange}>
</Form.Switch>
</Col>
<Col span={3}>
<Form.Input field='bv1' noLabel={true} style={{ marginBottom: 0,width: '93%' }}
rules={[{
required: true, message: "请输入数字", validator: checkNumber
}]} trigger='blur'
onChange={e =>batchBurr(e)} placeholder="毛刺阈值:数字">
</Form.Input>
</Col>
<Col span={2}>
<Button style={{ marginBottom: 0 }} type='primary' theme='borderless' onClick={batchSave}>批量保存</Button>
</Col>
<Col span={2}>
<Popconfirm title="确认批量删除选中的参数配置?" onConfirm={batchDelete}>
<Button style={{ fontSize: 13 }} theme='borderless' type='danger'>批量删除</Button>
</Popconfirm>
</Col>
</Row>
</Form>
<Table rowSelection={rowSelection} columns={columns} dataSource={tmpds} />
{modalVisible ? <BurrModal
project={project}
structId={structId}
visible={true}
closeModal={modalCancel}
modalData={modalData}
item={itName}
sensorId={sensorId}
/> : ''}
</Card>
)
}
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)

496
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 (
<Col span={4}>
<Form.Input field='iv' placeholder="中断阈值:正整数" label="中断阈值" rules={[{
required: true, message: "请输入正整数", validator: checkInterger
}]} trigger='blur'>
</Form.Input>
</Col>
);
case 'burr':
return (
<Col span={4}>
<Form.Input field='bv1' placeholder="毛刺阈值:数字" label="Burr阈值" rules={[{
required: true, message: "请输入数字", validator: checkNumber
}]} trigger='blur'>
</Form.Input>
</Col>
);
case 'trend':
return (
<Col span={18}>
<Row gutter={10}>
<Col span={4} >
<Form.Input field='bv2' placeholder="毛刺阈值:数字" label="中断阈值" rules={[{
required: true, message: "请输入正整数", validator: checkInterger
}]} trigger='blur' />
</Col>
<Col span={4}>
<Form.Input field='ws' placeholder="滑动中值:正值" label="滑动中值" rules={[{
required: true, message: "请输入正整数", validator: checkInterger
}]} trigger='blur' />
</Col>
<Col span={4}>
<Form.Input field='rc' placeholder="滑动均值:正值" label="滑动均值" rules={[{
required: true, message: "请输入正整数", validator: checkInterger
}]} trigger='blur' />
</Col>
<Col span={4}>
<Form.Input field='dv' placeholder="导数阈值:数字" label="导数阈值" rules={[{
required: true, message: "请输入数字", validator: checkNumber
}]} trigger='blur' />
</Col>
<Col span={4}>
<Form.Input field='pn' placeholder="渐变点个数:正值" label="渐变点个数" rules={[{
required: true, message: "请输入大于1的正整数", validator: checkPoint
}]} trigger='blur' />
</Col>
<Col span={4}>
<Form.Input field='gv' placeholder="渐变阈值:数字" label="渐变阈值" rules={[{
required: true, message: "请输入数字", validator: checkNumber
}]} trigger='blur' />
</Col>
</Row>
</Col>);
}
}
return (<>
<div style={{ background: '#FFFFFF', margin: '8px 12px', padding: '20px 20px 0px 20px' }}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ width: 0, height: 20, borderLeft: '3px solid #005ABD', borderTop: '3px solid transparent', borderBottom: '3px solid transparent' }}></div>
<div style={{ fontFamily: "YouSheBiaoTiHei", fontSize: 24, color: '#101531', marginLeft: 8 }}>数据计算</div>
<div style={{ marginLeft: 6, fontSize: 12, color: '#969799', fontFamily: "DINExp", }}>DATA CACL</div>
</div>
<div style={{ margin: '20px 0 16px 0' }}>
<Form labelPosition='left' getFormApi={formApi => {
form.current = formApi
}} style={{ textAlign: 'left', position: "relative", width: "100%", flex: 1 }}>
<Row>
<Col span={12} >
<Form.Select onChange={structChange} optionList={structList} option labelWidth="75px" label="结构物" field="struct" placeholder="请选择结构物" showSearch filter>
</Form.Select>
</Col>
{/* <Col span={12} >
<Form.Select label="监测因素" field="factorId" value={reCalcFactorId} onChange={reCalcFactorChange}
placeholder="请选择监测因素" style={{ width: 160 }} size="large">
</Form.Select>
</Col> */}
<Col span={12} >
<Form.DatePicker
labelWidth="75px"
field="timeRange"
label="查询时间"
initValue={[moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'), moment().format('YYYY-MM-DD HH:mm:ss')]}
type="dateTimeRange" density="compact"
/>
</Col>
</Row>
</Form>
<div style={{ marginTop: 15 }}>
<hr style={{ borderTopWidth: 0, marginBottom: 18 }} />
<Form labelPosition='left' style={{ textAlign: 'left', marginTop: 15, position: "relative", width: "100%" }}
getFormApi={formApi => { form2.current = formApi }}>
<Row gutter={10}>
<Col span={6} >
<Form.Cascader label="监测因素" field="factor" value={reCalcFactorId} onChange={reCalcFactorChange}
treeData={factosList} filter
placeholder="请选择监测因素" style={{ textAlign: 'left', width: 274 }} >
</Form.Cascader>
</Col>
<Col span={6} >
<Form.Select showSearch filter multiple iSelect label="设备" field="point" value={sensorId} onChange={pointChange}
optionList={sensorList} placeholder="请选择设备" style={{ textAlign: 'left', width: 274 }} >
</Form.Select>
</Col>
<Col span={6} >
<Form.Select showSearch filter label="异常识别算法" field="method" onChange={changeMethod}
placeholder="请选择异常识别算法" style={{ textAlign: 'left', width: 127 }} >
{abnMethods?.map(item => {
return <Form.Select.Option value={item.name} label={item.des}></Form.Select.Option>;
})}
</Form.Select>
</Col>
<Col span={3} >
{methodType === 'trend' ? <Form.Select showSearch filter label="分析时长" field="time"
rules={[{
required: true, message: "请选择分析时长"
}]}
placeholder="请选择分析时长" style={{ width: 127 }} >
<Select.Option value="1" label='1个月'></Select.Option>
<Select.Option value="2" label='2个月'></Select.Option>
<Select.Option value="3" label='3个月'></Select.Option>
</Form.Select> : ''}
</Col>
</Row>
<Row style={{ display: 'flex',alignItems:'center'}}>
{renderParamsConfig()}
<Col span={6}>
{/* <Button style={{ marginRight: 10, marginLeft: 10 }} type="tertiary">数据对比</Button> */}
<Button theme='borderless' type='secondary' style={{ marginRight: 10,marginLeft: 10 }} onClick={handleSave}>保存配置</Button>
</Col>
</Row>
</Form>
<AbnRecognize project={project} sensorId={sensorId} calcMethod={methodType} factorId={factorId} structId={structId} itName={itemName} />
</div>
</div>
</div>
</>)
}
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);

4
web/client/src/sections/install/containers/index.js

@ -4,5 +4,5 @@ import Roles from './roles';
import System from './system'; import System from './system';
import Setup from './setup'; import Setup from './setup';
import Set from './set'; import Set from './set';
import Cacl from './cacl'
export { Roles, System, Setup, Set }; export { Roles, System, Setup, Set,Cacl }

266
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 (<Input style={{ width: 121 }} defaultValue={text.thr_int} placeholder="" />)
// }
},
{
title: "启用状态",
dataIndex: "enabled",
key: "enabled",
width: '20%',
render: (text, record) => (
record.enabled ? <Tag color="blue">已启用</Tag> : <Tag>已禁用</Tag>
)
},
{
title: "操作",
key: "action",
width: '20%',
render: (text, record) => (
<span>
<Button theme='borderless' type='primary' onClick={(e) => compareAndEdit(e, record)}>编辑</Button>
<span className="ant-divider"></span>
<Popconfirm title="确认删除该参数配置?" id={record.id} onConfirm={() => { removeItem(record.id) }}>
<Button theme='borderless' type='danger'>删除</Button>
</Popconfirm>
</span>
),
}
]
return (<Card style={{ marginTop: 15 }}>
<Form style={{ marginBottom: 15 }}
getFormApi={formApi => {
form.current = formApi
}}
labelPosition='left'
>
<Row style={{ display: 'flex',alignItems:'center'}}>
<Col span={5}>
<Form.Input field='keywords' noLabel={true} style={{ marginBottom: 0, width: '95%' }} placeholder="关键词:测点位置、监测项" onChange={handleInputChange}>
</Form.Input>
</Col>
<Col span={2}>
<Form.Switch label='批量启用' field='switch' style={{ marginBottom: 0 }} checked onChange={onSwitchChange}>
</Form.Switch>
</Col>
<Col span={3}>
<Form.Input field='iv' noLabel={true} style={{ marginBottom: 0, width: '93%' }}
rules={[{
required: true, message: "请输入正整数", validator: checkInterger
}]} trigger='blur'
onChange={e => batchInterrupt(e)} placeholder="毛中断阈值:正整数">
</Form.Input>
</Col>
<Col span={2}>
<Button style={{ marginBottom: 0 }} onClick={batchSave} type='primary' theme='borderless'>批量保存</Button>
</Col>
<Col span={2}>
<Popconfirm title="确认批量删除选中的参数配置?" onConfirm={batchDelete}>
<Button style={{ fontSize: 13 }} theme='borderless' type='danger'>批量删除</Button>
</Popconfirm>
</Col>
</Row>
</Form>
<Table rowSelection={rowSelection} columns={columns} dataSource={tmpds} />
{modalVisible ? <InterruptModal
structId={structId}
visible={true}
project={project}
sensorId={sensorId}
closeModal={modalCancel}
modalData={modalData}
/> : ''}
</Card>
)
}
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)

425
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 ? <Tag color="blue">已启用</Tag> : <Tag>已禁用</Tag>
)
},
{
title: "操作",
key: "action",
width: '9%',
render: (text, record) => (
<span>
<Button theme='borderless' type='primary' onClick={(e) => compareAndEdit(e, record)}>编辑</Button>
<span className="ant-divider"></span>
<Popconfirm title="确认删除该参数配置?" id={record.id} onConfirm={() => { removeItem(record.id) }}>
<Button theme='borderless' type='danger' >删除</Button>
</Popconfirm>
</span>
),
}
]
return (<Card style={{ marginTop: 15 }}>
<Form style={{ marginBottom: 15 }} labelPosition='left' getFormApi={formApi => {
form.current = formApi
}}>
<Row style={{ display: 'flex',alignItems:'center'}}>
<Col span={5}>
<Form.Input field='keywords' noLabel={true} style={{ marginBottom: 0, width: '95%' }} placeholder="关键词:测点位置、监测因素" onChange={handleInputChange}>
</Form.Input>
</Col>
<Col span={2}>
<Form.Switch label='是否启用' field='switch' style={{ marginBottom: 0 }} checked onChange={onSwitchChange} >
</Form.Switch>
</Col>
<Col span={2}>
<Popconfirm style={{ marginBottom: 0 }} title="确认批量删除选中的参数配置?" onConfirm={batchDelete}>
<Button style={{ fontSize: 13 }} theme='borderless' type='danger'>批量删除</Button>
</Popconfirm>
</Col>
<Col span={2}>
<IconPlus style={{ paddingTop: 9 }} onClick={iconClick} />
</Col>
</Row>
{showBatchConfig ?
<Row style={{ display: 'flex',alignItems:'center',marginTop: 10}}>
<Col span={3}>
<Form.Select showSearch filter label="分析时长" field="timeRange" onChange={rangeChange}
placeholder="请选择分析时长" style={{ width: 127, marginBottom: 0 }}>
<Select.Option value="1">1个月</Select.Option>
<Select.Option value="2">2个月</Select.Option>
<Select.Option value="3">3个月</Select.Option>
</Form.Select>
</Col>
<Col span={3}>
<Form.Input style={{ marginBottom: 0, width: '93%' }}
field='bv'
placeholder="毛刺阈值:数字" label="毛刺阈值"
onChange={e => bvValueBatch(e)}
rules={[{
required: true, message: "请输入正整数", validator: checkInterger
}]} trigger='blur'/>
</Col>
<Col span={3}>
<Form.Input
style={{ marginBottom: 0, width: '93%' }}
field='ws'
placeholder="滑动中值:正值"
label="滑动中值"
rules={[{
required: true, message: "请输入正整数", validator: checkInterger
}]}
onChange={e => winSizeBatch(e)}
trigger='blur'/>
</Col>
<Col span={3}>
<Form.Input
style={{ marginBottom: 0, width: '93%' }}
field='rc'
placeholder="滑动均值:正值"
label="滑动均值" rules={[{
required: true, message: "请输入正整数", validator: checkInterger
}]}
onChange={e => reCoefBatch(e)}
trigger='blur'/>
</Col>
<Col span={3}>
<Form.Input
style={{ marginBottom: 0, width: '93%' }}
field='dv'
placeholder="导数阈值:数字"
label="导数阈值"
rules={[{
required: true, message: "请输入正整数", validator: checkNumber
}]}
onChange={e => deValueBatch(e)}
trigger='blur'/>
</Col>
<Col span={3}>
<Form.Input
style={{ marginBottom: 0, width: '93%' }}
field='pn'
placeholder="渐变点个数:正值"
label="渐变点个数" rules={[{
required: true, message: "请输入正整数", validator: checkPoint
}]}
onChange={e => graPointBatch(e)}
trigger='blur'/>
</Col>
<Col span={3}>
<Form.Input
style={{ marginBottom: 0, width: '93%' }}
field='gv'
placeholder="渐变阈值:数字"
label="渐变阈值" rules={[{
required: true, message: "请输入正整数", validator: checkNumber
}]}
onChange={e => graValueBatch(e)}
trigger='blur'/>
</Col>
<Col span={2}>
<Button onClick={batchSave} theme='solid' type='primary'>批量保存</Button>
</Col>
</Row> : ''}
</Form>
<Table rowSelection={rowSelection} columns={columns} dataSource={tmpds} />
{modalVisible ? <TrendModal
// structId={structId}
visible={true}
project={ project}
closeModal={modalCancel}
modalData={modalData}
sensorId={sensorId}
/> : ''}
</Card>
);
}
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)

9
web/client/src/sections/install/nav-item.jsx

@ -25,6 +25,15 @@ export function getNavItem (user, dispatch) {
items: [{ items: [{
itemKey: 'system', to: '/install/mapping/system', text: '系统映射' itemKey: 'system', to: '/install/mapping/system', text: '系统映射'
}] }]
},
{
itemKey: 'data',
text: '数据计算',
icon: <iconpark-icon style={{ width: 20, height: 20 }} name="iconjianshezhong"></iconpark-icon>,
to: '/install/data/cacl',
items: [{
itemKey: 'cacl', to: '/install/data/cacl', text: '异常数据识别'
}]
}, },
// { // {
// itemKey: 'order ', // itemKey: 'order ',

15
web/client/src/sections/install/routes.js

@ -1,5 +1,6 @@
'use strict'; 'use strict';
import { Roles, System, Setup, Set } from './containers'; import { Roles, System, Setup, Set,Cacl } from './containers/index.js';
export default [ export default [
{ {
@ -30,7 +31,17 @@ export default [
component: System, component: System,
breadcrumb: '系统映射', breadcrumb: '系统映射',
}] }]
}, { },{
path: '/data',
key: 'data',
breadcrumb: '数据计算',
childRoutes: [{
path: '/cacl',
key: 'cacl',
component: Cacl,
breadcrumb: '异常数据识别',
}]
}, {
path: '/order', path: '/order',
key: 'order', key: 'order',
breadcrumb: '工单管理', breadcrumb: '工单管理',

3
web/client/src/sections/install/style.less

@ -23,4 +23,5 @@
.separator{ .separator{
display: none; display: none;
} }
} }

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

@ -64,7 +64,7 @@ export const ApiTable = {
getVcmpAuth: 'vcmp/auth', // 获取视频平台应用鉴权token getVcmpAuth: 'vcmp/auth', // 获取视频平台应用鉴权token
getAlarmVideoExceptionType: 'alarm/video/exceptionType', //查询视频设备类型 getAlarmVideoExceptionType: 'alarm/video/exceptionType', //查询视频设备类型
// 数据查询 // 数据监测
getDataContinuityType: 'data/continuity/type_list', getDataContinuityType: 'data/continuity/type_list',
getDataContinuity: 'data/continuity', getDataContinuity: 'data/continuity',
getDataContinuityDetail: 'data/continuity/{continuityId}/detail', getDataContinuityDetail: 'data/continuity/{continuityId}/detail',
@ -174,7 +174,21 @@ export const ApiTable = {
//获取设备信息 //获取设备信息
getThingMessages: 'getThingMessages', 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}',//
}; };
// 项企的接口 // 项企的接口

3
web/package.json

@ -66,6 +66,9 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"fs-attachment": "^1.0.0", "fs-attachment": "^1.0.0",
"fs-web-server-scaffold": "^1.0.6", "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-better-http-proxy": "^0.2.5",
"koa-proxy": "^1.0.0-alpha.3", "koa-proxy": "^1.0.0-alpha.3",
"koa-view": "^2.1.4", "koa-view": "^2.1.4",

Loading…
Cancel
Save