1. 特色方模块
This commit is contained in:
@@ -51,3 +51,35 @@ export function switchDoctorWxAccount(payload) {
|
||||
data: payload,
|
||||
});
|
||||
}
|
||||
|
||||
export function getDoctorWxAuthConfig() {
|
||||
return req.request({
|
||||
url: `${AUTH_PREFIX}/config`,
|
||||
method: 'GET',
|
||||
header: { isToken: false },
|
||||
});
|
||||
}
|
||||
|
||||
export function doctorWxLoginByWechat(payload) {
|
||||
return req.request({
|
||||
url: `${AUTH_PREFIX}/wx-login`,
|
||||
method: 'POST',
|
||||
data: payload,
|
||||
header: { isToken: false },
|
||||
});
|
||||
}
|
||||
|
||||
export function getDoctorWxBindStatus() {
|
||||
return doctorAuthRequest({
|
||||
url: '/wx-bind-status',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
export function bindDoctorWxWechat(code) {
|
||||
return doctorAuthRequest({
|
||||
url: '/bind-wechat',
|
||||
method: 'POST',
|
||||
data: { code },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,6 +34,15 @@ export function getAcceptingPatientList(data) {
|
||||
})
|
||||
}
|
||||
|
||||
/** 工作台看板统计 */
|
||||
export function getWorkbenchStats(data) {
|
||||
return req.request({
|
||||
url: '/newApi/doctor-reception-wx/workbench-stats',
|
||||
method: 'GET',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取患者详情(挂号维度的就诊信息,不含处方列表)
|
||||
export function getPatientItem(id) {
|
||||
return req.request({
|
||||
@@ -43,6 +52,15 @@ export function getPatientItem(id) {
|
||||
})
|
||||
}
|
||||
|
||||
/** 医生应用特色方到开方区 */
|
||||
export function applySpecialPrescriptionApi(data) {
|
||||
return req.request({
|
||||
url: '/newApi/doctor-reception-wx/apply-special-prescription',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 常用回复模板(与 PC online-consultation/get-quick-reply-list 同源) */
|
||||
export function getQuickReplyListApi() {
|
||||
return req.request({
|
||||
|
||||
10
api/storeConsultation.js
Normal file
10
api/storeConsultation.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { req } from '@/common/js/index.js';
|
||||
|
||||
/** 检查门店在线复诊配置(委托诊所 + 在线复诊医生) */
|
||||
export function checkOnlineConsultationConfigApi(data) {
|
||||
return req.request({
|
||||
url: '/newApi/doctor-reception-wx/check-online-consultation-config',
|
||||
method: 'GET',
|
||||
data,
|
||||
});
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import Request from './request.js';
|
||||
import errorCode from './errorCode.js';
|
||||
import {Base64} from "js-base64";
|
||||
import { getDoctorWxEnv } from '@/config/app-env.js';
|
||||
import { resolveDoctorWxAuthToken } from '@/utils/doctorWxAuthToken.js';
|
||||
|
||||
const { legacyApiBase } = getDoctorWxEnv();
|
||||
|
||||
@@ -66,22 +67,9 @@ const reqInterceptor = async (options) => {
|
||||
const loginMode = uni.getStorageSync('loginMode')
|
||||
if (!skipToken) {
|
||||
if (isDoctorWxAuthReq) {
|
||||
if (loginMode === 'platform_admin' && platformToken) {
|
||||
options.header['authorization'] = `Bearer ${platformToken}`
|
||||
} else if (loginMode === 'salesperson' && salespersonToken) {
|
||||
options.header['authorization'] = `Bearer ${salespersonToken}`
|
||||
} else if (loginMode === 'clinic_salesperson' && clinicSalespersonToken) {
|
||||
options.header['authorization'] = `Bearer ${clinicSalespersonToken}`
|
||||
} else if (loginMode === 'clinic_admin' && clinicToken) {
|
||||
options.header['authorization'] = `Bearer ${clinicToken}`
|
||||
} else if (doctorToken) {
|
||||
options.header['authorization'] = `Bearer ${doctorToken}`
|
||||
} else if (platformToken) {
|
||||
options.header['authorization'] = `Bearer ${platformToken}`
|
||||
} else if (salespersonToken) {
|
||||
options.header['authorization'] = `Bearer ${salespersonToken}`
|
||||
} else if (clinicToken) {
|
||||
options.header['authorization'] = `Bearer ${clinicToken}`
|
||||
const authToken = resolveDoctorWxAuthToken()
|
||||
if (authToken) {
|
||||
options.header['authorization'] = `Bearer ${authToken}`
|
||||
}
|
||||
} else if (isPlatformAdminReq && platformToken) {
|
||||
options.header['authorization'] = `Bearer ${platformToken}`
|
||||
|
||||
@@ -55,6 +55,7 @@ export default {
|
||||
async onSwitched(result) {
|
||||
saveLoginSession(result);
|
||||
await fetchProfileAfterLogin(result);
|
||||
uni.$emit('login-account-switched');
|
||||
this.$toast('切换成功');
|
||||
setTimeout(() => {
|
||||
navigateAfterLogin(result);
|
||||
|
||||
122
components/d-top-message/d-top-message.vue
Normal file
122
components/d-top-message/d-top-message.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="visible"
|
||||
class="d-top-message"
|
||||
:class="['d-top-message--' + type, { 'd-top-message--show': animated }]"
|
||||
:style="{ top: topOffset + 'px' }"
|
||||
>
|
||||
<u-icon v-if="type === 'success'" name="checkmark-circle-fill" color="#6ACDBB" size="28" class="d-top-message-icon"></u-icon>
|
||||
<text class="d-top-message-text">{{ text }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DTopMessage',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
animated: false,
|
||||
text: '',
|
||||
type: 'success',
|
||||
topOffset: 88,
|
||||
hideTimer: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.calcTopOffset();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearTimer();
|
||||
},
|
||||
methods: {
|
||||
calcTopOffset() {
|
||||
try {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
const statusBar = systemInfo.statusBarHeight || 0;
|
||||
if (typeof wx !== 'undefined' && wx.getMenuButtonBoundingClientRect) {
|
||||
const menuButton = wx.getMenuButtonBoundingClientRect();
|
||||
this.topOffset = menuButton.bottom + uni.upx2px(16);
|
||||
} else {
|
||||
this.topOffset = statusBar + uni.upx2px(88);
|
||||
}
|
||||
} catch (e) {
|
||||
this.topOffset = 88;
|
||||
}
|
||||
},
|
||||
clearTimer() {
|
||||
if (this.hideTimer) {
|
||||
clearTimeout(this.hideTimer);
|
||||
this.hideTimer = null;
|
||||
}
|
||||
},
|
||||
show(options = {}) {
|
||||
const {
|
||||
text = '数据已刷新',
|
||||
type = 'success',
|
||||
duration = 1500,
|
||||
} = options;
|
||||
|
||||
this.clearTimer();
|
||||
this.text = text;
|
||||
this.type = type;
|
||||
this.visible = true;
|
||||
this.animated = false;
|
||||
this.$nextTick(() => {
|
||||
this.animated = true;
|
||||
});
|
||||
|
||||
this.hideTimer = setTimeout(() => {
|
||||
this.hide();
|
||||
}, duration);
|
||||
},
|
||||
hide() {
|
||||
this.clearTimer();
|
||||
this.animated = false;
|
||||
setTimeout(() => {
|
||||
this.visible = false;
|
||||
}, 200);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.d-top-message {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
z-index: 100000;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
max-width: 80vw;
|
||||
padding: 10rpx 28rpx;
|
||||
border-radius: 999rpx;
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -12rpx);
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.d-top-message--show {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
.d-top-message--success {
|
||||
background: #E8F8F3;
|
||||
}
|
||||
|
||||
.d-top-message-text {
|
||||
font-size: 24rpx;
|
||||
line-height: 1.4;
|
||||
color: #6ACDBB;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.d-top-message-icon {
|
||||
margin-right: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -73,14 +73,44 @@
|
||||
登录
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<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' }"
|
||||
>
|
||||
微信一键登录
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<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>
|
||||
import { sendDoctorWxCode, doctorWxLogin } from '@/api/doctorAuth.js';
|
||||
import { sendDoctorWxCode, doctorWxLogin, getDoctorWxAuthConfig, doctorWxLoginByWechat } from '@/api/doctorAuth.js';
|
||||
import {
|
||||
saveLoginSession,
|
||||
fetchProfileAfterLogin,
|
||||
@@ -100,10 +130,24 @@ export default {
|
||||
smsCode: '',
|
||||
codeText: '',
|
||||
loading: false,
|
||||
wxLoading: false,
|
||||
wxLoginBtn: false,
|
||||
pendingLoginPayload: null,
|
||||
loginMode: 'password',
|
||||
showAgreementConfirm: false,
|
||||
pendingLoginAction: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadWxConfig();
|
||||
},
|
||||
methods: {
|
||||
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;
|
||||
},
|
||||
@@ -134,6 +178,44 @@ export default {
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
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('请输入手机号');
|
||||
@@ -143,13 +225,55 @@ export default {
|
||||
this.$toast('请输入验证码');
|
||||
return;
|
||||
}
|
||||
if (!this.form.check) {
|
||||
this.$toast('请勾选协议');
|
||||
if (!this.ensureAgreement('password')) {
|
||||
return;
|
||||
}
|
||||
this.doLogin({});
|
||||
},
|
||||
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 = {}) {
|
||||
this.loginMode = 'password';
|
||||
this.loading = true;
|
||||
const payload = {
|
||||
phone: this.form.mobile,
|
||||
@@ -191,6 +315,25 @@ export default {
|
||||
});
|
||||
},
|
||||
onAccountPicked(account) {
|
||||
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;
|
||||
}
|
||||
this.doLogin(buildDoctorWxAuthExtra(account));
|
||||
},
|
||||
async finishLogin(result) {
|
||||
@@ -288,4 +431,66 @@ export default {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
|
||||
156
components/wx-bind-entry/wx-bind-entry.vue
Normal file
156
components/wx-bind-entry/wx-bind-entry.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<view v-if="featureEnabled" class="wx-bind-entry" @click="handleTap">
|
||||
<view class="wx-bind-left">
|
||||
<u-icon name="weixin-fill" color="#07C160" size="36"></u-icon>
|
||||
<text class="wx-bind-label">{{ bound ? '已绑定微信' : '绑定微信' }}</text>
|
||||
</view>
|
||||
<view class="wx-bind-right">
|
||||
<text v-if="bound && openidMasked" class="wx-bind-mask">{{ openidMasked }}</text>
|
||||
<u-icon name="arrow-right" color="#C9CDD4" size="28"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getDoctorWxAuthConfig, getDoctorWxBindStatus, bindDoctorWxWechat } from '@/api/doctorAuth.js';
|
||||
import { getCurrentLoginContext } from '@/utils/loginSession.js';
|
||||
|
||||
export default {
|
||||
name: 'WxBindEntry',
|
||||
data() {
|
||||
return {
|
||||
featureEnabled: false,
|
||||
bound: false,
|
||||
openidMasked: '',
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
uni.$on('login-account-switched', this.refresh);
|
||||
},
|
||||
beforeDestroy() {
|
||||
uni.$off('login-account-switched', this.refresh);
|
||||
},
|
||||
methods: {
|
||||
refresh() {
|
||||
this.bound = false;
|
||||
this.openidMasked = '';
|
||||
const ctx = getCurrentLoginContext();
|
||||
if (!ctx.phone || !ctx.account_id) {
|
||||
this.featureEnabled = false;
|
||||
return Promise.resolve();
|
||||
}
|
||||
return getDoctorWxAuthConfig()
|
||||
.then((res) => {
|
||||
const result = (res && res.code === 0 && res.result) ? res.result : {};
|
||||
if (!result.wx_login_btn) {
|
||||
this.featureEnabled = false;
|
||||
return;
|
||||
}
|
||||
this.featureEnabled = true;
|
||||
return getDoctorWxBindStatus({
|
||||
phone: ctx.phone,
|
||||
account_type: ctx.account_type,
|
||||
account_id: ctx.account_id,
|
||||
})
|
||||
.then(({ data, ok }) => {
|
||||
if (ok && data) {
|
||||
this.bound = !!data.bound;
|
||||
this.openidMasked = data.openid_masked || '';
|
||||
} else {
|
||||
this.bound = false;
|
||||
this.openidMasked = '';
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.bound = false;
|
||||
this.openidMasked = '';
|
||||
});
|
||||
},
|
||||
handleTap() {
|
||||
if (this.bound || this.loading) {
|
||||
return;
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
this.loading = true;
|
||||
uni.showLoading({ title: '绑定中...', mask: true });
|
||||
new Promise((resolve, reject) => {
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: (loginRes) => {
|
||||
if (!loginRes.code) {
|
||||
reject(new Error('no_code'));
|
||||
return;
|
||||
}
|
||||
resolve(loginRes.code);
|
||||
},
|
||||
fail: () => reject(new Error('login_fail')),
|
||||
});
|
||||
})
|
||||
.then((code) => bindDoctorWxWechat(code))
|
||||
.then(({ data, ok, res }) => {
|
||||
if (ok) {
|
||||
this.bound = true;
|
||||
this.openidMasked = (data && data.openid_masked) || '';
|
||||
this.$toast && this.$toast('绑定成功');
|
||||
} else {
|
||||
this.$toast && this.$toast((res && (res.message || res.msg)) || '绑定失败');
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err && err.message === 'no_code') {
|
||||
this.$toast && this.$toast('获取微信授权失败');
|
||||
} else if (err && err.message === 'login_fail') {
|
||||
this.$toast && this.$toast('微信授权失败');
|
||||
} else {
|
||||
this.$toast && this.$toast('绑定失败');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
uni.hideLoading();
|
||||
this.loading = false;
|
||||
});
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
this.$toast && this.$toast('请在微信小程序中使用');
|
||||
// #endif
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wx-bind-entry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 0;
|
||||
border-bottom: 1rpx solid #f0f1f3;
|
||||
}
|
||||
|
||||
.wx-bind-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wx-bind-label {
|
||||
margin-left: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #31353d;
|
||||
}
|
||||
|
||||
.wx-bind-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wx-bind-mask {
|
||||
font-size: 24rpx;
|
||||
color: #6c7380;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -26,6 +26,9 @@
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark",
|
||||
"backgroundColor": "#f7f8fa"
|
||||
},
|
||||
"usingComponents": {
|
||||
"d-top-message": "/components/d-top-message/d-top-message"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@ export default {
|
||||
},
|
||||
onShow() {
|
||||
if (this.pendingAccountSelect) return;
|
||||
this.$refs.doctorLogin?.loadWxConfig?.();
|
||||
redirectIfLoggedIn();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex-col m-t-2 white p-row-32">
|
||||
<view class="wx-bind-wrap">
|
||||
<wx-bind-entry ref="wxBindEntry" />
|
||||
</view>
|
||||
<view class="flex-row bottom-border flex-jus-sp p-col-32 flex-ali-center" v-for="(item,i) in items"
|
||||
:key="i" @click="$go(item.url)">
|
||||
<view class="flex-row flex-ali-center">
|
||||
@@ -61,10 +64,18 @@
|
||||
</view>
|
||||
<u-icon name="arrow-right" color="#bcbcbc"></u-icon>
|
||||
</view>
|
||||
<view class="account-switch-wrap">
|
||||
<account-switch-entry />
|
||||
</view>
|
||||
</view>
|
||||
<view class="logout-wrap p-row-32 m-t-2">
|
||||
<u-button :throttle-time="0" shape="circle"
|
||||
:custom-style="{ backgroundColor: '#6ACDBB', color: '#fff', height: '96rpx' }"
|
||||
@click="logout">退出登录</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<my-pharmacist v-if="parseInt(identity)==2" :info="userInfo" :avatar="getAvatar"></my-pharmacist>
|
||||
<my-pharmacist v-if="parseInt(identity)==2" ref="myPharmacist" :info="userInfo" :avatar="getAvatar"></my-pharmacist>
|
||||
<my-service v-if="parseInt(identity)>2" :info="userInfo" :avatar="getAvatar"></my-service>
|
||||
<d-tabbar></d-tabbar>
|
||||
</view>
|
||||
@@ -73,16 +84,21 @@
|
||||
<script>
|
||||
import {
|
||||
info,
|
||||
logout,
|
||||
personalData,
|
||||
leadInfo,
|
||||
servInfo
|
||||
} from "@/api/all.js"
|
||||
import myPharmacist from './my-pharmacist.vue'
|
||||
import myService from './my-service.vue'
|
||||
import WxBindEntry from '@/components/wx-bind-entry/wx-bind-entry.vue'
|
||||
import AccountSwitchEntry from '@/components/account-switch/account-switch-entry.vue'
|
||||
export default {
|
||||
components: {
|
||||
myService,
|
||||
myPharmacist,
|
||||
WxBindEntry,
|
||||
AccountSwitchEntry,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -157,8 +173,17 @@
|
||||
},
|
||||
onShow() {
|
||||
this.getInfo()
|
||||
this.refreshWxBind()
|
||||
},
|
||||
methods: {
|
||||
refreshWxBind() {
|
||||
if (parseInt(this.identity) === 1 && this.$refs.wxBindEntry) {
|
||||
this.$refs.wxBindEntry.refresh()
|
||||
}
|
||||
if (parseInt(this.identity) === 2 && this.$refs.myPharmacist) {
|
||||
this.$refs.myPharmacist.refreshWxBind()
|
||||
}
|
||||
},
|
||||
async getInfo() {
|
||||
let res = {}
|
||||
this.identity == 1 && (res = await info({
|
||||
@@ -167,17 +192,25 @@
|
||||
if (res.errcode == 0) {
|
||||
this.doctorInfo = res.data
|
||||
uni.setStorageSync('userInfo', res.data.DoctorInfo)
|
||||
} else {
|
||||
} else if (res.msg) {
|
||||
this.$toast(res.msg);
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
logout({ store_id: uni.getStorageSync('store_id') || 11001 }).then((res) => {
|
||||
if (res.errcode != -1) {
|
||||
uni.clearStorage()
|
||||
uni.clearStorageSync()
|
||||
this.$go('/pages/login/index', 2)
|
||||
} else {
|
||||
this.$toast(res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getInfo()
|
||||
},
|
||||
onShow() {
|
||||
this.getInfo()
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
@@ -216,6 +249,18 @@
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.wx-bind-wrap {
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
|
||||
.account-switch-wrap {
|
||||
border-top: 1rpx solid #f0f1f3;
|
||||
}
|
||||
|
||||
.logout-wrap {
|
||||
padding-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
</d-text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="p-32 white m-t-12">
|
||||
<wx-bind-entry ref="wxBindEntry" />
|
||||
</view>
|
||||
<view class="p-32 white m-t-12" @click="$go('../../subPackages/sub_pharmacist/pharmacist_info?avatar='+avatar+'&info='+JSON.stringify(info))">
|
||||
<view class="flex-row flex-jus-sp flex-ali-center">
|
||||
<u-icon margin-left="20" label-size="32" label-color="#31353D"
|
||||
@@ -37,7 +40,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WxBindEntry from '@/components/wx-bind-entry/wx-bind-entry.vue'
|
||||
export default {
|
||||
components: { WxBindEntry },
|
||||
props:{
|
||||
avatar:{
|
||||
type:String,
|
||||
@@ -57,9 +62,16 @@
|
||||
},
|
||||
onLoad() {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.refreshWxBind()
|
||||
},
|
||||
methods: {
|
||||
|
||||
refreshWxBind() {
|
||||
if (this.$refs.wxBindEntry) {
|
||||
this.$refs.wxBindEntry.refresh()
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,69 +1,136 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="bg" :style="{height:`calc(${navbarHeight}px + ${navbarHeight}px + 394rpx)`}">
|
||||
</view>
|
||||
<view class="navbar" :style="{ height: statusBarHeight+navbarHeight + 'px' }">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<view class="text-align-cen" :style="{ height:navbarHeight + 'px' ,lineHeight: navbarHeight + 'px'}">
|
||||
工作台
|
||||
</view>
|
||||
</view>
|
||||
<view class="box" :style="{marginTop: statusBarHeight+navbarHeight + 'px'}">
|
||||
<view class="store_" @click="openShow=true">
|
||||
<view class="header-wrap" :style="{ paddingTop: statusBarHeight + navbarHeight + 'px' }">
|
||||
<view class="box">
|
||||
<view class="store_" @click="openShow=true">
|
||||
{{doctorInfo.store.store.name || '默认门店'}}
|
||||
<u-icon name="map-fill" color="#fff" size="28" margin-left="12"></u-icon>
|
||||
</view>
|
||||
<view class="box_info">
|
||||
<view class="box_info_left">
|
||||
<view class="box_info">
|
||||
<view class="box_info_left">
|
||||
|
||||
<u-image width="96rpx" height="96rpx" shape="circle" :src="doctorInfo.DoctorInfo.avatar">
|
||||
<u-loading slot="loading"></u-loading>
|
||||
</u-image>
|
||||
<view class="flex-col flex-jus-sp" style="height: 106rpx;margin-left: 32rpx;">
|
||||
<view class="flex-row flex-ali-center">
|
||||
<text class="box_info_left_name">{{doctorInfo.DoctorInfo.name}}</text>
|
||||
<text class="m-r-16">{{doctorInfo.DoctorInfo.title['name']}}</text>
|
||||
<u-tag text="审核中" type="warning" size="mini" shape="circle" border-color="#FB8C00"
|
||||
bgColor="#FB8C00" color="#fff" v-if="doctorInfo.DoctorInfo.user.status<=1" />
|
||||
<u-tag text="已认证" type="warning" size="mini" shape="circle" border-color="#FB8C00"
|
||||
bgColor="#FB8C00" color="#fff" v-if="doctorInfo.DoctorInfo.user.status==2" />
|
||||
<u-tag v-if="doctorInfo.DoctorInfo.user.status==3" text="审核未通过" type="warning" size="mini"
|
||||
border-color="#A7ABB0" bgColor="#A7ABB0" color="#fff" />
|
||||
<u-image width="96rpx" height="96rpx" shape="circle" :src="doctorInfo.DoctorInfo.avatar">
|
||||
<u-loading slot="loading"></u-loading>
|
||||
</u-image>
|
||||
<view class="flex-col flex-jus-sp" style="height: 106rpx;margin-left: 32rpx;">
|
||||
<view class="flex-row flex-ali-center">
|
||||
<text class="box_info_left_name">{{doctorInfo.DoctorInfo.name}}</text>
|
||||
<text class="m-r-16">{{doctorInfo.DoctorInfo.title['name']}}</text>
|
||||
<u-tag text="审核中" type="warning" size="mini" shape="circle" border-color="#FB8C00"
|
||||
bgColor="#FB8C00" color="#fff" v-if="doctorInfo.DoctorInfo.user.status<=1" />
|
||||
<u-tag text="已认证" type="warning" size="mini" shape="circle" border-color="#FB8C00"
|
||||
bgColor="#FB8C00" color="#fff" v-if="doctorInfo.DoctorInfo.user.status==2" />
|
||||
<u-tag v-if="doctorInfo.DoctorInfo.user.status==3" text="审核未通过" type="warning" size="mini"
|
||||
border-color="#A7ABB0" bgColor="#A7ABB0" color="#fff" />
|
||||
</view>
|
||||
<text style="width: 400rpx;">
|
||||
{{`${doctorInfo.store.store.name}(${doctorInfo.DoctorInfo.depart['name']})`}}
|
||||
</text>
|
||||
</view>
|
||||
<text style="width: 400rpx;">
|
||||
{{`${doctorInfo.store.store.name}(${doctorInfo.DoctorInfo.depart['name']})`}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="box_info_right flex-col flex-jus-sp flex-ali-center"
|
||||
@click="$go('../../subPackages/sub_workbench/workbench_code')">
|
||||
<image src="../../static/image/mp.png"></image>
|
||||
<d-text text="我的名片" className="w-c fs-2"></d-text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="box_data flex-row flex-jus-sp flex-ali-center">
|
||||
<view class="flex-col flex-jus-sp flex-ali-center">
|
||||
<text class="box_data_nub">{{doctorInfo.wait_accept || '0'}}</text>
|
||||
<label>待接诊</label>
|
||||
</view>
|
||||
<view class="flex-col flex-jus-sp flex-ali-center">
|
||||
<text class="box_data_nub">{{doctorInfo.accepting || '0'}}</text>
|
||||
<label>已接诊</label>
|
||||
<view class="box_info_right flex-col flex-jus-sp flex-ali-center"
|
||||
@click="$go('../../subPackages/sub_workbench/workbench_code')">
|
||||
<image src="../../static/image/mp.png"></image>
|
||||
<d-text text="我的名片" className="w-c fs-2"></d-text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="header-spacer" :style="{ height: headerSpacerHeight + 'px' }"></view>
|
||||
|
||||
<view class="content">
|
||||
<view class="function p-32">
|
||||
<view class="function_grid">
|
||||
<view class="function_item flex-col flex-ali-center flex-jus-sp"
|
||||
@click="doctorInfo.DoctorInfo.user.status==3?$go('../../subPackages/sub_workbench/workbench_upInfo/index'):$go(item.url)"
|
||||
v-for="(item,i) in functions" :key="i">
|
||||
<image :src="item.icon"></image>
|
||||
<view>{{item.name}}</view>
|
||||
<u-badge :count="i===0?doctorInfo.wait_accept:0" v-if="i===0" :offset="[-6,98]"
|
||||
bgColor="#F44336"></u-badge>
|
||||
<!-- 看板统计 -->
|
||||
<view class="dashboard-panel">
|
||||
<view class="dashboard-tabs">
|
||||
<view
|
||||
v-for="(tab, tabIndex) in statsRangeTabs"
|
||||
:key="tab.value"
|
||||
class="dashboard-tab"
|
||||
:class="{ active: statsRange === tab.value }"
|
||||
@click="selectStatsTab(tabIndex)"
|
||||
>{{ tab.label }}</view>
|
||||
</view>
|
||||
<view class="dashboard-body">
|
||||
<view v-if="statsLoading && !hasStatsCache(statsRange)" class="dashboard-loading-overlay">
|
||||
<u-loading mode="circle" size="36"></u-loading>
|
||||
</view>
|
||||
<swiper
|
||||
class="dashboard-swiper"
|
||||
:current="statsTabIndex"
|
||||
:duration="200"
|
||||
@change="onStatsSwiperChange"
|
||||
>
|
||||
<swiper-item v-for="tab in statsRangeTabs" :key="tab.value">
|
||||
<view class="dashboard-grid">
|
||||
<view class="dashboard-stat">
|
||||
<text class="dashboard-stat-value">{{ getStatsForRange(tab.value).prescription_count || 0 }}</text>
|
||||
<text class="dashboard-stat-label">开方数量</text>
|
||||
</view>
|
||||
<view class="dashboard-stat">
|
||||
<text class="dashboard-stat-value">{{ getStatsForRange(tab.value).wait_accept || 0 }}</text>
|
||||
<text class="dashboard-stat-label">待接诊</text>
|
||||
</view>
|
||||
<view class="dashboard-stat">
|
||||
<text class="dashboard-stat-value">{{ getStatsForRange(tab.value).accepting || 0 }}</text>
|
||||
<text class="dashboard-stat-label">接诊中</text>
|
||||
</view>
|
||||
<view class="dashboard-stat">
|
||||
<text class="dashboard-stat-value">{{ getStatsForRange(tab.value).completed || 0 }}</text>
|
||||
<text class="dashboard-stat-label">已完成</text>
|
||||
</view>
|
||||
<view class="dashboard-stat dashboard-stat-wide">
|
||||
<text class="dashboard-stat-value amount">{{ formatStatsAmount(getStatsForRange(tab.value).prescription_amount) }}</text>
|
||||
<text class="dashboard-stat-label">处方金额(元)</text>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="function p-32" v-if="onlineConsultationConfigReady && canUseOnlineConsultation">
|
||||
<view class="function_grid">
|
||||
<view
|
||||
class="function_item flex-col flex-ali-center flex-jus-sp"
|
||||
v-for="(item, i) in receptionGridEntries"
|
||||
:key="i"
|
||||
@click="goReceptionEntry(item.url)"
|
||||
>
|
||||
<image :src="item.icon"></image>
|
||||
<view>{{ item.name }}</view>
|
||||
<u-badge
|
||||
:count="i === 0 ? doctorInfo.wait_accept : 0"
|
||||
v-if="i === 0"
|
||||
:offset="[-6, 98]"
|
||||
bgColor="#F44336"
|
||||
></u-badge>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="function p-32" v-else-if="onlineConsultationConfigReady">
|
||||
<view
|
||||
class="offline-entry-row flex-row flex-ali-center"
|
||||
@click="goReceptionEntry(offlineReceptionEntry.url)"
|
||||
>
|
||||
<image class="offline-entry-icon" :src="offlineReceptionEntry.icon"></image>
|
||||
<view class="offline-entry-body flex-col flex-1">
|
||||
<text class="offline-entry-title">{{ offlineReceptionEntry.name }}</text>
|
||||
<text class="offline-entry-desc">到店开方、处理线下挂号</text>
|
||||
</view>
|
||||
<u-badge
|
||||
v-if="doctorInfo.wait_accept > 0"
|
||||
:count="doctorInfo.wait_accept"
|
||||
:offset="[-8, 8]"
|
||||
bgColor="#F44336"
|
||||
></u-badge>
|
||||
<u-icon name="arrow-right" color="#6acdbb" size="28"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<block>
|
||||
@@ -107,6 +174,13 @@
|
||||
:type="item.consultation_channel === 'online' ? 'primary' : 'info'"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
<u-tag
|
||||
v-if="item.special_prescription_patient_record"
|
||||
class="wb-tag"
|
||||
:text="item.special_prescription_patient_record.status === 0 ? '特色方·待应用' : '特色方'"
|
||||
type="warning"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
<text class="wb-time">{{
|
||||
item.consultation_channel === 'online' ? formatAcceptingMsgTime(item) : acceptingOfflineRegisterTimeText(item)
|
||||
@@ -319,6 +393,7 @@
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<d-top-message ref="topMessage"></d-top-message>
|
||||
<d-tabbar></d-tabbar>
|
||||
</view>
|
||||
</template>
|
||||
@@ -338,10 +413,57 @@ import {
|
||||
} from '../../api/consult';
|
||||
import {
|
||||
getAcceptingPatientList as fetchAcceptingPatientListApi,
|
||||
getOnlineConsultationPatientListApi
|
||||
getOnlineConsultationPatientListApi,
|
||||
getWorkbenchStats,
|
||||
} from '../../api/reception';
|
||||
import { normalizeRoomId } from '@/utils/chat/chatRoomManager.js';
|
||||
import { formatSessionRelativeTime } from '@/utils/chat/sessionListFormat.js';
|
||||
import { checkOnlineConsultationConfigApi } from '../../api/storeConsultation.js';
|
||||
|
||||
/** 房间 ID 归一化(避免引入 chatRoomManager 整模块) */
|
||||
function normalizeRoomId(id) {
|
||||
return String(id == null ? '' : id).trim();
|
||||
}
|
||||
|
||||
/** 会话列表相对时间(与 utils/chat/sessionListFormat.js 一致) */
|
||||
function formatSessionRelativeTime(item) {
|
||||
if (!item) return '';
|
||||
const t =
|
||||
item.last_message_time ||
|
||||
item.updated_at ||
|
||||
item.created_at ||
|
||||
item.register_time;
|
||||
if (t == null || t === '') return '';
|
||||
const ms = typeof t === 'number' ? (t > 1e12 ? t : t * 1000) : new Date(t).getTime();
|
||||
if (Number.isNaN(ms)) return '';
|
||||
const d = new Date(ms);
|
||||
const diff = Math.floor((Date.now() - ms) / 60000);
|
||||
if (diff < 1) return '刚刚';
|
||||
if (diff < 60) return `${diff}分钟前`;
|
||||
const h = Math.floor(diff / 60);
|
||||
if (h < 24) return `${h}小时前`;
|
||||
const pad = (n) => (n < 10 ? '0' + n : '' + n);
|
||||
return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
}
|
||||
|
||||
const WORKBENCH_STATS_RANGE_KEY = 'workbench_stats_range';
|
||||
const OFFLINE_RECEPTION_ENTRY = {
|
||||
name: '线下接诊',
|
||||
icon: '../../static/image/zxzx.png',
|
||||
url: '../../subPackages/sub_workbench/workbench_consult?type=1',
|
||||
};
|
||||
const ONLINE_RECEPTION_ENTRY = {
|
||||
name: '在线接诊',
|
||||
icon: '../../static/image/xsfz.png',
|
||||
url: '../../subPackages/sub_online_reception/pages/list',
|
||||
};
|
||||
const RECEPTION_GRID_ENTRIES = [OFFLINE_RECEPTION_ENTRY, ONLINE_RECEPTION_ENTRY];
|
||||
const EMPTY_WORKBENCH_STATS = {
|
||||
prescription_count: 0,
|
||||
wait_accept: 0,
|
||||
accepting: 0,
|
||||
completed: 0,
|
||||
prescription_amount: '0.000',
|
||||
};
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -353,15 +475,10 @@ export default {
|
||||
loading: '努力加载中',
|
||||
nomore: '实在没有了'
|
||||
},
|
||||
functions: [{
|
||||
name: '到店接诊',
|
||||
icon: '../../static/image/zxzx.png',
|
||||
url: '../../subPackages/sub_workbench/workbench_consult?type=1'
|
||||
}, {
|
||||
name: '在线接诊',
|
||||
icon: '../../static/image/xsfz.png',
|
||||
url: '../../subPackages/sub_online_reception/pages/list'
|
||||
}],
|
||||
receptionGridEntries: RECEPTION_GRID_ENTRIES,
|
||||
offlineReceptionEntry: OFFLINE_RECEPTION_ENTRY,
|
||||
canUseOnlineConsultation: false,
|
||||
onlineConsultationConfigReady: false,
|
||||
states: {
|
||||
1: {
|
||||
name: '待接诊',
|
||||
@@ -398,8 +515,26 @@ export default {
|
||||
see_rate: "",
|
||||
acceptingPatientList: [], // 正在接诊的患者列表
|
||||
refreshTimer: null,
|
||||
refreshIntervalMs: 10000,
|
||||
refreshIntervalMs: 20000,
|
||||
isReceptionRefreshing: false,
|
||||
statsRange: 'today',
|
||||
statsTabIndex: 0,
|
||||
statsSwiperChanging: false,
|
||||
statsRangeTabs: [
|
||||
{ label: '今日', value: 'today' },
|
||||
{ label: '本周', value: 'week' },
|
||||
{ label: '本月', value: 'month' },
|
||||
{ label: '近一月', value: 'last_30d' },
|
||||
],
|
||||
statsCache: {
|
||||
today: null,
|
||||
week: null,
|
||||
month: null,
|
||||
last_30d: null,
|
||||
},
|
||||
workbenchStats: { ...EMPTY_WORKBENCH_STATS },
|
||||
statsLoading: false,
|
||||
statsInitialized: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -421,7 +556,13 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
headerSpacerHeight() {
|
||||
const contentPx = typeof uni.upx2px === 'function' ? uni.upx2px(312) : 156;
|
||||
return this.statusBarHeight + this.navbarHeight + contentPx;
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
this.initStatsRangeFromStorage();
|
||||
},
|
||||
onUnload() {
|
||||
this.stopRefreshTimer();
|
||||
@@ -451,14 +592,32 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initStatsRangeFromStorage() {
|
||||
const saved = uni.getStorageSync(WORKBENCH_STATS_RANGE_KEY);
|
||||
const tabIndex = this.statsRangeTabs.findIndex((tab) => tab.value === saved);
|
||||
if (tabIndex >= 0) {
|
||||
this.statsRange = saved;
|
||||
this.statsTabIndex = tabIndex;
|
||||
}
|
||||
},
|
||||
notifyWorkbenchRefreshed() {
|
||||
if (this.$refs.topMessage) {
|
||||
this.$refs.topMessage.show({ text: '数据已刷新', type: 'success' });
|
||||
}
|
||||
},
|
||||
refreshWorkbenchData() {
|
||||
const silentStats = this.statsInitialized || this.hasStatsCache(this.statsRange);
|
||||
return Promise.all([
|
||||
this.getNotice(),
|
||||
this.getInfo(),
|
||||
this.getStore(),
|
||||
this.getList(),
|
||||
this.getAcceptingPatientList()
|
||||
]);
|
||||
this.getAcceptingPatientList(),
|
||||
this.fetchOnlineConsultationConfig(),
|
||||
this.fetchWorkbenchStats({ silent: silentStats })
|
||||
]).then(() => {
|
||||
this.notifyWorkbenchRefreshed();
|
||||
});
|
||||
},
|
||||
refreshReceptionData() {
|
||||
if (this.isReceptionRefreshing) {
|
||||
@@ -468,8 +627,12 @@ export default {
|
||||
return Promise.all([
|
||||
this.getInfo(),
|
||||
this.getList(),
|
||||
this.getAcceptingPatientList()
|
||||
]).finally(() => {
|
||||
this.getAcceptingPatientList(),
|
||||
this.fetchOnlineConsultationConfig(),
|
||||
this.fetchWorkbenchStats({ silent: true })
|
||||
]).then(() => {
|
||||
this.notifyWorkbenchRefreshed();
|
||||
}).finally(() => {
|
||||
this.isReceptionRefreshing = false;
|
||||
});
|
||||
},
|
||||
@@ -505,6 +668,33 @@ export default {
|
||||
this.status = 'loading'
|
||||
this.page++
|
||||
},
|
||||
goReceptionEntry(url) {
|
||||
const doctorInfo = this.doctorInfo || {};
|
||||
const user = doctorInfo.DoctorInfo && doctorInfo.DoctorInfo.user;
|
||||
if (user && user.status === 3) {
|
||||
this.$go('../../subPackages/sub_workbench/workbench_upInfo/index');
|
||||
return;
|
||||
}
|
||||
this.$go(url);
|
||||
},
|
||||
fetchOnlineConsultationConfig() {
|
||||
const storeId = uni.getStorageSync('store_id') || '11001';
|
||||
return checkOnlineConsultationConfigApi({ store_id: storeId })
|
||||
.then((res) => {
|
||||
const payload = (res && res.result) || (res && res.data) || {};
|
||||
if (res && (res.code === 0 || res.errcode === 0)) {
|
||||
this.canUseOnlineConsultation = !!payload.can_use;
|
||||
} else {
|
||||
this.canUseOnlineConsultation = false;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.canUseOnlineConsultation = false;
|
||||
})
|
||||
.finally(() => {
|
||||
this.onlineConsultationConfigReady = true;
|
||||
});
|
||||
},
|
||||
getNotice() {
|
||||
return getNotice({
|
||||
page_size: 9999,
|
||||
@@ -567,7 +757,91 @@ export default {
|
||||
}
|
||||
|
||||
this.openShow = false
|
||||
this.getInfo()
|
||||
this.onlineConsultationConfigReady = false
|
||||
this.refreshWorkbenchData()
|
||||
})
|
||||
},
|
||||
selectStatsTab(index) {
|
||||
if (this.statsSwiperChanging) return;
|
||||
const tab = this.statsRangeTabs[index];
|
||||
if (!tab) return;
|
||||
this.statsTabIndex = index;
|
||||
this.changeStatsRange(tab.value);
|
||||
},
|
||||
onStatsSwiperChange(e) {
|
||||
const index = e.detail.current;
|
||||
const tab = this.statsRangeTabs[index];
|
||||
if (!tab || tab.value === this.statsRange) return;
|
||||
this.statsSwiperChanging = true;
|
||||
this.statsTabIndex = index;
|
||||
this.changeStatsRange(tab.value);
|
||||
this.$nextTick(() => {
|
||||
this.statsSwiperChanging = false;
|
||||
});
|
||||
},
|
||||
changeStatsRange(range) {
|
||||
if (this.statsRange === range && this.hasStatsCache(range)) {
|
||||
this.workbenchStats = { ...this.getStatsForRange(range) };
|
||||
return;
|
||||
}
|
||||
this.statsRange = range;
|
||||
uni.setStorageSync(WORKBENCH_STATS_RANGE_KEY, range);
|
||||
const tabIndex = this.statsRangeTabs.findIndex((tab) => tab.value === range);
|
||||
if (tabIndex >= 0) {
|
||||
this.statsTabIndex = tabIndex;
|
||||
}
|
||||
if (this.hasStatsCache(range)) {
|
||||
this.workbenchStats = { ...this.getStatsForRange(range) };
|
||||
this.fetchWorkbenchStats({ silent: true });
|
||||
return;
|
||||
}
|
||||
this.fetchWorkbenchStats({ silent: false });
|
||||
},
|
||||
hasStatsCache(range) {
|
||||
return this.statsCache[range] != null;
|
||||
},
|
||||
getStatsForRange(range) {
|
||||
return this.statsCache[range] || { ...EMPTY_WORKBENCH_STATS };
|
||||
},
|
||||
normalizeWorkbenchStats(payload = {}) {
|
||||
return {
|
||||
prescription_count: payload.prescription_count || 0,
|
||||
wait_accept: payload.wait_accept || 0,
|
||||
accepting: payload.accepting || 0,
|
||||
completed: payload.completed || 0,
|
||||
prescription_amount: payload.prescription_amount || '0.000',
|
||||
};
|
||||
},
|
||||
formatStatsAmount(amount) {
|
||||
const num = Number(amount)
|
||||
if (Number.isNaN(num)) return '0.000'
|
||||
return num.toFixed(3)
|
||||
},
|
||||
fetchWorkbenchStats(options = {}) {
|
||||
const silent = options.silent === true;
|
||||
const range = options.range || this.statsRange;
|
||||
const hasCache = this.hasStatsCache(range);
|
||||
if (!silent && !hasCache) {
|
||||
this.statsLoading = true;
|
||||
}
|
||||
return getWorkbenchStats({
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
range,
|
||||
}).then((res) => {
|
||||
const payload = (res && res.result) || (res && res.data) || {}
|
||||
if (res && (res.code === 0 || res.errcode === 0)) {
|
||||
const stats = this.normalizeWorkbenchStats(payload);
|
||||
this.$set(this.statsCache, range, stats);
|
||||
if (range === this.statsRange) {
|
||||
this.workbenchStats = { ...stats };
|
||||
}
|
||||
this.statsInitialized = true;
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$toast && this.$toast('看板数据加载失败')
|
||||
return Promise.reject(new Error('看板数据加载失败'));
|
||||
}).finally(() => {
|
||||
this.statsLoading = false;
|
||||
})
|
||||
},
|
||||
// 消息列表
|
||||
@@ -762,15 +1036,6 @@ page {
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.bg {
|
||||
width: 100vw;
|
||||
background: #6ACDBB;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: transparent;
|
||||
width: 100vw;
|
||||
@@ -784,11 +1049,27 @@ page {
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.header-wrap {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: #6ACDBB;
|
||||
border-radius: 0 60rpx 60rpx;
|
||||
padding: 0 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.header-spacer {
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 24rpx;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
margin-top: 30rpx;
|
||||
z-index: 1;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* 顶部门店选择器扁平化 */
|
||||
@@ -864,8 +1145,7 @@ page {
|
||||
|
||||
.box {
|
||||
position: relative;
|
||||
padding: 0 32rpx;
|
||||
height: 318rpx;
|
||||
min-height: 200rpx;
|
||||
|
||||
&_info {
|
||||
display: flex;
|
||||
@@ -897,28 +1177,6 @@ page {
|
||||
}
|
||||
}
|
||||
|
||||
&_data {
|
||||
width: 100%;
|
||||
height: 110rpx;
|
||||
font-size: 24rpx;
|
||||
color: #FFFFFF;
|
||||
margin-top: 40rpx;
|
||||
background: rgba(255,255,255,0.15);
|
||||
border-radius: 16rpx;
|
||||
padding: 0 40rpx;
|
||||
|
||||
view {
|
||||
height: 84rpx;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
&_nub {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.store_ {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -933,7 +1191,104 @@ page {
|
||||
}
|
||||
}
|
||||
|
||||
/* 功能菜单:完全恢复初始原始布局及代码 */
|
||||
/* 看板统计 */
|
||||
.dashboard-panel {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 24rpx 8rpx;
|
||||
margin-top: -24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.dashboard-tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 24rpx;
|
||||
background: #f5f6f8;
|
||||
border-radius: 12rpx;
|
||||
padding: 6rpx;
|
||||
}
|
||||
|
||||
.dashboard-tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #6c7380;
|
||||
padding: 12rpx 0;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.dashboard-tab.active {
|
||||
background: #fff;
|
||||
color: #6acdbb;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dashboard-body {
|
||||
position: relative;
|
||||
min-height: 280rpx;
|
||||
}
|
||||
|
||||
.dashboard-swiper {
|
||||
height: 280rpx;
|
||||
}
|
||||
|
||||
.dashboard-loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.dashboard-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
min-height: 280rpx;
|
||||
}
|
||||
|
||||
.dashboard-stat {
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16rpx 0 24rpx;
|
||||
}
|
||||
|
||||
.dashboard-stat-wide {
|
||||
width: 100%;
|
||||
border-top: 1rpx solid #f0f1f3;
|
||||
margin-top: 8rpx;
|
||||
padding-top: 24rpx;
|
||||
}
|
||||
|
||||
.dashboard-stat-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #0d111a;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.dashboard-stat-value.amount {
|
||||
font-size: 40rpx;
|
||||
color: #6acdbb;
|
||||
}
|
||||
|
||||
.dashboard-stat-label {
|
||||
font-size: 22rpx;
|
||||
color: #6c7380;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
/* 功能菜单:接诊入口 */
|
||||
.function {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
@@ -960,6 +1315,42 @@ page {
|
||||
}
|
||||
}
|
||||
|
||||
.offline-entry-row {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 132rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(135deg, #f0faf8 0%, #e8f7f4 100%);
|
||||
border-radius: 16rpx;
|
||||
border: 1rpx solid rgba(106, 205, 187, 0.25);
|
||||
}
|
||||
|
||||
.offline-entry-icon {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.offline-entry-body {
|
||||
margin-left: 24rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.offline-entry-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #0d111a;
|
||||
line-height: 44rpx;
|
||||
}
|
||||
|
||||
.offline-entry-desc {
|
||||
margin-top: 6rpx;
|
||||
font-size: 24rpx;
|
||||
color: #6c7380;
|
||||
line-height: 34rpx;
|
||||
}
|
||||
|
||||
/* 通用板块平铺和消息通知:完全恢复至初始样式 */
|
||||
.news {
|
||||
background-color: #fff;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
<!-- 操作面板(通栏) -->
|
||||
<view class="setting-panel">
|
||||
<wx-bind-entry ref="wxBindEntry" />
|
||||
<account-switch-entry />
|
||||
<view
|
||||
class="setting-item action-item"
|
||||
@@ -48,10 +49,11 @@
|
||||
<script>
|
||||
import ClinicPageLayout from '@/subPackages/sub_clinic_admin/components/clinic-page-layout.vue'
|
||||
import AccountSwitchEntry from '@/components/account-switch/account-switch-entry.vue'
|
||||
import WxBindEntry from '@/components/wx-bind-entry/wx-bind-entry.vue'
|
||||
import { clearLoginStorage } from '@/utils/loginSession.js'
|
||||
|
||||
export default {
|
||||
components: { ClinicPageLayout, AccountSwitchEntry },
|
||||
components: { ClinicPageLayout, AccountSwitchEntry, WxBindEntry },
|
||||
data() {
|
||||
return {
|
||||
displayName: '',
|
||||
@@ -68,6 +70,7 @@ export default {
|
||||
const user = uni.getStorageSync('clinic_admin_user') || {}
|
||||
this.displayName = user.nick_name || user.name || user.account || '管理员'
|
||||
this.storeName = user.store_name || (user.store && user.store.name) || '-'
|
||||
if (this.$refs.wxBindEntry) this.$refs.wxBindEntry.refresh()
|
||||
},
|
||||
methods: {
|
||||
logout() {
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
|
||||
<view class="setting-panel">
|
||||
|
||||
<wx-bind-entry ref="wxBindEntry" />
|
||||
|
||||
<account-switch-entry />
|
||||
|
||||
<view
|
||||
@@ -84,6 +86,8 @@ import ClinicSalespersonPageLayout from '@/subPackages/sub_clinic_salesperson/co
|
||||
|
||||
import AccountSwitchEntry from '@/components/account-switch/account-switch-entry.vue'
|
||||
|
||||
import WxBindEntry from '@/components/wx-bind-entry/wx-bind-entry.vue'
|
||||
|
||||
import { getClinicSalespersonMyInfo } from '@/api/clinicSalesperson.js'
|
||||
|
||||
import { clearLoginStorage } from '@/utils/loginSession.js'
|
||||
@@ -92,7 +96,7 @@ import { clearLoginStorage } from '@/utils/loginSession.js'
|
||||
|
||||
export default {
|
||||
|
||||
components: { ClinicSalespersonPageLayout, AccountSwitchEntry },
|
||||
components: { ClinicSalespersonPageLayout, AccountSwitchEntry, WxBindEntry },
|
||||
|
||||
data() {
|
||||
|
||||
@@ -124,6 +128,8 @@ export default {
|
||||
|
||||
this.loadProfile()
|
||||
|
||||
if (this.$refs.wxBindEntry) this.$refs.wxBindEntry.refresh()
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
@@ -35,28 +35,17 @@
|
||||
<d-text :text="userInfo.title.name" color="#31353D"></d-text>
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
<view class="p-row-32 m-t-8 white b-r-8">
|
||||
<account-switch-entry />
|
||||
</view>
|
||||
|
||||
<view class="p-row-32 m-t-8" style="height: 100rpx;">
|
||||
<u-button :throttle-time="0" shape="circle" :custom-style="{backgroundColor:'#6ACDBB',color:'#fff',heigth:'96rpx'}" @click="logout">
|
||||
退出登录</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
info,
|
||||
logout,
|
||||
toUpload,
|
||||
} from '@/api/all.js'
|
||||
import AccountSwitchEntry from '@/components/account-switch/account-switch-entry.vue'
|
||||
import { chooseAvatarImage } from '@/common/js/select-avatar-image.js';
|
||||
export default {
|
||||
components: { AccountSwitchEntry },
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
doctorInfo: []
|
||||
@@ -112,29 +101,6 @@
|
||||
},
|
||||
});
|
||||
},
|
||||
logout() {
|
||||
logout({store_id: uni.getStorageSync('store_id') || 11001,}).then((res) => {
|
||||
if (res.errcode != -1) {
|
||||
uni.clearStorage()
|
||||
uni.clearStorageSync()
|
||||
this.$go('/pages/login/index', 2)
|
||||
} else {
|
||||
this.$toast(res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// // 医生详情
|
||||
// getInfo() {
|
||||
// info({
|
||||
// store_id: uni.getStorageSync('store_id') || '11001'
|
||||
// }).then((res) => {
|
||||
// // console.log(res.data, 'work-res');
|
||||
// if (res.errcode == 0) {
|
||||
// this.doctorInfo = res.data
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
},
|
||||
mounted() {
|
||||
this.getDoctor()
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="setting-panel">
|
||||
<wx-bind-entry ref="wxBindEntry" />
|
||||
<account-switch-entry />
|
||||
<view class="setting-item action-item" @click="logout">
|
||||
<text class="danger-text">退出登录</text>
|
||||
@@ -21,11 +22,12 @@
|
||||
<script>
|
||||
import BusinessPageLayout from '@/subPackages/sub_platform_admin/components/business-page-layout.vue'
|
||||
import AccountSwitchEntry from '@/components/account-switch/account-switch-entry.vue'
|
||||
import WxBindEntry from '@/components/wx-bind-entry/wx-bind-entry.vue'
|
||||
import { getPlatformAdminContext } from '@/subPackages/sub_platform_admin/common/context.js'
|
||||
import { clearLoginStorage } from '@/utils/loginSession.js'
|
||||
|
||||
export default {
|
||||
components: { BusinessPageLayout, AccountSwitchEntry },
|
||||
components: { BusinessPageLayout, AccountSwitchEntry, WxBindEntry },
|
||||
provide() { return { businessContext: getPlatformAdminContext() } },
|
||||
data() { return { displayName: '', roleText: '平台管理员' } },
|
||||
computed: {
|
||||
@@ -35,6 +37,7 @@ export default {
|
||||
const user = uni.getStorageSync('platform_admin_user') || {}
|
||||
this.displayName = user.nick_name || '管理员'
|
||||
this.roleText = (user.roles && user.roles.name) || '平台管理员'
|
||||
if (this.$refs.wxBindEntry) this.$refs.wxBindEntry.refresh()
|
||||
},
|
||||
methods: {
|
||||
logout() {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="setting-panel">
|
||||
<wx-bind-entry ref="wxBindEntry" />
|
||||
<account-switch-entry />
|
||||
<view class="setting-item" @click="logout"><text class="danger-text">退出登录</text></view>
|
||||
</view>
|
||||
@@ -19,11 +20,12 @@
|
||||
<script>
|
||||
import BusinessPageLayout from '@/subPackages/sub_salesperson/components/business-page-layout.vue'
|
||||
import AccountSwitchEntry from '@/components/account-switch/account-switch-entry.vue'
|
||||
import WxBindEntry from '@/components/wx-bind-entry/wx-bind-entry.vue'
|
||||
import { getSalespersonContext } from '@/subPackages/sub_salesperson/common/context.js'
|
||||
import { clearLoginStorage } from '@/utils/loginSession.js'
|
||||
|
||||
export default {
|
||||
components: { BusinessPageLayout, AccountSwitchEntry },
|
||||
components: { BusinessPageLayout, AccountSwitchEntry, WxBindEntry },
|
||||
provide() { return { businessContext: getSalespersonContext() } },
|
||||
data() { return { displayName: '', roleText: '业务员' } },
|
||||
computed: { avatarText() { return (this.displayName || '业').slice(0, 1) } },
|
||||
@@ -31,6 +33,7 @@ export default {
|
||||
const user = uni.getStorageSync('salesperson_user') || {}
|
||||
this.displayName = user.nick_name || '业务员'
|
||||
this.roleText = (user.roles && user.roles.name) || '业务员'
|
||||
if (this.$refs.wxBindEntry) this.$refs.wxBindEntry.refresh()
|
||||
},
|
||||
methods: {
|
||||
logout() {
|
||||
|
||||
@@ -35,6 +35,55 @@
|
||||
<text class="register-mode-hint__text">{{ registerModeHint }}</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="!isSalespersonTransferMode && hasSpecialPrescriptionRecord"
|
||||
class="special-rx-card mx-32 m-t-12"
|
||||
>
|
||||
<view class="special-rx-card__inner flex-row flex-ali-center">
|
||||
<u-image
|
||||
v-if="specialPrescriptionRecord.special_prescription && specialPrescriptionRecord.special_prescription.cover_image"
|
||||
width="96rpx"
|
||||
height="96rpx"
|
||||
border-radius="12"
|
||||
:src="specialPrescriptionRecord.special_prescription.cover_image"
|
||||
/>
|
||||
<view class="flex-1 m-l-16">
|
||||
<view class="fs-28 font-bold text-gray">
|
||||
{{ (specialPrescriptionRecord.special_prescription && specialPrescriptionRecord.special_prescription.name) || '特色方' }}
|
||||
</view>
|
||||
<view class="fs-24 color-sub m-t-8">
|
||||
剂量:{{ specialPrescriptionRecord.dose_count }} 剂
|
||||
</view>
|
||||
<view class="m-t-8">
|
||||
<u-tag
|
||||
v-if="Number(specialPrescriptionRecord.status) === 0"
|
||||
text="待导入"
|
||||
type="warning"
|
||||
size="mini"
|
||||
/>
|
||||
<u-tag
|
||||
v-else-if="Number(specialPrescriptionRecord.status) === 1"
|
||||
text="已应用"
|
||||
type="success"
|
||||
size="mini"
|
||||
/>
|
||||
<u-tag v-else text="已完成" type="info" size="mini" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="special-rx-card__action m-t-16">
|
||||
<u-button
|
||||
size="default"
|
||||
type="primary"
|
||||
:loading="applyingSpecialPrescription"
|
||||
:custom-style="{ backgroundColor: '#6ACDBB', color: '#fff', border: 'none', width: '100%' }"
|
||||
@click="handleApplySpecialPrescription"
|
||||
>
|
||||
导入
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="!isSalespersonTransferMode && allowInsuranceCategory" class="mx-32 m-t-12 flex flex-ali-center">
|
||||
<u-button size="mini" :type="feeCategory === 1 ? 'success' : 'default'" @click="feeCategory = 1">自费</u-button>
|
||||
<u-button size="mini" class="m-l-16" :type="feeCategory === 2 ? 'success' : 'default'" @click="feeCategory = 2">医保</u-button>
|
||||
@@ -539,6 +588,7 @@ import {
|
||||
getProductListDoctorReception,
|
||||
getPatientInfoByPatientId,
|
||||
getPatientItem,
|
||||
applySpecialPrescriptionApi,
|
||||
getProcessRuleList,
|
||||
getCommonPrescriptionListApi,
|
||||
saveWestCommonPrescriptionApi,
|
||||
@@ -571,6 +621,10 @@ import {
|
||||
getClinicSalespersonSeePriceConfig,
|
||||
getClinicSalespersonProcessRuleList,
|
||||
} from '@/api/clinicSalesperson.js';
|
||||
import {
|
||||
pickSpecialPrescriptionRecord,
|
||||
applySpecialPrescriptionPayload,
|
||||
} from './utils/specialPrescription.js';
|
||||
|
||||
const PENDING_RX_FROM_CHAT = 'xk_pending_rx_from_chat';
|
||||
const SALESPERSON_TRANSFER_STORAGE_KEY = 'salesperson_transfer';
|
||||
@@ -676,6 +730,9 @@ export default {
|
||||
sendMode: 0,
|
||||
selectedStoreId: null,
|
||||
registerOrderType: null,
|
||||
specialPrescriptionRecord: null,
|
||||
applyingSpecialPrescription: false,
|
||||
pendingImportSpecial: false,
|
||||
|
||||
/** 在线复诊中药:术语表(HTTP 拉取,内存缓存) */
|
||||
traditionalTcmData: null,
|
||||
@@ -803,6 +860,9 @@ export default {
|
||||
if (this.registerOrderType === null || this.registerOrderType === undefined || this.registerOrderType === '') return '';
|
||||
return this.isOnlineRevisit ? '当前:在线复诊开方(提交后将向患者发送处方消息)' : '当前:线下开方';
|
||||
},
|
||||
hasSpecialPrescriptionRecord() {
|
||||
return !!this.specialPrescriptionRecord;
|
||||
},
|
||||
navbarTitle() {
|
||||
if (this.isSalespersonTransferMode) {
|
||||
return '传方';
|
||||
@@ -905,6 +965,7 @@ export default {
|
||||
}
|
||||
this.patientId = options.patient_id || '';
|
||||
this.pendingReusePrescriptionId = options.reuse_prescription_id ? String(options.reuse_prescription_id) : '';
|
||||
this.pendingImportSpecial = String(options.import_special) === '1';
|
||||
this.isCommonPrescription = String(options.save_common) === '1';
|
||||
if (!this.isCommonPrescription) {
|
||||
uni.removeStorageSync('add');
|
||||
@@ -969,6 +1030,10 @@ export default {
|
||||
await this.maybeApplyReusePrescription();
|
||||
if (this.activeCategory === 1) await this.checkAndShowTransferTip();
|
||||
this.applyPendingExperienceDrugFromChat();
|
||||
if (this.pendingImportSpecial && this.hasSpecialPrescriptionRecord) {
|
||||
await this.handleApplySpecialPrescription();
|
||||
this.pendingImportSpecial = false;
|
||||
}
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
this.$nextTick(() => {
|
||||
@@ -1099,6 +1164,7 @@ export default {
|
||||
async loadRegisterOrderType() {
|
||||
if (!this.registerId) {
|
||||
this.registerOrderType = null;
|
||||
this.specialPrescriptionRecord = null;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -1109,9 +1175,11 @@ export default {
|
||||
} else {
|
||||
this.registerOrderType = null;
|
||||
}
|
||||
this.specialPrescriptionRecord = pickSpecialPrescriptionRecord(data);
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
this.registerOrderType = null;
|
||||
this.specialPrescriptionRecord = null;
|
||||
}
|
||||
},
|
||||
async loadBasicConfigData() {
|
||||
@@ -1386,6 +1454,22 @@ export default {
|
||||
this.saveToLocalStorage();
|
||||
},
|
||||
handleOpenCommonPrescriptionModal() { this.showCommonPrescriptionModal = true; },
|
||||
async handleApplySpecialPrescription() {
|
||||
if (!this.registerId || this.applyingSpecialPrescription) return;
|
||||
this.applyingSpecialPrescription = true;
|
||||
try {
|
||||
const res = await applySpecialPrescriptionApi({ register_id: this.registerId });
|
||||
const data = res.result || res.data || res;
|
||||
applySpecialPrescriptionPayload(this, data);
|
||||
await this.loadRegisterOrderType();
|
||||
this.$toast(`已导入特色方:${data.special_prescription_name || ''}`);
|
||||
} catch (error) {
|
||||
console.error('导入特色方失败:', error);
|
||||
this.$toast('导入特色方失败,请稍后重试');
|
||||
} finally {
|
||||
this.applyingSpecialPrescription = false;
|
||||
}
|
||||
},
|
||||
async handleSelectCommonPrescription(item) {
|
||||
try {
|
||||
const payload = item && item.apply_payload;
|
||||
@@ -2541,6 +2625,18 @@ export default {
|
||||
color: #008771;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.special-rx-card {
|
||||
padding: 20rpx 24rpx;
|
||||
background: #fffaf5;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #ffe0c2;
|
||||
}
|
||||
.special-rx-card__inner {
|
||||
width: 100%;
|
||||
}
|
||||
.special-rx-card__action {
|
||||
width: 100%;
|
||||
}
|
||||
.bottom {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 从 patient-item 等接口响应中解析特色方选方记录
|
||||
*/
|
||||
export function pickSpecialPrescriptionRecord(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
data.special_prescription_patient_record
|
||||
|| data.specialPrescriptionPatientRecord
|
||||
|| null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 apply 接口返回的数据写入处方页 state
|
||||
*/
|
||||
export function applySpecialPrescriptionPayload(page, data) {
|
||||
const categoryMap = { west: 2, chinese: 1, granular: 1 };
|
||||
const prescriptionType = data.prescription_type || 'chinese';
|
||||
page.activeCategory = categoryMap[prescriptionType] || 1;
|
||||
page.loadCurrentCategoryDrugs();
|
||||
const recipes = data.recipes || [];
|
||||
page.currentDrugs.splice(0, page.currentDrugs.length);
|
||||
recipes.forEach((recipe) => {
|
||||
const drugId = recipe.drug_id || recipe.id;
|
||||
page.currentDrugs.push(page.prepareDrugForCart({
|
||||
index_id: recipe.id,
|
||||
id: drugId,
|
||||
drug_id: drugId,
|
||||
drug_name: recipe.drug_name || recipe.name,
|
||||
number: recipe.number || 1,
|
||||
price: recipe.price || 0,
|
||||
buy_price: recipe.buy_price,
|
||||
way_id: recipe.way_id || 0,
|
||||
}));
|
||||
});
|
||||
page.chineseConfig = {
|
||||
...page.chineseConfig,
|
||||
ruleType: data.rule_type ?? 1,
|
||||
dosage: data.dosage ?? data.dose_count ?? 7,
|
||||
dayDosage: data.day_dosage ?? 2,
|
||||
packageMethodId: data.package_method_id ?? 2,
|
||||
};
|
||||
if (data.clinical_diagnose) {
|
||||
page.diagnosisText = data.clinical_diagnose;
|
||||
const names = String(data.clinical_diagnose).split(',').filter(Boolean);
|
||||
page.diagnoses = names.map((name, index) => ({ id: `temp_${index}`, name }));
|
||||
}
|
||||
if (data.doctor_order) {
|
||||
page.medicalAdvice = data.doctor_order;
|
||||
}
|
||||
page.saveToLocalStorage();
|
||||
}
|
||||
@@ -67,6 +67,54 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 特色方信息 -->
|
||||
<view v-if="infoList.special_prescription_patient_record" class="modern-card">
|
||||
<view class="card-title">特色方</view>
|
||||
<view class="special-rx-info flex-row flex-ali-center">
|
||||
<u-image
|
||||
v-if="infoList.special_prescription_patient_record.special_prescription && infoList.special_prescription_patient_record.special_prescription.cover_image"
|
||||
width="96rpx"
|
||||
height="96rpx"
|
||||
border-radius="12"
|
||||
:src="infoList.special_prescription_patient_record.special_prescription.cover_image"
|
||||
/>
|
||||
<view class="flex-1 m-l-16">
|
||||
<view class="info-value font-bold">
|
||||
{{ (infoList.special_prescription_patient_record.special_prescription && infoList.special_prescription_patient_record.special_prescription.name) || '特色方' }}
|
||||
</view>
|
||||
<view class="info-row" style="border-bottom: none; padding: 8rpx 0 0;">
|
||||
<text class="info-label">剂量</text>
|
||||
<text class="info-value">{{ infoList.special_prescription_patient_record.dose_count }} 剂</text>
|
||||
</view>
|
||||
<view class="m-t-8">
|
||||
<u-tag
|
||||
v-if="Number(infoList.special_prescription_patient_record.status) === 0"
|
||||
text="待导入"
|
||||
type="warning"
|
||||
size="mini"
|
||||
/>
|
||||
<u-tag
|
||||
v-else-if="Number(infoList.special_prescription_patient_record.status) === 1"
|
||||
text="已应用"
|
||||
type="success"
|
||||
size="mini"
|
||||
/>
|
||||
<u-tag v-else text="已完成" type="info" size="mini" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="m-t-24">
|
||||
<u-button
|
||||
type="primary"
|
||||
shape="circle"
|
||||
:custom-style="{ backgroundColor: '#6ACDBB', color: '#fff', border: 'none' }"
|
||||
@click="goImportSpecialPrescription"
|
||||
>
|
||||
导入
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 健康信息 -->
|
||||
<view class="modern-card">
|
||||
<view class="card-title">健康信息</view>
|
||||
@@ -201,6 +249,7 @@ import {
|
||||
receptionApi,
|
||||
endOfDiagnosisApi
|
||||
} from '@/api/reception.js'
|
||||
import { pickSpecialPrescriptionRecord } from '@/subPackages/sub_workbench/prescription_v2/utils/specialPrescription.js'
|
||||
|
||||
function emptyHealthInquiry() {
|
||||
return {
|
||||
@@ -235,7 +284,8 @@ function normalizeRegisterDetail(raw) {
|
||||
store: store && store.name !== undefined ? store : {
|
||||
name: ''
|
||||
},
|
||||
healthInquery: hi || emptyHealthInquiry()
|
||||
healthInquery: hi || emptyHealthInquiry(),
|
||||
special_prescription_patient_record: pickSpecialPrescriptionRecord(raw),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -471,7 +521,7 @@ export default {
|
||||
this.$toast(this.apiMsg(res))
|
||||
return
|
||||
}
|
||||
const raw = res.result || res.data
|
||||
const raw = res.result || res.data || res
|
||||
this.infoList = normalizeRegisterDetail(raw)
|
||||
if (this.infoList.status !== 7) {
|
||||
if (this.rxTabIndex === 0) {
|
||||
@@ -489,6 +539,19 @@ export default {
|
||||
const patientId = this.infoList.user_patient_id;
|
||||
this.$go(`/subPackages/sub_workbench/prescription_v2/index?register_id=${registerId}&patient_id=${patientId}`);
|
||||
},
|
||||
goImportSpecialPrescription() {
|
||||
const registerId = this.infoList.id;
|
||||
const patientId = this.infoList.user_patient_id;
|
||||
if (!registerId) {
|
||||
this.$toast('挂号信息无效');
|
||||
return;
|
||||
}
|
||||
let url = `/subPackages/sub_workbench/prescription_v2/index?register_id=${registerId}&import_special=1`;
|
||||
if (patientId) {
|
||||
url += `&patient_id=${patientId}`;
|
||||
}
|
||||
this.$go(url);
|
||||
},
|
||||
goToReusePrescription(prescriptionId) {
|
||||
const registerId = this.infoList.id;
|
||||
const patientId = this.infoList.user_patient_id;
|
||||
|
||||
55
utils/doctorWxAuthToken.js
Normal file
55
utils/doctorWxAuthToken.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/** doctor-wx-auth 请求与绑定状态查询共用的登录态解析(无 API 依赖,避免与 common/js/index 循环引用) */
|
||||
|
||||
export const ADMIN_TOKEN_KEYS = {
|
||||
platform_admin: 'platform_admin_token',
|
||||
salesperson: 'salesperson_token',
|
||||
clinic_salesperson: 'clinic_salesperson_token',
|
||||
clinic_admin: 'clinic_admin_token',
|
||||
};
|
||||
|
||||
export const ADMIN_USER_KEYS = {
|
||||
platform_admin: 'platform_admin_user',
|
||||
salesperson: 'salesperson_user',
|
||||
clinic_salesperson: 'clinic_salesperson_user',
|
||||
clinic_admin: 'clinic_admin_user',
|
||||
};
|
||||
|
||||
/** 当前有效的 admin loginMode(须同时有 token) */
|
||||
export function resolveActiveAdminLoginMode() {
|
||||
const stored = uni.getStorageSync('loginMode');
|
||||
if (stored && ADMIN_TOKEN_KEYS[stored] && uni.getStorageSync(ADMIN_TOKEN_KEYS[stored])) {
|
||||
return stored;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/** doctor-wx-auth 接口应使用的 token:优先当前 loginMode 对应 admin,否则医生/药师 token,不 fallback 其它 admin token */
|
||||
export function resolveDoctorWxAuthToken() {
|
||||
const loginMode = resolveActiveAdminLoginMode();
|
||||
if (loginMode) {
|
||||
return uni.getStorageSync(ADMIN_TOKEN_KEYS[loginMode]) || '';
|
||||
}
|
||||
return uni.getStorageSync('token') || '';
|
||||
}
|
||||
|
||||
/** 当前登录上下文(手机号 + 账号维度,供 wx-bind-status 等) */
|
||||
export function getCurrentLoginContext() {
|
||||
const loginMode = resolveActiveAdminLoginMode();
|
||||
if (loginMode) {
|
||||
const user = uni.getStorageSync(ADMIN_USER_KEYS[loginMode]) || {};
|
||||
return {
|
||||
phone: String(user.phone || '').trim(),
|
||||
account_type: 'admin',
|
||||
account_id: Number(user.id || 0),
|
||||
token: uni.getStorageSync(ADMIN_TOKEN_KEYS[loginMode]) || '',
|
||||
};
|
||||
}
|
||||
|
||||
const loginInfo = uni.getStorageSync('loginInfo') || uni.getStorageSync('logInfo') || {};
|
||||
return {
|
||||
phone: String(loginInfo.mobile || loginInfo.phone || '').trim(),
|
||||
account_type: 'service_user',
|
||||
account_id: Number(loginInfo.id || uni.getStorageSync('login_id') || 0),
|
||||
token: uni.getStorageSync('token') || '',
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,13 @@ import { getClinicSalespersonMyInfo } from '@/api/clinicSalesperson.js';
|
||||
import { getPlatformAdminMyInfo } from '@/api/platformAdmin.js';
|
||||
import { getSalespersonMyInfo } from '@/api/salesperson.js';
|
||||
import { getUserInfo } from '@/api/all.js';
|
||||
import {
|
||||
resolveActiveAdminLoginMode,
|
||||
getCurrentLoginContext,
|
||||
resolveDoctorWxAuthToken,
|
||||
} from '@/utils/doctorWxAuthToken.js';
|
||||
|
||||
export { getCurrentLoginContext, resolveDoctorWxAuthToken };
|
||||
|
||||
const ADMIN_HOME = {
|
||||
platform_admin: '/subPackages/sub_platform_admin/home/index',
|
||||
@@ -11,13 +18,6 @@ const ADMIN_HOME = {
|
||||
clinic_admin: '/subPackages/sub_clinic_admin/home/index',
|
||||
};
|
||||
|
||||
const ADMIN_TOKEN_KEYS = {
|
||||
platform_admin: 'platform_admin_token',
|
||||
salesperson: 'salesperson_token',
|
||||
clinic_salesperson: 'clinic_salesperson_token',
|
||||
clinic_admin: 'clinic_admin_token',
|
||||
};
|
||||
|
||||
function applyServiceUserProfile(user) {
|
||||
if (!user || !user.id) return;
|
||||
uni.setStorageSync('loginInfo', user);
|
||||
@@ -60,18 +60,7 @@ export function clearAllLoginStorage() {
|
||||
}
|
||||
|
||||
function inferLoginMode() {
|
||||
const stored = uni.getStorageSync('loginMode');
|
||||
if (stored && ADMIN_TOKEN_KEYS[stored] && uni.getStorageSync(ADMIN_TOKEN_KEYS[stored])) {
|
||||
return stored;
|
||||
}
|
||||
const modes = ['platform_admin', 'salesperson', 'clinic_salesperson', 'clinic_admin'];
|
||||
for (let i = 0; i < modes.length; i++) {
|
||||
const mode = modes[i];
|
||||
if (uni.getStorageSync(ADMIN_TOKEN_KEYS[mode])) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
return resolveActiveAdminLoginMode();
|
||||
}
|
||||
|
||||
/** 已登录时返回应跳转的首页,否则 null */
|
||||
|
||||
Reference in New Issue
Block a user