Browse Source

组网展示

dev
wenlele 1 year ago
parent
commit
a94e99d1b0
  1. 56
      api/app/lib/controllers/analysis/network.js
  2. 8
      api/app/lib/routes/analysis/network.js
  3. 8
      web/client/assets/js/g6/index.js
  4. 2
      web/client/index.html
  5. 26
      web/client/src/sections/analysis/actions/network.js
  6. 164
      web/client/src/sections/analysis/components/device-tree/dimensionOption.js
  7. 161
      web/client/src/sections/analysis/components/device-tree/edit-map.js
  8. 97
      web/client/src/sections/analysis/components/device-tree/equipment-option.js
  9. 294
      web/client/src/sections/analysis/components/device-tree/index.js
  10. 650
      web/client/src/sections/analysis/components/device-tree/option.js
  11. 76
      web/client/src/sections/analysis/components/device-tree/state.js
  12. 401
      web/client/src/sections/analysis/components/device-tree/zuwang-main.js
  13. 1
      web/client/src/sections/analysis/containers/network.jsx
  14. 43
      web/client/src/sections/analysis/containers/treeShow.jsx
  15. 9
      web/client/src/sections/analysis/style.less
  16. 5
      web/client/src/utils/webapi.js

56
api/app/lib/controllers/analysis/network.js

@ -28,14 +28,14 @@ async function getOrganizationsStruc (ctx) {
async function getThingsDeploy (ctx) { async function getThingsDeploy (ctx) {
let error = { name: 'FindError', message: '获取设备部署信息失败' }; let error = { name: 'FindError', message: '获取设备部署信息失败' };
let rslt = null, errStatus = null; let rslt = null, errStatus = null;
let { thingId } = ctx.params; let { iotaThingId } = ctx.params;
try { try {
if (!thingId) { if (!iotaThingId) {
throw '缺少参数 thingId' throw '缺少参数 iotaThingId'
} }
let iotaResponse = await ctx.app.fs.iotRequest.get(`things/${thingId}/deploys`) let iotaResponse = await ctx.app.fs.iotRequest.get(`things/${iotaThingId}/deploys`)
rslt = iotaResponse rslt = JSON.parse(iotaResponse)
error = null; error = null;
} catch (err) { } catch (err) {
@ -54,13 +54,57 @@ async function getThingsDeploy (ctx) {
} }
} else { } else {
ctx.status = 200; ctx.status = 200;
ctx.body = rslt; ctx.body = rslt
}
}
async function getDeviceMetaDeployed (ctx) {
let error = { name: 'FindError', message: '设备部署原型获取失败' };
let rslt = null;
const { iotaThingId } = ctx.params;
try {
if (!iotaThingId) {
throw '缺少参数 iotaThingId'
}
let iotaResponse = await ctx.app.fs.iotRequest.get(`meta/things/${iotaThingId}/devices`);
ctx.status = 200;
ctx.body = JSON.parse(iotaResponse);
} catch (err) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${err}`);
} }
};
async function iotaLinkStatus (ctx, next) {
let error = { name: 'CreateError', message: '命令下发失败' };
const { iotaThingId } = ctx.params;
let rslt = null;
try {
if (!iotaThingId) {
throw '缺少参数 iotaThingId'
} }
let iotaResponse = await ctx.app.fs.iotRequest.get(`metrics/things/${iotaThingId}/devices/link_status`);
rslt = JSON.parse(iotaResponse);
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;
}
};
module.exports = { module.exports = {
getOrganizationsStruc, getOrganizationsStruc,
getThingsDeploy, getThingsDeploy,
getDeviceMetaDeployed,
iotaLinkStatus,
} }

8
api/app/lib/routes/analysis/network.js

@ -5,10 +5,14 @@ module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/organizations/:pepProjectId/struc'] = { content: '获取项目下的结构物信息', visible: true }; app.fs.api.logAttr['GET/organizations/:pepProjectId/struc'] = { content: '获取项目下的结构物信息', visible: true };
router.get('/organizations/:pepProjectId/struc', network.getOrganizationsStruc) router.get('/organizations/:pepProjectId/struc', network.getOrganizationsStruc)
app.fs.api.logAttr['GET/things/:thingId/deploy'] = { content: '获取设备部署信息', visible: true }; app.fs.api.logAttr['GET/things/:iotaThingId/deploy'] = { content: '获取设备部署信息', visible: true };
router.get('/things/:thingId/deploy', network.getThingsDeploy) router.get('/things/:iotaThingId/deploy', network.getThingsDeploy)
app.fs.api.logAttr['GET/meta/things/:iotaThingId/devices'] = { content: '获取部署设备原型', visible: true };
router.get('/meta/things/:iotaThingId/devices', network.getDeviceMetaDeployed)
app.fs.api.logAttr['GET/metrics/things/:iotaThingId/devices/link_status'] = { content: '获取设备在线状态/以结构物id集体获取', visible: true };
router.get('/metrics/things/:iotaThingId/devices/link_status', network.iotaLinkStatus)
// app.fs.api.logAttr['GET/systemAvailability'] = { content: '获取系统可用性', visible: true }; // app.fs.api.logAttr['GET/systemAvailability'] = { content: '获取系统可用性', visible: true };
// router.get('/systemAvailability', operationData.getSystemAvailability) // router.get('/systemAvailability', operationData.getSystemAvailability)

8
web/client/assets/js/g6/index.js

File diff suppressed because one or more lines are too long

2
web/client/index.html

@ -27,7 +27,7 @@
<!-- Webpack --> <!-- Webpack -->
<script type="text/javascript" src="http://localhost:5601/client/build/app.js"></script> <script type="text/javascript" src="http://localhost:5601/client/build/app.js"></script>
<script type="text/javascript" src="/assets/js/g6/index.js"></script>
<!-- Vite --> <!-- Vite -->
<!-- <script type="module"> <!-- <script type="module">
import RefreshRuntime from "http://localhost:5602/@react-refresh" import RefreshRuntime from "http://localhost:5602/@react-refresh"

26
web/client/src/sections/analysis/actions/network.js

@ -20,13 +20,35 @@ export function getThingsDeploy (id) {
type: 'get', type: 'get',
dispatch: dispatch, dispatch: dispatch,
actionType: 'GET_THINGS_DEPLOY', actionType: 'GET_THINGS_DEPLOY',
url: `${ApiTable.thingsDeploy.replace('{thingId}', id)}`, url: `${ApiTable.thingsDeploy.replace('{iotaThingId}', id)}`,
msg: { error: '获取设备部署信息失败' }, msg: { error: '获取设备部署信息失败' },
reducer: { name: 'thingsDeploy' } reducer: { name: 'deviceList' }
}) })
} }
export function getDeviceMetaDeployed (id) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
actionType: 'GET_DEVIC_META_DEPLOYED',
url: `${ApiTable.deviceMetaDeployed.replace('{iotaThingId}', id)}`,
msg: { error: '获取部署设备原型失败' },
reducer: { name: 'deviceMeta' }
})
}
export function getIotaThingsLlinkStatus (id) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
actionType: 'GET_IOTA_ThINGS_LINK_STATUS',
url: `${ApiTable.getIotaThingsLlinkStatus.replace('{iotaThingId}', id)}`,
msg: { error: '获取设备在线状态/以结构物id集体获取失败' },
reducer: { name: '' }
})
}
// export function getSystemAvailability() { // export function getSystemAvailability() {

164
web/client/src/sections/analysis/components/device-tree/dimensionOption.js

@ -0,0 +1,164 @@
'use strict'
import React, { Component } from 'react';
import { Form, Select, DatePicker, Input, InputNumber,} from '@douyinfe/semi-ui';
import moment from 'moment';
import Style from '../../style.less';
const FormItem = Form.Item;
const Option = Select.Option;
class DimensionOption extends Component {
constructor(props) {
super(props);
this.formRef = React.createRef();
this.state = {
dimensionUnit: props.info ?
props.info.type == "dimension" ?
props.info.dimension.scheme ? props.info.dimension.scheme.unit : "minute"
: null
: null,
dimensionMode: props.info ?
props.info.type == "dimension" ?
props.info.dimension.scheme ? props.info.dimension.scheme.mode : "R"
: null
: null,
showSenior: false,
}
}
getFormItems = (info) => {
const { isEdit } = this.props;
const { dimensionMode } = this.state;
const formItemDimensionLayout = {
labelCol: { span: 7 },
wrapperCol: { span: 15 }
}
if (info) {
if (info.type == "dimension") {
return (
<div>
<Form
ref={this.formRef}
name="dimensionForm"
id="dimensionForm"
scrollToFirstError
>
<FormItem {...formItemDimensionLayout} label="采集策略"
name={`dimensionName`}
initialValue={info.hasOwnProperty('dimension') && info.dimension.scheme ? info.dimension.scheme.name : info.dimension.name}
>
<Input disabled={true} />
</FormItem>
<FormItem {...formItemDimensionLayout} label="监测方式"
name={`mode`}
initialValue={info.hasOwnProperty('dimension') && info.dimension.scheme ? info.dimension.scheme.mode : "R"}
>
<Select>
<Option value="R">周期</Option>
<Option value="L">监听</Option>
</Select>
</FormItem>
<FormItem {...formItemDimensionLayout} label="间隔" style={dimensionMode == "R" ? {} : { display: "none" }}
name={`interval_1`}
initialValue={info.hasOwnProperty('dimension') && info.dimension.scheme ? info.dimension.scheme.interval : 30}
>
<InputNumber style={{ width: '75%', marginRight: 0 }} disabled={!isEdit} />
<Select value={this.state.dimensionUnit} style={{ width: '25%' }} >
<Option value="month"></Option>
<Option value="week"></Option>
<Option value="day"></Option>
<Option value="hour"></Option>
<Option value="minute"></Option>
{/* <Options value="second">秒</Options> */}
</Select>
</FormItem>
<FormItem {...formItemDimensionLayout} label="开始时间"
name={`beginTime`}
initialValue={info.hasOwnProperty('dimension') && info.dimension.scheme ? info.dimension.scheme.beginTime ? moment(info.dimension.scheme.beginTime) : moment() : moment()}
>
<DatePicker
showTime
format="YYYY-MM-DD HH:mm:ss"
placeholder="选择开始时间"
/>
</FormItem>
<FormItem {...formItemDimensionLayout} label="结束时间"
name={`endTime`}
initialValue={info.hasOwnProperty('dimension') && info.dimension.scheme ? info.dimension.scheme.endTime ? moment(info.dimension.scheme.endTime) : null : null}
>
<DatePicker
showTime
format="YYYY-MM-DD HH:mm:ss"
placeholder="结束时间,未设置表示无期限"
/>
</FormItem>
<FormItem {...formItemDimensionLayout} label="方案数据通知" style={{ display: "none" }}
name={`notifyMode`}
initialValue={info.hasOwnProperty('dimension') && info.dimension.scheme ? info.dimension.scheme.notifyMode.toString() : "1"}
>
<Select>
<Option value="1">所有设备</Option>
<Option value="2">单个设备</Option>
</Select>
</FormItem>
<FormItem {...formItemDimensionLayout} label="设备数据通知" style={{ display: "none" }}
name={'dimension'}
initialValue={info.hasOwnProperty('dimension') && info.dimension.scheme ? info.dimension.scheme.capabilityNotifyMode.toString() : "1"}
>
<Select>
<Option value="1">组合</Option>
<Option value="2">每次</Option>
</Select>
</FormItem>
</Form>
</div>
);
}
}
}
componentDidMount() {
}
UNSAFE_componentWillReceiveProps = (nextProps) => {
if (nextProps.info != this.props.info) {
const { info } = nextProps
const curForm = this.formRef.current
if (curForm) {
this.formRef.current.resetFields();
}
this.setState({
dimensionUnit: info ? info.type == "dimension" ? info.hasOwnProperty('dimension') && info.dimension.scheme ? info.dimension.scheme.unit : "minute" : null : null,
dimensionMode: info ? info.type == "dimension" ? info.hasOwnProperty('dimension') && info.dimension.scheme ? info.dimension.scheme.mode : "R" : null : null
})
}
}
render() {
const { isEdit, info, height } = this.props;
return (
<div className={Style.option_modal} id="OptionModal" style={{ height: height - 72 }}>
{
info ?
<div>
{this.getFormItems(info)}
</div>
: null
}
</div>
)
}
}
export default DimensionOption;

161
web/client/src/sections/analysis/components/device-tree/edit-map.js

@ -0,0 +1,161 @@
import React, { useState, useEffect } from 'react';
import { PinyinHelper } from '@peace/utils';
// const { G6 } = window;
// import G6 from '@antv/g6';
let uniqueId = 0;
function generateUniqueId() {
return `rc-g6-${uniqueId++}`;
}
function createG6(__operation) {
const editMap = ({ ...props }) => {
const {
emitChange, changeFinish, width, height, handelNodeClick, handleNodeEnter,
onCollapse, setG6Tree, setG6TreeData, data, targetNode, emitDataChange_,
} = props;
const [treeRef, setTreeRef] = useState(null);
const [isItemClicking, setIsItemClicking] = useState(false);
const [orientation, setOrientation] = useState(false);
const [treeId, setTreeId] = useState(generateUniqueId());
// let treeId = generateUniqueId();
const themeKey = localStorage.getItem('theme-name');
const selectNode = (node) => {
handelNodeClick && handelNodeClick(node);
};
const toolTipMsg = (node) => handleNodeEnter(node);
const initTree = (prop) => {
const { Global } = G6;
Global.nodeAcitveBoxStyle = {
stroke: '#108EE9',
fill: '#00B5F4',
fillOpacity: 0.2,
lineWidth: 2,
radius: 4,
};
const grid = themeKey === 'light' ? {
grid: {
cell: 10,
line: {
stroke: '#eee',
},
},
} : {};
const tree = new G6.Tree({
id: treeId,
fitView: 'cc',
layoutCfg: {
direction: 'TB',
getHGap(/* d */) {
return 10;
},
getVGap(/* d */) {
return 10;
},
},
...grid,
...prop,
});
tree.addBehaviour('default', ['clickActive']);
tree.addBehaviour('default', ['clickBlankClearActive']);
tree.tooltip({
title: '标题', // @type {String} 标题
split: '=>', // @type {String} 分割符号
dx: 10, // @type {Number} 水平偏移
dy: 10, // @type {Number} 竖直偏移
});
// tree.node().tooltip('采集策略为空,请在左上角【+ 采集策略】添加至少一种采集策略,再进行相关设备的添加配置');
tree.edge().shape('smooth');
tree.on('itemclick', (ev) => {
setIsItemClicking(true);
const { item } = ev;
const { shape } = ev;
if (shape && !shape.hasClass('Button')) {
selectNode(item);
}
}).on('itemunactived', (ev) => {
selectNode();
}).on('itemmouseenter', (ev) => {
if (ev.itemType !== 'node') {
return;
}
const dd = toolTipMsg(null);
if (dd) {
tree.tooltip({
split: '!',
dx: 10,
dy: 10,
});
} else {
tree.tooltip(false);
}
const keyShape = ev.item.getKeyShape();
keyShape.attr({
lineWidth: 2,
});
tree.refresh();
}).on('itemmouseleave', (ev) => {
if (ev.itemType !== 'node') {
return;
}
const keyShape = ev.item.getKeyShape();
keyShape.attr({
lineWidth: 1,
});
tree.refresh();
})
.on('collapse', (ev) => {
onCollapse(ev.item._attrs.model, true);
})
.on('spreadout', (ev) => {
onCollapse(ev.item._attrs.model, false);
});
__operation(tree);
setTreeRef(tree);
setG6Tree(tree);
setG6TreeData(data);
};
useEffect(() => {
initTree(props);
return () => {
treeRef && treeRef.destroy();
setTreeRef(null);
};
}, []);
useEffect(() => {
treeRef && treeRef.changeSize(width, height);
}, [width, height]);
useEffect(() => {
if (emitChange == true || data) {
treeRef && treeRef.changeData(data);
treeRef && treeRef.refresh();
changeFinish();
}
}, [emitChange, data]);
return (
<div id={treeId} />
);
};
return React.memo(editMap, (prevProps, nextProps) => {
if ((prevProps.emitChange !== nextProps.emitChange)
|| (nextProps.width !== prevProps.width)
|| (nextProps.height !== prevProps.height)) {
return false;
}
return true;
});
}
export default createG6;

97
web/client/src/sections/analysis/components/device-tree/equipment-option.js

@ -0,0 +1,97 @@
'use strict'
import React, { useState, useEffect } from 'react';
// import { useSafeState } from 'ahooks';
import { Tabs, message, } from '@douyinfe/semi-ui';
import moment from 'moment';
import Option from './option';
import DimensionOption from './dimensionOption';
import State from './state';
import { ApiTable } from '$utils';
import { Request } from '@peace/utils'
const TabPane = Tabs.TabPane;
export default ({ ...props }) => {
const { isEdit, devices, info, parentNode, dimensions, height, deviceMetas, dispatch } = props;
const [linkState, setLinkState] = useState(null);
const [alarmMsg, setAlarmMsg] = useState({ new: [], history: [] });
const [currentDeviceId, setCurrentDeviceId] = useState(null);
const [activeKey, setActiveKey] = useState('1');
const callback = (key) => {
setActiveKey(key)
}
useEffect(() => {
console.log(3213213, info);
if (info) {
if (info?.linkState) {
setLinkState(info.linkState)
}
// if (info?.device?.id && info.device.id != currentDeviceId) {
// setCurrentDeviceId(info.device.id);
// setActiveKey('1');
// let url = ApiTable.getDevicesLlinkStatus.replace('{deviceId}', info.device.id);
// Request.get(url)
// .then(res => {
// setLinkState(res.status)
// }, error => {
// message.warning('设备在线状态获取失败');
// });
// url = ApiTable.getDeviceAlarms.replace('{deviceId}', info.device.id);
// Request.get(url, { limit: 5 })
// .then(res => {
// setAlarmMsg({ new: res.new || [], history: res.history || [] })
// }
// , error => {
// message.warning('设备告警信息获取失败');
// });
// }
}
}, [info]);
let tab = "属性";
if (info && info?.type == "equipment") {
tab = "属性"
} else if (info && info?.type == "dimension") {
tab = "采集策略"
}
return (
<Tabs defaultActiveKey="1" activeKey={activeKey} onChange={callback} style={{ paddingRight: 16 }}>
<TabPane tab={tab} key="1">
{info && parentNode ?
info.type == "equipment" ?
<Option
isEdit={isEdit}
devices={devices}
info={info}
parentNode={parentNode}
deviceMetas={deviceMetas}
dimensions={dimensions}
height={height}
dispatch={dispatch}
/>
: < DimensionOption
isEdit={isEdit}
info={info}
height={height}
/>
: null
}
</TabPane>
{
parentNode && info && info.meta && info.meta.filteredResource && (JSON.parse(info.meta.filteredResource.properties).deviceType != 'dau.node') ?
<TabPane tab="状态" key="2">
<State
linkState={linkState}
alarmMsg={alarmMsg}
/>
</TabPane> : null
}
</Tabs>
)
}

294
web/client/src/sections/analysis/components/device-tree/index.js

@ -0,0 +1,294 @@
import React, { useState, useEffect } from 'react';
import moment from 'moment';
import { Input, Button, SideSheet, } from '@douyinfe/semi-ui';
// import { useSafeState } from 'ahooks';
import Immutable from 'immutable';
import { AuthorizationCode } from '$utils';
import { Func } from '@peace/utils';
// import { PlusOutlined } from '@ant-design/icons';
import { IconPlus } from '@douyinfe/semi-icons';
import EquipmentOption from './equipment-option';
import ZuwangMain from './zuwang-main';
const { ModifyEquipmentNetworking } = AuthorizationCode;
export default function ({ ...props }) {
const {
dispatch, clientHeight, deviceMetasWithFollow, deviceList, dimensionlist, struct,
} = props;
const [g6TreeDirection, setG6TreeDirection] = useState('TB');
const [devices, setDevices] = useState(null);
const [selectedNode, setSelectedNode] = useState(null);
const [selectedParentNode, setSelectedParentNode] = useState(null);
const [emitDataChange, setEmitDataChange] = useState(false);
const [collapseAll, setCollapseAll] = useState(false);
const [collapsed, setCollapsed] = useState([]);
const [expandAll, setExpandAll] = useState(false);
const [expanded, setExpanded] = useState([]);
const [targetNode, setTargetNode] = useState('');
const [showOptionModal, setShowOptionModal] = useState(false);
const [clientWidth, setClientWidth] = useState(document.body.clientWidth - 64 - 20 - 16 - 2);
const [dimensions, setDimensions] = useState({});
const [sensorsId, setSensorsId] = useState([]);
const [sensorsDataItems, setSensorsDataItems] = useState({});
const [foldAllG6ToCenter, setFoldAllG6ToCenter] = useState(false);
const [inputSearching, setInputSearching] = useState(false);
useEffect(() => {
if (deviceMetasWithFollow?.devices) {
if (deviceList?.instances) {
const sensorsId = [];
const sensorsDataItems = {};
for (const id in deviceList.instances) {
const instances = deviceList.instances[id];
if (instances.type == 's.d' && instances.instance.properties.deviceType == 'sensor') {
const meta = deviceMetasWithFollow.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);
}
}
setSensorsId(sensorsId);
setSensorsDataItems(sensorsDataItems);
}
}
if (deviceList?.instances) {
const newInstances = { ...deviceList.instances, ...dimensionlist };
setDevices({ ...deviceList, instances: newInstances });
} else if (!devices) {
const data = { instances: {} };
const uuidNode = guid();
data.instances[uuidNode] = {
instance: {},
svg: {
compX: 0,
compY: 0,
isSelected: false,
rotateAng: 0,
scaleX: 1,
scaleY: 1,
x: 2498,
y: 1083,
},
type: 's.iota',
};
data.settings = {
grid: {
step: 20,
},
height: 3072,
padding: 40,
scale: 10,
scrollLeft: 2047,
scrollTop: 1082,
width: 5120,
};
setDevices(data);
}
}, [deviceList, deviceMetasWithFollow]);
const guid = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0; const
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
const closeOptionModal = () => {
setShowOptionModal(false);
setSelectedNode(null);
};
const _handelNodeClick = (node) => {
if (node) {
const targetId = node.get('model').id;
const selectedNode = { ...devices?.instances[targetId], id: targetId };
const deviceMetaId = selectedNode?.instance?.deviceMetaId;
const selectedDeviceMetaArr = [];
let selectedDeviceMeta;
if (selectedDeviceMetaArr.length == 0) {
selectedDeviceMeta = deviceMetasWithFollow && deviceMetasWithFollow.devices
? deviceMetasWithFollow.devices.filter((s) => s.id == deviceMetaId)[0]
: [];
} else {
selectedDeviceMeta = selectedDeviceMetaArr[0];
}
if (selectedNode.type && selectedNode.type != 's.iota') {
// parentNode
const parentNode = Object.keys(devices.instances).reduce((p, k) => {
const instance = devices.instances[k];
if (instance.type == 's.l' && instance.instance.from.ownerSvgId == targetId) {
p = { ...devices.instances[instance.instance.to.ownerSvgId], id: instance.instance.to.ownerSvgId };
}
return p;
}, null);
const selectedParentNodeMetaId = parentNode ? parentNode.instance.deviceMetaId : null;
const selectedParentNodeMeta = selectedParentNodeMetaId ? deviceMetasWithFollow.devices.filter((s) => s.id == selectedParentNodeMetaId)[0] : null;
setSelectedNode({ type: 'equipment', device: selectedNode, meta: selectedDeviceMeta });
setSelectedParentNode({ type: 'equipment', device: parentNode, meta: selectedParentNodeMeta });
setShowOptionModal(true);
} else {
setShowOptionModal(false);
setSelectedNode(null);
setSelectedParentNode(null);
}
} else {
setShowOptionModal(false);
setSelectedNode(null);
setSelectedParentNode(null);
}
};
const emitDataChange_ = () => {
setEmitDataChange(true);
};
const _finishDataChange = () => {
setEmitDataChange(false);
};
const handleNodeEnter = (node) => {
const result = dimensionlist?.dimensions?.length <= 0;
return result;
};
const onCollapse = (model, collapse) => {
if (collapse) {
setCollapsed(collapsed.concat([model.id]));
setExpanded(expanded.filter((c) => c != model.id));
} else {
setCollapsed(collapsed.filter((c) => c != model.id));
setExpanded(expanded.concat([model.id]));
}
};
const resetFoldAllG6ToCenter = () => {
setFoldAllG6ToCenter(false);
};
const changeG6TreeDirection = () => {
setG6TreeDirection(g6TreeDirection == 'TB' ? 'LR' : 'TB');
setShowOptionModal(false);
};
const containerHeight = clientHeight - 270;
return (
<div id="flag" style={{ position: "relative" }}>
<div style={{
position: 'absolute', top: 10, left: 0, zIndex: 2,
}}
>
<Button
style={{ marginRight: 12, marginBottom: 12, float: 'left' }}
type="primary" theme='solid'
onClick={() => {
setExpandAll(false);
setCollapseAll(true);
setCollapsed([]);
setExpanded([]);
setEmitDataChange(true);
// setFoldAllG6ToCenter(true);
setTargetNode('');
document.getElementById('searchInput').value = '';
}}
>
折叠全部
</Button>
<Button
type="primary" theme='solid'
style={{ marginRight: 12, marginBottom: 12, float: 'left' }}
onClick={() => {
setCollapseAll(false);
setExpandAll(true);
setCollapsed([]);
setExpanded([]);
setEmitDataChange(true);
setTargetNode('');
document.getElementById('searchInput').value = '';
}}
>
展开全部
</Button>
<Button type="primary" theme='solid' style={{ marginLeft: 12, marginBottom: 12 }} onClick={changeG6TreeDirection}>组图方向</Button>
<span style={{ float: 'left' }}>
<Input
style={{ width: 200 }}
id="searchInput"
onEnterPress={(e) => {
setTargetNode(e.target.value);
setInputSearching(true);
// localStorage.setItem('inputSearching', true)
}}
/>
</span>
</div>
{
devices?.instances
&& (
<ZuwangMain
data={devices.instances}
collapseAll={collapseAll}
expandAll={expandAll}
collapsed={collapsed}
expanded={expanded}
onCollapse={onCollapse}
width={clientWidth}
height={containerHeight}
emitChange={emitDataChange}
emitDataChange_={emitDataChange_}
changeFinish={_finishDataChange}
handelNodeClick={_handelNodeClick}
targetNode={targetNode}
inputSearching={inputSearching}
setInputSearching={setInputSearching}
struct={struct}
handleNodeEnter={handleNodeEnter}
foldAllG6ToCenter={foldAllG6ToCenter}
resetFoldAllG6ToCenter={resetFoldAllG6ToCenter}
g6TreeDirection={g6TreeDirection}
/>
)
}
<SideSheet
getPopupContainer={() => document.getElementById('flag')}
style={{ position: 'absolute' }}
mask={false}
maskClosable={false}
visible={showOptionModal}
width={480}
onCancel={closeOptionModal}
bodyStyle={{ padding: 8 }}
>
<EquipmentOption
isEdit={false}
struct={struct}
devices={devices}
deviceMetas={deviceMetasWithFollow}
info={selectedNode}
parentNode={selectedParentNode}
dimensions={dimensionlist}
height={containerHeight}
dispatch={dispatch}
/>
</SideSheet>
</div>
);
}

650
web/client/src/sections/analysis/components/device-tree/option.js

@ -0,0 +1,650 @@
'use strict';
import React, { Component } from 'react';
import { Form, Select, Input, InputNumber, Switch, Row, Col, Button, Popconfirm, Collapse, Tag } from '@douyinfe/semi-ui';
import PerfectScrollbar from 'perfect-scrollbar';
import Style from '../../style.less';
import { sort } from '@peace/utils';
const FormItem = Form.Item;
const Panel = Collapse.Panel
const productTypes = {
"DTU": "DTU",
"gateway": "网关",
"sensor": "传感器",
"acqUnit": "采集单元",
"dau.gateway": '分布式智能云采集网关',
"dau.node": '分布式智能云采集节点',
"tcp.dtu": '工作站'
}
class Option extends Component {
constructor(props) {
super(props);
this.saving = false;
this.formRef = React.createRef();
let tempShowSenior = false;
if (props.info && props.devices && props.info.type == "equipment" && props.info.meta &&
props.info.meta.interfaces && props.info.meta.interfaces.length && props.info.meta.capabilities &&
props.info.meta.capabilities.length && props.parentNode) {
let upInterface = this.getUpLinkInterface(props.info.meta, props.parentNode);
let interfaceMetaId = upInterface ? upInterface.id : null,
capabilityMetaIds = props.info.meta.capabilities.filter(c => c.interfaces.some(i => i.deviceMetaInterfaceId == interfaceMetaId) && c.capabilityCategoryId == 3),
capabilityMetaId = capabilityMetaIds.length > 0 ? capabilityMetaIds[0].id : null;
let targetDimension = interfaceMetaId && capabilityMetaId ? props.info.device.instance.interfaces[interfaceMetaId]?.capabilities[capabilityMetaId]?.dimension : null;
if (targetDimension && targetDimension.dimensionId) {
let devicesKeys = Object.keys(props.devices.instances);
if (devicesKeys.includes(targetDimension.dimensionId) && props.devices.instances[targetDimension.dimensionId].instance && props.devices.instances[targetDimension.dimensionId].instance.mode == "L") {
tempShowSenior = false;
} else {
tempShowSenior = true
}
} else {
tempShowSenior = false
}
} else {
tempShowSenior = false
}
this.state = {
showSenior: tempShowSenior,
selectedFormulaId: null
}
}
componentDidUpdate() {
this.Ps ? this.Ps.update() : ''
}
UNSAFE_componentWillReceiveProps = (nextProps) => {
if (nextProps.info != this.props.info) {
const { info, devices } = nextProps
const { current } = this.formRef
if (current) current.resetFields();
if (info && devices && info.type == "equipment" && info.meta && info.meta.interfaces.length && info.meta.capabilities.length) {
let upInterface = info.meta.interfaces.filter(i => i.directType == 1);
let interfaceMetaId = upInterface.length > 0 ? upInterface[0].id : null,
capabilityMetaId = info.meta.capabilities[0].id;
let targetDimension = interfaceMetaId ? info.device.instance.interfaces[interfaceMetaId] ? info.device.instance.interfaces[interfaceMetaId].capabilities[capabilityMetaId].dimension : null : null;
if (targetDimension && targetDimension.dimensionId) {
let devicesKeys = Object.keys(devices.instances);
if (devicesKeys.includes(targetDimension.dimensionId) && devices.instances[targetDimension.dimensionId].instance && devices.instances[targetDimension.dimensionId].instance.mode == "L") {
this.setState({ "showSenior": false });
} else {
this.setState({ "showSenior": true });
}
} else {
this.setState({ "showSenior": false });
}
} else {
this.setState({ "showSenior": false });
}
}
}
componentDidMount() {
this.Ps = new PerfectScrollbar(document.getElementById('optionModal'), { suppressScrollX: true })
}
_enableSelectStrategy(device, parentNode) {
if (device.capabilities.length == 0) return false;
const parentNodeCapabilities = parentNode.meta && parentNode.meta.capabilities.length > 0 ?
parentNode.meta.capabilities : null;
if ((device.capabilities.some(c => c.capabilityCategoryId == 3)) && (!parentNodeCapabilities || !parentNodeCapabilities.some(c => c.capabilityCategoryId == 3))) {
return true;
}
return false;
}
_renderDimensionType = () => {
const { dimensions } = this.props;
return dimensions.dimensions.map(item => {
return (
<Select.Option key={item.id} value={item.id}>{item.name}</Select.Option>
)
})
}
getInterfaceToRenderProp(meta, parentNode) {
let up = meta.interfaces.filter(i => i.directType == 1);
if (parentNode.meta) {
up = up.filter(i => parentNode.meta.interfaces.some(pi => pi.interfaceMetaId == i.interfaceMetaId && pi.directType == 2))
}
if (up.length > 0) {
return up[0];
}
return false;
}
getUpLinkInterface(meta, parentNode) {
let up = meta.interfaces.filter(i => i.directType == 1);
if (parentNode.meta && parentNode.meta.interfaces) {
up = up.filter(i => parentNode.meta.interfaces.some(pi => pi.interfaceMetaId == i.interfaceMetaId && pi.directType == 2))
}
if (up.length > 0) {
return up[0];
}
return false;
}
getChildNodes = (id) => {
const { devices } = this.props;
const existedEquipment = devices.instances
let equipmentKeys = Object.keys(existedEquipment),
childNodes = {};
for (let eq = 0; eq < equipmentKeys.length; eq++) {
if (existedEquipment[equipmentKeys[eq]].type == 's.l' && existedEquipment[equipmentKeys[eq]].instance.to.ownerSvgId == id) {
childNodes[existedEquipment[equipmentKeys[eq]].instance.from.ownerSvgId] = existedEquipment[existedEquipment[equipmentKeys[eq]].instance.from.ownerSvgId]
}
}
return childNodes;
}
getParentNodes = (id) => {
const { devices } = this.props;
const existedEquipment = devices.instances
let equipmentKeys = Object.keys(existedEquipment),
childNodes = {};
for (let eq = 0; eq < equipmentKeys.length; eq++) {
if (existedEquipment[equipmentKeys[eq]].type == 's.l' && existedEquipment[equipmentKeys[eq]].instance.from.ownerSvgId == id) {
childNodes[existedEquipment[equipmentKeys[eq]].instance.to.ownerSvgId] = existedEquipment[existedEquipment[equipmentKeys[eq]].instance.to.ownerSvgId]
}
}
return childNodes;
}
getFormItems = (info) => {
const { isEdit } = this.props;
const { showSenior } = this.state;
const formItemLayout = {
labelCol: { span: 7 },
wrapperCol: { span: 15 }
}
const capabilityMap = {
"dimensionId": "采集策略",
"interval": "间隔",
"repeats": "次数",
"timeout": "超时"
}
const initialCapabilityValue = {
"dimensionId": null,
"interval": 1,
"repeats": 1,
"timeout": 20
}
const DTURules = [{ required: true, message: '请输入接口id' }, { validator: this.checkDTUNum }];
const SIMRulies = [{ required: true, message: '请输入11位、13位或19位SIM卡号' }, { validator: this.checkSim }]
const HTTPSERVERIDRules = [{ required: true, message: '请输入' }, { validator: this.checkHttpServerId }]
let about = [], capabilityBox = {}, capabilityPropsBox = [], s = [], otherParameter = [];
if (info) {
if (info.type == "equipment") {
if (info.device.type != "s.iota") {
let device = info.device;
let meta = info.meta;
let properties = device.instance.properties,
interfaces = device.instance.interfaces,
name = device.instance.name;
about.push({
"key": "deviceType",
"value": Object.keys(productTypes).includes(
meta.filteredResource ?
JSON.parse(meta.filteredResource.properties).deviceType
: null
) ?
productTypes[JSON.parse(meta.filteredResource.properties).deviceType]
: JSON.parse(meta.filteredResource.properties).deviceType,
"showName": "设备类型",
"name": "设备类型",
"required": true,
"proType": "Constant"
})
about.push({ "key": "name", "value": name, "showName": "名称", "name": "名称", "proType": "Variable", validator: this.checkExistedName, required: true });
about.push({ "key": "company", "value": meta.vendor ? meta.vendor.name : '', "showName": "厂商", "name": "厂商", "proType": "Constant" });
about.push({ "key": "productName", "value": meta.name, "showName": "产品", "name": "产品", "proType": "Constant" });
about.push({ "key": "model", "value": meta.model, "showName": "型号", "name": "型号", "proType": "Constant" });
try {
meta.properties = sort(meta.properties, "showName")
} catch (error) {
}
for (let i = 0; i < meta.properties.length; i++) {
if (meta.properties[i].name != 'deviceType'
&& meta.properties[i].name != 'module'
&& meta.properties[i].name != 'moduleId'
&& meta.properties[i].name != 'channel'
&& meta.properties[i].name != 'channelId') {
about.push({
"key": meta.properties[i].name,
"value": properties[meta.properties[i].name],
"showName": meta.properties[i].showName,
"name": meta.properties[i].showName,
"required": meta.properties[i].required,
"proType": meta.properties[i].category,
enum: meta.properties[i].enum,
propertyTypeId: meta.properties[i].propertyTypeId,
unit: meta.properties[i].unit,
max: meta.properties[i].max,
min: meta.properties[i].min,
precision: meta.properties[i].precision,
isOtherParameter: meta.properties[i].isOtherParameter,
})
}
}
let interfaceToRender = this.getInterfaceToRenderProp(meta, this.props.parentNode);
if (interfaceToRender) {
let deviceInterface = device.instance.interfaces[interfaceToRender.id];
const hasChannel = interfaceToRender.interfaceMeta.properties.some(p => p.name == 'channel');
const hasId = interfaceToRender.interfaceMeta.properties.some(p => p.name == 'id');
const isSoipSensor = (interfaceToRender.interfaceMeta.interfaceTypeId == 4) && (JSON.parse(meta.filteredResource.properties).deviceType == 'sensor');
if (hasId && interfaceToRender.interfaceMeta.interfaceTypeId != 11 && meta.filteredResource && (isSoipSensor || ["gateway", 'dau.gateway', 'tcp.dtu'].some(t => t == JSON.parse(meta.filteredResource.properties).deviceType))) {
let SoIPInterfaceMeta = meta.interfaces.filter(face => face.directType == 1);
if (SoIPInterfaceMeta.length) {
let deviceInterface = device.instance.interfaces[SoIPInterfaceMeta[0].id];
about.push({ "key": "portID", "value": deviceInterface.properties.id, "showName": "接口ID(DTU ID)", "name": "接口ID(DTU ID)", "required": true, "proType": "Variable" })
}
}
try {
interfaceToRender.interfaceMeta.properties = sort(interfaceToRender.interfaceMeta.properties, "showName")
} catch (error) {
}
interfaceToRender.interfaceMeta.properties.forEach(p => {
let propType = null, required = null;
if ((p.name == 'id' && interfaceToRender.interfaceMeta.interfaceTypeId != 11) || p.name == 'vendor' || p.name == 'model') return;
if (interfaceToRender.interfaceMeta.interfaceTypeId == 4) {
if (['port', 'escape', 'protocol', 'escapegw'].some(n => p.name == n)) {
propType = 'Constant';
required = false;
}
}
let initialValue = deviceInterface?.properties[p.name] || null;
let validator;
if (p.name == 'module') {
initialValue = initialValue || device.instance.properties['moduleId'];
if (!hasChannel) {
validator = this.checkExists;
}
}
if (p.name == 'channel') {
initialValue = initialValue || device.instance.properties['channelId'];
validator = this.checkExists;
}
about.push({
"key": p.name,
"value": initialValue,
"showName": p.showName,
"name": p.showName,
required: required == null ? p.required : required,
validator: validator,
"proType": propType || p.category,
enum: p.enum,
propertyTypeId: p.propertyTypeId,
max: p.max,
min: p.min,
unit: p.unit,
precision: p.precision,
isOtherParameter: p.isOtherParameter
});
})
}
let capabilityFormulaProps = {};
let upLinkInterface = null;
if (meta.capabilities.length && meta.interfaces.length) {
upLinkInterface = this.getUpLinkInterface(meta, this.props.parentNode);
let interfacesMeta = upLinkInterface ? upLinkInterface.id : null,
capabilityIdMetas = meta.capabilities.filter(c => c.interfaces.some(i => i.deviceMetaInterfaceId == interfacesMeta) && c.capabilityCategoryId == 3),
capabilityIdMeta = capabilityIdMetas.length > 0 ? capabilityIdMetas[0].id : null;
if (interfacesMeta && capabilityIdMeta) {
let capabilityDimensions = interfaces[interfacesMeta]?.capabilities[capabilityIdMeta]?.dimension;
let capabilityProps = interfaces[interfacesMeta]?.capabilities[capabilityIdMeta]?.properties;
const capabilityFormula = interfaces[interfacesMeta]?.capabilities[capabilityIdMeta]?.formula;
if (capabilityFormula && capabilityFormula.formulaId) {
capabilityFormulaProps = capabilityFormula.properties;
}
if (capabilityDimensions) {
if (Object.keys(capabilityDimensions).length) {
let capabilityDimensionKeys = Object.keys(capabilityDimensions);
capabilityDimensionKeys.forEach(capItem => {
//interval 会重复要特殊处理
if(capItem == 'interval'){
capabilityBox[capItem] = ({ "key": 'dimensionInterval', "value": capabilityDimensions[capItem] ? capabilityDimensions[capItem] : initialCapabilityValue[capItem], "showName": capabilityMap[capItem], "required": true })
}else{
capabilityBox[capItem] = ({ "key": capItem, "value": capabilityDimensions[capItem] ? capabilityDimensions[capItem] : initialCapabilityValue[capItem], "showName": capabilityMap[capItem], "required": true })
}
})
} else { // 采集
capabilityBox = {
"dimensionId": { "key": "dimensionId", "value": null, "showName": "采集策略", "name": "采集策略", "required": true },
"interval": { "key": "dimensionInterval", "value": 1, "showName": "间隔", "name": "间隔", "required": true },
"repeats": { "key": "repeats", "value": 1, "showName": "次数", "name": "次数", "required": true },
"timeout": { "key": "timeout", "value": 20, "showName": "超时", "name": "超时", "required": true }
}
}
}
if (capabilityProps) {
let capabilityPropsKeys = Object.keys(capabilityProps);
capabilityPropsKeys.forEach(propItem => {
let propItemMetas = meta.capabilities.filter(cap => cap.id == capabilityIdMeta);
if (propItemMetas.length) {
let propItemMeta = propItemMetas[0].properties.filter(m => (m.category == "Variable" || m.name == "formula") && m.name == propItem)[0];
if (propItemMeta) {
capabilityPropsBox.push({
"key": propItemMeta.name,
"value": capabilityProps[propItem],
"showName": propItemMeta.showName,
"name": propItemMeta.showName,
"required": true,
isOtherParameter: propItemMeta.isOtherParameter
})
}
}
})
try {
capabilityPropsBox = sort(capabilityPropsBox);
} catch (error) {
}
}
}
}
about.forEach((item,index) => {
let validators = item.required ?
[{ required: true, message: `请输入${item.showName}` }] : [];
if (item.validator) {
validators.push({ validator: item.validator });
}
const precisionProps = item.precision > 0 ? {
precision: item.precision
}: {};
let parameter = (
<FormItem
key={`extItem1-${index}`}
{...formItemLayout}
label={item.showName + (item.unit ? '(' + item.unit + ')' : '')}
name={`${item.key}`}
initialValue={item.value}
valuePropName={item.propertyTypeId == 3 ? 'checked' : 'value'}
>
{
item.enum ?
<Select disabled={!isEdit || item.proType != "Variable"}>
{
item.enum.split(',').map(o => <Select.Option key={o}>{o}</Select.Option>)
}
</Select>
: (
item.propertyTypeId == 3 ?
<Switch checkedChildren={"是"} unCheckedChildren={"否"} disabled={!isEdit || item.proType != "Variable"} />
: (
item.propertyTypeId == 1 || (item.propertyTypeId == 2 && item.showName != '模量系数') ?
<InputNumber max={item.max || Infinity} min={item.min || -Infinity} {...precisionProps} disabled={!isEdit || item.proType != "Variable"} />
: <Input disabled={!isEdit || item.proType != "Variable"} />
)
)
}
</FormItem>
)
if (item.isOtherParameter) {
otherParameter.push(parameter)
} else {
s.push(parameter)
}
})
if (capabilityPropsBox.length) {
let capabilityFormula = capabilityPropsBox.filter(itemName => itemName.key == "formula"),
capabilityFormulaProps = capabilityPropsBox.filter(itemName => itemName.key != "formula");
capabilityFormula.length ?
s.push(
<FormItem
key={`extItem2`}
{...formItemLayout}
label={capabilityFormula[0].showName} style={{ marginBottom: 5 }}
name={`${capabilityFormula[0].key}`}
initialValue={capabilityFormula[0].value}
>
<Input disabled={true} />
</FormItem>
) : null;
let titleParameter = (
<Row key={`extItem3`} className={Style.optionNames}>
<Col span={8}>参数</Col>
<Col span={16}></Col>
</Row>
)
s.push(titleParameter)
capabilityFormulaProps.forEach((item, index) => {
let parameter = (
<FormItem
key={`extItem4-${index}`}
labelCol={{ span: 7 }}
wrapperCol={{ span: 15 }}
className={
index == capabilityFormulaProps.length - 1 ?
`${Style.last_moduleOptionItem} ${Style.moduleOptionItem}` : `${Style.moduleOptionItem}`
}
label={item.showName}
name={`${item.key}`}
initialValue={item.value}
>
<Input disabled={!isEdit} />
</FormItem>
)
s.push(parameter)
})
}
// 采集仪等父(上级)节点公式参数
const formulas = this.props.parentNode.meta ? this.props.parentNode.meta.formulas : [];//.length > 0 ? this.props.parentNode.meta.formulas : meta.formulas;
if (formulas.length > 0) {
s.push(
<FormItem
key={`extItem5`}
label="公式"
{...formItemLayout}
style={{ marginTop: 15 }}
name={'up_formula'}
initialValue={properties['up_formula']}
>
<Select size="large" disabled={!isEdit} placeholder="请选择公式" >
{formulas.map(f => <Select.Option key={f.id}>{f.name}</Select.Option>)}
</Select>
</FormItem >
);
const selectedFormula = formulas.filter(f => f.id == (this.state.selectedFormulaId || properties['up_formula']))[0];
s.push(
<FormItem
key={`extItem6`}
label="表达式"
{...formItemLayout}
hasFeedback
>
<Input.TextArea disabled value={selectedFormula ? selectedFormula.formula : ''} autosize={{ minRows: 2, maxRows: 6 }} />
</FormItem>
);
const formulaProperties = selectedFormula ? selectedFormula.properties : [];
if (formulaProperties.length > 0) {
s.push(
<Row key={`extItem7`} className={Style.formulaNames}>
<Col span={8}>参数</Col>
<Col span={16}></Col>
</Row>
)
formulaProperties.forEach((item,index) => {
const precisionProps = item.precision > 0 ? {
precision: item.precision
}: {};
s.push(
<FormItem
key={`extItem8-${index}`}
label={item.showName ? item.showName : `${item.name}(${item.showName})`}
labelCol={{ span: 8 }}
wrapperCol={{ span: 12 }}
className={Style.moduleFormuleItem}
name={`formula-p-${item.name}`}
initialValue={
capabilityFormulaProps[item.name] != null ?
capabilityFormulaProps[item.name] : (
properties[`formula-p-${item.name}`] != null ?
properties[`formula-p-${item.name}`] : properties[item.name]
)
}
>
{
item.enum ?
<Select disabled={!isEdit || item.category != "Variable"}>
{
item.enum.split(',').map(o => <Select.Option key={o}>{o}</Select.Option>)
}
</Select>
: (
item.propertyTypeId == 3 ?
<Switch checkedChildren={"是"} unCheckedChildren={"否"} disabled={!isEdit || item.category != "Variable"} />
: (
item.propertyTypeId == 1 || (item.propertyTypeId == 2 && item.showName != '模量系数') ?
<InputNumber max={item.max || Infinity} min={item.min || -Infinity} {...precisionProps} disabled={!isEdit || item.category != "Variable"} />
: <Input disabled={!isEdit || item.category != "Variable"} />
)
)
}
</FormItem>
)
})
}
}
if (meta.interfaces.length && this._enableSelectStrategy(meta, this.props.parentNode) && Object.keys(capabilityBox).length) {
let dimensionMeta = capabilityBox.dimensionId ?
this.props.dimensions.dimensions.filter(dimen => dimen.id == capabilityBox.dimensionId.value) : [];
s.push(
<div key={`extItem9`}>
<FormItem
{...formItemLayout}
label={capabilityBox.dimensionId.showName}
name={`${capabilityBox.dimensionId.key}`}
initialValue={dimensionMeta.length ? capabilityBox.dimensionId.value : null}
>
<Select size="large" disabled={!isEdit} placeholder="请选择采集策略">
{this._renderDimensionType()}
</Select>
</FormItem>
<div style={showSenior ? { display: 'block' } : { display: 'none' }}>
<FormItem
{...formItemLayout}
label={capabilityBox.repeats.showName}
name={`${capabilityBox.repeats.key}`}
initialValue={capabilityBox.repeats.value}
>
<Input disabled={!isEdit} />
</FormItem>
<FormItem
{...formItemLayout}
label={capabilityBox.interval.showName}
>
<FormItem
noStyle
name={`${capabilityBox.interval.key}`}
initialValue={capabilityBox.interval.value}
>
<Input disabled={!isEdit} style={{ width: "75%", marginRight: 0 }} />
</FormItem>
<Select defaultValue="second" disabled={!isEdit} style={{ width: "25%" }}>
<Select.Option value="second"></Select.Option>
</Select>
</FormItem>
<FormItem
{...formItemLayout}
label={capabilityBox.timeout.showName}
>
<FormItem
noStyle
name={`${capabilityBox.timeout.key}`}
initialValue={capabilityBox.timeout.value}
>
<Input disabled={!isEdit} style={{ width: "75%", marginRight: 0 }} />
</FormItem>
<Select defaultValue="second" disabled={!isEdit} style={{ width: "25%" }}>
<Select.Option value="second"></Select.Option>
</Select>
{
isEdit ?
<Tag color="orange">建议按照采集仪下配置设备数量*10填写</Tag> : null
}
</FormItem>
</div>
</div>
)
}
}
return (
<div>
{/* <Form
ref={this.formRef}
name="optionForm"
id="optionForm"
layout='horizontal'
scrollToFirstError
>
{s}
<Collapse accordion
style={
otherParameter.length ?
{ margin: '15px 7px', background: "transparent" }
: { marginTop: 15, background: "transparent", display: "none" }
}
>
<Panel header={'高级'} key="1">
<div>
{otherParameter}
</div>
</Panel>
</Collapse>
</Form> */}
</div>
);
}
}
}
render() {
const { info, height } = this.props;
return (
<div className={Style.option_modal} id="optionModal" style={{
height: height,
position: 'relative',
}}>
{
info ? this.getFormItems(info) : null
}
</div>
)
}
}
export default Option;

76
web/client/src/sections/analysis/components/device-tree/state.js

@ -0,0 +1,76 @@
'use strict'
import React, { Component } from 'react';
import { Row, Col, Timeline } from '@douyinfe/semi-ui';
class State extends Component {
constructor(props) {
super(props);
this.state = {
alarmMsgRowStyle: {
marginTop: 7,
},
alarmMsgColStyle: {
paddingTop: 3,
color: '#909090',
fontSize: 10
},
}
}
render() {
const { linkState, alarmMsg } = this.props
const { alarmMsgColStyle } = this.state
return (
<div style={{
// height:,
// overflow: 'auto'
}}>
<Row >
<Col span={1}></Col>
<Col span={22}>
<span>在线状态 </span>
{linkState == 1 ? <span style={{ color: 'green' }}>在线</span>
:
linkState == 0 ? <span style={{ color: 'red' }}>离线</span>
: <span style={{ color: '#787878' }}>未知</span>
}
<p style={{ marginTop: 10 }}>实时告警{alarmMsg.new.length > 0 ? '' : "无"} </p>
{
alarmMsg.new.length > 0 ? alarmMsg.new.map(s => <Row style={{ marginTop: 7 }}>
<Col span="1"></Col>
<Col span="23" style={{}}>{s.alarmContent}</Col>
<Col span="1"></Col>
<Col span="23" style={alarmMsgColStyle}>产生次数{s.alarmCount}</Col>
<Col span="1"></Col>
<Col span="23" style={alarmMsgColStyle}>产生时间{s.startTime}</Col>
<Col span="1"></Col>
<Col span="23" style={alarmMsgColStyle}>更新时间{s.endTime}</Col>
</Row>) : null
}
<p style={{ marginTop: 21 }}>历史告警{alarmMsg.history.length > 0 ? '' : "无"} </p>
<Timeline style={{ marginTop: 7, marginLeft: 13 }}>
{
alarmMsg.history.length > 0 ? alarmMsg.history.map(s =>
<Timeline.Item>
<p> {s.alarmContent} / <span style={alarmMsgColStyle}> {s.startTime}</span></p>
<p style={alarmMsgColStyle}>产生次数{s.alarmCount}</p>
<p style={alarmMsgColStyle}>更新时间{s.endTime}</p>
</Timeline.Item>
) : null
}
</Timeline>
</Col>
<Col span={1}></Col>
</Row>
</div>
)
}
}
export default State;

401
web/client/src/sections/analysis/components/device-tree/zuwang-main.js

@ -0,0 +1,401 @@
import React, { useState, useEffect, useBoolean } from 'react';
// import { useSafeState, useBoolean } from 'ahooks';
import Immutable from 'immutable';
import { sort, Request, PinyinHelper } from '@peace/utils';
import { ApiTable } from '$utils';
import { connect } from 'react-redux';
import { message, Tree } from '@douyinfe/semi-ui';
import Ps from 'perfect-scrollbar';
import createG6 from './edit-map';
// import { useThingsLinkStatus } from '../../actions/hooks';
const Tree_ = createG6((tree) => {
tree.render();
});
const Index = ({ ...props }) => {
const { actions, dispatch,
struct, data, resetFoldAllG6ToCenter, foldAllG6ToCenter, g6TreeDirection, emitChange, emitDataChange_,
width, height, collapseAll, collapsed, expanded, expandAll, changeFinish, targetNode, inputSearching, setInputSearching,
onCollapse, handleNodeEnter, handelNodeClick,
} = props;
const { analysis } = actions
const [isGetLinkStateDone, setTrue] = useState(false);
const [devicesLinkState, setDevicesLinkState] = useState(null);
const [treeNodeCheckedKeys, setTreeNodeCheckedKeys] = useState([]);
const [currSDids, setCurrSDids] = useState([]);
const [g6Tree, setG6Tree] = useState(null);
const [g6TreeData, setG6TreeData] = useState(null);
const [lastClickNodeKey, setLastClickNodeKey] = useState(null);
const [disCheckedKeys, setDisCheckedKeys] = useState([]);
let intervalLinkStatus = null;
const getThingsLinkStatus = (isCycle = false) => {
const iotaThingId = struct?.thingId;
if (iotaThingId) {
dispatch(analysis.getIotaThingsLlinkStatus(iotaThingId)).then((res) => {
if (res.payload?.data?.devices.length >= 0) {
setDevicesLinkState(res.payload?.data?.devices || []);
} else {
intervalLinkStatus && clearInterval(intervalLinkStatus);
}
if (!isCycle) {
setTrue(true);
}
}, (error) => {
if (!isCycle) {
message.warning('设备在线状态获取失败')
setTrue(true);
}
})
}
};
useEffect(() => {
getThingsLinkStatus();
// intervalLinkStatus = setInterval(() => {
// getThingsLinkStatus(true);
// }, 4000);
new Ps('#deviceTree', { suppressScrollX: true });
return () => {
intervalLinkStatus && clearInterval(intervalLinkStatus);
};
}, []);
useEffect(() => {
const nextDeviceIds = Object.keys(data);
if (currSDids.length != nextDeviceIds.length) {
const nextSDids = [];
const checked = [].concat(treeNodeCheckedKeys);
for (const id of nextDeviceIds) {
if ((data[id]?.type == 's.d' || data[id]?.type == 's.iota') && !currSDids.some((c) => c == id)) {
checked.push(id);
}
nextSDids.push(id);
}
emitDataChange_ && emitDataChange_(true);
setTreeNodeCheckedKeys(checked);
setCurrSDids(nextSDids);
}
if (foldAllG6ToCenter && g6Tree) {
toG6Center(width, height, g6TreeDirection);
resetFoldAllG6ToCenter();
}
}, [data, foldAllG6ToCenter]);
useEffect(() => {
if (g6Tree) {
toG6Center(width, height, g6TreeDirection);
g6Tree.changeLayout(
new G6.Layout.CompactBoxTree({ direction: g6TreeDirection }),
);
}
}, [g6TreeDirection]);
useEffect(() => {
if (g6Tree && inputSearching) {
const items = g6Tree.getItems();
if (items && items.length) {
items.forEach((item) => {
const { model } = item._attrs;
if (model && model.label && targetNode && (
model.label.indexOf(targetNode) != -1 || PinyinHelper.isPinyinMatched(model.label, targetNode)
)) {
g6Tree.update(model.id, {
color: '#ff7300',
});
g6Tree.focusPoint({ x: model.x, y: model.y });
} else if (model && model.type == 's.iota') {
g6Tree.update(model.id, {
color: '#108ee9',
});
} else {
g6Tree.update(model.id, {
color: '#fff',
});
}
});
}
// g6Tree.changeSize(width, height);
// g6Tree.refresh();
}
setInputSearching(false);
}, [targetNode, inputSearching]);
const toG6Center = (width, height, direction = null) => {
let x = 30; let
y = 50;
if (direction == 'LR') {
x = 400;
y = -10;
}
g6Tree.changeSize(width, height);
g6Tree.focusPoint({ x, y });
};
const _finishDataChange = () => {
changeFinish && changeFinish();
};
const _handelNodeClick = (node) => {
handelNodeClick(node);
};
const _handelNodeEnter = (node) => handleNodeEnter(node);
const renderTreeNodes = (data, parentId) => {
const combinTreeNode = (item, parentId) => {
const treeNode = {
label: item.name,
value: item.id,
key: item.id,
disableCheckbox: item.name == '数据中心',
};
if (item.children) {
treeNode.children = item.children.map((ci) => combinTreeNode(ci, item.id));
}
return treeNode;
};
return data.map((item) => combinTreeNode(item));
};
const onTreeNodeSelect = (checkedKeys, e, data) => {
if (e.checked) {
const newDisCheckedKeys = [];
disCheckedKeys.forEach((d) => {
if (d != e.node.key) {
newDisCheckedKeys.push(d);
}
});
setDisCheckedKeys(newDisCheckedKeys);
} else {
setDisCheckedKeys([].concat(disCheckedKeys, [e.node.key]));
}
setTreeNodeCheckedKeys(checkedKeys.checked);
toG6Center(width, height);
emitDataChange_();
};
const onTreeNodeClick = (nodeKeys, e) => {
const lastClickNodeKey_ = lastClickNodeKey;
if (nodeKeys.length) {
const nodeKey = nodeKeys
if (g6Tree && treeNodeCheckedKeys.some((cid) => cid == nodeKey)) {
const node = g6Tree.find(nodeKey);
if (node) {
const boxStatsh = node._attrs.boxStash;
g6Tree.update(nodeKey, {
color: '#ff7300',
});
if (lastClickNodeKey_ && lastClickNodeKey_ != nodeKey) {
const node = g6Tree.find(lastClickNodeKey_);
if (node && node._attrs.model.type == 's.iota') {
g6Tree.update(lastClickNodeKey_, {
color: '#108ee9',
});
} else if (node) {
g6Tree.update(lastClickNodeKey_, {
color: '#fff',
});
}
}
setLastClickNodeKey(nodeKey);
// lastClickNodeKey = nodeKey;
g6Tree.focusPoint({ x: boxStatsh.centerX, y: boxStatsh.centerY });
}
}
} else if (lastClickNodeKey_) {
if (g6Tree) {
const node = g6Tree.find(lastClickNodeKey_);
if (node && node._attrs.model.type == 's.iota') {
g6Tree.update(lastClickNodeKey_, {
color: '#108ee9',
});
} else {
g6Tree.update(lastClickNodeKey_, {
color: '#fff',
});
}
}
}
};
const formedData = () => {
const deviceInfo = JSON.parse(JSON.stringify(data));
const deviceIds = Object.keys(deviceInfo);
const node = []; const
link = [];
const defaultTreeNodesCheckedKeys = [];
for (let i = 0; i < deviceIds.length; i++) {
if (deviceInfo[deviceIds[i]].type == 's.iota') {
deviceInfo[deviceIds[i]].name = '数据中心';
const a = {
...deviceInfo[deviceIds[i]], id: deviceIds[i], label: '数据中心', color: '#108ee9', children: [],
};
node.push(a);
defaultTreeNodesCheckedKeys.push(a.id);
} else if (deviceInfo[deviceIds[i]].type == 's.d') {
let linkState = null;
if (devicesLinkState) {
linkState = devicesLinkState.find((dls) => dls.deviceId == deviceIds[i]);
if (linkState) {
linkState = linkState.status;
}
}
let linkStateMsg = '';
let LinlStateColor = '#666666';
if ((linkState != null || linkState != undefined)) {
if (linkState == 1) {
// linkStateMsg = "(在线)"
} else if (linkState == 0) {
linkStateMsg = '(离线)';
LinlStateColor = 'red';
}
}
const a = {
...deviceInfo[deviceIds[i]],
id: deviceIds[i],
label: `${deviceInfo[deviceIds[i]]?.instance?.name || ''}${linkStateMsg}`,
style: { stroke: LinlStateColor },
children: [],
linkState,
};
if (collapseAll) {
if (expanded.some((c) => c == deviceIds[i])) {
a.isCollapsed = false;
} else {
a.isCollapsed = true;
}
}
if (expandAll) {
if (collapsed.some((c) => c == deviceIds[i])) {
a.isCollapsed = true;
} else {
a.isCollapsed = false;
}
}
node.push(a);
defaultTreeNodesCheckedKeys.push(a.id);
} else if (deviceInfo[deviceIds[i]]?.type == 's.l') {
const temp = deviceInfo[deviceIds[i]];
const b = { from: temp?.instance?.from?.ownerSvgId, to: temp?.instance?.to?.ownerSvgId };
link.push(b);
}
}
const deviceInfos = {
nodes: node,
links: link,
};
const parent = deviceInfos.nodes.filter((s) => s.type == 's.iota')[0]?.id;
const nodes = new Map();
deviceInfos.nodes.forEach((s) => {
nodes.set(s.id, s);
});
const { links } = deviceInfos;
function getLeaf (parentId) {
const fromStructs = nodes.get(parentId);
let tempTreeData = [];
links.forEach((m) => {
if (m.to == parentId) {
const node = nodes.get(m.from);
if (((treeNodeCheckedKeys && treeNodeCheckedKeys.some((c) => c == node.id))
|| treeNodeCheckedKeys.length == 0
) && !disCheckedKeys.some((d) => d == node.id)) {
fromStructs.children.push(node);
}
const nextTempTreeData = getLeaf(m.from);
tempTreeData = tempTreeData.concat(nextTempTreeData.treeData);
// tempTreeData.push({ name: node.name, id: node.id, children: nextTempTreeData.treeData })
}
});
const treeData = [{ name: fromStructs.name, id: fromStructs.id, children: tempTreeData.length ? tempTreeData : null }];
fromStructs.children = sort(fromStructs.children);
return { tree_data: fromStructs, treeData, defaultTreeNodesCheckedKeys };
}
return getLeaf(parent);
};
const treeData = formedData();
console.log(22, treeData);
console.log(3333, renderTreeNodes(treeData?.treeData || []));
return (
<div id="editMap" style={{ position: 'relative', overflow: 'hidden' }}>
<div
id="deviceTree"
style={{
position: 'absolute',
top: 50,
zIndex: 999,
maxHeight: height - 50,
overflow: 'auto',
}}
>
<Tree
// showIcon={false}
multiple
onCheck={(checkedKeys, e) => onTreeNodeSelect(checkedKeys, e, treeData.treeData)}
onSelect={(checkedKeys, e) => {
console.log(4324, checkedKeys, e);
onTreeNodeClick(checkedKeys, e)
}}
defaultValue={treeData.defaultTreeNodesCheckedKeys}
expandAll={false}
// checkStrictly
// value={checkedKeys}
treeData={renderTreeNodes(treeData.treeData, null)}
style={{ width: 200, height: 500 }}
/>
</div>
{isGetLinkStateDone
? (
<Tree_
data={treeData.tree_data}
emitChange={emitChange}
changeFinish={_finishDataChange}
emitDataChange_={emitDataChange_}
width={width}
height={height}
targetNode={targetNode}
handelNodeClick={_handelNodeClick}
onCollapse={onCollapse}
handleNodeEnter={_handelNodeEnter}
setG6Tree={(g6Tree) => { setG6Tree(g6Tree); }}
setG6TreeData={(data) => { setG6TreeData(data); }}
/>
) : null}
</div>
);
}
function mapStateToProps (state) {
const { auth, global, } = state;
return {
user: auth.user,
actions: global.actions,
clientHeight: global.clientHeight,
};
}
export default connect(mapStateToProps)(Index);

1
web/client/src/sections/analysis/containers/network.jsx

@ -86,7 +86,6 @@ const Network = ({
// value={projectValue} // value={projectValue}
optionList={projectList || []} optionList={projectList || []}
onSelect={v => { onSelect={v => {
console.log(v);
setProjectValue(v) setProjectValue(v)
form.current.setValue('projectId', v) form.current.setValue('projectId', v)
}} }}

43
web/client/src/sections/analysis/containers/treeShow.jsx

@ -2,40 +2,67 @@ import React, { useEffect, useState, useRef } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ReactECharts from 'echarts-for-react'; import ReactECharts from 'echarts-for-react';
import echarts from 'echarts'; import echarts from 'echarts';
import { Spin, Card, CardGroup, Form, Button } from '@douyinfe/semi-ui'; import { Spin, Card, CardGroup, Form, Button, Empty } from '@douyinfe/semi-ui';
import { IllustrationNoContent, IllustrationNoContentDark } from '@douyinfe/semi-illustrations';
import DeviceTree from '../components/device-tree';
const Network = ({ dispatch, actions, user, clientHeight, thingId }) => {
const Network = ({ dispatch, actions, user, clientHeight, thingId,
organizationsStruc, deviceMetasWithFollow, devices,
}) => {
const { analysis } = actions const { analysis } = actions
const form = useRef(); // const form = useRef(); //
useEffect(() => { useEffect(async () => {
if (thingId) { if (thingId) {
dispatch(analysis.getThingsDeploy(thingId)) dispatch(analysis.getThingsDeploy(thingId))
dispatch(analysis.getDeviceMetaDeployed(thingId))
} }
}, [thingId]) }, [thingId])
return ( return (
<div style={{ width: "100%", height: "100%" }}> <div style={{ width: "100%", height: "100%" }}>
树状展示 {thingId && deviceMetasWithFollow?.devices?.length > 0 && devices.instances
? (
<DeviceTree
deviceMetasWithFollow={deviceMetasWithFollow}
deviceList={devices}
// dimensionlist={dimensions||{}}
dimensionlist={{}}
dispatch={dispatch}
struct={organizationsStruc?.find((v) => v.thingId == thingId) || {}}
clientHeight={clientHeight}
clientWidth={500}
/>
) :
<Empty
image={<IllustrationNoContent style={{ width: 150, height: 150 }} />}
darkModeImage={<IllustrationNoContentDark style={{ width: 150, height: 150 }} />}
description={'暂无内容,请添加'}
style={{ padding: 30 }}
/>
}
</div> </div>
) )
} }
function mapStateToProps (state) { function mapStateToProps (state) {
const { auth, global, members, webSocket } = state; const { auth, global, organizationsStruc, deviceList, deviceMeta } = state;
return { return {
user: auth.user, user: auth.user,
actions: global.actions, actions: global.actions,
clientHeight: global.clientHeight clientHeight: global.clientHeight,
organizationsStruc: organizationsStruc?.data || [], //
deviceMetasWithFollow: deviceMeta?.data || {},
devices: deviceList?.data || {},
}; };
} }

9
web/client/src/sections/analysis/style.less

@ -0,0 +1,9 @@
.device-monitor-search {
margin-bottom: 1px;
}
.device-monitor-pro-table {
.ant-card-body {
padding: 0;
}
}

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

@ -153,7 +153,10 @@ export const ApiTable = {
//分析-一图统揽 //分析-一图统揽
organizationsStruc: 'organizations/{pepProjectId}/struc', //获取项目下的结构物信息 organizationsStruc: 'organizations/{pepProjectId}/struc', //获取项目下的结构物信息
thingsDeploy: 'things/{thingId}/deploy',//获取设备部署信息-组网数据 thingsDeploy: 'things/{iotaThingId}/deploy',//获取设备部署信息-组网数据
deviceMetaDeployed: "meta/things/{iotaThingId}/devices", //获取部署设备原型
getIotaThingsLlinkStatus: 'metrics/things/{iotaThingId}/devices/link_status', //获取设备在线状态/以结构物id集体获取
respondRecord: 'respond-record', respondRecord: 'respond-record',
//待办工单 //待办工单

Loading…
Cancel
Save