Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
VueElementTemplate
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
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
黄瑜
VueElementTemplate
Commits
5aa22731
Commit
5aa22731
authored
May 15, 2017
by
Pan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
rewrite axios && mock data
parent
1d05d661
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
137 additions
and
204 deletions
+137
-204
article.js
src/api/article.js
+1
-1
article_table.js
src/api/article_table.js
+1
-1
login.js
src/api/login.js
+1
-2
qiniu.js
src/api/qiniu.js
+25
-25
remoteSearch.js
src/api/remoteSearch.js
+1
-1
article.js
src/mock/article.js
+16
-28
article_table.js
src/mock/article_table.js
+9
-18
index.js
src/mock/index.js
+14
-16
login.js
src/mock/login.js
+12
-29
remoteSearch.js
src/mock/remoteSearch.js
+3
-8
fetch.js
src/utils/fetch.js
+40
-65
index.js
src/utils/index.js
+5
-0
dragTable.vue
src/views/example/dragTable.vue
+3
-2
form1.vue
src/views/example/form1.vue
+2
-2
inlineEditTable.vue
src/views/example/inlineEditTable.vue
+1
-3
table.vue
src/views/example/table.vue
+2
-2
index.vue
src/views/permission/index.vue
+1
-1
No files found.
src/api/article.js
View file @
5aa22731
import
{
fetch
}
from
'utils/fetch'
;
import
fetch
from
'utils/fetch'
;
export
function
getList
()
{
export
function
getList
()
{
return
fetch
({
return
fetch
({
...
...
src/api/article_table.js
View file @
5aa22731
import
{
fetch
}
from
'utils/fetch'
;
import
fetch
from
'utils/fetch'
;
export
function
fetchList
(
query
)
{
export
function
fetchList
(
query
)
{
return
fetch
({
return
fetch
({
...
...
src/api/login.js
View file @
5aa22731
import
{
fetch
}
from
'utils/fetch'
;
import
fetch
from
'utils/fetch'
;
export
function
loginByEmail
(
email
,
password
)
{
export
function
loginByEmail
(
email
,
password
)
{
const
data
=
{
const
data
=
{
...
@@ -19,7 +19,6 @@ export function logout() {
...
@@ -19,7 +19,6 @@ export function logout() {
});
});
}
}
export
function
getInfo
(
token
)
{
export
function
getInfo
(
token
)
{
return
fetch
({
return
fetch
({
url
:
'/user/info'
,
url
:
'/user/info'
,
...
...
src/api/qiniu.js
View file @
5aa22731
import
fetch
,
{
tpFetch
}
from
'utils/fetch'
;
//
import fetch, { tpFetch } from 'utils/fetch';
export
function
getToken
()
{
//
export function getToken() {
return
fetch
({
//
return fetch({
url
:
'/qiniu/upload/token'
,
//
url: '/qiniu/upload/token',
method
:
'get'
//
method: 'get'
});
//
});
}
//
}
export
function
upload
(
data
)
{
//
export function upload(data) {
return
tpFetch
({
//
return tpFetch({
url
:
'https://upload.qbox.me'
,
//
url: 'https://upload.qbox.me',
method
:
'post'
,
//
method: 'post',
data
//
data
});
//
});
}
//
}
/* 外部uri转七牛uri*/
/
/ /
* 外部uri转七牛uri*/
export
function
netUpload
(
token
,
net_url
)
{
//
export function netUpload(token, net_url) {
const
imgData
=
{
//
const imgData = {
net_url
//
net_url
};
//
};
return
fetch
({
//
return fetch({
url
:
'/qiniu/upload/net/async'
,
//
url: '/qiniu/upload/net/async',
method
:
'post'
,
//
method: 'post',
data
:
imgData
//
data: imgData
});
//
});
}
//
}
src/api/remoteSearch.js
View file @
5aa22731
import
{
fetch
}
from
'utils/fetch'
;
import
fetch
from
'utils/fetch'
;
export
function
userSearch
(
name
)
{
export
function
userSearch
(
name
)
{
return
fetch
({
return
fetch
({
...
...
src/mock/article.js
View file @
5aa22731
...
@@ -17,33 +17,21 @@ for (let i = 0; i < count; i++) {
...
@@ -17,33 +17,21 @@ for (let i = 0; i < count; i++) {
}
}
export
default
{
export
default
{
getList
:
()
=>
new
Promise
(
resolve
=>
{
getList
:
()
=>
List
,
setTimeout
(()
=>
{
getArticle
:
()
=>
({
resolve
([
200
,
{
id
:
120000000001
,
data
:
List
author
:
{
key
:
'mockPan'
},
}]);
source_name
:
'原创作者'
,
},
100
);
category_item
:
[{
key
:
'global'
,
name
:
'全球'
}],
}),
comment_disabled
:
false
,
getArticle
:
()
=>
new
Promise
(
resolve
=>
{
content
:
'<p>我是测试数据我是测试数据</p><p><img class="wscnph" src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943" data-wscntype="image" data-wscnh="300" data-wscnw="400" data-mce-src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>"'
,
setTimeout
(()
=>
{
content_short
:
'我是测试数据'
,
resolve
([
200
,
{
display_time
:
+
new
Date
(),
data
:
{
image_uri
:
'https://wpimg.wallstcn.com/e4558086-631c-425c-9430-56ffb46e70b3'
,
id
:
120000000001
,
platforms
:
[
'a-platform'
],
author
:
{
key
:
'mockPan'
},
source_uri
:
'https://github.com/PanJiaChen/vue-element-admin'
,
source_name
:
'原创作者'
,
status
:
'published'
,
category_item
:
[{
key
:
'global'
,
name
:
'全球'
}],
tags
:
[],
comment_disabled
:
false
,
title
:
''
content
:
'<p>我是测试数据我是测试数据</p><p><img class="wscnph" src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943" data-wscntype="image" data-wscnh="300" data-wscnw="400" data-mce-src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>"'
,
content_short
:
'我是测试数据'
,
display_time
:
+
new
Date
(),
image_uri
:
'https://wpimg.wallstcn.com/e4558086-631c-425c-9430-56ffb46e70b3'
,
platforms
:
[
'a-platform'
],
source_uri
:
'https://github.com/PanJiaChen/vue-element-admin'
,
status
:
'published'
,
tags
:
[],
title
:
''
}
}]);
},
100
);
})
})
};
};
src/mock/article_table.js
View file @
5aa22731
import
Mock
from
'mockjs'
;
import
Mock
from
'mockjs'
;
import
{
param2Obj
}
from
'utils'
;
const
List
=
[];
const
List
=
[];
const
count
=
100
;
const
count
=
100
;
...
@@ -21,9 +21,9 @@ for (let i = 0; i < count; i++) {
...
@@ -21,9 +21,9 @@ for (let i = 0; i < count; i++) {
export
default
{
export
default
{
getList
:
config
=>
{
getList
:
config
=>
{
const
{
importance
,
type
,
title
,
page
,
limit
,
sort
}
=
config
.
params
;
const
{
importance
,
type
,
title
,
page
,
limit
,
sort
}
=
param2Obj
(
config
.
url
)
;
let
mockList
=
List
.
filter
(
item
=>
{
let
mockList
=
List
.
filter
(
item
=>
{
if
(
importance
&&
item
.
importance
!==
importance
)
return
false
;
if
(
importance
&&
item
.
importance
!==
+
importance
)
return
false
;
if
(
type
&&
item
.
type
!==
type
)
return
false
;
if
(
type
&&
item
.
type
!==
type
)
return
false
;
if
(
title
&&
item
.
title
.
indexOf
(
title
)
<
0
)
return
false
;
if
(
title
&&
item
.
title
.
indexOf
(
title
)
<
0
)
return
false
;
return
true
;
return
true
;
...
@@ -33,21 +33,12 @@ export default {
...
@@ -33,21 +33,12 @@ export default {
}
}
const
pageList
=
mockList
.
filter
((
item
,
index
)
=>
index
<
limit
*
page
&&
index
>=
limit
*
(
page
-
1
));
const
pageList
=
mockList
.
filter
((
item
,
index
)
=>
index
<
limit
*
page
&&
index
>=
limit
*
(
page
-
1
));
return
{
return
new
Promise
(
resolve
=>
{
total
:
mockList
.
length
,
setTimeout
(()
=>
{
items
:
pageList
resolve
([
200
,
{
}
total
:
mockList
.
length
,
items
:
pageList
}]);
},
100
);
})
},
},
getPv
:
()
=>
new
Promise
(
resolve
=>
{
getPv
:
()
=>
({
setTimeout
(()
=>
{
pvData
:
[{
key
:
'PC网站'
,
pv
:
1024
},
{
key
:
'mobile网站'
,
pv
:
1024
},
{
key
:
'ios'
,
pv
:
1024
},
{
key
:
'android'
,
pv
:
1024
}]
resolve
([
200
,
{
pvData
:
[{
key
:
'PC网站'
,
pv
:
1024
},
{
key
:
'mobile网站'
,
pv
:
1024
},
{
key
:
'ios'
,
pv
:
1024
},
{
key
:
'android'
,
pv
:
1024
}]
}]);
},
100
);
})
})
};
};
src/mock/index.js
View file @
5aa22731
import
axios
from
'axios'
;
import
Mock
from
'mockjs'
;
import
MockAdapter
from
'axios-mock-adapter'
;
import
loginAPI
from
'./login'
;
import
loginAPI
from
'./login'
;
import
articleAPI
from
'./article'
;
import
articleAPI
from
'./article'
;
import
article_tableAPI
from
'./article_table'
;
import
article_tableAPI
from
'./article_table'
;
import
remoteSearchAPI
from
'./remoteSearch'
;
import
remoteSearchAPI
from
'./remoteSearch'
;
const
mock
=
new
MockAdapter
(
axios
);
// 登录相关
// 登录相关
mock
.
onPost
(
'/login/loginbyemail'
).
reply
(
loginAPI
.
loginByEmail
);
Mock
.
mock
(
/
\/
login
\/
loginbyemail/
,
'post'
,
loginAPI
.
loginByEmail
);
mock
.
onPost
(
'/login/logout'
).
reply
(
loginAPI
.
logout
);
Mock
.
mock
(
/
\/
login
\/
logout/
,
'post'
,
loginAPI
.
logout
);
mock
.
onGet
(
'/user/info'
).
reply
(
loginAPI
.
getInfo
);
Mock
.
mock
(
/
\/
user
\/
info
\.
*/
,
'get'
,
loginAPI
.
getInfo
)
// 文章相关
//
//
文章相关
mock
.
onGet
(
'/article/list'
).
reply
(
articleAPI
.
getList
);
Mock
.
mock
(
/
\/
article
\/
list/
,
'get'
,
articleAPI
.
getList
);
mock
.
onGet
(
'/article/detail'
).
reply
(
articleAPI
.
getArticle
);
Mock
.
mock
(
/
\/
article
\/
detail/
,
'get'
,
articleAPI
.
getArticle
);
// table example相关
//
//
table example相关
mock
.
onGet
(
'/article_table/list'
).
reply
(
article_tableAPI
.
getList
);
Mock
.
mock
(
/
\/
article_table
\/
list/
,
'get'
,
article_tableAPI
.
getList
);
mock
.
onGet
(
'/article_table/pv'
).
reply
(
article_tableAPI
.
getPv
);
Mock
.
mock
(
/
\/
article_table
\/
p/
,
'get'
,
article_tableAPI
.
getPv
);
// 搜索相关
//
//
搜索相关
mock
.
onGet
(
'/search/user'
).
reply
(
remoteSearchAPI
.
searchUser
);
Mock
.
mock
(
/
\/
search
\/
user/
,
'get'
,
remoteSearchAPI
.
searchUser
);
mock
.
onAny
().
passThrough
();
export
default
m
ock
;
export
default
M
ock
;
src/mock/login.js
View file @
5aa22731
import
{
param2Obj
}
from
'utils'
;
const
userMap
=
{
const
userMap
=
{
admin
:
{
admin
:
{
role
:
[
'admin'
],
role
:
[
'admin'
],
...
@@ -28,36 +30,17 @@ const userMap = {
...
@@ -28,36 +30,17 @@ const userMap = {
export
default
{
export
default
{
loginByEmail
:
config
=>
{
loginByEmail
:
config
=>
{
const
{
email
}
=
JSON
.
parse
(
config
.
data
);
console
.
log
(
config
)
return
new
Promise
((
resolve
,
reject
)
=>
{
const
{
email
}
=
JSON
.
parse
(
config
.
body
);
if
(
userMap
[
email
.
split
(
'@'
)[
0
]])
{
return
userMap
[
email
.
split
(
'@'
)[
0
]];
setTimeout
(()
=>
{
resolve
([
200
,
{
data
:
userMap
[
email
.
split
(
'@'
)[
0
]]
}]);
},
500
);
}
else
{
reject
(
'账号不正确'
)
}
})
},
},
getInfo
:
config
=>
{
getInfo
:
config
=>
{
const
{
token
}
=
config
.
params
;
const
{
token
}
=
param2Obj
(
config
.
url
);
return
new
Promise
((
resolve
,
reject
)
=>
{
if
(
userMap
[
token
])
{
if
(
userMap
[
token
])
{
return
userMap
[
token
];
setTimeout
(()
=>
{
}
else
{
resolve
([
200
,
{
return
Promise
.
reject
(
'a'
);
data
:
userMap
[
token
]
}
}]);
},
100
);
}
else
{
reject
(
'获取失败'
)
}
})
},
},
logout
:
()
=>
new
Promise
(
resolve
=>
{
logout
:
()
=>
'success'
setTimeout
(()
=>
{
resolve
([
200
,
{
data
:
'success'
}]);
},
100
);
})
};
};
src/mock/remoteSearch.js
View file @
5aa22731
import
Mock
from
'mockjs'
;
import
Mock
from
'mockjs'
;
import
{
param2Obj
}
from
'utils'
;
const
NameList
=
[];
const
NameList
=
[];
const
count
=
100
;
const
count
=
100
;
...
@@ -12,18 +13,12 @@ NameList.push({ name: 'mockPan' })
...
@@ -12,18 +13,12 @@ NameList.push({ name: 'mockPan' })
export
default
{
export
default
{
searchUser
:
config
=>
{
searchUser
:
config
=>
{
const
{
name
}
=
config
.
params
;
const
{
name
}
=
param2Obj
(
config
.
url
)
;
const
mockNameList
=
NameList
.
filter
(
item
=>
{
const
mockNameList
=
NameList
.
filter
(
item
=>
{
const
lowerCaseName
=
item
.
name
.
toLowerCase
()
const
lowerCaseName
=
item
.
name
.
toLowerCase
()
if
(
name
&&
lowerCaseName
.
indexOf
(
name
.
toLowerCase
())
<
0
)
return
false
;
if
(
name
&&
lowerCaseName
.
indexOf
(
name
.
toLowerCase
())
<
0
)
return
false
;
return
true
;
return
true
;
});
});
return
new
Promise
(
resolve
=>
{
return
{
items
:
mockNameList
}
setTimeout
(()
=>
{
resolve
([
200
,
{
items
:
mockNameList
}]);
},
100
);
})
}
}
};
};
src/utils/fetch.js
View file @
5aa22731
...
@@ -3,69 +3,44 @@ import { Message } from 'element-ui';
...
@@ -3,69 +3,44 @@ import { Message } from 'element-ui';
import
store
from
'../store'
;
import
store
from
'../store'
;
import
router
from
'../router'
;
import
router
from
'../router'
;
export
default
function
_fetch
(
options
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
const
instance
=
axios
.
create
({
baseURL
:
process
.
env
.
BASE_API
,
// timeout: 2000,
headers
:
{
'X-Ivanka-Token'
:
store
.
getters
.
token
}
});
instance
(
options
)
.
then
(
response
=>
{
const
res
=
response
.
data
;
if
(
res
.
code
!==
20000
)
{
console
.
log
(
options
);
// for debug
Message
({
message
:
res
.
message
,
type
:
'error'
,
duration
:
5
*
1000
});
// 50014:Token 过期了 50012:其他客户端登录了 50008:非法的token
if
(
res
.
code
===
50008
||
res
.
code
===
50014
||
res
.
code
===
50012
)
{
Message
({
message
:
res
.
message
,
type
:
'error'
,
duration
:
5
*
1000
});
// 登出
store
.
dispatch
(
'FedLogOut'
).
then
(()
=>
{
router
.
push
({
path
:
'/login'
})
});
}
reject
(
res
);
}
resolve
(
res
);
})
.
catch
(
error
=>
{
Message
({
message
:
'发生异常错误,请刷新页面重试,或联系程序员'
,
type
:
'error'
,
duration
:
5
*
1000
});
console
.
log
(
error
);
// for debug
reject
(
error
);
});
});
}
export
function
fetch
(
options
)
{
const
service
=
axios
.
create
({
return
new
Promise
((
resolve
,
reject
)
=>
{
baseURL
:
process
.
env
.
BASE_API
const
instance
=
axios
.
create
({
});
timeout
:
2000
// 超时
});
service
.
interceptors
.
request
.
use
(
config
=>
{
instance
(
options
)
// Do something before request is sent
.
then
(
response
=>
{
if
(
store
.
state
.
token
)
{
const
res
=
response
.
data
;
config
.
headers
.
Token
=
store
.
state
.
token
;
resolve
(
res
);
}
})
return
config
;
.
catch
(
error
=>
{
},
error
=>
{
Message
({
// Do something with request error
message
:
error
,
console
.
log
(
error
);
// for debug
type
:
'error'
,
Promise
.
reject
(
error
);
duration
:
5
*
1000
})
});
console
.
log
(
error
);
// for debug
service
.
interceptors
.
response
.
use
(
reject
(
error
);
response
=>
{
});
console
.
log
(
response
)
});
return
response
;
}
},
error
=>
{
console
.
log
(
'err'
+
error
);
// for debug
const
code
=
error
.
response
.
data
;
if
(
code
===
50008
||
code
===
50014
||
code
===
50012
)
{
Message
({
message
:
res
.
message
,
type
:
'error'
,
duration
:
5
*
1000
});
// 登出
store
.
dispatch
(
'FedLogOut'
).
then
(()
=>
{
router
.
push
({
path
:
'/login'
})
});
}
return
Promise
.
reject
(
error
);
}
)
export
default
service
;
src/utils/index.js
View file @
5aa22731
...
@@ -110,6 +110,11 @@
...
@@ -110,6 +110,11 @@
})).
join
(
'&'
);
})).
join
(
'&'
);
}
}
export
function
param2Obj
(
url
)
{
const
search
=
url
.
split
(
'?'
)[
1
];
return
JSON
.
parse
(
'{"'
+
decodeURIComponent
(
search
).
replace
(
/"/g
,
'
\\
"'
).
replace
(
/&/g
,
'","'
).
replace
(
/=/g
,
'":"'
)
+
'"}'
)
}
export
function
html2Text
(
val
)
{
export
function
html2Text
(
val
)
{
const
div
=
document
.
createElement
(
'div'
);
const
div
=
document
.
createElement
(
'div'
);
div
.
innerHTML
=
val
;
div
.
innerHTML
=
val
;
...
...
src/views/example/dragTable.vue
View file @
5aa22731
...
@@ -96,8 +96,8 @@
...
@@ -96,8 +96,8 @@
getList
()
{
getList
()
{
this
.
listLoading
=
true
;
this
.
listLoading
=
true
;
fetchList
(
this
.
listQuery
).
then
(
response
=>
{
fetchList
(
this
.
listQuery
).
then
(
response
=>
{
this
.
list
=
response
.
items
;
this
.
list
=
response
.
data
.
items
;
this
.
total
=
response
.
total
;
this
.
total
=
response
.
data
.
total
;
this
.
listLoading
=
false
;
this
.
listLoading
=
false
;
this
.
olderList
=
this
.
list
.
map
(
v
=>
v
.
id
);
this
.
olderList
=
this
.
list
.
map
(
v
=>
v
.
id
);
this
.
newList
=
this
.
olderList
.
slice
();
this
.
newList
=
this
.
olderList
.
slice
();
...
@@ -124,6 +124,7 @@
...
@@ -124,6 +124,7 @@
.
drag
-
handler
{
.
drag
-
handler
{
width
:
30
px
;
width
:
30
px
;
height
:
30
px
;
height
:
30
px
;
cursor
:
pointer
;
}
}
.
show
-
d
{
.
show
-
d
{
margin
-
top
:
15
px
;
margin
-
top
:
15
px
;
...
...
src/views/example/form1.vue
View file @
5aa22731
...
@@ -237,9 +237,9 @@
...
@@ -237,9 +237,9 @@
},
},
getRemoteUserList
(
query
)
{
getRemoteUserList
(
query
)
{
userSearch
(
query
).
then
(
response
=>
{
userSearch
(
query
).
then
(
response
=>
{
if
(
!
response
.
items
)
return
;
if
(
!
response
.
data
.
items
)
return
;
console
.
log
(
response
)
console
.
log
(
response
)
this
.
userLIstOptions
=
response
.
items
.
map
(
v
=>
({
this
.
userLIstOptions
=
response
.
data
.
items
.
map
(
v
=>
({
key
:
v
.
name
key
:
v
.
name
}));
}));
})
})
...
...
src/views/example/inlineEditTable.vue
View file @
5aa22731
...
@@ -59,7 +59,6 @@
...
@@ -59,7 +59,6 @@
data
()
{
data
()
{
return
{
return
{
list
:
null
,
list
:
null
,
total
:
null
,
listLoading
:
true
,
listLoading
:
true
,
listQuery
:
{
listQuery
:
{
page
:
1
,
page
:
1
,
...
@@ -84,11 +83,10 @@
...
@@ -84,11 +83,10 @@
getList
()
{
getList
()
{
this
.
listLoading
=
true
;
this
.
listLoading
=
true
;
fetchList
(
this
.
listQuery
).
then
(
response
=>
{
fetchList
(
this
.
listQuery
).
then
(
response
=>
{
this
.
list
=
response
.
items
.
map
(
v
=>
{
this
.
list
=
response
.
data
.
items
.
map
(
v
=>
{
v
.
edit
=
false
;
v
.
edit
=
false
;
return
v
return
v
}
);
}
);
this
.
total
=
response
.
total
;
this
.
listLoading
=
false
;
this
.
listLoading
=
false
;
}
)
}
)
}
}
...
...
src/views/example/table.vue
View file @
5aa22731
...
@@ -227,8 +227,8 @@
...
@@ -227,8 +227,8 @@
getList
()
{
getList
()
{
this
.
listLoading
=
true
;
this
.
listLoading
=
true
;
fetchList
(
this
.
listQuery
).
then
(
response
=>
{
fetchList
(
this
.
listQuery
).
then
(
response
=>
{
this
.
list
=
response
.
items
;
this
.
list
=
response
.
data
.
items
;
this
.
total
=
response
.
total
;
this
.
total
=
response
.
data
.
total
;
this
.
listLoading
=
false
;
this
.
listLoading
=
false
;
}
)
}
)
}
,
}
,
...
...
src/views/permission/index.vue
View file @
5aa22731
...
@@ -24,7 +24,7 @@
...
@@ -24,7 +24,7 @@
watch
:
{
watch
:
{
role
(
val
)
{
role
(
val
)
{
this
.
$store
.
commit
(
'SET_ROLES'
,
[
val
]);
this
.
$store
.
commit
(
'SET_ROLES'
,
[
val
]);
window
.
location
.
reload
()
this
.
$router
.
push
({
path
:
'/permission/index?'
+
+
new
Date
()
});
}
}
}
}
}
}
...
...
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