Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wisdom-components
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ReactWeb5
wisdom-components
Commits
4b15851b
Commit
4b15851b
authored
Jan 26, 2024
by
陈龙
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 新增预测曲线功能;新增统计曲线功能
parent
a2ebc0ae
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
395 additions
and
87 deletions
+395
-87
EC_HistoryView.md
...es/extend-components/EC_HistoryView/src/EC_HistoryView.md
+2
-2
GridChart.js
packages/extend-components/EC_HistoryView/src/GridChart.js
+171
-19
SingleChart.js
packages/extend-components/EC_HistoryView/src/SingleChart.js
+12
-11
index.js
packages/extend-components/EC_HistoryView/src/apis/index.js
+29
-3
GridDemo.js
...es/extend-components/EC_HistoryView/src/demos/GridDemo.js
+35
-6
index.js
packages/extend-components/EC_HistoryView/src/demos/index.js
+7
-1
index.js
packages/extend-components/EC_HistoryView/src/index.js
+43
-30
utils.js
packages/extend-components/EC_HistoryView/src/utils.js
+96
-15
No files found.
packages/extend-components/EC_HistoryView/src/EC_HistoryView.md
View file @
4b15851b
...
@@ -22,7 +22,7 @@ path: /
...
@@ -22,7 +22,7 @@ path: /
## 单图表
## 单图表
<code
src=
"./demos/index.js"
></code>
[
//
]:
#
(<code src="./demos/index.js"></code>)
## 单图表-状态
## 单图表-状态
...
@@ -37,7 +37,7 @@ path: /
...
@@ -37,7 +37,7 @@ path: /
## 多图表
## 多图表
[
//
]:
#
(<code src="./demos/GridDemo.js"></code>)
<code
src=
"./demos/GridDemo.js"
></code>
## API
## API
...
...
packages/extend-components/EC_HistoryView/src/GridChart.js
View file @
4b15851b
import
React
,
{
memo
,
use
Memo
}
from
'react'
;
import
React
,
{
memo
,
use
Effect
,
useMemo
,
useState
}
from
'react'
;
import
_
from
'lodash'
;
import
_
,
{
cloneDeep
}
from
'lodash'
;
import
{
BasicChart
}
from
'@wisdom-components/basicchart'
;
import
{
BasicChart
}
from
'@wisdom-components/basicchart'
;
import
PandaEmpty
from
'@wisdom-components/empty'
;
import
PandaEmpty
from
'@wisdom-components/empty'
;
import
optionGenerator
from
'./utils'
;
import
optionGenerator
from
'./utils'
;
import
{
getPointAddress
,
getPointAddressEntry
,
getSensorsRealName
,
getSensorType
,
getStatisticsInfo
}
from
"./apis"
;
import
moment
from
"moment"
;
import
{
isString
}
from
"util"
;
const
ChartTitle
=
({
prefixCls
,
title
,
unit
})
=>
{
const
ChartTitle
=
({
prefixCls
,
title
,
unit
})
=>
{
const
cls
=
`
${
prefixCls
}
-grid-item-title`
;
const
cls
=
`
${
prefixCls
}
-grid-item-title`
;
...
@@ -20,29 +23,178 @@ const GridChart = memo((props) => {
...
@@ -20,29 +23,178 @@ const GridChart = memo((props) => {
contrastOption
=
'day'
,
contrastOption
=
'day'
,
smooth
=
true
,
smooth
=
true
,
curveCenter
,
curveCenter
,
allPointAddress
,
allSensorType
,
dateRange
}
=
props
;
}
=
props
;
const
{
prefixCls
}
=
props
;
const
{
prefixCls
}
=
props
;
const
[
gridData
,
setGridData
]
=
useState
([]);
const
[
pointAddressData
,
setPointAddressData
]
=
useState
(
null
);
const
[
pointAddressEntryData
,
setPointAddressEntryData
]
=
useState
(
null
);
const
[
sensorType
,
setSensorType
]
=
useState
(
null
);
// 新增逻辑:需要区分出哪些是统计值
const
gridData
=
useMemo
(()
=>
{
/**
const
grids
=
dataSource
.
reduce
((
pre
,
item
,
index
)
=>
{
* @param {array} dataSource
const
{
sensorName
,
deviceType
}
=
item
;
*/
const
key
=
`
${
deviceType
}
_
${
sensorName
}
`
;
// 同设备类型同指标才在同一组
const
handleDataSource
=
async
(
dataSource
)
=>
{
let
grid
=
pre
.
find
((
g
)
=>
g
.
key
===
key
);
props
.
setLoading
(
true
);
if
(
!
grid
)
{
// 1. 统计设备
const
restProp
=
_
.
pick
(
item
,
[
'equipmentName'
,
'sensorName'
,
'stationCode'
,
'unit'
]);
let
_deviceTypes
=
[];
grid
=
{
let
_deviceCodes
=
dataSource
.
reduce
((
final
,
cur
)
=>
{
key
:
key
,
if
(
!
final
.
includes
(
cur
.
stationCode
)
&&
!
_deviceTypes
.
includes
(
cur
.
deviceType
))
{
list
:
[],
final
.
push
(
cur
.
stationCode
);
...
restProp
,
_deviceTypes
.
push
(
cur
.
deviceType
);
};
pre
.
push
(
grid
);
}
}
grid
.
list
.
push
(
item
);
return
final
;
return
pre
;
},
[]);
},
[]);
return
grids
;
// 2. 获取对应的版本id
},
[
dataSource
]);
let
_ids
=
[];
if
(
pointAddressData
!==
null
)
{
_ids
=
pointAddressData
;
}
else
{
let
_idRequest
=
await
getPointAddress
({
code
:
_deviceCodes
.
join
(
','
)});
_ids
=
_idRequest
?.
data
??
[];
setPointAddressData
(
_ids
)
}
// 3. 获取对应的点表
let
_map
=
{};
for
await
(
let
item
of
_ids
)
{
let
_index
=
_deviceCodes
.
findIndex
(
code
=>
code
===
item
.
code
);
if
(
pointAddressEntryData
&&
pointAddressEntryData
[
item
.
id
])
{
_map
[
_deviceTypes
[
_index
]]
=
pointAddressEntryData
[
item
.
id
];
}
else
{
let
_entry
=
await
getPointAddressEntry
({
versionId
:
item
.
id
});
_map
[
_deviceTypes
[
_index
]]
=
_entry
?.
data
??
[];
setPointAddressEntryData
({...
pointAddressEntryData
,
[
item
.
id
]:
_entry
?.
data
})
}
}
// 4. 获取点类型
let
_sensorType
=
[]
if
(
sensorType
)
{
_sensorType
=
sensorType
;
}
else
{
_sensorType
=
(
await
getSensorType
())?.
data
??
[];
}
//5. 找出统计值,合并
let
_dataSource
=
cloneDeep
(
dataSource
);
let
_nameListMap
=
{};
let
_indexArr
=
[];
let
_tempValue
=
{};
let
_finalData
=
{};
_dataSource
.
forEach
((
item
,
index
)
=>
{
let
_sensorTypeId
=
_map
[
item
.
deviceType
].
find
(
sensor
=>
sensor
.
name
===
item
.
sensorName
)?.
sensorTypeID
||
0
;
let
_type
=
_sensorType
.
find
(
sensor
=>
sensor
.
id
===
_sensorTypeId
)?.
type
??
''
;
if
(
_type
===
'统计值'
)
{
// 移除掉,并存储
_tempValue
[
`needToReplace_
${
item
.
stationCode
}
_
${
item
.
sensorName
}
`
]
=
_dataSource
.
splice
(
index
,
1
,
`needToReplace_
${
item
.
stationCode
}
_
${
item
.
sensorName
}
`
)?.[
0
];
if
(
!
_nameListMap
[
item
.
stationCode
])
{
_nameListMap
[
item
.
stationCode
]
=
{
code
:
item
.
stationCode
,
deviceType
:
item
.
deviceType
,
sensors
:
[
item
.
sensorName
]
}
}
else
{
_nameListMap
[
item
.
stationCode
].
sensors
.
push
(
item
.
sensorName
)
}
}
})
//6. 请求数据并替换数据。grid模式下,请求的时间是一致的。
let
baseParam
=
{
pageIndex
:
1
,
pageSize
:
999
,
dateFrom
:
dateRange
[
0
].
dateFrom
,
dateTo
:
dateRange
[
0
].
dateTo
,
}
let
_arr
=
Object
.
values
(
_nameListMap
)
for
await
(
let
item
of
_arr
)
{
let
_params
=
{
...
baseParam
,
accountName
:
item
.
deviceType
,
deviceCode
:
item
.
code
,
nameTypeList
:
item
.
sensors
.
map
(
sensor
=>
({
name
:
sensor
,
type
:
'Sub'
})),
/* nameTypeList: ['今日用电量', '今日供水量'].map(sensor => ({
name: sensor,
type: 'Sub'
})),*/
dateType
:
returnDateType
(
dateRange
[
0
])
};
// 虚拟点需要查出实际点后,进行查找
let
_realSensors
=
{};
let
_realSensorsMap
=
{};
// 统计类的如果是虚拟点,那么需要查出实际数据来源的点,查出映射关系
(
await
getSensorsRealName
(
_params
))?.
data
?.
forEach
(
sensor
=>
{
// name 虚拟点 staticName实际的点
_realSensors
[
sensor
.
staticName
]
=
sensor
.
name
;
_realSensorsMap
[
sensor
.
name
]
=
sensor
.
staticName
;
});
// 请求统计数据时,需要使用实际点去获取
_params
.
nameTypeList
.
forEach
(
sensor
=>
{
sensor
.
name
=
_realSensors
[
sensor
.
name
]
});
// 获取数据后,将原始数据中的dataModel这部分替换掉
((
await
getStatisticsInfo
(
_params
))?.
data
?.
list
?.[
0
].
dNameDataList
??
[])?.
forEach
(
obj
=>
{
let
_v
=
_tempValue
[
`needToReplace_
${
item
.
code
}
_
${
_realSensorsMap
[
obj
.
dName
]}
`
];
_v
.
dataModel
=
obj
.
nameDate
.
map
(
d
=>
{
return
{
pt
:
moment
(
d
.
time
),
pv
:
d
.
value
,
maxPV
:
d
.
value
,
minPV
:
d
.
value
,
firstPV
:
d
.
value
,
lastPV
:
d
.
value
,
}
});
_finalData
[
`needToReplace_
${
item
.
code
}
_
${
_realSensorsMap
[
obj
.
dName
]}
`
]
=
_v
;
});
// 替换数据
_dataSource
.
forEach
((
d
,
index
)
=>
{
if
(
_
.
isString
(
d
)
&&
d
.
includes
(
'needToReplace'
)
&&
_finalData
[
d
])
{
_dataSource
[
index
]
=
_finalData
[
d
];
}
})
// 有不存在数据的,将原始数据替换回来
_dataSource
.
forEach
((
d
,
index
)
=>
{
if
(
_
.
isString
(
d
)
&&
d
.
includes
(
'needToReplace'
))
{
_dataSource
[
index
]
=
dataSource
[
index
];
}
})
}
props
.
setLoading
(
false
);
return
_dataSource
};
const
returnDateType
=
(
date
)
=>
{
let
{
dateFrom
,
dateTo
}
=
date
;
let
_duration
=
moment
.
duration
(
moment
(
dateTo
)
-
moment
(
dateFrom
),
'ms'
).
days
();
if
(
_duration
>=
7
)
return
'month'
;
if
(
_duration
>=
30
)
return
'year'
;
return
'day'
;
};
useEffect
(()
=>
{
async
function
handle
()
{
let
_data
=
await
handleDataSource
(
dataSource
);
const
grids
=
_data
.
reduce
((
pre
,
item
,
index
)
=>
{
const
{
sensorName
,
deviceType
}
=
item
;
const
key
=
`
${
deviceType
}
_
${
sensorName
}
`
;
// 同设备类型同指标才在同一组
let
grid
=
pre
.
find
((
g
)
=>
g
.
key
===
key
);
if
(
!
grid
)
{
const
restProp
=
_
.
pick
(
item
,
[
'equipmentName'
,
'sensorName'
,
'stationCode'
,
'unit'
]);
grid
=
{
key
:
key
,
list
:
[],
...
restProp
,
};
pre
.
push
(
grid
);
}
grid
.
list
.
push
(
item
);
return
pre
;
},
[]);
setGridData
(
grids
);
}
handle
();
},
[
dataSource
])
const
options
=
useMemo
(()
=>
{
const
options
=
useMemo
(()
=>
{
let
_options
=
gridData
.
map
((
item
)
=>
{
let
_options
=
gridData
.
map
((
item
)
=>
{
const
{
key
,
list
,
equipmentName
,
sensorName
,
stationCode
,
unit
}
=
item
;
const
{
key
,
list
,
equipmentName
,
sensorName
,
stationCode
,
unit
}
=
item
;
...
...
packages/extend-components/EC_HistoryView/src/SingleChart.js
View file @
4b15851b
import
React
,
{
memo
,
useEffect
,
useMemo
,
useRef
}
from
'react'
;
import
React
,
{
memo
,
useEffect
,
useMemo
,
useRef
}
from
'react'
;
import
{
BasicChart
}
from
'@wisdom-components/basicchart'
;
import
{
BasicChart
}
from
'@wisdom-components/basicchart'
;
import
PandaEmpty
from
'@wisdom-components/empty'
;
import
PandaEmpty
from
'@wisdom-components/empty'
;
import
optionGenerator
,
{
import
optionGenerator
,
{
alarmMarkLine
,
alarmMarkLine
,
...
@@ -8,7 +8,7 @@ import optionGenerator, {
...
@@ -8,7 +8,7 @@ import optionGenerator, {
specialTypeChartOptionGenerator
,
specialTypeChartOptionGenerator
,
statusChartOptionGenerator
statusChartOptionGenerator
}
from
'./utils'
;
}
from
'./utils'
;
import
{
isArray
,
cloneDeep
}
from
'lodash'
;
import
{
isArray
,
cloneDeep
}
from
'lodash'
;
const
SingleChart
=
memo
((
props
)
=>
{
const
SingleChart
=
memo
((
props
)
=>
{
const
{
const
{
...
@@ -22,7 +22,8 @@ const SingleChart = memo((props) => {
...
@@ -22,7 +22,8 @@ const SingleChart = memo((props) => {
chartType
,
chartType
,
lineDataType
,
lineDataType
,
showBoxOption
,
showBoxOption
,
special
special
,
predicateData
}
=
props
;
}
=
props
;
const
chartRef
=
useRef
();
const
chartRef
=
useRef
();
const
SpecialType
=
[
'状态值'
,
'开关值'
];
// 横向柱状条
const
SpecialType
=
[
'状态值'
,
'开关值'
];
// 横向柱状条
...
@@ -51,16 +52,16 @@ const SingleChart = memo((props) => {
...
@@ -51,16 +52,16 @@ const SingleChart = memo((props) => {
if
(
dataSource
.
length
===
1
&&
SpecialType
.
includes
(
_allPointAddress
[
dataSource
[
0
].
sensorName
]))
{
if
(
dataSource
.
length
===
1
&&
SpecialType
.
includes
(
_allPointAddress
[
dataSource
[
0
].
sensorName
]))
{
config
.
sensorType
=
_allPointAddress
[
dataSource
[
0
].
sensorName
];
config
.
sensorType
=
_allPointAddress
[
dataSource
[
0
].
sensorName
];
config
.
special
.
allValDesc
=
allValDesc
;
config
.
special
.
allValDesc
=
allValDesc
;
return
specialTypeChartOptionGenerator
({
dataSource
,
config
});
return
specialTypeChartOptionGenerator
({
dataSource
,
config
});
}
}
return
optionGenerator
(
dataSource
,
null
,
contrast
,
contrastOption
,
smooth
,
config
,
lineDataType
);
return
optionGenerator
(
dataSource
,
null
,
contrast
,
contrastOption
,
smooth
,
config
,
lineDataType
,
predicateData
);
},
[
dataSource
,
smooth
,
curveCenter
,
chartType
]);
},
[
dataSource
,
smooth
,
curveCenter
,
chartType
,
predicateData
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
chartRef
.
current
?.
resize
?.();
chartRef
.
current
?.
resize
?.();
const
chart
=
chartRef
.
current
?.
getEchartsInstance
?.();
const
chart
=
chartRef
.
current
?.
getEchartsInstance
?.();
function
hander
(
params
)
{
function
hander
(
params
)
{
const
{
selected
}
=
params
;
const
{
selected
}
=
params
;
const
count
=
Object
.
values
(
selected
||
{}).
filter
((
item
)
=>
item
).
length
;
const
count
=
Object
.
values
(
selected
||
{}).
filter
((
item
)
=>
item
).
length
;
const
option
=
cloneDeep
(
chart
.
getOption
());
const
option
=
cloneDeep
(
chart
.
getOption
());
const
needMarkLine
=
count
===
1
;
const
needMarkLine
=
count
===
1
;
...
@@ -126,7 +127,7 @@ const SingleChart = memo((props) => {
...
@@ -126,7 +127,7 @@ const SingleChart = memo((props) => {
};
};
let
yAxis
=
axisConfig
;
let
yAxis
=
axisConfig
;
if
(
isArray
(
option
.
yAxis
))
{
if
(
isArray
(
option
.
yAxis
))
{
yAxis
=
option
.
yAxis
.
map
((
item
)
=>
({
...
axisConfig
}));
yAxis
=
option
.
yAxis
.
map
((
item
)
=>
({
...
axisConfig
}));
}
}
let
xAxis
=
axisConfig
;
let
xAxis
=
axisConfig
;
chart
.
setOption
({
chart
.
setOption
({
...
@@ -145,9 +146,9 @@ const SingleChart = memo((props) => {
...
@@ -145,9 +146,9 @@ const SingleChart = memo((props) => {
[
dataSource
],
[
dataSource
],
);
);
return
isEmpty
?
(
return
isEmpty
?
(
<
PandaEmpty
/>
<
PandaEmpty
/>
)
:
(
)
:
(
<
BasicChart
ref
=
{
chartRef
}
option
=
{
option
}
notMerge
style
=
{{
width
:
'100%'
,
height
:
'100%'
}}
/
>
<
BasicChart
ref
=
{
chartRef
}
option
=
{
option
}
notMerge
style
=
{{
width
:
'100%'
,
height
:
'100%'
}}
/
>
);
);
});
});
...
...
packages/extend-components/EC_HistoryView/src/apis/index.js
View file @
4b15851b
...
@@ -10,7 +10,7 @@ const monitorDeviceUrl = `${baseUrl}/PandaMonitor/Monitor/Device`;
...
@@ -10,7 +10,7 @@ const monitorDeviceUrl = `${baseUrl}/PandaMonitor/Monitor/Device`;
// 获取单个设备的配置信息
// 获取单个设备的配置信息
export
function
getPointAddress
(
params
)
{
export
function
getPointAddress
(
params
)
{
return
request
({
return
request
({
url
:
`/PandaMonitor/Monitor/PointAddress/GetPointAddress`
,
url
:
`
${
baseUrl
}
/PandaMonitor/Monitor/PointAddress/GetPointAddress`
,
method
:
REQUEST_METHOD_GET
,
method
:
REQUEST_METHOD_GET
,
params
params
});
});
...
@@ -19,7 +19,7 @@ export function getPointAddress(params) {
...
@@ -19,7 +19,7 @@ export function getPointAddress(params) {
// 获取点表信息
// 获取点表信息
export
function
getPointAddressEntry
(
params
)
{
export
function
getPointAddressEntry
(
params
)
{
return
request
({
return
request
({
url
:
`/PandaMonitor/Monitor/PointAddress/GetPointAddressEntry`
,
url
:
`
${
baseUrl
}
/PandaMonitor/Monitor/PointAddress/GetPointAddressEntry`
,
method
:
REQUEST_METHOD_GET
,
method
:
REQUEST_METHOD_GET
,
params
params
});
});
...
@@ -62,7 +62,32 @@ export function getDictionaryInfoAll(params) {
...
@@ -62,7 +62,32 @@ export function getDictionaryInfoAll(params) {
export
function
getSensorType
()
{
export
function
getSensorType
()
{
return
request
({
return
request
({
url
:
'/PandaMonitor/Monitor/Sensor/GetSensorType'
,
url
:
`
${
baseUrl
}
/PandaMonitor/Monitor/Sensor/GetSensorType`
,
method
:
REQUEST_METHOD_GET
,
method
:
REQUEST_METHOD_GET
,
})
})
}
export
function
getPredicateSensor
(
params
)
{
return
request
({
url
:
`
${
baseUrl
}
/PandaWater/CityWater/PiZhou/GetPredicateSensor`
,
method
:
REQUEST_METHOD_GET
,
params
})
}
// 获取统计数据
export
function
getStatisticsInfo
(
data
)
{
return
request
({
url
:
`
${
baseUrl
}
/PandaMonitor/Monitor/Device/EquipmentDataReport`
,
method
:
REQUEST_METHOD_POST
,
data
,
});
}
export
function
getSensorsRealName
(
data
)
{
return
request
({
url
:
`
${
baseUrl
}
/PandaMonitor/Monitor/Device/GetStaticRealName`
,
method
:
REQUEST_METHOD_POST
,
data
,
})
}
}
\ No newline at end of file
packages/extend-components/EC_HistoryView/src/demos/GridDemo.js
View file @
4b15851b
import
React
from
'react'
;
import
React
from
'react'
;
import
HistoryView
from
'../index'
;
import
HistoryView
from
'../index'
;
const
deviceParams
=
[
/*
const deviceParams = [
{
{
deviceCode: 'EGBF00000136',
deviceCode: 'EGBF00000136',
sensors: '进水压力,出水瞬时流量,出水累计流量',
sensors: '进水压力,出水瞬时流量,出水累计流量',
deviceType: '二供泵房',
deviceType: '二供泵房',
pointAddressID: 4,
pointAddressID: 4,
},
},
*/
/* {
/* {
deviceCode: 'EGBF00000137',
deviceCode: 'EGBF00000137',
sensors: '进水压力,出水瞬时流量,出水累计流量',
sensors: '进水压力,出水瞬时流量,出水累计流量',
...
@@ -21,7 +21,7 @@ const deviceParams = [
...
@@ -21,7 +21,7 @@ const deviceParams = [
deviceType: '二供泵房',
deviceType: '二供泵房',
pointAddressID: 4,
pointAddressID: 4,
},*/
},*/
];
//
];
/*const deviceParams = [
/*const deviceParams = [
{
{
"deviceCode": "LLJ00000055",
"deviceCode": "LLJ00000055",
...
@@ -58,10 +58,39 @@ const deviceParams = [
...
@@ -58,10 +58,39 @@ const deviceParams = [
"deviceType": "二供泵房"
"deviceType": "二供泵房"
}
}
]*/
]*/
/*const deviceParams = [
{
"deviceCode": "EGBF00000244",
"sensors": "进水压力,出水瞬时流量,今日用电量",
"deviceType": "二供泵房"
},
{
"deviceCode": "EGBF00000081",
"sensors": "进水压力,出水瞬时流量,今日用电量",
"deviceType": "二供泵房"
}
]*/
const
deviceParams
=
[
{
"deviceCode"
:
"EGBF00000244"
,
"sensors"
:
"进水压力,出水瞬时流量,今日用电量,今日供水量"
,
"deviceType"
:
"二供泵房"
},
{
"deviceCode"
:
"EGJZ00000317"
,
"sensors"
:
"进水压力,出水瞬时流量"
,
"deviceType"
:
"二供机组"
},
{
"deviceCode"
:
"EGBF00000184"
,
"sensors"
:
"进水压力,出水瞬时流量,今日用电量,今日供水量"
,
"deviceType"
:
"二供泵房"
}
];
const
Demo
=
()
=>
{
const
Demo
=
()
=>
{
return
<
div
style
=
{{
height
:
700
}}
>
return
<
div
style
=
{{
height
:
700
}}
>
<
HistoryView
deviceParams
=
{
deviceParams
}
grid
/>
<
HistoryView
deviceParams
=
{
deviceParams
}
grid
/>
<
/div>
;
<
/div>
;
};
};
export
default
Demo
;
export
default
Demo
;
packages/extend-components/EC_HistoryView/src/demos/index.js
View file @
4b15851b
...
@@ -178,10 +178,16 @@ const deviceParams = [
...
@@ -178,10 +178,16 @@ const deviceParams = [
"sensors": "PH,浑浊度,氨氮,总氮,总磷,溶解氧,水温,电导率,叶绿素a,藻密度,高锰酸盐指数"
"sensors": "PH,浑浊度,氨氮,总氮,总磷,溶解氧,水温,电导率,叶绿素a,藻密度,高锰酸盐指数"
}*/
}*/
/*邳州*/
/*邳州*/
{
/*
{
"deviceCode": "TLC00000001",
"deviceCode": "TLC00000001",
"sensors": "1号碳滤池浊度",
"sensors": "1号碳滤池浊度",
"deviceType": "碳滤池"
"deviceType": "碳滤池"
}*/
/*邳州*/
{
"deviceCode"
:
"SSBF00000001"
,
"sensors"
:
"出水压力"
,
"deviceType"
:
"送水泵房"
}
}
];
];
...
...
packages/extend-components/EC_HistoryView/src/index.js
View file @
4b15851b
...
@@ -35,7 +35,7 @@ import {
...
@@ -35,7 +35,7 @@ import {
getExportDeviceHistoryUrl
,
getExportDeviceHistoryUrl
,
getDictionaryInfoAll
,
getDictionaryInfoAll
,
getPointAddress
,
getPointAddress
,
getPointAddressEntry
,
getPointAddressEntry
,
getPredicateSensor
,
}
from
'./apis'
;
}
from
'./apis'
;
import
SingleChart
from
'./SingleChart'
;
import
SingleChart
from
'./SingleChart'
;
import
GridChart
from
'./GridChart'
;
import
GridChart
from
'./GridChart'
;
...
@@ -337,6 +337,8 @@ const HistoryView = (props) => {
...
@@ -337,6 +337,8 @@ const HistoryView = (props) => {
//查询所有sensorType
//查询所有sensorType
const
[
allSensorType
,
setAllSensorType
]
=
useState
([]);
const
[
allSensorType
,
setAllSensorType
]
=
useState
([]);
const
[
isSingleStatusSensor
,
setIsSingleStatusSensor
]
=
useState
(
false
);
const
[
isSingleStatusSensor
,
setIsSingleStatusSensor
]
=
useState
(
false
);
const
[
predicateDevice
,
setPredicateDevice
]
=
useState
(
null
);
const
[
predicateData
,
setPredicateData
]
=
useState
(
null
);
// 历史数据相关的特征描述
// 历史数据相关的特征描述
const
deviceConfig
=
useRef
({
const
deviceConfig
=
useRef
({
oneDevice
:
deviceParams
.
length
===
1
,
//单设备
oneDevice
:
deviceParams
.
length
===
1
,
//单设备
...
@@ -1079,11 +1081,6 @@ const HistoryView = (props) => {
...
@@ -1079,11 +1081,6 @@ const HistoryView = (props) => {
deviceParams
deviceParams
.
map
((
item
)
=>
{
.
map
((
item
)
=>
{
let
_item
=
{...
item
};
let
_item
=
{...
item
};
// 历史曲线中,是否在线暂时去除,不显示 要显示是否在线解开这里即可 2023-09-15
/* _item.sensors =
item.sensors && !item.sensors.includes('是否在线')
? item.sensors + ',是否在线'
: item.sensors;*/
_item
.
sensors
=
item
.
sensors
;
_item
.
sensors
=
item
.
sensors
;
// special 业务
// special 业务
if
(
special1
)
{
if
(
special1
)
{
...
@@ -1092,7 +1089,7 @@ const HistoryView = (props) => {
...
@@ -1092,7 +1089,7 @@ const HistoryView = (props) => {
return
_item
;
return
_item
;
})
})
.
forEach
((
i
)
=>
{
.
forEach
((
i
)
=>
{
if
(
i
.
sensors
&&
i
.
deviceCode
&&
i
.
deviceCode
)
if
(
i
.
sensors
&&
i
.
deviceCode
)
acrossTables
.
push
(
_
.
omit
(
i
,
[
'pointAddressID'
]));
acrossTables
.
push
(
_
.
omit
(
i
,
[
'pointAddressID'
]));
});
});
if
(
!
acrossTables
?.
length
||
!
dateRange
.
length
)
{
if
(
!
acrossTables
?.
length
||
!
dateRange
.
length
)
{
...
@@ -1127,6 +1124,11 @@ const HistoryView = (props) => {
...
@@ -1127,6 +1124,11 @@ const HistoryView = (props) => {
let
_isNaN
=
isNaN
(
_num
);
let
_isNaN
=
isNaN
(
_num
);
if
(
!
_isNaN
&&
_num
>=
12
)
_finalParams
.
isInterpolation
=
false
;
if
(
!
_isNaN
&&
_num
>=
12
)
_finalParams
.
isInterpolation
=
false
;
}
}
// 2024年1月23日 增加预测曲线,单设备单曲线
// 同期对比不允许、多设备的不允许
if
(
dateRange
.
length
===
1
&&
predicateDevice
)
{
_finalParams
.
acrossTables
.
push
(
predicateDevice
);
}
requestArr
.
push
(
getHistoryInfo
(
_finalParams
));
requestArr
.
push
(
getHistoryInfo
(
_finalParams
));
});
});
setLoading
(
true
);
setLoading
(
true
);
...
@@ -1135,6 +1137,7 @@ const HistoryView = (props) => {
...
@@ -1135,6 +1137,7 @@ const HistoryView = (props) => {
setLoading
(
false
);
setLoading
(
false
);
if
(
results
.
length
)
{
if
(
results
.
length
)
{
let
data
=
[];
let
data
=
[];
let
_predicateData
=
[];
results
.
forEach
((
res
,
index
)
=>
{
results
.
forEach
((
res
,
index
)
=>
{
const
{
dateFrom
,
dateTo
}
=
dateRange
?.[
index
]
??
{};
const
{
dateFrom
,
dateTo
}
=
dateRange
?.[
index
]
??
{};
if
(
res
.
code
===
0
&&
res
.
data
.
length
)
{
if
(
res
.
code
===
0
&&
res
.
data
.
length
)
{
...
@@ -1165,7 +1168,8 @@ const HistoryView = (props) => {
...
@@ -1165,7 +1168,8 @@ const HistoryView = (props) => {
};
};
});
});
});
});
deviceParams
.
forEach
((
p
)
=>
{
// 加入预测
(
predicateDevice
?
deviceParams
.
concat
(
predicateDevice
)
:
deviceParams
).
forEach
((
p
)
=>
{
// 返回数据按查询指标顺序排序
// 返回数据按查询指标顺序排序
const
sensors
=
p
.
sensors
?.
split
(
','
)
??
[];
const
sensors
=
p
.
sensors
?.
split
(
','
)
??
[];
if
(
sensors
?.
length
)
{
if
(
sensors
?.
length
)
{
...
@@ -1174,27 +1178,29 @@ const HistoryView = (props) => {
...
@@ -1174,27 +1178,29 @@ const HistoryView = (props) => {
sensors
.
push
(
special1
.
name
);
sensors
.
push
(
special1
.
name
);
}
}
}
}
const
list
=
sensors
const
list
=
sensors
.
map
((
s
)
=>
{
.
map
((
s
)
=>
{
const
dataItem
=
res
.
data
.
find
(
const
dataItem
=
res
.
data
.
find
(
(
d
)
=>
d
.
stationCode
===
p
.
deviceCode
&&
d
.
sensorName
===
s
,
(
d
)
=>
d
.
stationCode
===
p
.
deviceCode
&&
d
.
sensorName
===
s
,
);
);
if
(
dataItem
)
{
if
(
dataItem
)
{
dataItem
.
dateFrom
=
dateFrom
||
''
;
dataItem
.
dateFrom
=
dateFrom
||
''
;
dataItem
.
dateTo
=
dateTo
||
''
;
dataItem
.
dateTo
=
dateTo
||
''
;
dataItem
.
deviceType
=
p
.
deviceType
;
return
dataItem
;
return
dataItem
;
}
else
{
}
else
{
return
{};
return
{};
}
}
})
}).
filter
((
item
)
=>
item
.
sensorName
);
.
filter
((
item
)
=>
item
.
sensorName
);
// 预测的
data
=
data
.
concat
(
list
);
data
=
data
.
concat
(
list
.
filter
(
item
=>
item
.
deviceType
!==
'预测'
));
_predicateData
=
_predicateData
.
concat
(
list
.
filter
(
item
=>
item
.
deviceType
===
'预测'
));
});
});
}
}
});
});
setLoading
(
false
);
setLoading
(
false
);
handleTableData
(
data
)
;
handleTableData
(
data
)
setChartDataSource
(
data
);
setChartDataSource
(
data
);
setPredicateData
(
_predicateData
);
}
}
})
})
.
catch
((
err
)
=>
{
.
catch
((
err
)
=>
{
...
@@ -1322,6 +1328,11 @@ const HistoryView = (props) => {
...
@@ -1322,6 +1328,11 @@ const HistoryView = (props) => {
contrast
=
{
timeValue
===
'contrast'
}
contrast
=
{
timeValue
===
'contrast'
}
contrastOption
=
{
contrastOption
}
contrastOption
=
{
contrastOption
}
deviceAlarmSchemes
=
{
deviceAlarmSchemes
}
deviceAlarmSchemes
=
{
deviceAlarmSchemes
}
dateRange
=
{
dateRange
}
allPointAddress
=
{
allPointAddress
}
allSensorType
=
{
allSensorType
}
loading
=
{
loading
}
setLoading
=
{
setLoading
}
/
>
/
>
)
:
(
)
:
(
<
SingleChart
<
SingleChart
...
@@ -1331,6 +1342,7 @@ const HistoryView = (props) => {
...
@@ -1331,6 +1342,7 @@ const HistoryView = (props) => {
showGridLine
=
{
chartGrid
}
showGridLine
=
{
chartGrid
}
prefixCls
=
{
prefixCls
}
prefixCls
=
{
prefixCls
}
dataSource
=
{
chartDataSource
}
dataSource
=
{
chartDataSource
}
predicateData
=
{
predicateData
}
chartType
=
{
isBoxPlots
?
chartType
:
null
}
chartType
=
{
isBoxPlots
?
chartType
:
null
}
contrast
=
{
timeValue
===
'contrast'
}
contrast
=
{
timeValue
===
'contrast'
}
contrastOption
=
{
contrastOption
}
contrastOption
=
{
contrastOption
}
...
@@ -1374,11 +1386,13 @@ const HistoryView = (props) => {
...
@@ -1374,11 +1386,13 @@ const HistoryView = (props) => {
// 以下请求为处理状态值、开关值的图表,只允许单曲线单指标情况下展示
// 以下请求为处理状态值、开关值的图表,只允许单曲线单指标情况下展示
let
_request1
=
getPointAddressEntry
(
_params
);
let
_request1
=
getPointAddressEntry
(
_params
);
let
_request2
=
getSensorType
();
let
_request2
=
getSensorType
();
await
Promise
.
all
([
_request0
,
_request1
,
_request2
]).
then
((
result
)
=>
{
let
_request3
=
getPredicateSensor
({
deviceCode
,
sensors
});
await
Promise
.
all
([
_request0
,
_request1
,
_request2
,
_request3
]).
then
((
result
)
=>
{
if
(
result
)
{
if
(
result
)
{
let
_res0
=
result
[
0
];
let
_res0
=
result
[
0
];
let
_res1
=
result
[
1
];
let
_res1
=
result
[
1
];
let
_res2
=
result
[
2
];
let
_res2
=
result
[
2
];
let
_res3
=
result
[
3
];
// 查字典配置
// 查字典配置
if
(
_res0
.
code
===
0
)
{
if
(
_res0
.
code
===
0
)
{
let
_opt
=
_res0
.
data
.
reduce
((
final
,
cur
)
=>
{
let
_opt
=
_res0
.
data
.
reduce
((
final
,
cur
)
=>
{
...
@@ -1421,6 +1435,10 @@ const HistoryView = (props) => {
...
@@ -1421,6 +1435,10 @@ const HistoryView = (props) => {
let
_isStatusSensor
=
[
'状态值'
,
'开关值'
].
includes
(
_sensor
);
let
_isStatusSensor
=
[
'状态值'
,
'开关值'
].
includes
(
_sensor
);
setIsSingleStatusSensor
(
_isStatusSensor
);
setIsSingleStatusSensor
(
_isStatusSensor
);
}
}
// 单设备单曲线的设定加载
if
(
_res3
.
code
===
0
)
{
setPredicateDevice
({...
_res3
.
data
,
deviceType
:
'预测'
});
}
}
}
});
});
setCompleteInit
(
true
);
setCompleteInit
(
true
);
...
@@ -1497,11 +1515,6 @@ const HistoryView = (props) => {
...
@@ -1497,11 +1515,6 @@ const HistoryView = (props) => {
<
DownloadOutlined
/>
<
DownloadOutlined
/>
下载
下载
<
/Button
>
<
/Button
>
{
/* 保留此处代码,当项目需要表格定制时需要使用 */
}
{
/*<Button type="link" onClick={exportExcelBtn}>
<DownloadOutlined/>
原始数据
</Button> */
}
<
/
>
<
/
>
)}
)}
<
/div
>
<
/div
>
...
...
packages/extend-components/EC_HistoryView/src/utils.js
View file @
4b15851b
...
@@ -71,7 +71,7 @@ const currentOption = isMobile() ? MOBILE_OPTION : PC_OPTION;
...
@@ -71,7 +71,7 @@ const currentOption = isMobile() ? MOBILE_OPTION : PC_OPTION;
*/
*/
const
nameFormatter
=
(
data
,
contrast
,
contrastOption
,
nameWithSensor
,
isSingle
)
=>
{
const
nameFormatter
=
(
data
,
contrast
,
contrastOption
,
nameWithSensor
,
isSingle
)
=>
{
const
{
equipmentName
,
sensorName
,
unit
,
dataModel
,
dateFrom
,
dateTo
}
=
data
;
const
{
equipmentName
,
sensorName
,
unit
,
dataModel
,
dateFrom
,
dateTo
}
=
data
;
let
name
=
nameWithSensor
?
(
isSingle
?
`
${
sensorName
}
`
:
`
${
equipmentName
}
-
${
sensorName
}
`
)
:
equipmentName
;
let
name
=
nameWithSensor
?
(
isSingle
?
`
${
sensorName
}
`
:
`
${
equipmentName
}
-
${
sensorName
}
`
)
:
equipmentName
;
if
(
contrast
)
{
if
(
contrast
)
{
const
time
=
dateFrom
.
slice
(
0
,
contrastOption
===
'day'
?
10
:
7
).
replace
(
/-/g
,
''
);
const
time
=
dateFrom
.
slice
(
0
,
contrastOption
===
'day'
?
10
:
7
).
replace
(
/-/g
,
''
);
name
=
`
${
name
}
-
${
time
}
`
;
name
=
`
${
name
}
-
${
time
}
`
;
...
@@ -182,7 +182,20 @@ export const alarmMarkLine = (dataItem, index, dataSource, schemes) => {
...
@@ -182,7 +182,20 @@ export const alarmMarkLine = (dataItem, index, dataSource, schemes) => {
data
,
data
,
};
};
};
};
export
const
minMaxMarkPointForPredicateDevice
=
(
dataItem
,
index
,
dateSource
)
=>
{
return
{
symbol
:
'circle'
,
animation
:
false
,
label
:
{
show
:
false
},
symbolSize
:
10
,
data
:
[
{
type
:
'max'
,
itemStyle
:
{
color
:
'#dc5a5a'
}},
{
type
:
'min'
,
itemStyle
:
{
color
:
'#62b659'
}}
]
};
}
export
const
minMaxMarkPoint
=
(
dataItem
,
index
,
dataSource
)
=>
{
export
const
minMaxMarkPoint
=
(
dataItem
,
index
,
dataSource
)
=>
{
const
_isMobile
=
isMobile
();
const
_isMobile
=
isMobile
();
// 只有一个数据曲线时显示markline
// 只有一个数据曲线时显示markline
...
@@ -501,6 +514,8 @@ const returnXAxis = ({
...
@@ -501,6 +514,8 @@ const returnXAxis = ({
smooth,
smooth,
special,
special,
yAxis,
yAxis,
predicateData,
chartType
}) => {
}) => {
// 根据"指标名称"分类yAxis
// 根据"指标名称"分类yAxis
const yAxisInterator = (() => {
const yAxisInterator = (() => {
...
@@ -522,7 +537,8 @@ const returnXAxis = ({
...
@@ -522,7 +537,8 @@ const returnXAxis = ({
if (!final.includes(cur.stationCode)) final.push(cur.stationCode);
if (!final.includes(cur.stationCode)) final.push(cur.stationCode);
return final
return final
}, [])?.length;
}, [])?.length;
let series = dataSource
// 线图 且 有预测数据情况下,才合并预测数据
let series = (predicateData && chartType === 'lineChart' ? dataSource.concat(predicateData) : dataSource)
.filter((item) => {
.filter((item) => {
if (item.sensorName === '是否在线') {
if (item.sensorName === '是否在线') {
_offlineData.push(item);
_offlineData.push(item);
...
@@ -537,20 +553,22 @@ const returnXAxis = ({
...
@@ -537,20 +553,22 @@ const returnXAxis = ({
const areaStyle = areaStyleFormatter(item);
const areaStyle = areaStyleFormatter(item);
const _index = yAxis.findIndex((item) => item.name === unit);
const _index = yAxis.findIndex((item) => item.name === unit);
const yAxisIndex = _index > -1 ? _index : 0;
const yAxisIndex = _index > -1 ? _index : 0;
cons
t markLine = showMarkLine
le
t markLine = showMarkLine
? alarmMarkLine(item, index, dataSource, deviceAlarmSchemes)
? alarmMarkLine(item, index, dataSource, deviceAlarmSchemes)
: {};
: {};
const markPoint = showPoint ? minMaxMarkPoint(item, index, dataSource) : {};
let markPoint = showPoint ? minMaxMarkPoint(item, index, dataSource) : {};
// let markArea = null;
let _lineStyle = {};
// 需求变更:设备离线改用“是否在线”的数据,离线的状态标记的数据用该部分的数据。 2023年4月25日09:36:55
if (item.deviceType === '预测' && chartType === 'lineChart') {
// 暂时注释,离线逻辑需要再确认 2023-09-15
// markPoint = minMaxMarkPointForPredicateDevice(item, index, dataSource);
/* let _offlineAreasData = _offlineData
markPoint = null;
.find((offline) => offline.stationCode === item.stationCode);
markLine = null;
let offlineAreas = offlineArea(_offlineAreasData);
_lineStyle = {
if (offlineAreas.data?.length) {
lineStyle: {
restOption.markArea = null;
type: 'dashed',
markArea = offlineAreas;
color: '#07a49a'
}*/
}
}
}
// 需求新增:增加频率业务
// 需求新增:增加频率业务
return {
return {
notMerge: true,
notMerge: true,
...
@@ -564,6 +582,7 @@ const returnXAxis = ({
...
@@ -564,6 +582,7 @@ const returnXAxis = ({
markLine,
markLine,
markPoint,
markPoint,
markArea,
markArea,
..._lineStyle,
};
};
});
});
// 由于series更新后,没有的数据曲线仍然停留在图表区上,导致图表可视区范围有问题
// 由于series更新后,没有的数据曲线仍然停留在图表区上,导致图表可视区范围有问题
...
@@ -967,6 +986,8 @@ const renderStatusItem = (params, api) => {
...
@@ -967,6 +986,8 @@ const renderStatusItem = (params, api) => {
* @param {any} contrastOption 同期对比周期配置, day|month
* @param {any} contrastOption 同期对比周期配置, day|month
* @param {any} smooth Ture/false, 曲线/折线
* @param {any} smooth Ture/false, 曲线/折线
* @param {any} config 额外配置信息
* @param {any} config 额外配置信息
* @param lineDataType
* @param predicateData
*/
*/
const optionGenerator = (
const optionGenerator = (
...
@@ -977,6 +998,7 @@ const optionGenerator = (
...
@@ -977,6 +998,7 @@ const optionGenerator = (
smooth,
smooth,
config,
config,
lineDataType = '',
lineDataType = '',
predicateData
) => {
) => {
// 1. 处理配置,配置分配默认值;
// 1. 处理配置,配置分配默认值;
const {
const {
...
@@ -1010,6 +1032,8 @@ const optionGenerator = (
...
@@ -1010,6 +1032,8 @@ const optionGenerator = (
restOption,
restOption,
special,
special,
yAxis,
yAxis,
predicateData,
chartType
});
});
// 3. 判断是否开启网格;
// 3. 判断是否开启网格;
const grid = handleGrid(dataSource, needUnit, leftNum, rightNum, chartType);
const grid = handleGrid(dataSource, needUnit, leftNum, rightNum, chartType);
...
@@ -1110,6 +1134,7 @@ const optionGenerator = (
...
@@ -1110,6 +1134,7 @@ const optionGenerator = (
symbol: 'none',
symbol: 'none',
});
});
});
});
// 加入预测逻辑
tooltip = {
tooltip = {
trigger: 'axis',
trigger: 'axis',
formatter: (e) => {
formatter: (e) => {
...
@@ -1156,6 +1181,62 @@ const optionGenerator = (
...
@@ -1156,6 +1181,62 @@ const optionGenerator = (
<
/div>`
;
<
/div>`
;
},
},
};
};
if
(
predicateData
)
{
tooltip
=
{
trigger
:
'axis'
,
formatter
:
(
e
)
=>
{
return
`<div>
${
headTemplate
(
e
[
0
])}
<div>
<div style="display: flex; align-items: center;">
<span style="
${
isMobile
()
?
'width: '
+
handlePx
(
90
,
'px'
)
+
';overflow:hidden;text-overflow:ellipsis;white-space:nowrap'
:
''
}
">
${
e
[
0
].
seriesName
}
</span><span style="display:inline-block;">:</span>
<span style="color:
${
COLOR
.
NORMAL
}
;margin: 0
${
handlePx
(
5
,
'px'
,
)}
0 auto;">
${
e
[
0
]?.
value
?.[
1
]
??
'-'
}
<
/span
>
<
span
style
=
"font-size: ${handlePx(12, 'px')};"
>
$
{
_unit
??
''
}
<
/span
>
<
/div
>
<
div
style
=
"display: ${predicateData && chartType === 'lineChart' && e.length >= 2 ? 'flex' : 'none'}"
>
<
span
>
$
{
e
?.[
1
]?.
seriesName
}
:
<
/span
>
<
span
style
=
"color: ${COLOR.NORMAL};margin: 0 ${handlePx(
5,
'px',
)} 0 auto;"
>
$
{
e
[
1
]?.
value
?.[
1
]
??
'-'
}
<
/span
>
<
span
style
=
"font-size: ${handlePx(12, 'px')};"
>
$
{
_unit
??
''
}
<
/span>
<
/div
>
<
div
style
=
"display: ${
lineDataType === '特征曲线' ? 'flex' : 'none'
}; align-items: center;"
>
<
span
>
周期最小值
<
/span><span style="display:inline-block;">:</
span
>
<
span
style
=
"color: ${COLOR.AVG};margin: 0 ${handlePx(
5,
'px',
)} 0 auto;"
>
$
{
e
?.[
2
]?.
value
?.[
1
]
??
'-'
}
<
/span
>
<
span
style
=
"font-size: ${handlePx(12, 'px')};"
>
$
{
_unit
??
''
}
<
/span
>
<
/div
>
<
div
style
=
"display: ${
lineDataType === '特征曲线' ? 'flex' : 'none'
}; align-items: center;"
>
<
span
>
周期最大值
<
/span><span style="display:inline-block;">:</
span
>
<
span
style
=
"color: ${COLOR.AVG};margin: 0 ${handlePx(
5,
'px',
)} 0 auto;"
>
$
{
_maxValues
[
e
?.[
3
]?.
dataIndex
]
??
'-'
}
<
/span
>
<
span
style
=
"font-size: ${handlePx(12, 'px')};"
>
$
{
_unit
??
''
}
<
/span
>
<
/div
>
<
/div
>
<
/div>`
;
},
};
}
}
}
// 单曲线需要标记最大值、最小值的情况下,需要增加自定义的series,将最大最小值显示在图表上
// 单曲线需要标记最大值、最小值的情况下,需要增加自定义的series,将最大最小值显示在图表上
if
(
dataSource
?.[
0
]?.
dataModel
?.
length
&&
chartType
===
'lineChart'
)
{
if
(
dataSource
?.[
0
]?.
dataModel
?.
length
&&
chartType
===
'lineChart'
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment