1. 更新切换账号逻辑
2. 诊所管理员功能上线
This commit is contained in:
84
components/account-switch/account-switch-entry.vue
Normal file
84
components/account-switch/account-switch-entry.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<view v-if="canSwitch">
|
||||
<view
|
||||
class="switch-row"
|
||||
hover-class="switch-row-hover"
|
||||
:hover-stay-time="100"
|
||||
@tap.stop="openSwitch"
|
||||
>
|
||||
<text class="switch-label">切换账号</text>
|
||||
<u-icon name="arrow-right" color="#C9CDD4" size="28" />
|
||||
</view>
|
||||
<account-switch-panel
|
||||
ref="accountSwitchPanel"
|
||||
mode="switch"
|
||||
@switched="onSwitched"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AccountSwitchPanel from '@/components/account-switch/account-switch-panel.vue';
|
||||
import { getDoctorWxSiblingAccounts } from '@/api/doctorAuth.js';
|
||||
import {
|
||||
saveLoginSession,
|
||||
fetchProfileAfterLogin,
|
||||
navigateAfterLogin,
|
||||
} from '@/utils/loginSession.js';
|
||||
|
||||
export default {
|
||||
name: 'AccountSwitchEntry',
|
||||
components: { AccountSwitchPanel },
|
||||
data() {
|
||||
return {
|
||||
canSwitch: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.checkAccounts();
|
||||
},
|
||||
methods: {
|
||||
checkAccounts() {
|
||||
getDoctorWxSiblingAccounts()
|
||||
.then((wrap) => {
|
||||
if (wrap && wrap.ok) {
|
||||
this.canSwitch = (wrap.data || []).length > 1;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.canSwitch = false;
|
||||
});
|
||||
},
|
||||
openSwitch() {
|
||||
this.$refs.accountSwitchPanel.open();
|
||||
},
|
||||
async onSwitched(result) {
|
||||
saveLoginSession(result);
|
||||
await fetchProfileAfterLogin(result);
|
||||
this.$toast('切换成功');
|
||||
setTimeout(() => {
|
||||
navigateAfterLogin(result);
|
||||
}, 400);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.switch-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 36rpx 0;
|
||||
border-bottom: 1rpx solid #f2f3f5;
|
||||
}
|
||||
|
||||
.switch-row-hover {
|
||||
background: #f7f8fa;
|
||||
}
|
||||
|
||||
.switch-label {
|
||||
font-size: 30rpx;
|
||||
color: #31353d;
|
||||
}
|
||||
</style>
|
||||
275
components/account-switch/account-switch-panel.vue
Normal file
275
components/account-switch/account-switch-panel.vue
Normal file
@@ -0,0 +1,275 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<page-container
|
||||
v-if="mode === 'switch'"
|
||||
:show="visible"
|
||||
:overlay="false"
|
||||
@leave="handlePageLeave"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
<u-popup
|
||||
v-model="visible"
|
||||
mode="bottom"
|
||||
length="80%"
|
||||
border-radius="24"
|
||||
z-index="10078"
|
||||
:closeable="true"
|
||||
:mask-close-able="true"
|
||||
:safe-area-inset-bottom="true"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<view class="sheet-inner white">
|
||||
<view class="sheet-title">{{ mode === 'switch' ? '切换账号' : '选择登录账号' }}</view>
|
||||
<scroll-view scroll-y class="account-scroll">
|
||||
<view
|
||||
v-for="(item, index) in accounts"
|
||||
:key="item._rowKey"
|
||||
class="account-item"
|
||||
:class="{ active: isSelected(item) }"
|
||||
hover-class="account-item-hover"
|
||||
@tap.stop="selectAccount(item)"
|
||||
>
|
||||
<view class="name">{{ item.nick_name }}</view>
|
||||
<view class="meta">
|
||||
{{ item.role_name }}<text v-if="item.store_name"> · {{ item.store_name }}</text><text v-if="item.login_account"> · {{ item.login_account }}</text>
|
||||
</view>
|
||||
<view v-if="item.is_current" class="current-tag">当前</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="actions">
|
||||
<button class="btn cancel" @tap.stop="handleCancel">取消</button>
|
||||
<button class="btn confirm" :loading="loading" @tap.stop="handleConfirm">确认</button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { switchDoctorWxAccount, getDoctorWxSiblingAccounts } from '@/api/doctorAuth.js';
|
||||
|
||||
export default {
|
||||
name: 'AccountSwitchPanel',
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'login',
|
||||
},
|
||||
phone: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
accounts: [],
|
||||
selected: null,
|
||||
loading: false,
|
||||
defaultAccountId: null,
|
||||
defaultAccountType: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
normalizeAccounts(accounts) {
|
||||
return (accounts || []).map((item, index) => ({
|
||||
...item,
|
||||
_rowKey: `${item.account_type}_${item.id}_${index}`,
|
||||
}));
|
||||
},
|
||||
resolveDefaultSelection(accounts, options = {}) {
|
||||
const list = accounts || [];
|
||||
if (!list.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.mode === 'switch') {
|
||||
return list.find((item) => item.is_current) || list[0];
|
||||
}
|
||||
|
||||
const defaultId = options.defaultAccountId ?? this.defaultAccountId;
|
||||
const defaultType = options.defaultAccountType ?? this.defaultAccountType;
|
||||
if (defaultId != null && defaultType) {
|
||||
const matched = list.find(
|
||||
(item) => item.account_type === defaultType && Number(item.id) === Number(defaultId)
|
||||
);
|
||||
if (matched) {
|
||||
return matched;
|
||||
}
|
||||
}
|
||||
|
||||
return list.find((item) => item.password_matched) || list[0];
|
||||
},
|
||||
showPanel() {
|
||||
this.$nextTick(() => {
|
||||
this.visible = true;
|
||||
});
|
||||
},
|
||||
open(accounts = null, options = {}) {
|
||||
this.defaultAccountId = options.defaultAccountId ?? null;
|
||||
this.defaultAccountType = options.defaultAccountType ?? null;
|
||||
|
||||
if (Array.isArray(accounts)) {
|
||||
this.accounts = this.normalizeAccounts(accounts);
|
||||
this.selected = this.resolveDefaultSelection(this.accounts, options);
|
||||
this.showPanel();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return getDoctorWxSiblingAccounts().then((wrap) => {
|
||||
if (!wrap || !wrap.ok) {
|
||||
return Promise.reject(new Error('获取账号列表失败'));
|
||||
}
|
||||
this.accounts = this.normalizeAccounts(wrap.data || []);
|
||||
this.selected = this.resolveDefaultSelection(this.accounts, options);
|
||||
this.showPanel();
|
||||
});
|
||||
},
|
||||
close() {
|
||||
this.visible = false;
|
||||
},
|
||||
handlePageLeave() {
|
||||
this.close();
|
||||
},
|
||||
isSelected(item) {
|
||||
return this.selected && this.selected._rowKey === item._rowKey;
|
||||
},
|
||||
selectAccount(item) {
|
||||
this.selected = item;
|
||||
},
|
||||
handleCancel() {
|
||||
this.close();
|
||||
this.$emit('cancel');
|
||||
},
|
||||
handleConfirm() {
|
||||
if (!this.selected) {
|
||||
this.$toast('请选择账号');
|
||||
return;
|
||||
}
|
||||
if (this.mode === 'switch') {
|
||||
if (this.selected.is_current) {
|
||||
this.$toast('已是当前账号');
|
||||
return;
|
||||
}
|
||||
this.submitSwitch();
|
||||
return;
|
||||
}
|
||||
this.$emit('confirm', this.selected);
|
||||
this.close();
|
||||
},
|
||||
resolveSwitchError(wrap, err) {
|
||||
return wrap?.res?.msg
|
||||
|| wrap?.res?.message
|
||||
|| err?.msg
|
||||
|| err?.message
|
||||
|| '切换失败';
|
||||
},
|
||||
submitSwitch() {
|
||||
this.loading = true;
|
||||
switchDoctorWxAccount({
|
||||
account_type: this.selected.account_type,
|
||||
account_id: this.selected.id,
|
||||
}).then((wrap) => {
|
||||
if (!wrap || !wrap.ok || !wrap.data || !wrap.data.token) {
|
||||
return Promise.reject({
|
||||
message: this.resolveSwitchError(wrap),
|
||||
wrap,
|
||||
});
|
||||
}
|
||||
this.$emit('switched', wrap.data);
|
||||
this.close();
|
||||
}).catch((err) => {
|
||||
const message = this.resolveSwitchError(err?.wrap, err);
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: 'none',
|
||||
duration: 2500,
|
||||
});
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sheet-inner {
|
||||
padding: 32rpx 32rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sheet-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24rpx;
|
||||
flex-shrink: 0;
|
||||
color: #222b2a;
|
||||
}
|
||||
|
||||
.account-scroll {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.account-item {
|
||||
position: relative;
|
||||
padding: 24rpx;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.account-item.active {
|
||||
border-color: #6acdbb;
|
||||
background: #f0fbf8;
|
||||
}
|
||||
|
||||
.account-item-hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
padding-right: 80rpx;
|
||||
color: #222b2a;
|
||||
}
|
||||
|
||||
.meta {
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.current-tag {
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
top: 24rpx;
|
||||
font-size: 22rpx;
|
||||
color: #6acdbb;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
margin-top: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.confirm {
|
||||
background: #6acdbb;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
284
components/doctor-login/doctor-login.vue
Normal file
284
components/doctor-login/doctor-login.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<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"
|
||||
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>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { sendDoctorWxCode, doctorWxLogin } from '@/api/doctorAuth.js';
|
||||
import {
|
||||
saveLoginSession,
|
||||
fetchProfileAfterLogin,
|
||||
navigateAfterLogin,
|
||||
} from '@/utils/loginSession.js';
|
||||
|
||||
export default {
|
||||
name: 'DoctorLogin',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
mobile: '',
|
||||
password: '',
|
||||
check: false,
|
||||
},
|
||||
smsCode: '',
|
||||
codeText: '',
|
||||
loading: false,
|
||||
pendingLoginPayload: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
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) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.uCode.start();
|
||||
if (res.result && res.result.code) {
|
||||
this.smsCode = res.result.code;
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
submitLogin() {
|
||||
if (!this.form.mobile) {
|
||||
this.$toast('请输入手机号');
|
||||
return;
|
||||
}
|
||||
if (!this.smsCode) {
|
||||
this.$toast('请输入验证码');
|
||||
return;
|
||||
}
|
||||
if (!this.form.check) {
|
||||
this.$toast('请勾选协议');
|
||||
return;
|
||||
}
|
||||
this.doLogin({});
|
||||
},
|
||||
doLogin(extra = {}) {
|
||||
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;
|
||||
if (result.need_select && result.accounts && result.accounts.length) {
|
||||
this.pendingLoginPayload = payload;
|
||||
this.$emit('need-select', {
|
||||
accounts: result.accounts,
|
||||
defaultAccountId: result.default_account_id,
|
||||
defaultAccountType: result.default_account_type,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return this.finishLogin(result);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.$toast(err.message || '登录失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
onAccountPicked(account) {
|
||||
this.doLogin({
|
||||
account_type: account.account_type,
|
||||
account_id: account.id,
|
||||
});
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user