fix(login): 密码登录补充密码非空前端校验
- doctor-login submitLogin 增加「请输入密码」前置校验, 与 getCode 一致,避免空密码误触发登录请求 - 配合后端 service_user 空密码回退 PC 医生/药师密码校验
This commit is contained in:
12
.cursor/rules/Code-Standards.md
Normal file
12
.cursor/rules/Code-Standards.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
description:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
1. 写代码的时候要补充详细的中文注释(每个方法是干嘛的,为什么要这样写),如果是工作区,则所有项目都适用该规则
|
||||
2. 注意不要生成太多的空行,上一部分代码和下一部分代码中间的空行不要大于2行
|
||||
3. 有封装好的方法、组件需要复用,不要重复造轮子
|
||||
4. 小程序端的抽屉全部需要用page-container来防止用户意外退出页面(记得使用v-if而不是v-show)
|
||||
5. 数据库的(created_at、updated_at、deleted_at)统一使用时间戳,不要使用字符串,并且我在查询器中一级格式化成字符串了,无需再次格式化
|
||||
6. 微信小程序 `.vue` 模板(会编译成 WXML)禁止使用 `??`、`?.` 等现代运算符,否则会报 `unexpected token '?'`;模板里用 `||` / 显式判断,脚本里可用 `??`/`?.`
|
||||
7. 微信小程序模板里 `:class` / `:style` 禁止写 `fn(arg)` 方法调用(如 `:class="sexClass(p)"` 会编译失败);用对象/数组字面量(如 `:class="{ male: p.sex == 1 }"`),或把结果先算进 data/computed 再绑定
|
||||
@@ -8,3 +8,5 @@ alwaysApply: true
|
||||
3. 有封装好的方法、组件需要复用,不要重复造轮子
|
||||
4. 小程序端的抽屉全部需要用page-container来防止用户意外退出页面(记得使用v-if而不是v-show)
|
||||
5. 数据库的(created_at、updated_at、deleted_at)统一使用时间戳,不要使用字符串,并且我在查询器中一级格式化成字符串了,无需再次格式化
|
||||
6. 微信小程序 `.vue` 模板(会编译成 WXML)禁止使用 `??`、`?.` 等现代运算符,否则会报 `unexpected token '?'`;模板里用 `||` / 显式判断,脚本里可用 `??`/`?.`
|
||||
7. 微信小程序模板里 `:class` / `:style` 禁止写 `fn(arg)` 方法调用(如 `:class="sexClass(p)"` 会编译失败);用对象/数组字面量(如 `:class="{ male: p.sex == 1 }"`),或把结果先算进 data/computed 再绑定
|
||||
|
||||
3
.idea/inspectionProfiles/Project_Default.xml
generated
3
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -15,7 +15,7 @@
|
||||
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myValues">
|
||||
<value>
|
||||
<list size="24">
|
||||
<list size="25">
|
||||
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
||||
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
||||
<item index="2" class="java.lang.String" itemvalue="comment" />
|
||||
@@ -40,6 +40,7 @@
|
||||
<item index="21" class="java.lang.String" itemvalue="u-subsection" />
|
||||
<item index="22" class="java.lang.String" itemvalue="u-tabbar" />
|
||||
<item index="23" class="java.lang.String" itemvalue="u-navbar" />
|
||||
<item index="24" class="java.lang.String" itemvalue="u-field" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
|
||||
@@ -5,8 +5,9 @@ const PREFIX = '/newApi/clinic-admin';
|
||||
/** 解析 Laravel jok 或 Yii 响应 */
|
||||
export function unwrapClinicRes(res) {
|
||||
if (!res) return null;
|
||||
if (res.code === 0 && res.result !== undefined) return res.result;
|
||||
if (res.errcode != null && res.errcode !== -1) return res.data;
|
||||
// code 可能是 0 / "0"
|
||||
if (Number(res.code) === 0 && res.result !== undefined) return res.result;
|
||||
if (res.errcode != null && Number(res.errcode) !== -1) return res.data;
|
||||
if (res.result !== undefined) return res.result;
|
||||
return res.data != null ? res.data : res;
|
||||
}
|
||||
@@ -20,7 +21,10 @@ function clinicRequest(options) {
|
||||
},
|
||||
}).then((res) => {
|
||||
const data = unwrapClinicRes(res);
|
||||
const ok = res && (res.code === 0 || (res.errcode != null && Number(res.errcode) !== -1));
|
||||
const ok = !!(res && (
|
||||
Number(res.code) === 0
|
||||
|| (res.errcode != null && Number(res.errcode) !== -1)
|
||||
));
|
||||
return { res, data, ok };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,110 +1,24 @@
|
||||
import { req } from '@/common/js/index.js';
|
||||
import { unwrapClinicRes } from '@/api/clinicAdmin.js';
|
||||
|
||||
const STORE_PREFIX = '/newApi/platform-admin-store';
|
||||
const CONFIG_PREFIX = '/newApi/platform-admin-salesperson-store-config';
|
||||
|
||||
function platformRequest(options) {
|
||||
return req.request({
|
||||
...options,
|
||||
header: {
|
||||
...(options.header || {}),
|
||||
_platformAdmin: '1',
|
||||
},
|
||||
}).then((res) => {
|
||||
const data = unwrapClinicRes(res);
|
||||
const ok = res && (res.code === 0 || (res.errcode != null && Number(res.errcode) !== -1));
|
||||
return { res, data, ok };
|
||||
});
|
||||
}
|
||||
|
||||
export function getPlatformStoreList(params) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/list`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function getPlatformStoreDetail(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/detail`, method: 'GET', data: { id } });
|
||||
}
|
||||
|
||||
export function updatePlatformStore(data) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update`, method: 'POST', data });
|
||||
}
|
||||
|
||||
export function deletePlatformStore(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/delete`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function openStorePcWindows(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/open-pc-windows`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function openStoreQrCode(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/open-qr-code`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreShippingFree(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update-shipping-free`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreSubscribeStatus(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update-subscribe-status`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreSeeRate(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update-see-rate`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreAllowInsurance(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update-allow-insurance-category`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreClinicType(id, clinicType) {
|
||||
return platformRequest({
|
||||
url: `${STORE_PREFIX}/update-clinic-type`,
|
||||
method: 'POST',
|
||||
data: { id, clinic_type: clinicType },
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleSalespersonSeePrice(storeId) {
|
||||
return platformRequest({
|
||||
url: `${CONFIG_PREFIX}/toggle-see-price`,
|
||||
method: 'POST',
|
||||
data: { store_id: storeId },
|
||||
});
|
||||
}
|
||||
|
||||
export function getStoreDrugsByType(storeId, type) {
|
||||
return platformRequest({
|
||||
url: `${STORE_PREFIX}/get-store-drugs-by-type`,
|
||||
method: 'GET',
|
||||
data: { store_id: storeId, type },
|
||||
});
|
||||
}
|
||||
|
||||
export function updateStoreDrugPrices(data) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update-store-drug-prices`, method: 'POST', data });
|
||||
}
|
||||
|
||||
export function getDoctorOption(storeId) {
|
||||
return platformRequest({
|
||||
url: `${STORE_PREFIX}/doctor-option`,
|
||||
method: 'GET',
|
||||
data: storeId ? { store_id: storeId } : {},
|
||||
});
|
||||
}
|
||||
|
||||
export function getInternetMedicalStoreOption() {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/internet-medical-store-option`, method: 'GET' });
|
||||
}
|
||||
|
||||
export function bindOnlineConsultation(data) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/bind-online-consultation`, method: 'POST', data });
|
||||
}
|
||||
|
||||
export function updateStoreExternalField(data) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update-store-external-field`, method: 'POST', data });
|
||||
}
|
||||
/**
|
||||
* 兼容旧引用:平台门店 API 统一走 storeManage(按 loginMode 切前缀)
|
||||
*/
|
||||
export {
|
||||
getStoreList as getPlatformStoreList,
|
||||
getStoreDetail as getPlatformStoreDetail,
|
||||
updateStore as updatePlatformStore,
|
||||
openStorePcWindows,
|
||||
openStoreQrCode,
|
||||
updateStoreShippingFree,
|
||||
updateStoreSubscribeStatus,
|
||||
updateStoreSeeRate,
|
||||
updateStoreAllowInsurance,
|
||||
updateStoreClinicType,
|
||||
toggleSalespersonSeePrice,
|
||||
getStoreDrugsByType,
|
||||
updateStoreDrugPrices,
|
||||
getDoctorOption,
|
||||
getInternetMedicalStoreOption,
|
||||
bindOnlineConsultation,
|
||||
} from './storeManage.js';
|
||||
|
||||
export const DRUG_TYPE_OPTIONS = [
|
||||
{ label: '中药', value: 1 },
|
||||
@@ -114,3 +28,13 @@ export const DRUG_TYPE_OPTIONS = [
|
||||
{ label: '非药品', value: 6 },
|
||||
{ label: '医疗器械', value: 7 },
|
||||
];
|
||||
|
||||
/** @deprecated 请使用 updateStore */
|
||||
export async function updateStoreExternalField(data) {
|
||||
const { updateStore } = await import('./storeManage.js');
|
||||
return updateStore(data);
|
||||
}
|
||||
|
||||
export async function deletePlatformStore() {
|
||||
return { ok: false, data: null, res: null };
|
||||
}
|
||||
|
||||
@@ -250,6 +250,18 @@ export function addWestPrescription(data) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 按药品查可选配送仓(药店开方选仓)
|
||||
* 走 PC 同源 admin JWT 接口;医生小程序侧若无权限需后端另开 doctor 路由,此处与 delivery-warehouse-drug 对齐
|
||||
*/
|
||||
export function getDeliveryWarehouseOptionsByDrugs(data) {
|
||||
return req.request({
|
||||
url: '/newApi/doctor-reception-wx/delivery-warehouse-options-by-drugs',
|
||||
method: 'GET',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 检查中药相冲
|
||||
export function checkChineseMedicineConflictApi(data) {
|
||||
return req.request({
|
||||
|
||||
152
api/storeManage.js
Normal file
152
api/storeManage.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 门店管理 API:按 loginMode 切换平台超管 / 业务员前缀
|
||||
* 业务员与超管复用同一套门店列表/详情页
|
||||
*/
|
||||
import { req } from '@/common/js/index.js';
|
||||
import { unwrapClinicRes } from '@/api/clinicAdmin.js';
|
||||
|
||||
function getStoreContext() {
|
||||
const mode = uni.getStorageSync('loginMode');
|
||||
if (mode === 'salesperson') {
|
||||
return {
|
||||
prefix: '/newApi/salesperson-store',
|
||||
configPrefix: '',
|
||||
headerKey: '_salesperson',
|
||||
isSalesperson: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
prefix: '/newApi/platform-admin-store',
|
||||
configPrefix: '/newApi/platform-admin-salesperson-store-config',
|
||||
headerKey: '_platformAdmin',
|
||||
isSalesperson: false,
|
||||
};
|
||||
}
|
||||
|
||||
function storeRequest(options) {
|
||||
const ctx = getStoreContext();
|
||||
return req.request({
|
||||
...options,
|
||||
header: {
|
||||
...(options.header || {}),
|
||||
[ctx.headerKey]: '1',
|
||||
},
|
||||
}).then((res) => {
|
||||
const data = unwrapClinicRes(res);
|
||||
const ok = res && (res.code === 0 || (res.errcode != null && Number(res.errcode) !== -1));
|
||||
return { res, data, ok };
|
||||
});
|
||||
}
|
||||
|
||||
/** 业务员是否允许编辑门店(系统配置开关) */
|
||||
export function getSalespersonStoreEditPermission() {
|
||||
return storeRequest({
|
||||
url: '/newApi/salesperson-store/edit-permission',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
export function getStoreList(params) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({ url: `${prefix}/list`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function getStoreDetail(id) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({ url: `${prefix}/detail`, method: 'GET', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStore(data) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({ url: `${prefix}/update`, method: 'POST', data });
|
||||
}
|
||||
|
||||
export function openStorePcWindows(id) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({ url: `${prefix}/open-pc-windows`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function openStoreQrCode(id) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({ url: `${prefix}/open-qr-code`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreShippingFree(id) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({ url: `${prefix}/update-shipping-free`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreSubscribeStatus(id) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({ url: `${prefix}/update-subscribe-status`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreSeeRate(id) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({ url: `${prefix}/update-see-rate`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreAllowInsurance(id) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({ url: `${prefix}/update-allow-insurance-category`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreClinicType(id, clinicType) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({
|
||||
url: `${prefix}/update-clinic-type`,
|
||||
method: 'POST',
|
||||
data: { id, clinic_type: clinicType },
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleSalespersonSeePrice(storeId) {
|
||||
const { configPrefix, isSalesperson } = getStoreContext();
|
||||
if (isSalesperson || !configPrefix) {
|
||||
return Promise.resolve({ ok: false, data: null, res: null });
|
||||
}
|
||||
return storeRequest({
|
||||
url: `${configPrefix}/toggle-see-price`,
|
||||
method: 'POST',
|
||||
data: { store_id: storeId },
|
||||
});
|
||||
}
|
||||
|
||||
export function getStoreDrugsByType(storeId, type) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({
|
||||
url: `${prefix}/get-store-drugs-by-type`,
|
||||
method: 'GET',
|
||||
data: { store_id: storeId, type },
|
||||
});
|
||||
}
|
||||
|
||||
export function updateStoreDrugPrices(data) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({ url: `${prefix}/update-store-drug-prices`, method: 'POST', data });
|
||||
}
|
||||
|
||||
export function getDoctorOption(storeId) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({
|
||||
url: `${prefix}/doctor-option`,
|
||||
method: 'GET',
|
||||
data: storeId ? { store_id: storeId } : {},
|
||||
});
|
||||
}
|
||||
|
||||
export function getInternetMedicalStoreOption() {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({ url: `${prefix}/internet-medical-store-option`, method: 'GET' });
|
||||
}
|
||||
|
||||
export function bindOnlineConsultation(data) {
|
||||
const { prefix } = getStoreContext();
|
||||
return storeRequest({ url: `${prefix}/bind-online-consultation`, method: 'POST', data });
|
||||
}
|
||||
|
||||
export function isSalespersonStoreMode() {
|
||||
return uni.getStorageSync('loginMode') === 'salesperson';
|
||||
}
|
||||
|
||||
export { getStoreContext };
|
||||
132
api/userPatientProfile.js
Normal file
132
api/userPatientProfile.js
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* 用户管理 / 就诊人档案 API(诊所管理员 / 平台超管)
|
||||
*/
|
||||
import { req } from '@/common/js/index.js';
|
||||
import { unwrapClinicRes } from '@/api/clinicAdmin.js';
|
||||
|
||||
function getProfileContext() {
|
||||
const mode = uni.getStorageSync('loginMode');
|
||||
if (mode === 'clinic_admin') {
|
||||
return {
|
||||
prefix: '/newApi/clinic-admin-user-patient-profile',
|
||||
headerKey: '_clinicAdmin',
|
||||
};
|
||||
}
|
||||
return {
|
||||
prefix: '/newApi/platform-admin-user-patient-profile',
|
||||
headerKey: '_platformAdmin',
|
||||
};
|
||||
}
|
||||
|
||||
function profileRequest(options) {
|
||||
const ctx = getProfileContext();
|
||||
return req.request({
|
||||
...options,
|
||||
header: {
|
||||
...(options.header || {}),
|
||||
[ctx.headerKey]: '1',
|
||||
},
|
||||
}).then((res) => {
|
||||
const data = unwrapClinicRes(res);
|
||||
// code 可能是数字 0 或字符串 "0";勿用 === 导致 ok 为 false、列表被丢弃
|
||||
const ok = !!(res && (
|
||||
Number(res.code) === 0
|
||||
|| (res.errcode != null && Number(res.errcode) !== -1)
|
||||
));
|
||||
return { res, data, ok };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 profile 接口包装中取出列表 items(兼容 result/data/items 多层)
|
||||
*/
|
||||
export function pickProfileItems(wrap) {
|
||||
const raw = (wrap && wrap.data) || null;
|
||||
const body = wrap && wrap.res;
|
||||
const fromBody = body && (body.result || body.data);
|
||||
let payload = raw;
|
||||
// unwrap 后若仍带着 code/result,再剥一层
|
||||
if (payload && payload.items == null && payload.result != null) {
|
||||
payload = payload.result;
|
||||
}
|
||||
if (payload == null && fromBody) {
|
||||
payload = fromBody;
|
||||
}
|
||||
if (Array.isArray(payload)) {
|
||||
return payload;
|
||||
}
|
||||
if (payload && Array.isArray(payload.items)) {
|
||||
return payload.items;
|
||||
}
|
||||
if (payload && Array.isArray(payload.list)) {
|
||||
return payload.list;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function getUserPatientList(params) {
|
||||
const { prefix } = getProfileContext();
|
||||
return profileRequest({ url: `${prefix}/list`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
/** 会员管理:微信用户分页列表 */
|
||||
export function getWxUserList(params) {
|
||||
const { prefix } = getProfileContext();
|
||||
return profileRequest({ url: `${prefix}/user-list`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
/** 某微信用户下的就诊人列表 */
|
||||
export function getPatientListByUser(userId) {
|
||||
const { prefix } = getProfileContext();
|
||||
return profileRequest({
|
||||
url: `${prefix}/patient-list-by-user`,
|
||||
method: 'GET',
|
||||
data: { user_id: userId },
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserPatientDetail(upId) {
|
||||
const { prefix } = getProfileContext();
|
||||
return profileRequest({
|
||||
url: `${prefix}/detail`,
|
||||
method: 'GET',
|
||||
data: { up_id: upId },
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserPatientRegisterList(upId, params) {
|
||||
const { prefix } = getProfileContext();
|
||||
return profileRequest({
|
||||
url: `${prefix}/register-list`,
|
||||
method: 'GET',
|
||||
data: { up_id: upId, ...params },
|
||||
});
|
||||
}
|
||||
|
||||
/** 就诊人单次挂号详情(基本信息 + 选药 + 回答记录) */
|
||||
export function getUserPatientRegisterDetail(registerId) {
|
||||
const { prefix } = getProfileContext();
|
||||
return profileRequest({
|
||||
url: `${prefix}/register-detail`,
|
||||
method: 'GET',
|
||||
data: { register_id: registerId },
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserPatientPrescriptionList(upId, params) {
|
||||
const { prefix } = getProfileContext();
|
||||
return profileRequest({
|
||||
url: `${prefix}/prescription-list`,
|
||||
method: 'GET',
|
||||
data: { up_id: upId, ...params },
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserPatientOrderList(upId, params) {
|
||||
const { prefix } = getProfileContext();
|
||||
return profileRequest({
|
||||
url: `${prefix}/order-list`,
|
||||
method: 'GET',
|
||||
data: { up_id: upId, ...params },
|
||||
});
|
||||
}
|
||||
@@ -12,6 +12,7 @@ export const config = {
|
||||
|
||||
const arr = [
|
||||
'mobile',
|
||||
'call_mobile',
|
||||
'express_mobile',
|
||||
'doctor',
|
||||
'accept_tel',
|
||||
@@ -23,6 +24,7 @@ const arr = [
|
||||
]
|
||||
|
||||
// 敏感数据(解密后做脱敏;需与下方 switch 分支一致)
|
||||
// 注意:call_mobile 只解密不脱敏,供回访拨号使用
|
||||
const sensitiveData = [
|
||||
'id_card',
|
||||
'idcard',
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
|
||||
<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' }"
|
||||
@@ -83,7 +85,7 @@
|
||||
:loading="wxLoading"
|
||||
:custom-style="{ background: '#07C160', color: '#fff' }"
|
||||
>
|
||||
微信一键登录
|
||||
一键登录
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -221,6 +223,10 @@ export default {
|
||||
this.$toast('请输入手机号');
|
||||
return;
|
||||
}
|
||||
if (!this.form.password) {
|
||||
this.$toast('请输入密码');
|
||||
return;
|
||||
}
|
||||
if (!this.smsCode) {
|
||||
this.$toast('请输入验证码');
|
||||
return;
|
||||
|
||||
@@ -13,10 +13,19 @@ const DEV = {
|
||||
wsUrl: 'ws://127.0.0.1:12080/ws',
|
||||
};
|
||||
|
||||
// const PROD = {
|
||||
// legacyApiBase: 'https://app.xiaokang88.com/service/v1/',
|
||||
// newApiBase: 'https://api.xiaokang88.com/api/doctor/',
|
||||
// wsUrl: 'wss://api.ws.g.xiaokang88.com/ws',
|
||||
// };
|
||||
|
||||
// const TEST = {
|
||||
const PROD = {
|
||||
legacyApiBase: 'https://app.xiaokang88.com/service/v1/',
|
||||
newApiBase: 'https://api.xiaokang88.com/api/doctor/',
|
||||
wsUrl: 'wss://api.ws.g.xiaokang88.com/ws',
|
||||
legacyApiBase: 'https://xk.test.saas.api-yii.nailaoyun.cn/service/v1/',
|
||||
newApiBase: 'https://xk.test.saas.api.nailaoyun.cn/api/doctor/',
|
||||
// wsUrl: 'wss://api.ws.g.xiaokang88.com/ws',
|
||||
wsUrl: 'wss://xk.ws.nailaoyun.cn/ws',
|
||||
// wsUrl: '',
|
||||
};
|
||||
|
||||
export function isDoctorWxDev() {
|
||||
|
||||
17
pages.json
17
pages.json
@@ -441,7 +441,16 @@
|
||||
},
|
||||
{
|
||||
"path": "order/detail",
|
||||
"style": { "navigationBarTitleText": "订单详情", "navigationStyle": "custom" }
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情",
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"order-price-percent-adjust-popup": "/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup"
|
||||
},
|
||||
"componentPlaceholder": {
|
||||
"order-price-percent-adjust-popup": "view"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "order/trace/index",
|
||||
@@ -471,7 +480,11 @@
|
||||
{ "path": "withdrawal/settlement/detail", "style": { "navigationBarTitleText": "结算明细", "navigationStyle": "custom" } },
|
||||
{ "path": "withdrawal/account-change/index", "style": { "navigationBarTitleText": "资金变动", "navigationStyle": "custom" } },
|
||||
{ "path": "warehouse/index", "style": { "navigationBarTitleText": "仓库管理", "navigationStyle": "custom" } },
|
||||
{ "path": "warehouse/detail", "style": { "navigationBarTitleText": "药品详情", "navigationStyle": "custom" } }
|
||||
{ "path": "warehouse/detail", "style": { "navigationBarTitleText": "药品详情", "navigationStyle": "custom" } },
|
||||
{ "path": "user-patient/index", "style": { "navigationBarTitleText": "会员管理", "navigationStyle": "custom" } },
|
||||
{ "path": "user-patient/patients", "style": { "navigationBarTitleText": "就诊人列表", "navigationStyle": "custom" } },
|
||||
{ "path": "user-patient/detail", "style": { "navigationBarTitleText": "就诊人详情", "navigationStyle": "custom" } },
|
||||
{ "path": "user-patient/register-detail", "style": { "navigationBarTitleText": "挂号详情", "navigationStyle": "custom" } }
|
||||
]
|
||||
}, {
|
||||
"root": "subPackages/sub_platform_admin",
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<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>
|
||||
<my-service v-if="parseInt(identity)>2" ref="myService" :info="userInfo" :avatar="getAvatar"></my-service>
|
||||
<d-tabbar></d-tabbar>
|
||||
</view>
|
||||
</template>
|
||||
@@ -183,6 +183,9 @@
|
||||
if (parseInt(this.identity) === 2 && this.$refs.myPharmacist) {
|
||||
this.$refs.myPharmacist.refreshWxBind()
|
||||
}
|
||||
if (parseInt(this.identity) > 2 && this.$refs.myService) {
|
||||
this.$refs.myService.refreshWxBind()
|
||||
}
|
||||
},
|
||||
async getInfo() {
|
||||
let res = {}
|
||||
|
||||
@@ -18,31 +18,50 @@
|
||||
</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"
|
||||
:name="require('../../static/image/grzl.png')" label="个人资料" size="30"></u-icon>
|
||||
<!-- 菜单卡:绑定微信 / 个人资料 / 设置 / 切换账号(对齐医生「我的」) -->
|
||||
<view class="flex-col m-t-12 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"
|
||||
@click="$go('../../subPackages/sub_pharmacist/pharmacist_info?avatar='+avatar+'&info='+JSON.stringify(info))">
|
||||
<view class="flex-row flex-ali-center">
|
||||
<u-icon margin-left="20" label-size="32" label-color="#31353D"
|
||||
:name="require('../../static/image/grzl.png')" label="个人资料" size="30"></u-icon>
|
||||
</view>
|
||||
<u-icon name="arrow-right" size="32" color="#A7ABB0"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="p-32 white m-t-2" @click="$go('../../subPackages/sub_pharmacist/pharmacist_setInfo')">
|
||||
<view class="flex-row flex-jus-sp flex-ali-center">
|
||||
<u-icon margin-left="20" label-size="32" label-color="#31353D"
|
||||
:name="require('../../static/image/set.png')" label="设置" size="30"></u-icon>
|
||||
<view class="flex-row bottom-border flex-jus-sp p-col-32 flex-ali-center"
|
||||
@click="$go('../../subPackages/sub_pharmacist/pharmacist_setInfo')">
|
||||
<view class="flex-row flex-ali-center">
|
||||
<u-icon margin-left="20" label-size="32" label-color="#31353D"
|
||||
:name="require('../../static/image/set.png')" label="设置" size="30"></u-icon>
|
||||
</view>
|
||||
<u-icon name="arrow-right" size="32" color="#A7ABB0"></u-icon>
|
||||
</view>
|
||||
<view class="account-switch-wrap">
|
||||
<account-switch-entry />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 独立绿色退出按钮,不再藏在二级设置页 -->
|
||||
<view class="logout-wrap p-row-32 m-t-12">
|
||||
<u-button :throttle-time="0" shape="circle"
|
||||
:custom-style="{ backgroundColor: '#6ACDBB', color: '#fff', height: '96rpx' }"
|
||||
@click="logout">退出登录</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 药师「我的」:资料 + 绑定微信 + 切换账号 + 退出登录(对齐医生主页结构)
|
||||
*/
|
||||
import { logout } from '@/api/all.js'
|
||||
import WxBindEntry from '@/components/wx-bind-entry/wx-bind-entry.vue'
|
||||
import AccountSwitchEntry from '@/components/account-switch/account-switch-entry.vue'
|
||||
export default {
|
||||
components: { WxBindEntry },
|
||||
components: { WxBindEntry, AccountSwitchEntry },
|
||||
props:{
|
||||
avatar:{
|
||||
type:String,
|
||||
@@ -56,12 +75,7 @@
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
|
||||
return {};
|
||||
},
|
||||
mounted() {
|
||||
this.refreshWxBind()
|
||||
@@ -72,6 +86,18 @@
|
||||
this.$refs.wxBindEntry.refresh()
|
||||
}
|
||||
},
|
||||
/** 退出登录:清本地会话并回登录页 */
|
||||
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);
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -85,4 +111,13 @@
|
||||
background-color: #F9FAFB;
|
||||
padding: 16rpx;
|
||||
}
|
||||
.wx-bind-wrap {
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.account-switch-wrap {
|
||||
padding-top: 8rpx;
|
||||
}
|
||||
.logout-wrap {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,17 +11,19 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 菜单卡:绑定微信 / 业务项 / 设置 / 切换账号 -->
|
||||
<view class="p-row-32 b-r-8 white m-t-2">
|
||||
<view class="p-col-32" @click="$go('../../subPackages/sub_my/my_commonReply/index')">
|
||||
<view class="wx-bind-wrap p-col-24">
|
||||
<wx-bind-entry ref="wxBindEntry" />
|
||||
</view>
|
||||
<view class="p-col-32 bottom-border" @click="$go('../../subPackages/sub_my/my_commonReply/index')">
|
||||
<view class="flex-row flex-jus-sp flex-ali-center">
|
||||
<u-icon margin-left="20" label-size="32" label-color="#31353D" name="chat" label="快捷回复" size="40"
|
||||
color="#6C7380"></u-icon>
|
||||
<u-icon name="arrow-right" size="32" color="#A7ABB0"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="p-row-32 b-r-8 white m-t-2">
|
||||
<view class="p-col-32 flex-jus-sp flex-row flex-ali-center bottom-border" @click="$go()">
|
||||
<view class="p-col-32 bottom-border flex-jus-sp flex-row flex-ali-center" @click="$go()">
|
||||
<u-icon margin-left="20" label-size="32" label-color="#31353D" name="kefu-ermai" label="联系客服" size="40"
|
||||
color="#6C7380"></u-icon>
|
||||
<view class="flex-row flex-ali-center">
|
||||
@@ -31,19 +33,35 @@
|
||||
<u-icon name="arrow-right" size="32" color="#A7ABB0"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="p-col-32" @click="$go('../../subPackages/sub_pharmacist/pharmacist_setInfo')">
|
||||
<view class="p-col-32 bottom-border" @click="$go('../../subPackages/sub_pharmacist/pharmacist_setInfo')">
|
||||
<view class="flex-row flex-jus-sp flex-ali-center">
|
||||
<u-icon margin-left="20" label-size="32" label-color="#31353D" name="setting" label="设置" size="40"
|
||||
color="#6C7380"></u-icon>
|
||||
<u-icon name="arrow-right" size="32" color="#A7ABB0"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="account-switch-wrap p-col-8">
|
||||
<account-switch-entry />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 独立绿色退出按钮 -->
|
||||
<view class="logout-wrap p-row-32 m-t-12">
|
||||
<u-button :throttle-time="0" shape="circle"
|
||||
:custom-style="{ backgroundColor: '#6ACDBB', color: '#fff', height: '96rpx' }"
|
||||
@click="logout">退出登录</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 导医/客服「我的」:绑定微信 + 切换账号 + 退出登录上提到主页(对齐医生)
|
||||
*/
|
||||
import { logout } from '@/api/all.js'
|
||||
import WxBindEntry from '@/components/wx-bind-entry/wx-bind-entry.vue'
|
||||
import AccountSwitchEntry from '@/components/account-switch/account-switch-entry.vue'
|
||||
export default {
|
||||
components: { WxBindEntry, AccountSwitchEntry },
|
||||
props: {
|
||||
avatar: {
|
||||
type: String,
|
||||
@@ -60,12 +78,26 @@
|
||||
return {
|
||||
identity: (uni.getStorageSync('userInfo') || {}).role || 3,
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 父页 onShow 时刷新绑定微信入口展示状态 */
|
||||
refreshWxBind() {
|
||||
const ref = this.$refs.wxBindEntry
|
||||
if (ref && typeof ref.refresh === 'function') {
|
||||
ref.refresh()
|
||||
}
|
||||
},
|
||||
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);
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
options: {
|
||||
styleIsolation: 'shared'
|
||||
@@ -78,8 +110,16 @@
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
::v-deep .u-badge {
|
||||
display: flex !important;
|
||||
position: relative;
|
||||
.badge {
|
||||
background-color: #F7F8FA;
|
||||
border-radius: 32rpx;
|
||||
width: 64rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
.wx-bind-wrap {
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.logout-wrap {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -214,6 +214,8 @@ export function mapOrderListRow(item) {
|
||||
deliveryText: deliveryMethodText(item.delivery_method),
|
||||
deliveryClass: 'tag-delivery',
|
||||
userNickname: user.nickname || '-',
|
||||
patientName: (item.user_patient && item.user_patient.name) || item.patient || '-',
|
||||
upId: Number(item.up_id || (item.user_patient && item.user_patient.id) || 0),
|
||||
payTime: formatDateTime(item.pay_time),
|
||||
createdAt: formatDateTime(item.created_at),
|
||||
storeName: store.name || '-',
|
||||
@@ -346,7 +348,12 @@ export function mapPrescriptionDetailView(item) {
|
||||
const name = d.name || d.drug_name || '—'
|
||||
const num = d.number != null ? d.number : ''
|
||||
const way = (d.use_way && d.use_way.name) || d.useWay || '煎服'
|
||||
return { name, qty: num ? num + ' /g' : '', usage: way }
|
||||
return {
|
||||
name,
|
||||
qty: num ? num + ' /g' : '',
|
||||
usage: way,
|
||||
specification: d.specification || '',
|
||||
}
|
||||
})
|
||||
: []
|
||||
recipes.push({
|
||||
@@ -369,6 +376,7 @@ export function mapPrescriptionDetailView(item) {
|
||||
name: d.name || d.drug_name || '—',
|
||||
qty: (d.number != null ? d.number : '') + unitName,
|
||||
usage: d.useWay || (d.use_way && d.use_way.name) || '',
|
||||
specification: d.specification || '',
|
||||
}],
|
||||
usage: '',
|
||||
process: '',
|
||||
|
||||
105
subPackages/sub_business_shared/components/UserPatientCell.vue
Normal file
105
subPackages/sub_business_shared/components/UserPatientCell.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<!-- 就诊人/微信用户摘要单元格,点击打开患者视角详情 -->
|
||||
<view class="up-cell" @click.stop="handleOpen">
|
||||
<view class="up-row">
|
||||
<image class="avatar" :src="userAvatar" mode="aspectFill" />
|
||||
<view class="text">
|
||||
<text class="lbl">微信用户</text>
|
||||
<text class="val">{{ userNickname }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="up-row">
|
||||
<view class="avatar placeholder" />
|
||||
<view class="text">
|
||||
<text class="lbl">就诊人</text>
|
||||
<text class="val link">{{ patientName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 列表内紧凑展示微信用户 + 就诊人,点击抛出 open 事件供父级打开详情抽屉
|
||||
*/
|
||||
export default {
|
||||
name: 'UserPatientCell',
|
||||
props: {
|
||||
/** 订单行或用户管理行:需含 user / user_patient / up_id / patient */
|
||||
row: { type: Object, default: () => ({}) },
|
||||
},
|
||||
emits: ['open'],
|
||||
computed: {
|
||||
user() {
|
||||
return this.row.user || {};
|
||||
},
|
||||
patient() {
|
||||
return this.row.user_patient || this.row.userPatient || this.row.patient || {};
|
||||
},
|
||||
upId() {
|
||||
if (typeof this.patient === 'object' && this.patient) {
|
||||
return Number(this.patient.id || this.row.up_id || 0);
|
||||
}
|
||||
return Number(this.row.up_id || 0);
|
||||
},
|
||||
userNickname() {
|
||||
return this.user.nickname || '—';
|
||||
},
|
||||
userAvatar() {
|
||||
return this.user.avatarurl || '/static/image/default-avatar.png';
|
||||
},
|
||||
patientName() {
|
||||
if (typeof this.patient === 'string') return this.patient || '—';
|
||||
return (this.patient && this.patient.name) || this.row.patient || '—';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleOpen() {
|
||||
if (!this.upId) {
|
||||
uni.showToast({ title: '缺少就诊人信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
this.$emit('open', { upId: this.upId, patientName: this.patientName });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.up-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
.up-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
.avatar {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
background: #f3f4f6;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.avatar.placeholder {
|
||||
background: #e5e7eb;
|
||||
}
|
||||
.text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
.lbl {
|
||||
font-size: 20rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
.val {
|
||||
font-size: 24rpx;
|
||||
color: #374151;
|
||||
}
|
||||
.val.link {
|
||||
color: #6acdbb;
|
||||
}
|
||||
</style>
|
||||
556
subPackages/sub_business_shared/components/UserPatientDetail.vue
Normal file
556
subPackages/sub_business_shared/components/UserPatientDetail.vue
Normal file
@@ -0,0 +1,556 @@
|
||||
<template>
|
||||
<!-- 患者视角详情:page-container 抽屉,避免误退出页面;用 v-if 控制挂载 -->
|
||||
<page-container
|
||||
v-if="visible"
|
||||
:show="visible"
|
||||
:overlay="true"
|
||||
position="bottom"
|
||||
:round="true"
|
||||
@beforeleave="onBeforeLeave"
|
||||
@clickoverlay="close"
|
||||
>
|
||||
<view class="drawer">
|
||||
<view class="drawer-head">
|
||||
<text class="title">患者视角 · {{ patientName || '就诊人' }}</text>
|
||||
<text class="close" @click="close">关闭</text>
|
||||
</view>
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<template v-else>
|
||||
<!-- 资料卡:对齐就诊人详情页 -->
|
||||
<view class="profile-card">
|
||||
<view class="profile-row">
|
||||
<image
|
||||
class="avatar"
|
||||
:src="profile.user.avatarurl || '/static/image/default-avatar.png'"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="meta">
|
||||
<text class="name">{{ profile.user.nickname || '微信用户' }}</text>
|
||||
<text class="sub">
|
||||
ID {{ profile.user.id || '—' }}
|
||||
<template v-if="profile.user.mobile"> · {{ profile.user.mobile }}</template>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="patient-block">
|
||||
<view class="patient-head">
|
||||
<view class="patient-head-left">
|
||||
<text class="p-name">{{ profile.patient.name || '—' }}</text>
|
||||
<text v-if="sexText !== '—'" class="sex-tag" :class="sexTagClass">{{ sexText }}</text>
|
||||
</view>
|
||||
<!-- 回访:拨打就诊人/用户手机 -->
|
||||
<view class="callback-btn" @click.stop="onCallbackCall">
|
||||
<text class="callback-btn-text">回访</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="p-sub">
|
||||
{{ profile.patient.age || '—' }}岁
|
||||
<template v-if="profile.patient.mobile"> · {{ profile.patient.mobile }}</template>
|
||||
</text>
|
||||
<text v-if="profile.patient.id_card" class="p-sub">身份证 {{ profile.patient.id_card }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- Tab:下划线选中态 -->
|
||||
<view class="tabs">
|
||||
<view
|
||||
v-for="(t, idx) in tabs"
|
||||
:key="t.key"
|
||||
class="tab"
|
||||
:class="{ on: tabIndex === idx }"
|
||||
@click="selectTab(idx)"
|
||||
>
|
||||
<text class="tab-text">{{ t.label }}</text>
|
||||
<view v-if="tabIndex === idx" class="tab-bar" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 四页左右滑:资料 / 挂号 / 处方 / 订单 -->
|
||||
<swiper
|
||||
class="tab-swiper"
|
||||
:style="{ height: swiperHeightPx + 'px' }"
|
||||
:current="tabIndex"
|
||||
:duration="200"
|
||||
@change="onTabSwiperChange"
|
||||
>
|
||||
<swiper-item v-for="t in tabs" :key="'sw-' + t.key">
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="tab-scroll"
|
||||
:style="{ height: swiperHeightPx + 'px' }"
|
||||
>
|
||||
<!-- 资料:不绑 tabLoading,避免切列表时闪加载中 -->
|
||||
<view v-if="t.key === 'info'" class="health-card">
|
||||
<text class="section-title">健康信息</text>
|
||||
<template v-if="health">
|
||||
<view class="info-row">
|
||||
<text class="info-label">既往史</text>
|
||||
<text class="info-value">{{ historyText(health.person_status, health.person_history) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">过敏史</text>
|
||||
<text
|
||||
class="info-value"
|
||||
:class="{ danger: Number(health.allergic_status) !== 0 }"
|
||||
>{{ historyText(health.allergic_status, health.allergic_history) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">家族遗传史</text>
|
||||
<text class="info-value">{{ historyText(health.family_status, health.family_history) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">肝功能异常</text>
|
||||
<text
|
||||
class="info-value"
|
||||
:class="{ danger: Number(health.liver_function) !== 0 }"
|
||||
>{{ functionText(health.liver_function, health.liver_index) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">肾功能异常</text>
|
||||
<text
|
||||
class="info-value"
|
||||
:class="{ danger: Number(health.renal_function) !== 0 }"
|
||||
>{{ functionText(health.renal_function, health.renal_index) }}</text>
|
||||
</view>
|
||||
</template>
|
||||
<view v-else class="empty-inline">暂无健康问诊记录</view>
|
||||
</view>
|
||||
<template v-else>
|
||||
<view v-if="tabLoading && activeTab === t.key" class="empty">加载中...</view>
|
||||
<template v-else>
|
||||
<view v-if="!listForTab(t.key).length" class="empty">暂无记录</view>
|
||||
<view
|
||||
v-for="item in listForTab(t.key)"
|
||||
:key="t.key + '-' + item.id"
|
||||
class="record-card"
|
||||
@click="onRecordClick(item, t.key)"
|
||||
>
|
||||
<view class="record-main">
|
||||
<template v-if="t.key === 'register'">
|
||||
<text class="r-title">{{ item.order_no || item.order_number || '—' }}</text>
|
||||
<text class="r-sub">{{ item.store || '—' }} · {{ item.doctor || '—' }}</text>
|
||||
<text class="r-sub">{{ item.status_text || '—' }} · {{ item.created_at || '' }}</text>
|
||||
</template>
|
||||
<template v-else-if="t.key === 'prescription'">
|
||||
<text class="r-title">{{ item.prescription_no || '—' }}</text>
|
||||
<text class="r-sub">{{ item.store || '—' }} · {{ item.doctor || '—' }}</text>
|
||||
<text class="r-sub">{{ item.clinical_diagnose || '—' }} · {{ item.created_at || '' }}</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="r-title">{{ item.order_no || '—' }}</text>
|
||||
<text class="r-sub">{{ item.store || '—' }} · ¥{{ item.total_pay_price || '—' }}</text>
|
||||
<text class="r-sub">{{ item.created_at || '' }}</text>
|
||||
</template>
|
||||
</view>
|
||||
<text v-if="t.key === 'register'" class="arrow">›</text>
|
||||
</view>
|
||||
</template>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</template>
|
||||
</view>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 患者视角详情抽屉:微信用户/就诊人信息 + 挂号/处方/订单
|
||||
* 对齐详情页展示与回访;四 Tab 支持点击与左右滑动(懒加载)
|
||||
*/
|
||||
import {
|
||||
getUserPatientDetail,
|
||||
getUserPatientRegisterList,
|
||||
getUserPatientPrescriptionList,
|
||||
getUserPatientOrderList,
|
||||
} from '@/api/userPatientProfile.js';
|
||||
|
||||
/** 抽屉左右滑提示只展示一次 */
|
||||
const SWIPE_TIP_STORAGE_KEY = 'user_patient_drawer_swipe_tip';
|
||||
|
||||
export default {
|
||||
name: 'UserPatientDetail',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
upId: { type: [Number, String], default: 0 },
|
||||
patientName: { type: String, default: '' },
|
||||
},
|
||||
emits: ['close'],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
tabLoading: false,
|
||||
activeTab: 'info',
|
||||
tabIndex: 0,
|
||||
tabSwiperChanging: false,
|
||||
swiperHeightPx: 320,
|
||||
tabs: [
|
||||
{ key: 'info', label: '资料' },
|
||||
{ key: 'register', label: '挂号' },
|
||||
{ key: 'prescription', label: '处方' },
|
||||
{ key: 'order', label: '订单' },
|
||||
],
|
||||
profile: {
|
||||
user: {},
|
||||
patient: {},
|
||||
},
|
||||
health: null,
|
||||
registerList: [],
|
||||
prescriptionList: [],
|
||||
orderList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
sexText() {
|
||||
const sex = this.profile.patient && this.profile.patient.sex;
|
||||
if (Number(sex) === 1) return '男';
|
||||
if (Number(sex) === 2) return '女';
|
||||
return '—';
|
||||
},
|
||||
sexTagClass() {
|
||||
const sex = this.profile.patient && this.profile.patient.sex;
|
||||
if (Number(sex) === 1) return 'male';
|
||||
if (Number(sex) === 2) return 'female';
|
||||
return '';
|
||||
},
|
||||
/**
|
||||
* 回访拨号:只用就诊人 call_mobile(拦截器只解密不脱敏)
|
||||
* 不用脱敏后的 mobile,也不回退微信用户号
|
||||
*/
|
||||
callbackPhone() {
|
||||
const patient = this.profile.patient || {};
|
||||
return String(patient.call_mobile || '').trim();
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
/**
|
||||
* v-if 挂载时 visible 已是 true,必须 immediate,否则 loadDetail 永不触发
|
||||
*/
|
||||
visible: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
if (val && this.upId) {
|
||||
this.openAndLoad();
|
||||
}
|
||||
},
|
||||
},
|
||||
/** 抽屉打开时切换不同就诊人,重新拉详情 */
|
||||
upId(val) {
|
||||
if (this.visible && val) {
|
||||
this.openAndLoad();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit('close');
|
||||
},
|
||||
onBeforeLeave() {
|
||||
this.close();
|
||||
},
|
||||
/**
|
||||
* 打开抽屉:重置 Tab/列表并请求就诊人详情
|
||||
*/
|
||||
openAndLoad() {
|
||||
this.activeTab = 'info';
|
||||
this.tabIndex = 0;
|
||||
this.health = null;
|
||||
this.registerList = [];
|
||||
this.prescriptionList = [];
|
||||
this.orderList = [];
|
||||
this.profile = { user: {}, patient: {} };
|
||||
this.calcSwiperHeight();
|
||||
this.loadDetail();
|
||||
},
|
||||
/**
|
||||
* 抽屉内 swiper 高度:80vh − 头 − 资料卡 − Tab(保证横向滑动可用)
|
||||
*/
|
||||
calcSwiperHeight() {
|
||||
try {
|
||||
const sys = uni.getSystemInfoSync() || {};
|
||||
const winH = Number(sys.windowHeight) || 600;
|
||||
const drawerH = Math.floor(winH * 0.8);
|
||||
const rpx = (Number(sys.windowWidth) || 375) / 750;
|
||||
const headH = Math.ceil(100 * rpx);
|
||||
const profileH = Math.ceil(280 * rpx);
|
||||
const tabsH = Math.ceil(90 * rpx);
|
||||
const pad = Math.ceil(24 * rpx);
|
||||
const h = drawerH - headH - profileH - tabsH - pad;
|
||||
this.swiperHeightPx = h > 160 ? h : 160;
|
||||
} catch (e) {
|
||||
this.swiperHeightPx = 320;
|
||||
}
|
||||
},
|
||||
/** 按 Tab key 取对应列表(四页同时渲染) */
|
||||
listForTab(key) {
|
||||
if (key === 'register') return this.registerList;
|
||||
if (key === 'prescription') return this.prescriptionList;
|
||||
if (key === 'order') return this.orderList;
|
||||
return [];
|
||||
},
|
||||
/** 既往/过敏/家族:0 无,否则展示 history */
|
||||
historyText(status, history) {
|
||||
if (Number(status) === 0) return '无';
|
||||
const t = String(history || '').trim();
|
||||
return t || '有';
|
||||
},
|
||||
/** 肝/肾功能:0 正常,异常时附带指标文案 */
|
||||
functionText(flag, indexText) {
|
||||
if (Number(flag) === 0) return '正常';
|
||||
const t = String(indexText || '').trim();
|
||||
return t ? `异常 · ${t}` : '异常';
|
||||
},
|
||||
/**
|
||||
* 回访:调起系统拨号;无号 toast,按钮始终显示
|
||||
*/
|
||||
onCallbackCall() {
|
||||
const phone = this.callbackPhone;
|
||||
if (!phone) {
|
||||
uni.showToast({ title: '暂无手机号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: phone,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 首次打开抽屉短暂提示可左右滑动
|
||||
*/
|
||||
maybeShowSwipeTip() {
|
||||
let shown = '';
|
||||
try {
|
||||
shown = uni.getStorageSync(SWIPE_TIP_STORAGE_KEY);
|
||||
} catch (e) {
|
||||
shown = '';
|
||||
}
|
||||
if (shown) return;
|
||||
uni.showToast({
|
||||
title: '左右滑动可切换标签',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
try {
|
||||
uni.setStorageSync(SWIPE_TIP_STORAGE_KEY, '1');
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
async loadDetail() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const wrap = await getUserPatientDetail(this.upId);
|
||||
if (wrap && wrap.ok && wrap.data) {
|
||||
this.profile = {
|
||||
user: wrap.data.user || {},
|
||||
patient: wrap.data.patient || {},
|
||||
};
|
||||
this.health = wrap.data.health_inquiry || null;
|
||||
}
|
||||
this.calcSwiperHeight();
|
||||
this.$nextTick(() => {
|
||||
this.maybeShowSwipeTip();
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
/** 点击顶部 Tab */
|
||||
selectTab(index) {
|
||||
if (this.tabSwiperChanging) return;
|
||||
const tab = this.tabs[index];
|
||||
if (!tab) return;
|
||||
this.tabIndex = index;
|
||||
this.switchTab(tab.key);
|
||||
},
|
||||
/** swiper 滑动切换 */
|
||||
onTabSwiperChange(e) {
|
||||
const index = e && e.detail ? e.detail.current : 0;
|
||||
const tab = this.tabs[index];
|
||||
if (!tab || tab.key === this.activeTab) {
|
||||
this.tabIndex = index;
|
||||
return;
|
||||
}
|
||||
this.tabSwiperChanging = true;
|
||||
this.tabIndex = index;
|
||||
this.switchTab(tab.key);
|
||||
this.$nextTick(() => {
|
||||
this.tabSwiperChanging = false;
|
||||
});
|
||||
},
|
||||
/** 切换 Tab 并懒加载对应列表 */
|
||||
async switchTab(key) {
|
||||
this.activeTab = key;
|
||||
const idx = this.tabs.findIndex((t) => t.key === key);
|
||||
if (idx >= 0 && this.tabIndex !== idx) {
|
||||
this.tabIndex = idx;
|
||||
}
|
||||
if (key === 'info') return;
|
||||
if (key === 'register' && !this.registerList.length) {
|
||||
await this.loadRegisters();
|
||||
} else if (key === 'prescription' && !this.prescriptionList.length) {
|
||||
await this.loadPrescriptions();
|
||||
} else if (key === 'order' && !this.orderList.length) {
|
||||
await this.loadOrders();
|
||||
}
|
||||
},
|
||||
async loadRegisters() {
|
||||
this.tabLoading = true;
|
||||
try {
|
||||
const wrap = await getUserPatientRegisterList(this.upId);
|
||||
this.registerList = (wrap && wrap.ok && wrap.data && wrap.data.items) || [];
|
||||
} finally {
|
||||
this.tabLoading = false;
|
||||
}
|
||||
},
|
||||
async loadPrescriptions() {
|
||||
this.tabLoading = true;
|
||||
try {
|
||||
const wrap = await getUserPatientPrescriptionList(this.upId);
|
||||
this.prescriptionList = (wrap && wrap.ok && wrap.data && wrap.data.items) || [];
|
||||
} finally {
|
||||
this.tabLoading = false;
|
||||
}
|
||||
},
|
||||
async loadOrders() {
|
||||
this.tabLoading = true;
|
||||
try {
|
||||
const wrap = await getUserPatientOrderList(this.upId);
|
||||
this.orderList = (wrap && wrap.ok && wrap.data && wrap.data.items) || [];
|
||||
} finally {
|
||||
this.tabLoading = false;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 挂号行:关抽屉后进挂号详情(与详情页一致)
|
||||
*/
|
||||
onRecordClick(item, tabKey) {
|
||||
if (tabKey !== 'register') return;
|
||||
const rid = Number((item && item.id) || 0);
|
||||
if (!rid) {
|
||||
uni.showToast({ title: '缺少挂号信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
uni.setStorageSync('user_patient_register_detail', item || {});
|
||||
} catch (e) { /* ignore */ }
|
||||
this.close();
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_business_shared/user-patient/register-detail?id=${rid}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drawer {
|
||||
height: 80vh;
|
||||
background: #f9fafb;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.drawer-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 28rpx 32rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.title { font-size: 30rpx; font-weight: 600; color: #111827; }
|
||||
.close { font-size: 26rpx; color: #6acdbb; }
|
||||
.profile-card {
|
||||
margin: 20rpx 24rpx 0;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.profile-row { display: flex; align-items: center; gap: 16rpx; margin-bottom: 16rpx; }
|
||||
.avatar { width: 80rpx; height: 80rpx; border-radius: 50%; background: #e5e7eb; }
|
||||
.meta { display: flex; flex-direction: column; min-width: 0; }
|
||||
.name { font-size: 28rpx; font-weight: 600; color: #1f2937; }
|
||||
.sub { font-size: 22rpx; color: #9ca3af; margin-top: 4rpx; }
|
||||
.patient-block { padding-top: 12rpx; border-top: 1rpx solid #f3f4f6; }
|
||||
.patient-head {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 16rpx;
|
||||
}
|
||||
.patient-head-left { display: flex; align-items: center; gap: 12rpx; min-width: 0; flex: 1; }
|
||||
.p-name { font-size: 28rpx; font-weight: 600; color: #111827; }
|
||||
.sex-tag {
|
||||
font-size: 20rpx; padding: 2rpx 12rpx; border-radius: 8rpx;
|
||||
background: #e8f5f2; color: #2a9d8f;
|
||||
}
|
||||
.sex-tag.male { background: #e8f0fe; color: #3b82f6; }
|
||||
.sex-tag.female { background: #fce7f3; color: #db2777; }
|
||||
.callback-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 8rpx 22rpx;
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid #6acdbb;
|
||||
background: #e8f5f2;
|
||||
}
|
||||
.callback-btn-text { font-size: 24rpx; color: #1a9b88; font-weight: 500; }
|
||||
.p-sub { display: block; font-size: 22rpx; color: #6b7280; margin-top: 6rpx; }
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
margin: 16rpx 24rpx 0;
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
padding: 0 8rpx;
|
||||
border-bottom: 1rpx solid #eef2f1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.tab {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20rpx 0 14rpx;
|
||||
position: relative;
|
||||
}
|
||||
.tab-text { font-size: 26rpx; color: #6b7280; }
|
||||
.tab.on .tab-text { color: #1a9b88; font-weight: 600; }
|
||||
.tab-bar {
|
||||
position: absolute; bottom: 0; width: 48rpx; height: 4rpx;
|
||||
border-radius: 4rpx; background: #6acdbb;
|
||||
}
|
||||
.tab-swiper {
|
||||
width: 100%;
|
||||
padding: 0 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tab-scroll { width: 100%; box-sizing: border-box; }
|
||||
.health-card {
|
||||
background: #fff; border-radius: 0 0 16rpx 16rpx; padding: 8rpx 20rpx 20rpx;
|
||||
}
|
||||
.section-title {
|
||||
display: block; font-size: 26rpx; font-weight: 600; color: #1f2937;
|
||||
padding: 16rpx 0 8rpx;
|
||||
}
|
||||
.info-row {
|
||||
display: flex; justify-content: space-between; align-items: flex-start;
|
||||
padding: 16rpx 0; border-bottom: 1rpx solid #f3f4f6; gap: 24rpx;
|
||||
}
|
||||
.info-row:last-child { border-bottom: none; }
|
||||
.info-label { font-size: 24rpx; color: #9ca3af; flex-shrink: 0; }
|
||||
.info-value { font-size: 24rpx; color: #374151; text-align: right; flex: 1; word-break: break-all; }
|
||||
.info-value.danger { color: #dc2626; font-weight: 600; }
|
||||
.empty-inline { text-align: center; color: #9ca3af; padding: 40rpx 0; font-size: 24rpx; }
|
||||
.record-card {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
.record-main { flex: 1; min-width: 0; }
|
||||
.r-title { display: block; font-size: 26rpx; font-weight: 600; color: #111827; }
|
||||
.r-sub { display: block; font-size: 22rpx; color: #6b7280; margin-top: 6rpx; }
|
||||
.arrow { font-size: 36rpx; color: #d1d5db; line-height: 1; }
|
||||
.empty { text-align: center; color: #9ca3af; padding: 48rpx 0; font-size: 26rpx; }
|
||||
</style>
|
||||
@@ -1,14 +1,25 @@
|
||||
<template>
|
||||
<view v-if="showBlock" class="express-panel">
|
||||
<view class="section-title">物流信息</view>
|
||||
<template v-if="display.hasData">
|
||||
<view class="row"><text>物流公司</text><text>{{ display.companyName }}</text></view>
|
||||
<view class="row"><text>运单号</text><text>{{ display.expressNo }}</text></view>
|
||||
<view class="row"><text>最新状态</text><text class="highlight-text">{{ display.stateText }}</text></view>
|
||||
|
||||
<view v-if="display.tracks.length" class="timeline">
|
||||
<scroll-view v-if="packageList.length > 1" class="pkg-tabs" scroll-x>
|
||||
<view
|
||||
v-for="(pkg, idx) in packageList"
|
||||
:key="idx"
|
||||
class="pkg-tab"
|
||||
:class="{ active: activePkg === idx }"
|
||||
@click="activePkg = idx"
|
||||
>
|
||||
包裹{{ pkg.package_no || idx + 1 }}
|
||||
<text v-if="pkg.warehouse_name" class="pkg-sub">{{ pkg.warehouse_name }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<template v-if="currentDisplay.hasData">
|
||||
<view class="row"><text>物流公司</text><text>{{ currentDisplay.companyName }}</text></view>
|
||||
<view class="row"><text>运单号</text><text>{{ currentDisplay.expressNo }}</text></view>
|
||||
<view class="row"><text>最新状态</text><text class="highlight-text">{{ currentDisplay.stateText }}</text></view>
|
||||
<view v-if="currentDisplay.tracks.length" class="timeline">
|
||||
<view class="timeline-title">物流追踪</view>
|
||||
<view v-for="(t, idx) in display.tracks" :key="idx" class="track-item">
|
||||
<view v-for="(t, idx) in currentDisplay.tracks" :key="idx" class="track-item">
|
||||
<view class="track-dot" :class="{ 'active': idx === 0 }" />
|
||||
<view class="track-body">
|
||||
<text class="track-status" :class="{ 'active': idx === 0 }">{{ t.status }}</text>
|
||||
@@ -18,13 +29,14 @@
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<view v-else class="empty-express">暂无物流信息</view>
|
||||
<view v-else class="empty-express">
|
||||
{{ currentPkg && Number(currentPkg.is_send) === 0 ? '本包裹尚未发货' : '暂无物流信息' }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapExpressDetail } from '@/subPackages/sub_business_shared/common/display.js'
|
||||
|
||||
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
||||
|
||||
export default {
|
||||
@@ -34,14 +46,38 @@ export default {
|
||||
express: { type: Object, default: null },
|
||||
show: { type: Boolean, default: true },
|
||||
},
|
||||
data() {
|
||||
return { activePkg: 0 }
|
||||
},
|
||||
computed: {
|
||||
showBlock() {
|
||||
return this.show
|
||||
},
|
||||
display() {
|
||||
packageList() {
|
||||
const pkgs = this.express && Array.isArray(this.express.packages) ? this.express.packages : []
|
||||
if (pkgs.length) return pkgs
|
||||
if (this.express && (this.express.express_no || this.express.detail)) {
|
||||
return [{ package_no: 1, warehouse_name: '', is_send: 1, express: this.express }]
|
||||
}
|
||||
return []
|
||||
},
|
||||
currentPkg() {
|
||||
return this.packageList[this.activePkg] || null
|
||||
},
|
||||
currentDisplay() {
|
||||
const pkg = this.currentPkg
|
||||
if (pkg && pkg.express) return mapExpressDetail(pkg.express)
|
||||
if (pkg && Number(pkg.is_send) === 0) {
|
||||
return { hasData: false, companyName: '', expressNo: '', stateText: '', tracks: [] }
|
||||
}
|
||||
return mapExpressDetail(this.express)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
express() {
|
||||
this.activePkg = 0
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -56,7 +92,6 @@ $color-border: #F2F3F5;
|
||||
background: #ffffff;
|
||||
padding: 32rpx 40rpx;
|
||||
margin-bottom: 24rpx;
|
||||
/* 移除圆角和阴影,采用通栏设计 */
|
||||
}
|
||||
|
||||
.section-title {
|
||||
@@ -66,6 +101,34 @@ $color-border: #F2F3F5;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.pkg-tabs {
|
||||
white-space: nowrap;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.pkg-tab {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 12rpx 24rpx;
|
||||
margin-right: 12rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #f5f5f5;
|
||||
font-size: 26rpx;
|
||||
color: $color-text-muted;
|
||||
|
||||
&.active {
|
||||
background: rgba(106, 205, 187, 0.15);
|
||||
color: $theme-primary;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.pkg-sub {
|
||||
font-size: 20rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -130,7 +193,6 @@ $color-border: #F2F3F5;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* 最后一个元素去掉线 */
|
||||
.track-item:last-child .track-body {
|
||||
border-left-color: transparent;
|
||||
padding-bottom: 0;
|
||||
@@ -168,4 +230,4 @@ $color-border: #F2F3F5;
|
||||
font-size: 26rpx;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,189 +1,185 @@
|
||||
<template>
|
||||
<drawer-page-container
|
||||
v-model="show"
|
||||
title="处方详情"
|
||||
height="85%"
|
||||
tall
|
||||
:show-close="true"
|
||||
:closeable="true"
|
||||
@close="onClose"
|
||||
<!-- 小程序抽屉须用 page-container + v-if,避免 u-popup 在订单详情页底内联 -->
|
||||
<page-container
|
||||
v-if="show"
|
||||
:show="show"
|
||||
:overlay="true"
|
||||
position="bottom"
|
||||
:round="true"
|
||||
@beforeleave="onClose"
|
||||
@clickoverlay="onClose"
|
||||
>
|
||||
<view class="popup-wrap">
|
||||
<scroll-view v-if="view" scroll-y class="popup-scroll">
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<!-- 处方头部 -->
|
||||
<view class="head">
|
||||
<view class="head_record_top">
|
||||
<view class="record">
|
||||
处方编号:{{ view.prescriptionNo }}
|
||||
<view class="rx-drawer">
|
||||
<view class="rx-drawer-head">
|
||||
<text class="rx-drawer-title">处方详情</text>
|
||||
<text class="rx-drawer-close" @click="onClose">关闭</text>
|
||||
</view>
|
||||
<view class="popup-wrap">
|
||||
<scroll-view v-if="view" scroll-y class="popup-scroll">
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<!-- 处方头部 -->
|
||||
<view class="head">
|
||||
<view class="head_record_top">
|
||||
<view class="record">
|
||||
处方编号:{{ view.prescriptionNo }}
|
||||
</view>
|
||||
<view class="record_state">
|
||||
{{ view.typeText }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="record_state">
|
||||
{{ view.typeText }}
|
||||
<!-- 动态医院与公章 -->
|
||||
<view class="head_record">
|
||||
<image :src="view.sealImage || '/static/group/xkyard.png'" mode="aspectFit"></image>
|
||||
<view class="record_yard">
|
||||
<view class="yard">
|
||||
{{ view.storeName }}
|
||||
</view>
|
||||
<view class="state">
|
||||
处方笺
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 时间 -->
|
||||
<view class="head_record_time">
|
||||
<text style="text-align: right;">开具日期:{{ view.createdAt }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 动态医院与公章 -->
|
||||
<view class="head_record">
|
||||
<image :src="view.sealImage || '/static/group/xkyard.png'" mode="aspectFit"></image>
|
||||
<view class="record_yard">
|
||||
<view class="yard">
|
||||
{{ view.storeName }}
|
||||
<!-- 患者信息 -->
|
||||
<view class="my_info">
|
||||
<view class="info">
|
||||
<view class="info_item">
|
||||
<view class="name">姓名:<text>{{ view.patientName }}</text></view>
|
||||
</view>
|
||||
<view class="state">
|
||||
处方笺
|
||||
<view class="info_item">
|
||||
<view class="name">性别:<text>{{ view.patientSex }}</text></view>
|
||||
</view>
|
||||
<view class="info_item">
|
||||
<view class="name">年龄:<text>{{ view.patientAge }}</text></view>
|
||||
</view>
|
||||
<view class="info_item">
|
||||
<view class="name">类别:<text>{{ view.category }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="info_item">
|
||||
<view class="name">科室:<text>{{ view.departName }}</text></view>
|
||||
</view>
|
||||
<view class="info_item" v-if="view.patientMobile">
|
||||
<view class="name">电话:<text>{{ view.patientMobile }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="info_item">
|
||||
<view class="names">诊断:<text>{{ view.diagnose }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 中医专属字段 -->
|
||||
<view v-if="view.tcmSyndrome || view.tcmMethod || view.tcmDisease" class="info">
|
||||
<view v-if="view.tcmSyndrome" class="info_item">
|
||||
<view class="names">中医证候:<text>{{ view.tcmSyndrome }}</text></view>
|
||||
</view>
|
||||
<view v-if="view.tcmMethod" class="info_item">
|
||||
<view class="names">中医治法:<text>{{ view.tcmMethod }}</text></view>
|
||||
</view>
|
||||
<view v-if="view.tcmDisease" class="info_item">
|
||||
<view class="names">中医疾病:<text>{{ view.tcmDisease }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间 -->
|
||||
<view class="head_record_time">
|
||||
<text style="text-align: right;">开具日期:{{ view.createdAt }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 患者信息 -->
|
||||
<view class="my_info">
|
||||
<view class="info">
|
||||
<view class="info_item">
|
||||
<view class="name">姓名:<text>{{ view.patientName }}</text></view>
|
||||
</view>
|
||||
<view class="info_item">
|
||||
<view class="name">性别:<text>{{ view.patientSex }}</text></view>
|
||||
</view>
|
||||
<view class="info_item">
|
||||
<view class="name">年龄:<text>{{ view.patientAge }}</text></view>
|
||||
</view>
|
||||
<view class="info_item">
|
||||
<view class="name">类别:<text>{{ view.category }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info">
|
||||
<view class="info_item">
|
||||
<view class="name">科室:<text>{{ view.departName }}</text></view>
|
||||
</view>
|
||||
<view class="info_item" v-if="view.patientMobile">
|
||||
<view class="name">电话:<text>{{ view.patientMobile }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info">
|
||||
<view class="info_item">
|
||||
<view class="names">诊断:<text>{{ view.diagnose }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 中医专属字段 -->
|
||||
<view v-if="view.tcmSyndrome || view.tcmMethod || view.tcmDisease" class="info">
|
||||
<view v-if="view.tcmSyndrome" class="info_item">
|
||||
<view class="names">中医证候:<text>{{ view.tcmSyndrome }}</text></view>
|
||||
</view>
|
||||
<view v-if="view.tcmMethod" class="info_item">
|
||||
<view class="names">中医治法:<text>{{ view.tcmMethod }}</text></view>
|
||||
</view>
|
||||
<view v-if="view.tcmDisease" class="info_item">
|
||||
<view class="names">中医疾病:<text>{{ view.tcmDisease }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bg">
|
||||
<!-- 处方明细区 (Rp) -->
|
||||
<view class="my_medical">
|
||||
<view class="title">Rp</view>
|
||||
|
||||
<view v-for="(recipe, rIdx) in view.recipes" :key="rIdx" class="item">
|
||||
<view class="name">
|
||||
<view class="_name" v-for="(drug, dIdx) in recipe.drugs" :key="dIdx">
|
||||
<view class="text">
|
||||
<text>{{ drug.name }}</text>
|
||||
<text class="_abbr" v-if="drug.usage">[{{ drug.usage }}]</text>
|
||||
<view class="bg">
|
||||
<!-- 处方明细区 (Rp) -->
|
||||
<view class="my_medical">
|
||||
<view class="title">Rp</view>
|
||||
<view v-for="(recipe, rIdx) in view.recipes" :key="rIdx" class="item">
|
||||
<view class="name">
|
||||
<view class="_name" v-for="(drug, dIdx) in recipe.drugs" :key="dIdx">
|
||||
<view class="text">
|
||||
<text>{{ drug.name }}</text>
|
||||
<text v-if="drug.specification" class="drug-spec">{{ drug.specification }}</text>
|
||||
<text class="_abbr" v-if="drug.usage">[{{ drug.usage }}]</text>
|
||||
</view>
|
||||
<text class="name_num">{{ drug.qty }}</text>
|
||||
</view>
|
||||
<text class="name_num">{{ drug.qty }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="details" v-if="recipe.usage || recipe.process">
|
||||
<view class="text">
|
||||
<text v-if="recipe.usage">用法:{{ recipe.usage }}</text>
|
||||
<text v-if="recipe.process" style="margin-left: 10rpx;">包装方式:{{ recipe.process }}</text>
|
||||
<view class="details" v-if="recipe.usage || recipe.process">
|
||||
<view class="text">
|
||||
<text v-if="recipe.usage">用法:{{ recipe.usage }}</text>
|
||||
<text v-if="recipe.process" style="margin-left: 10rpx;">包装方式:{{ recipe.process }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 医嘱 -->
|
||||
<view class="doctor_order">
|
||||
<view class="yz">
|
||||
<text class="order_info_left">医嘱:</text>
|
||||
<view class="order_info">
|
||||
<view class="info">{{ view.doctorOrder !== '-' ? view.doctorOrder : '无' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="titles">处方开具已完毕</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 医嘱 -->
|
||||
<view class="doctor_order">
|
||||
<view class="yz">
|
||||
<text class="order_info_left">医嘱:</text>
|
||||
<view class="order_info">
|
||||
<view class="info">{{ view.doctorOrder !== '-' ? view.doctorOrder : '无' }}</view>
|
||||
<!-- 动态医生与药师签名区 -->
|
||||
<view class="my_doctor">
|
||||
<view class="doctor_info">
|
||||
<view class="info">
|
||||
<text>医师</text>
|
||||
<image v-if="view.doctorSignImage" :src="checkImageUrl(view.doctorSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name">{{ view.doctorName }}</text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>审核药师</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>发药人</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>核对人</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>调配人</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="titles">处方开具已完毕</view>
|
||||
<!-- 价格 -->
|
||||
<view class="price" v-if="view.totalPayPrice">
|
||||
价格 ¥{{ view.totalPayPrice }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- 动态有效期温馨提示 -->
|
||||
<view class="my_prompt">
|
||||
<view class="title">
|
||||
温馨提示:请遵医嘱服药!处方{{ view.validHours }}小时有效!
|
||||
</view>
|
||||
<image src="/static/group/img-yzf.png" mode="aspectFit" v-if="view.status == 2"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 动态医生与药师签名区 -->
|
||||
<view class="my_doctor">
|
||||
<view class="doctor_info">
|
||||
<view class="info">
|
||||
<text>医师</text>
|
||||
<image v-if="view.doctorSignImage" :src="checkImageUrl(view.doctorSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name">{{ view.doctorName }}</text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>审核药师</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>发药人</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>核对人</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>调配人</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 价格 -->
|
||||
<view class="price" v-if="view.totalPayPrice">
|
||||
价格 ¥{{ view.totalPayPrice }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 动态有效期温馨提示 -->
|
||||
<view class="my_prompt">
|
||||
<view class="title">
|
||||
温馨提示:请遵医嘱服药!处方{{ view.validHours }}小时有效!
|
||||
</view>
|
||||
<image src="/static/group/img-yzf.png" mode="aspectFit" v-if="view.status == 2"></image>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-else-if="loading" class="popup-loading">加载中...</view>
|
||||
<view v-else class="popup-loading">暂无数据</view>
|
||||
</scroll-view>
|
||||
<view v-else-if="loading" class="popup-loading">加载中...</view>
|
||||
<view v-else class="popup-loading">暂无数据</view>
|
||||
</view>
|
||||
</view>
|
||||
</drawer-page-container>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DrawerPageContainer from '@/subPackages/sub_business_shared/components/DrawerPageContainer.vue'
|
||||
/**
|
||||
* 处方详情抽屉:page-container 底部浮层(管理端看方)
|
||||
*/
|
||||
import { mapPrescriptionDetailView } from '@/subPackages/sub_business_shared/common/display.js'
|
||||
|
||||
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
||||
import { formatStoreNameWithHu } from '@/utils/formatStoreNameWithHu.js'
|
||||
|
||||
export default {
|
||||
mixins: [businessMixin],
|
||||
components: { DrawerPageContainer },
|
||||
name: 'PrescriptionDetailPopup',
|
||||
data() {
|
||||
return {
|
||||
@@ -193,7 +189,7 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 解析签名图片,自动兼容 base64 和 http 链接
|
||||
/** 解析签名图片,自动兼容 base64 和 http 链接 */
|
||||
checkImageUrl(url) {
|
||||
if (!url) return ''
|
||||
return url.includes('http') ? url : 'data:image/jpeg;base64,' + url
|
||||
@@ -205,20 +201,27 @@ export default {
|
||||
this.loading = true
|
||||
this.bizApi.getPrescriptionDetail(prescriptionId).then(w => {
|
||||
if (w.ok && w.data) {
|
||||
// 复用原有的处方解析逻辑
|
||||
const mapped = mapPrescriptionDetailView(w.data)
|
||||
|
||||
// --- 动态注入你新版 UI 需要的独有字段 ---
|
||||
mapped.storeName = w.data.store ? w.data.store.name : '未知诊所'
|
||||
mapped.storeName = formatStoreNameWithHu(
|
||||
w.data.store ? w.data.store.name : '未知诊所',
|
||||
w.data.is_online,
|
||||
)
|
||||
mapped.sealImage = w.data.store ? w.data.store.offical_seal : ''
|
||||
mapped.typeText = w.data.type == 1 ? '普通' : '常用'
|
||||
mapped.totalPayPrice = w.data.total_pay_price || '0.00'
|
||||
mapped.doctorSignImage = w.data.doctor_sign_image || ''
|
||||
mapped.pharmacistSignImage = w.data.Pharmacist_sign_image || ''
|
||||
mapped.doctorSignImage =
|
||||
w.data.doctor_sign_image
|
||||
|| (w.data.doctor_info && w.data.doctor_info.identity_info && w.data.doctor_info.identity_info.sign_image)
|
||||
|| (w.data.doctorInfo && w.data.doctorInfo.identityInfo && w.data.doctorInfo.identityInfo.sign_image)
|
||||
|| ''
|
||||
mapped.pharmacistSignImage =
|
||||
w.data.Pharmacist_sign_image
|
||||
|| (w.data.pharmacist_info && w.data.pharmacist_info.identity && w.data.pharmacist_info.identity.sign_image)
|
||||
|| (w.data.pharmacistInfo && w.data.pharmacistInfo.identity && w.data.pharmacistInfo.identity.sign_image)
|
||||
|| ''
|
||||
mapped.validHours = w.data.valid_hours || 72
|
||||
mapped.status = w.data.status
|
||||
mapped.patientMobile = w.data.patient ? w.data.patient.mobile : ''
|
||||
|
||||
this.view = mapped
|
||||
}
|
||||
}).finally(() => {
|
||||
@@ -234,20 +237,37 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rx-drawer {
|
||||
height: 85vh;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.rx-drawer-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 28rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.rx-drawer-title { font-size: 32rpx; font-weight: 600; color: #1d2129; }
|
||||
.rx-drawer-close { font-size: 28rpx; color: #6acdbb; }
|
||||
.popup-wrap {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
padding-top: 60rpx; /* 给默认的关闭按钮留出空间 */
|
||||
}
|
||||
|
||||
.popup-scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.popup-loading {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
@@ -445,6 +465,11 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.drug-spec {
|
||||
color: #8A92A3;
|
||||
font-size: 22rpx;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
._abbr {
|
||||
color: #A7ABB0;
|
||||
font-size: 20rpx;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<view>
|
||||
<business-page-layout title="订单详情" :show-back="true">
|
||||
<view class="detail-container">
|
||||
<!-- 头部面板:通栏,展示金额与状态 -->
|
||||
@@ -17,7 +18,14 @@
|
||||
<view class="sub-block">
|
||||
<view class="sub-block-title">基本信息</view>
|
||||
<view class="row"><text>订单类型</text><text>{{ detailExtra.orderTypeText }}</text></view>
|
||||
<view class="row"><text>患者</text><text>{{ info.patient || '-' }}</text></view>
|
||||
<view class="row">
|
||||
<text>患者</text>
|
||||
<text
|
||||
class="patient-link"
|
||||
:class="{ disabled: !patientUpId }"
|
||||
@click="openPatient"
|
||||
>{{ info.patient || '-' }}</text>
|
||||
</view>
|
||||
<view class="row"><text>医生</text><text>{{ doctorName }}</text></view>
|
||||
<view class="row"><text>处方来源</text><text>{{ storeName }}</text></view>
|
||||
<view class="row"><text>订单来源</text><text>{{ onlineText(info.is_online) }}</text></view>
|
||||
@@ -50,7 +58,14 @@
|
||||
<!-- 配送信息面板 -->
|
||||
<view v-if="info.id" class="detail-panel">
|
||||
<view class="section-title">{{ receiverBlockTitle }}</view>
|
||||
<view class="row"><text>{{ receiverLabel }}</text><text>{{ receiverName }}</text></view>
|
||||
<view class="row">
|
||||
<text>{{ receiverLabel }}</text>
|
||||
<text
|
||||
class="patient-link"
|
||||
:class="{ disabled: !patientUpId }"
|
||||
@click="openPatient"
|
||||
>{{ receiverName }}</text>
|
||||
</view>
|
||||
<view class="row"><text>联系电话</text><text>{{ receiverMobile }}</text></view>
|
||||
<view class="row" v-if="info.delivery_method === 0">
|
||||
<text>配送地址</text><text class="addr">{{ deliveryAddress }}</text>
|
||||
@@ -148,20 +163,28 @@
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 处方弹窗 -->
|
||||
<prescription-detail-popup ref="prescriptionPopup" />
|
||||
<ship-order-popup ref="shipPopup" :biz-api="bizApi" @success="load" />
|
||||
<refund-order-popup ref="refundPopup" :biz-api="bizApi" @success="load" />
|
||||
<erp-sync-popup ref="erpPopup" :biz-api="bizApi" @success="load" />
|
||||
<order-price-percent-adjust-popup
|
||||
:visible="priceAdjustVisible"
|
||||
:current-discount="info.price_discount || 100"
|
||||
:quick-options="priceAdjustQuickOptions"
|
||||
:submitting="priceAdjustSubmitting"
|
||||
@close="closePriceAdjust"
|
||||
@confirm="onSharedPriceAdjustConfirm"
|
||||
/>
|
||||
</business-page-layout>
|
||||
<!-- page-container 抽屉挂在页面根级,避免被 layout 裁切成页底内联 -->
|
||||
<prescription-detail-popup ref="prescriptionPopup" />
|
||||
<ship-order-popup ref="shipPopup" :biz-api="bizApi" @success="load" />
|
||||
<refund-order-popup ref="refundPopup" :biz-api="bizApi" @success="load" />
|
||||
<erp-sync-popup ref="erpPopup" :biz-api="bizApi" @success="load" />
|
||||
<order-price-percent-adjust-popup
|
||||
:visible="priceAdjustVisible"
|
||||
:current-discount="info.price_discount || 100"
|
||||
:quick-options="priceAdjustQuickOptions"
|
||||
:submitting="priceAdjustSubmitting"
|
||||
@close="closePriceAdjust"
|
||||
@confirm="onSharedPriceAdjustConfirm"
|
||||
/>
|
||||
<user-patient-detail
|
||||
v-if="patientDetailVisible"
|
||||
:visible="patientDetailVisible"
|
||||
:up-id="patientDetailUpId"
|
||||
:patient-name="patientDetailName"
|
||||
@close="patientDetailVisible = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -185,6 +208,7 @@ import { formatMoney } from '@/subPackages/sub_business_shared/common/format.js'
|
||||
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
||||
import { orderPriceAdjustMixin } from '@/subPackages/sub_business_shared/common/orderPriceAdjustMixin.js'
|
||||
import OrderPricePercentAdjustPopup from '@/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup.vue'
|
||||
import UserPatientDetail from '@/subPackages/sub_business_shared/components/UserPatientDetail.vue'
|
||||
|
||||
export default {
|
||||
mixins: [businessMixin, orderPriceAdjustMixin],
|
||||
@@ -196,6 +220,7 @@ export default {
|
||||
RefundOrderPopup,
|
||||
ErpSyncPopup,
|
||||
OrderPricePercentAdjustPopup,
|
||||
UserPatientDetail,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -203,6 +228,9 @@ export default {
|
||||
info: {},
|
||||
expressInfo: null,
|
||||
detailExtra: {},
|
||||
patientDetailVisible: false,
|
||||
patientDetailUpId: 0,
|
||||
patientDetailName: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -225,6 +253,13 @@ export default {
|
||||
const s = this.info.store
|
||||
return (s && s.name) ? s.name : '-'
|
||||
},
|
||||
/** 就诊人 ID:订单 up_id 或关联 user_patient.id */
|
||||
patientUpId() {
|
||||
const up = Number(this.info.up_id || 0)
|
||||
if (up) return up
|
||||
const rel = this.info.user_patient
|
||||
return Number((rel && rel.id) || 0)
|
||||
},
|
||||
receiverBlockTitle() {
|
||||
return this.info.delivery_method === 0 ? '收货人信息' : '就诊人信息'
|
||||
},
|
||||
@@ -272,16 +307,54 @@ export default {
|
||||
this.id = Number(q.id || 0)
|
||||
this.load()
|
||||
},
|
||||
/** 离开页关闭处方抽屉,避免 page-container 残留遮罩导致列表白屏 */
|
||||
onHide() {
|
||||
this.closePrescriptionPopup()
|
||||
this.patientDetailVisible = false
|
||||
},
|
||||
onUnload() {
|
||||
this.closePrescriptionPopup()
|
||||
this.patientDetailVisible = false
|
||||
},
|
||||
methods: {
|
||||
/** 关闭处方抽屉 */
|
||||
closePrescriptionPopup() {
|
||||
const pop = this.$refs.prescriptionPopup
|
||||
if (pop && typeof pop.onClose === 'function') {
|
||||
pop.onClose()
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 打开患者视角抽屉(与订单列表一致)
|
||||
*/
|
||||
openPatient() {
|
||||
const upId = this.patientUpId
|
||||
if (!upId) {
|
||||
uni.showToast({ title: '缺少就诊人信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.patientDetailUpId = upId
|
||||
this.patientDetailName = this.info.patient || ''
|
||||
this.patientDetailVisible = true
|
||||
},
|
||||
load() {
|
||||
if (!this.id) {
|
||||
uni.showToast({ title: '缺少订单信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.bizApi.getOrderDetail(this.id).then(w => {
|
||||
if (!w.ok) return
|
||||
if (!w.ok) {
|
||||
uni.showToast({ title: '订单加载失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.info = w.data || {}
|
||||
this.detailExtra = mapOrderDetailDisplay(this.info)
|
||||
this.loadPriceAdjustMeta(() => this.bizApi.getPriceAdjustConfig(this.info.store_id))
|
||||
if (this.info.delivery_method === 0) {
|
||||
this.loadExpress()
|
||||
}
|
||||
}).catch(() => {
|
||||
uni.showToast({ title: '订单加载失败', icon: 'none' })
|
||||
})
|
||||
},
|
||||
loadExpress() {
|
||||
@@ -584,6 +657,11 @@ $color-danger: #F53F3F;
|
||||
|
||||
.price-main { font-size: 28rpx; color: $color-text-title; font-weight: 600; }
|
||||
.price-link { color: $theme-primary; text-decoration: underline; }
|
||||
.patient-link {
|
||||
color: $theme-primary;
|
||||
text-align: right;
|
||||
&.disabled { color: inherit; }
|
||||
}
|
||||
|
||||
/* ================= 底部动作栏 ================= */
|
||||
.actions-bar {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<view>
|
||||
<business-page-layout title="商品订单" :show-back="true">
|
||||
<view class="order-list-container">
|
||||
|
||||
@@ -96,9 +97,13 @@
|
||||
|
||||
<!-- 去掉臃肿的灰色岛,采用精致的文本网格 -->
|
||||
<view class="info-grid">
|
||||
<view class="info-item" v-if="rowDisplay(item).userNickname !== '-'">
|
||||
<view
|
||||
class="info-item"
|
||||
v-if="rowDisplay(item).patientName !== '-' || rowDisplay(item).userNickname !== '-'"
|
||||
@click.stop="openPatient(item)"
|
||||
>
|
||||
<text class="info-lbl">就诊人</text>
|
||||
<text class="info-val">{{ rowDisplay(item).userNickname }}</text>
|
||||
<text class="info-val link">{{ rowDisplay(item).patientName !== '-' ? rowDisplay(item).patientName : rowDisplay(item).userNickname }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-lbl">收件人</text>
|
||||
@@ -136,8 +141,16 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<prescription-detail-popup ref="prescriptionPopup" />
|
||||
</business-page-layout>
|
||||
<prescription-detail-popup ref="prescriptionPopup" />
|
||||
<user-patient-detail
|
||||
v-if="patientDetailVisible"
|
||||
:visible="patientDetailVisible"
|
||||
:up-id="patientDetailUpId"
|
||||
:patient-name="patientDetailName"
|
||||
@close="patientDetailVisible = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -145,6 +158,7 @@
|
||||
import BusinessPageLayout from '@/subPackages/sub_business_shared/components/business-page-layout.vue'
|
||||
import CollapsibleFilterPanel from '@/subPackages/sub_business_shared/components/CollapsibleFilterPanel.vue'
|
||||
import PrescriptionDetailPopup from './components/PrescriptionDetailPopup.vue'
|
||||
import UserPatientDetail from '@/subPackages/sub_business_shared/components/UserPatientDetail.vue'
|
||||
import { withWxKey } from '@/utils/wxListKey.js'
|
||||
import { mapOrderListRow } from '@/subPackages/sub_business_shared/common/display.js'
|
||||
import { formatMoney } from '@/subPackages/sub_business_shared/common/format.js'
|
||||
@@ -176,7 +190,7 @@ const PRESCRIPTION_TYPE_OPTIONS = [
|
||||
|
||||
export default {
|
||||
mixins: [businessMixin],
|
||||
components: { BusinessPageLayout, CollapsibleFilterPanel, PrescriptionDetailPopup },
|
||||
components: { BusinessPageLayout, CollapsibleFilterPanel, PrescriptionDetailPopup, UserPatientDetail },
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
@@ -190,7 +204,10 @@ export default {
|
||||
dateStart: '',
|
||||
dateEnd: '',
|
||||
statItems: [],
|
||||
navTopOffset: 0, // 新增:用于存放导航栏计算后的确切高度
|
||||
navTopOffset: 0,
|
||||
patientDetailVisible: false,
|
||||
patientDetailUpId: 0,
|
||||
patientDetailName: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -225,10 +242,24 @@ export default {
|
||||
})
|
||||
this.checkDateEndAndReload()
|
||||
},
|
||||
/** 离开/隐藏时关掉处方抽屉,避免 page-container 残留白屏 */
|
||||
onHide() {
|
||||
this.closePrescriptionPopup()
|
||||
},
|
||||
onUnload() {
|
||||
this.closePrescriptionPopup()
|
||||
},
|
||||
onReachBottom() {
|
||||
this.load(this.page + 1, true)
|
||||
},
|
||||
methods: {
|
||||
/** 关闭处方抽屉 */
|
||||
closePrescriptionPopup() {
|
||||
const pop = this.$refs.prescriptionPopup
|
||||
if (pop && typeof pop.onClose === 'function') {
|
||||
pop.onClose()
|
||||
}
|
||||
},
|
||||
applyOrderFilter(f) {
|
||||
this.orderNo = f.orderNo
|
||||
this.statusIndex = f.statusIndex
|
||||
@@ -337,6 +368,18 @@ export default {
|
||||
if (!pId) return
|
||||
this.$refs.prescriptionPopup.open(pId)
|
||||
},
|
||||
/** 打开患者视角详情抽屉 */
|
||||
openPatient(item) {
|
||||
const d = this.rowDisplay(item)
|
||||
const upId = Number(d.upId || 0)
|
||||
if (!upId) {
|
||||
uni.showToast({ title: '缺少就诊人信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.patientDetailUpId = upId
|
||||
this.patientDetailName = d.patientName !== '-' ? d.patientName : ''
|
||||
this.patientDetailVisible = true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -543,6 +586,9 @@ $color-border: #E5E6EB;
|
||||
.info-val {
|
||||
color: $color-text-body;
|
||||
flex: 1;
|
||||
&.link {
|
||||
color: $theme-primary;
|
||||
}
|
||||
}
|
||||
.info-sub {
|
||||
color: $color-text-muted;
|
||||
|
||||
515
subPackages/sub_business_shared/user-patient/detail.vue
Normal file
515
subPackages/sub_business_shared/user-patient/detail.vue
Normal file
@@ -0,0 +1,515 @@
|
||||
<template>
|
||||
<view>
|
||||
<business-page-layout :title="pageTitle" :show-back="true">
|
||||
<view class="page">
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<template v-else>
|
||||
<!-- 顶部:用户 + 就诊人分区卡片(固定,不进 swiper) -->
|
||||
<view class="profile-card">
|
||||
<view class="profile-row">
|
||||
<image class="avatar" :src="profile.user.avatarurl || '/static/image/default-avatar.png'" mode="aspectFill" />
|
||||
<view class="meta">
|
||||
<text class="name">{{ profile.user.nickname || '微信用户' }}</text>
|
||||
<text class="sub">ID {{ profile.user.id || '—' }}<template v-if="profile.user.mobile"> · {{ profile.user.mobile }}</template></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="patient-block">
|
||||
<view class="patient-head">
|
||||
<view class="patient-head-left">
|
||||
<text class="p-name">{{ profile.patient.name || '—' }}</text>
|
||||
<text v-if="sexText !== '—'" class="sex-tag" :class="sexTagClass">{{ sexText }}</text>
|
||||
</view>
|
||||
<!-- 回访:拨打就诊人/用户手机 -->
|
||||
<view class="callback-btn" @click.stop="onCallbackCall">
|
||||
<text class="callback-btn-text">回访</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="p-sub">
|
||||
{{ profile.patient.age || '—' }}岁
|
||||
<template v-if="profile.patient.mobile"> · {{ profile.patient.mobile }}</template>
|
||||
</text>
|
||||
<!-- 门店后端不返回身份证时不展示 -->
|
||||
<text v-if="profile.patient.id_card" class="p-sub">身份证 {{ profile.patient.id_card }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- Tab:下划线选中态 -->
|
||||
<view class="tabs">
|
||||
<view
|
||||
v-for="(t, idx) in tabs"
|
||||
:key="t.key"
|
||||
class="tab"
|
||||
:class="{ on: tabIndex === idx }"
|
||||
@click="selectTab(idx)"
|
||||
>
|
||||
<text class="tab-text">{{ t.label }}</text>
|
||||
<view v-if="tabIndex === idx" class="tab-bar" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 四页左右滑:资料 / 挂号 / 处方 / 订单 -->
|
||||
<swiper
|
||||
class="tab-swiper"
|
||||
:style="{ height: swiperHeightPx + 'px' }"
|
||||
:current="tabIndex"
|
||||
:duration="200"
|
||||
@change="onTabSwiperChange"
|
||||
>
|
||||
<swiper-item v-for="t in tabs" :key="'sw-' + t.key">
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="tab-scroll"
|
||||
:style="{ height: swiperHeightPx + 'px' }"
|
||||
>
|
||||
<!-- 资料 -->
|
||||
<view v-if="t.key === 'info'" class="health-card">
|
||||
<text class="section-title">健康信息</text>
|
||||
<template v-if="health">
|
||||
<view class="info-row">
|
||||
<text class="info-label">既往史</text>
|
||||
<text class="info-value">{{ historyText(health.person_status, health.person_history) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">过敏史</text>
|
||||
<text
|
||||
class="info-value"
|
||||
:class="{ danger: Number(health.allergic_status) !== 0 }"
|
||||
>{{ historyText(health.allergic_status, health.allergic_history) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">家族遗传史</text>
|
||||
<text class="info-value">{{ historyText(health.family_status, health.family_history) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">肝功能异常</text>
|
||||
<text
|
||||
class="info-value"
|
||||
:class="{ danger: Number(health.liver_function) !== 0 }"
|
||||
>{{ functionText(health.liver_function, health.liver_index) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">肾功能异常</text>
|
||||
<text
|
||||
class="info-value"
|
||||
:class="{ danger: Number(health.renal_function) !== 0 }"
|
||||
>{{ functionText(health.renal_function, health.renal_index) }}</text>
|
||||
</view>
|
||||
</template>
|
||||
<view v-else class="empty-inline">暂无健康问诊记录</view>
|
||||
</view>
|
||||
<!-- 挂号 / 处方 / 订单列表 -->
|
||||
<template v-else>
|
||||
<view v-if="tabLoading && activeTab === t.key" class="empty">加载中...</view>
|
||||
<template v-else>
|
||||
<view v-if="!listForTab(t.key).length" class="empty">暂无记录</view>
|
||||
<view
|
||||
v-for="item in listForTab(t.key)"
|
||||
:key="t.key + '-' + item.id"
|
||||
class="record-card"
|
||||
@click="onRecordClick(item, t.key)"
|
||||
>
|
||||
<view class="record-main">
|
||||
<template v-if="t.key === 'register'">
|
||||
<text class="r-title">{{ item.order_no || item.order_number || '—' }}</text>
|
||||
<text class="r-sub">{{ item.store || '—' }} · {{ item.doctor || '—' }}</text>
|
||||
<text class="r-sub">{{ item.status_text || '—' }} · {{ item.created_at || '' }}</text>
|
||||
</template>
|
||||
<template v-else-if="t.key === 'prescription'">
|
||||
<text class="r-title">{{ item.prescription_no || '—' }}</text>
|
||||
<text class="r-sub">{{ item.store || '—' }} · {{ item.doctor || '—' }}</text>
|
||||
<text class="r-sub">{{ item.clinical_diagnose || '—' }} · {{ item.created_at || '' }}</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="r-title">{{ item.order_no || '—' }}</text>
|
||||
<text class="r-sub">{{ item.store || '—' }} · ¥{{ item.total_pay_price || '—' }}</text>
|
||||
<text class="r-sub">{{ item.created_at || '' }}</text>
|
||||
</template>
|
||||
</view>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</template>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</template>
|
||||
</view>
|
||||
</business-page-layout>
|
||||
<!-- page-container 抽屉挂在页面根级 -->
|
||||
<prescription-detail-popup ref="prescriptionPopup" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 就诊人详情:资料(健康问诊)+ 挂号/处方/订单
|
||||
* 顶部资料卡固定,下方四 Tab 支持点击与左右滑动切换(懒加载列表)
|
||||
*/
|
||||
import BusinessPageLayout from '@/subPackages/sub_business_shared/components/business-page-layout.vue';
|
||||
import PrescriptionDetailPopup from '@/subPackages/sub_business_shared/order/components/PrescriptionDetailPopup.vue';
|
||||
import {
|
||||
getUserPatientDetail,
|
||||
getUserPatientRegisterList,
|
||||
getUserPatientPrescriptionList,
|
||||
getUserPatientOrderList,
|
||||
} from '@/api/userPatientProfile.js';
|
||||
|
||||
/** 就诊人详情左右滑提示只展示一次 */
|
||||
const SWIPE_TIP_STORAGE_KEY = 'user_patient_detail_swipe_tip';
|
||||
|
||||
export default {
|
||||
components: { BusinessPageLayout, PrescriptionDetailPopup },
|
||||
data() {
|
||||
return {
|
||||
upId: 0,
|
||||
patientName: '',
|
||||
loading: false,
|
||||
tabLoading: false,
|
||||
activeTab: 'info',
|
||||
tabIndex: 0,
|
||||
tabSwiperChanging: false,
|
||||
swiperHeightPx: 400,
|
||||
tabs: [
|
||||
{ key: 'info', label: '资料' },
|
||||
{ key: 'register', label: '挂号' },
|
||||
{ key: 'prescription', label: '处方' },
|
||||
{ key: 'order', label: '订单' },
|
||||
],
|
||||
profile: { user: {}, patient: {} },
|
||||
health: null,
|
||||
registerList: [],
|
||||
prescriptionList: [],
|
||||
orderList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
return this.patientName ? `就诊人 · ${this.patientName}` : '就诊人详情';
|
||||
},
|
||||
sexText() {
|
||||
const sex = this.profile.patient && this.profile.patient.sex;
|
||||
if (Number(sex) === 1) return '男';
|
||||
if (Number(sex) === 2) return '女';
|
||||
return '—';
|
||||
},
|
||||
sexTagClass() {
|
||||
const sex = this.profile.patient && this.profile.patient.sex;
|
||||
if (Number(sex) === 1) return 'male';
|
||||
if (Number(sex) === 2) return 'female';
|
||||
return '';
|
||||
},
|
||||
/**
|
||||
* 回访拨号:只用就诊人 call_mobile(拦截器只解密不脱敏)
|
||||
* 不用脱敏后的 mobile,也不回退微信用户号
|
||||
*/
|
||||
callbackPhone() {
|
||||
const patient = this.profile.patient || {};
|
||||
return String(patient.call_mobile || '').trim();
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.calcSwiperHeight();
|
||||
this.upId = Number((options && options.up_id) || 0);
|
||||
try {
|
||||
this.patientName = decodeURIComponent((options && options.name) || '');
|
||||
} catch (e) {
|
||||
this.patientName = (options && options.name) || '';
|
||||
}
|
||||
if (this.upId) {
|
||||
this.loadDetail();
|
||||
}
|
||||
},
|
||||
onReady() {
|
||||
this.calcSwiperHeight();
|
||||
},
|
||||
/** 离开页时关掉处方抽屉,避免 page-container 残留遮罩 */
|
||||
onHide() {
|
||||
this.closePrescriptionPopup();
|
||||
},
|
||||
onUnload() {
|
||||
this.closePrescriptionPopup();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 按窗口高度算死 swiper:窗口 − 状态栏 − 导航 − 资料卡预估 − Tab − 边距
|
||||
*/
|
||||
calcSwiperHeight() {
|
||||
try {
|
||||
const sys = uni.getSystemInfoSync() || {};
|
||||
const winH = Number(sys.windowHeight) || 600;
|
||||
const statusBar = Number(sys.statusBarHeight) || 0;
|
||||
const navbar = Number(this.$navbarHeight) || 44;
|
||||
const rpx = (Number(sys.windowWidth) || 375) / 750;
|
||||
const pagePad = Math.ceil(24 * rpx);
|
||||
const profileH = Math.ceil(280 * rpx);
|
||||
const tabsH = Math.ceil(90 * rpx);
|
||||
const h = winH - statusBar - navbar - pagePad - profileH - tabsH;
|
||||
this.swiperHeightPx = h > 180 ? h : 180;
|
||||
} catch (e) {
|
||||
this.swiperHeightPx = 400;
|
||||
}
|
||||
},
|
||||
/** 按 Tab key 取对应列表(swiper 四页同时渲染时不能只用 activeTab) */
|
||||
listForTab(key) {
|
||||
if (key === 'register') return this.registerList;
|
||||
if (key === 'prescription') return this.prescriptionList;
|
||||
if (key === 'order') return this.orderList;
|
||||
return [];
|
||||
},
|
||||
/** 关闭处方抽屉,防止 page-container 残留导致返回后白屏 */
|
||||
closePrescriptionPopup() {
|
||||
const pop = this.$refs.prescriptionPopup;
|
||||
if (pop && typeof pop.onClose === 'function') {
|
||||
pop.onClose();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 回访:调起系统拨号盘拨打 callbackPhone
|
||||
* 无号时 toast,不隐藏按钮以免布局跳动
|
||||
*/
|
||||
onCallbackCall() {
|
||||
const phone = this.callbackPhone;
|
||||
if (!phone) {
|
||||
uni.showToast({ title: '暂无手机号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: phone,
|
||||
});
|
||||
},
|
||||
/** 既往/过敏/家族:0 无,否则展示 history */
|
||||
historyText(status, history) {
|
||||
if (Number(status) === 0) return '无';
|
||||
const t = String(history || '').trim();
|
||||
return t || '有';
|
||||
},
|
||||
/** 肝/肾功能:0 正常,异常时附带指标文案 */
|
||||
functionText(flag, indexText) {
|
||||
if (Number(flag) === 0) return '正常';
|
||||
const t = String(indexText || '').trim();
|
||||
return t ? `异常 · ${t}` : '异常';
|
||||
},
|
||||
async loadDetail() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const wrap = await getUserPatientDetail(this.upId);
|
||||
if (wrap && wrap.ok && wrap.data) {
|
||||
this.profile = {
|
||||
user: wrap.data.user || {},
|
||||
patient: wrap.data.patient || {},
|
||||
};
|
||||
this.health = wrap.data.health_inquiry || null;
|
||||
if (!this.patientName && this.profile.patient.name) {
|
||||
this.patientName = this.profile.patient.name;
|
||||
}
|
||||
}
|
||||
this.calcSwiperHeight();
|
||||
this.$nextTick(() => {
|
||||
this.maybeShowSwipeTip();
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 首次进入短暂提示可左右滑动(独立缓存键,与挂号详情互不影响)
|
||||
*/
|
||||
maybeShowSwipeTip() {
|
||||
let shown = '';
|
||||
try {
|
||||
shown = uni.getStorageSync(SWIPE_TIP_STORAGE_KEY);
|
||||
} catch (e) {
|
||||
shown = '';
|
||||
}
|
||||
if (shown) return;
|
||||
uni.showToast({
|
||||
title: '左右滑动可切换标签',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
try {
|
||||
uni.setStorageSync(SWIPE_TIP_STORAGE_KEY, '1');
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
/** 点击顶部 Tab */
|
||||
selectTab(index) {
|
||||
if (this.tabSwiperChanging) return;
|
||||
const tab = this.tabs[index];
|
||||
if (!tab) return;
|
||||
this.tabIndex = index;
|
||||
this.switchTab(tab.key);
|
||||
},
|
||||
/** swiper 滑动切换 */
|
||||
onTabSwiperChange(e) {
|
||||
const index = e && e.detail ? e.detail.current : 0;
|
||||
const tab = this.tabs[index];
|
||||
if (!tab || tab.key === this.activeTab) {
|
||||
this.tabIndex = index;
|
||||
return;
|
||||
}
|
||||
this.tabSwiperChanging = true;
|
||||
this.tabIndex = index;
|
||||
this.switchTab(tab.key);
|
||||
this.$nextTick(() => {
|
||||
this.tabSwiperChanging = false;
|
||||
});
|
||||
},
|
||||
/** 切换 Tab 并懒加载对应列表 */
|
||||
async switchTab(key) {
|
||||
this.activeTab = key;
|
||||
const idx = this.tabs.findIndex((t) => t.key === key);
|
||||
if (idx >= 0 && this.tabIndex !== idx) {
|
||||
this.tabIndex = idx;
|
||||
}
|
||||
if (key === 'info') return;
|
||||
if (key === 'register' && !this.registerList.length) {
|
||||
await this.loadRegisters();
|
||||
} else if (key === 'prescription' && !this.prescriptionList.length) {
|
||||
await this.loadPrescriptions();
|
||||
} else if (key === 'order' && !this.orderList.length) {
|
||||
await this.loadOrders();
|
||||
}
|
||||
},
|
||||
async loadRegisters() {
|
||||
this.tabLoading = true;
|
||||
try {
|
||||
const wrap = await getUserPatientRegisterList(this.upId);
|
||||
this.registerList = (wrap && wrap.ok && wrap.data && wrap.data.items) || [];
|
||||
} finally {
|
||||
this.tabLoading = false;
|
||||
}
|
||||
},
|
||||
async loadPrescriptions() {
|
||||
this.tabLoading = true;
|
||||
try {
|
||||
const wrap = await getUserPatientPrescriptionList(this.upId);
|
||||
this.prescriptionList = (wrap && wrap.ok && wrap.data && wrap.data.items) || [];
|
||||
} finally {
|
||||
this.tabLoading = false;
|
||||
}
|
||||
},
|
||||
async loadOrders() {
|
||||
this.tabLoading = true;
|
||||
try {
|
||||
const wrap = await getUserPatientOrderList(this.upId);
|
||||
this.orderList = (wrap && wrap.ok && wrap.data && wrap.data.items) || [];
|
||||
} finally {
|
||||
this.tabLoading = false;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 列表项点击:处方弹窗 / 订单页 / 挂号详情页
|
||||
* @param item 列表行
|
||||
* @param tabKey 所在 Tab(避免滑动瞬间 activeTab 未同步)
|
||||
*/
|
||||
onRecordClick(item, tabKey) {
|
||||
const key = tabKey || this.activeTab;
|
||||
if (key === 'prescription') {
|
||||
const pid = Number((item && item.id) || 0);
|
||||
if (!pid) {
|
||||
uni.showToast({ title: '缺少处方信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
this.$refs.prescriptionPopup && this.$refs.prescriptionPopup.open(pid);
|
||||
return;
|
||||
}
|
||||
if (key === 'order') {
|
||||
const oid = Number((item && item.id) || 0);
|
||||
if (!oid) {
|
||||
uni.showToast({ title: '缺少订单信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
// 诊所端进本包订单详情,平台端进 shared;避免跨包 + page-container 残留导致列表白屏
|
||||
const mode = uni.getStorageSync('loginMode') || '';
|
||||
const detailUrl = mode === 'clinic_admin'
|
||||
? `/subPackages/sub_clinic_admin/order/detail?id=${oid}`
|
||||
: `/subPackages/sub_business_shared/order/detail?id=${oid}`;
|
||||
uni.navigateTo({ url: detailUrl });
|
||||
return;
|
||||
}
|
||||
if (key === 'register') {
|
||||
// 轻量详情页用列表字段即可,暂存避免 URL 过长
|
||||
try {
|
||||
uni.setStorageSync('user_patient_register_detail', item || {});
|
||||
} catch (e) { /* ignore */ }
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_business_shared/user-patient/register-detail?id=${Number((item && item.id) || 0)}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx 24rpx 0; background: #f5f7f8; box-sizing: border-box; }
|
||||
.profile-card {
|
||||
background: #fff; border-radius: 20rpx; padding: 28rpx 24rpx; margin-bottom: 20rpx;
|
||||
}
|
||||
.profile-row { display: flex; align-items: center; gap: 20rpx; margin-bottom: 20rpx; }
|
||||
.avatar { width: 72rpx; height: 72rpx; border-radius: 50%; background: #e8f5f2; }
|
||||
.meta { display: flex; flex-direction: column; min-width: 0; }
|
||||
.name { font-size: 28rpx; font-weight: 600; color: #1f2937; }
|
||||
.sub { font-size: 22rpx; color: #9ca3af; margin-top: 6rpx; }
|
||||
.patient-block { padding-top: 20rpx; border-top: 1rpx solid #eef2f1; }
|
||||
.patient-head {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 16rpx;
|
||||
}
|
||||
.patient-head-left { display: flex; align-items: center; gap: 12rpx; min-width: 0; flex: 1; }
|
||||
.p-name { font-size: 30rpx; font-weight: 600; color: #111827; }
|
||||
.sex-tag {
|
||||
font-size: 20rpx; padding: 2rpx 12rpx; border-radius: 8rpx;
|
||||
background: #e8f5f2; color: #2a9d8f;
|
||||
}
|
||||
.callback-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 8rpx 22rpx;
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid #6acdbb;
|
||||
background: #e8f5f2;
|
||||
}
|
||||
.callback-btn-text { font-size: 24rpx; color: #1a9b88; font-weight: 500; }
|
||||
.sex-tag.male { background: #e8f0fe; color: #3b82f6; }
|
||||
.sex-tag.female { background: #fce7f3; color: #db2777; }
|
||||
.p-sub { display: block; font-size: 24rpx; color: #6b7280; margin-top: 8rpx; }
|
||||
.tabs {
|
||||
display: flex; background: #fff; border-radius: 16rpx 16rpx 0 0;
|
||||
margin-bottom: 0; padding: 0 8rpx; border-bottom: 1rpx solid #eef2f1;
|
||||
}
|
||||
.tab {
|
||||
flex: 1; display: flex; flex-direction: column; align-items: center;
|
||||
padding: 22rpx 0 16rpx; position: relative;
|
||||
}
|
||||
.tab-text { font-size: 26rpx; color: #6b7280; }
|
||||
.tab.on .tab-text { color: #1a9b88; font-weight: 600; }
|
||||
.tab-bar {
|
||||
position: absolute; bottom: 0; width: 48rpx; height: 4rpx;
|
||||
border-radius: 4rpx; background: #6acdbb;
|
||||
}
|
||||
.tab-swiper { width: 100%; }
|
||||
.tab-scroll { width: 100%; box-sizing: border-box; }
|
||||
.health-card {
|
||||
background: #fff; border-radius: 0 0 16rpx 16rpx; padding: 24rpx;
|
||||
}
|
||||
.section-title {
|
||||
display: block; font-size: 26rpx; font-weight: 600; color: #1f2937; margin-bottom: 16rpx;
|
||||
}
|
||||
.info-row {
|
||||
display: flex; justify-content: space-between; align-items: flex-start;
|
||||
padding: 16rpx 0; border-bottom: 1rpx solid #f3f4f6; gap: 24rpx;
|
||||
}
|
||||
.info-row:last-child { border-bottom: none; }
|
||||
.info-label { font-size: 24rpx; color: #9ca3af; flex-shrink: 0; }
|
||||
.info-value { font-size: 24rpx; color: #374151; text-align: right; flex: 1; word-break: break-all; }
|
||||
.info-value.danger { color: #dc2626; font-weight: 600; }
|
||||
.empty-inline { text-align: center; color: #9ca3af; padding: 40rpx 0; font-size: 24rpx; }
|
||||
.record-card {
|
||||
background: #fff; border-radius: 14rpx; padding: 22rpx 20rpx; margin-bottom: 12rpx;
|
||||
display: flex; align-items: center; gap: 12rpx;
|
||||
}
|
||||
.record-main { flex: 1; min-width: 0; }
|
||||
.r-title { display: block; font-size: 26rpx; font-weight: 600; color: #111827; }
|
||||
.r-sub { display: block; font-size: 22rpx; color: #6b7280; margin-top: 6rpx; }
|
||||
.arrow { font-size: 36rpx; color: #d1d5db; line-height: 1; padding-left: 4rpx; }
|
||||
.empty { text-align: center; color: #9ca3af; padding: 80rpx 0; font-size: 28rpx; }
|
||||
</style>
|
||||
144
subPackages/sub_business_shared/user-patient/index.vue
Normal file
144
subPackages/sub_business_shared/user-patient/index.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<business-page-layout title="会员管理" :show-back="true">
|
||||
<view class="page">
|
||||
<view class="search-bar">
|
||||
<input
|
||||
class="search-input"
|
||||
v-model="keyword"
|
||||
placeholder="昵称 / 手机 / 就诊人"
|
||||
confirm-type="search"
|
||||
@confirm="reload"
|
||||
/>
|
||||
<view class="search-btn" @click="reload">查询</view>
|
||||
</view>
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<view v-else-if="!list.length" class="empty">暂无用户</view>
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="card"
|
||||
@click="goPatients(item)"
|
||||
>
|
||||
<view class="user-row">
|
||||
<image class="avatar" :src="avatarSrc(item.avatarurl)" mode="aspectFill" />
|
||||
<view class="text">
|
||||
<text class="name">{{ item.nickname || '—' }}</text>
|
||||
<text class="sub">
|
||||
ID:{{ item.id || '—' }}
|
||||
<template v-if="item.mobile"> · {{ item.mobile }}</template>
|
||||
</text>
|
||||
</view>
|
||||
<text class="chevron">›</text>
|
||||
</view>
|
||||
<!-- 就诊人标签化:一行多枚、自动换行 -->
|
||||
<view v-if="item.patients && item.patients.length" class="patients">
|
||||
<view
|
||||
v-for="p in item.patients"
|
||||
:key="p.up_id"
|
||||
class="patient-tag"
|
||||
>
|
||||
<text class="tag-text">{{ patientTagText(p) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="patients empty-p">暂无就诊人</view>
|
||||
</view>
|
||||
</view>
|
||||
</business-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 会员管理用户列表:就诊人以标签展示;点用户进入就诊人列表页
|
||||
*/
|
||||
import BusinessPageLayout from '@/subPackages/sub_business_shared/components/business-page-layout.vue';
|
||||
import { getWxUserList, pickProfileItems } from '@/api/userPatientProfile.js';
|
||||
|
||||
export default {
|
||||
components: { BusinessPageLayout },
|
||||
data() {
|
||||
return {
|
||||
keyword: '',
|
||||
list: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
onShow() {
|
||||
this.reload();
|
||||
},
|
||||
methods: {
|
||||
avatarSrc(raw) {
|
||||
const s = String(raw || '').trim();
|
||||
return s || '/static/image/default-avatar.png';
|
||||
},
|
||||
/** 标签文案:姓名 或 姓名 · 脱敏手机 */
|
||||
patientTagText(p) {
|
||||
const name = (p && p.name) || '—';
|
||||
const mobile = (p && p.mobile) ? String(p.mobile).trim() : '';
|
||||
return mobile ? `${name} · ${mobile}` : name;
|
||||
},
|
||||
async reload() {
|
||||
this.loading = true;
|
||||
try {
|
||||
// 无关键字时不要传 keyword,uni 请求会把 undefined 序列化成字符串 "undefined"
|
||||
const params = {
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
};
|
||||
const kw = (this.keyword || '').trim();
|
||||
if (kw) {
|
||||
params.keyword = kw;
|
||||
}
|
||||
const wrap = await getWxUserList(params);
|
||||
this.list = pickProfileItems(wrap);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
/** 跳转就诊人列表页 */
|
||||
goPatients(item) {
|
||||
const userId = Number((item && item.id) || 0);
|
||||
if (!userId) {
|
||||
uni.showToast({ title: '缺少用户信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const nick = encodeURIComponent((item && item.nickname) || '');
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_business_shared/user-patient/patients?user_id=${userId}&nickname=${nick}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.search-bar { display: flex; gap: 12rpx; margin-bottom: 20rpx; }
|
||||
.search-input {
|
||||
flex: 1; background: #fff; border-radius: 12rpx;
|
||||
padding: 16rpx 20rpx; font-size: 26rpx;
|
||||
}
|
||||
.search-btn {
|
||||
background: #6acdbb; color: #fff; padding: 16rpx 28rpx;
|
||||
border-radius: 12rpx; font-size: 26rpx;
|
||||
}
|
||||
.card {
|
||||
background: #fff; border-radius: 16rpx; padding: 20rpx 20rpx 16rpx; margin-bottom: 14rpx;
|
||||
}
|
||||
.user-row { display: flex; align-items: center; gap: 14rpx; }
|
||||
.avatar { width: 56rpx; height: 56rpx; border-radius: 50%; background: #e8f5f2; flex-shrink: 0; }
|
||||
.text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
|
||||
.name { font-size: 28rpx; color: #111827; font-weight: 500; }
|
||||
.sub { font-size: 22rpx; color: #9ca3af; }
|
||||
.chevron { font-size: 32rpx; color: #d1d5db; line-height: 1; }
|
||||
.patients {
|
||||
margin-top: 14rpx; padding-top: 14rpx; border-top: 1rpx solid #eef2f1;
|
||||
display: flex; flex-wrap: wrap; gap: 12rpx;
|
||||
}
|
||||
.patient-tag {
|
||||
padding: 6rpx 16rpx; border-radius: 8rpx;
|
||||
background: #f0faf7; border: 1rpx solid #6acdbb;
|
||||
}
|
||||
.tag-text { font-size: 22rpx; color: #1a9b88; line-height: 1.4; }
|
||||
.empty-p { font-size: 22rpx; color: #d1d5db; }
|
||||
.empty { text-align: center; color: #9ca3af; padding: 80rpx 0; font-size: 28rpx; }
|
||||
</style>
|
||||
118
subPackages/sub_business_shared/user-patient/patients.vue
Normal file
118
subPackages/sub_business_shared/user-patient/patients.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<business-page-layout :title="pageTitle" :show-back="true">
|
||||
<view class="page">
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<!-- 小程序端:勿把 v-else-if 与 v-for 写在同级,否则有数据也可能只显示空态 -->
|
||||
<block v-else>
|
||||
<view v-if="!patientList.length" class="empty">暂无就诊人</view>
|
||||
<view
|
||||
v-for="p in patientList"
|
||||
:key="p.up_id"
|
||||
class="card"
|
||||
@click="goDetail(p)"
|
||||
>
|
||||
<view class="card-main">
|
||||
<view class="name-row">
|
||||
<text class="pname">{{ p.name || p.patient_name || '—' }}</text>
|
||||
<!-- 小程序 :class 不支持 sexClass(p) 方法调用,用对象字面量 -->
|
||||
<text
|
||||
v-if="p.sex == 1 || p.sex == 2"
|
||||
class="sex-tag"
|
||||
:class="{ male: p.sex == 1, female: p.sex == 2 }"
|
||||
>{{ p.sex == 1 ? '男' : '女' }}</text>
|
||||
</view>
|
||||
<text class="psub">
|
||||
<template v-if="p.mobile || p.patient_mobile">{{ p.mobile || p.patient_mobile }}</template>
|
||||
<template v-else>暂无手机号</template>
|
||||
</text>
|
||||
</view>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</business-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 某微信用户下的就诊人列表页:左主信息 + 右箭头,点进入详情
|
||||
*/
|
||||
import BusinessPageLayout from '@/subPackages/sub_business_shared/components/business-page-layout.vue';
|
||||
import { getPatientListByUser, pickProfileItems } from '@/api/userPatientProfile.js';
|
||||
|
||||
export default {
|
||||
components: { BusinessPageLayout },
|
||||
data() {
|
||||
return {
|
||||
userId: 0,
|
||||
nickname: '',
|
||||
patientList: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
return this.nickname ? `就诊人 · ${this.nickname}` : '就诊人列表';
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.userId = Number((options && options.user_id) || 0);
|
||||
try {
|
||||
this.nickname = decodeURIComponent((options && options.nickname) || '');
|
||||
} catch (e) {
|
||||
this.nickname = (options && options.nickname) || '';
|
||||
}
|
||||
this.reload();
|
||||
},
|
||||
methods: {
|
||||
async reload() {
|
||||
if (!this.userId) {
|
||||
this.patientList = [];
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const wrap = await getPatientListByUser(this.userId);
|
||||
// 有 items 就展示,避免 ok 误判把有效数据丢掉
|
||||
this.patientList = pickProfileItems(wrap);
|
||||
} catch (e) {
|
||||
console.error('就诊人列表加载失败', e);
|
||||
this.patientList = [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
goDetail(p) {
|
||||
const upId = Number((p && (p.up_id || p.id)) || 0);
|
||||
if (!upId) {
|
||||
uni.showToast({ title: '缺少就诊人信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const name = encodeURIComponent((p && (p.name || p.patient_name)) || '');
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_business_shared/user-patient/detail?up_id=${upId}&name=${name}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.card {
|
||||
background: #fff; border-radius: 16rpx; padding: 24rpx 20rpx; margin-bottom: 14rpx;
|
||||
display: flex; align-items: center; gap: 12rpx;
|
||||
}
|
||||
.card-main { flex: 1; min-width: 0; }
|
||||
.name-row { display: flex; align-items: center; gap: 12rpx; }
|
||||
.pname { font-size: 30rpx; color: #111827; font-weight: 500; }
|
||||
.sex-tag {
|
||||
font-size: 20rpx; padding: 2rpx 12rpx; border-radius: 8rpx;
|
||||
background: #e8f5f2; color: #2a9d8f;
|
||||
}
|
||||
.sex-tag.male { background: #e8f0fe; color: #3b82f6; }
|
||||
.sex-tag.female { background: #fce7f3; color: #db2777; }
|
||||
.psub { display: block; font-size: 24rpx; color: #9ca3af; margin-top: 8rpx; }
|
||||
.arrow { font-size: 36rpx; color: #d1d5db; line-height: 1; }
|
||||
.empty { text-align: center; color: #9ca3af; padding: 80rpx 0; font-size: 28rpx; }
|
||||
</style>
|
||||
440
subPackages/sub_business_shared/user-patient/register-detail.vue
Normal file
440
subPackages/sub_business_shared/user-patient/register-detail.vue
Normal file
@@ -0,0 +1,440 @@
|
||||
<template>
|
||||
<business-page-layout title="挂号详情" :show-back="true">
|
||||
<view class="page">
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<template v-else-if="!detail.id">
|
||||
<view class="empty">暂无挂号信息</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- 有药品/回答时才出 Tab;仅基本信息时不展示 Tab 栏 -->
|
||||
<view v-if="visibleTabs.length > 1" class="tabs">
|
||||
<view
|
||||
v-for="(t, idx) in visibleTabs"
|
||||
:key="t.key"
|
||||
class="tab"
|
||||
:class="{ on: tabIndex === idx }"
|
||||
@click="selectTab(idx)"
|
||||
>
|
||||
<text class="tab-text">{{ t.label }}</text>
|
||||
<view v-if="tabIndex === idx" class="tab-bar" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 多 Tab:固定高度 swiper,避免与外层页面滚动抢手势 -->
|
||||
<swiper
|
||||
v-if="visibleTabs.length > 1"
|
||||
class="tab-swiper"
|
||||
:style="{ height: swiperHeightPx + 'px' }"
|
||||
:current="tabIndex"
|
||||
:duration="200"
|
||||
@change="onTabSwiperChange"
|
||||
>
|
||||
<swiper-item v-for="t in visibleTabs" :key="'sw-' + t.key">
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="tab-scroll"
|
||||
:style="{ height: swiperHeightPx + 'px' }"
|
||||
>
|
||||
<view v-if="t.key === 'basic'" class="card">
|
||||
<view class="row">
|
||||
<text class="label">订单号</text>
|
||||
<text class="value">{{ detail.order_no || detail.order_number || '—' }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">诊所</text>
|
||||
<text class="value">{{ detail.store || '—' }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">医生</text>
|
||||
<text class="value">{{ detail.doctor || '—' }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">挂号类型</text>
|
||||
<text class="value">{{ detail.type_text || '—' }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">状态</text>
|
||||
<text class="value accent">{{ detail.status_text || '—' }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">金额</text>
|
||||
<text class="value">¥{{ formatPrice(detail.price) }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">支付</text>
|
||||
<text class="value">{{ Number(detail.is_pay) === 1 ? '已支付' : '未支付' }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">创建时间</text>
|
||||
<text class="value">{{ detail.created_at || '—' }}</text>
|
||||
</view>
|
||||
<view v-if="detail.pay_time" class="row">
|
||||
<text class="label">支付时间</text>
|
||||
<text class="value">{{ detail.pay_time }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="t.key === 'drugs'" class="card">
|
||||
<view v-if="!drugRows.length" class="empty-inline">暂无选药</view>
|
||||
<view v-for="row in drugRows" :key="row._wxKey" class="drug-row">
|
||||
<image
|
||||
v-if="row.image"
|
||||
class="drug-thumb"
|
||||
:src="row.image"
|
||||
mode="aspectFill"
|
||||
@click="previewDrug(row.image)"
|
||||
/>
|
||||
<view v-else class="drug-thumb drug-thumb-placeholder">
|
||||
<text class="drug-thumb-letter">{{ drugThumbLetter(row.name) }}</text>
|
||||
</view>
|
||||
<view class="drug-meta">
|
||||
<text class="drug-name">{{ row.name || '药品' }}</text>
|
||||
<text class="drug-spec">{{ row.specification || '暂无规格' }}</text>
|
||||
</view>
|
||||
<text class="drug-qty">×{{ row.quantity }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="t.key === 'answers'" class="card">
|
||||
<view v-if="!answerRecords.length" class="empty-inline">暂无回答</view>
|
||||
<view v-for="(a, aidx) in answerRecords" :key="'a-' + aidx" class="row answer-row">
|
||||
<text class="label">{{ a.question || '问题' }}</text>
|
||||
<text class="value">{{ a.answer || '—' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<!-- 仅基本信息:无滑动 -->
|
||||
<view v-else class="card">
|
||||
<view class="row">
|
||||
<text class="label">订单号</text>
|
||||
<text class="value">{{ detail.order_no || detail.order_number || '—' }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">诊所</text>
|
||||
<text class="value">{{ detail.store || '—' }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">医生</text>
|
||||
<text class="value">{{ detail.doctor || '—' }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">挂号类型</text>
|
||||
<text class="value">{{ detail.type_text || '—' }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">状态</text>
|
||||
<text class="value accent">{{ detail.status_text || '—' }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">金额</text>
|
||||
<text class="value">¥{{ formatPrice(detail.price) }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">支付</text>
|
||||
<text class="value">{{ Number(detail.is_pay) === 1 ? '已支付' : '未支付' }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">创建时间</text>
|
||||
<text class="value">{{ detail.created_at || '—' }}</text>
|
||||
</view>
|
||||
<view v-if="detail.pay_time" class="row">
|
||||
<text class="label">支付时间</text>
|
||||
<text class="value">{{ detail.pay_time }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</business-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 挂号详情:基本信息 + 条件 Tab(本次选药 / 回答记录)
|
||||
* 多 Tab 时用固定高度 swiper 左右滑切换,并在首次进入时短暂提示
|
||||
*/
|
||||
import BusinessPageLayout from '@/subPackages/sub_business_shared/components/business-page-layout.vue';
|
||||
import { getUserPatientRegisterDetail } from '@/api/userPatientProfile.js';
|
||||
|
||||
/** 左右滑切换提示只展示一次,避免每次进入打扰 */
|
||||
const SWIPE_TIP_STORAGE_KEY = 'user_patient_register_detail_swipe_tip';
|
||||
|
||||
export default {
|
||||
components: { BusinessPageLayout },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
registerId: 0,
|
||||
activeTab: 'basic',
|
||||
/** swiper 当前下标,与 activeTab / visibleTabs 同步 */
|
||||
tabIndex: 0,
|
||||
/** 点击 Tab 与 swiper change 互斥,避免回环 */
|
||||
tabSwiperChanging: false,
|
||||
/** 按窗口算出的 swiper 像素高度,保证横向滑动可用 */
|
||||
swiperHeightPx: 400,
|
||||
detail: {},
|
||||
registerInfo: null,
|
||||
answerRecords: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
/** 药品行:多选西药优先,否则单药 */
|
||||
drugRows() {
|
||||
const info = this.registerInfo;
|
||||
if (!info || typeof info !== 'object') return [];
|
||||
const arr = info.selected_western_drugs;
|
||||
const fallbackQty = info.number != null && info.number !== '' ? Number(info.number) : 1;
|
||||
if (Array.isArray(arr) && arr.length) {
|
||||
return arr.map((d, idx) => {
|
||||
const q = d && d.quantity != null ? Number(d.quantity) : fallbackQty;
|
||||
const id = d && (d.drug_id || d.id);
|
||||
return {
|
||||
_wxKey: 'd-' + (id != null ? id : idx),
|
||||
name: (d && d.name) || '',
|
||||
specification: (d && (d.specification || d.spec)) || '',
|
||||
image: (d && (d.image || d.drug_image)) || '',
|
||||
quantity: q >= 1 ? q : 1,
|
||||
};
|
||||
});
|
||||
}
|
||||
if (info.drug && info.drug.name) {
|
||||
return [{
|
||||
_wxKey: 'd-' + (info.drug.drug_id || info.drug.id || 0),
|
||||
name: info.drug.name,
|
||||
specification: info.drug.specification || info.drug.spec || '',
|
||||
image: info.drug.image || info.drug.drug_image || '',
|
||||
quantity: fallbackQty >= 1 ? fallbackQty : 1,
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
hasDrugs() {
|
||||
return this.drugRows.length > 0;
|
||||
},
|
||||
hasAnswers() {
|
||||
return Array.isArray(this.answerRecords) && this.answerRecords.length > 0;
|
||||
},
|
||||
/** 仅展示有数据的 Tab;基本信息始终保留 */
|
||||
visibleTabs() {
|
||||
const all = [
|
||||
{ key: 'basic', label: '基本信息' },
|
||||
{ key: 'drugs', label: '本次挂号选择药品', show: this.hasDrugs },
|
||||
{ key: 'answers', label: '回答记录', show: this.hasAnswers },
|
||||
];
|
||||
return all.filter((t) => t.show !== false);
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.calcSwiperHeight();
|
||||
const id = Number((options && options.id) || 0);
|
||||
this.registerId = id;
|
||||
let cached = {};
|
||||
try {
|
||||
cached = uni.getStorageSync('user_patient_register_detail') || {};
|
||||
} catch (e) {
|
||||
cached = {};
|
||||
}
|
||||
// 缓存作乐观展示;id 不一致则只用 URL 中的 id
|
||||
if (cached && Number(cached.id) === id) {
|
||||
this.detail = cached;
|
||||
} else {
|
||||
this.detail = { id };
|
||||
}
|
||||
if (id) {
|
||||
this.loadDetail(id);
|
||||
}
|
||||
},
|
||||
onReady() {
|
||||
// 布局就绪后再算一次,避免状态栏/导航高度未就绪
|
||||
this.calcSwiperHeight();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 按窗口高度算死 swiper 高度:窗口 − 状态栏 − 导航 − Tab − 页边距
|
||||
* 微信小程序 swiper 无明确高度时横向滑动常失效
|
||||
*/
|
||||
calcSwiperHeight() {
|
||||
try {
|
||||
const sys = uni.getSystemInfoSync() || {};
|
||||
const winH = Number(sys.windowHeight) || 600;
|
||||
const statusBar = Number(sys.statusBarHeight) || 0;
|
||||
const navbar = Number(this.$navbarHeight) || 44;
|
||||
const rpx = (Number(sys.windowWidth) || 375) / 750;
|
||||
const pagePad = Math.ceil(24 * rpx);
|
||||
const tabsH = Math.ceil(88 * rpx);
|
||||
const h = winH - statusBar - navbar - pagePad * 2 - tabsH;
|
||||
this.swiperHeightPx = h > 200 ? h : 200;
|
||||
} catch (e) {
|
||||
this.swiperHeightPx = 400;
|
||||
}
|
||||
},
|
||||
formatPrice(v) {
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) ? n.toFixed(2) : '0.00';
|
||||
},
|
||||
/** 无图时取药名首字作占位,与接诊面板一致 */
|
||||
drugThumbLetter(name) {
|
||||
const s = String(name || '').trim();
|
||||
return s ? s.charAt(0) : '药';
|
||||
},
|
||||
previewDrug(url) {
|
||||
if (!url) return;
|
||||
uni.previewImage({ urls: [url], current: url });
|
||||
},
|
||||
/**
|
||||
* 点击顶部 Tab:同步 activeTab + tabIndex(与工作台 swiper 防抖一致)
|
||||
*/
|
||||
selectTab(index) {
|
||||
if (this.tabSwiperChanging) return;
|
||||
const tab = this.visibleTabs[index];
|
||||
if (!tab) return;
|
||||
this.tabIndex = index;
|
||||
this.activeTab = tab.key;
|
||||
},
|
||||
/**
|
||||
* 左右滑动 swiper:同步 Tab 下标与 activeTab
|
||||
*/
|
||||
onTabSwiperChange(e) {
|
||||
const index = e && e.detail ? e.detail.current : 0;
|
||||
const tab = this.visibleTabs[index];
|
||||
if (!tab || tab.key === this.activeTab) return;
|
||||
this.tabSwiperChanging = true;
|
||||
this.tabIndex = index;
|
||||
this.activeTab = tab.key;
|
||||
this.$nextTick(() => {
|
||||
this.tabSwiperChanging = false;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 按 activeTab 校正 tabIndex(数据加载后 Tab 集合可能变化)
|
||||
*/
|
||||
syncTabIndexFromActive() {
|
||||
const keys = this.visibleTabs.map((t) => t.key);
|
||||
let idx = keys.indexOf(this.activeTab);
|
||||
if (idx < 0) {
|
||||
this.activeTab = 'basic';
|
||||
idx = 0;
|
||||
}
|
||||
this.tabIndex = idx;
|
||||
},
|
||||
/**
|
||||
* 多 Tab 时首次进入短暂提示「可左右滑动」;本地标记只提示一次
|
||||
*/
|
||||
maybeShowSwipeTip() {
|
||||
if (this.visibleTabs.length <= 1) return;
|
||||
let shown = '';
|
||||
try {
|
||||
shown = uni.getStorageSync(SWIPE_TIP_STORAGE_KEY);
|
||||
} catch (e) {
|
||||
shown = '';
|
||||
}
|
||||
if (shown) return;
|
||||
uni.showToast({
|
||||
title: '左右滑动可切换标签',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
try {
|
||||
uni.setStorageSync(SWIPE_TIP_STORAGE_KEY, '1');
|
||||
} catch (e) {
|
||||
// 写缓存失败不影响主流程
|
||||
}
|
||||
},
|
||||
/** 拉取挂号详情并同步 Tab 可见性 */
|
||||
async loadDetail(id) {
|
||||
this.loading = true;
|
||||
try {
|
||||
const wrap = await getUserPatientRegisterDetail(id);
|
||||
if (!wrap || !wrap.ok || !wrap.data) {
|
||||
uni.showToast({ title: '挂号详情加载失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const data = wrap.data;
|
||||
this.detail = {
|
||||
id: data.id,
|
||||
order_no: data.order_no,
|
||||
order_number: data.order_number,
|
||||
store: data.store,
|
||||
doctor: data.doctor,
|
||||
type: data.type,
|
||||
type_text: data.type_text,
|
||||
status_text: data.status_text,
|
||||
price: data.price,
|
||||
is_pay: data.is_pay,
|
||||
created_at: data.created_at,
|
||||
pay_time: data.pay_time,
|
||||
};
|
||||
this.registerInfo = data.register_info || null;
|
||||
this.answerRecords = Array.isArray(data.answer_records) ? data.answer_records : [];
|
||||
this.syncTabIndexFromActive();
|
||||
this.calcSwiperHeight();
|
||||
this.$nextTick(() => {
|
||||
this.maybeShowSwipeTip();
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
padding: 0 4rpx;
|
||||
}
|
||||
.tab {
|
||||
position: relative;
|
||||
padding: 8rpx 0 12rpx;
|
||||
}
|
||||
.tab-text { font-size: 28rpx; color: #6b7280; }
|
||||
.tab.on .tab-text { color: #1a9b88; font-weight: 600; }
|
||||
.tab-bar {
|
||||
position: absolute; left: 20%; right: 20%; bottom: 0;
|
||||
height: 6rpx; border-radius: 6rpx; background: #6acdbb;
|
||||
}
|
||||
.tab-swiper {
|
||||
width: 100%;
|
||||
}
|
||||
.tab-scroll {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.card {
|
||||
background: #fff; border-radius: 20rpx; padding: 8rpx 24rpx;
|
||||
}
|
||||
.row {
|
||||
display: flex; justify-content: space-between; align-items: flex-start;
|
||||
padding: 24rpx 0; border-bottom: 1rpx solid #f3f4f6; gap: 24rpx;
|
||||
}
|
||||
.row:last-child { border-bottom: none; }
|
||||
.answer-row .label { max-width: 46%; }
|
||||
.label { font-size: 26rpx; color: #9ca3af; flex-shrink: 0; }
|
||||
.value { font-size: 26rpx; color: #1f2937; text-align: right; flex: 1; word-break: break-all; }
|
||||
.value.accent { color: #1a9b88; font-weight: 600; }
|
||||
.drug-row {
|
||||
display: flex; align-items: flex-start; gap: 16rpx;
|
||||
padding: 24rpx 0; border-bottom: 1rpx solid #f3f4f6;
|
||||
}
|
||||
.drug-row:last-child { border-bottom: none; }
|
||||
.drug-thumb {
|
||||
width: 72rpx; height: 72rpx; border-radius: 10rpx; background: #f3f4f6; flex-shrink: 0;
|
||||
}
|
||||
.drug-thumb-placeholder {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: #e5e7eb;
|
||||
}
|
||||
.drug-thumb-letter { font-size: 28rpx; color: #6b7280; font-weight: 600; }
|
||||
.drug-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6rpx; }
|
||||
.drug-name { font-size: 28rpx; color: #1f2937; font-weight: 500; }
|
||||
.drug-spec { font-size: 22rpx; color: #9ca3af; }
|
||||
.drug-qty { font-size: 28rpx; color: #1f2937; font-weight: 600; flex-shrink: 0; }
|
||||
.empty { text-align: center; color: #9ca3af; padding: 80rpx 0; font-size: 28rpx; }
|
||||
.empty-inline { text-align: center; color: #9ca3af; padding: 48rpx 0; font-size: 26rpx; }
|
||||
</style>
|
||||
@@ -214,6 +214,8 @@ export function mapOrderListRow(item) {
|
||||
deliveryText: deliveryMethodText(item.delivery_method),
|
||||
deliveryClass: 'tag-delivery',
|
||||
userNickname: user.nickname || '-',
|
||||
patientName: (item.user_patient && item.user_patient.name) || item.patient || '-',
|
||||
upId: Number(item.up_id || (item.user_patient && item.user_patient.id) || 0),
|
||||
payTime: formatDateTime(item.pay_time),
|
||||
createdAt: formatDateTime(item.created_at),
|
||||
storeName: store.name || '-',
|
||||
@@ -346,7 +348,12 @@ export function mapPrescriptionDetailView(item) {
|
||||
const name = d.name || d.drug_name || '—'
|
||||
const num = d.number != null ? d.number : ''
|
||||
const way = (d.use_way && d.use_way.name) || d.useWay || '煎服'
|
||||
return { name, qty: num ? num + ' /g' : '', usage: way }
|
||||
return {
|
||||
name,
|
||||
qty: num ? num + ' /g' : '',
|
||||
usage: way,
|
||||
specification: d.specification || '',
|
||||
}
|
||||
})
|
||||
: []
|
||||
recipes.push({
|
||||
@@ -369,6 +376,7 @@ export function mapPrescriptionDetailView(item) {
|
||||
name: d.name || d.drug_name || '—',
|
||||
qty: (d.number != null ? d.number : '') + unitName,
|
||||
usage: d.useWay || (d.use_way && d.use_way.name) || '',
|
||||
specification: d.specification || '',
|
||||
}],
|
||||
usage: '',
|
||||
process: '',
|
||||
|
||||
@@ -78,7 +78,8 @@ const FINANCE_MENUS = [
|
||||
]
|
||||
|
||||
const BUSINESS_MENUS = [
|
||||
{ key: 'order', label: '商品订单', desc: '全平台订单概览', icon: 'bag-fill', theme: GLOBAL_THEMES[3], path: '/subPackages/sub_clinic_admin/order/index' },
|
||||
{ key: 'order', label: '商品订单', desc: '本诊所订单概览', icon: 'bag-fill', theme: GLOBAL_THEMES[3], path: '/subPackages/sub_clinic_admin/order/index' },
|
||||
{ key: 'user-patient', label: '会员管理', desc: '本诊所微信用户与就诊人', icon: 'account', theme: GLOBAL_THEMES[0], path: '/subPackages/sub_business_shared/user-patient/index' },
|
||||
]
|
||||
|
||||
const SALESPERSON_MENUS = [
|
||||
|
||||
@@ -1,52 +1,41 @@
|
||||
<template>
|
||||
<clinic-page-layout title="我的" :show-tabbar="true" :tab-index="1">
|
||||
<view class="mine-container">
|
||||
|
||||
<!-- 个人信息面板(通栏) -->
|
||||
<!-- 资料区:圆形头像 + 姓名 + 角色(对齐医生「我的」) -->
|
||||
<view class="profile-panel">
|
||||
<!-- 超椭圆高质感头像 -->
|
||||
<view class="avatar-squircle">
|
||||
<view class="avatar-circle">
|
||||
<text>{{ avatarText }}</text>
|
||||
</view>
|
||||
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{ displayName }}</text>
|
||||
<!-- 角色标签:替代普通文本,提升专业感 -->
|
||||
<view class="role-badge">
|
||||
<text class="role-text">诊所管理员</text>
|
||||
</view>
|
||||
<text v-if="storeName && storeName !== '-'" class="store-line">{{ storeName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- <!– 信息与设置面板(通栏) –>-->
|
||||
<!-- <view class="setting-panel">-->
|
||||
<!-- <view class="setting-item">-->
|
||||
<!-- <text class="item-label">所属诊所</text>-->
|
||||
<!-- <text class="item-value">{{ storeName }}</text>-->
|
||||
<!-- </view>-->
|
||||
<!-- </view>-->
|
||||
|
||||
<!-- 操作面板(通栏) -->
|
||||
<!-- 白色菜单卡:绑定微信 + 切换账号 -->
|
||||
<view class="setting-panel">
|
||||
<wx-bind-entry ref="wxBindEntry" />
|
||||
<account-switch-entry />
|
||||
<view
|
||||
class="setting-item action-item"
|
||||
hover-class="item-hover"
|
||||
:hover-stay-time="100"
|
||||
@click="logout"
|
||||
>
|
||||
<text class="danger-text">退出登录</text>
|
||||
<!-- 保留原有的 u-icon -->
|
||||
<u-icon name="arrow-right" color="#C9CDD4" size="28" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 独立绿色退出按钮 -->
|
||||
<view class="logout-wrap">
|
||||
<u-button
|
||||
:throttle-time="0"
|
||||
shape="circle"
|
||||
:custom-style="{ backgroundColor: '#6ACDBB', color: '#fff', height: '96rpx' }"
|
||||
@click="logout"
|
||||
>退出登录</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</clinic-page-layout>
|
||||
</template>
|
||||
|
||||
<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'
|
||||
@@ -77,7 +66,7 @@ export default {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定退出登录?',
|
||||
confirmColor: '#f53f3f', // 将确认按钮改为警示红,提升用户体验
|
||||
confirmColor: '#6ACDBB',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return
|
||||
clearLoginStorage()
|
||||
@@ -90,15 +79,11 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 保持与工作台完全一致的色彩变量 */
|
||||
$theme-primary: #6acdbb;
|
||||
$theme-dark: #4db6a8;
|
||||
$color-page-bg: #F5F7F8;
|
||||
$color-text-title: #222B2A;
|
||||
$color-text-body: #4E5969;
|
||||
$color-text-muted: #86909C;
|
||||
$color-border: #F2F3F5;
|
||||
$color-danger: #F53F3F; /* 现代系统标准红 */
|
||||
|
||||
.mine-container {
|
||||
padding: 0 0 60rpx;
|
||||
@@ -106,21 +91,17 @@ $color-danger: #F53F3F; /* 现代系统标准红 */
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 个人信息面板:通栏纯白 */
|
||||
.profile-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
padding: 60rpx 40rpx 50rpx; /* 顶部增加呼吸感 */
|
||||
padding: 60rpx 40rpx 50rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
/* 超椭圆头像:抛弃死板的纯圆,注入品牌色和微弱发光质感 */
|
||||
.avatar-squircle {
|
||||
.avatar-circle {
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
border-radius: 40rpx; /* 超椭圆倒角 */
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, $theme-primary 0%, $theme-dark 100%);
|
||||
box-shadow: 0 8rpx 24rpx rgba(106, 205, 187, 0.25);
|
||||
color: #ffffff;
|
||||
@@ -131,23 +112,18 @@ $color-danger: #F53F3F; /* 现代系统标准红 */
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
margin-left: 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: $color-text-title;
|
||||
margin-bottom: 12rpx;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
/* 角色标签:用线框+背景色取代纯文本,更具系统身份感 */
|
||||
.role-badge {
|
||||
align-self: flex-start;
|
||||
background-color: rgba(106, 205, 187, 0.1);
|
||||
@@ -155,62 +131,22 @@ $color-danger: #F53F3F; /* 现代系统标准红 */
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.role-text {
|
||||
font-size: 22rpx;
|
||||
color: $theme-dark;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 设置项面板(通栏组) */
|
||||
.store-line {
|
||||
margin-top: 12rpx;
|
||||
font-size: 26rpx;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
.setting-panel {
|
||||
background-color: #ffffff;
|
||||
margin-bottom: 24rpx;
|
||||
padding: 0 40rpx;
|
||||
padding: 8rpx 40rpx 16rpx;
|
||||
}
|
||||
|
||||
/* 单个设置项 */
|
||||
.setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 36rpx 0;
|
||||
border-bottom: 1rpx solid $color-border;
|
||||
transition: background-color 0.2s;
|
||||
.logout-wrap {
|
||||
padding: 0 40rpx 32rpx;
|
||||
}
|
||||
|
||||
/* 移除组内最后一个元素的底边框 */
|
||||
.setting-panel .setting-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-size: 30rpx;
|
||||
color: $color-text-title;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
font-size: 30rpx;
|
||||
color: $color-text-muted;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 交互列表项 */
|
||||
.action-item {
|
||||
/* 扩大点击热区补偿 padding */
|
||||
margin: 0 -40rpx;
|
||||
padding: 36rpx 40rpx;
|
||||
}
|
||||
|
||||
.item-hover {
|
||||
background-color: #F7F8FA;
|
||||
}
|
||||
|
||||
/* 退出登录专用红色 */
|
||||
.danger-text {
|
||||
font-size: 30rpx;
|
||||
color: $color-danger;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
<template>
|
||||
<view v-if="showBlock" class="express-panel">
|
||||
<view class="section-title">物流信息</view>
|
||||
<template v-if="display.hasData">
|
||||
<view class="row"><text>物流公司</text><text>{{ display.companyName }}</text></view>
|
||||
<view class="row"><text>运单号</text><text>{{ display.expressNo }}</text></view>
|
||||
<view class="row"><text>最新状态</text><text class="highlight-text">{{ display.stateText }}</text></view>
|
||||
|
||||
<view v-if="display.tracks.length" class="timeline">
|
||||
<!-- 多包裹 Tab -->
|
||||
<scroll-view v-if="packageList.length > 1" class="pkg-tabs" scroll-x>
|
||||
<view
|
||||
v-for="(pkg, idx) in packageList"
|
||||
:key="idx"
|
||||
class="pkg-tab"
|
||||
:class="{ active: activePkg === idx }"
|
||||
@click="activePkg = idx"
|
||||
>
|
||||
包裹{{ pkg.package_no || idx + 1 }}
|
||||
<text v-if="pkg.warehouse_name" class="pkg-sub">{{ pkg.warehouse_name }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<template v-if="currentDisplay.hasData">
|
||||
<view class="row"><text>物流公司</text><text>{{ currentDisplay.companyName }}</text></view>
|
||||
<view class="row"><text>运单号</text><text>{{ currentDisplay.expressNo }}</text></view>
|
||||
<view class="row"><text>最新状态</text><text class="highlight-text">{{ currentDisplay.stateText }}</text></view>
|
||||
<view v-if="currentDisplay.tracks.length" class="timeline">
|
||||
<view class="timeline-title">物流追踪</view>
|
||||
<view v-for="(t, idx) in display.tracks" :key="idx" class="track-item">
|
||||
<view v-for="(t, idx) in currentDisplay.tracks" :key="idx" class="track-item">
|
||||
<view class="track-dot" :class="{ 'active': idx === 0 }" />
|
||||
<view class="track-body">
|
||||
<text class="track-status" :class="{ 'active': idx === 0 }">{{ t.status }}</text>
|
||||
@@ -18,7 +30,9 @@
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<view v-else class="empty-express">暂无物流信息</view>
|
||||
<view v-else class="empty-express">
|
||||
{{ currentPkg && Number(currentPkg.is_send) === 0 ? '本包裹尚未发货' : '暂无物流信息' }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -31,14 +45,39 @@ export default {
|
||||
express: { type: Object, default: null },
|
||||
show: { type: Boolean, default: true },
|
||||
},
|
||||
data() {
|
||||
return { activePkg: 0 }
|
||||
},
|
||||
computed: {
|
||||
showBlock() {
|
||||
return this.show
|
||||
},
|
||||
display() {
|
||||
/** 优先 packages,否则当作单包 */
|
||||
packageList() {
|
||||
const pkgs = this.express && Array.isArray(this.express.packages) ? this.express.packages : []
|
||||
if (pkgs.length) return pkgs
|
||||
if (this.express && (this.express.express_no || this.express.detail)) {
|
||||
return [{ package_no: 1, warehouse_name: '', is_send: 1, express: this.express }]
|
||||
}
|
||||
return []
|
||||
},
|
||||
currentPkg() {
|
||||
return this.packageList[this.activePkg] || null
|
||||
},
|
||||
currentDisplay() {
|
||||
const pkg = this.currentPkg
|
||||
if (pkg && pkg.express) return mapExpressDetail(pkg.express)
|
||||
if (pkg && Number(pkg.is_send) === 0) {
|
||||
return { hasData: false, companyName: '', expressNo: '', stateText: '', tracks: [] }
|
||||
}
|
||||
return mapExpressDetail(this.express)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
express() {
|
||||
this.activePkg = 0
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -53,7 +92,6 @@ $color-border: #F2F3F5;
|
||||
background: #ffffff;
|
||||
padding: 32rpx 40rpx;
|
||||
margin-bottom: 24rpx;
|
||||
/* 移除圆角和阴影,采用通栏设计 */
|
||||
}
|
||||
|
||||
.section-title {
|
||||
@@ -63,6 +101,34 @@ $color-border: #F2F3F5;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.pkg-tabs {
|
||||
white-space: nowrap;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.pkg-tab {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 12rpx 24rpx;
|
||||
margin-right: 12rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #f5f5f5;
|
||||
font-size: 26rpx;
|
||||
color: $color-text-muted;
|
||||
|
||||
&.active {
|
||||
background: rgba(106, 205, 187, 0.15);
|
||||
color: $theme-primary;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.pkg-sub {
|
||||
font-size: 20rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -127,7 +193,6 @@ $color-border: #F2F3F5;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* 最后一个元素去掉线 */
|
||||
.track-item:last-child .track-body {
|
||||
border-left-color: transparent;
|
||||
padding-bottom: 0;
|
||||
@@ -165,4 +230,4 @@ $color-border: #F2F3F5;
|
||||
font-size: 26rpx;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,187 +1,174 @@
|
||||
<template>
|
||||
<drawer-page-container
|
||||
v-model="show"
|
||||
title="处方详情"
|
||||
height="85%"
|
||||
tall
|
||||
:show-close="true"
|
||||
:closeable="true"
|
||||
@close="onClose"
|
||||
<!-- 小程序抽屉须用 page-container + v-if,避免 u-popup 在订单详情页底内联 -->
|
||||
<page-container
|
||||
v-if="show"
|
||||
:show="show"
|
||||
:overlay="true"
|
||||
position="bottom"
|
||||
:round="true"
|
||||
@beforeleave="onClose"
|
||||
@clickoverlay="onClose"
|
||||
>
|
||||
<view class="popup-wrap">
|
||||
<scroll-view v-if="view" scroll-y class="popup-scroll">
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<!-- 处方头部 -->
|
||||
<view class="head">
|
||||
<view class="head_record_top">
|
||||
<view class="record">
|
||||
处方编号:{{ view.prescriptionNo }}
|
||||
<view class="rx-drawer">
|
||||
<view class="rx-drawer-head">
|
||||
<text class="rx-drawer-title">处方详情</text>
|
||||
<text class="rx-drawer-close" @click="onClose">关闭</text>
|
||||
</view>
|
||||
<view class="popup-wrap">
|
||||
<scroll-view v-if="view" scroll-y class="popup-scroll">
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<view class="head">
|
||||
<view class="head_record_top">
|
||||
<view class="record">
|
||||
处方编号:{{ view.prescriptionNo }}
|
||||
</view>
|
||||
<view class="record_state">
|
||||
{{ view.typeText }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="record_state">
|
||||
{{ view.typeText }}
|
||||
<view class="head_record">
|
||||
<image :src="view.sealImage || '/static/group/xkyard.png'" mode="aspectFit"></image>
|
||||
<view class="record_yard">
|
||||
<view class="yard">
|
||||
{{ view.storeName }}
|
||||
</view>
|
||||
<view class="state">
|
||||
处方笺
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="head_record_time">
|
||||
<text style="text-align: right;">开具日期:{{ view.createdAt }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 动态医院与公章 -->
|
||||
<view class="head_record">
|
||||
<image :src="view.sealImage || '/static/group/xkyard.png'" mode="aspectFit"></image>
|
||||
<view class="record_yard">
|
||||
<view class="yard">
|
||||
{{ view.storeName }}
|
||||
<view class="my_info">
|
||||
<view class="info">
|
||||
<view class="info_item">
|
||||
<view class="name">姓名:<text>{{ view.patientName }}</text></view>
|
||||
</view>
|
||||
<view class="state">
|
||||
处方笺
|
||||
<view class="info_item">
|
||||
<view class="name">性别:<text>{{ view.patientSex }}</text></view>
|
||||
</view>
|
||||
<view class="info_item">
|
||||
<view class="name">年龄:<text>{{ view.patientAge }}</text></view>
|
||||
</view>
|
||||
<view class="info_item">
|
||||
<view class="name">类别:<text>{{ view.category }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="info_item">
|
||||
<view class="name">科室:<text>{{ view.departName }}</text></view>
|
||||
</view>
|
||||
<view class="info_item" v-if="view.patientMobile">
|
||||
<view class="name">电话:<text>{{ view.patientMobile }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="info_item">
|
||||
<view class="names">诊断:<text>{{ view.diagnose }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="view.tcmSyndrome || view.tcmMethod || view.tcmDisease" class="info">
|
||||
<view v-if="view.tcmSyndrome" class="info_item">
|
||||
<view class="names">中医证候:<text>{{ view.tcmSyndrome }}</text></view>
|
||||
</view>
|
||||
<view v-if="view.tcmMethod" class="info_item">
|
||||
<view class="names">中医治法:<text>{{ view.tcmMethod }}</text></view>
|
||||
</view>
|
||||
<view v-if="view.tcmDisease" class="info_item">
|
||||
<view class="names">中医疾病:<text>{{ view.tcmDisease }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间 -->
|
||||
<view class="head_record_time">
|
||||
<text style="text-align: right;">开具日期:{{ view.createdAt }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 患者信息 -->
|
||||
<view class="my_info">
|
||||
<view class="info">
|
||||
<view class="info_item">
|
||||
<view class="name">姓名:<text>{{ view.patientName }}</text></view>
|
||||
</view>
|
||||
<view class="info_item">
|
||||
<view class="name">性别:<text>{{ view.patientSex }}</text></view>
|
||||
</view>
|
||||
<view class="info_item">
|
||||
<view class="name">年龄:<text>{{ view.patientAge }}</text></view>
|
||||
</view>
|
||||
<view class="info_item">
|
||||
<view class="name">类别:<text>{{ view.category }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info">
|
||||
<view class="info_item">
|
||||
<view class="name">科室:<text>{{ view.departName }}</text></view>
|
||||
</view>
|
||||
<view class="info_item" v-if="view.patientMobile">
|
||||
<view class="name">电话:<text>{{ view.patientMobile }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info">
|
||||
<view class="info_item">
|
||||
<view class="names">诊断:<text>{{ view.diagnose }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 中医专属字段 -->
|
||||
<view v-if="view.tcmSyndrome || view.tcmMethod || view.tcmDisease" class="info">
|
||||
<view v-if="view.tcmSyndrome" class="info_item">
|
||||
<view class="names">中医证候:<text>{{ view.tcmSyndrome }}</text></view>
|
||||
</view>
|
||||
<view v-if="view.tcmMethod" class="info_item">
|
||||
<view class="names">中医治法:<text>{{ view.tcmMethod }}</text></view>
|
||||
</view>
|
||||
<view v-if="view.tcmDisease" class="info_item">
|
||||
<view class="names">中医疾病:<text>{{ view.tcmDisease }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bg">
|
||||
<!-- 处方明细区 (Rp) -->
|
||||
<view class="my_medical">
|
||||
<view class="title">Rp</view>
|
||||
|
||||
<view v-for="(recipe, rIdx) in view.recipes" :key="rIdx" class="item">
|
||||
<view class="name">
|
||||
<view class="_name" v-for="(drug, dIdx) in recipe.drugs" :key="dIdx">
|
||||
<view class="text">
|
||||
<text>{{ drug.name }}</text>
|
||||
<text class="_abbr" v-if="drug.usage">[{{ drug.usage }}]</text>
|
||||
<view class="bg">
|
||||
<view class="my_medical">
|
||||
<view class="title">Rp</view>
|
||||
<view v-for="(recipe, rIdx) in view.recipes" :key="rIdx" class="item">
|
||||
<view class="name">
|
||||
<view class="_name" v-for="(drug, dIdx) in recipe.drugs" :key="dIdx">
|
||||
<view class="text">
|
||||
<text>{{ drug.name }}</text>
|
||||
<text v-if="drug.specification" class="drug-spec">{{ drug.specification }}</text>
|
||||
<text class="_abbr" v-if="drug.usage">[{{ drug.usage }}]</text>
|
||||
</view>
|
||||
<text class="name_num">{{ drug.qty }}</text>
|
||||
</view>
|
||||
<text class="name_num">{{ drug.qty }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="details" v-if="recipe.usage || recipe.process">
|
||||
<view class="text">
|
||||
<text v-if="recipe.usage">用法:{{ recipe.usage }}</text>
|
||||
<text v-if="recipe.process" style="margin-left: 10rpx;">包装方式:{{ recipe.process }}</text>
|
||||
<view class="details" v-if="recipe.usage || recipe.process">
|
||||
<view class="text">
|
||||
<text v-if="recipe.usage">用法:{{ recipe.usage }}</text>
|
||||
<text v-if="recipe.process" style="margin-left: 10rpx;">包装方式:{{ recipe.process }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="doctor_order">
|
||||
<view class="yz">
|
||||
<text class="order_info_left">医嘱:</text>
|
||||
<view class="order_info">
|
||||
<view class="info">{{ view.doctorOrder !== '-' ? view.doctorOrder : '无' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="titles">处方开具已完毕</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 医嘱 -->
|
||||
<view class="doctor_order">
|
||||
<view class="yz">
|
||||
<text class="order_info_left">医嘱:</text>
|
||||
<view class="order_info">
|
||||
<view class="info">{{ view.doctorOrder !== '-' ? view.doctorOrder : '无' }}</view>
|
||||
<view class="my_doctor">
|
||||
<view class="doctor_info">
|
||||
<view class="info">
|
||||
<text>医师</text>
|
||||
<image v-if="view.doctorSignImage" :src="checkImageUrl(view.doctorSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name">{{ view.doctorName }}</text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>审核药师</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>发药人</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>核对人</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>调配人</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="titles">处方开具已完毕</view>
|
||||
<view class="price" v-if="view.totalPayPrice">
|
||||
价格 ¥{{ view.totalPayPrice }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="my_prompt">
|
||||
<view class="title">
|
||||
温馨提示:请遵医嘱服药!处方{{ view.validHours }}小时有效!
|
||||
</view>
|
||||
<image src="/static/group/img-yzf.png" mode="aspectFit" v-if="view.status == 2"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 动态医生与药师签名区 -->
|
||||
<view class="my_doctor">
|
||||
<view class="doctor_info">
|
||||
<view class="info">
|
||||
<text>医师</text>
|
||||
<image v-if="view.doctorSignImage" :src="checkImageUrl(view.doctorSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name">{{ view.doctorName }}</text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>审核药师</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>发药人</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>核对人</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text>调配人</text>
|
||||
<image v-if="view.pharmacistSignImage" :src="checkImageUrl(view.pharmacistSignImage)" mode="aspectFit"></image>
|
||||
<text v-else class="name"></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 价格 -->
|
||||
<view class="price" v-if="view.totalPayPrice">
|
||||
价格 ¥{{ view.totalPayPrice }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 动态有效期温馨提示 -->
|
||||
<view class="my_prompt">
|
||||
<view class="title">
|
||||
温馨提示:请遵医嘱服药!处方{{ view.validHours }}小时有效!
|
||||
</view>
|
||||
<image src="/static/group/img-yzf.png" mode="aspectFit" v-if="view.status == 2"></image>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-else-if="loading" class="popup-loading">加载中...</view>
|
||||
<view v-else class="popup-loading">暂无数据</view>
|
||||
</scroll-view>
|
||||
<view v-else-if="loading" class="popup-loading">加载中...</view>
|
||||
<view v-else class="popup-loading">暂无数据</view>
|
||||
</view>
|
||||
</view>
|
||||
</drawer-page-container>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DrawerPageContainer from '@/subPackages/sub_business_shared/components/DrawerPageContainer.vue'
|
||||
/**
|
||||
* 处方详情抽屉:page-container 底部浮层(诊所管理端)
|
||||
*/
|
||||
import { getPrescriptionDetail } from '@/api/clinicAdmin.js'
|
||||
import { mapPrescriptionDetailView } from '@/subPackages/sub_clinic_admin/common/display.js'
|
||||
import { formatStoreNameWithHu } from '@/utils/formatStoreNameWithHu.js'
|
||||
|
||||
export default {
|
||||
components: { DrawerPageContainer },
|
||||
name: 'PrescriptionDetailPopup',
|
||||
data() {
|
||||
return {
|
||||
@@ -191,7 +178,7 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 解析签名图片,自动兼容 base64 和 http 链接
|
||||
/** 解析签名图片,自动兼容 base64 和 http 链接 */
|
||||
checkImageUrl(url) {
|
||||
if (!url) return ''
|
||||
return url.includes('http') ? url : 'data:image/jpeg;base64,' + url
|
||||
@@ -203,20 +190,27 @@ export default {
|
||||
this.loading = true
|
||||
getPrescriptionDetail(prescriptionId).then(w => {
|
||||
if (w.ok && w.data) {
|
||||
// 复用原有的处方解析逻辑
|
||||
const mapped = mapPrescriptionDetailView(w.data)
|
||||
|
||||
// --- 动态注入你新版 UI 需要的独有字段 ---
|
||||
mapped.storeName = w.data.store ? w.data.store.name : '未知诊所'
|
||||
mapped.storeName = formatStoreNameWithHu(
|
||||
w.data.store ? w.data.store.name : '未知诊所',
|
||||
w.data.is_online,
|
||||
)
|
||||
mapped.sealImage = w.data.store ? w.data.store.offical_seal : ''
|
||||
mapped.typeText = w.data.type == 1 ? '普通' : '常用'
|
||||
mapped.totalPayPrice = w.data.total_pay_price || '0.00'
|
||||
mapped.doctorSignImage = w.data.doctor_sign_image || ''
|
||||
mapped.pharmacistSignImage = w.data.Pharmacist_sign_image || ''
|
||||
mapped.doctorSignImage =
|
||||
w.data.doctor_sign_image
|
||||
|| (w.data.doctor_info && w.data.doctor_info.identity_info && w.data.doctor_info.identity_info.sign_image)
|
||||
|| (w.data.doctorInfo && w.data.doctorInfo.identityInfo && w.data.doctorInfo.identityInfo.sign_image)
|
||||
|| ''
|
||||
mapped.pharmacistSignImage =
|
||||
w.data.Pharmacist_sign_image
|
||||
|| (w.data.pharmacist_info && w.data.pharmacist_info.identity && w.data.pharmacist_info.identity.sign_image)
|
||||
|| (w.data.pharmacistInfo && w.data.pharmacistInfo.identity && w.data.pharmacistInfo.identity.sign_image)
|
||||
|| ''
|
||||
mapped.validHours = w.data.valid_hours || 72
|
||||
mapped.status = w.data.status
|
||||
mapped.patientMobile = w.data.patient ? w.data.patient.mobile : ''
|
||||
|
||||
this.view = mapped
|
||||
}
|
||||
}).finally(() => {
|
||||
@@ -232,20 +226,37 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rx-drawer {
|
||||
height: 85vh;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.rx-drawer-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 28rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.rx-drawer-title { font-size: 32rpx; font-weight: 600; color: #1d2129; }
|
||||
.rx-drawer-close { font-size: 28rpx; color: #6acdbb; }
|
||||
.popup-wrap {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
padding-top: 60rpx; /* 给默认的关闭按钮留出空间 */
|
||||
}
|
||||
|
||||
.popup-scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.popup-loading {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
@@ -443,6 +454,11 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.drug-spec {
|
||||
color: #8A92A3;
|
||||
font-size: 22rpx;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
._abbr {
|
||||
color: #A7ABB0;
|
||||
font-size: 20rpx;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<view>
|
||||
<clinic-page-layout title="订单详情" :show-back="true">
|
||||
<view class="detail-container">
|
||||
<!-- 头部面板:通栏,展示金额与状态 -->
|
||||
@@ -17,7 +18,14 @@
|
||||
<view class="sub-block">
|
||||
<view class="sub-block-title">基本信息</view>
|
||||
<view class="row"><text>订单类型</text><text>{{ detailExtra.orderTypeText }}</text></view>
|
||||
<view class="row"><text>患者</text><text>{{ info.patient || '-' }}</text></view>
|
||||
<view class="row">
|
||||
<text>患者</text>
|
||||
<text
|
||||
class="patient-link"
|
||||
:class="{ disabled: !patientUpId }"
|
||||
@click="openPatient"
|
||||
>{{ info.patient || '-' }}</text>
|
||||
</view>
|
||||
<view class="row"><text>医生</text><text>{{ doctorName }}</text></view>
|
||||
<view class="row"><text>处方来源</text><text>{{ storeName }}</text></view>
|
||||
<view class="row"><text>订单来源</text><text>{{ onlineText(info.is_online) }}</text></view>
|
||||
@@ -49,7 +57,14 @@
|
||||
<!-- 配送信息面板 -->
|
||||
<view v-if="info.id" class="detail-panel">
|
||||
<view class="section-title">{{ receiverBlockTitle }}</view>
|
||||
<view class="row"><text>{{ receiverLabel }}</text><text>{{ receiverName }}</text></view>
|
||||
<view class="row">
|
||||
<text>{{ receiverLabel }}</text>
|
||||
<text
|
||||
class="patient-link"
|
||||
:class="{ disabled: !patientUpId }"
|
||||
@click="openPatient"
|
||||
>{{ receiverName }}</text>
|
||||
</view>
|
||||
<view class="row"><text>联系电话</text><text>{{ receiverMobile }}</text></view>
|
||||
<view class="row" v-if="info.delivery_method === 0">
|
||||
<text>配送地址</text><text class="addr">{{ deliveryAddress }}</text>
|
||||
@@ -141,17 +156,25 @@
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 处方弹窗 -->
|
||||
<prescription-detail-popup ref="prescriptionPopup" />
|
||||
<order-price-percent-adjust-popup
|
||||
:visible="priceAdjustVisible"
|
||||
:current-discount="info.price_discount || 100"
|
||||
:quick-options="priceAdjustQuickOptions"
|
||||
:submitting="priceAdjustSubmitting"
|
||||
@close="closePriceAdjust"
|
||||
@confirm="(p) => onPriceAdjustConfirm(p, { adjustOrderPercent })"
|
||||
/>
|
||||
</clinic-page-layout>
|
||||
<!-- page-container 抽屉挂在页面根级,避免被 layout 裁切成页底内联 -->
|
||||
<prescription-detail-popup ref="prescriptionPopup" />
|
||||
<order-price-percent-adjust-popup
|
||||
:visible="priceAdjustVisible"
|
||||
:current-discount="info.price_discount || 100"
|
||||
:quick-options="priceAdjustQuickOptions"
|
||||
:submitting="priceAdjustSubmitting"
|
||||
@close="closePriceAdjust"
|
||||
@confirm="(p) => onPriceAdjustConfirm(p, { adjustOrderPercent })"
|
||||
/>
|
||||
<user-patient-detail
|
||||
v-if="patientDetailVisible"
|
||||
:visible="patientDetailVisible"
|
||||
:up-id="patientDetailUpId"
|
||||
:patient-name="patientDetailName"
|
||||
@close="patientDetailVisible = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -167,6 +190,7 @@ import {
|
||||
} from '@/api/clinicAdmin.js'
|
||||
import { orderPriceAdjustMixin } from '@/subPackages/sub_business_shared/common/orderPriceAdjustMixin.js'
|
||||
import OrderPricePercentAdjustPopup from '@/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup.vue'
|
||||
import UserPatientDetail from '@/subPackages/sub_business_shared/components/UserPatientDetail.vue'
|
||||
import {
|
||||
orderStatusText,
|
||||
deliveryMethodText,
|
||||
@@ -180,13 +204,22 @@ import { formatMoney } from '@/subPackages/sub_clinic_admin/common/format.js'
|
||||
|
||||
export default {
|
||||
mixins: [orderPriceAdjustMixin],
|
||||
components: { ClinicPageLayout, ExpressTimeline, PrescriptionDetailPopup, OrderPricePercentAdjustPopup },
|
||||
components: {
|
||||
ClinicPageLayout,
|
||||
ExpressTimeline,
|
||||
PrescriptionDetailPopup,
|
||||
OrderPricePercentAdjustPopup,
|
||||
UserPatientDetail,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: 0,
|
||||
info: {},
|
||||
expressInfo: null,
|
||||
detailExtra: {},
|
||||
patientDetailVisible: false,
|
||||
patientDetailUpId: 0,
|
||||
patientDetailName: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -209,6 +242,13 @@ export default {
|
||||
const s = this.info.store
|
||||
return (s && s.name) ? s.name : '-'
|
||||
},
|
||||
/** 就诊人 ID:订单 up_id 或关联 user_patient.id */
|
||||
patientUpId() {
|
||||
const up = Number(this.info.up_id || 0)
|
||||
if (up) return up
|
||||
const rel = this.info.user_patient
|
||||
return Number((rel && rel.id) || 0)
|
||||
},
|
||||
receiverBlockTitle() {
|
||||
return this.info.delivery_method === 0 ? '收货人信息' : '就诊人信息'
|
||||
},
|
||||
@@ -244,16 +284,54 @@ export default {
|
||||
this.id = Number(q.id || 0)
|
||||
this.load()
|
||||
},
|
||||
/** 离开页关闭处方抽屉,避免 page-container 残留遮罩导致列表白屏 */
|
||||
onHide() {
|
||||
this.closePrescriptionPopup()
|
||||
this.patientDetailVisible = false
|
||||
},
|
||||
onUnload() {
|
||||
this.closePrescriptionPopup()
|
||||
this.patientDetailVisible = false
|
||||
},
|
||||
methods: {
|
||||
/** 关闭处方抽屉 */
|
||||
closePrescriptionPopup() {
|
||||
const pop = this.$refs.prescriptionPopup
|
||||
if (pop && typeof pop.onClose === 'function') {
|
||||
pop.onClose()
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 打开患者视角抽屉(与订单列表一致)
|
||||
*/
|
||||
openPatient() {
|
||||
const upId = this.patientUpId
|
||||
if (!upId) {
|
||||
uni.showToast({ title: '缺少就诊人信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.patientDetailUpId = upId
|
||||
this.patientDetailName = this.info.patient || ''
|
||||
this.patientDetailVisible = true
|
||||
},
|
||||
load() {
|
||||
if (!this.id) {
|
||||
uni.showToast({ title: '缺少订单信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
getOrderDetail(this.id).then(w => {
|
||||
if (!w.ok) return
|
||||
if (!w.ok) {
|
||||
uni.showToast({ title: '订单加载失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.info = w.data || {}
|
||||
this.detailExtra = mapOrderDetailDisplay(this.info)
|
||||
this.loadPriceAdjustMeta(() => getPriceAdjustConfig(this.info.store_id))
|
||||
if (this.info.delivery_method === 0) {
|
||||
this.loadExpress()
|
||||
}
|
||||
}).catch(() => {
|
||||
uni.showToast({ title: '订单加载失败', icon: 'none' })
|
||||
})
|
||||
},
|
||||
loadExpress() {
|
||||
@@ -539,6 +617,11 @@ $color-danger: #F53F3F;
|
||||
|
||||
.price-main { font-size: 28rpx; color: $color-text-title; font-weight: 600; }
|
||||
.price-link { color: $theme-primary; text-decoration: underline; }
|
||||
.patient-link {
|
||||
color: $theme-primary;
|
||||
text-align: right;
|
||||
&.disabled { color: inherit; }
|
||||
}
|
||||
|
||||
/* ================= 底部动作栏 ================= */
|
||||
.actions-bar {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<view>
|
||||
<clinic-page-layout title="商品订单" :show-back="true">
|
||||
<view class="order-list-container">
|
||||
<!-- 顶部筛选 -->
|
||||
@@ -96,9 +97,13 @@
|
||||
|
||||
<!-- 信息岛 (Info Island):聚合次要信息的高级灰底区块 -->
|
||||
<view class="info-island">
|
||||
<view class="island-row" v-if="rowDisplay(item).userNickname !== '-'">
|
||||
<view
|
||||
class="island-row"
|
||||
v-if="rowDisplay(item).patientName !== '-' || rowDisplay(item).userNickname !== '-'"
|
||||
@click.stop="openPatient(item)"
|
||||
>
|
||||
<text class="island-label">就诊人</text>
|
||||
<text class="island-value">{{ rowDisplay(item).userNickname }}</text>
|
||||
<text class="island-value link">{{ rowDisplay(item).patientName !== '-' ? rowDisplay(item).patientName : rowDisplay(item).userNickname }}</text>
|
||||
</view>
|
||||
<view class="island-row">
|
||||
<text class="island-label">收件人</text>
|
||||
@@ -135,8 +140,16 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<prescription-detail-popup ref="prescriptionPopup" />
|
||||
</clinic-page-layout>
|
||||
<prescription-detail-popup ref="prescriptionPopup" />
|
||||
<user-patient-detail
|
||||
v-if="patientDetailVisible"
|
||||
:visible="patientDetailVisible"
|
||||
:up-id="patientDetailUpId"
|
||||
:patient-name="patientDetailName"
|
||||
@close="patientDetailVisible = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -144,6 +157,7 @@ import ClinicPageLayout from '@/subPackages/sub_clinic_admin/components/clinic-p
|
||||
import PageStickyHeader from '@/subPackages/sub_clinic_admin/components/PageStickyHeader.vue'
|
||||
import CollapsibleFilterPanel from '@/subPackages/sub_clinic_admin/components/CollapsibleFilterPanel.vue'
|
||||
import PrescriptionDetailPopup from './components/PrescriptionDetailPopup.vue'
|
||||
import UserPatientDetail from '@/subPackages/sub_business_shared/components/UserPatientDetail.vue'
|
||||
import { getOrderList, getOrderStatusOption, getOrderSaleAmount } from '@/api/clinicAdmin.js'
|
||||
import { withWxKey } from '@/utils/wxListKey.js'
|
||||
import { mapOrderListRow } from '@/subPackages/sub_clinic_admin/common/display.js'
|
||||
@@ -174,7 +188,7 @@ const PRESCRIPTION_TYPE_OPTIONS = [
|
||||
]
|
||||
|
||||
export default {
|
||||
components: { ClinicPageLayout, PageStickyHeader, CollapsibleFilterPanel, PrescriptionDetailPopup },
|
||||
components: { ClinicPageLayout, PageStickyHeader, CollapsibleFilterPanel, PrescriptionDetailPopup, UserPatientDetail },
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
@@ -188,6 +202,9 @@ export default {
|
||||
dateStart: '',
|
||||
dateEnd: '',
|
||||
statItems: [],
|
||||
patientDetailVisible: false,
|
||||
patientDetailUpId: 0,
|
||||
patientDetailName: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -214,10 +231,24 @@ export default {
|
||||
})
|
||||
this.checkDateEndAndReload()
|
||||
},
|
||||
/** 离开/隐藏时关掉处方抽屉,避免 page-container 残留白屏 */
|
||||
onHide() {
|
||||
this.closePrescriptionPopup()
|
||||
},
|
||||
onUnload() {
|
||||
this.closePrescriptionPopup()
|
||||
},
|
||||
onReachBottom() {
|
||||
this.load(this.page + 1, true)
|
||||
},
|
||||
methods: {
|
||||
/** 关闭处方抽屉 */
|
||||
closePrescriptionPopup() {
|
||||
const pop = this.$refs.prescriptionPopup
|
||||
if (pop && typeof pop.onClose === 'function') {
|
||||
pop.onClose()
|
||||
}
|
||||
},
|
||||
applyOrderFilter(f) {
|
||||
this.orderNo = f.orderNo
|
||||
this.statusIndex = f.statusIndex
|
||||
@@ -326,6 +357,17 @@ export default {
|
||||
if (!pId) return
|
||||
this.$refs.prescriptionPopup.open(pId)
|
||||
},
|
||||
openPatient(item) {
|
||||
const d = this.rowDisplay(item)
|
||||
const upId = Number(d.upId || 0)
|
||||
if (!upId) {
|
||||
uni.showToast({ title: '缺少就诊人信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.patientDetailUpId = upId
|
||||
this.patientDetailName = d.patientName !== '-' ? d.patientName : ''
|
||||
this.patientDetailVisible = true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -569,6 +611,9 @@ $color-text-muted: #86909C;
|
||||
color: $color-text-body;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
&.link {
|
||||
color: $theme-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.muted {
|
||||
|
||||
@@ -1,436 +1,188 @@
|
||||
<template>
|
||||
|
||||
<clinic-salesperson-page-layout title="我的" :show-tabbar="true" :tab-index="1">
|
||||
|
||||
<view class="mine-container">
|
||||
|
||||
<view class="profile-panel">
|
||||
|
||||
<view class="avatar-squircle">
|
||||
|
||||
<view class="avatar-circle">
|
||||
<text>{{ avatarText }}</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="user-info">
|
||||
|
||||
<text class="user-name">{{ displayName }}</text>
|
||||
|
||||
<view class="role-badge">
|
||||
|
||||
<text class="role-text">推广员</text>
|
||||
|
||||
</view>
|
||||
|
||||
<text v-if="phone" class="phone-line">{{ phone }}</text>
|
||||
|
||||
<text v-if="storeName" class="store-line">{{ storeName }}</text>
|
||||
|
||||
<text v-if="phone" class="meta-line">{{ phone }}</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view class="setting-panel">
|
||||
|
||||
<view class="setting-item">
|
||||
|
||||
<view v-if="storeName" class="setting-panel info-panel">
|
||||
<view class="info-row">
|
||||
<text class="item-label">所属诊所</text>
|
||||
|
||||
<text class="item-value">{{ storeName || '-' }}</text>
|
||||
|
||||
<text class="item-value">{{ storeName }}</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view class="setting-panel">
|
||||
|
||||
<wx-bind-entry ref="wxBindEntry" />
|
||||
|
||||
<account-switch-entry />
|
||||
|
||||
<view
|
||||
|
||||
class="setting-item action-item"
|
||||
|
||||
hover-class="item-hover"
|
||||
|
||||
:hover-stay-time="100"
|
||||
|
||||
@click="logout"
|
||||
|
||||
>
|
||||
|
||||
<text class="danger-text">退出登录</text>
|
||||
|
||||
<u-icon name="arrow-right" color="#C9CDD4" size="28" />
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="logout-wrap">
|
||||
<u-button
|
||||
:throttle-time="0"
|
||||
shape="circle"
|
||||
:custom-style="{ backgroundColor: '#6ACDBB', color: '#fff', height: '96rpx' }"
|
||||
@click="logout"
|
||||
>退出登录</u-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</clinic-salesperson-page-layout>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
/**
|
||||
* 推广员「我的」:对齐医生(菜单卡 + 绿色退出),保留所属诊所与绑定微信
|
||||
*/
|
||||
import ClinicSalespersonPageLayout from '@/subPackages/sub_clinic_salesperson/components/clinic-salesperson-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 { getClinicSalespersonMyInfo } from '@/api/clinicSalesperson.js'
|
||||
|
||||
import { clearLoginStorage } from '@/utils/loginSession.js'
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
components: { ClinicSalespersonPageLayout, AccountSwitchEntry, WxBindEntry },
|
||||
|
||||
data() {
|
||||
|
||||
return {
|
||||
|
||||
displayName: '',
|
||||
|
||||
phone: '',
|
||||
|
||||
storeName: '',
|
||||
|
||||
loading: false,
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
avatarText() {
|
||||
|
||||
return (this.displayName || '推').slice(0, 1)
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
onShow() {
|
||||
|
||||
this.loadProfile()
|
||||
|
||||
if (this.$refs.wxBindEntry) this.$refs.wxBindEntry.refresh()
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
async loadProfile() {
|
||||
|
||||
if (this.loading) return
|
||||
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
|
||||
const wrap = await getClinicSalespersonMyInfo()
|
||||
|
||||
if (!wrap || !wrap.ok) return
|
||||
|
||||
const info = wrap.data || {}
|
||||
|
||||
this.displayName = info.salesperson?.nick_name || '推广员'
|
||||
|
||||
this.phone = info.salesperson?.phone || ''
|
||||
|
||||
this.storeName = info.store?.name || ''
|
||||
|
||||
uni.setStorageSync('clinic_salesperson_user', info)
|
||||
|
||||
} finally {
|
||||
|
||||
this.loading = false
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
logout() {
|
||||
|
||||
uni.showModal({
|
||||
|
||||
title: '提示',
|
||||
|
||||
content: '确定退出登录?',
|
||||
|
||||
confirmColor: '#f53f3f',
|
||||
|
||||
confirmColor: '#6ACDBB',
|
||||
success: (res) => {
|
||||
|
||||
if (!res.confirm) return
|
||||
|
||||
clearLoginStorage()
|
||||
|
||||
uni.reLaunch({ url: '/pages/login/index' })
|
||||
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
$theme-primary: #6acdbb;
|
||||
|
||||
$theme-dark: #4db6a8;
|
||||
|
||||
$color-page-bg: #f5f7f8;
|
||||
|
||||
$color-text-title: #222b2a;
|
||||
|
||||
$color-text-muted: #86909c;
|
||||
|
||||
$color-border: #f2f3f5;
|
||||
|
||||
$color-danger: #f53f3f;
|
||||
|
||||
|
||||
|
||||
.mine-container {
|
||||
|
||||
padding: 0 0 60rpx;
|
||||
|
||||
background-color: $color-page-bg;
|
||||
|
||||
min-height: 100vh;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.profile-panel {
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
background-color: #fff;
|
||||
|
||||
padding: 60rpx 40rpx 50rpx;
|
||||
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.avatar-squircle {
|
||||
|
||||
.avatar-circle {
|
||||
width: 128rpx;
|
||||
|
||||
height: 128rpx;
|
||||
|
||||
border-radius: 40rpx;
|
||||
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, $theme-primary 0%, $theme-dark 100%);
|
||||
|
||||
box-shadow: 0 8rpx 24rpx rgba(106, 205, 187, 0.25);
|
||||
|
||||
color: #fff;
|
||||
|
||||
font-size: 52rpx;
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.user-info {
|
||||
|
||||
margin-left: 32rpx;
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.user-name {
|
||||
|
||||
font-size: 40rpx;
|
||||
|
||||
font-weight: 700;
|
||||
|
||||
color: $color-text-title;
|
||||
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.role-badge {
|
||||
|
||||
align-self: flex-start;
|
||||
|
||||
background-color: rgba(106, 205, 187, 0.1);
|
||||
|
||||
border: 1rpx solid rgba(106, 205, 187, 0.2);
|
||||
|
||||
padding: 4rpx 16rpx;
|
||||
|
||||
border-radius: 8rpx;
|
||||
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.role-text {
|
||||
|
||||
font-size: 22rpx;
|
||||
|
||||
color: $theme-dark;
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.phone-line,
|
||||
|
||||
.store-line {
|
||||
|
||||
.meta-line {
|
||||
font-size: 26rpx;
|
||||
|
||||
color: $color-text-muted;
|
||||
|
||||
margin-top: 8rpx;
|
||||
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.setting-panel {
|
||||
|
||||
background-color: #fff;
|
||||
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
padding: 0 40rpx;
|
||||
|
||||
padding: 8rpx 40rpx 16rpx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.setting-item {
|
||||
|
||||
.info-panel {
|
||||
padding: 28rpx 40rpx;
|
||||
}
|
||||
.info-row {
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
padding: 36rpx 0;
|
||||
|
||||
border-bottom: 1rpx solid $color-border;
|
||||
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.setting-panel .setting-item:last-child {
|
||||
|
||||
border-bottom: none;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.item-label {
|
||||
|
||||
font-size: 30rpx;
|
||||
|
||||
color: $color-text-title;
|
||||
|
||||
font-weight: 500;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.item-value {
|
||||
|
||||
font-size: 30rpx;
|
||||
|
||||
color: $color-text-muted;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.action-item {
|
||||
|
||||
margin: 0 -40rpx;
|
||||
|
||||
padding: 36rpx 40rpx;
|
||||
|
||||
.logout-wrap {
|
||||
padding: 0 40rpx 32rpx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.item-hover {
|
||||
|
||||
background-color: #f7f8fa;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.danger-text {
|
||||
|
||||
font-size: 30rpx;
|
||||
|
||||
color: $color-danger;
|
||||
|
||||
font-weight: 500;
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<image :src="lists.store.offical_seal||'/static/group/xkyard.png'" mode=""></image>
|
||||
<view class="record_yard">
|
||||
<view class="yard">
|
||||
{{lists.store.name}}
|
||||
{{ storeDisplayName }}
|
||||
</view>
|
||||
<view class="state">
|
||||
处方笺
|
||||
@@ -223,7 +223,15 @@
|
||||
prescripDetail,
|
||||
getUseWay
|
||||
} from "@/api/all.js";
|
||||
import { formatStoreNameWithHu } from '@/utils/formatStoreNameWithHu.js';
|
||||
export default {
|
||||
computed: {
|
||||
/** 在线处方诊所名加(互) */
|
||||
storeDisplayName() {
|
||||
const store = this.lists && this.lists.store ? this.lists.store : {}
|
||||
return formatStoreNameWithHu(store.name, this.lists && this.lists.is_online)
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statusName: {
|
||||
|
||||
@@ -131,15 +131,35 @@
|
||||
<text class="block-label">主诉</text>
|
||||
<text class="block-content">{{ parsedObj.chief_complaint }}</text>
|
||||
</view>
|
||||
<view v-if="registerCardDrugNamesLine" class="info-row">
|
||||
<text class="label">所选药品</text>
|
||||
<text class="value">{{ registerCardDrugNamesLine }}</text>
|
||||
</view>
|
||||
<view v-if="parsedObj.number != null && parsedObj.number !== ''" class="info-row">
|
||||
<text class="label">数量</text>
|
||||
<text class="value">各 {{ parsedObj.number }} 盒</text>
|
||||
<view v-if="registerCardDrugRows.length" class="drug-list-block">
|
||||
<text class="block-label">所选药品</text>
|
||||
<view v-for="row in registerCardDrugRows" :key="row._wxKey" class="info-row drug-spec-row">
|
||||
<image
|
||||
v-if="row.image"
|
||||
class="drug-thumb"
|
||||
:src="row.image"
|
||||
mode="aspectFill"
|
||||
@click.stop="$emit('previewImage', row.image)"
|
||||
/>
|
||||
<view class="drug-name-wrap">
|
||||
<text class="label">{{ row.name || '药品' }}</text>
|
||||
<text v-if="row.specification" class="spec-text">{{ row.specification }}</text>
|
||||
</view>
|
||||
<text class="value">×{{ row.quantity }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="registerCardDrugRows.length && canAddExperienceToRx && !isMine"
|
||||
class="card-footer card-footer--compact"
|
||||
>
|
||||
<button
|
||||
class="action-btn action-btn--ghost"
|
||||
@click.stop="$emit('addRegisterDrugsToRx')"
|
||||
>
|
||||
添加到处方单
|
||||
</button>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 11 复诊用药 -->
|
||||
@@ -154,8 +174,11 @@
|
||||
<view v-if="parsedObj.question" class="text-block">
|
||||
<text class="block-content">{{ parsedObj.question }}</text>
|
||||
</view>
|
||||
<view v-for="row in followUpDrugs" :key="row._wxKey" class="info-row">
|
||||
<text class="label">{{ row.name || '药品' }}</text>
|
||||
<view v-for="row in followUpDrugs" :key="row._wxKey" class="info-row drug-spec-row">
|
||||
<view class="drug-name-wrap">
|
||||
<text class="label">{{ row.name || '药品' }}</text>
|
||||
<text v-if="row.specification" class="spec-text">{{ row.specification }}</text>
|
||||
</view>
|
||||
<text class="value">×{{ row.quantity != null ? row.quantity : 1 }}</text>
|
||||
</view>
|
||||
<view v-if="followUpAnsweredLabel" class="info-row">
|
||||
@@ -166,6 +189,17 @@
|
||||
<text class="follow-up-pending-tip">请在挂号后的用药确认页完成选择,此处不可更改</text>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="followUpDrugs.length && canAddExperienceToRx && !isMine"
|
||||
class="card-footer card-footer--compact"
|
||||
>
|
||||
<button
|
||||
class="action-btn action-btn--ghost"
|
||||
@click.stop="$emit('addRegisterDrugsToRx')"
|
||||
>
|
||||
添加到处方单
|
||||
</button>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 11 就诊经历 -->
|
||||
@@ -202,7 +236,7 @@
|
||||
class="action-btn action-btn--ghost"
|
||||
@click.stop="$emit('addExperienceToRx', experienceRxPayload)"
|
||||
>
|
||||
加入处方
|
||||
添加到处方单
|
||||
</button>
|
||||
</view>
|
||||
</block>
|
||||
@@ -370,16 +404,47 @@ export default {
|
||||
if (typeof c === 'object') return JSON.stringify(c);
|
||||
return String(c);
|
||||
},
|
||||
registerCardDrugNamesLine() {
|
||||
if (this.msg.message_type !== 10) return '';
|
||||
registerCardDrugRows() {
|
||||
if (this.msg.message_type !== 10) return [];
|
||||
const pc = this.parsedObj;
|
||||
if (!pc || typeof pc !== 'object') return '';
|
||||
if (!pc || typeof pc !== 'object') return [];
|
||||
const fallbackQty = pc.number != null && pc.number !== '' ? Number(pc.number) : 1;
|
||||
const arr = pc.selected_western_drugs;
|
||||
if (Array.isArray(arr) && arr.length) {
|
||||
return arr.map((d) => d && d.name).filter(Boolean).join('、');
|
||||
return withWxKey(
|
||||
arr.map((d) => {
|
||||
const q = d && d.quantity != null ? Number(d.quantity) : fallbackQty;
|
||||
return {
|
||||
name: (d && d.name) || '',
|
||||
specification: (d && (d.specification || d.spec || d.drug_spec)) || '',
|
||||
image: (d && (d.image || d.drug_image)) || '',
|
||||
quantity: q >= 1 ? q : 1,
|
||||
id: d && (d.drug_id || d.id),
|
||||
};
|
||||
}),
|
||||
'id',
|
||||
'rd'
|
||||
);
|
||||
}
|
||||
if (pc.drug && pc.drug.name) return pc.drug.name;
|
||||
return '';
|
||||
if (pc.drug && pc.drug.name) {
|
||||
return withWxKey(
|
||||
[
|
||||
{
|
||||
name: pc.drug.name,
|
||||
specification: pc.drug.specification || pc.drug.spec || '',
|
||||
image: pc.drug.image || pc.drug.drug_image || '',
|
||||
quantity: fallbackQty >= 1 ? fallbackQty : 1,
|
||||
id: pc.drug.drug_id || pc.drug.id,
|
||||
},
|
||||
],
|
||||
'id',
|
||||
'rd'
|
||||
);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
registerCardDrugNamesLine() {
|
||||
return this.registerCardDrugRows.map((r) => r.name).filter(Boolean).join('、');
|
||||
},
|
||||
followUpAnsweredLabel() {
|
||||
const pc = this.parsedObj;
|
||||
@@ -391,7 +456,14 @@ export default {
|
||||
},
|
||||
followUpDrugs() {
|
||||
const drugs = (this.parsedObj && this.parsedObj.drugs) || [];
|
||||
return withWxKey(drugs, 'id', 'fd');
|
||||
return withWxKey(
|
||||
drugs.map((d) => ({
|
||||
...d,
|
||||
specification: d.specification || d.spec || d.drug_spec || '',
|
||||
})),
|
||||
'id',
|
||||
'fd'
|
||||
);
|
||||
},
|
||||
transferQuestionsWithKey() {
|
||||
const qs = (this.parsedObj && this.parsedObj.questions) || [];
|
||||
@@ -683,6 +755,40 @@ $radius-bubble: 12rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.drug-spec-row {
|
||||
align-items: flex-start;
|
||||
.drug-thumb {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #f3f4f6;
|
||||
flex-shrink: 0;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
.drug-name-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.spec-text {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #9ca3af;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.drug-list-block {
|
||||
.block-label {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #888;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
}
|
||||
.desc-text {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
|
||||
@@ -1 +1,184 @@
|
||||
<template>
|
||||
<template>
|
||||
<view v-if="info" class="panel">
|
||||
<view class="head-row" v-if="info.user_patient">
|
||||
<image class="avatar" :src="patientAvatarSrc" mode="aspectFill" />
|
||||
<view class="head-meta">
|
||||
<view class="row name-row">
|
||||
<text class="v name">{{ info.user_patient.name }}</text>
|
||||
<text class="sub" v-if="info.user_patient.age">{{ info.user_patient.age }}岁</text>
|
||||
</view>
|
||||
<view class="row" v-if="info.order_no">
|
||||
<text class="k">订单号</text>
|
||||
<text class="v">{{ info.order_no }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="row" v-else-if="info.order_no">
|
||||
<text class="k">订单号</text>
|
||||
<text class="v">{{ info.order_no }}</text>
|
||||
</view>
|
||||
<view class="row" v-if="healthLine"><text class="k">健康问卷</text><text class="v">{{ healthLine }}</text></view>
|
||||
<view v-if="registerOrder" class="block">
|
||||
<text class="block-title">本次挂号</text>
|
||||
<view v-if="registerOrder.order_no" class="row">
|
||||
<text class="k">挂号单号</text>
|
||||
<text class="v order-font">{{ registerOrder.order_no }}</text>
|
||||
</view>
|
||||
<view v-if="registerOrder.price != null && registerOrder.price !== ''" class="row">
|
||||
<text class="k">费用</text>
|
||||
<text class="v price">¥{{ registerOrder.price }}</text>
|
||||
</view>
|
||||
<view v-if="registerOrder.status != null && registerOrder.status !== ''" class="row">
|
||||
<text class="k">挂号状态</text>
|
||||
<text class="v">{{ registerStatusText(registerOrder.status) }}</text>
|
||||
</view>
|
||||
<view v-if="registerOrder.pay_status != null && registerOrder.pay_status !== ''" class="row">
|
||||
<text class="k">支付状态</text>
|
||||
<text class="v">{{ registerOrder.pay_status }}</text>
|
||||
</view>
|
||||
<view v-for="(sub, six) in childOrders" :key="six" class="sub-order">
|
||||
<text class="sub-order-label">子订单</text>
|
||||
<text class="sub-order-no">{{ sub.order_no || sub.no || '—' }}</text>
|
||||
<text v-if="sub.status != null" class="sub-order-st">{{ sub.status }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 在线问诊顶部患者信息面板:头像 + 基本信息 + 本次挂号摘要
|
||||
*/
|
||||
export default {
|
||||
name: 'PatientDetailPanel',
|
||||
props: {
|
||||
info: { type: Object, default: null }
|
||||
},
|
||||
computed: {
|
||||
/** 有就诊人头像用真实图,否则按性别默认 nan/nv */
|
||||
patientAvatarSrc() {
|
||||
const up = this.info && this.info.user_patient;
|
||||
if (up && up.avatar) return up.avatar;
|
||||
const sex = up ? Number(up.sex) : 0;
|
||||
return require(`@/static/image/${sex % 2 === 0 ? 'nv' : 'nan'}.png`);
|
||||
},
|
||||
healthLine() {
|
||||
const h = this.info && this.info.user_patient_health_inquiry;
|
||||
if (!h) return '';
|
||||
if (typeof h === 'string') return h;
|
||||
return h.summary || h.chief_complaint || '';
|
||||
},
|
||||
registerOrder() {
|
||||
const i = this.info;
|
||||
if (!i) return null;
|
||||
return i.register_order || null;
|
||||
},
|
||||
childOrders() {
|
||||
const ro = this.registerOrder;
|
||||
if (!ro) return [];
|
||||
const raw = ro.child_orders || ro.sub_orders || ro.orders;
|
||||
return Array.isArray(raw) ? raw : [];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
registerStatusText(status) {
|
||||
const map = { 0: '待就诊', 1: '已缴费', 2: '已就诊', 3: '已取消', 4: '已退费' };
|
||||
const n = Number(status);
|
||||
return map[n] != null ? map[n] : String(status);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.panel {
|
||||
padding: 16rpx 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin: 16rpx 24rpx;
|
||||
}
|
||||
.head-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background: #e8f5f2;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.head-meta {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.name-row {
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
.k {
|
||||
color: #888;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
.v {
|
||||
color: #333;
|
||||
}
|
||||
.v.name {
|
||||
font-weight: 600;
|
||||
}
|
||||
.sub {
|
||||
margin-left: 16rpx;
|
||||
color: #666;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.block {
|
||||
margin-top: 16rpx;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.block-title {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.order-font {
|
||||
font-family: Courier, monospace;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.price {
|
||||
color: #ef4444;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sub-order {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-top: 8rpx;
|
||||
padding: 12rpx;
|
||||
background: #f9fafb;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.sub-order-label {
|
||||
color: #999;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
.sub-order-no {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
}
|
||||
.sub-order-st {
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<view class="bar" v-if="visible">
|
||||
<button v-if="showAccept" class="btn primary" size="mini" @click="$emit('accept')">接诊</button>
|
||||
<button v-if="showRefuse" class="btn warn" size="mini" @click="$emit('refuse')">拒诊</button>
|
||||
<button v-if="showPrescription" class="btn primary" size="mini" @click="$emit('prescription')">开方</button>
|
||||
<button v-if="showEnd" class="btn plain" size="mini" @click="$emit('end')">结束诊断</button>
|
||||
<button v-if="showAccept" class="btn primary" @click="$emit('accept')">接诊</button>
|
||||
<button v-if="showRefuse" class="btn warn" @click="$emit('refuse')">拒诊</button>
|
||||
<button v-if="showPrescription" class="btn primary" @click="$emit('prescription')">开方</button>
|
||||
<button v-if="showEnd" class="btn plain" @click="$emit('end')">结束诊断</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/** 与 RegisterStatusEnum 对齐:1=已支付待接诊 2=接诊中 */
|
||||
/** 与 RegisterStatusEnum 对齐:1=已支付待接诊 2=接诊中;按钮等宽铺满避免挤在左侧 */
|
||||
export default {
|
||||
name: 'ReceptionActionBar',
|
||||
props: {
|
||||
@@ -39,14 +39,25 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
.bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 16rpx 24rpx;
|
||||
gap: 16rpx;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 24rpx;
|
||||
gap: 24rpx;
|
||||
background: #f7f8fa;
|
||||
border-top: 1rpx solid #eee;
|
||||
}
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 30rpx;
|
||||
border-radius: 40rpx;
|
||||
border: none;
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.primary {
|
||||
background: #6acdbb !important;
|
||||
@@ -60,5 +71,8 @@ export default {
|
||||
background: #fff !important;
|
||||
color: #333 !important;
|
||||
border: 1rpx solid #ddd !important;
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
@playAudio="playAudio"
|
||||
@viewPrescription="onBubbleViewPrescription"
|
||||
@addExperienceToRx="onAddExperienceToPrescription"
|
||||
@addRegisterDrugsToRx="onRegisterDrugsBulkToPrescription"
|
||||
/>
|
||||
</view>
|
||||
|
||||
@@ -70,8 +71,8 @@
|
||||
</scroll-view>
|
||||
|
||||
<ReceptionActionBar
|
||||
:register-status="Number(patientInfo && patientInfo.status) || 0"
|
||||
:room-ended="roomEnded"
|
||||
:register-status="registerStatusNum"
|
||||
:room-ended="actionBarRoomEnded"
|
||||
@accept="onAccept"
|
||||
@refuse="onRefuseTap"
|
||||
@prescription="goPrescriptionV2"
|
||||
@@ -384,31 +385,58 @@ export default {
|
||||
navbarTitle() {
|
||||
return this.patientName || '在线问诊';
|
||||
},
|
||||
/**
|
||||
* 患者气泡头像:有就诊人头像用真实图,否则按性别默认 nan/nv
|
||||
*/
|
||||
patientAvatar() {
|
||||
return this.patientInfo?.user_patient?.avatar || '';
|
||||
const up = this.patientInfo && this.patientInfo.user_patient;
|
||||
if (up && up.avatar) return up.avatar;
|
||||
const sex = up ? Number(up.sex) : 0;
|
||||
return require(`@/static/image/${sex % 2 === 0 ? 'nv' : 'nan'}.png`);
|
||||
},
|
||||
doctorAvatar() {
|
||||
return uni.getStorageSync('doctor_avatar') || '';
|
||||
},
|
||||
/** 当前挂号状态(与 RegisterStatusEnum 对齐:1=待接诊 2=接诊中) */
|
||||
registerStatusNum() {
|
||||
return Number(this.patientInfo && this.patientInfo.status) || 0;
|
||||
},
|
||||
/**
|
||||
* 问诊会话是否已结束(锁输入、隐藏操作栏)
|
||||
* 待接诊(1)/接诊中(2) 均不以复用房间的历史结束态为准:
|
||||
* 否则会藏掉接诊按钮,或接诊后房间未同步导致无法发消息
|
||||
*/
|
||||
roomEnded() {
|
||||
const st = this.registerStatusNum;
|
||||
if (st === 1 || st === 2) return false;
|
||||
return this.roomChatStatus === 1 || this.isConsultationEnded;
|
||||
},
|
||||
inputLocked() {
|
||||
if (this.roomEnded) return true;
|
||||
const st = Number(this.patientInfo && this.patientInfo.status);
|
||||
if (!st) return false;
|
||||
/**
|
||||
* 传给 ReceptionActionBar:待接诊/接诊中强制不隐藏操作栏
|
||||
*/
|
||||
actionBarRoomEnded() {
|
||||
const st = this.registerStatusNum;
|
||||
if (st === 1 || st === 2) return false;
|
||||
return this.roomEnded;
|
||||
},
|
||||
inputLocked() {
|
||||
const st = this.registerStatusNum;
|
||||
// 待接诊、接诊中均可发消息,不因房间残留结束态锁定
|
||||
if (st === 1 || st === 2) return false;
|
||||
if (this.roomEnded) return true;
|
||||
if (!st) return false;
|
||||
return true;
|
||||
},
|
||||
lockReason() {
|
||||
const st = this.registerStatusNum;
|
||||
if (st === 1 || st === 2) return '';
|
||||
if (this.roomChatStatus === 1 || this.isConsultationEnded) return '问诊已结束';
|
||||
const st = Number(this.patientInfo && this.patientInfo.status);
|
||||
if (st === 7 || st === 8) return '已拒诊';
|
||||
if (st >= 3) return '当前状态不可发送消息';
|
||||
return '不可发送';
|
||||
},
|
||||
statusLine() {
|
||||
const st = Number(this.patientInfo && this.patientInfo.status);
|
||||
const st = this.registerStatusNum;
|
||||
const m = {
|
||||
0: '待支付',
|
||||
1: '待接诊',
|
||||
@@ -421,7 +449,7 @@ export default {
|
||||
return m[st] || '问诊中';
|
||||
},
|
||||
canAddExperienceDrugFromChat() {
|
||||
const st = Number(this.patientInfo && this.patientInfo.status);
|
||||
const st = this.registerStatusNum;
|
||||
return st === 2 && !this.inputLocked && !!this.registerId;
|
||||
},
|
||||
registerOrderBlock() {
|
||||
@@ -586,7 +614,11 @@ export default {
|
||||
const res = await getRoomStatusApi({ room_id: this.roomId });
|
||||
const raw = this.unwrap(res) || {};
|
||||
this.roomChatStatus = Number(raw.status) || 0;
|
||||
if (this.roomChatStatus === 1) this.isConsultationEnded = true;
|
||||
const st = this.registerStatusNum;
|
||||
// 待接诊/接诊中:房间可能仍是上一单结束态,不据此标记本单已结束
|
||||
if (this.roomChatStatus === 1 && st !== 1 && st !== 2) {
|
||||
this.isConsultationEnded = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
@@ -694,31 +726,57 @@ export default {
|
||||
if (tempIndex !== -1) {
|
||||
const patched = ensureWxKey({ ...message, isTemporary: false }, 'id', `m${tempIndex}`);
|
||||
this.$set(this.messages, tempIndex, patched);
|
||||
if (message.message_type === chatConfig.messageTypes['end-consultation']) {
|
||||
this.applyEndConsultationMessage(message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
const existingIndex = this.messages.findIndex((m) => m.id === message.id);
|
||||
if (existingIndex !== -1) {
|
||||
this.$set(this.messages, existingIndex, ensureWxKey(message, 'id', `m${existingIndex}`));
|
||||
if (message.message_type === chatConfig.messageTypes['end-consultation']) {
|
||||
this.applyEndConsultationMessage(message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.messages.push(ensureWxKey(message, 'id', `m${this.messages.length}`));
|
||||
// 结束问诊卡片:待接诊忽略历史结束态;接诊中仅认本单创建之后的结束消息
|
||||
if (message.message_type === chatConfig.messageTypes['end-consultation']) {
|
||||
this.isConsultationEnded = true;
|
||||
this.applyEndConsultationMessage(message);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 根据结束问诊消息更新本单结束态,避免医患复用房间时历史卡片误锁待接诊
|
||||
*/
|
||||
applyEndConsultationMessage(message) {
|
||||
const st = this.registerStatusNum;
|
||||
if (st === 1) {
|
||||
return;
|
||||
}
|
||||
if (st === 2) {
|
||||
const msgTs = Number(message.created_at || message.timestamp || 0);
|
||||
const regTs = Number(this.patientInfo && this.patientInfo.created_at) || 0;
|
||||
const msgSec = msgTs > 1e12 ? Math.floor(msgTs / 1000) : msgTs;
|
||||
if (regTs > 0 && msgSec < regTs) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.isConsultationEnded = true;
|
||||
},
|
||||
isMsgMine(msg) {
|
||||
const senderId = msg.sender_user_id || '';
|
||||
return senderId === this.currentUserId;
|
||||
},
|
||||
getMsgAvatar(msg) {
|
||||
const sid = msg.sender_user_id || '';
|
||||
const fallback = 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250108/29bdb9560340373bb3096394a845afa5.jpg';
|
||||
if (sid === 'doctor_assistant') return fallback;
|
||||
const assistantFallback = 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250108/29bdb9560340373bb3096394a845afa5.jpg';
|
||||
if (sid === 'doctor_assistant') return assistantFallback;
|
||||
if (this.isMsgMine(msg)) {
|
||||
return this.doctorAvatar || fallback;
|
||||
return this.doctorAvatar || assistantFallback;
|
||||
}
|
||||
return this.patientAvatar || fallback;
|
||||
// 患者侧:真实头像或性别默认(patientAvatar 已处理)
|
||||
return this.patientAvatar;
|
||||
},
|
||||
toMessageMs(t) {
|
||||
const n = Number(t) || 0;
|
||||
@@ -1372,8 +1430,12 @@ export default {
|
||||
async onAccept() {
|
||||
try {
|
||||
await receptionApi(this.registerId);
|
||||
// 接诊成功后清掉上一单遗留的结束态,并刷新房间状态
|
||||
this.isConsultationEnded = false;
|
||||
this.roomChatStatus = 0;
|
||||
uni.showToast({ title: '接诊成功', icon: 'none' });
|
||||
await this.loadPatientDetail();
|
||||
await this.refreshRoomStatus();
|
||||
} catch (e) {
|
||||
uni.showToast({ title: (e && e.msg) || '接诊失败', icon: 'none' });
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<image src="../../static/image/dy.png" class="m-t-8 b-r-8"></image>
|
||||
<d-text text="设置" className="content-c fs-32"></d-text>
|
||||
</view>
|
||||
<!-- 协议与客服:退出/切账号已上提到「我的」主页 -->
|
||||
<view class="p-row-32 white m-t-48">
|
||||
<view @click="$go('/subPackages/sub_agreement/agreement?type=service_user_agreement')"
|
||||
class="bottom-border flex-row flex-jus-sp flex-ali-center p-col-32">
|
||||
@@ -17,44 +18,22 @@
|
||||
<d-text text="隐私权政策" className="content-c fs-32"></d-text>
|
||||
<u-icon name="arrow-right" color="#A7ABB0" size="32"></u-icon>
|
||||
</view>
|
||||
<view class="bottom-border flex-row flex-jus-sp flex-ali-center p-col-32">
|
||||
<view class="flex-row flex-jus-sp flex-ali-center p-col-32">
|
||||
<d-text text="客服热线" className="content-c fs-32"></d-text>
|
||||
40056256325
|
||||
</view>
|
||||
</view>
|
||||
<view class="p-row-32 m-t-8 white">
|
||||
<account-switch-entry />
|
||||
</view>
|
||||
<view class="p-row-32 m-t-8" style="height: 80rpx;">
|
||||
<u-button :throttle-time="0" shape="circle" :custom-style="{backgroundColor:'#6ACDBB',color:'#fff',heigth:'86rpx'}" @click="logout">
|
||||
退出登录</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
logout
|
||||
} from '@/api/all.js'
|
||||
import AccountSwitchEntry from '@/components/account-switch/account-switch-entry.vue'
|
||||
/**
|
||||
* 药师/导医客服设置页:仅保留协议与客服热线
|
||||
*/
|
||||
export default {
|
||||
components: { AccountSwitchEntry },
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
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);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
@@ -68,11 +47,4 @@
|
||||
height: 160rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.m-t-8 {
|
||||
::v-deep button {
|
||||
color: #fff;
|
||||
background: #6ACDBB;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -106,6 +106,11 @@ export default {
|
||||
icon: 'bag-fill',
|
||||
theme: { bg: 'linear-gradient(135deg, #F0FDF4, #DCFCE7)', color: '#22C55E' } // 安全绿
|
||||
},
|
||||
{
|
||||
key: 'user-patient', label: '会员管理', desc: '微信用户与就诊人', path: `${shared}/user-patient/index`,
|
||||
icon: 'account',
|
||||
theme: { bg: 'linear-gradient(135deg, #FFF7ED, #FFEDD5)', color: '#F97316' }
|
||||
},
|
||||
{
|
||||
key: 'store-manage', label: '门店管理', desc: '诊所列表与价格配置', path: `${root}/store/index`,
|
||||
icon: 'home-fill',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<business-page-layout title="我的" :show-tabbar="true" :tab-index="1">
|
||||
<view class="mine-container">
|
||||
<view class="profile-panel">
|
||||
<view class="avatar-squircle"><text>{{ avatarText }}</text></view>
|
||||
<view class="avatar-circle"><text>{{ avatarText }}</text></view>
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{ displayName }}</text>
|
||||
<view class="role-badge"><text class="role-text">{{ roleText }}</text></view>
|
||||
@@ -11,15 +11,23 @@
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
<view class="logout-wrap">
|
||||
<u-button
|
||||
:throttle-time="0"
|
||||
shape="circle"
|
||||
:custom-style="{ backgroundColor: '#6ACDBB', color: '#fff', height: '96rpx' }"
|
||||
@click="logout"
|
||||
>退出登录</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</business-page-layout>
|
||||
</template>
|
||||
|
||||
<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'
|
||||
@@ -42,7 +50,7 @@ export default {
|
||||
methods: {
|
||||
logout() {
|
||||
uni.showModal({
|
||||
title: '提示', content: '确定退出登录?',
|
||||
title: '提示', content: '确定退出登录?', confirmColor: '#6ACDBB',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return
|
||||
clearLoginStorage()
|
||||
@@ -55,14 +63,17 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mine-container { min-height: 100vh; background: #F5F7F8; }
|
||||
.mine-container { min-height: 100vh; background: #F5F7F8; padding-bottom: 60rpx; }
|
||||
.profile-panel { display: flex; align-items: center; background: #fff; padding: 60rpx 40rpx; margin-bottom: 24rpx; }
|
||||
.avatar-squircle { width: 128rpx; height: 128rpx; border-radius: 40rpx; background: linear-gradient(135deg, #6acdbb, #4db6a8); color: #fff; font-size: 52rpx; display: flex; align-items: center; justify-content: center; }
|
||||
.avatar-circle {
|
||||
width: 128rpx; height: 128rpx; border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6acdbb, #4db6a8); color: #fff; font-size: 52rpx;
|
||||
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
|
||||
}
|
||||
.user-info { margin-left: 32rpx; }
|
||||
.user-name { font-size: 40rpx; font-weight: 700; display: block; margin-bottom: 12rpx; }
|
||||
.role-badge { display: inline-block; background: rgba(106,205,187,.1); padding: 4rpx 16rpx; border-radius: 8rpx; }
|
||||
.role-text { font-size: 22rpx; color: #4db6a8; }
|
||||
.setting-panel { background: #fff; padding: 0 40rpx; margin-bottom: 24rpx; }
|
||||
.setting-item { padding: 36rpx 0; border-bottom: 1rpx solid #F2F3F5; }
|
||||
.danger-text { color: #F53F3F; font-size: 30rpx; }
|
||||
.setting-panel { background: #fff; padding: 8rpx 40rpx 16rpx; margin-bottom: 24rpx; }
|
||||
.logout-wrap { padding: 0 40rpx 32rpx; }
|
||||
</style>
|
||||
|
||||
@@ -7,9 +7,20 @@
|
||||
<text class="addr">{{ store.position || '暂无地址' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<!-- 只读信息:业务员无编辑权限时展示配置摘要 -->
|
||||
<view v-if="!canEdit" class="section">
|
||||
<text class="section-title">门店信息</text>
|
||||
<view class="info-row"><text>推广员可见价格</text><text>{{ Number(store.salesperson_see_price) === 1 ? '是' : '否' }}</text></view>
|
||||
<view class="info-row"><text>包邮</text><text>{{ Number(store.is_shipping_free) === 1 ? '是' : '否' }}</text></view>
|
||||
<view class="info-row"><text>订阅价格波动</text><text>{{ Number(store.subscribe_price_change) === 0 ? '是' : '否' }}</text></view>
|
||||
<view class="info-row"><text>查看毛利率</text><text>{{ Number(store.see_rate) === 1 ? '是' : '否' }}</text></view>
|
||||
<view class="info-row"><text>开方可选医保</text><text>{{ Number(store.allow_insurance_category) === 1 ? '是' : '否' }}</text></view>
|
||||
<view class="info-row"><text>诊所类型</text><text>{{ clinicTypeText(store.clinic_type) }}</text></view>
|
||||
</view>
|
||||
|
||||
<view v-if="canEdit" class="section">
|
||||
<text class="section-title">门店配置</text>
|
||||
<view class="switch-row">
|
||||
<view v-if="!isSalesperson" class="switch-row">
|
||||
<text>推广员可见价格</text>
|
||||
<switch :checked="Number(store.salesperson_see_price) === 1" color="#6ACDBB" @change="toggleSeePrice" />
|
||||
</view>
|
||||
@@ -40,13 +51,13 @@
|
||||
<view class="section">
|
||||
<text class="section-title">快捷操作</text>
|
||||
<view class="action-grid">
|
||||
<view class="action-btn" @click="goEdit">编辑资料</view>
|
||||
<view class="action-btn" @click="goDrugPrice">修改药品价格</view>
|
||||
<view class="action-btn" @click="goConsultation">在线复诊配置</view>
|
||||
<view class="action-btn" @click="goPromoters">管理推广员</view>
|
||||
<view v-if="canEdit" class="action-btn" @click="goEdit">编辑资料</view>
|
||||
<view v-if="canEdit" class="action-btn" @click="goDrugPrice">修改药品价格</view>
|
||||
<view v-if="canEdit" class="action-btn" @click="goConsultation">在线复诊配置</view>
|
||||
<view v-if="canEdit && !isSalesperson" class="action-btn" @click="goPromoters">管理推广员</view>
|
||||
<view class="action-btn" @click="openQr">诊所二维码</view>
|
||||
<view class="action-btn" @click="goBankCardReport">{{ bankCardReportButtonText }}</view>
|
||||
<view v-if="!hasAdminAccount" class="action-btn warn" @click="openPc">开通后台</view>
|
||||
<view v-if="canEdit && !hasAdminAccount" class="action-btn warn" @click="openPc">开通后台</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -60,9 +71,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 门店详情:超管可编辑;业务员默认只读,系统配置开启后显示编辑入口
|
||||
*/
|
||||
import StorePageLayout from '@/subPackages/sub_platform_admin/components/store-page-layout.vue';
|
||||
import {
|
||||
getPlatformStoreDetail,
|
||||
getStoreDetail,
|
||||
getSalespersonStoreEditPermission,
|
||||
toggleSalespersonSeePrice,
|
||||
updateStoreShippingFree,
|
||||
updateStoreSubscribeStatus,
|
||||
@@ -71,7 +86,8 @@ import {
|
||||
updateStoreClinicType,
|
||||
openStorePcWindows,
|
||||
openStoreQrCode,
|
||||
} from '@/api/platformStore.js';
|
||||
isSalespersonStoreMode,
|
||||
} from '@/api/storeManage.js';
|
||||
import { openQrCodePreview, buildClinicQrPayload } from '@/utils/qrCodePreview.js';
|
||||
|
||||
const ROOT = '/subPackages/sub_platform_admin/store';
|
||||
@@ -84,9 +100,16 @@ export default {
|
||||
storeId: 0,
|
||||
store: {},
|
||||
clinicTypeLabels: ['未设置', '西医诊所', '中医诊所'],
|
||||
isSalesperson: false,
|
||||
/** 业务员编辑开关;超管始终 true */
|
||||
salespersonEditEnabled: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canEdit() {
|
||||
if (!this.isSalesperson) return true;
|
||||
return !!this.salespersonEditEnabled;
|
||||
},
|
||||
clinicTypeIndex() {
|
||||
return Number(this.store.clinic_type) || 0;
|
||||
},
|
||||
@@ -104,15 +127,28 @@ export default {
|
||||
},
|
||||
onLoad(options) {
|
||||
this.storeId = Number(options.id || 0);
|
||||
this.loadDetail();
|
||||
this.isSalesperson = isSalespersonStoreMode();
|
||||
this.bootstrap();
|
||||
},
|
||||
methods: {
|
||||
clinicTypeText(type) {
|
||||
return this.clinicTypeLabels[Number(type) || 0] || '未设置';
|
||||
},
|
||||
async bootstrap() {
|
||||
if (this.isSalesperson) {
|
||||
try {
|
||||
const wrap = await getSalespersonStoreEditPermission();
|
||||
const data = wrap && wrap.ok ? wrap.data : null;
|
||||
this.salespersonEditEnabled = !!(data && data.salesperson_store_edit_enabled);
|
||||
} catch (e) {
|
||||
this.salespersonEditEnabled = false;
|
||||
}
|
||||
}
|
||||
await this.loadDetail();
|
||||
},
|
||||
async loadDetail() {
|
||||
if (!this.storeId) return;
|
||||
const wrap = await getPlatformStoreDetail(this.storeId);
|
||||
const wrap = await getStoreDetail(this.storeId);
|
||||
if (wrap && wrap.ok) {
|
||||
this.store = wrap.data || {};
|
||||
}
|
||||
@@ -204,10 +240,11 @@ export default {
|
||||
.sub, .addr { display: block; font-size: 24rpx; margin-top: 8rpx; opacity: 0.9; }
|
||||
.section { background: #fff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 20rpx; }
|
||||
.section-title { display: block; font-size: 28rpx; font-weight: 600; margin-bottom: 16rpx; }
|
||||
.switch-row, .picker-row {
|
||||
.switch-row, .picker-row, .info-row {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 20rpx 0; border-bottom: 1rpx solid #f0f0f0; font-size: 28rpx;
|
||||
}
|
||||
.info-row text:last-child { color: #6b7280; }
|
||||
.picker-val { color: #6ACDBB; }
|
||||
.action-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
|
||||
.action-btn {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<store-page-layout title="门店管理" :show-back="true">
|
||||
<store-page-layout :title="pageTitle" :show-back="true">
|
||||
<view class="page">
|
||||
<view class="filter-panel">
|
||||
<input class="filter-input" v-model="filters.name" placeholder="诊所名称" />
|
||||
@@ -39,8 +39,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 门店/诊所列表:超管看全量,业务员看自己推广码绑定的诊所
|
||||
*/
|
||||
import StorePageLayout from '@/subPackages/sub_platform_admin/components/store-page-layout.vue';
|
||||
import { getPlatformStoreList } from '@/api/platformStore.js';
|
||||
import { getStoreList, isSalespersonStoreMode } from '@/api/storeManage.js';
|
||||
|
||||
const ROOT = '/subPackages/sub_platform_admin/store';
|
||||
|
||||
@@ -51,9 +54,16 @@ export default {
|
||||
filters: { name: '', shouzimu: '', mobile: '' },
|
||||
list: [],
|
||||
loading: false,
|
||||
isSalesperson: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
return this.isSalesperson ? '我的诊所' : '门店管理';
|
||||
},
|
||||
},
|
||||
onShow() {
|
||||
this.isSalesperson = isSalespersonStoreMode();
|
||||
this.reload();
|
||||
},
|
||||
methods: {
|
||||
@@ -65,7 +75,7 @@ export default {
|
||||
async reload() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const wrap = await getPlatformStoreList({
|
||||
const wrap = await getStoreList({
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
...this.filters,
|
||||
@@ -99,20 +109,22 @@ export default {
|
||||
.filter-btn {
|
||||
background: #6ACDBB; color: #fff; padding: 16rpx 32rpx; border-radius: 12rpx; font-size: 26rpx;
|
||||
}
|
||||
.empty { text-align: center; color: #999; padding: 80rpx 0; }
|
||||
.empty { text-align: center; color: #999; padding: 80rpx 0; font-size: 28rpx; }
|
||||
.card {
|
||||
background: #fff; border-radius: 20rpx; padding: 28rpx; margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.04);
|
||||
background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx;
|
||||
}
|
||||
.card-hover { opacity: 0.92; }
|
||||
.card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8rpx; }
|
||||
.name { font-size: 30rpx; font-weight: 600; flex: 1; }
|
||||
.card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8rpx; }
|
||||
.name { font-size: 30rpx; font-weight: 600; color: #1f2937; }
|
||||
.type-tag { font-size: 22rpx; color: #6ACDBB; background: #f0fbf8; padding: 4rpx 12rpx; border-radius: 8rpx; }
|
||||
.addr { font-size: 24rpx; color: #666; display: block; margin-bottom: 12rpx; }
|
||||
.meta-row { display: flex; justify-content: space-between; font-size: 22rpx; color: #999; margin-bottom: 12rpx; }
|
||||
.tag-row { display: flex; gap: 12rpx; flex-wrap: wrap; }
|
||||
.mini-tag { font-size: 22rpx; color: #999; background: #f5f5f5; padding: 4rpx 12rpx; border-radius: 8rpx; }
|
||||
.mini-tag.on { color: #22C55E; background: #F0FDF4; }
|
||||
.mini-tag.warn { color: #D97706; background: #FEF3C7; }
|
||||
.mini-tag.bank { color: #3B82F6; background: #EFF6FF; }
|
||||
.addr { display: block; font-size: 24rpx; color: #6b7280; margin-bottom: 12rpx; }
|
||||
.meta-row { display: flex; gap: 24rpx; font-size: 22rpx; color: #9ca3af; margin-bottom: 12rpx; }
|
||||
.tag-row { display: flex; flex-wrap: wrap; gap: 8rpx; }
|
||||
.mini-tag {
|
||||
font-size: 20rpx; padding: 4rpx 10rpx; border-radius: 6rpx;
|
||||
background: #f3f4f6; color: #9ca3af;
|
||||
}
|
||||
.mini-tag.on { background: #ecfdf5; color: #059669; }
|
||||
.mini-tag.warn { background: #fef3c7; color: #d97706; }
|
||||
.mini-tag.bank { background: #eff6ff; color: #3b82f6; }
|
||||
</style>
|
||||
|
||||
@@ -93,6 +93,7 @@ export default {
|
||||
{ key: 'doctor-form', label: '医生录入', desc: '新增合作医生', path: '/subPackages/sub_salesperson/doctor-input/form', inputType: 'doctor', isForm: true, icon: 'plus-circle-fill', theme: this.themes.doctor },
|
||||
]},
|
||||
{ key: 'list', title: '档案管理', items: [
|
||||
{ key: 'my-store', label: '我的诊所', desc: '查看所属正式诊所', path: '/subPackages/sub_platform_admin/store/index', icon: 'home', theme: this.themes.store },
|
||||
{ key: 'store-list', label: '门店列表', desc: '查看已录门店', path: '/subPackages/sub_salesperson/store-input/index', icon: 'home-fill', theme: this.themes.store },
|
||||
{ key: 'doctor-list', label: '医生列表', desc: '查看已录医生', path: '/subPackages/sub_salesperson/doctor-input/index', icon: 'account-fill', theme: this.themes.doctor },
|
||||
]}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<business-page-layout title="我的" :show-tabbar="true" :tab-index="1">
|
||||
<view class="mine-container">
|
||||
<view class="profile-panel">
|
||||
<view class="avatar-squircle"><text>{{ avatarText }}</text></view>
|
||||
<view class="avatar-circle"><text>{{ avatarText }}</text></view>
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{ displayName }}</text>
|
||||
<view class="role-badge"><text class="role-text">{{ roleText }}</text></view>
|
||||
@@ -11,13 +11,23 @@
|
||||
<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>
|
||||
<view class="logout-wrap">
|
||||
<u-button
|
||||
:throttle-time="0"
|
||||
shape="circle"
|
||||
:custom-style="{ backgroundColor: '#6ACDBB', color: '#fff', height: '96rpx' }"
|
||||
@click="logout"
|
||||
>退出登录</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</business-page-layout>
|
||||
</template>
|
||||
|
||||
<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'
|
||||
@@ -38,7 +48,7 @@ export default {
|
||||
methods: {
|
||||
logout() {
|
||||
uni.showModal({
|
||||
title: '提示', content: '确定退出登录?',
|
||||
title: '提示', content: '确定退出登录?', confirmColor: '#6ACDBB',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return
|
||||
clearLoginStorage()
|
||||
@@ -51,12 +61,17 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mine-container { min-height: 100vh; background: #F5F7F8; }
|
||||
.mine-container { min-height: 100vh; background: #F5F7F8; padding-bottom: 60rpx; }
|
||||
.profile-panel { display: flex; align-items: center; background: #fff; padding: 60rpx 40rpx; margin-bottom: 24rpx; }
|
||||
.avatar-squircle { width: 128rpx; height: 128rpx; border-radius: 40rpx; background: linear-gradient(135deg, #6acdbb, #4db6a8); color: #fff; font-size: 52rpx; display: flex; align-items: center; justify-content: center; }
|
||||
.avatar-circle {
|
||||
width: 128rpx; height: 128rpx; border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6acdbb, #4db6a8); color: #fff; font-size: 52rpx;
|
||||
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
|
||||
}
|
||||
.user-info { margin-left: 32rpx; }
|
||||
.user-name { font-size: 40rpx; font-weight: 700; display: block; margin-bottom: 12rpx; }
|
||||
.role-badge { display: inline-block; background: rgba(106,205,187,.1); padding: 4rpx 16rpx; border-radius: 8rpx; }
|
||||
.role-text { font-size: 22rpx; color: #4db6a8; }
|
||||
.setting-panel { background: #fff; padding: 36rpx 40rpx; }
|
||||
.danger-text { color: #F53F3F; }
|
||||
.setting-panel { background: #fff; padding: 8rpx 40rpx 16rpx; margin-bottom: 24rpx; }
|
||||
.logout-wrap { padding: 0 40rpx 32rpx; }
|
||||
</style>
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
>
|
||||
<text class="selected-chip-name">{{ sel.drug_name || sel.name || '—' }}</text>
|
||||
<text class="selected-chip-dose">{{ String(sel.number || 0) }}{{ sel.unit && sel.unit.name ? sel.unit.name : 'g' }}</text>
|
||||
<text v-if="getWayLabel(sel)" class="selected-chip-way">{{ getWayLabel(sel) }}</text>
|
||||
</view>
|
||||
<view class="selected-chip-close" :data-wx-key="sel._wxKey" @click.stop="handleSelectedChipRemove">
|
||||
<u-icon name="close" size="22" color="#8A92A3"></u-icon>
|
||||
@@ -70,6 +71,7 @@
|
||||
<view class="flex-row flex-ali-center">
|
||||
<view class="selected-indicator" v-if="isInSelected(item)"></view>
|
||||
<d-text :text="getDrugName(item)" className="fs-32 font-bold color-title"></d-text>
|
||||
<text v-if="Number(item.has_delivery_warehouse) === 1" class="multi-wh-tag m-l-12">多仓</text>
|
||||
</view>
|
||||
<d-text
|
||||
v-if="showDrugPrice"
|
||||
@@ -98,6 +100,43 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 先煎/后下:气泡标签卡片点选(不用 ActionSheet,选项超过 6 个会失败) -->
|
||||
<view v-if="drugUseWayList.length" class="way-block m-b-16">
|
||||
<view
|
||||
class="way-row flex-row flex-ali-center flex-jus-sp"
|
||||
@click.stop="toggleWayPanel"
|
||||
:data-index="index"
|
||||
>
|
||||
<d-text text="煎法:" className="fs-28 color-title"></d-text>
|
||||
<view class="way-picker flex-row flex-ali-center">
|
||||
<text class="fs-26" :class="getWayLabel(item) ? 'color-way' : 'color-sub'">
|
||||
{{ getWayDisplayName(item) }}
|
||||
</text>
|
||||
<u-icon
|
||||
:name="wayPanelDrugIndex === index ? 'arrow-up' : 'arrow-right'"
|
||||
size="24"
|
||||
color="#8A92A3"
|
||||
class="m-l-8"
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="wayPanelDrugIndex === index"
|
||||
class="way-bubble-card"
|
||||
@click.stop
|
||||
>
|
||||
<view
|
||||
v-for="w in drugUseWayList"
|
||||
:key="w.id"
|
||||
class="way-bubble-tag"
|
||||
:class="{ active: isWayActive(item, w) }"
|
||||
@click.stop="selectWayTag"
|
||||
:data-index="index"
|
||||
:data-way-id="w.id"
|
||||
>{{ w.name }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="quantity-input-row flex-row flex-ali-center flex-jus-sp pt-20 border-t-dashed">
|
||||
<view class="quantity-input-left flex-row flex-ali-center">
|
||||
<d-text text="用量:" className="fs-28 color-title"></d-text>
|
||||
@@ -223,6 +262,11 @@ export default {
|
||||
showDrugPrice: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/** 药材级先煎/后下选项(父页 drugUseList.drug_use_way) */
|
||||
drugUseWayList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -237,13 +281,16 @@ export default {
|
||||
pageSize: 20,
|
||||
hasMore: false,
|
||||
// 常用克数(后端全局配置,每次打开抽屉拉取)
|
||||
commonQuantities: []
|
||||
commonQuantities: [],
|
||||
/** 当前展开煎法气泡的药材下标,-1 表示收起 */
|
||||
wayPanelDrugIndex: -1
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.show = newVal;
|
||||
if (newVal) {
|
||||
this.wayPanelDrugIndex = -1;
|
||||
this.selectedDrugs = this.mapSelectedDrugs(JSON.parse(JSON.stringify(this.currentDrugs)));
|
||||
this.fetchQuickGrams();
|
||||
const kw = (this.initialSearchKey || '').trim();
|
||||
@@ -256,10 +303,13 @@ export default {
|
||||
} else {
|
||||
this.resetList();
|
||||
}
|
||||
} else {
|
||||
this.wayPanelDrugIndex = -1;
|
||||
}
|
||||
},
|
||||
show(newVal) {
|
||||
if (!newVal) {
|
||||
this.wayPanelDrugIndex = -1;
|
||||
this.$emit('input', false);
|
||||
}
|
||||
}
|
||||
@@ -334,6 +384,7 @@ export default {
|
||||
this.drugList = [];
|
||||
this.page = 1;
|
||||
this.hasMore = false;
|
||||
this.wayPanelDrugIndex = -1;
|
||||
},
|
||||
/**
|
||||
* 加载药品列表
|
||||
@@ -388,17 +439,27 @@ export default {
|
||||
return matchByIndexId || matchByEntityId || matchByDrugId;
|
||||
});
|
||||
this.$set(drug, '_quantity', selected ? selected.number : 0);
|
||||
// 恢复已选煎法(先煎/后下)
|
||||
const wayId = selected
|
||||
? Number(selected.way_id ?? selected.use_ways?.id ?? 0)
|
||||
: Number(drug.way_id || (drug.drug && drug.drug.way_id) || 0);
|
||||
this.$set(drug, '_way_id', wayId || 0);
|
||||
return drug;
|
||||
});
|
||||
if (this.page > 1) {
|
||||
this.drugList = this.drugList.concat(merged);
|
||||
} else {
|
||||
// 重新搜索/首屏加载后列表下标变化,收起煎法气泡
|
||||
this.wayPanelDrugIndex = -1;
|
||||
this.drugList = merged;
|
||||
}
|
||||
this.drugList.forEach(drug => {
|
||||
if (typeof drug._quantity === 'undefined') {
|
||||
this.$set(drug, '_quantity', 0);
|
||||
}
|
||||
if (typeof drug._way_id === 'undefined') {
|
||||
this.$set(drug, '_way_id', Number(drug.way_id || (drug.drug && drug.drug.way_id) || 0));
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取药品列表失败:', error);
|
||||
@@ -420,6 +481,7 @@ export default {
|
||||
clearTimeout(this.searchTimer);
|
||||
this.searchTimer = setTimeout(() => {
|
||||
const name = (this.searchKey || '').trim();
|
||||
this.wayPanelDrugIndex = -1;
|
||||
if (!name) {
|
||||
this.page = 1;
|
||||
this.hasMore = false;
|
||||
@@ -529,6 +591,10 @@ export default {
|
||||
const indexId = drug.id;
|
||||
const entityId = (drug.drug && drug.drug.id) || drug.id;
|
||||
const wxKeyBase = indexId != null && indexId !== '' ? String(indexId) : String(entityId || '');
|
||||
const wayId = Number(
|
||||
drug._way_id ?? (drug.drug && drug.drug.way_id) ?? drug.way_id ?? 0
|
||||
);
|
||||
const wayHit = (this.drugUseWayList || []).find((w) => Number(w.id) === wayId);
|
||||
return {
|
||||
index_id: indexId,
|
||||
id: entityId,
|
||||
@@ -536,12 +602,80 @@ export default {
|
||||
number: drug._quantity,
|
||||
price: drug.price || 0,
|
||||
buy_price: drug.buy_price ?? 0,
|
||||
way_id: (drug.drug && drug.drug.way_id) || drug.way_id || 0,
|
||||
way_id: wayId || 0,
|
||||
use_ways: wayHit || null,
|
||||
select_number: 1,
|
||||
_wxKey: wxKeyBase ? `sch-${wxKeyBase}` : `sch${this.selectedDrugs.length}`,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 展示非默认煎法文案;煎服/0 不展示(chip)或用「煎服(默认)」在选择行
|
||||
*/
|
||||
getWayLabel(drugOrSel) {
|
||||
if (!drugOrSel) return '';
|
||||
const wayId = Number(
|
||||
drugOrSel._way_id ?? drugOrSel.way_id ?? drugOrSel.use_ways?.id ?? 0
|
||||
);
|
||||
if (!wayId) return '';
|
||||
const hit = (this.drugUseWayList || []).find((w) => Number(w.id) === wayId);
|
||||
const name =
|
||||
(hit && hit.name) ||
|
||||
(drugOrSel.use_ways && drugOrSel.use_ways.name) ||
|
||||
'';
|
||||
if (!name || name === '煎服') return '';
|
||||
return name;
|
||||
},
|
||||
|
||||
/**
|
||||
* 煎法行展示文案:有先煎/后下等显示标签名,否则「煎服(默认)」
|
||||
*/
|
||||
getWayDisplayName(drugOrSel) {
|
||||
return this.getWayLabel(drugOrSel) || '煎服(默认)';
|
||||
},
|
||||
/**
|
||||
* 判断气泡标签是否为当前药材选中煎法
|
||||
*/
|
||||
isWayActive(drug, wayItem) {
|
||||
if (!drug || !wayItem) return false;
|
||||
const wayId = Number(drug._way_id ?? drug.way_id ?? 0);
|
||||
return wayId > 0 && wayId === Number(wayItem.id);
|
||||
},
|
||||
/**
|
||||
* 展开/收起当前药材的煎法气泡卡片(同时只开一行)
|
||||
*/
|
||||
toggleWayPanel(event) {
|
||||
const index = Number(event.currentTarget.dataset.index);
|
||||
if (Number.isNaN(index)) return;
|
||||
if (!(this.drugUseWayList || []).length) {
|
||||
this.$toast('暂无煎法选项');
|
||||
return;
|
||||
}
|
||||
this.wayPanelDrugIndex = this.wayPanelDrugIndex === index ? -1 : index;
|
||||
},
|
||||
/**
|
||||
* 气泡标签点选煎法:写入 _way_id 并同步已选处方后收起
|
||||
*/
|
||||
selectWayTag(event) {
|
||||
const ds = event.currentTarget.dataset || {};
|
||||
const index = Number(ds.index);
|
||||
const wayId = Number(ds.wayId ?? ds['way-id'] ?? 0);
|
||||
const drug = this.drugList[index];
|
||||
if (!drug || Number.isNaN(index)) return;
|
||||
const picked = (this.drugUseWayList || []).find((w) => Number(w.id) === wayId) || null;
|
||||
this.$set(drug, '_way_id', wayId || 0);
|
||||
this.$set(drug, 'way_id', wayId || 0);
|
||||
const existIndex = this.findSelectedIndex(drug);
|
||||
if (existIndex !== -1) {
|
||||
this.$set(this.selectedDrugs[existIndex], 'way_id', wayId || 0);
|
||||
this.$set(this.selectedDrugs[existIndex], 'use_ways', picked);
|
||||
this.emitSelect();
|
||||
} else if (drug._quantity > 0) {
|
||||
this.tryAutoAddDrug(drug, { showToast: false });
|
||||
}
|
||||
this.wayPanelDrugIndex = -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* emit select 事件,传出当前完整的 selectedDrugs(深拷贝)
|
||||
*/
|
||||
@@ -563,6 +697,11 @@ export default {
|
||||
const isNew = existIndex === -1;
|
||||
if (existIndex !== -1) {
|
||||
this.$set(this.selectedDrugs[existIndex], 'number', drug._quantity);
|
||||
// 保持煎法与列表一致
|
||||
const wayId = Number(drug._way_id ?? drug.way_id ?? 0);
|
||||
this.$set(this.selectedDrugs[existIndex], 'way_id', wayId);
|
||||
const wayHit = (this.drugUseWayList || []).find((w) => Number(w.id) === wayId);
|
||||
this.$set(this.selectedDrugs[existIndex], 'use_ways', wayHit || null);
|
||||
} else {
|
||||
this.selectedDrugs.push(this.buildSelectedItem(drug));
|
||||
}
|
||||
@@ -818,6 +957,7 @@ export default {
|
||||
.color-title { color: #2A2E35; }
|
||||
.color-sub { color: #8A92A3; }
|
||||
.color-price { color: #F53F3F; }
|
||||
.color-way { color: #5b8ff9; }
|
||||
.fs-26 { font-size: 26rpx; }
|
||||
.fs-28 { font-size: 28rpx; }
|
||||
.fs-32 { font-size: 32rpx; }
|
||||
@@ -904,7 +1044,48 @@ export default {
|
||||
font-size: 24rpx;
|
||||
color: #00A88A;
|
||||
margin-left: 8rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.selected-chip-way {
|
||||
font-size: 22rpx;
|
||||
color: #5b8ff9;
|
||||
margin-left: 8rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
.way-block {
|
||||
width: 100%;
|
||||
}
|
||||
.way-row {
|
||||
padding: 8rpx 0;
|
||||
}
|
||||
.way-picker {
|
||||
padding: 8rpx 16rpx;
|
||||
background: #F7F8FA;
|
||||
border-radius: 8rpx;
|
||||
border: 1rpx solid #E2E8F0;
|
||||
}
|
||||
/* 煎法气泡卡片:标签直接点选,不限 ActionSheet 6 项 */
|
||||
.way-bubble-card {
|
||||
margin-top: 12rpx;
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
border: 1rpx solid #E2E8F0;
|
||||
box-shadow: 0 8rpx 24rpx rgba(42, 46, 53, 0.1);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.way-bubble-tag {
|
||||
padding: 10rpx 24rpx;
|
||||
background-color: #F4F6F8;
|
||||
color: #6C7380;
|
||||
border-radius: 30rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.way-bubble-tag.active {
|
||||
background-color: #00A88A;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.selected-chip-close {
|
||||
@@ -1065,4 +1246,17 @@ export default {
|
||||
padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 配送仓绑定药品标识 */
|
||||
.multi-wh-tag {
|
||||
flex-shrink: 0;
|
||||
padding: 0 10rpx;
|
||||
height: 32rpx;
|
||||
line-height: 30rpx;
|
||||
font-size: 20rpx;
|
||||
color: #2b6de5;
|
||||
background: rgba(43, 109, 229, 0.08);
|
||||
border: 1rpx solid rgba(43, 109, 229, 0.45);
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -44,7 +44,10 @@
|
||||
></image>
|
||||
<view class="flex-1 m-l-24 flex-col">
|
||||
<view class="flex-row flex-jus-sp flex-ali-start">
|
||||
<text class="fs-30 font-bold color-title line-clamp-2 w-70">{{item.drug.drug_name || item.drug.name}}</text>
|
||||
<view class="flex-row flex-ali-center w-70">
|
||||
<text class="fs-30 font-bold color-title line-clamp-2">{{item.drug.drug_name || item.drug.name}}</text>
|
||||
<text v-if="Number(item.has_delivery_warehouse) === 1" class="multi-wh-tag m-l-8">多仓</text>
|
||||
</view>
|
||||
<text class="fs-32 font-bold color-price">¥{{parseFloat(item.price || 0).toFixed(2)}}</text>
|
||||
</view>
|
||||
<view class="flex-row flex-jus-sp flex-ali-center m-t-12">
|
||||
@@ -357,4 +360,17 @@ export default {
|
||||
padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 配送仓绑定药品标识 */
|
||||
.multi-wh-tag {
|
||||
flex-shrink: 0;
|
||||
padding: 0 10rpx;
|
||||
height: 32rpx;
|
||||
line-height: 30rpx;
|
||||
font-size: 20rpx;
|
||||
color: #2b6de5;
|
||||
background: rgba(43, 109, 229, 0.08);
|
||||
border: 1rpx solid rgba(43, 109, 229, 0.45);
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- 用 page-container 防止右滑误退出开方页 -->
|
||||
<page-container
|
||||
v-if="show"
|
||||
:show="show"
|
||||
:overlay="false"
|
||||
@leave="handleClose"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
<u-popup
|
||||
v-model="show"
|
||||
mode="bottom"
|
||||
:closeable="true"
|
||||
@close="handleClose"
|
||||
:safe-area-inset-bottom="true"
|
||||
:mask-close-able="true"
|
||||
z-index="10080"
|
||||
height="75%"
|
||||
>
|
||||
<view class="warehouse-select-drawer page-bg">
|
||||
<view class="modal-header white">
|
||||
<d-text text="选择配送仓库" className="fs-32 font-bold color-title"></d-text>
|
||||
</view>
|
||||
<scroll-view class="scroll-container" scroll-y>
|
||||
<view v-if="currentRow" class="step-body">
|
||||
<view class="step-progress">第 {{ stepIndex + 1 }} / {{ localRows.length }} 个药品</view>
|
||||
<!-- 当前药品:图 / 名 / 规格 -->
|
||||
<view class="drug-info flex-row flex-ali-start">
|
||||
<image
|
||||
v-if="currentRow.image"
|
||||
class="drug-img"
|
||||
:src="currentRow.image"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view v-else class="drug-img drug-img-placeholder">暂无图</view>
|
||||
<view class="drug-meta">
|
||||
<view class="drug-name">{{ currentRow.drug_name }}</view>
|
||||
<view class="drug-spec">规格:{{ currentRow.specification || '--' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-list">
|
||||
<view
|
||||
v-for="opt in currentRow.options"
|
||||
:key="opt.warehouse_id"
|
||||
class="wh-card"
|
||||
:class="{ 'is-active': currentRow.warehouse_id === opt.warehouse_id }"
|
||||
@click="selectWarehouse(opt.warehouse_id)"
|
||||
>
|
||||
<view class="wh-name">{{ opt.warehouse_name }}</view>
|
||||
<view class="wh-meta">
|
||||
供货价 {{ opt.quote }} · 库存 {{ opt.available_stock }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty-tip">当前药品无需选择配送仓库</view>
|
||||
</scroll-view>
|
||||
<!-- 底部:外包 flex 半宽,避免 u-button custom-style 的 flex 在小程序被忽略 -->
|
||||
<view class="modal-footer white shadow-up">
|
||||
<view class="footer-btn-wrap">
|
||||
<u-button
|
||||
:throttle-time="0"
|
||||
@click="onLeftClick"
|
||||
shape="circle"
|
||||
:custom-style="leftBtnStyle"
|
||||
>{{ leftBtnText }}</u-button>
|
||||
</view>
|
||||
<view class="footer-btn-wrap">
|
||||
<u-button
|
||||
:throttle-time="0"
|
||||
@click="onRightClick"
|
||||
shape="circle"
|
||||
:custom-style="rightBtnStyle"
|
||||
>{{ rightBtnText }}</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 在线复诊开方:按药分步卡片选配送仓
|
||||
* 默认选中每药 options[0](与 auto 最低 quote 一致)
|
||||
*/
|
||||
export default {
|
||||
name: 'WarehouseSelectDrawer',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/** [{ drug_id, drug_name, image, specification, options, warehouse_id? }] */
|
||||
rows: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localRows: [],
|
||||
stepIndex: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
show: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(v) {
|
||||
this.$emit('input', v);
|
||||
},
|
||||
},
|
||||
currentRow() {
|
||||
return this.localRows[this.stepIndex] || null;
|
||||
},
|
||||
isFirstStep() {
|
||||
return this.stepIndex <= 0;
|
||||
},
|
||||
isLastStep() {
|
||||
return this.stepIndex >= Math.max(this.localRows.length - 1, 0);
|
||||
},
|
||||
leftBtnText() {
|
||||
return this.isFirstStep ? '取消' : '上一步';
|
||||
},
|
||||
rightBtnText() {
|
||||
return this.isLastStep ? '确认' : '下一步';
|
||||
},
|
||||
leftBtnStyle() {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '88rpx',
|
||||
margin: '0',
|
||||
background: '#F5F6F8',
|
||||
color: '#333',
|
||||
border: 'none',
|
||||
fontSize: '30rpx',
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
},
|
||||
rightBtnStyle() {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '88rpx',
|
||||
margin: '0',
|
||||
background: '#2B6DE5',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
fontSize: '30rpx',
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
if (val) {
|
||||
// 打开时默认选中每药最低价仓(options[0]);规格/图/名可从 option 回退
|
||||
this.localRows = (this.rows || []).map((row) => {
|
||||
const first =
|
||||
Array.isArray(row.options) && row.options[0] ? row.options[0] : null;
|
||||
return {
|
||||
...row,
|
||||
warehouse_id:
|
||||
row.warehouse_id ||
|
||||
(first && first.warehouse_id) ||
|
||||
0,
|
||||
options: Array.isArray(row.options) ? row.options : [],
|
||||
drug_name: row.drug_name || (first && first.drug_name) || '',
|
||||
image: row.image || (first && first.image) || '',
|
||||
specification:
|
||||
row.specification || (first && first.specification) || '',
|
||||
};
|
||||
});
|
||||
this.stepIndex = 0;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/** 点击卡片选中当前步骤的仓 */
|
||||
selectWarehouse(warehouseId) {
|
||||
if (!this.currentRow) return;
|
||||
this.$set
|
||||
? this.$set(this.currentRow, 'warehouse_id', warehouseId)
|
||||
: (this.currentRow.warehouse_id = warehouseId);
|
||||
},
|
||||
onLeftClick() {
|
||||
if (this.isFirstStep) {
|
||||
this.handleClose();
|
||||
return;
|
||||
}
|
||||
this.stepIndex -= 1;
|
||||
},
|
||||
onRightClick() {
|
||||
if (!this.currentRow || !this.currentRow.warehouse_id) {
|
||||
uni.showToast({ title: '请先选择配送仓库', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!this.isLastStep) {
|
||||
this.stepIndex += 1;
|
||||
return;
|
||||
}
|
||||
this.handleConfirm();
|
||||
},
|
||||
handleClose() {
|
||||
this.show = false;
|
||||
this.$emit('cancel');
|
||||
},
|
||||
handleConfirm() {
|
||||
const map = {};
|
||||
for (const row of this.localRows) {
|
||||
if (!row.warehouse_id) {
|
||||
uni.showToast({ title: `请为【${row.drug_name}】选择配送仓库`, icon: 'none' });
|
||||
return;
|
||||
}
|
||||
map[row.drug_id] = row.warehouse_id;
|
||||
}
|
||||
this.show = false;
|
||||
this.$emit('confirm', map);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.warehouse-select-drawer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.modal-header {
|
||||
padding: 32rpx 32rpx 16rpx;
|
||||
}
|
||||
.scroll-container {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
padding: 0 24rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.step-progress {
|
||||
font-size: 24rpx;
|
||||
color: #8a92a3;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.drug-info {
|
||||
padding: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #f5f6f8;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.drug-img {
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
background: #e5e6eb;
|
||||
}
|
||||
.drug-img-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22rpx;
|
||||
color: #8a92a3;
|
||||
}
|
||||
.drug-meta {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.drug-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1f2329;
|
||||
}
|
||||
.drug-spec {
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: #8a92a3;
|
||||
}
|
||||
.card-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.wh-card {
|
||||
min-width: 280rpx;
|
||||
flex: 1;
|
||||
max-width: 48%;
|
||||
padding: 20rpx 24rpx;
|
||||
border-radius: 16rpx;
|
||||
border: 2rpx solid #e5e6eb;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.wh-card.is-active {
|
||||
border-color: #2b6de5;
|
||||
background: rgba(43, 109, 229, 0.06);
|
||||
}
|
||||
.wh-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #1f2329;
|
||||
}
|
||||
.wh-meta {
|
||||
margin-top: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: #8a92a3;
|
||||
}
|
||||
.empty-tip {
|
||||
padding: 48rpx 0;
|
||||
text-align: center;
|
||||
color: #8a92a3;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.footer-btn-wrap {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.shadow-up {
|
||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
</style>
|
||||
@@ -46,7 +46,10 @@
|
||||
></image>
|
||||
<view class="flex-1 m-l-24 flex-col">
|
||||
<view class="flex-row flex-jus-sp flex-ali-start">
|
||||
<text class="fs-30 font-bold color-title line-clamp-2 w-70">{{item.drug.drug_name || item.drug.name}}</text>
|
||||
<view class="flex-row flex-ali-center w-70">
|
||||
<text class="fs-30 font-bold color-title line-clamp-2">{{item.drug.drug_name || item.drug.name}}</text>
|
||||
<text v-if="Number(item.has_delivery_warehouse) === 1" class="multi-wh-tag m-l-8">多仓</text>
|
||||
</view>
|
||||
<text class="fs-32 font-bold color-price">¥{{parseFloat(item.price || 0).toFixed(2)}}</text>
|
||||
</view>
|
||||
<view class="flex-row flex-jus-sp flex-ali-center m-t-12">
|
||||
@@ -390,4 +393,17 @@ export default {
|
||||
padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 配送仓绑定药品标识 */
|
||||
.multi-wh-tag {
|
||||
flex-shrink: 0;
|
||||
padding: 0 10rpx;
|
||||
height: 32rpx;
|
||||
line-height: 30rpx;
|
||||
font-size: 20rpx;
|
||||
color: #2b6de5;
|
||||
background: rgba(43, 109, 229, 0.08);
|
||||
border: 1rpx solid rgba(43, 109, 229, 0.45);
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -279,7 +279,13 @@
|
||||
<!-- 中药极简高密度标签云设计 -->
|
||||
<view class="white radius-12 p-32" v-if="currentDrugs.length > 0">
|
||||
<view class="flex-row flex-jus-sp flex-ali-center m-b-24">
|
||||
<text class="fs-28 font-bold color-title">药材清单 (共{{currentDrugs.length}}味)</text>
|
||||
<view class="flex-col">
|
||||
<text class="fs-28 font-bold color-title">药材清单 (共{{currentDrugs.length}}味)</text>
|
||||
<text
|
||||
v-if="!isSpecialPrescriptionCartLocked"
|
||||
class="fs-22 text-gray m-t-4"
|
||||
>先煎/后下请在「选择中药」中设置</text>
|
||||
</view>
|
||||
<view class="flex-row">
|
||||
<template v-if="!isSpecialPrescriptionCartLocked">
|
||||
<text class="fs-26 color-primary m-r-32" @click="handleOpenChineseMedicineModal">修改剂量</text>
|
||||
@@ -302,6 +308,8 @@
|
||||
@longpress.stop="!isSpecialPrescriptionCartLocked && onTcmTagLongPress">
|
||||
<text class="tcm-name">{{it.drug_name || it.name}}</text>
|
||||
<text class="tcm-weight">{{String(it.number)}}{{it.unit ? it.unit.name : 'g'}}</text>
|
||||
<!-- 展示药材级先煎/后下(在选择中药弹窗设置) -->
|
||||
<text v-if="getTcmWayLabel(it)" class="tcm-way">{{ getTcmWayLabel(it) }}</text>
|
||||
<text
|
||||
v-if="seeRate == 1 && getChineseItemMargin(it) !== '--'"
|
||||
class="tcm-margin"
|
||||
@@ -546,6 +554,7 @@
|
||||
:see-rate="seeRate"
|
||||
:salesperson-transfer-mode="isSalespersonTransferMode"
|
||||
:show-drug-price="showSalespersonTransferPrice"
|
||||
:drug-use-way-list="drugUseList.drug_use_way || []"
|
||||
@select="handleSelectChineseDrug"
|
||||
/>
|
||||
|
||||
@@ -556,6 +565,14 @@
|
||||
@confirm="handleTransferPatientConfirm"
|
||||
/>
|
||||
|
||||
<!-- 在线复诊:卡片选配送仓 -->
|
||||
<WarehouseSelectDrawer
|
||||
v-model="showWarehouseSelectDrawer"
|
||||
:rows="warehouseSelectRows"
|
||||
@confirm="onWarehouseSelectConfirm"
|
||||
@cancel="onWarehouseSelectCancel"
|
||||
/>
|
||||
|
||||
<!-- 简单产品选择弹窗 -->
|
||||
<SimpleProductModal
|
||||
v-model="showSimpleProductModal"
|
||||
@@ -602,6 +619,7 @@
|
||||
// 注意:这部分保持你原有的逻辑代码,不进行任何修改,确保业务完全正常
|
||||
import {
|
||||
addWestPrescription,
|
||||
getDeliveryWarehouseOptionsByDrugs,
|
||||
checkChineseMedicineConflictApi,
|
||||
getDiseaseList,
|
||||
getDrugUseList,
|
||||
@@ -634,6 +652,7 @@ import WesternMedicineUsageModal from './components/modals/WesternMedicineUsageM
|
||||
import ChineseMedicineConfig from './components/ChineseMedicineConfig.vue';
|
||||
import TraditionalTermPickerModal from './components/modals/TraditionalTermPickerModal.vue';
|
||||
import TransferPatientDrawer from './components/modals/TransferPatientDrawer.vue';
|
||||
import WarehouseSelectDrawer from './components/modals/WarehouseSelectDrawer.vue';
|
||||
import OrderPricePercentAdjustPopup from '@/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup.vue';
|
||||
import { applyRatioToDrugs, formatPriceDiscountLabel, normalizeQuickOptions } from '@/subPackages/sub_business_shared/common/pricePercentAdjust.js';
|
||||
import {
|
||||
@@ -665,7 +684,8 @@ const PRESCRIPTION_OVERLAY_KEYS = [
|
||||
'showDiagnosisModal',
|
||||
'showDoctorOrderModal',
|
||||
'showSaveCommonPrescriptionModal',
|
||||
'showTransferPatientDrawer'
|
||||
'showTransferPatientDrawer',
|
||||
'showWarehouseSelectDrawer',
|
||||
];
|
||||
|
||||
export default {
|
||||
@@ -681,6 +701,7 @@ export default {
|
||||
ChineseMedicineConfig,
|
||||
TraditionalTermPickerModal,
|
||||
TransferPatientDrawer,
|
||||
WarehouseSelectDrawer,
|
||||
OrderPricePercentAdjustPopup,
|
||||
},
|
||||
data() {
|
||||
@@ -738,6 +759,8 @@ export default {
|
||||
commonPrescriptionName: '',
|
||||
isCommonPrescription: false,
|
||||
_presetActiveCategory: null,
|
||||
/** URL/传方指定了分类时,禁止被诊所类型默认覆盖 */
|
||||
_categoryLockedByQuery: false,
|
||||
isSubmitting: false,
|
||||
identity: uni.getStorageSync('role'),
|
||||
registerStoreInfo: null,
|
||||
@@ -817,6 +840,11 @@ export default {
|
||||
salespersonSeePrice: 0,
|
||||
showTransferPatientDrawer: false,
|
||||
transferDrawerSubmitting: false,
|
||||
/** 在线复诊选仓抽屉 */
|
||||
showWarehouseSelectDrawer: false,
|
||||
warehouseSelectRows: [],
|
||||
/** resolvePharmacyWarehouseMap 的 Promise resolve;false=取消,null=无需选,map=已选 */
|
||||
_warehouseSelectResolve: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -1016,6 +1044,7 @@ export default {
|
||||
if (this.isSalespersonTransferMode) {
|
||||
this.registerId = SALESPERSON_TRANSFER_STORAGE_KEY;
|
||||
this._presetActiveCategory = 1;
|
||||
this._categoryLockedByQuery = true;
|
||||
// 传方模式默认自制剂 + 剂包
|
||||
this.chineseConfig = {
|
||||
...this.chineseConfig,
|
||||
@@ -1034,7 +1063,10 @@ export default {
|
||||
}
|
||||
if (options.initial_category !== undefined && options.initial_category !== '') {
|
||||
const n = parseInt(options.initial_category, 10);
|
||||
if (!Number.isNaN(n)) this._presetActiveCategory = n;
|
||||
if (!Number.isNaN(n)) {
|
||||
this._presetActiveCategory = n;
|
||||
this._categoryLockedByQuery = true;
|
||||
}
|
||||
}
|
||||
this.initializePageData();
|
||||
},
|
||||
@@ -1083,6 +1115,8 @@ export default {
|
||||
await this.loadRegisterOrderType();
|
||||
await this.loadBasicConfigData();
|
||||
await this.loadRegisterStoreInfo();
|
||||
// 无 URL/上次切换/草稿时,按诊所类型兜底中药或西药 Tab
|
||||
this.applyDefaultCategoryByClinicType();
|
||||
}
|
||||
this.loadCurrentCategoryDrugs();
|
||||
if (this.activeCategory === 1 && this.chineseConfig.ruleType === 2) {
|
||||
@@ -1147,6 +1181,7 @@ export default {
|
||||
if (this._presetActiveCategory !== null && this._presetActiveCategory !== undefined && !Number.isNaN(this._presetActiveCategory)) {
|
||||
this.activeCategory = this._presetActiveCategory;
|
||||
this._presetActiveCategory = null;
|
||||
// 保留 _categoryLockedByQuery,避免随后被诊所类型默认覆盖
|
||||
return;
|
||||
}
|
||||
const savedCategory = PrescriptionStorage.getActiveCategory(this.registerId);
|
||||
@@ -1163,6 +1198,28 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 开方默认 Tab 兜底:仅当无 URL 锁定、无上次切换分类、无任何药品草稿时,
|
||||
* 按诊所类型设中药(1)/西药(2)。优先级:URL > 上次切换 > 草稿 > clinic_type
|
||||
*/
|
||||
applyDefaultCategoryByClinicType() {
|
||||
if (this.isSalespersonTransferMode || this._categoryLockedByQuery) return;
|
||||
if (!this.registerId) return;
|
||||
// 最后切换的分类优先,不再用诊所类型覆盖
|
||||
if (PrescriptionStorage.getActiveCategory(this.registerId)) return;
|
||||
// 任一类有草稿则沿用 restore 结果
|
||||
if (PrescriptionStorage.hasDraftWithDrugs(this.registerId)) return;
|
||||
const clinicType = Number(this.registerStoreInfo?.clinic_type ?? 0);
|
||||
let next = 0;
|
||||
if (clinicType === 2) {
|
||||
next = 1; // 中医诊所 → 中药
|
||||
} else if (clinicType === 1) {
|
||||
next = 2; // 西医诊所 → 西(中成)药
|
||||
}
|
||||
if (!next || next === this.activeCategory) return;
|
||||
this.activeCategory = next;
|
||||
PrescriptionStorage.saveActiveCategory(next, this.registerId);
|
||||
},
|
||||
loadCurrentCategoryDrugs() {
|
||||
const data = PrescriptionStorage.loadPrescriptionData(this.activeCategory, this.registerId);
|
||||
if (data) {
|
||||
@@ -2215,8 +2272,120 @@ export default {
|
||||
await this.executeSendPrescription(doctorSecondSign, 0, null);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 主界面药材清单展示先煎/后下文案(在选择中药弹窗设置;默认煎服不展示)
|
||||
*/
|
||||
getTcmWayLabel(drug) {
|
||||
if (!drug) return '';
|
||||
const wayId = Number(drug.way_id ?? drug.use_ways?.id ?? 0);
|
||||
if (!wayId) return '';
|
||||
const list = (this.drugUseList && this.drugUseList.drug_use_way) || [];
|
||||
const hit = list.find((w) => Number(w.id) === wayId);
|
||||
const name = (hit && hit.name) || (drug.use_ways && drug.use_ways.name) || '';
|
||||
if (!name || name === '煎服') return '';
|
||||
return name;
|
||||
},
|
||||
/**
|
||||
* 在线复诊:有仓药绑定时弹卡片选仓;默认最低价;取消返回 false
|
||||
*/
|
||||
async resolvePharmacyWarehouseMap() {
|
||||
if (!this.isOnlineRevisit) {
|
||||
return null;
|
||||
}
|
||||
const drugs = this.currentDrugs || [];
|
||||
const drugIds = [];
|
||||
const needQtyMap = {};
|
||||
for (const d of drugs) {
|
||||
const id = Number(d?.id || 0);
|
||||
const qty = Number(d?.select_number || d?.number || 0);
|
||||
if (id <= 0 || qty <= 0) continue;
|
||||
drugIds.push(id);
|
||||
needQtyMap[id] = (needQtyMap[id] || 0) + qty;
|
||||
}
|
||||
if (!drugIds.length) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const res = await getDeliveryWarehouseOptionsByDrugs({
|
||||
drug_ids: drugIds.join(','),
|
||||
need_qty_map: needQtyMap,
|
||||
});
|
||||
const map = (res && (res.result || res.data || res)) || {};
|
||||
const rows = [];
|
||||
for (const d of drugs) {
|
||||
const id = Number(d?.id || 0);
|
||||
if (id <= 0) continue;
|
||||
const options = map[String(id)] || map[id] || [];
|
||||
if (!Array.isArray(options) || !options.length) continue;
|
||||
rows.push({
|
||||
drug_id: id,
|
||||
// 优先用仓选项接口带回的药品信息,避免处方草稿未存规格/图
|
||||
drug_name: String(
|
||||
(options[0] && options[0].drug_name) ||
|
||||
d.drug_name ||
|
||||
d.name ||
|
||||
`药品#${id}`,
|
||||
),
|
||||
image: String(
|
||||
(options[0] && options[0].image) ||
|
||||
d.image ||
|
||||
d._image ||
|
||||
(d.drug && d.drug.image) ||
|
||||
'',
|
||||
),
|
||||
specification: String(
|
||||
(options[0] && options[0].specification) ||
|
||||
d.specification ||
|
||||
(d.drug && d.drug.specification) ||
|
||||
'',
|
||||
),
|
||||
options: options.map((o) => ({
|
||||
warehouse_id: Number(o.warehouse_id),
|
||||
warehouse_name: String(o.warehouse_name || ''),
|
||||
quote: String(o.quote ?? '0'),
|
||||
available_stock: Number(o.available_stock ?? 0),
|
||||
// 保留药品展示字段,供抽屉从 options[0] 回退读取
|
||||
drug_name: String(o.drug_name || ''),
|
||||
image: String(o.image || ''),
|
||||
specification: String(o.specification || ''),
|
||||
})),
|
||||
warehouse_id: Number(options[0].warehouse_id),
|
||||
});
|
||||
}
|
||||
if (!rows.length) {
|
||||
return null;
|
||||
}
|
||||
this.warehouseSelectRows = rows;
|
||||
this.showWarehouseSelectDrawer = true;
|
||||
return new Promise((resolve) => {
|
||||
this._warehouseSelectResolve = resolve;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('加载配送仓库失败', e);
|
||||
this.$toast((e && e.message) || '加载配送仓库失败');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
onWarehouseSelectConfirm(map) {
|
||||
const resolve = this._warehouseSelectResolve;
|
||||
this._warehouseSelectResolve = null;
|
||||
this.showWarehouseSelectDrawer = false;
|
||||
if (typeof resolve === 'function') resolve(map || {});
|
||||
},
|
||||
onWarehouseSelectCancel() {
|
||||
const resolve = this._warehouseSelectResolve;
|
||||
this._warehouseSelectResolve = null;
|
||||
this.showWarehouseSelectDrawer = false;
|
||||
if (typeof resolve === 'function') resolve(false);
|
||||
},
|
||||
async executeSendPrescription(doctorSecondSign = 0, sendMode = 0, customStoreId = null) {
|
||||
try {
|
||||
// 在线复诊有绑仓时先卡片选仓
|
||||
const warehouseMap = await this.resolvePharmacyWarehouseMap();
|
||||
if (warehouseMap === false) {
|
||||
this.isSubmitting = false;
|
||||
return;
|
||||
}
|
||||
if (this.activeCategory === 2) {
|
||||
if (!this.patientInfo || !this.patientId) {
|
||||
if (this.patientId && !this.patientInfo) await this.loadPatientInfo();
|
||||
@@ -2269,6 +2438,13 @@ export default {
|
||||
price_discount: this.priceDiscount,
|
||||
special_prescription_id: Number(this.appliedSpecialPrescriptionId) || 0,
|
||||
};
|
||||
if (warehouseMap && typeof warehouseMap === 'object') {
|
||||
// 列表格式避免数字键对象在 form/部分环境序列化丢失
|
||||
params.warehouse_map = Object.keys(warehouseMap).map((drugId) => ({
|
||||
drug_id: Number(drugId),
|
||||
warehouse_id: Number(warehouseMap[drugId]),
|
||||
}));
|
||||
}
|
||||
if (this.activeCategory === 1) {
|
||||
params.package_method_id = this.chineseConfig.packageMethodId || null; params.process_rule_id = this.chineseConfig.processRuleId || null; params.process_rule_note_id = this.chineseConfig.processRuleNoteId || null; params.child_process_rule_id = this.chineseConfig.childProcessRuleId || null; params.process_rule_type = this.chineseConfig.ruleType || 1; params.processing_fee = this.processingFee; params.dosage = this.chineseConfig.dosage || 7; params.day_dosage = this.chineseConfig.dayDosage || 2;
|
||||
if (this.isOnlineRevisit) {
|
||||
@@ -2718,6 +2894,12 @@ export default {
|
||||
color: #00A88A;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
.tcm-way {
|
||||
font-size: 22rpx;
|
||||
color: #5b8ff9;
|
||||
margin-left: 8rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
.tcm-margin {
|
||||
font-size: 22rpx;
|
||||
color: #ff9800;
|
||||
|
||||
12
utils/formatStoreNameWithHu.js
Normal file
12
utils/formatStoreNameWithHu.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 在线处方诊所名追加「(互)」:is_online 为 1/2/3,线下 0 不加
|
||||
*/
|
||||
export function formatStoreNameWithHu(storeName, isOnline) {
|
||||
const name = String(storeName || '').trim()
|
||||
if (!name) return ''
|
||||
const online = Number(isOnline)
|
||||
if (online === 1 || online === 2 || online === 3) {
|
||||
return name.endsWith('(互)') ? name : `${name}(互)`
|
||||
}
|
||||
return name
|
||||
}
|
||||
@@ -6,8 +6,8 @@ export function checkDev(key = '') {
|
||||
// 判断某些模块是否开启
|
||||
switch (key) {
|
||||
case 'dev':
|
||||
// return false;
|
||||
return true;
|
||||
return false;
|
||||
// return true;
|
||||
case 'open-im':
|
||||
// return false;
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user