14 changed files with 1103 additions and 81 deletions
|
After Width: | Height: | Size: 357 KiB |
|
After Width: | Height: | Size: 9.3 MiB |
@ -0,0 +1,296 @@ |
|||
<template> |
|||
<div class="index_loginPage__K_bGK login-wrap"> |
|||
<div class="index_banner__1d8CU"> |
|||
<div class="index_rootContainer"> |
|||
<b class="title">扫码登录</b> |
|||
<p class="sub_title mt32">打开 <b>微信</b> 扫码登录</p> |
|||
<div class="wxlogin-qrcode-box mb32" v-if="status == 'waiting'"> |
|||
<Wxlogin |
|||
v-if="appid && redirect_uri" |
|||
:appid="appid" |
|||
:redirect_uri="redirect_uri" |
|||
scope="snsapi_login" |
|||
:state="state" |
|||
height="186px" |
|||
href="data:text/css;base64,LmltcG93ZXJCb3ggLnFyY29kZSB7d2lkdGg6IDE4OHB4O2JvcmRlcjpub25lO21hcmdpbi10b3A6MHB4O30KLmltcG93ZXJCb3ggLnRpdGxlIHtkaXNwbGF5OiBub25lO30KLmltcG93ZXJCb3ggLmluZm8ge3dpZHRoOiAxODhweDtkaXNwbGF5Om5vbmU7fQouc3RhdHVzX2ljb24ge2Rpc3BsYXk6IG5vbmV9Ci5pbXBvd2VyQm94IC5zdGF0dXMge3RleHQtYWxpZ246IGNlbnRlcjt9Ci53ZWJfcXJjb2RlX3R5cGVfaWZyYW1le2hlaWdodDoxODZweDt9Ci53ZWJfcXJjb2RlX3N3aXRjaF93cnAuanNfc3dpdGNoVG9GYXN0X3dycHttYXJnaW4tdG9wOjBweCAhaW1wb3J0YW50O2NvbG9yOiAjOEE5MDk5ICFpbXBvcnRhbnQ7fQoud2ViX3FyY29kZV9zd2l0Y2hfd3JwLmpzX3N3aXRjaFRvRmFzdF93cnAgYnV0dG9ue2NvbG9yOiAjOEE5MDk5ICFpbXBvcnRhbnQ7Y3Vyc29yOnBvaW50ZXI7fQoud2ViX3FyY29kZV9zd2l0Y2hfd3JwLmpzX3N3aXRjaFRvTm9ybWFsX3dycCBidXR0b257Y29sb3I6ICM4QTkwOTkgIWltcG9ydGFudDtjdXJzb3I6cG9pbnRlcjt9" |
|||
></Wxlogin> |
|||
</div> |
|||
<div class="refreshCode" v-if="status == 'expired'" @click="refreshCode"> |
|||
<i class="el-icon-refresh"></i> |
|||
刷新二维码 |
|||
</div> |
|||
|
|||
<div class="attention gap10"> |
|||
<el-checkbox v-model="checked"></el-checkbox> |
|||
<b>登录即代表同意 <a @click="jumpDoc">《用户协议》</a>和 <a @click="jumpDoc">《隐私条款》</a></b> |
|||
</div> |
|||
<p class="tip">若无账号,请先联系客服注册</p> |
|||
</div> |
|||
</div> |
|||
<div class="style_footerWrapper"> |
|||
<div class="left flex"> |
|||
<img src="@/assets/doctor_h5.svg" alt=""> |
|||
<div> |
|||
<b>手机端</b> |
|||
<p>微信扫码体验</p> |
|||
</div> |
|||
</div> |
|||
<div class="center"> |
|||
<p><b>入驻指南</b>个人入驻 组织(医院等)入驻</p> |
|||
<p><b>人工咨询</b>扫左侧码,进入手机端-我的-页面底部“联系我们”</p> |
|||
</div> |
|||
<div class="right"> |
|||
<p><b>网站备案</b>© 2012-2025 something , <a href="">Inc. All rights 测试</a></p> |
|||
<p></p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<script> |
|||
import Wxlogin from 'vue-wxlogin'; |
|||
export default { |
|||
components: { |
|||
Wxlogin |
|||
}, |
|||
data() { |
|||
return { |
|||
checked: true, |
|||
qrCodeUrl: 'https://example.com', |
|||
token: '', |
|||
status: 'expired', |
|||
loginTime:'', |
|||
appid:false, |
|||
redirectUri:false, |
|||
state:'zjadmin' |
|||
} |
|||
}, |
|||
created(){ |
|||
this.getLoginConfig() |
|||
}, |
|||
methods: { |
|||
async getLoginConfig() { |
|||
this.status = 'waiting'; |
|||
this.redirect_uri = ''; |
|||
this.appid = ''; |
|||
try { |
|||
await this.$http('POST', '/api/callback/get_admin_login_config').then(response => { |
|||
this.appid = response.data.appid |
|||
this.redirect_uri = response.data.redirect_uri |
|||
}).catch(error => { |
|||
console.error('生成二维码失败:', error); |
|||
this.status = 'error'; |
|||
}); |
|||
} catch (error) { |
|||
console.error('生成二维码失败:', error); |
|||
this.status = 'error'; |
|||
} |
|||
}, |
|||
handleLoginSuccess(data) { |
|||
this.status = 'confirmed'; |
|||
this.userInfo = data.user_info; |
|||
this.loginTime = new Date().toLocaleString(); |
|||
|
|||
// 保存登录状态 |
|||
localStorage.setItem('authToken', data.auth_token); |
|||
localStorage.setItem('nick', JSON.stringify(data.nick)); |
|||
localStorage.setItem('userInfo', JSON.stringify(data.user_info)); |
|||
|
|||
setTimeout(() => { |
|||
this.isLoggedIn = true; |
|||
this.$emit('login-success', data); |
|||
}, 1000); |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
<style lang="scss" scoped> |
|||
.index_banner__1d8CU { |
|||
align-items: center; |
|||
background-image: url(../assets/login_backImg.svg); |
|||
background-size: cover; |
|||
display: flex; |
|||
flex: 1 1; |
|||
justify-content: flex-end; |
|||
-webkit-transition: padding-right .5s; |
|||
transition: padding-right .5s; |
|||
width: 100%; |
|||
} |
|||
|
|||
.index_rootContainer { |
|||
background: #fff; |
|||
border-radius: 16px; |
|||
box-shadow: 0 6px 40px 0 rgba(53, 76, 166, .08); |
|||
display: inline-block; |
|||
flex-shrink: 0; |
|||
height: 480px; |
|||
overflow: hidden; |
|||
width: 432px; |
|||
margin-right: 192px; |
|||
padding: 40px; |
|||
box-sizing: border-box; |
|||
display: flex; |
|||
flex-direction: column; |
|||
|
|||
.title { |
|||
font-family: Microsoft YaHei; |
|||
font-size: 22px; |
|||
font-weight: bold; |
|||
line-height: normal; |
|||
text-align: center; |
|||
letter-spacing: 0.08em; |
|||
color: #1E2226; |
|||
} |
|||
|
|||
.sub_title { |
|||
font-family: Microsoft YaHei; |
|||
font-size: 12px; |
|||
font-weight: normal; |
|||
line-height: normal; |
|||
letter-spacing: 0.08em; |
|||
color: #626573; |
|||
margin-top: 32px; |
|||
margin-bottom: 12px; |
|||
b{ |
|||
font-weight: bold; |
|||
color: #1E2226; |
|||
} |
|||
} |
|||
|
|||
.img { |
|||
width: 192px; |
|||
height: 192px; |
|||
background: #006AFF; |
|||
margin: 0 auto; |
|||
margin-bottom: 32px; |
|||
} |
|||
} |
|||
|
|||
.attention, |
|||
.tip { |
|||
font-size: 12px; |
|||
font-weight: normal; |
|||
line-height: 13px; |
|||
letter-spacing: 0.08em; |
|||
color: #626573; |
|||
text-align: center; |
|||
display: flex; |
|||
justify-content: center; |
|||
|
|||
b { |
|||
font-size: 12px; |
|||
font-weight: normal; |
|||
|
|||
a { |
|||
text-decoration: none; |
|||
color: #006AFF; |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.attention { |
|||
margin-bottom: 12px; |
|||
} |
|||
|
|||
.refreshCode { |
|||
width: 172px; |
|||
height: 172px; |
|||
background: rgba(255, 255, 255, 0.3); |
|||
backdrop-filter: blur(10px); |
|||
-webkit-backdrop-filter: blur(10px); |
|||
border-radius: 12px; |
|||
border: 1px solid rgba(255, 255, 255, 0.2); |
|||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 10px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
color: #626573; |
|||
font-family: Arial, sans-serif; |
|||
font-weight: bold; |
|||
cursor: pointer; |
|||
margin: 20px auto; |
|||
i{ |
|||
font-size: 28px; |
|||
} |
|||
} |
|||
|
|||
|
|||
@media(max-width: 1420px) { |
|||
.index_rootContainer { |
|||
margin-right: 102px; |
|||
} |
|||
} |
|||
|
|||
.style_footerWrapper { |
|||
background: #f3f4f6; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 46px 49px; |
|||
width: 100%; |
|||
height: 192px; |
|||
box-sizing: border-box; |
|||
gap: 72px; |
|||
|
|||
b { |
|||
font-size: 12px; |
|||
font-weight: bold; |
|||
line-height: 13px; |
|||
letter-spacing: 0.08em; |
|||
color: #1E2226; |
|||
} |
|||
|
|||
.left { |
|||
gap: 12px; |
|||
|
|||
div { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 8px; |
|||
text-align: left; |
|||
} |
|||
|
|||
img { |
|||
width: 97px; |
|||
height: 100px; |
|||
} |
|||
} |
|||
|
|||
.center, |
|||
.right { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 24px; |
|||
text-align: left; |
|||
|
|||
p { |
|||
display: flex; |
|||
gap: 8px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
.style_iconWrapper__3qi7l { |
|||
flex-basis: 80px; |
|||
} |
|||
|
|||
.index_loginPage__K_bGK { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100vh; |
|||
min-width: 1200px; |
|||
} |
|||
|
|||
#ecomLoginForm { |
|||
height: 280px; |
|||
} |
|||
|
|||
.wxlogin-qrcode-box{ |
|||
div:first-of-type{ |
|||
height: 214px; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,382 @@ |
|||
<template> |
|||
<div class="index_loginPage__K_bGK login-wrap"> |
|||
<div class="index_banner__1d8CU"> |
|||
<div class="index_rootContainer"> |
|||
<b class="title">扫码登录</b> |
|||
<p class="sub_title mt32">打开 <b>微信</b> 扫码登录</p> |
|||
<div class=" mb32" v-if="status == 'waiting'"> |
|||
<vue-qr :text="qrCodeUrl" :size="192" :dot-scale="1"></vue-qr> |
|||
</div> |
|||
<div class="refreshCode" v-if="status == 'expired'" @click="refreshCode"> |
|||
<i class="el-icon-refresh"></i> |
|||
刷新二维码 |
|||
</div> |
|||
<!-- :logo-src="logoUrl" |
|||
:logo-scale="0.2" --> |
|||
<div class="attention gap10"> |
|||
<el-checkbox v-model="checked"></el-checkbox> |
|||
<b>登录即代表同意 <a @click="jumpDoc">《用户协议》</a>和 <a @click="jumpDoc">《隐私条款》</a></b> |
|||
</div> |
|||
<p class="tip">若无账号,请先联系客服注册</p> |
|||
</div> |
|||
</div> |
|||
<div class="style_footerWrapper"> |
|||
<div class="left flex"> |
|||
<img src="@/assets/doctor_h5.svg" alt=""> |
|||
<div> |
|||
<b>手机端</b> |
|||
<p>微信扫码体验</p> |
|||
</div> |
|||
</div> |
|||
<div class="center"> |
|||
<p><b>入驻指南</b>个人入驻 组织(医院等)入驻</p> |
|||
<p><b>人工咨询</b>扫左侧码,进入手机端-我的-页面底部“联系我们”</p> |
|||
</div> |
|||
<div class="right"> |
|||
<p><b>网站备案</b>© 2012-2025 something , <a href="">Inc. All rights 测试</a></p> |
|||
<p></p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<script> |
|||
import VueQr from 'vue-qr' |
|||
export default { |
|||
components: { |
|||
VueQr |
|||
}, |
|||
data() { |
|||
return { |
|||
checked: true, |
|||
qrCodeUrl: 'https://example.com', |
|||
token: '', |
|||
status: 'expired', // waiting, scanned, confirmed, expired, error |
|||
loginTime:'', |
|||
} |
|||
}, |
|||
created(){ |
|||
// this.generateQRCode() |
|||
}, |
|||
beforeDestroy() { |
|||
this.clearTimers(); |
|||
}, |
|||
methods: { |
|||
jumpDoc() { |
|||
|
|||
}, |
|||
// 刷新二维码 |
|||
refreshCode() { |
|||
|
|||
}, |
|||
async generateQRCode() { |
|||
this.status = 'waiting'; |
|||
this.qrCodeUrl = ''; |
|||
|
|||
try { |
|||
await this.$http('POST', '/api/admin/generate_qrcode', { |
|||
device_type: 'web', |
|||
timestamp: Date.now() |
|||
}).then(response => { |
|||
this.token = response.data.token; |
|||
this.qrCodeUrl = response.data.qr_code_url; |
|||
|
|||
// 开始轮询检查状态 |
|||
this.startPolling(); |
|||
|
|||
// 设置二维码过期时间(5分钟) |
|||
this.setExpireTimer(); |
|||
}).catch(error => { |
|||
console.error('生成二维码失败:', error); |
|||
this.status = 'error'; |
|||
}); |
|||
} catch (error) { |
|||
console.error('生成二维码失败:', error); |
|||
this.status = 'error'; |
|||
} |
|||
}, |
|||
|
|||
// 开始轮询检查扫码状态 |
|||
startPolling() { |
|||
this.clearTimers(); |
|||
|
|||
this.pollInterval = setInterval(async () => { |
|||
if (this.status === 'confirmed' || this.status === 'expired' || this.status === 'error') { |
|||
this.clearTimers(); |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
await this.$http('POST', '/api/admin/check_scan_status', { |
|||
token: this.token |
|||
}).then(response => { |
|||
this.status = response.data.status; |
|||
|
|||
if (response.data.status === 'scanned') { |
|||
// 扫码后等待确认 |
|||
setTimeout(() => { |
|||
this.handleLoginConfirm(); |
|||
}, 2000); |
|||
} else if (response.data.status === 'confirmed') { |
|||
this.handleLoginSuccess(response.data); |
|||
} else if (response.data.status === 'expired') { |
|||
this.status = 'expired'; |
|||
this.clearTimers(); |
|||
} |
|||
}).catch(error => { |
|||
console.error('检查扫码状态失败:', error); |
|||
this.status = 'error'; |
|||
this.clearTimers(); |
|||
}); |
|||
} catch (error) { |
|||
console.error('检查扫码状态失败:', error); |
|||
this.status = 'error'; |
|||
this.clearTimers(); |
|||
} |
|||
}, 2000); |
|||
}, |
|||
|
|||
// 处理登录确认 |
|||
async handleLoginConfirm() { |
|||
try { |
|||
await this.$http('POST', '/api/admin/confirm_login', { |
|||
token: this.token |
|||
}).then(response => { |
|||
if (response.data.success) { |
|||
this.handleLoginSuccess(response.data); |
|||
} else { |
|||
this.status = 'expired'; |
|||
} |
|||
}).catch(error => { |
|||
console.error('登录确认失败:', error); |
|||
this.status = 'error'; |
|||
}); |
|||
} catch (error) { |
|||
console.error('登录确认失败:', error); |
|||
this.status = 'error'; |
|||
} |
|||
}, |
|||
|
|||
// 处理登录成功 |
|||
handleLoginSuccess(data) { |
|||
this.status = 'confirmed'; |
|||
this.userInfo = data.user_info; |
|||
this.loginTime = new Date().toLocaleString(); |
|||
|
|||
// 保存登录状态 |
|||
localStorage.setItem('authToken', data.auth_token); |
|||
localStorage.setItem('nick', JSON.stringify(data.nick)); |
|||
localStorage.setItem('userInfo', JSON.stringify(data.user_info)); |
|||
|
|||
setTimeout(() => { |
|||
this.isLoggedIn = true; |
|||
this.$emit('login-success', data); |
|||
}, 1000); |
|||
}, |
|||
|
|||
// 设置过期定时器 |
|||
setExpireTimer() { |
|||
this.expireTimer = setTimeout(() => { |
|||
if (this.status !== 'confirmed') { |
|||
this.status = 'expired'; |
|||
this.clearTimers(); |
|||
} |
|||
}, 5 * 60 * 1000); // 5分钟过期 |
|||
}, |
|||
|
|||
// 清除所有定时器 |
|||
clearTimers() { |
|||
if (this.pollInterval) { |
|||
clearInterval(this.pollInterval); |
|||
this.pollInterval = null; |
|||
} |
|||
if (this.expireTimer) { |
|||
clearTimeout(this.expireTimer); |
|||
this.expireTimer = null; |
|||
} |
|||
}, |
|||
|
|||
|
|||
} |
|||
} |
|||
</script> |
|||
<style lang="scss" scoped> |
|||
.index_banner__1d8CU { |
|||
align-items: center; |
|||
background-image: url(../assets/login_backImg.svg); |
|||
background-size: cover; |
|||
display: flex; |
|||
flex: 1 1; |
|||
justify-content: flex-end; |
|||
-webkit-transition: padding-right .5s; |
|||
transition: padding-right .5s; |
|||
width: 100%; |
|||
} |
|||
|
|||
.index_rootContainer { |
|||
background: #fff; |
|||
border-radius: 16px; |
|||
box-shadow: 0 6px 40px 0 rgba(53, 76, 166, .08); |
|||
display: inline-block; |
|||
flex-shrink: 0; |
|||
height: 480px; |
|||
overflow: hidden; |
|||
width: 432px; |
|||
margin-right: 192px; |
|||
padding: 40px; |
|||
box-sizing: border-box; |
|||
display: flex; |
|||
flex-direction: column; |
|||
|
|||
.title { |
|||
font-family: Microsoft YaHei; |
|||
font-size: 22px; |
|||
font-weight: bold; |
|||
line-height: normal; |
|||
text-align: center; |
|||
letter-spacing: 0.08em; |
|||
color: #1E2226; |
|||
} |
|||
|
|||
.sub_title { |
|||
font-family: Microsoft YaHei; |
|||
font-size: 12px; |
|||
font-weight: normal; |
|||
line-height: normal; |
|||
letter-spacing: 0.08em; |
|||
color: #626573; |
|||
margin-top: 32px; |
|||
margin-bottom: 12px; |
|||
} |
|||
|
|||
.img { |
|||
width: 192px; |
|||
height: 192px; |
|||
background: #006AFF; |
|||
margin: 0 auto; |
|||
margin-bottom: 32px; |
|||
} |
|||
} |
|||
|
|||
.attention, |
|||
.tip { |
|||
font-size: 12px; |
|||
font-weight: normal; |
|||
line-height: 13px; |
|||
letter-spacing: 0.08em; |
|||
color: #626573; |
|||
text-align: center; |
|||
display: flex; |
|||
justify-content: center; |
|||
|
|||
b { |
|||
font-size: 12px; |
|||
font-weight: normal; |
|||
|
|||
a { |
|||
text-decoration: none; |
|||
color: #006AFF; |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.attention { |
|||
margin-bottom: 12px; |
|||
} |
|||
|
|||
.refreshCode { |
|||
width: 172px; |
|||
height: 172px; |
|||
background: rgba(255, 255, 255, 0.3); |
|||
backdrop-filter: blur(10px); |
|||
-webkit-backdrop-filter: blur(10px); |
|||
border-radius: 12px; |
|||
border: 1px solid rgba(255, 255, 255, 0.2); |
|||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 10px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
color: #626573; |
|||
font-family: Arial, sans-serif; |
|||
font-weight: bold; |
|||
cursor: pointer; |
|||
margin: 20px auto; |
|||
i{ |
|||
font-size: 28px; |
|||
} |
|||
} |
|||
|
|||
|
|||
@media(max-width: 1420px) { |
|||
.index_rootContainer { |
|||
margin-right: 102px; |
|||
} |
|||
} |
|||
|
|||
.style_footerWrapper { |
|||
background: #f3f4f6; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 46px 49px; |
|||
width: 100%; |
|||
height: 192px; |
|||
box-sizing: border-box; |
|||
gap: 72px; |
|||
|
|||
b { |
|||
font-size: 12px; |
|||
font-weight: bold; |
|||
line-height: 13px; |
|||
letter-spacing: 0.08em; |
|||
color: #1E2226; |
|||
} |
|||
|
|||
.left { |
|||
gap: 12px; |
|||
|
|||
div { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 8px; |
|||
text-align: left; |
|||
} |
|||
|
|||
img { |
|||
width: 97px; |
|||
height: 100px; |
|||
} |
|||
} |
|||
|
|||
.center, |
|||
.right { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 24px; |
|||
text-align: left; |
|||
|
|||
p { |
|||
display: flex; |
|||
gap: 8px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
.style_iconWrapper__3qi7l { |
|||
flex-basis: 80px; |
|||
} |
|||
|
|||
.index_loginPage__K_bGK { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100vh; |
|||
min-width: 1200px; |
|||
} |
|||
|
|||
#ecomLoginForm { |
|||
height: 280px; |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue