提交 309465e9 编写于 作者: nengyuangzhang's avatar nengyuangzhang

Merge branch 'develop'

......@@ -431,6 +431,7 @@ VALUES
(108,'Statistics','/space/statistics',100,0),
(109,'Saving','/space/saving',100,0),
(110,'Carbon','/space/carbon',100,0),
(111,'Environment Monitor','/space/environmentmonitor',100,0),
(200,'Equipment Data','/equipment',NULL,0),
(201,'Energy Category Data','/equipment/energycategory',200,0),
(202,'Energy Item Data','/equipment/energyitem',200,0),
......@@ -1327,7 +1328,7 @@ USE `myems_system_db`;
INSERT INTO `myems_system_db`.`tbl_versions`
(`id`, `version`, `release_date`)
VALUES
(1, '3.2.0RC', '2023-03-10');
(1, '3.2.0', '2023-03-31');
COMMIT;
......
......@@ -7,6 +7,11 @@
DROP TABLE IF EXISTS `myems_production_db`.`tbl_shopfloor_working_days` ;
-- ADD MENUS
INSERT INTO myems_system_db.tbl_menus (id,name,route, parent_menu_id,is_hidden)
VALUES
(111,'Environment Monitor','/space/environmentmonitor',100,0);
-- UPDATE VERSION NUMBER
UPDATE `myems_system_db`.`tbl_versions` SET version='3.2.0RC', release_date='2023-03-10' WHERE id=1;
UPDATE `myems_system_db`.`tbl_versions` SET version='3.2.0', release_date='2023-03-31' WHERE id=1;
......@@ -63,6 +63,7 @@ from reports import spacecost
from reports import spaceefficiency
from reports import spaceenergycategory
from reports import spaceenergyitem
from reports import spaceenvironmentmonitor
from reports import spaceincome
from reports import spaceload
from reports import spaceoutput
......@@ -673,6 +674,8 @@ api.add_route('/reports/storeenergycategory',
storeenergycategory.Reporting())
api.add_route('/reports/storeenergyitem',
storeenergyitem.Reporting())
api.add_route('/reports/spaceenvironmentmonitor',
spaceenvironmentmonitor.Reporting())
api.add_route('/reports/storeload',
storeload.Reporting())
api.add_route('/reports/storesaving',
......
import re
from datetime import datetime, timedelta, timezone
import falcon
import mysql.connector
import simplejson as json
import config
class Reporting:
@staticmethod
def __init__():
""""Initializes Reporting"""
pass
@staticmethod
def on_options(req, resp):
resp.status = falcon.HTTP_200
####################################################################################################################
# PROCEDURES
# Step 1: valid parameters
# Step 2: query the sensor
# Step 3: query associated points
# Step 4: query associated points data
# Step 5: construct the report
####################################################################################################################
@staticmethod
def on_get(req, resp):
print(req.params)
sensor_id = req.params.get('sensorid')
sensor_uuid = req.params.get('sensoruuid')
quick_mode = req.params.get('quickmode')
################################################################################################################
# Step 1: valid parameters
################################################################################################################
if sensor_id is None and sensor_uuid is None:
raise falcon.HTTPError(falcon.HTTP_400,
title='API.BAD_REQUEST',
description='API.INVALID_SENSOR_ID')
if sensor_id is not None:
sensor_id = str.strip(sensor_id)
if not sensor_id.isdigit() or int(sensor_id) <= 0:
raise falcon.HTTPError(falcon.HTTP_400,
title='API.BAD_REQUEST',
description='API.INVALID_SENSOR_UUID')
is_quick_mode = False
if quick_mode is not None and \
len(str.strip(quick_mode)) > 0 and \
str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'):
is_quick_mode = True
timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
if config.utc_offset[0] == '-':
timezone_offset = -timezone_offset
reporting_end_datetime_utc = datetime.utcnow()
reporting_start_datetime_utc = reporting_end_datetime_utc - timedelta(minutes=60)
################################################################################################################
# Step 2: query the sensor
################################################################################################################
cnx_system = mysql.connector.connect(**config.myems_system_db)
cursor_system = cnx_system.cursor()
cnx_historical = mysql.connector.connect(**config.myems_historical_db)
cursor_historical = cnx_historical.cursor()
if sensor_id is not None:
cursor_system.execute(" SELECT id, name, uuid, description "
" FROM tbl_sensors "
" WHERE id = %s ", (sensor_id,))
row_sensor = cursor_system.fetchone()
elif sensor_uuid is not None:
cursor_system.execute(" SELECT id, name, uuid, description "
" FROM tbl_sensors "
" WHERE uuid = %s ", (sensor_uuid,))
row_sensor = cursor_system.fetchone()
if row_sensor is None:
if cursor_system:
cursor_system.close()
if cnx_system:
cnx_system.close()
if cursor_historical:
cursor_historical.close()
if cnx_historical:
cnx_historical.close()
raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND', description='API.SENSOR_NOT_FOUND')
sensor = dict()
sensor['id'] = row_sensor[0]
sensor['name'] = row_sensor[1]
sensor['uuid'] = row_sensor[2]
sensor['description'] = row_sensor[3]
################################################################################################################
# Step 3: query associated points
################################################################################################################
point_list = list()
cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type "
" FROM tbl_sensors s, tbl_sensors_points sp, tbl_points p "
" WHERE s.id = %s AND s.id = sp.sensor_id AND p.id = sp.point_id "
" ORDER BY p.id ", (sensor['id'],))
rows_points = cursor_system.fetchall()
if rows_points is not None and len(rows_points) > 0:
for row in rows_points:
point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]})
################################################################################################################
# Step 4: query associated points data
################################################################################################################
energy_value_data = dict()
energy_value_data['name'] = None
energy_value_data['timestamps'] = list()
energy_value_data['values'] = list()
parameters_data = dict()
parameters_data['names'] = list()
parameters_data['timestamps'] = list()
parameters_data['values'] = list()
if not is_quick_mode:
for point in point_list:
point_values = []
point_timestamps = []
if point['object_type'] == 'ENERGY_VALUE':
energy_value_data['name'] = point['name'] + ' (' + point['units'] + ')'
query = (" SELECT utc_date_time, actual_value "
" FROM tbl_energy_value "
" WHERE point_id = %s "
" AND utc_date_time BETWEEN %s AND %s "
" ORDER BY utc_date_time ")
cursor_historical.execute(query, (point['id'],
reporting_start_datetime_utc,
reporting_end_datetime_utc))
rows = cursor_historical.fetchall()
if rows is not None and len(rows) > 0:
for row in rows:
current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
timedelta(minutes=timezone_offset)
current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
energy_value_data['timestamps'].append(current_datetime)
energy_value_data['values'].append(row[1])
elif point['object_type'] == 'ANALOG_VALUE':
query = (" SELECT utc_date_time, actual_value "
" FROM tbl_analog_value "
" WHERE point_id = %s "
" AND utc_date_time BETWEEN %s AND %s "
" ORDER BY utc_date_time ")
cursor_historical.execute(query, (point['id'],
reporting_start_datetime_utc,
reporting_end_datetime_utc))
rows = cursor_historical.fetchall()
if rows is not None and len(rows) > 0:
for row in rows:
current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
timedelta(minutes=timezone_offset)
current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
point_timestamps.append(current_datetime)
point_values.append(row[1])
parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')')
parameters_data['timestamps'].append(point_timestamps)
parameters_data['values'].append(point_values)
elif point['object_type'] == 'DIGITAL_VALUE':
query = (" SELECT utc_date_time, actual_value "
" FROM tbl_digital_value "
" WHERE point_id = %s "
" AND utc_date_time BETWEEN %s AND %s "
" ORDER BY utc_date_time ")
cursor_historical.execute(query, (point['id'],
reporting_start_datetime_utc,
reporting_end_datetime_utc))
rows = cursor_historical.fetchall()
if rows is not None and len(rows) > 0:
for row in rows:
current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
timedelta(minutes=timezone_offset)
current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
point_timestamps.append(current_datetime)
point_values.append(row[1])
parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')')
parameters_data['timestamps'].append(point_timestamps)
parameters_data['values'].append(point_values)
################################################################################################################
# Step 5: construct the report
################################################################################################################
if cursor_system:
cursor_system.close()
if cnx_system:
cnx_system.close()
if cursor_historical:
cursor_historical.close()
if cnx_historical:
cnx_historical.close()
result = {
"sensor": {
"sensor_id": sensor['id'],
"name": sensor['name'],
"uuid": sensor['uuid'],
"description": sensor['description'],
},
"energy_value": energy_value_data,
"parameters": {
"names": parameters_data['names'],
"timestamps": parameters_data['timestamps'],
"values": parameters_data['values']
},
}
resp.text = json.dumps(result)
import React, { Component } from 'react';
import { Card, CardHeader, CardBody, ListGroup, ListGroupItem } from 'reactstrap';
import { withTranslation } from 'react-i18next';
import {v4 as uuid} from 'uuid';
import { APIBaseURL } from '../../../config';
import { getCookieValue } from '../../../helpers/utils';
import { toast } from 'react-toastify';
const dividerBorder = '1px solid rgba(255, 255, 255, 0.15)';
const listItemBorderColor = 'rgba(255, 255, 255, 0.05)';
class RealtimeData extends Component {
_isMounted = false;
refreshInterval;
refreshTimeout;
state = {
trendLog: [],
currentEnergyValue: undefined,
energyValuePointName: undefined,
pointList: [],
};
componentWillUnmount() {
this._isMounted = false;
clearInterval(this.refreshInterval);
clearTimeout(this.refreshTimeout);
}
componentDidMount() {
this._isMounted = true;
const { t } = this.props;
// fetch meter realtime data at the first time
let isResponseOK = false;
fetch(APIBaseURL + '/reports/spaceenvironmentmonitor?sensorid=' + this.props.sensorId, {
method: 'GET',
headers: {
"Content-type": "application/json",
"User-UUID": getCookieValue('user_uuid'),
"Token": getCookieValue('token')
},
body: null,
}).then(response => {
if (response.ok) {
isResponseOK = true;
}
return response.json();
}).then(json => {
if (isResponseOK) {
console.log(json);
let length = json['energy_value']['values'].length;
let trendLog = length > 60 ? json['energy_value']['values'].slice(length - 60, length)
: json['energy_value']['values'];
let currentEnergyValue = undefined;
let energyValuePointName = json['energy_value']['name'];
let pointList = [];
if (trendLog.length > 0) {
currentEnergyValue = trendLog[trendLog.length - 1];
}
json['parameters']['names'].forEach((currentName, index) => {
let pointItem = {}
pointItem['name'] = currentName;
pointItem['value'] = undefined;
let currentValues = json['parameters']['values'][index];
if (currentValues.length > 0) {
pointItem['value'] = currentValues[currentValues.length - 1];
}
pointList.push(pointItem);
});
if (this._isMounted) {
this.setState({
trendLog: trendLog,
currentEnergyValue: currentEnergyValue,
energyValuePointName: energyValuePointName,
pointList: pointList,
});
}
} else {
toast.error(t(json.description));
}
}).catch(err => {
console.log(err);
});
//fetch meter realtime data at regular intervals
this.refreshInterval = setInterval(() => {
let isResponseOK = false;
fetch(APIBaseURL + '/reports/spaceenvironmentmonitor?sensorid=' + this.props.sensorId, {
method: 'GET',
headers: {
"Content-type": "application/json",
"User-UUID": getCookieValue('user_uuid'),
"Token": getCookieValue('token')
},
body: null,
}).then(response => {
if (response.ok) {
isResponseOK = true;
}
return response.json();
}).then(json => {
if (isResponseOK) {
console.log(json);
let trendLog = json['energy_value']['values'];
let currentEnergyValue = undefined;
let energyValuePointName = json['energy_value']['name'];
let pointList = [];
if (trendLog.length > 0) {
currentEnergyValue = trendLog[trendLog.length - 1];
}
json['parameters']['names'].forEach((currentName, index) => {
let pointItem = {}
pointItem['name'] = currentName;
pointItem['value'] = undefined;
let currentValues = json['parameters']['values'][index];
if (currentValues.length > 0) {
pointItem['value'] = currentValues[currentValues.length - 1];
}
pointList.push(pointItem);
});
if (this._isMounted) {
this.setState({
trendLog: trendLog,
currentEnergyValue: currentEnergyValue,
energyValuePointName: energyValuePointName,
pointList: pointList,
});
}
} else {
toast.error(t(json.description))
}
}).catch(err => {
console.log(err);
});
}, (60 + Math.floor(Math.random() * Math.floor(10))) * 1000); // use random interval to avoid paralels requests
}
render() {
const { t } = this.props;
return (
<Card className="h-100 bg-gradient">
<CardHeader className="bg-transparent">
<h5 className="text-white">{this.props.sensorName}</h5>
<div className="real-time-user display-4 font-weight-normal text-white"
style={{display:!this.state.currentEnergyValue ? "none" : "inline"}}>{this.state.currentEnergyValue}</div>
</CardHeader>
<CardBody className="text-white fs--1">
<p className="pb-2" style={{ borderBottom: dividerBorder, display:!this.state.energyValuePointName ? "none" : "inline" }}>
{t('Trend in the last hour of Energy Value Point')} {this.state.energyValuePointName}
</p>
<ListGroup flush className="mt-4">
<ListGroupItem
className="bg-transparent d-flex justify-content-between px-0 py-1 font-weight-semi-bold border-top-0"
style={{ borderColor: listItemBorderColor }}
>
<p className="mb-0">{t('Related Parameters')}</p>
<p className="mb-0">{t('Realtime Value')}</p>
</ListGroupItem>
{this.state.pointList.map(pointItem => (
<ListGroupItem key={uuid()}
className="bg-transparent d-flex justify-content-between px-0 py-1"
style={{ borderColor: listItemBorderColor }}
>
<p className="mb-0">{pointItem['name']}</p>
<p className="mb-0">{pointItem['value']}</p>
</ListGroupItem>
))}
</ListGroup>
</CardBody>
</Card>
);
}
}
export default withTranslation()(RealtimeData);
import React, { Fragment, useEffect, useState } from 'react';
import {
Breadcrumb,
BreadcrumbItem,
Card,
CardBody,
Col,
Form,
FormGroup,
Input,
Label,
Row,
Spinner,
Pagination,
PaginationItem,
PaginationLink
} from 'reactstrap';
import Cascader from 'rc-cascader';
import RealtimeData from './RealtimeData';
import { getCookieValue, createCookie } from '../../../helpers/utils';
import withRedirect from '../../../hoc/withRedirect';
import { withTranslation } from 'react-i18next';
import {v4 as uuid} from 'uuid';
import { toast } from 'react-toastify';
import { APIBaseURL } from '../../../config';
const SpaceEnvironmentMonitor = ({ setRedirect, setRedirectUrl, t }) => {
const [cursor, setCursor] = useState(0);
const [maxCursor, setMaxCursor] = useState(0);
const [selectSensorList, setSelectSensorList] = useState([]);
const len = 8;
useEffect(() => {
let is_logged_in = getCookieValue('is_logged_in');
let user_name = getCookieValue('user_name');
let user_display_name = getCookieValue('user_display_name');
let user_uuid = getCookieValue('user_uuid');
let token = getCookieValue('token');
if (is_logged_in === null || !is_logged_in) {
setRedirectUrl(`/authentication/basic/login`);
setRedirect(true);
} else {
//update expires time of cookies
createCookie('is_logged_in', true, 1000 * 60 * 10 * 1);
createCookie('user_name', user_name, 1000 * 60 * 10 * 1);
createCookie('user_display_name', user_display_name, 1000 * 60 * 10 * 1);
createCookie('user_uuid', user_uuid, 1000 * 60 * 10 * 1);
createCookie('token', token, 1000 * 60 * 10 * 1);
}
});
useEffect(() => {
let timer = setInterval(() => {
let is_logged_in = getCookieValue('is_logged_in');
if (is_logged_in === null || !is_logged_in) {
setRedirectUrl(`/authentication/basic/login`);
setRedirect(true);
}
}, 1000);
return () => clearInterval(timer);
}, []);
// State
const [selectedSpaceName, setSelectedSpaceName] = useState(undefined);
const [cascaderOptions, setCascaderOptions] = useState(undefined);
const [sensorList, setSensorList] = useState([]);
const [spinnerHidden, setSpinnerHidden] = useState(false);
useEffect(() => {
//begin of getting space tree
let isResponseOK = false;
fetch(APIBaseURL + '/spaces/tree', {
method: 'GET',
headers: {
'Content-type': 'application/json',
'User-UUID': getCookieValue('user_uuid'),
Token: getCookieValue('token')
},
body: null
})
.then(response => {
console.log(response);
if (response.ok) {
isResponseOK = true;
}
return response.json();
})
.then(json => {
console.log(json);
if (isResponseOK) {
// rename keys
json = JSON.parse(
JSON.stringify([json])
.split('"id":')
.join('"value":')
.split('"name":')
.join('"label":')
);
setCascaderOptions(json);
// set the default space
setSelectedSpaceName([json[0]].map(o => o.label));
let selectedSpaceID = [json[0]].map(o => o.value);
//begin of getting sensors of the default space
let isSecondResponseOK = false;
fetch(APIBaseURL + '/spaces/' + selectedSpaceID + '/sensors', {
method: 'GET',
headers: {
'Content-type': 'application/json',
'User-UUID': getCookieValue('user_uuid'),
Token: getCookieValue('token')
},
body: null
})
.then(response => {
if (response.ok) {
isSecondResponseOK = true;
}
return response.json();
})
.then(json => {
if (isSecondResponseOK) {
json = JSON.parse(JSON.stringify([json]));
console.log(json);
setSensorList(json[0]);
setSpinnerHidden(true);
} else {
toast.error(t(json.description));
}
})
.catch(err => {
console.log(err);
});
//end of getting sensors of the default space
} else {
toast.error(t(json.description));
}
})
.catch(err => {
console.log(err);
});
//end of getting space tree
}, []);
const labelClasses = 'ls text-uppercase text-600 font-weight-semi-bold mb-0';
let onSpaceCascaderChange = (value, selectedOptions) => {
setSelectedSpaceName(selectedOptions.map(o => o.label).join('/'));
let selectedSpaceID = value[value.length - 1];
setSpinnerHidden(false);
//begin of getting sensors of the selected space
let isResponseOK = false;
fetch(APIBaseURL + '/spaces/' + selectedSpaceID + '/sensors', {
method: 'GET',
headers: {
'Content-type': 'application/json',
'User-UUID': getCookieValue('user_uuid'),
Token: getCookieValue('token')
},
body: null
})
.then(response => {
if (response.ok) {
isResponseOK = true;
}
return response.json();
})
.then(json => {
if (isResponseOK) {
json = JSON.parse(JSON.stringify([json]));
console.log(json);
setSensorList(json[0]);
setSpinnerHidden(true);
} else {
toast.error(t(json.description));
}
})
.catch(err => {
console.log(err);
});
//end of getting sensors of the selected space
};
useEffect(() => {
const sensorLen = sensorList.length;
const maxCursor = Math.ceil(sensorLen / len);
setCursor(1);
setMaxCursor(maxCursor);
document.getElementById("cursor_2").hidden=true;
document.getElementById("cursor_3").hidden=true;
document.getElementById("cursor_4").hidden=true;
if(maxCursor == 2){
document.getElementById("cursor_2").hidden=false;
}
if(maxCursor == 3){
document.getElementById("cursor_2").hidden=false;
document.getElementById("cursor_3").hidden=false;
}
if(maxCursor>=4)
{
document.getElementById("cursor_2").hidden=false;
document.getElementById("cursor_3").hidden=false;
document.getElementById("cursor_4").hidden=false;
}
}, [sensorList]);
useEffect(() => {
setSelectSensorList(sensorList.slice(cursor * len - 8, cursor * len));
}, [cursor, sensorList]);
function getCursor(location){
switch (location){
case 1:
return cursor > maxCursor-3&&maxCursor - 3 >= 0 ? maxCursor-3 : cursor;
case 2:
return cursor > maxCursor-3&&maxCursor - 3 >= 0 ? maxCursor -2 : cursor +1;
case 3:
return cursor > maxCursor-3&&maxCursor - 3 >= 0 ? maxCursor -1: cursor +2;
case 4:
return cursor > maxCursor-3&&maxCursor - 3 >= 0 ? maxCursor : cursor+3;
}
}
return (
<Fragment>
<div>
<Breadcrumb>
<BreadcrumbItem>{t('Space Data')}</BreadcrumbItem>
<BreadcrumbItem active>{t('Environment Monitor')}</BreadcrumbItem>
</Breadcrumb>
</div>
<Card className="bg-light mb-3">
<CardBody className="p-3">
<Form>
<Row form>
<Col xs={6} sm={3}>
<FormGroup className="form-group">
<Label className={labelClasses} for="space">
{t('Space')}
</Label>
<br />
<Cascader
options={cascaderOptions}
onChange={onSpaceCascaderChange}
changeOnSelect
expandTrigger="hover"
>
<Input value={selectedSpaceName || ''} readOnly />
</Cascader>
</FormGroup>
</Col>
<Col xs="auto">
<FormGroup>
<br />
<Spinner color="primary" hidden={spinnerHidden} />
</FormGroup>
</Col>
</Row>
</Form>
</CardBody>
</Card>
<Row noGutters>
{selectSensorList.map(sensor => (
<Col lg="3" className="pr-lg-2" key={uuid()}>
<RealtimeData sensorId={sensor['id']} sensorName={sensor['name']} />
</Col>
))}
</Row>
<Pagination>
<PaginationItem>
<PaginationLink first href="#" onClick={() => setCursor(1)} />
</PaginationItem>
<PaginationItem>
<PaginationLink previous href="#" onClick={() => (cursor - 1 >= 1 ? setCursor(cursor - 1) : null)} />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" onClick={() => (setCursor(getCursor(1)))}>{getCursor(1)}</PaginationLink>
</PaginationItem>
<PaginationItem id="cursor_2">
<PaginationLink href="#" onClick={() => (setCursor(getCursor(2)))}>{getCursor(2)}</PaginationLink>
</PaginationItem>
<PaginationItem id="cursor_3">
<PaginationLink href="#" onClick={() => (setCursor(getCursor(3)))}>{getCursor(3)}</PaginationLink>
</PaginationItem>
<PaginationItem id="cursor_4">
<PaginationLink href="#" onClick={() => (setCursor(getCursor(4)))}>{getCursor(4)}</PaginationLink>
</PaginationItem>
<PaginationItem >
<PaginationLink next href="#" onClick={() => (cursor + 1 <= maxCursor ? setCursor(cursor + 1) : null)} />
</PaginationItem>
<PaginationItem>
<PaginationLink last href="#" onClick={() => setCursor(maxCursor)} />
</PaginationItem>
</Pagination>
</Fragment>
);
};
export default withTranslation()(withRedirect(SpaceEnvironmentMonitor));
\ No newline at end of file
......@@ -32,6 +32,7 @@ const resources = {
'Batch Analysis': 'Batch Analysis',
'Saving': 'Saving',
'Equipment Tracking': 'Equipment Tracking',
'Environment Monitor': 'Environment Monitor',
'Meter Energy': 'Meter Energy',
'Meter Carbon': 'Meter Carbon Dioxide Emissions',
'Meter Cost': 'Meter Cost',
......@@ -871,6 +872,7 @@ const resources = {
'Batch Analysis': 'Charge Analyse',
'Saving': 'Sparen',
'Equipment Tracking': 'Ausrüstung Datei',
'Environment Monitor': 'Umgebungsmonitor',
'Meter Energy': 'Meter Energie ',
'Meter Carbon': 'Meter Kohlendioxidemission',
'Meter Cost': 'Meter Kosten',
......@@ -1722,6 +1724,7 @@ const resources = {
'Batch Analysis': '批量分析',
'Saving': '节能分析',
'Equipment Tracking': '设备台账',
'Environment Monitor': '环境监测',
'Meter Energy': '计量表能耗分析',
'Meter Carbon': '计量表碳排放分析',
'Meter Cost': '计量表成本分析',
......
......@@ -113,6 +113,7 @@ import SpaceEfficiency from '../components/MyEMS/Space/SpaceEfficiency';
import SpaceLoad from '../components/MyEMS/Space/SpaceLoad';
import SpaceStatistics from '../components/MyEMS/Space/SpaceStatistics';
import SpaceSaving from '../components/MyEMS/Space/SpaceSaving';
import SpaceEnvironmentMonitor from '../components/MyEMS/Space/SpaceEnvironmentMonitor';
// Equipment
import EquipmentBatch from '../components/MyEMS/Equipment/EquipmentBatch';
import EquipmentCarbon from '../components/MyEMS/Equipment/EquipmentCarbon';
......@@ -133,7 +134,7 @@ import MeterComparison from '../components/MyEMS/Meter/MeterComparison';
import MeterCost from '../components/MyEMS/Meter/MeterCost';
import MeterEnergy from '../components/MyEMS/Meter/MeterEnergy';
import MeterRealtime from '../components/MyEMS/Meter/MeterRealtime';
import MeterSaving from '../components/MyEMS/Meter/MeterSaving'
import MeterSaving from '../components/MyEMS/Meter/MeterSaving';
import MeterSubmetersBalance from '../components/MyEMS/Meter/MeterSubmetersBalance';
import MeterTracking from '../components/MyEMS/Meter/MeterTracking';
import MeterTrend from '../components/MyEMS/Meter/MeterTrend';
......@@ -359,6 +360,7 @@ const MyEMSRoutes = () => (
<Route path="/space/load" exact component={SpaceLoad} />
<Route path="/space/statistics" exact component={SpaceStatistics} />
<Route path="/space/saving" exact component={SpaceSaving} />
<Route path="/space/environmentmonitor" exact component={SpaceEnvironmentMonitor} />
{/*Equipment*/}
<Route path="/equipment/batch" exact component={EquipmentBatch} />
......
......@@ -330,7 +330,8 @@ export const spaceRoutes = {
{ to: '/space/efficiency', name: 'Efficiency' },
{ to: '/space/load', name: 'Load' },
{ to: '/space/statistics', name: 'Statistics' },
{ to: '/space/saving', name: 'Saving' }
{ to: '/space/saving', name: 'Saving' },
{ to: '/space/environmentmonitor', name: 'Environment Monitor' }
]
};
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册