Files
xk-doctor-wx/components/doctor-login/doctor-login.vue

508 lines
12 KiB
Vue
Raw Normal View History

<template>
<view class="container safe-area-inset-bottom">
<view class="title">欢迎登录</view>
<view class="box">
<view class="info">
<view class="pwd">
<d-input
:maxlength="11"
height="96"
v-model="form.mobile"
:prefixIcon="require('../../static/image/act.png')"
borderRadius="96rpx"
prefixIconSize="40rpx"
placeholder="请输入手机号"
:placeholder-style="{ fontSize: '32rpx' }"
:custom-style="{ fontSize: '32rpx', paddingLeft: '60rpx' }"
/>
</view>
<view class="pwd">
<d-input
height="96"
v-model="form.password"
type="password"
:prefixIcon="require('../../static/image/pwd.png')"
password
borderRadius="96rpx"
prefixIconSize="40rpx"
placeholder="请输入登录密码"
:placeholder-style="{ fontSize: '32rpx' }"
:custom-style="{ fontSize: '32rpx', paddingLeft: '60rpx' }"
/>
</view>
<view class="pwd">
<u-field v-model="smsCode"
type="number"
:maxlength="6"
placeholder="请填写验证码"
:prefixIcon="require('../../static/image/pwd.png')"
:placeholder-style="{ fontSize: '32rpx', paddingLeft: '60rpx' }"
:custom-style="{ fontSize: '32rpx', paddingLeft: '60rpx' }"
>
<template slot="icon">
<!-- <image src="../../../../static/image/pwd.png"></image>-->
<u-icon
size="40"
name="chat"
style="margin-left: 15rpx"
@click="form.check = !form.check"
></u-icon>
</template>
<u-button size="mini" slot="right" @click="getCode">{{ codeText }}</u-button>
</u-field>
<u-verification-code ref="uCode" @change="codeChange" class="code"></u-verification-code>
</view>
<view class="flex-row" style="margin-top: 88rpx;">
<u-icon
size="35"
:color="form.check ? '#6ACDBB' : '#e0fdff'"
name="checkmark-circle-fill"
@click="form.check = !form.check"
></u-icon>
<view class="agreement">
我已阅读并同意
<text @click="$go('/subPackages/sub_agreement/agreement?type=service_user_agreement')">用户服务协议</text>
<text @click="$go('/subPackages/sub_agreement/agreement?type=service_privacy_agreement')">隐私权政策</text>
</view>
</view>
<view class="btn">
<u-button :throttle-time="20" shape="circle" @click="submitLogin" :ripple="true" :loading="loading">
登录
</u-button>
</view>
2026-07-03 07:57:26 +08:00
<view class="btn wx-btn" v-if="wxLoginBtn">
<u-button
:throttle-time="20"
shape="circle"
@click="submitWxLogin"
:ripple="true"
:loading="wxLoading"
:custom-style="{ background: '#07C160', color: '#fff' }"
>
一键登录
2026-07-03 07:57:26 +08:00
</u-button>
</view>
</view>
</view>
2026-07-03 07:57:26 +08:00
<u-popup v-model="showAgreementConfirm" mode="center" border-radius="24" width="600" z-index="10075">
<view class="confirm-modal">
<view class="confirm-title">服务协议和隐私政策</view>
<view class="confirm-desc">
请您在使用前仔细阅读并同意
<text class="link" @click="$go('/subPackages/sub_agreement/agreement?type=service_user_agreement')">用户服务协议</text>
<text class="link" @click="$go('/subPackages/sub_agreement/agreement?type=service_privacy_agreement')">隐私权政策</text>
</view>
<view class="confirm-footer">
<button class="cancel-btn" @click="showAgreementConfirm = false">不同意</button>
<button class="agree-btn" @click="agreeAndLogin">同意并登录</button>
</view>
</view>
</u-popup>
</view>
</template>
<script>
2026-07-03 07:57:26 +08:00
import { sendDoctorWxCode, doctorWxLogin, getDoctorWxAuthConfig, doctorWxLoginByWechat } from '@/api/doctorAuth.js';
import {
saveLoginSession,
fetchProfileAfterLogin,
navigateAfterLogin,
} from '@/utils/loginSession.js';
2026-06-18 17:04:50 +08:00
import { buildDoctorWxAuthExtra } from '@/utils/doctorWxAuth.js';
export default {
name: 'DoctorLogin',
data() {
return {
form: {
mobile: '',
password: '',
check: false,
},
smsCode: '',
codeText: '',
loading: false,
2026-07-03 07:57:26 +08:00
wxLoading: false,
wxLoginBtn: false,
pendingLoginPayload: null,
2026-07-03 07:57:26 +08:00
loginMode: 'password',
showAgreementConfirm: false,
pendingLoginAction: null,
};
},
2026-07-03 07:57:26 +08:00
mounted() {
this.loadWxConfig();
},
methods: {
2026-07-03 07:57:26 +08:00
loadWxConfig() {
return getDoctorWxAuthConfig().then((res) => {
const result = (res && res.code === 0 && res.result) ? res.result : {};
this.wxLoginBtn = !!result.wx_login_btn;
}).catch(() => {});
},
codeChange(text) {
this.codeText = text;
},
getCode() {
if (!this.$refs.uCode.canGetCode) {
this.$u.toast('倒计时结束后再发送');
return;
}
if (!this.form.mobile) {
this.$toast('请输入手机号');
return;
}
if (!this.form.password) {
this.$toast('请输入密码');
return;
}
uni.showLoading({ title: '正在获取验证码' });
sendDoctorWxCode(this.form.mobile, this.form.password)
.then((res) => {
// HTTP 200 但业务 code 非 0 时 reject统一走 catch 弹 toast
if (res.code !== 0) {
return Promise.reject(new Error(res.message || res.msg || '获取验证码失败'));
}
this.$refs.uCode.start();
if (res.result && res.result.code) {
this.smsCode = res.result.code;
}
})
.catch((err) => {
this.$toast(err.message || err.msg || '获取验证码失败');
})
.finally(() => {
uni.hideLoading();
});
},
2026-07-03 07:57:26 +08:00
ensureAgreement(action) {
if (this.form.check) {
return true;
}
this.pendingLoginAction = action;
this.showAgreementConfirm = true;
return false;
},
agreeAndLogin() {
this.form.check = true;
this.showAgreementConfirm = false;
if (this.pendingLoginAction === 'wx') {
this.proceedWxLogin();
} else {
this.doLogin({});
}
this.pendingLoginAction = null;
},
proceedWxLogin() {
// #ifdef MP-WEIXIN
this.wxLoading = true;
uni.login({
provider: 'weixin',
success: (loginRes) => {
if (!loginRes.code) {
this.$toast('获取微信授权失败');
this.wxLoading = false;
return;
}
this.doWxLogin(loginRes.code, {});
},
fail: () => {
this.$toast('微信授权失败');
this.wxLoading = false;
},
});
// #endif
},
submitLogin() {
if (!this.form.mobile) {
this.$toast('请输入手机号');
return;
}
if (!this.form.password) {
this.$toast('请输入密码');
return;
}
if (!this.smsCode) {
this.$toast('请输入验证码');
return;
}
2026-07-03 07:57:26 +08:00
if (!this.ensureAgreement('password')) {
return;
}
this.doLogin({});
},
2026-07-03 07:57:26 +08:00
submitWxLogin() {
if (!this.ensureAgreement('wx')) {
return;
}
this.proceedWxLogin();
},
doWxLogin(code, extra = {}) {
this.loginMode = 'wx';
this.wxLoading = true;
doctorWxLoginByWechat({ code, ...extra })
.then((res) => {
if (res.code !== 0 || !res.result) {
return Promise.reject(new Error(res.message || res.msg || '登录失败'));
}
const result = res.result;
if (result.need_select) {
const accounts = Array.isArray(result.accounts) ? result.accounts : [];
if (!accounts.length) {
return Promise.reject(new Error('请选择登录账号'));
}
this.wxLoading = false;
return new Promise((resolve) => {
this.$nextTick(() => {
this.$emit('need-select', {
accounts,
defaultAccountId: result.default_account_id,
defaultAccountType: result.default_account_type,
mode: 'wx',
});
resolve(null);
});
});
}
return this.finishLogin(result);
})
.catch((err) => {
this.$toast(err.message || '登录失败');
})
.finally(() => {
this.wxLoading = false;
});
},
doLogin(extra = {}) {
2026-07-03 07:57:26 +08:00
this.loginMode = 'password';
this.loading = true;
const payload = {
phone: this.form.mobile,
password: this.form.password,
code: this.smsCode,
...extra,
};
doctorWxLogin(payload)
.then((res) => {
if (res.code !== 0 || !res.result) {
return Promise.reject(new Error(res.message || res.msg || '登录失败'));
}
const result = res.result;
2026-06-09 17:02:13 +08:00
if (result.need_select) {
const accounts = Array.isArray(result.accounts) ? result.accounts : [];
if (!accounts.length) {
return Promise.reject(new Error('请选择登录账号'));
}
this.pendingLoginPayload = payload;
2026-06-09 17:02:13 +08:00
this.loading = false;
return new Promise((resolve) => {
this.$nextTick(() => {
this.$emit('need-select', {
accounts,
defaultAccountId: result.default_account_id,
defaultAccountType: result.default_account_type,
});
resolve(null);
});
});
}
return this.finishLogin(result);
})
.catch((err) => {
this.$toast(err.message || '登录失败');
})
.finally(() => {
this.loading = false;
});
},
onAccountPicked(account) {
2026-07-03 07:57:26 +08:00
if (this.loginMode === 'wx') {
this.wxLoading = true;
uni.login({
provider: 'weixin',
success: (loginRes) => {
if (!loginRes.code) {
this.$toast('获取微信授权失败');
this.wxLoading = false;
return;
}
this.doWxLogin(loginRes.code, buildDoctorWxAuthExtra(account));
},
fail: () => {
this.$toast('微信授权失败');
this.wxLoading = false;
},
});
return;
}
2026-06-18 17:04:50 +08:00
this.doLogin(buildDoctorWxAuthExtra(account));
},
async finishLogin(result) {
saveLoginSession(result);
await fetchProfileAfterLogin(result);
this.$toast('登录成功');
if (result.account_type !== 'admin') {
uni.$emit('user-login');
}
setTimeout(() => {
navigateAfterLogin(result);
}, 400);
},
},
};
</script>
<style lang="scss" scoped>
.container {
width: 100vw;
height: 100vh;
padding-bottom: env(safe-area-inset-bottom);
}
.title {
width: 750rpx;
padding-top: 200rpx;
text-align: center;
font-size: 48rpx;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #0D111A;
background: linear-gradient(180deg, #EAF2FF 0%, #FFFFFF 100%);
margin-bottom: 60rpx;
}
.flex-row {
display: flex;
flex-direction: row;
margin-bottom: 34rpx;
.agreement {
font-size: 24rpx;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #6C7380;
margin-left: 16rpx;
text {
color: #2979FF;
size: 24rpx;
}
}
}
.box {
box-sizing: border-box;
padding: 0 52rpx;
}
.code {
font-size: 32rpx;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #1777FF;
background: #F9FAFB;
}
.pwd {
width: 646rpx;
height: 96rpx;
background: #F9FAFB;
border-radius: 48rpx 48rpx 48rpx 48rpx;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #CCCCCC;
margin: 0rpx 0 30rpx;
::v-deep button {
color: #1777FF;
border: 0;
}
}
.btn {
::v-deep button {
width: 648rpx;
height: 100rpx;
line-height: 100rpx;
background: #6ACDBB;
border: 0;
font-size: 32rpx;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #FFFFFF;
}
}
2026-07-03 07:57:26 +08:00
.wx-btn {
margin-top: 24rpx;
::v-deep button {
background: #07C160 !important;
}
}
.confirm-modal {
padding: 40rpx 30rpx;
background: #fff;
.confirm-title {
font-size: 34rpx;
font-weight: bold;
color: #333;
text-align: center;
margin-bottom: 30rpx;
}
.confirm-desc {
font-size: 28rpx;
color: #666;
line-height: 1.6;
margin-bottom: 40rpx;
text-align: center;
.link {
color: #2979FF;
}
}
.confirm-footer {
display: flex;
justify-content: space-between;
gap: 20rpx;
button {
flex: 1;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
font-size: 30rpx;
border: none;
&::after {
border: none;
}
}
.cancel-btn {
background: #F5F7FA;
color: #909399;
}
.agree-btn {
background: #6ACDBB;
color: #FFFFFF;
}
}
}
</style>