Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
client
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
725a01c4
authored
Aug 11, 2025
by
Jony.L
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/develop' into trade
parents
ed107576
7b102035
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
276 additions
and
105 deletions
+276
-105
.env.development
+1
-0
src/api/home.js
+16
-10
src/api/login.js
+3
-4
src/layout/components/Sidebar-custom/index.vue
+1
-1
src/layout/user-layout.vue
+3
-3
src/permission.js
+33
-48
src/store/modules/user.js
+6
-7
src/views/componentServices/list.vue
+37
-17
src/views/index.vue
+176
-15
No files found.
.env.development
View file @
725a01c4
...
...
@@ -6,3 +6,4 @@ VITE_APP_ENV = 'development'
# 先进计算普惠算力公共服务平台/开发环境
VITE_APP_BASE_API = 'http://localhost:48080/app-api/'
#VITE_APP_BASE_API = 'http://localhost:48080/'
src/api/home.js
View file @
725a01c4
import
request
from
'@/utils/request'
// 获取首页banner
export
function
banner
(
query
)
{
//获取首页banner
// export function banner (query) {
// return request({
// url: '/biz/banner-info/get',
// method: 'get',
// params: query
// })
// }
export
function
banner
()
{
return
request
({
url
:
'/api/v1/banner'
,
method
:
'get'
,
params
:
query
url
:
'/biz/banner-info/bannerList'
,
method
:
'get'
})
}
// 获取组件服务列表数据
export
function
assemblyList
(
query
)
{
return
request
({
url
:
'/
api/v1
/assemblyList'
,
url
:
'/
biz/assembly
/assemblyList'
,
method
:
'get'
,
params
:
query
})
...
...
@@ -29,7 +36,7 @@ export function assemblyType (query) {
// 获取活动资讯列表数据
export
function
informationList
(
query
)
{
return
request
({
url
:
'/
api/v1/information
'
,
url
:
'/
biz/information/informationList
'
,
method
:
'get'
,
params
:
query
})
...
...
@@ -57,8 +64,7 @@ export function partnerList (query) {
// 获取计算资源相关
export
function
informationResourceList
(
query
){
return
request
({
url
:
'/api/v1/computility'
,
method
:
'get'
,
params
:
query
url
:
'/biz/computility-information/computilityList'
,
method
:
'get'
})
}
src/api/login.js
View file @
725a01c4
import
request
from
'@/utils/request'
// 登录方法
export
function
login
(
mobile
,
password
,
code
,
uuid
)
{
export
function
login
(
mobile
,
password
,
code
)
{
const
data
=
{
mobile
,
password
,
code
,
uuid
code
}
return
request
({
url
:
'/member/auth/login'
,
...
...
@@ -34,7 +33,7 @@ export function register(data) {
// 获取用户详细信息
export
function
getInfo
()
{
return
request
({
url
:
'/
getInfo
'
,
url
:
'/
member/user/get
'
,
method
:
'get'
})
}
...
...
src/layout/components/Sidebar-custom/index.vue
View file @
725a01c4
...
...
@@ -42,7 +42,7 @@ const appStore = useAppStore()
const
settingsStore
=
useSettingsStore
()
const
permissionStore
=
usePermissionStore
()
const
sidebarRouters
=
computed
(()
=>
{
return
permissionStore
.
sidebarRouter
s
.
filter
(
item
=>
item
.
path
===
'/console'
)
// 只展示控制台的菜单项
return
constantRoute
s
.
filter
(
item
=>
item
.
path
===
'/console'
)
// 只展示控制台的菜单项
})
const
showLogo
=
computed
(()
=>
settingsStore
.
sidebarLogo
)
const
sideTheme
=
computed
(()
=>
settingsStore
.
sideTheme
)
...
...
src/layout/user-layout.vue
View file @
725a01c4
...
...
@@ -45,11 +45,11 @@
</div>
<div
class=
"right-menu flex-align-center"
>
<router-link
v-if=
"userStore.
id
"
to=
"/console/overview"
class=
"console"
style=
"font-size: 18px;"
>
控制台
<router-link
v-if=
"userStore.
token
"
to=
"/console/overview"
class=
"console"
style=
"font-size: 18px;"
>
控制台
</router-link>
<el-dropdown
v-if=
"userStore.
id
"
@
command=
"handleCommand"
class=
""
>
<div
v-if=
"userStore.
id
"
class=
"avatar-wrapper flex-align-center"
>
<el-dropdown
v-if=
"userStore.
token
"
@
command=
"handleCommand"
class=
""
>
<div
v-if=
"userStore.
token
"
class=
"avatar-wrapper flex-align-center"
>
<img
:src=
"userStore.avatar"
class=
"user-avatar"
/>
<div
class=
"user-name"
>
{{ userStore.name }}
</div>
</div>
...
...
src/permission.js
View file @
725a01c4
...
...
@@ -3,13 +3,10 @@ import { ElMessage } from 'element-plus'
import
NProgress
from
'nprogress'
import
'nprogress/nprogress.css'
import
{
getToken
}
from
'@/utils/auth'
import
{
isHttp
}
from
'@/utils/validate'
import
{
isRelogin
}
from
'@/utils/request'
import
useUserStore
from
'@/store/modules/user'
import
useSettingsStore
from
'@/store/modules/settings'
import
use
PermissionStore
from
'@/store/modules/permission'
import
use
UserStore
from
"@/store/modules/user.js"
;
NProgress
.
configure
({
showSpinner
:
false
})
NProgress
.
configure
({
showSpinner
:
false
})
const
whiteList
=
[
'/login'
,
...
...
@@ -25,50 +22,38 @@ const whiteList = [
router
.
beforeEach
((
to
,
from
,
next
)
=>
{
NProgress
.
start
()
if
(
getToken
())
{
to
.
meta
.
title
&&
useSettingsStore
().
setTitle
(
to
.
meta
.
title
)
/* has token*/
if
(
to
.
path
===
'/login'
)
{
next
({
path
:
'/'
})
NProgress
.
done
()
}
else
if
(
whiteList
.
indexOf
(
to
.
path
)
!==
-
1
)
{
useUserStore
().
getInfo
().
then
(
res
=>
{
})
next
()
}
else
{
if
(
useUserStore
().
roles
.
length
===
0
)
{
isRelogin
.
show
=
true
// 判断当前用户是否已拉取完user_info信息
useUserStore
().
getInfo
().
then
(()
=>
{
isRelogin
.
show
=
false
usePermissionStore
().
generateRoutes
().
then
(
accessRoutes
=>
{
// 根据roles权限生成可访问的路由表
accessRoutes
.
forEach
(
route
=>
{
if
(
!
isHttp
(
route
.
path
))
{
router
.
addRoute
(
route
)
// 动态添加可访问路由表
}
})
next
({...
to
,
replace
:
true
})
// hack方法 确保addRoutes已完成
})
}).
catch
(
err
=>
{
useUserStore
().
logOut
().
then
(()
=>
{
ElMessage
.
error
(
err
)
next
({
path
:
'/'
})
})
})
}
else
{
next
()
}
}
// 设置页面标题
to
.
meta
.
title
&&
useSettingsStore
().
setTitle
(
to
.
meta
.
title
)
// 1. 判断是否在白名单中
if
(
whiteList
.
includes
(
to
.
path
))
{
// 白名单路由直接放行,不需要检查token
next
()
NProgress
.
done
()
return
}
// 2. 非白名单路由,检查是否登录
const
hasToken
=
getToken
()
if
(
!
hasToken
)
{
// 未登录访问非白名单路由,重定向到登录页
next
(
`/login?redirect=
${
to
.
fullPath
}
`
)
NProgress
.
done
()
return
}
else
{
useUserStore
().
getInfo
().
then
(
res
=>
{
})
}
// 3. 已登录访问非白名单路由,直接放行(不做权限检查)
if
(
to
.
path
===
'/login'
)
{
// 已登录但访问登录页,重定向到首页
next
({
path
:
'/'
})
NProgress
.
done
()
}
else
{
// 没有token
if
(
whiteList
.
indexOf
(
to
.
path
)
!==
-
1
)
{
// 在免登录白名单,直接进入
next
()
}
else
{
next
(
`/login?redirect=
${
to
.
fullPath
}
`
)
// 否则全部重定向到登录页
NProgress
.
done
()
}
next
()
NProgress
.
done
()
}
})
...
...
src/store/modules/user.js
View file @
725a01c4
...
...
@@ -19,13 +19,12 @@ const useUserStore = defineStore(
const
mobile
=
userInfo
.
mobile
.
trim
()
const
password
=
userInfo
.
password
const
code
=
userInfo
.
code
const
uuid
=
userInfo
.
uuid
return
new
Promise
((
resolve
,
reject
)
=>
{
login
(
mobile
,
password
,
code
,
uuid
).
then
(
res
=>
{
login
(
mobile
,
password
,
code
).
then
(
res
=>
{
setToken
(
res
.
data
.
accessToken
)
this
.
token
=
res
.
data
.
t
oken
this
.
token
=
res
.
data
.
accessT
oken
resolve
()
}).
catch
(
error
=>
{
reject
(
error
)
...
...
@@ -36,16 +35,16 @@ const useUserStore = defineStore(
getInfo
()
{
return
new
Promise
((
resolve
,
reject
)
=>
{
getInfo
().
then
(
res
=>
{
const
user
=
res
.
user
const
user
=
res
.
data
const
avatar
=
(
user
.
avatar
==
""
||
user
.
avatar
==
null
)
?
defAva
:
import
.
meta
.
env
.
VITE_APP_BASE_API
+
user
.
avatar
;
if
(
res
.
roles
&&
res
.
roles
.
length
>
0
)
{
// 验证返回的roles是否是一个非空数组
/*
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
this.roles = res.roles
this.permissions = res.permissions
} else {
this.roles = ['ROLE_DEFAULT']
}
this
.
id
=
user
.
userI
d
}
*/
this
.
id
=
user
.
i
d
this
.
name
=
user
.
mobile
this
.
avatar
=
avatar
resolve
(
res
)
...
...
src/views/componentServices/list.vue
View file @
725a01c4
<
template
>
<div
class=
"app-container"
>
<div
style=
"margin: 0 auto;padding: 0 84px;'"
>
<el-tabs
v-model=
"tabActive"
@
tab-change=
"tabChange"
>
<el-tab-pane
v-for=
"i in assemblyTypes"
:key=
"i.value"
:label=
"i.name"
:name=
"i.value"
>
</el-tab-pane>
</el-tabs>
<!-- 内容过滤器-->
<!--
<el-tabs
v-model=
"tabActive"
@
tab-change=
"tabChange"
>
-->
<!--
<el-tab-pane-->
<!-- v-for="i in assemblyTypes"-->
<!-- :key="i.value"-->
<!-- :label="i.name"-->
<!-- :name="i.value">-->
<!--
</el-tab-pane>
-->
<!--
</el-tabs>
-->
<el-row
:gutter=
"24"
>
<el-col
v-for=
"(item,index) in assemblyData"
:key=
"item.id"
:span=
"6"
>
<div
class=
"item flex"
>
<img
:src=
"
baseUrl +
item.image"
alt=
""
>
<img
:src=
"item.image"
alt=
""
>
<div>
<div
class=
"title"
>
{{
item
.
title
}}
</div>
<p>
{{
item
.
description
}}
</p>
...
...
@@ -65,21 +66,40 @@ const tabActive = ref("0")
onMounted
(()
=>
{
getAssemblyType
()
// getAssemblyType()
getAassemblyList
()
})
function
getAassemblyList
()
{
assemblyList
({
type
:
Number
(
tabActive
.
value
)}).
then
(
res
=>
{
assemblyData
.
value
=
res
.
data
// assemblyData.value = res.data
const
validData
=
res
.
data
.
filter
(
item
=>
item
.
showStatus
===
true
);
validData
.
forEach
(
item
=>
{
// 处理description字段,去除
<
p
>
标签
if
(
item
.
description
)
{
item
.
description
=
item
.
description
.
replace
(
/<p>/g
,
''
).
replace
(
/<
\/
p>/g
,
''
)
}
// 处理images数组,只取第一张图片
if
(
Array
.
isArray
(
item
.
images
)
&&
item
.
images
.
length
>
0
)
{
item
.
image
=
item
.
images
[
0
];
}
else
{
item
.
image
=
''
;
// 如果没有图片,设置为空字符串
}
});
assemblyData
.
value
=
validData
.
sort
(
function
(
a
,
b
)
{
return
a
.
orderNum
-
b
.
orderNum
});
})
}
function
getAssemblyType
()
{
assemblyType
().
then
(
res
=>
{
assemblyTypes
.
value
=
res
.
data
getAassemblyList
()
})
}
// function getAssemblyType() {
// assemblyType().then(res => {
// assemblyTypes.value = res.data
// getAassemblyList()
// })
// }
function
tabChange
()
{
getAassemblyList
()
...
...
src/views/index.vue
View file @
725a01c4
...
...
@@ -3,10 +3,13 @@
<div
class=
"banner"
>
<el-carousel
height=
"600px"
:indicator-position=
"bannerImgList.length
<
=
1
?
'
none
'
:
''
"
>
<el-carousel-item
v-for=
"item in bannerImgList"
:key=
"item"
@
click=
"handleCarousel(item.url)"
>
<img
:src=
"baseUrl + item.image"
alt=
""
>
<!--
<img
:src=
"baseUrl + item.image"
alt=
""
>
-->
<img
:src=
"item.image"
alt=
""
>
</el-carousel-item>
</el-carousel>
</div>
<div
class=
"new-model"
>
<div
style=
"padding-top: 32px;"
>
<div
class=
"model-box"
v-for=
"(i,index) in list"
:key=
"i"
>
...
...
@@ -32,7 +35,7 @@
@
click=
"i.id === 1? $router.push('/computingResource/resourceList?type=' + x.category):openAssembly(x)"
>
<!-- style="background: url("@/assets/images/indexImg/resource1.png") no-repeat center center / 100%;"-->
<div
class=
"resource-box"
:style=
"
{background: `url(${index === 0 ?
baseUrl+x.image:baseUrl+
x.homeImage}) no-repeat center center / 100%`}">
<div
class=
"resource-box"
:style=
"
{background: `url(${index === 0 ?
x.image:
x.homeImage}) no-repeat center center / 100%`}">
<div
class=
"title"
>
{{
x
.
title
}}
</div>
<div
class=
"introduce"
>
{{
index
===
0
?
x
.
information
:
x
.
description
}}
</div>
</div>
...
...
@@ -71,7 +74,7 @@
<div
class=
"content"
v-html=
"informationMainData.remark"
></div>
</div>
</div>
<img
:src=
"
baseUrl +
informationMainData.image"
alt=
""
>
<img
:src=
"informationMainData.image"
alt=
""
>
</div>
</div>
</el-col>
...
...
@@ -187,9 +190,39 @@ const dialogTitle = ref('')
const
iframeSrc
=
ref
(
''
)
const
iframeShow
=
ref
(
false
)
// function getBanner() {
// banner().then(res => {
// // 筛选条件:showStatus为1且image字段存在且不为空
// const validData = res.data.filter(item =>
// item.showStatus === 1 &&
// item.image &&
// item.image.trim() !== ''
// );
//
// // 对筛选后的数据进行排序
// bannerImgList.value = validData.sort(function (a, b) {
// return a.orderNum - b.orderNum
// })
// })
// }
function
getBanner
()
{
banner
().
then
(
res
=>
{
bannerImgList
.
value
=
res
.
data
.
sort
(
function
(
a
,
b
)
{
// 筛选条件:showStatus为1的数据
const
validData
=
res
.
data
.
filter
(
item
=>
item
.
showStatus
===
1
);
// 处理图片字段,只取第一张图片
validData
.
forEach
(
item
=>
{
// 注意后台字段名为images(复数),需要取第一张图片
if
(
Array
.
isArray
(
item
.
images
)
&&
item
.
images
.
length
>
0
)
{
item
.
image
=
item
.
images
[
0
];
}
else
{
item
.
image
=
''
;
// 如果没有图片,设置为空字符串
}
});
// 对筛选后的数据进行排序
bannerImgList
.
value
=
validData
.
sort
(
function
(
a
,
b
)
{
return
a
.
orderNum
-
b
.
orderNum
})
})
...
...
@@ -197,42 +230,170 @@ function getBanner() {
getBanner
()
function
getAassemblyList
()
{
// function getAssemblyList() {
// assemblyList({type: 0}).then(res => {
// // 筛选条件:showStatus为1且description字段存在
// const filteredData = res.data.filter(item =>
// item.showStatus === true &&
// item.description
// );
//
// const data = filteredData.sort(function (a, b) {
// return a.orderNum - b.orderNum
// }).map(item => {
// // 处理description字段,去除
<
p
>
标签
// if (item.description) {
// item.description = item.description.replace(/
<
p
>
/g, ''
)
.replace
(
/
<
\
/
p
>
/g, ''
)
// }
// return item
// })
// assemblyData.value = data.length > 3 ? data.slice(0, 3) : data
// list.value[1].assemblyData = data.length > 3 ? data.slice(0, 3) : data
// })
// }
function
getAssemblyList
()
{
assemblyList
({
type
:
0
}).
then
(
res
=>
{
const
data
=
res
.
data
.
sort
(
function
(
a
,
b
)
{
// 筛选条件:showStatus为true的数据
const
filteredData
=
res
.
data
.
filter
(
item
=>
item
.
showStatus
===
true
);
const
data
=
filteredData
.
sort
(
function
(
a
,
b
)
{
return
a
.
orderNum
-
b
.
orderNum
}).
map
(
item
=>
{
// 处理description字段,去除
<
p
>
标签
if
(
item
.
description
)
{
item
.
description
=
item
.
description
.
replace
(
/<p>/g
,
''
).
replace
(
/<
\/
p>/g
,
''
)
}
// 处理images数组,只取第一张图片
if
(
Array
.
isArray
(
item
.
images
)
&&
item
.
images
.
length
>
0
)
{
item
.
image
=
item
.
images
[
0
];
}
else
{
item
.
image
=
''
;
// 如果没有图片,设置为空字符串
}
// 处理homeImages数组,只取第一张图片
if
(
Array
.
isArray
(
item
.
homeImages
)
&&
item
.
homeImages
.
length
>
0
)
{
item
.
homeImage
=
item
.
homeImages
[
0
];
}
else
{
item
.
homeImage
=
''
;
// 如果没有图片,设置为空字符串
}
return
item
})
assemblyData
.
value
=
data
.
length
>
3
?
data
.
slice
(
0
,
3
)
:
data
list
.
value
[
1
].
assemblyData
=
data
.
length
>
3
?
data
.
slice
(
0
,
3
)
:
data
})
}
// 计算自选
// function getResource() {
// informationResourceList().then(res => {
// // 筛选条件:showStatus为true的数据
// const filteredData = res.data.filter(item => item.showStatus === true);
//
// // 可选:同时处理information字段,去除
<
p
>
标签
// const processedData = filteredData.map(item => {
// if (item.information) {
// item.information = item.information.replace(/
<
p
>
/g, ''
)
.replace
(
/
<
\
/
p
>
/g, ''
)
;
// }
// return item;
// });
//
// list.value[0].assemblyData = processedData.length > 3 ? processedData.slice(0, 3) : processedData;
// console.log(list.value[0].assemblyData, 'list.value[0].assemblyData');
// });
// }
function
getResource
()
{
informationResourceList
().
then
(
res
=>
{
list
.
value
[
0
].
assemblyData
=
res
.
data
.
length
>
3
?
res
.
data
.
slice
(
0
,
3
)
:
res
.
data
console
.
log
(
list
.
value
[
0
].
assemblyData
,
'list.value[0].assemblyData'
)
})
// 筛选条件:showStatus为true的数据
const
filteredData
=
res
.
data
.
filter
(
item
=>
item
.
showStatus
===
true
);
// 处理information字段,去除
<
p
>
标签,并处理图片数组
const
processedData
=
filteredData
.
map
(
item
=>
{
if
(
item
.
information
)
{
item
.
information
=
item
.
information
.
replace
(
/<p>/g
,
''
).
replace
(
/<
\/
p>/g
,
''
);
}
// 处理images数组,只取第一张图片
if
(
Array
.
isArray
(
item
.
images
)
&&
item
.
images
.
length
>
0
)
{
item
.
image
=
item
.
images
[
0
];
}
else
{
item
.
image
=
''
;
// 如果没有图片,设置为空字符串
}
return
item
;
});
list
.
value
[
0
].
assemblyData
=
processedData
.
length
>
3
?
processedData
.
slice
(
0
,
3
)
:
processedData
;
console
.
log
(
list
.
value
[
0
].
assemblyData
,
'list.value[0].assemblyData'
);
});
}
getA
a
ssemblyList
()
getAssemblyList
()
getResource
()
const
informationData
=
ref
([])
const
informationMainData
=
ref
({})
// function getInformation() {
// informationList().then(res => {
// // 筛选条件:showStatus为true的数据
// const filteredData = res.data.filter(item => item.showStatus === true);
//
// // 按创建时间降序排序(最新的在前)
// const sortedData = filteredData.sort((a, b) => {
// return new Date(b.createTime) - new Date(a.createTime);
// });
//
// sortedData.forEach(item => {
// item.year = new Date(item.createTime).getFullYear()
// item.month = new Date(item.createTime).getMonth() + 1
// item.month = item.month
<
10
?
'0'
+
item
.
month
:
item
.
month
// item.day = new Date(item.createTime).getDate()
// item.day = item.day
<
10
?
'0'
+
item
.
day
:
item
.
day
// // 使用正则表达式去除所有
<
p
>
和
<
/p>标
签
// if (item.description) {
// item.description = item.description.replace(/
<
p
>
/g, ''
)
.replace
(
/
<
\
/
p
>
/g, ''
)
// }
// })
// informationMainData.value = sortedData.length ? sortedData[0] : {}
// const arr = sortedData.length ? sortedData.slice(1, sortedData.length) : []
// informationData.value = arr.length > 3 ? arr.slice(0, 3) : arr
// })
// }
function
getInformation
()
{
informationList
().
then
(
res
=>
{
res
.
data
.
forEach
(
item
=>
{
// 筛选条件:showStatus为true的数据
const
filteredData
=
res
.
data
.
filter
(
item
=>
item
.
showStatus
===
true
);
// 按创建时间降序排序(最新的在前)
const
sortedData
=
filteredData
.
sort
((
a
,
b
)
=>
{
return
new
Date
(
b
.
createTime
)
-
new
Date
(
a
.
createTime
);
});
sortedData
.
forEach
(
item
=>
{
item
.
year
=
new
Date
(
item
.
createTime
).
getFullYear
()
item
.
month
=
new
Date
(
item
.
createTime
).
getMonth
()
+
1
item
.
month
=
item
.
month
<
10
?
'0'
+
item
.
month
:
item
.
month
item
.
day
=
new
Date
(
item
.
createTime
).
getDate
()
item
.
day
=
item
.
day
<
10
?
'0'
+
item
.
day
:
item
.
day
item
.
description
=
item
.
description
.
replace
(
'
<
p
><
br
><
/p>', ''
)
// 使用正则表达式去除所有
<
p
>
和
<
/p>标
签
if
(
item
.
description
)
{
item
.
description
=
item
.
description
.
replace
(
/<p>/g
,
''
).
replace
(
/<
\/
p>/g
,
''
)
}
// 处理images数组,只取第一张图片
if
(
Array
.
isArray
(
item
.
images
)
&&
item
.
images
.
length
>
0
)
{
item
.
image
=
item
.
images
[
0
];
}
else
{
item
.
image
=
''
;
// 如果没有图片,设置为空字符串
}
})
informationMainData
.
value
=
res
.
data
.
length
?
res
.
d
ata
[
0
]
:
{}
const
arr
=
res
.
data
.
length
?
res
.
data
.
slice
(
1
,
res
.
d
ata
.
length
)
:
[]
informationMainData
.
value
=
sortedData
.
length
?
sortedD
ata
[
0
]
:
{}
const
arr
=
sortedData
.
length
?
sortedData
.
slice
(
1
,
sortedD
ata
.
length
)
:
[]
informationData
.
value
=
arr
.
length
>
3
?
arr
.
slice
(
0
,
3
)
:
arr
})
}
...
...
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