zhaobing’
1 year ago
11 changed files with 1213 additions and 146 deletions
@ -0,0 +1,582 @@ |
|||
'use strict'; |
|||
import React, { useState, useEffect } from 'react'; |
|||
import PropTypes from 'prop-types'; |
|||
import { Button, Notification } from "@douyinfe/semi-ui"; |
|||
import moment from 'moment'; |
|||
import XLSX from 'xlsx'; |
|||
import { fromJS } from 'immutable'; |
|||
import { Request } from '@peace/utils'; |
|||
import FileSaver from 'file-saver'; |
|||
import { IconArrowDown } from '@douyinfe/semi-icons'; |
|||
|
|||
//通用前端导出组件 使用方法查看底部propTypes |
|||
const ExportData = ({...props}) => { |
|||
//const [form] = Form.useForm(); |
|||
const [exportLoading, setExportLoading] = useState(false); |
|||
const { customRender, title, exportType, style, showIcon } = props; |
|||
|
|||
|
|||
const loop = (data, keypath, values) => { // deal with array |
|||
let dkey = keypath.slice(0, 1)[0]; |
|||
if (dkey) { |
|||
let dvalue = data[dkey]; |
|||
let otherKeypath = keypath.slice(1); |
|||
if (Array.isArray(dvalue)) { |
|||
if (otherKeypath.length) { |
|||
let immutableData = fromJS(data); |
|||
for (let index = 0; index < dvalue.length; index++) { |
|||
let tmp = immutableData.getIn([dkey, index]).toJS(); |
|||
loop(tmp, otherKeypath, values); |
|||
} |
|||
} |
|||
} else { |
|||
values.push(dvalue); |
|||
} |
|||
} |
|||
return values; |
|||
}; |
|||
const getColumnData = (opts) => { |
|||
const { data, keypath, render, spliter, rawdata, valueEnum } = opts; |
|||
let v = null; |
|||
let outer = data[keypath[0]]; |
|||
if (Array.isArray(outer)) { |
|||
let values = loop(data, keypath, []); |
|||
v = rawdata ? values : values.join(spliter || ','); |
|||
} else { |
|||
v = fromJS(data).getIn(keypath) |
|||
} |
|||
//处理proTable 枚举 |
|||
if(valueEnum && valueEnum[v]?.text){ |
|||
v = valueEnum[v]?.text; |
|||
} |
|||
//处理render |
|||
// if (render && typeof render === 'function') { |
|||
// v = render(outer, data); |
|||
|
|||
// } |
|||
return v; |
|||
}; |
|||
const getDataSource = (attrs, filterData) => { |
|||
debugger |
|||
let dataSource = filterData.map(item => { |
|||
let record = {}; |
|||
attrs.forEach(attr => { |
|||
const { key, dataIndex, render, child, valueEnum } = attr; |
|||
if (child) { |
|||
|
|||
record[key] = getDataSource(child, item[key] || []); |
|||
} else { |
|||
let v = getColumnData({ |
|||
data: item, |
|||
keypath: Array.isArray(dataIndex) ? dataIndex : [dataIndex], |
|||
render: render || null, |
|||
valueEnum: valueEnum |
|||
}); |
|||
record[key] = v; |
|||
} |
|||
|
|||
|
|||
}); |
|||
|
|||
return record; |
|||
}); |
|||
return dataSource; |
|||
|
|||
|
|||
}; |
|||
|
|||
const getNewColumns = (attrs) => { |
|||
return attrs.filter(f=> f.dataIndex).map(v=>{ |
|||
const { dataIndex } = v; |
|||
return { |
|||
...v, |
|||
key: Array.isArray(dataIndex) ? dataIndex.reduce((p,c)=>{ |
|||
p = `${p}${c.trim().replace(c[0], c[0].toUpperCase())}`; |
|||
return p |
|||
},'') : dataIndex |
|||
} |
|||
}) |
|||
} |
|||
//暂时只处理两层 |
|||
const getFlatData = (attrs, filterData, dataToAoa, deep = 0) => { |
|||
|
|||
filterData.map(item => { |
|||
let cur = dataToAoa[deep] |
|||
if (!cur) { |
|||
cur = dataToAoa[deep] = [] |
|||
} |
|||
attrs.map((attr, index) => { |
|||
const { key, child } = attr; |
|||
if (child) { |
|||
if (Array.isArray(item[key])) { |
|||
//getFlatData(child,item[key],dataToAoa,deep) |
|||
|
|||
item[key].map((s, i) => { |
|||
if (i == 0) { |
|||
child.map(c => { |
|||
cur.push(s[c.key]); |
|||
}) |
|||
} else { |
|||
deep++ |
|||
let childCur = dataToAoa[deep] = [] |
|||
pushNull(childCur, index); |
|||
child.map(c => { |
|||
childCur.push(s[c.key]); |
|||
}); |
|||
} |
|||
|
|||
}) |
|||
|
|||
} |
|||
} else { |
|||
cur.push(item[key]); |
|||
} |
|||
|
|||
}); |
|||
deep++ |
|||
}); |
|||
|
|||
|
|||
}; |
|||
|
|||
const getHeader = (headers, excelHeader, deep, perOffset) => { |
|||
let offset = 0 |
|||
let cur = excelHeader[deep] |
|||
if (!cur) { |
|||
cur = excelHeader[deep] = [] |
|||
} |
|||
pushNull(cur, perOffset - cur.length) |
|||
for (let i = 0; i < headers.length; i++) { |
|||
let head = headers[i] |
|||
cur.push(head.name) |
|||
if (head.hasOwnProperty('child') && Array.isArray(head.child) && head.child.length > 0) { |
|||
let childOffset = getHeader(head.child, excelHeader, deep + 1, cur.length - 1) |
|||
pushNull(cur, childOffset - 1) |
|||
offset += childOffset |
|||
} else { |
|||
offset++ |
|||
} |
|||
} |
|||
return offset; |
|||
} |
|||
|
|||
const pushNull = (arr, count) => { |
|||
for (let i = 0; i < count; i++) { |
|||
arr.push(null) |
|||
} |
|||
} |
|||
const fillNull = (arr) => { |
|||
let max = Math.max(...(arr.map(a => a.length))) |
|||
arr.filter(e => e.length < max).forEach(e => pushNull(e, max - e.length)) |
|||
} |
|||
const doMerges = (arr) => { |
|||
// 要么横向合并 要么纵向合并 |
|||
let deep = arr.length; |
|||
let merges = []; |
|||
for (let y = 0; y < deep; y++) { |
|||
// 先处理横向合并 |
|||
let row = arr[y]; |
|||
let colSpan = 0 |
|||
for (let x = 0; x < row.length; x++) { |
|||
if (row[x] === null) { |
|||
colSpan++ |
|||
if (((x + 1) === row.length) && (colSpan > 0 && x > colSpan)) { |
|||
merges.push({ s: { r: y, c: x - colSpan }, e: { r: y, c: x } }) |
|||
} |
|||
} else if (colSpan > 0 && x > colSpan) { |
|||
merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x - 1 } }) |
|||
colSpan = 0 |
|||
} else { |
|||
colSpan = 0 |
|||
} |
|||
} |
|||
} |
|||
// 再处理纵向合并 |
|||
let colLength = arr[0].length |
|||
for (let x = 0; x < colLength; x++) { |
|||
let rowSpan = 0 |
|||
for (let y = 0; y < deep; y++) { |
|||
if (arr[y][x] != null) { |
|||
rowSpan = 0 |
|||
} else { |
|||
rowSpan++; |
|||
} |
|||
} |
|||
if (rowSpan > 0) { |
|||
merges.push({ s: { r: deep - rowSpan - 1, c: x }, e: { r: deep - 1, c: x } }) |
|||
} |
|||
} |
|||
return merges; |
|||
} |
|||
|
|||
//内容暂只出了纵向合并 |
|||
const doContetMerges = (arr, headerLength) => { |
|||
let deep = arr.length; |
|||
let merges = []; |
|||
//处理纵向合并 |
|||
let colLength = arr[0].length |
|||
for (let x = 0; x < colLength; x++) { |
|||
let rowSpan = 0; |
|||
let mergY = 0; |
|||
for (let y = 0; y < deep; y++) { |
|||
if (rowSpan > 0) { |
|||
//如果还有null 继续加 |
|||
if (arr[y][x] === null) { |
|||
rowSpan = rowSpan + 1 |
|||
} else { |
|||
//不为null 增加merge |
|||
merges.push({ s: { r: headerLength + (y - rowSpan - 1), c: x }, e: { r: headerLength + y - 1, c: x } }); |
|||
rowSpan = 0; |
|||
} |
|||
} else { |
|||
if (arr[y][x] === null) { |
|||
rowSpan = rowSpan + 1 |
|||
} |
|||
} |
|||
|
|||
} |
|||
if (rowSpan > 0) { |
|||
merges.push({ s: { r: headerLength + (deep - rowSpan - 1), c: x }, e: { r: headerLength + deep - 1, c: x } }) |
|||
rowSpan = 0; |
|||
} |
|||
} |
|||
return merges; |
|||
} |
|||
|
|||
//导出可以纵向合并单元格的数据 不建议使用 |
|||
const exportMergeExcel = async () => { |
|||
setExportLoading(true) |
|||
const { columns, data, fileName, exportUrl, exportQuery, exportBody, requestType, header, showYearMouth } = props || {}; |
|||
|
|||
let resultData = []; |
|||
if (exportUrl) { |
|||
|
|||
resultData = requestType == 'post' ? await Request.post(exportUrl, exportBody || {}, exportQuery || {}).then(data => { |
|||
//数据接口返回的结果 如果是对象 必须把返回数组放入rows |
|||
if (typeof data === 'object' && data.rows) { |
|||
|
|||
return data.rows |
|||
|
|||
} else { |
|||
return data; |
|||
} |
|||
}, err => { |
|||
Notification.error({ |
|||
content: '获取数据失败,导出失败!', |
|||
duration: 3, |
|||
}) |
|||
}) : await Request.get(exportUrl, exportQuery || {}).then(data => { |
|||
if (typeof data === 'object' && data.rows) { |
|||
|
|||
return data.rows |
|||
|
|||
} else { |
|||
return data; |
|||
} |
|||
}, err => { |
|||
Notification.error({ |
|||
content: '获取数据失败,导出失败!', |
|||
duration: 3, |
|||
}) |
|||
}); |
|||
if (!resultData) { |
|||
return; |
|||
} |
|||
|
|||
} else { |
|||
resultData = data |
|||
} |
|||
let excelHeader = []; |
|||
const newColumns = getNewColumns(columns); |
|||
getHeader(newColumns, excelHeader, 0, 0); |
|||
fillNull(excelHeader); |
|||
|
|||
//console.log(excelHeader); |
|||
|
|||
let loopData = getDataSource(newColumns, resultData); |
|||
//console.log(loopData) |
|||
|
|||
let dataToAoa = []; |
|||
getFlatData(newColumns, loopData, dataToAoa, 0); |
|||
fillNull(dataToAoa); |
|||
//console.log(dataToAoa); |
|||
|
|||
let aoa = [].concat(excelHeader, dataToAoa); |
|||
//console.log(aoa) |
|||
|
|||
let headerMerges = doMerges(excelHeader); |
|||
let contentMerages = doContetMerges(dataToAoa, excelHeader.length); |
|||
let merges = [].concat(headerMerges, contentMerages); |
|||
// console.log(contentMerages) |
|||
|
|||
// let opts = { |
|||
// defaultCellStyle: { |
|||
// font: { name: "宋体", sz: 11, color: { auto: 1 } }, |
|||
// border: { |
|||
// color: { auto: 1 } |
|||
// }, |
|||
// alignment: { |
|||
// /// 自动换行 |
|||
// wrapText: 1, |
|||
// // 居中 |
|||
// horizontal: "center", |
|||
// vertical: "center", |
|||
// indent: 0 |
|||
// } |
|||
// } |
|||
// } |
|||
let sheet = XLSX.utils.aoa_to_sheet(aoa); |
|||
// let newSheet = {}; |
|||
// for (let [key, value] of Object.entries(sheet)) { |
|||
// if(key == '!ref'){ |
|||
// newSheet[key] = value |
|||
// }else if(typeof value === 'object'){ |
|||
// newSheet[key] = { |
|||
// ...value, |
|||
// s: opts.defaultCellStyle |
|||
// } |
|||
// } |
|||
// } |
|||
const wpx = columns.map(c => { |
|||
return { |
|||
wpx: Number.parseInt(c.wpx) ? Number.parseInt(c.wpx) : 100 |
|||
} |
|||
}) |
|||
sheet['!cols'] = wpx; |
|||
sheet['!merges'] = merges; |
|||
|
|||
// 构建 workbook 对象 |
|||
const workbook = XLSX.utils.book_new(); |
|||
|
|||
const time = moment().format('YYYY-MM-DD'); |
|||
|
|||
|
|||
XLSX.utils.book_append_sheet(workbook, sheet, 'mySheet'); |
|||
// 导出 Excel |
|||
XLSX.writeFile(workbook, fileName ? `${fileName}-${time}.xlsx` : `导出数据-${time}.xlsx`); |
|||
setExportLoading(false); |
|||
Notification.success({ |
|||
content: `成功导出了 ${loopData.length || 0} 条数据`, |
|||
duration: 3, |
|||
}) |
|||
} |
|||
//FileSaver 方式导出可以自定义样式 columns可定义 headStyle, rowStyle |
|||
const exportFileSaver = async () => { |
|||
setExportLoading(true) |
|||
const { columns, data, fileName, exportUrl, exportQuery, exportBody, requestType } = props || {}; |
|||
let resultData = []; |
|||
if (exportUrl) { |
|||
resultData = requestType == 'post' ? await Request.post(exportUrl, exportBody || {}, exportQuery || {}).then(data => { |
|||
//数据接口返回的结果 如果是对象 必须把返回数组放入rows |
|||
if (typeof data === 'object') { |
|||
return data.data ? data.data : data.rows |
|||
} else { |
|||
return data; |
|||
} |
|||
}, err => { |
|||
Notification.error({ |
|||
content: '获取数据失败,导出失败!', |
|||
duration: 3, |
|||
}) |
|||
}) : await Request.get(exportUrl, exportQuery || {}).then(data => { |
|||
|
|||
if (typeof data === 'object' && data.rows) { |
|||
return data.rows |
|||
} else { |
|||
return data; |
|||
} |
|||
}, err => { |
|||
Notification.error({ |
|||
content: '获取数据失败,导出失败!', |
|||
duration: 3, |
|||
}) |
|||
}); |
|||
if (!resultData) { |
|||
return; |
|||
} |
|||
|
|||
} else { |
|||
resultData = data |
|||
} |
|||
const newColumns = getNewColumns(columns); |
|||
|
|||
const loopData = getDataSource(newColumns, resultData); |
|||
|
|||
let content = ''; |
|||
let header = '<tr>'; |
|||
//header += `<th><div>序号</div></th>`; |
|||
newColumns.map(colum => { |
|||
header += `<th style="${colum.headStyle || ''}"><div>${colum.title}</div></th>` |
|||
}); |
|||
header += '</tr>'; |
|||
loopData.map(data => { |
|||
content += `<tr>`; |
|||
newColumns.map(c => { |
|||
if (c.style) { |
|||
content += `<th style="${c.rowStyle || ''}"><div>${data[c.key] || ''}</div></th>` |
|||
} else { |
|||
content += `<th><div>${data[c.key] || ''}</div></th>` |
|||
} |
|||
}); |
|||
content += `</tr>`; |
|||
}) |
|||
|
|||
let exportTable = `\uFEFF |
|||
<table style='text-alagin:center' border="1"> |
|||
${header} |
|||
${content} |
|||
</table> |
|||
`; |
|||
const time = moment().format('YYYY-MM-DD'); |
|||
let tempStrs = new Blob([exportTable], { type: 'text/xls' }) |
|||
FileSaver.saveAs(tempStrs, fileName ? `${fileName}-${time}.xls` : `导出数据-${time}.xlsx`); |
|||
setExportLoading(false); |
|||
Notification.success({ |
|||
content: `成功导出了 ${loopData.length || 0} 条数据`, |
|||
duration: 3, |
|||
}) |
|||
} |
|||
|
|||
//普通XLSX导出 |
|||
const exportExcel = async () => { |
|||
setExportLoading(true) |
|||
const { columns, data, fileName, exportUrl, exportQuery, exportBody, requestType } = props || {}; |
|||
|
|||
const newColumns = getNewColumns(columns); |
|||
|
|||
const _headers = newColumns |
|||
.map((item, i) => Object.assign({}, { key: item.key, title: item.title, position: String.fromCharCode(65 + i) + 1 })) |
|||
.reduce((prev, next) => Object.assign({}, prev, { [next.position]: { key: next.key, v: next.title } }), {}); |
|||
let resultData = []; |
|||
if (exportUrl) { |
|||
resultData = requestType == 'post' ? await Request.post(exportUrl, exportBody || {}, exportQuery || {}).then(data => { |
|||
//数据接口返回的结果 如果是对象 必须把返回数组放入rows |
|||
|
|||
if (typeof data === 'object' && (data.rows || data.data)) { |
|||
return data.data ? data.data : data.rows |
|||
} else { |
|||
return data; |
|||
} |
|||
}, err => { |
|||
Notification.error({ |
|||
content: '获取数据失败,导出失败!', |
|||
duration: 3, |
|||
}) |
|||
}) : await Request.get(exportUrl, exportQuery || {}).then(data => { |
|||
if (typeof data === 'object' && data.rows) { |
|||
return data.rows |
|||
|
|||
} else { |
|||
return data; |
|||
} |
|||
}, err => { |
|||
Notification.error({ |
|||
content: '获取数据失败,导出失败!', |
|||
duration: 3, |
|||
}) |
|||
}); |
|||
if (!resultData) { |
|||
return; |
|||
} |
|||
|
|||
} else { |
|||
resultData = data |
|||
} |
|||
|
|||
const loopData = getDataSource(newColumns, resultData); |
|||
|
|||
|
|||
const wpx = newColumns.map(c => { |
|||
return { |
|||
wpx: Number.parseInt(c.wpx) ? Number.parseInt(c.wpx) : 100 |
|||
} |
|||
}) |
|||
if (!(loopData.length > 0)) { |
|||
setExportLoading(false); |
|||
return; |
|||
} |
|||
const _data = loopData |
|||
.map((item, i) => newColumns.map((key, j) => Object.assign({}, { content: item[key.key], position: String.fromCharCode(65 + j) + (i + 2) }))) |
|||
// 对刚才的结果进行降维处理(二维数组变成一维数组) |
|||
.reduce((prev, next) => prev.concat(next)) |
|||
// 转换成 worksheet 需要的结构 |
|||
.reduce((prev, next) => Object.assign({}, prev, { [next.position]: { v: next.content } }), {}); |
|||
|
|||
// 合并 columns 和 data |
|||
const output = Object.assign({}, _headers, _data); |
|||
// 获取所有单元格的位置 |
|||
const outputPos = Object.keys(output); |
|||
// 计算出范围 ,["A1",..., "H2"] |
|||
const ref = `${outputPos[0]}:${outputPos[outputPos.length - 1]}`; |
|||
|
|||
// 构建 workbook 对象 |
|||
const workbook = { |
|||
SheetNames: ['mySheet'], |
|||
Sheets: { |
|||
mySheet: Object.assign( |
|||
{}, |
|||
output, |
|||
{ |
|||
'!ref': ref, |
|||
'!cols': wpx, |
|||
}, |
|||
), |
|||
}, |
|||
}; |
|||
const time = moment().format('YYYY-MM-DD'); |
|||
// 导出 Excel |
|||
XLSX.writeFile(workbook, fileName ? `${fileName}-${time}.xlsx` : `导出数据-${time}.xlsx`); |
|||
setExportLoading(false); |
|||
Notification.success({ |
|||
content: `成功导出了 ${loopData.length || 0} 条数据`, |
|||
duration: 3, |
|||
}) |
|||
} |
|||
|
|||
|
|||
const handleExport = async () => { |
|||
switch (exportType) { |
|||
case 'fileSaver': |
|||
await exportFileSaver(); |
|||
break; |
|||
case 'xlsx': |
|||
await exportExcel(); |
|||
break; |
|||
case 'merge': |
|||
await exportMergeExcel(); |
|||
break; |
|||
default: |
|||
await exportExcel(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
customRender ? |
|||
<span style={style} loading={exportLoading} onClick={handleExport}> |
|||
{customRender} |
|||
</span> : |
|||
|
|||
<Button style={style} loading={exportLoading} onClick={handleExport}> |
|||
{title || '导出'} |
|||
{showIcon && <IconArrowDown />} |
|||
</Button> |
|||
) |
|||
} |
|||
|
|||
ExportData.propTypes = { |
|||
fileName: PropTypes.string, //导出文件名称前缀 |
|||
showIcon: PropTypes.bool, //导出按钮是否显示icon,默认不显示 |
|||
customRender: PropTypes.element, //自定义导出组件渲染 不传默认按钮样式 |
|||
style: PropTypes.object,//透传style |
|||
title: PropTypes.string, //导出按钮文字 |
|||
columns: PropTypes.array.isRequired, //导出显示的header数组 兼容antd columns 可直接拿table或者protable的columns使用 注:columns每列的属性wpx设置导出的execl每列的宽度值 默认 100 |
|||
data: PropTypes.array.isRequired, //导出的数据 兼容antd table 数组嵌套处理,如果传入exportUrl 则从接口获取数据导出,此参数无效 |
|||
exportUrl: PropTypes.string, //导出数据从接口获取的url地址 返回的数据1、数组必须支持columns的设置 ,2、如果是对象,数组需放在rows属性上 |
|||
exportBody: PropTypes.object, //导出数据接口body参数 |
|||
exportQuery: PropTypes.object, //导出数据从接口获取的url地址上的参数 |
|||
requestType: PropTypes.string, //请求类型 get,post,默认get |
|||
exportType: PropTypes.string, //导出执行类型函数 'fileSaver','xlsx','merge'纵向单元格合并 |
|||
}; |
|||
|
|||
export default ExportData; |
@ -1,66 +1,340 @@ |
|||
import React, { useEffect, useState, useRef } from 'react'; |
|||
import { connect } from 'react-redux'; |
|||
import ReactECharts from 'echarts-for-react'; |
|||
import echarts from 'echarts'; |
|||
import { Spin, Card, CardGroup, Form, Button,Table } from '@douyinfe/semi-ui'; |
|||
import React, { useEffect, useState, useRef, useMemo } from 'react' |
|||
import { connect } from 'react-redux' |
|||
import { Spin, Card, CardGroup, Form, Button, Table, Pagination, Tooltip } from '@douyinfe/semi-ui' |
|||
import ExportData from '../components/export-data' |
|||
import moment from 'moment' |
|||
|
|||
const Network = props => { |
|||
const { dispatch, actions, user, clientHeight, thingId, deviceListAlarms, devicesCardStatusList, project } = props |
|||
const { analysis } = actions |
|||
const form = useRef() //表单 |
|||
const [deployData, setDeployData] = useState([]) |
|||
const [deviceData, setDeviceData] = useState([]) |
|||
const [deviceMetasDeployed, setDeviceMetasDeployed] = useState([]) |
|||
const [sensorId, setSensorId] = useState([]) |
|||
const [sensorsDataItems, setSensorsDataItems] = useState({}) |
|||
const [tableData, setTableData] = useState([]) //最新一次的数据 |
|||
const [lastData, setLastData] = useState([]) //最终数据 |
|||
const [lastDataCopy, setLastDataCopy] = useState([]) //最终数据 |
|||
const [searchType, setSearchType] = useState('') |
|||
const [searchName, setSearchName] = useState('') |
|||
const [typeList, setTypeList] = useState([]) |
|||
const [query, setQuery] = useState({ limit: 10, page: 0 }) //页码信息 |
|||
|
|||
const DeviceTypes = { |
|||
'DTU': 'DTU', |
|||
'gateway': '网关', |
|||
'sensor': '传感器', |
|||
'acqUnit': '采集单元', |
|||
'dau.gateway': '分布式智能云采集网关', |
|||
'dau.node': '分布式智能云采集节点', |
|||
'tcp.dtu': '工作站', |
|||
} |
|||
|
|||
const Network = (props) => { |
|||
const { dispatch, actions, user, clientHeight } = props |
|||
useEffect(() => { |
|||
setLastData([]) |
|||
setLastDataCopy([]) |
|||
}, [project]) |
|||
|
|||
const form = useRef();//表单 |
|||
useEffect(() => { |
|||
if (thingId) { |
|||
let dataList = [] |
|||
dispatch(analysis.getThingsDeploy(thingId)).then(rs => { |
|||
if (rs.success) { |
|||
setDeployData(rs.payload.data) |
|||
dataList = rs.payload.data |
|||
//列表渲染数据 |
|||
let da = [] |
|||
if (dataList.instances) { |
|||
Object.keys(dataList.instances).forEach(i => { |
|||
if (dataList.instances[i].type == 's.d') { |
|||
da.push({ |
|||
sensorId: i, |
|||
sensorName: dataList.instances[i]?.name, |
|||
deviceType: dataList?.instances[i]?.instance?.properties?.deviceType, |
|||
collectTime: '--', |
|||
data: '--', |
|||
iotCardStatus: '--', |
|||
status: '--', |
|||
option: '--', |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
dispatch(analysis.findDeviceMetaDeployed(thingId)).then(res => { |
|||
if (res.success) { |
|||
setDeviceMetasDeployed(res.payload.data) |
|||
const deviceMetaDeployed = res.payload.data |
|||
if (deviceMetaDeployed && dataList && deviceMetaDeployed.devices) { |
|||
const sensorsId = [] |
|||
let alarmSensorId = [] //所有设备的id |
|||
const sensorsDataItems = {} |
|||
for (const id in dataList.instances) { |
|||
alarmSensorId.push(id) |
|||
const instances = dataList.instances[id] |
|||
|
|||
useEffect(() => { |
|||
if (instances.type == 's.d' && instances.instance.properties.deviceType == 'sensor') { |
|||
const meta = deviceMetaDeployed.devices.find(m => m.id == instances.instance.deviceMetaId) |
|||
sensorsDataItems[id] = { |
|||
items: {}, |
|||
deviceName: instances.name, |
|||
} |
|||
if (meta) { |
|||
sensorsDataItems[id].items = meta.capabilities[0].properties.reduce((p, n) => { |
|||
if (n.category == 'Output') { |
|||
p[n.name] = { name: n.showName, unit: n.unit } |
|||
} |
|||
return p |
|||
}, {}) |
|||
} |
|||
sensorsId.push(id) |
|||
} |
|||
} |
|||
dispatch(analysis.getDevicesAlarms({ deviceIds: alarmSensorId }, { limit: 5 })) |
|||
dispatch(analysis.findDevicesCardStatus({ deviceIds: alarmSensorId })) |
|||
setSensorsDataItems(sensorsDataItems) |
|||
setSensorId(sensorsId) |
|||
setDeviceData(da) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
}, [thingId]) |
|||
|
|||
}, []) |
|||
const columns = [ |
|||
{ |
|||
title: '设备名称', |
|||
dataIndex: 'deviceName', |
|||
width: 200, |
|||
key:'deviceName' |
|||
}, |
|||
{ |
|||
title: '设备类型', |
|||
dataIndex: 'deviceType', |
|||
width:200, |
|||
key:'deviceType' |
|||
}, |
|||
{ |
|||
title: '最后采集时间', |
|||
dataIndex: 'collectTime', |
|||
width:200, |
|||
key:'collectTime' |
|||
useEffect(async () => { |
|||
if (sensorId && sensorId.length && sensorsDataItems) { |
|||
const rs = await dispatch(analysis.findSensorLastData(sensorId)) |
|||
const tableData = [] |
|||
if (rs.success) { |
|||
rs.payload.data.forEach(sd => { |
|||
if (Object.keys(sensorsDataItems).length) { |
|||
let sensorDataItem = sensorsDataItems[sd.sensorId] |
|||
let sensorName = sensorDataItem && sensorDataItem.deviceName ? sensorDataItem.deviceName : '' |
|||
let msg = sd.data.length |
|||
? sd.data[0] |
|||
: { |
|||
collectTime: null, |
|||
sensorName: sensorName, |
|||
data: { noData: '暂无数据' }, |
|||
} |
|||
let dataStr = '' |
|||
let dataKeys = Object.keys(msg.data) |
|||
dataKeys.forEach(k => { |
|||
let item = sensorDataItem && sensorDataItem.items ? sensorDataItem.items[k] : null |
|||
if (item) { |
|||
dataStr += `${item.name}:${msg.data[k]}(${item.unit}); ` |
|||
} else if (k == 'noData') { |
|||
dataStr += msg.data[k] |
|||
} else { |
|||
dataStr += `${k}:${msg.data[k]};` |
|||
} |
|||
}) |
|||
let collectTime = msg.collectTime ? moment(msg.collectTime).format('YYYY-MM-DD HH:mm:ss') : '--' |
|||
tableData.push({ |
|||
sensorId: sd.sensorId, |
|||
sensorName: sensorName, |
|||
collectTime: collectTime, |
|||
data: dataStr, |
|||
deviceType: 'sensor', //传感器 |
|||
iotCardStatus: '--', |
|||
status: '--', |
|||
option: '--', |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
setTableData(tableData) |
|||
} |
|||
}, [sensorId]) |
|||
useEffect(() => { |
|||
if (deviceData && deviceData.length && tableData && tableData.length) { |
|||
const dataD = deviceData?.map(p => { |
|||
const objRslt = tableData.find(q => q.sensorId == p.sensorId) |
|||
return { |
|||
sensorId: objRslt ? objRslt.sensorId : p.sensorId, |
|||
sensorName: objRslt ? objRslt.sensorName : p.sensorName, |
|||
collectTime: objRslt ? objRslt.collectTime : p.collectTime, |
|||
data: objRslt ? objRslt.data : p.data, |
|||
deviceType: DeviceTypes[objRslt ? objRslt.deviceType : p.deviceType], |
|||
iotCardStatus: |
|||
devicesCardStatusList && devicesCardStatusList.length |
|||
? devicesCardStatusList.find(v => v.deviceId == p.sensorId).status === 0 |
|||
? '正常' |
|||
: devicesCardStatusList.find(v => v.deviceId == p.sensorId).status === 1 |
|||
? '未激活' |
|||
: '停机' |
|||
: '--', |
|||
status: |
|||
deviceListAlarms && deviceListAlarms.length |
|||
? deviceListAlarms?.find(v => v.deviceId == p.sensorId) |
|||
? '异常' |
|||
: '正常' |
|||
: '正常', |
|||
option: objRslt ? objRslt.option : p.option, |
|||
} |
|||
}) |
|||
const typeList = dataD.reduce((p, c) => { |
|||
let isExist = p.some(q => q.label === c.deviceType) |
|||
if (!isExist) { |
|||
p.push({ label: c.deviceType, value: c.sensorId }) |
|||
} |
|||
return p |
|||
}, []) |
|||
setTypeList(typeList) |
|||
setLastData(dataD) |
|||
setLastDataCopy(dataD) |
|||
} |
|||
}, [deviceData, tableData]) |
|||
// const lastDataCopy=useMemo(()=>{ |
|||
// return lastData |
|||
// },[thingId]) |
|||
// const scroll = useMemo(() => ({ y: 400 }), []) |
|||
//名称回调事件 |
|||
const inputChange = e => { |
|||
setSearchName(e) |
|||
} |
|||
//选择设备类型下拉框回调 |
|||
const selectChange = e => { |
|||
setSearchType(typeList.find(f => f.value == e)?.label) |
|||
} |
|||
|
|||
//查询事件回调 |
|||
const searchHandler = () => { |
|||
setLastData( |
|||
searchName || searchType |
|||
? lastDataCopy.filter(f => f.sensorName.includes(searchName) && f.deviceType.includes(searchType)) |
|||
: lastDataCopy |
|||
) |
|||
} |
|||
|
|||
const columns = [ |
|||
{ |
|||
title: '设备名称', |
|||
dataIndex: 'sensorName', |
|||
width: 200, |
|||
key: 'sensorName', |
|||
render: (_, r) => { |
|||
return ( |
|||
<> |
|||
<Tooltip content={r.sensorName}> |
|||
<div>{r.sensorName.length > 7 ? `${r.sensorName.substr(0, 7)}...` : r.sensorName}</div> |
|||
</Tooltip> |
|||
</> |
|||
) |
|||
}, |
|||
{ |
|||
title: '更新日期', |
|||
dataIndex: 'updateTime', |
|||
sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1), |
|||
render: value => { |
|||
return dateFns.format(new Date(value), 'yyyy-MM-dd'); |
|||
}, |
|||
}, |
|||
{ |
|||
title: '设备类型', |
|||
dataIndex: 'deviceType', |
|||
width: 200, |
|||
key: 'deviceType', |
|||
}, |
|||
{ |
|||
title: '最后采集时间', |
|||
dataIndex: 'collectTime', |
|||
width: 200, |
|||
key: 'collectTime', |
|||
}, |
|||
{ |
|||
title: '数据', |
|||
dataIndex: 'data', |
|||
width: 200, |
|||
key: 'data', |
|||
render: (_, r) => { |
|||
return ( |
|||
<> |
|||
<Tooltip content={r.data}> |
|||
<div>{r.data.length > 6 ? `${r.data.substr(0, 6)}...` : r.data}</div> |
|||
</Tooltip> |
|||
</> |
|||
) |
|||
}, |
|||
]; |
|||
return ( |
|||
|
|||
}, |
|||
{ |
|||
title: '物联网卡状态', |
|||
width: 200, |
|||
dataIndex: 'iotCardStatus', |
|||
key: 'iotCardStatus', |
|||
}, |
|||
{ |
|||
title: '状态', |
|||
width: 200, |
|||
dataIndex: 'status', |
|||
key: 'status', |
|||
}, |
|||
{ |
|||
title: '操作', |
|||
width: 200, |
|||
dataIndex: 'option', |
|||
key: 'option', |
|||
}, |
|||
] |
|||
return ( |
|||
<> |
|||
<div style={{ marginBottom: 12, display: 'flex' }}> |
|||
<div> |
|||
<Form> |
|||
<Form.Input |
|||
// suffix={<IconSearch />} |
|||
field='name' |
|||
pure |
|||
showClear |
|||
label='名称' |
|||
style={{ width: 260, marginRight: 12 }} |
|||
placeholder='请输入设备名称' |
|||
onChange={inputChange} |
|||
/> |
|||
<Form.Select |
|||
optionList={typeList} |
|||
field='type' |
|||
pure |
|||
showClear |
|||
label='设备类型' |
|||
onChange={selectChange} |
|||
style={{ width: 260, marginLeft: 12, marginRight: 12 }} |
|||
placeholder='请选择设备类型' |
|||
/> |
|||
<Button theme='solid' type='primary' htmlType='submit' onClick={searchHandler}> |
|||
查询 |
|||
</Button> |
|||
</Form> |
|||
</div> |
|||
<div style={{ marginLeft: 10 }}> |
|||
{' '} |
|||
{lastData.length ? ( |
|||
<ExportData |
|||
// showIcon |
|||
fileName='设备列表' |
|||
exportType='fileSaver' |
|||
data={lastData} |
|||
columns={columns} |
|||
key='export' |
|||
/> |
|||
) : ( |
|||
'' |
|||
)} |
|||
</div> |
|||
</div> |
|||
<Table |
|||
columns={columns} |
|||
dataSource={dataSource} |
|||
></Table> |
|||
|
|||
) |
|||
// scroll={scroll} |
|||
columns={columns} |
|||
dataSource={lastData}></Table> |
|||
</> |
|||
) |
|||
} |
|||
|
|||
function mapStateToProps (state) { |
|||
const { auth, global, members, webSocket } = state; |
|||
return { |
|||
user: auth.user, |
|||
actions: global.actions, |
|||
clientHeight: global.clientHeight |
|||
}; |
|||
function mapStateToProps(state) { |
|||
const { auth, global, members, webSocket, deviceListAlarms, devicesCardStatus } = state |
|||
return { |
|||
user: auth.user, |
|||
actions: global.actions, |
|||
clientHeight: global.clientHeight, |
|||
deviceListAlarms: deviceListAlarms?.data || [], |
|||
devicesCardStatusList: devicesCardStatus?.data || [], |
|||
} |
|||
} |
|||
|
|||
export default connect(mapStateToProps)(Network); |
|||
export default connect(mapStateToProps)(Network) |
|||
|
Loading…
Reference in new issue