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
d920b3f2
Commit
d920b3f2
authored
Sep 05, 2023
by
陈龙
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 调整历史曲线
parent
df570e92
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
221 additions
and
56 deletions
+221
-56
index.js
packages/extend-components/EC_HistoryView/src/demos/index.js
+37
-23
index.js
packages/extend-components/EC_HistoryView/src/index.js
+104
-29
index.less
packages/extend-components/EC_HistoryView/src/index.less
+61
-0
utils.js
packages/extend-components/EC_HistoryView/src/utils.js
+19
-4
No files found.
packages/extend-components/EC_HistoryView/src/demos/index.js
View file @
d920b3f2
...
...
@@ -3,13 +3,13 @@ import HistoryView from '../index';
import
{
MobileHistoryChart
}
from
"../mobile"
;
const
deviceParams
=
[
{
deviceCode
:
'EGBF00000141'
,
// sensors: '进水压力,出水瞬时流量,出水累计流量',
sensors
:
'进水压力'
,
deviceType
:
'二供泵房'
,
pointAddressID
:
208
,
},
/*
{
deviceCode: 'EGBF00000141',
// sensors: '进水压力,出水瞬时流量,出水累计流量',
sensors: '进水压力',
deviceType: '二供泵房',
pointAddressID: 208,
},*/
/* {
"deviceCode": "SYJ00000008",
"sensors": "瞬时流量",
...
...
@@ -32,28 +32,42 @@ const deviceParams = [
deviceType: '二供机组',
pointAddressID: 4,
},*/
/* {
"deviceCode": "LLJ00000055",
"sensors": "正累计流量,瞬时流量,是否在线",
"deviceType": "流量计"
}*/
/*
{
"deviceCode": "LLJ00000055",
"sensors": "正累计流量,瞬时流量,是否在线",
"deviceType": "流量计"
}*/
/* {
"deviceCode": "EGJZ00000163",
"sensors": "进水压力,是否在线",
"deviceType": "二供机组"
}*/
// 邳州张楼水厂
/* {
"deviceCode": "SC00000023",
"sensors": "出水压力",
"deviceType": "水厂"
}*/
/*
{
"deviceCode": "JFJ00000001",
"sensors": "沉淀池投矾量瞬时,",
"deviceType": "加矾间"
}*/
/* {
"deviceCode": "SC00000023",
"sensors": "出水压力",
"deviceType": "水厂"
}*/
/* {
"deviceCode": "JFJ00000001",
"sensors": "沉淀池投矾量瞬时,",
"deviceType": "加矾间"
}*/
/* {
"deviceCode": "QSBF00000001",
"sensors": "取水浊度",
"deviceType": "取水泵房"
}*/
/* {
"deviceCode": "SC00000023",
"sensors": "出水瞬时流量,是否在线",
"deviceType": "水厂"
}*/
{
"deviceCode"
:
"XNCDC00000001"
,
"sensors"
:
"沉淀池1号进水管流量"
,
"deviceType"
:
"絮凝沉淀池"
}
];
const
Demo
=
()
=>
{
return
(
...
...
packages/extend-components/EC_HistoryView/src/index.js
View file @
d920b3f2
import
React
,
{
useContext
,
useEffect
,
useMemo
,
useState
,
useCallback
}
from
'react'
;
import
React
,
{
useContext
,
useEffect
,
useMemo
,
useState
,
useCallback
,
useRef
}
from
'react'
;
import
PropTypes
from
'prop-types'
;
import
classNames
from
'classnames'
;
import
{
...
...
@@ -11,7 +11,7 @@ import {
Tabs
,
Tooltip
,
Button
,
message
,
message
,
Progress
,
}
from
'antd'
;
import
{
CloseCircleFilled
,
...
...
@@ -47,6 +47,10 @@ const timeList = [
key
:
'roundClock'
,
name
:
'近24小时'
,
},
{
key
:
'yesterday'
,
name
:
'昨日'
},
{
key
:
'oneWeek'
,
name
:
'近1周'
,
...
...
@@ -111,6 +115,7 @@ const CheckboxData = [
showInCurve
:
true
,
showInTable
:
true
,
tooltip
:
'本算法采用递推平均滤波法(滑动平均滤波法)对采样数据进行均值化平滑处理。'
,
hasSub
:
true
},
{
key
:
'dataThin'
,
...
...
@@ -190,6 +195,10 @@ const updateTime = (key) => {
start
=
moment
().
subtract
(
24
,
'hour'
).
format
(
timeFormat
);
end
=
moment
().
format
(
timeFormat
);
break
;
case
'yesterday'
:
start
=
moment
().
subtract
(
1
,
'days'
).
format
(
'YYYY-MM-DD 00:00:00'
);
end
=
moment
().
subtract
(
1
,
'days'
).
format
(
'YYYY-MM-DD 23:59:59'
);
break
;
case
'oneWeek'
:
start
=
moment
().
subtract
(
7
,
'day'
).
format
(
timeFormat
);
end
=
moment
().
format
(
timeFormat
);
...
...
@@ -244,8 +253,8 @@ const timeColumn = {
sorter
:
true
,
// sortOrder:['descend','ascend']
};
const
OriginMaxDays
=
7
;
// 原始曲线请求数据的最大天数
const
CharacteristicMaxDays
=
60
;
// 特征曲线或者其他曲线的最大天数
const
OriginMaxDays
=
31
;
// 原始曲线请求数据的最大天数
const
CharacteristicMaxDays
=
null
;
// 特征曲线或者其他曲线的最大天数
const
HistoryView
=
(
props
)
=>
{
const
[
completeInit
,
setCompleteInit
]
=
useState
(
false
);
const
{
getPrefixCls
}
=
useContext
(
ConfigProvider
.
ConfigContext
);
...
...
@@ -279,6 +288,7 @@ const HistoryView = (props) => {
const
[
checkboxData
,
setCheckboxData
]
=
useState
(()
=>
[...
CheckboxData
]);
// 曲线设置项
const
[
dataThinKey
,
setDataThinKey
]
=
useState
(
timeIntervalList
[
0
].
key
);
// 曲线抽稀时间设置
const
[
algorithmValue
,
setAlgorithmValue
]
=
useState
(
1
);
const
[
columns
,
setColumns
]
=
useState
([]);
const
[
tableData
,
setTableData
]
=
useState
([]);
...
...
@@ -292,6 +302,7 @@ const HistoryView = (props) => {
// onShortcutsChange
const
[
shortcutsValue
,
setShortcutsValue
]
=
useState
(
''
);
const
[
shortcutsDatePickerArr
,
setShortcutsDatePickerArr
]
=
useState
([]);
const
[
percent
,
setPercent
]
=
useState
(
0
);
// 选择的时间范围值
const
dateRange
=
useMemo
(()
=>
{
if
(
timeValue
===
'customer'
)
{
...
...
@@ -339,10 +350,10 @@ const HistoryView = (props) => {
// 自定义模式: 快速选择
const
onCustomerTimeChange
=
(
key
)
=>
{
if
(
key
===
'oneMonth'
&&
lineDataType
===
'原始曲线'
)
{
setLineDataType
(
'特征曲线'
);
message
.
info
(
'月模式数据量较大,不支持原始曲线模式,已切换为特征曲线'
)
}
/*
if (key === 'oneMonth' && lineDataType === '原始曲线') {
setLineDataType('特征曲线');
message.info('月模式数据量较大,不支持原始曲线模式,已切换为特征曲线')
}*/
setCustomerChecked
(
key
);
!!
customerTime
&&
setCustomerTime
(
null
);
};
...
...
@@ -356,7 +367,7 @@ const HistoryView = (props) => {
}
else
{
setCustomerChecked
(
null
);
let
diffDays
=
moment
(
value
[
1
]).
diff
(
moment
(
value
[
0
]),
'days'
);
if
(
diffDays
>
7
&&
lineDataType
===
'原始曲线'
)
{
if
(
diffDays
>
OriginMaxDays
&&
lineDataType
===
'原始曲线'
)
{
setLineDataType
(
'特征曲线'
);
message
.
info
(
'时间区间超过7天,已切换为特征曲线'
);
}
...
...
@@ -493,10 +504,11 @@ const HistoryView = (props) => {
format
=
{
'YYYY-MM-DD HH:mm'
}
onChange
=
{
onCustomerTimeChange
}
value
=
{
customerChecked
}
dataSource
=
{
timeList
.
filter
(
item
=>
{
if
(
lineDataType
===
'原始曲线'
)
return
item
.
key
!==
'oneMonth'
;
return
true
})}
/* dataSource={timeList.filter(item => {
if (lineDataType === '原始曲线') return item.key !== 'oneMonth';
return true
})}*/
dataSource
=
{
timeList
}
/
>
<
RangePicker
format
=
{
'YYYY-MM-DD HH:mm'
}
...
...
@@ -504,7 +516,6 @@ const HistoryView = (props) => {
onChange
=
{
onCustomerRangeChange
}
value
=
{
dates
||
customerTime
}
onCalendarChange
=
{(
val
)
=>
{
console
.
log
(
'val: '
,
val
);
setDates
(
val
);
}}
onOpenChange
=
{(
open
)
=>
{
...
...
@@ -520,6 +531,7 @@ const HistoryView = (props) => {
if
(
!
dates
)
{
return
false
;
}
if
(
!
_days
)
return
false
;
const
tooLate
=
dates
[
0
]
&&
current
.
diff
(
dates
[
0
],
'days'
)
>
_days
;
const
tooEarly
=
dates
[
1
]
&&
dates
[
1
].
diff
(
current
,
'days'
)
>
_days
;
return
!!
tooEarly
||
!!
tooLate
;
...
...
@@ -533,16 +545,12 @@ const HistoryView = (props) => {
)}
{
timeValue
===
'contrast'
&&
(
// 同期对比
<>
{
lineDataType
!==
'原始曲线'
&&
<
Select
value
=
{
contrastOption
}
style
=
{{
width
:
60
}}
onChange
=
{
onContrastChange
}
>
<
Option
value
=
"day"
>
日
<
/Option
>
<
Option
value
=
"month"
>
月
<
/Option
>
<
/Select
>
}
<
Select
value
=
{
contrastOption
}
style
=
{{
width
:
60
}}
onChange
=
{
onContrastChange
}
>
<
Option
value
=
"day"
>
日
<
/Option
>
<
Option
value
=
"month"
disabled
=
{
lineDataType
===
'原始曲线'
}
>
月
<
/Option
>
<
/Select
>
{
/*增加快捷日期*/
}
{
lineDataType
!==
'原始曲线'
&&
deviceParams
?.
length
===
1
&&
deviceParams
?.[
0
]?.
sensors
?.
split
(
','
).
length
===
1
?
<
Radio
.
Group
value
=
{
shortcutsValue
}
onChange
=
{
onShortcutsChange
}
>
{
...
...
@@ -624,9 +632,12 @@ const HistoryView = (props) => {
let
_startDate
=
dateRange
[
0
]?.
dateFrom
;
let
_endDate
=
dateRange
[
0
]?.
dateTo
;
let
diffDays
=
moment
(
_endDate
).
diff
(
moment
(
_startDate
),
'days'
);
if
(
_val
===
'原始曲线'
&&
diffDays
>
7
)
{
message
.
info
(
'查阅原始曲线时,需选择小于或等于7天的时间间隔,已自动切换为近一周'
);
setCustomerChecked
(
'oneWeek'
);
if
(
_val
===
'原始曲线'
&&
diffDays
>
OriginMaxDays
)
{
message
.
info
(
'查阅原始曲线时,需选择小于或等于31天的时间间隔,已自动切换为近一月'
);
setCustomerChecked
(
'oneMonth'
);
}
if
(
_val
===
'原始曲线'
)
{
setContrastOption
(
'day'
);
}
setLineDataType
(
_val
)
};
...
...
@@ -647,6 +658,15 @@ const HistoryView = (props) => {
<
QuestionCircleFilled
className
=
{
`
${
prefixCls
}
-question`
}
/
>
<
/Tooltip
>
)}
{
child
.
hasSub
&&
child
.
checked
&&
false
?
<
Select
style
=
{{
width
:
80
,
marginLeft
:
10
}}
value
=
{
algorithmValue
}
onChange
=
{(
e
)
=>
setAlgorithmValue
(
e
)}
>
<
Option
value
=
{
1
}
>
低
<
/Option
>
<
Option
value
=
{
5
}
>
中
<
/Option
>
<
Option
value
=
{
10
}
>
高
<
/Option
>
<
/Select> : '
'
}
<
/
>
)
);
...
...
@@ -670,7 +690,7 @@ const HistoryView = (props) => {
<
Radio
.
Button
value
=
{
'原始曲线'
}
>
原始曲线
<
/Radio.Button
>
<
/Radio.Group
>
{
/*<Segmented value={lineDataType} options={['特征曲线', '原始曲线']} onChange={switchLineDataType}/>*/
}
<
Tooltip
title
=
{
'原始曲线数据量较大,
请查阅小于7天的数据~
'
}
>
<
Tooltip
title
=
{
'原始曲线数据量较大,
单次查询最多展示1万条数据
'
}
>
<
QuestionCircleFilled
style
=
{{
marginLeft
:
6
}}
className
=
{
`
${
prefixCls
}
-question`
}
/
>
<
/Tooltip
>
<
/div
>
...
...
@@ -929,6 +949,8 @@ const HistoryView = (props) => {
acrossTables,
isBoxPlots: isBoxPlots,
};
// if (ignoreOutliers) param.algorithmValue = algorithmValue;
param.algorithmValue = algorithmValue;
let diffYears = moment(item.dateTo).diff(moment(item.dateFrom), 'years');
let diffDays = moment(item.dateTo).diff(moment(item.dateFrom), 'days');
let diffHours = moment(item.dateTo).diff(moment(item.dateFrom), 'hours');
...
...
@@ -994,7 +1016,7 @@ const HistoryView = (props) => {
isBoxPlots: isBoxPlots,
});
});
}, [dateRange, dataConfig, deviceParams, chartType, lineDataType, completeInit]);
}, [dateRange, dataConfig, deviceParams, chartType, lineDataType, completeInit
, algorithmValue
]);
const handleChange = (pagination, filter, sort) => {
if (sort.field === 'time') {
setTimeOrder(sort.order)
...
...
@@ -1035,6 +1057,26 @@ const HistoryView = (props) => {
<
/div
>
<
/
>
},
[
timeOrder
,
chartDataSource
,
columns
,
tableProps
,
tableData
])
const
returnLongestPeriod
=
(
data
)
=>
{
let
_earliest
=
''
let
_latest
=
''
;
data
.
forEach
(
item
=>
{
let
_length
=
item
.
dataModel
.
length
;
let
_tempFirst
=
item
.
dataModel
[
0
].
pt
;
let
_tempLast
=
item
.
dataModel
[
_length
-
1
].
pt
;
if
(
_earliest
)
{
_earliest
=
moment
(
_earliest
)
>
moment
(
_tempFirst
)
?
_tempFirst
:
_earliest
}
else
{
_earliest
=
_tempFirst
}
if
(
_latest
)
{
_latest
=
moment
(
_latest
)
<
moment
(
_tempLast
)
?
_tempLast
:
_latest
}
else
{
_latest
=
_tempLast
;
}
})
return
`
${
_earliest
}
-
${
_latest
}
`
;
};
const
renderPanel
=
(
model
)
=>
{
if
(
model
===
'curve'
)
{
return
(
...
...
@@ -1046,6 +1088,10 @@ const HistoryView = (props) => {
deviceParams
?.
length
===
1
&&
deviceParams
?.[
0
]?.
sensors
?.
split
(
','
).
length
===
1
,
)}
<
/div
>
{
lineDataType
===
'原始曲线'
?
<
div
style
=
{{
marginTop
:
10
}}
>
展示区间:
{
returnLongestPeriod
(
chartDataSource
)}
<
/div> : '
'
}
<
div
className
=
{
`
${
prefixCls
}
-content`
}
>
{
!
chartDataSource
.
length
?
(
<
PandaEmpty
/>
...
...
@@ -1108,9 +1154,37 @@ const HistoryView = (props) => {
useEffect
(()
=>
{
getDefaultOptions
();
},
[])
let
percentTimer
=
useRef
({
timer
:
null
});
useEffect
(()
=>
{
if
(
loading
)
{
let
_percent
=
percent
;
percentTimer
.
current
.
timer
=
setInterval
(()
=>
{
_percent
+=
5
;
if
(
_percent
>
90
)
return
clearInterval
(
percentTimer
.
current
.
timer
);
setPercent
(
_percent
);
},
100
)
}
else
{
clearInterval
(
percentTimer
.
current
.
timer
);
setPercent
(
100
);
setTimeout
(()
=>
{
setPercent
(
0
);
},
500
)
}
},
[
loading
])
return
(
<
div
className
=
{
classNames
(
prefixCls
,
'wkt-scroll-light'
)}
>
<
Spin
spinning
=
{
loading
}
wrapperClassName
=
{
classNames
(
`
${
prefixCls
}
-spin`
)}
>
{
/*<Spin spinning={loading} wrapperClassName={classNames(`${prefixCls}-spin`)}>*/
}
<
div
className
=
{
classNames
(
`
${
prefixCls
}
-spin`
)}
style
=
{{
position
:
"relative"
}}
>
{
loading
||
percent
!==
0
?
<
div
className
=
{
classNames
(
`
${
prefixCls
}
-progressWrapper`
)}
>
<
Progress
percent
=
{
percent
}
steps
=
{
20
}
className
=
{
classNames
(
`
${
prefixCls
}
-progress`
,
`
${
prefixCls
}
-blink-2`
)}
showInfo
=
{
false
}
/
>
<
div
className
=
{
classNames
(
`
${
prefixCls
}
-tip`
)}
>
加载中。。
<
/div
>
<
/div> : '
'
}
{
showModels
.
length
===
1
&&
(
<
div
className
=
{
`
${
prefixCls
}
-single-panel`
}
>
{
renderPanel
(
showModels
[
0
])}
<
/div
>
)}
...
...
@@ -1143,7 +1217,8 @@ const HistoryView = (props) => {
<
/Tabs.TabPane
>
<
/Tabs
>
)}
<
/Spin
>
<
/div
>
{
/*</Spin>*/
}
<
/div
>
);
};
...
...
packages/extend-components/EC_HistoryView/src/index.less
View file @
d920b3f2
...
...
@@ -38,6 +38,34 @@
height: 100%;
}
.@{history-view}-progressWrapper {
z-index: 100;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: rgba(255, 255, 255, 0.9);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.@{history-view}-progress {
display: inline-flex;
}
.@{history-view}-tip {
display: inline-block;
margin-top: 10px;
color: #1890ff;
}
}
.@{history-view}-progress {
}
.@{history-view}-single-panel {
display: flex;
flex-direction: column;
...
...
@@ -205,3 +233,36 @@
align-items: center;
margin-bottom: 0.3rem;
}
.@{history-view}-blink-2 {
-webkit-animation: blink-2 10s infinite both;
animation: blink-2 10s infinite both;
}
/**
* ----------------------------------------
* animation blink-2
* ----------------------------------------
*/
@-webkit-keyframes blink-2 {
0% {
opacity: 1;
}
50% {
opacity: 0.4;
}
100% {
opacity: 1;
}
}
@keyframes blink-2 {
0% {
opacity: 1;
}
50% {
opacity: 0.4;
}
100% {
opacity: 1;
}
}
packages/extend-components/EC_HistoryView/src/utils.js
View file @
d920b3f2
...
...
@@ -546,7 +546,7 @@ const handleYAxis = ({dataSource, needUnit, curveCenter, showGridLine}) => {
const rightNum = Math.floor(yAxisMap.size / 2);
return {leftNum, rightNum, yAxis};
}
const assignOptions = (restOption, xAxis, legendData, chartType) => {
const assignOptions = (restOption, xAxis, legendData, chartType
, contrast, contrastOption
) => {
restOption.dataZoom = [
{
show: true,
...
...
@@ -566,6 +566,14 @@ const assignOptions = (restOption, xAxis, legendData, chartType) => {
height: currentOption['dataZoomHeight'],
type: 'slider',
zoomOnMouseWheel: true,
labelFormatter: function (e) {
let _formatterStr = 'YYYY-MM-DD HH:mm:ss';
if (contrast) {
if (contrastOption === 'day') _formatterStr = 'HH:mm';
if (contrastOption === 'month') _formatterStr = 'MM月DD日 HH时';
}
return moment(e).format(_formatterStr)
}
},
];
xAxis.minInterval = 3600 * (1 * 1000);
...
...
@@ -576,6 +584,12 @@ const assignOptions = (restOption, xAxis, legendData, chartType) => {
const returnMaxOrMinNumber = (dataSource, type) => {
let _obj = null;
if (type === 'period' && dataSource?.[0]?.dataModel?.length) {
let _length = dataSource?.[0]?.dataModel?.length;
let _first = dataSource?.[0]?.dataModel[0]?.pt;
let _last = dataSource?.[0]?.dataModel[_length - 1]?.pt;
return ['展示时段: ', _first, _last, type]
}
dataSource?.[0]?.dataModel.filter(item => item.pv !== null).forEach(item => {
if (!_obj) {
_obj = item;
...
...
@@ -667,6 +681,7 @@ const renderItem = (params, api) => {
const returnCustomSeries = (dataSource) => {
let _maxNumber = returnMaxOrMinNumber(dataSource, 'max');
let _minNumber = returnMaxOrMinNumber(dataSource, 'min');
// let _period = returnMaxOrMinNumber(dataSource, 'period');
// 需要将最大值最小分别传入,后续计算图例位置需要,先min后max
let _max = _maxNumber[1];
let _min = _minNumber[1];
...
...
@@ -813,13 +828,13 @@ const optionGenerator = (dataSource, cusOption, contrast, contrastOption, smooth
}
<
/span
>
<
span
style
=
"font-size: ${handlePx(12, 'px')};"
>
$
{
_unit
??
''
}
<
/span
>
<
/div
>
<
div
style
=
"display: ${lineDataType
==='特征曲线'?'flex':
'none'}; align-items: center;"
>
<
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
?.[
1
]?.
value
?.[
1
]
??
'-'
}
<
/span
>
<
span
style
=
"font-size: ${handlePx(12, 'px')};"
>
$
{
_unit
??
''
}
<
/span
>
<
/div
>
<
div
style
=
"display: ${lineDataType
==='特征曲线'?'flex':
'none'}; align-items: center;"
>
<
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
?.[
2
]?.
dataIndex
]
??
'-'
}
<
/span
>
...
...
@@ -840,7 +855,7 @@ const optionGenerator = (dataSource, cusOption, contrast, contrastOption, smooth
}
tooltip
.
timeFormat
=
tooltipTimeFormat
;
let
_legendData
=
series
.
filter
(
item
=>
!
[
'周期最大值'
,
'周期最小值'
,
'自定义'
].
includes
(
item
.
name
)).
map
(
item
=>
item
.
name
);
assignOptions
(
restOption
,
xAxis
,
_legendData
,
chartType
);
assignOptions
(
restOption
,
xAxis
,
_legendData
,
chartType
,
contrast
,
contrastOption
);
return
{
yAxis
,
grid
,
...
...
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