zhaobing’
1 year ago
40 changed files with 5115 additions and 54 deletions
@ -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 |
|||
}; |
@ -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 |
|||
} |
@ -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; |
|||
}; |
@ -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; |
|||
}; |
@ -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) |
|||
|
|||
} |
@ -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); |
|||
} |
@ -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)) |
|||
|
|||
|
@ -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 |
|||
); |
@ -1,7 +1,7 @@ |
|||
'use strict'; |
|||
|
|||
import * as dataQuery from './dataQuery' |
|||
|
|||
import * as monitor from './monitor' |
|||
export default { |
|||
...dataQuery |
|||
...dataQuery,...monitor |
|||
} |
@ -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 } }, |
|||
}); |
|||
} |
@ -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; |
@ -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); |
|||
|
|||
|
|||
|
|||
|
|||
|
@ -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); |
@ -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); |
@ -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; |
|||
|
|||
} |
@ -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 } }, |
|||
}) |
|||
} |
@ -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; |
@ -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) |
@ -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) |
@ -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) |
@ -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); |
@ -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) |
@ -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) |
|||
|
@ -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); |
|||
|
|||
|
@ -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) |
@ -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) |
|||
|
Loading…
Reference in new issue