Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
穆春红
/
frontend-hub
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
719b00ea
authored
Jun 23, 2026
by
chunhong.mu
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
feat(qqqf-mp): 实现微信小程序登录
parent
20c75a3a
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
314 additions
and
37 deletions
packages/qqqf-mp/src/App.vue
packages/qqqf-mp/src/api/login-service.ts
packages/qqqf-mp/src/config/index.ts
packages/qqqf-mp/src/pages/login/index.vue
packages/qqqf-mp/src/pages/login/login.vue
packages/qqqf-mp/src/App.vue
View file @
719b00ea
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
ClientId
,
PassportTokenController
,
UICore
,
uniAdapter
}
from
"@common/sdk"
import
{
onLaunch
}
from
"@dcloudio/uni-app"
import
{
onLaunch
}
from
"@dcloudio/uni-app"
import
{
sdk
}
from
"@/api/http"
onLaunch
(()
=>
{
onLaunch
(()
=>
{
console
.
log
(
"App Launch"
)
console
.
log
(
"App Launch"
)
sdkInit
()
})
})
function
sdkInit
()
{
UICore
.
setupSdk
(
sdk
.
core
,
{
client
:
ClientId
.
Hrs100WechatMiniApp
,
config
:
uniAdapter
,
})
const
token
=
PassportTokenController
.
hasToken
()
if
(
token
)
{
sdk
.
core
.
loginByToken
({
token
:
token
as
string
,
})
}
}
</
script
>
</
script
>
<
style
lang=
"scss"
>
<
style
lang=
"scss"
>
...
...
packages/qqqf-mp/src/api/login-service.ts
0 → 100644
View file @
719b00ea
/**
* 登录服务
* 提供微信一键登录、短信验证码登录、密码登录等接口调用
*/
import
{
sdk
}
from
"@/api/http"
import
{
config
}
from
"@/config"
export
interface
LoginResult
{
jwt
:
string
user_id
?:
string
mobile
?:
string
}
export
interface
RegisterChannel
{
app
?:
string
scene
?:
string
channel
?:
string
app_store
?:
string
[
key
:
string
]:
any
}
/**
* 获取微信登录 code
*/
export
function
getWxLoginCode
():
Promise
<
string
>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
uni
.
login
({
provider
:
"weixin"
,
success
:
res
=>
resolve
(
res
.
code
),
fail
:
err
=>
reject
(
err
),
})
})
}
/**
* 微信小程序绑定手机号登录
*/
export
function
miniProgramBindPhone
(
appid
:
string
,
code
:
string
,
clientId
:
string
,
encryptedData
:
string
,
iv
:
string
,
registerChannel
:
RegisterChannel
=
{},
):
Promise
<
LoginResult
>
{
return
sdk
.
domainServicePost
<
any
,
any
,
LoginResult
>
(
"bind_phone"
,
{
data
:
{
appid
,
code
,
client_id
:
clientId
,
encryptedData
,
iv
,
...
registerChannel
,
},
},
true
,
// 匿名接口
"uniplat_base"
,
"system.wechat"
,
)
}
/**
* 发送短信验证码
*/
export
function
sendSmsCode
(
mobile
:
string
,
imgCode
:
string
,
):
Promise
<
void
>
{
return
sdk
.
domainServicePost
<
any
,
any
,
void
>
(
"send_verifycode"
,
{
data
:
{
mobile
,
img_code
:
imgCode
,
client_id
:
config
.
clientId
,
},
},
true
,
"uniplat_base"
,
"anonymous/system.user"
,
)
}
/**
* 短信验证码登录
*/
export
function
smsLogin
(
mobile
:
string
,
verifycode
:
string
,
registerChannel
:
RegisterChannel
=
{},
):
Promise
<
LoginResult
>
{
return
sdk
.
domainServicePost
<
any
,
any
,
LoginResult
>
(
"login_with_mobile"
,
{
data
:
{
mobile
,
verifycode
,
client_id
:
config
.
clientId
,
...
registerChannel
,
},
},
true
,
"uniplat_base"
,
"anonymous/system.user"
,
)
}
/**
* 密码登录
*/
export
function
passwordLogin
(
username
:
string
,
password
:
string
,
registerChannel
:
RegisterChannel
=
{},
):
Promise
<
LoginResult
>
{
return
sdk
.
domainServicePost
<
any
,
any
,
LoginResult
>
(
"login_with_password"
,
{
data
:
{
username
,
password
,
client_id
:
config
.
clientId
,
...
registerChannel
,
},
},
true
,
"uniplat_base"
,
"anonymous/system.user"
,
)
}
/**
* 获取图形验证码
*/
export
function
getImageCode
():
Promise
<
string
>
{
return
sdk
.
domainServiceGet
<
any
,
{
img_code
:
string
}
>
(
"get_image_code"
,
{},
true
,
"uniplat_base"
,
"system.user"
,
).
then
(
res
=>
res
.
img_code
)
}
packages/qqqf-mp/src/config/index.ts
View file @
719b00ea
...
@@ -13,10 +13,18 @@ export interface MpConfig extends SdkConfig {
...
@@ -13,10 +13,18 @@ export interface MpConfig extends SdkConfig {
appName
?:
string
appName
?:
string
appSourceId
?:
string
appSourceId
?:
string
officialAccountName
?:
string
officialAccountName
?:
string
payUrl
?:
string
passportUrl
?:
string
passportApi
?:
string
passportOrg
?:
string
userCenter
?:
string
uniplatApi
:
string
uniplatApi
:
string
qqxbH5Url
?:
string
xbUrl
?:
string
h5Url
?:
string
h5Url
?:
string
collectionUrl
?:
string
collectionUrl
?:
string
selfH5
?:
string
selfH5
?:
string
selfJobH5
?:
string
imgBaseUrl
:
string
imgBaseUrl
:
string
clientId
:
ClientId
clientId
:
ClientId
uniplatSocketUrl
:
string
uniplatSocketUrl
:
string
...
@@ -48,6 +56,8 @@ export interface MpConfig extends SdkConfig {
...
@@ -48,6 +56,8 @@ export interface MpConfig extends SdkConfig {
get_lbs
?:
string
get_lbs
?:
string
}
}
cityCodeLength
:
number
|
6
cityCodeLength
:
number
|
6
reviewVersion
?:
number
registerConfig
?:
any
fastCity
:
any
fastCity
:
any
testAccount
?:
string
testAccount
?:
string
testPassport
?:
string
testPassport
?:
string
...
@@ -55,33 +65,69 @@ export interface MpConfig extends SdkConfig {
...
@@ -55,33 +65,69 @@ export interface MpConfig extends SdkConfig {
path
:
string
path
:
string
id
:
string
id
:
string
}[]
}[]
registerConfig
?:
any
hideshareBy
?:
boolean
hideshareBy
?:
boolean
qqxbH5Url
?:
string
applicationKey
?:
any
applicationKey
?:
any
[
key
:
string
]:
any
[
key
:
string
]:
any
}
}
export
const
config
:
MpConfig
=
{
export
const
config
:
MpConfig
=
{
appName
:
"亲亲企服"
,
appName
:
"亲亲企服"
,
uniplatApi
:
import
.
meta
.
env
.
VITE_API_BASE_URL
||
"https://api.example.com"
,
payUrl
:
rootEntrance
:
"qqqf-mp"
,
import
.
meta
.
env
.
VITE_APP_PAY_URL
||
"https://payment-api.qqqf.com"
,
imgBaseUrl
:
`
${
import
.
meta
.
env
.
VITE_APP_LAND_PAGE
||
""
}
/img/`
,
passportUrl
:
clientId
:
import
.
meta
.
env
.
VITE_APP_CLIENT_ID
||
"qqqf-mp-client"
,
import
.
meta
.
env
.
VITE_APP_WWW_WORK_APP_URL
||
uniplatSocketUrl
:
import
.
meta
.
env
.
VITE_APP_UNIPLAT_WEBSOCKET_URI
||
""
,
"https://passport.qqqf.com"
,
clientSecret
:
import
.
meta
.
env
.
VITE_APP_CLIENT_SECRET
||
""
,
passportApi
:
logEnv
:
import
.
meta
.
env
.
VITE_ENV
||
"development"
,
import
.
meta
.
env
.
VITE_APP_API_WORK_APP_URL
||
version
:
packageConfig
.
version
,
"https://userapi.qqqf.com"
,
passportOrg
:
import
.
meta
.
env
.
VITE_APP_API_WORK_ORG_URL
||
"https://userapi.qqqf.com"
,
userCenter
:
import
.
meta
.
env
.
VITE_APP_USER_CENTER
||
"http://tmxlogin.qqqf.com"
,
uniplatApi
:
import
.
meta
.
env
.
VITE_APP_UNIPLAT
||
"https://api-hro.qqqf.com"
,
qqxbH5Url
:
import
.
meta
.
env
.
VITE_APP_QQXB
||
"http://test-qqxb-h5.qqqf.com"
,
rootEntrance
:
"统一业务平台"
,
domainService
:
{
domainService
:
{
subProjectName
:
"
qqqf
"
,
subProjectName
:
"
welfare_v2
"
,
serviceName
:
"api"
,
serviceName
:
"
smart_app_
api"
,
},
},
xbUrl
:
import
.
meta
.
env
.
VITE_APP_XB_URL
||
"https://qqxb-h5.qqqf.com"
,
h5Url
:
import
.
meta
.
env
.
VITE_APP_H5_URL
||
"https://static.qqqf.com/flb-mp"
,
imgBaseUrl
:
`
${
import
.
meta
.
env
.
VITE_APP_LAND_PAGE
||
"https://static.qqqf.com/flb-mp"
}
/img/`
,
clientId
:
import
.
meta
.
env
.
VITE_APP_CLIENT_ID
||
"hrs100_wechat_mini_app"
,
uniplatSocketUrl
:
import
.
meta
.
env
.
VITE_APP_UNIPLAT_WEBSOCKET_URI
||
"wss://channel.qqqf.com/ws"
,
clientSecret
:
import
.
meta
.
env
.
VITE_APP_CLIENT_SECRET
||
"qqxb#teammix#2019"
,
logEnv
:
+
(
import
.
meta
.
env
.
VITE_APP_LOG_ENV
||
"1"
)
as
Environment
,
version
:
packageConfig
.
version
,
mpAccountAppid
:
"wx67ad4269eb98b531"
,
mpAppid
:
"wx71116f4df50d7f93"
,
officialAccountMpH5
:
"https://mp.weixin.qq.com/s?__biz=Mzg3NjcyMDkwNw==&mid=2247483658&idx=1&sn=11c01272402f190d4ddc07e5f66c21e5"
,
testAccount
:
import
.
meta
.
env
.
VITE_APP_TEST_ACCOUNT
||
""
,
testPassport
:
import
.
meta
.
env
.
VITE_APP_TEST_PASSPORT
||
""
,
mapKey
:
"CQLBZ-RHTCO-3VKWA-S5NTZ-V4ALV-6OBRZ"
,
shareConfig
:
{
shareConfig
:
{
title
:
"亲亲企服"
,
title
:
"亲亲企服"
,
path
:
"/pages/home/index"
,
img
:
`
${
import
.
meta
.
env
.
VITE_APP_LAND_PAGE
||
"https://static.qqqf.com/flb-mp"
}
/img/qqqf-share.png`
,
path
:
"/pages/login/index"
,
},
},
selfJobH5
:
import
.
meta
.
env
.
VITE_APP_SELF_JOB_URL
||
""
,
selfH5
:
""
,
cityCodeLength
:
6
,
cityCodeLength
:
6
,
fastCity
:
{},
reviewVersion
:
126
,
registerConfig
:
{
app
:
"Reg_MiniPro"
,
scene
:
"wxapp_scene_Myqqxb"
,
app_store
:
"qq_hehuoren_minreg"
,
}
as
any
,
}
}
export
const
noNeedAuthPages
=
[
export
const
noNeedAuthPages
=
[
...
...
packages/qqqf-mp/src/pages/login/index.vue
View file @
719b00ea
...
@@ -43,8 +43,10 @@
...
@@ -43,8 +43,10 @@
</
template
>
</
template
>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
PassportTokenController
,
wechatLoginService
}
from
"@common/sdk"
import
{
onLoad
}
from
"@dcloudio/uni-app"
import
{
onLoad
}
from
"@dcloudio/uni-app"
import
{
ref
}
from
"vue"
import
{
ref
}
from
"vue"
import
{
config
}
from
"@/config"
const
agree
=
ref
(
false
)
const
agree
=
ref
(
false
)
...
@@ -65,7 +67,7 @@ function showAgreement(type: "service" | "privacy") {
...
@@ -65,7 +67,7 @@ function showAgreement(type: "service" | "privacy") {
})
})
}
}
function
doWxLogin
(
e
:
any
)
{
async
function
doWxLogin
(
e
:
any
)
{
if
(
!
agree
.
value
)
{
if
(
!
agree
.
value
)
{
uni
.
showToast
({
title
:
"需同意《服务协议》《隐私政策》"
,
icon
:
"none"
})
uni
.
showToast
({
title
:
"需同意《服务协议》《隐私政策》"
,
icon
:
"none"
})
return
return
...
@@ -84,15 +86,34 @@ function doWxLogin(e: any) {
...
@@ -84,15 +86,34 @@ function doWxLogin(e: any) {
}
}
uni
.
showLoading
({
title
:
"登录中..."
,
mask
:
true
})
uni
.
showLoading
({
title
:
"登录中..."
,
mask
:
true
})
// TODO: 调用微信登录接口
try
{
setTimeout
(()
=>
{
// 获取微信登录 code
const
code
=
await
new
Promise
<
string
>
((
resolve
,
reject
)
=>
{
uni
.
login
({
provider
:
"weixin"
,
success
:
res
=>
resolve
(
res
.
code
),
fail
:
err
=>
reject
(
err
),
})
})
// 调用绑定手机号登录接口
const
result
=
await
wechatLoginService
.
miniProgramBindPhone
(
config
.
mpAppid
||
""
,
code
,
e
.
detail
.
encryptedData
,
e
.
detail
.
iv
,
)
// 保存 token
PassportTokenController
.
saveToken2Storage
(
result
.
jwt
)
uni
.
hideLoading
()
uni
.
hideLoading
()
uni
.
showToast
({
title
:
"登录成功"
,
icon
:
"success"
})
uni
.
showToast
({
title
:
"登录成功"
,
icon
:
"success"
})
setTimeout
(()
=>
{
setTimeout
(()
=>
{
const
redirect
=
uni
.
getStorageSync
(
"loginRedirect"
)
||
"/pages/home/index"
const
redirect
=
uni
.
getStorageSync
(
"loginRedirect"
)
||
"/pages/home/index"
uni
.
reLaunch
({
url
:
redirect
})
uni
.
reLaunch
({
url
:
redirect
})
},
1500
)
},
1500
)
},
1500
)
}
catch
(
err
:
any
)
{
uni
.
hideLoading
()
uni
.
showToast
({
title
:
err
?.
msg
||
err
?.
message
||
"登录失败,请重试"
,
icon
:
"none"
})
}
}
}
function
back
()
{
function
back
()
{
...
...
packages/qqqf-mp/src/pages/login/login.vue
View file @
719b00ea
...
@@ -38,9 +38,13 @@
...
@@ -38,9 +38,13 @@
placeholder-style=
"color: #bdbdc7;"
placeholder-style=
"color: #bdbdc7;"
type=
"text"
type=
"text"
/>
/>
<view
class=
"code-placeholder"
@
click=
"getVerifyCodeImg"
>
<image
图形验证码
v-if=
"imageCodeModel"
</view>
:src=
"imageCodeModel.img"
mode=
"aspectFit"
class=
"code-img"
@
click=
"getVerifyCodeImg"
/>
</view>
</view>
<view
class=
"form-item form-item-row"
>
<view
class=
"form-item form-item-row"
>
<input
<input
...
@@ -136,6 +140,8 @@
...
@@ -136,6 +140,8 @@
</
template
>
</
template
>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
type
{
VerifyImageResult
}
from
"@common/sdk"
import
{
loginService
,
PassportTokenController
,
verifyService
}
from
"@common/sdk"
import
{
onLoad
}
from
"@dcloudio/uni-app"
import
{
onLoad
}
from
"@dcloudio/uni-app"
import
{
computed
,
reactive
,
ref
}
from
"vue"
import
{
computed
,
reactive
,
ref
}
from
"vue"
...
@@ -167,9 +173,10 @@ const codeCounting = ref(false)
...
@@ -167,9 +173,10 @@ const codeCounting = ref(false)
const
codeCountdown
=
ref
(
60
)
const
codeCountdown
=
ref
(
60
)
let
codeTimer
:
ReturnType
<
typeof
setInterval
>
|
null
=
null
let
codeTimer
:
ReturnType
<
typeof
setInterval
>
|
null
=
null
const
imageCodeModel
=
ref
<
VerifyImageResult
|
null
>
(
null
)
function
getVerifyCodeImg
()
{
function
getVerifyCodeImg
()
{
// TODO: 调用图形验证码接口
imageCodeModel
.
value
=
verifyService
.
generateImage
()
uni
.
showToast
({
title
:
"图形验证码功能待实现"
,
icon
:
"none"
})
}
}
function
startCodeCountdown
()
{
function
startCodeCountdown
()
{
...
@@ -199,7 +206,7 @@ function isPhone(val: string) {
...
@@ -199,7 +206,7 @@ function isPhone(val: string) {
return
/^1
[
3-9
]\d{9}
$/
.
test
(
val
)
return
/^1
[
3-9
]\d{9}
$/
.
test
(
val
)
}
}
function
getCode
()
{
async
function
getCode
()
{
if
(
!
smsModel
.
mobile
)
{
if
(
!
smsModel
.
mobile
)
{
uni
.
showToast
({
title
:
"请输入手机号"
,
icon
:
"none"
})
uni
.
showToast
({
title
:
"请输入手机号"
,
icon
:
"none"
})
return
return
...
@@ -217,15 +224,22 @@ function getCode() {
...
@@ -217,15 +224,22 @@ function getCode() {
return
return
}
}
uni
.
showLoading
({
title
:
"正在获取验证码"
,
mask
:
true
})
uni
.
showLoading
({
title
:
"正在获取验证码"
,
mask
:
true
})
// TODO: 调用发送验证码接口
try
{
setTimeout
(()
=>
{
await
verifyService
.
sendVerifyCode
(
smsModel
.
mobile
,
imageCodeModel
.
value
?.
seed
||
""
,
smsModel
.
imgCode
,
)
uni
.
hideLoading
()
uni
.
hideLoading
()
uni
.
showToast
({
title
:
"验证码已发送"
,
icon
:
"success"
})
uni
.
showToast
({
title
:
"验证码已发送"
,
icon
:
"success"
})
startCodeCountdown
()
startCodeCountdown
()
},
1000
)
}
catch
(
err
:
any
)
{
uni
.
hideLoading
()
uni
.
showToast
({
title
:
err
?.
msg
||
err
?.
message
||
"发送失败"
,
icon
:
"none"
})
}
}
}
function
smsLogin
()
{
async
function
smsLogin
()
{
if
(
!
smsAgree
.
value
)
{
if
(
!
smsAgree
.
value
)
{
uni
.
showToast
({
title
:
"需同意《服务协议》《隐私政策》"
,
icon
:
"none"
})
uni
.
showToast
({
title
:
"需同意《服务协议》《隐私政策》"
,
icon
:
"none"
})
return
return
...
@@ -240,18 +254,22 @@ function smsLogin() {
...
@@ -240,18 +254,22 @@ function smsLogin() {
return
uni
.
showToast
({
title
:
"请输入验证码"
,
icon
:
"none"
})
return
uni
.
showToast
({
title
:
"请输入验证码"
,
icon
:
"none"
})
}
}
smsLoading
.
value
=
true
smsLoading
.
value
=
true
// TODO: 调用验证码登录接口
try
{
setTimeout
(()
=>
{
const
result
=
await
loginService
.
verifyCodeLogin
(
smsModel
.
mobile
,
smsModel
.
code
)
smsLoading
.
value
=
false
PassportTokenController
.
saveToken2Storage
(
result
.
jwt
)
uni
.
showToast
({
title
:
"登录成功"
,
icon
:
"success"
})
uni
.
showToast
({
title
:
"登录成功"
,
icon
:
"success"
})
setTimeout
(()
=>
{
setTimeout
(()
=>
{
const
redirect
=
uni
.
getStorageSync
(
"loginRedirect"
)
||
"/pages/home/index"
const
redirect
=
uni
.
getStorageSync
(
"loginRedirect"
)
||
"/pages/home/index"
uni
.
reLaunch
({
url
:
redirect
})
uni
.
reLaunch
({
url
:
redirect
})
},
1500
)
},
1500
)
},
1500
)
}
catch
(
err
:
any
)
{
uni
.
showToast
({
title
:
err
?.
msg
||
err
?.
message
||
"登录失败"
,
icon
:
"none"
})
}
finally
{
smsLoading
.
value
=
false
}
}
}
function
passwordLogin
()
{
async
function
passwordLogin
()
{
if
(
!
pwdAgree
.
value
)
{
if
(
!
pwdAgree
.
value
)
{
uni
.
showToast
({
title
:
"需同意《服务协议》《隐私政策》"
,
icon
:
"none"
})
uni
.
showToast
({
title
:
"需同意《服务协议》《隐私政策》"
,
icon
:
"none"
})
return
return
...
@@ -263,15 +281,19 @@ function passwordLogin() {
...
@@ -263,15 +281,19 @@ function passwordLogin() {
return
uni
.
showToast
({
title
:
"请输入密码"
,
icon
:
"none"
})
return
uni
.
showToast
({
title
:
"请输入密码"
,
icon
:
"none"
})
}
}
pwdLoading
.
value
=
true
pwdLoading
.
value
=
true
// TODO: 调用密码登录接口
try
{
setTimeout
(()
=>
{
const
result
=
await
loginService
.
login
(
passwordModel
.
username
,
passwordModel
.
password
)
pwdLoading
.
value
=
false
PassportTokenController
.
saveToken2Storage
(
result
.
jwt
)
uni
.
showToast
({
title
:
"登录成功"
,
icon
:
"success"
})
uni
.
showToast
({
title
:
"登录成功"
,
icon
:
"success"
})
setTimeout
(()
=>
{
setTimeout
(()
=>
{
const
redirect
=
uni
.
getStorageSync
(
"loginRedirect"
)
||
"/pages/home/index"
const
redirect
=
uni
.
getStorageSync
(
"loginRedirect"
)
||
"/pages/home/index"
uni
.
reLaunch
({
url
:
redirect
})
uni
.
reLaunch
({
url
:
redirect
})
},
1500
)
},
1500
)
},
1500
)
}
catch
(
err
:
any
)
{
uni
.
showToast
({
title
:
err
?.
msg
||
err
?.
message
||
"登录失败"
,
icon
:
"none"
})
}
finally
{
pwdLoading
.
value
=
false
}
}
}
function
goForget
()
{
function
goForget
()
{
...
@@ -296,6 +318,7 @@ function back() {
...
@@ -296,6 +318,7 @@ function back() {
onLoad
(()
=>
{
onLoad
(()
=>
{
uni
.
setNavigationBarTitle
({
title
:
"验证码登录"
})
uni
.
setNavigationBarTitle
({
title
:
"验证码登录"
})
getVerifyCodeImg
()
})
})
</
script
>
</
script
>
...
@@ -366,6 +389,13 @@ onLoad(() => {
...
@@ -366,6 +389,13 @@ onLoad(() => {
color
:
#93939e
;
color
:
#93939e
;
}
}
.code-img
{
width
:
160
rpx
;
height
:
60
rpx
;
margin-left
:
20
rpx
;
flex-shrink
:
0
;
}
.code-text
{
.code-text
{
font-size
:
28
rpx
;
font-size
:
28
rpx
;
white-space
:
nowrap
;
white-space
:
nowrap
;
...
...
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