fix:
1. 优化了审方pc的效果 2. 就诊人信息优化
This commit is contained in:
@@ -0,0 +1,276 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 处方溯源展示内容(对齐小程序药师溯源:就诊→开具→审核)
|
||||||
|
* 由审方/业务处方两个 Modal 共用,避免双份漂移
|
||||||
|
*/
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { Timeline, TimelineItem } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FileTextOutlined,
|
||||||
|
MedicineBoxOutlined,
|
||||||
|
ShopOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
data: Record<string, any> | null;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const formatTime = (timestamp: unknown) => {
|
||||||
|
if (!timestamp) return '--';
|
||||||
|
if (typeof timestamp === 'string' && timestamp.includes('-')) return timestamp;
|
||||||
|
const n = Number(timestamp);
|
||||||
|
if (!n) return '--';
|
||||||
|
return new Date(n * 1000).toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const prescriptionTypeMap: Record<number, string> = {
|
||||||
|
1: '中药处方',
|
||||||
|
2: '西药处方',
|
||||||
|
3: '保健食品',
|
||||||
|
5: '产品服务包',
|
||||||
|
6: '非药品',
|
||||||
|
7: '医疗器械',
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusMap: Record<number, string> = {
|
||||||
|
0: '待审核',
|
||||||
|
1: '已通过',
|
||||||
|
2: '已拒绝',
|
||||||
|
3: '已过期',
|
||||||
|
};
|
||||||
|
|
||||||
|
const prescriptionTypeText = computed(() => {
|
||||||
|
if (!props.data) return '--';
|
||||||
|
return prescriptionTypeMap[props.data.prescription_type] || '未知';
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusText = computed(() => {
|
||||||
|
if (!props.data) return '--';
|
||||||
|
return statusMap[props.data.status] || '未知';
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusColor = computed(() => {
|
||||||
|
if (!props.data) return 'gray';
|
||||||
|
const map: Record<number, string> = {
|
||||||
|
0: 'orange',
|
||||||
|
1: 'green',
|
||||||
|
2: 'red',
|
||||||
|
3: 'gray',
|
||||||
|
};
|
||||||
|
return map[props.data.status] || 'gray';
|
||||||
|
});
|
||||||
|
|
||||||
|
const expireTime = computed(() =>
|
||||||
|
props.data ? formatTime(props.data.auto_expire_time) : '--',
|
||||||
|
);
|
||||||
|
|
||||||
|
const patientSexText = computed(() => {
|
||||||
|
const sex = props.data?.user_patient?.sex;
|
||||||
|
if (sex === 1) return '男';
|
||||||
|
if (sex === 2) return '女';
|
||||||
|
return '--';
|
||||||
|
});
|
||||||
|
|
||||||
|
const doctorInfo = computed(
|
||||||
|
() => props.data?.doctor_info || props.data?.doctorInfo || null,
|
||||||
|
);
|
||||||
|
const pharmacistInfo = computed(
|
||||||
|
() => props.data?.pharmacist_info || props.data?.pharmacistInfo || null,
|
||||||
|
);
|
||||||
|
const userPatient = computed(
|
||||||
|
() => props.data?.user_patient || props.data?.userPatient || null,
|
||||||
|
);
|
||||||
|
const registerInfo = computed(
|
||||||
|
() => props.data?.register || null,
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="data" class="prescription-source-container">
|
||||||
|
<div class="mb-6 rounded-lg p-6 shadow-md">
|
||||||
|
<div class="mb-4 flex items-center">
|
||||||
|
<ShopOutlined class="mr-2 text-xl text-purple-500" />
|
||||||
|
<h2 class="text-xl font-bold">开具机构</h2>
|
||||||
|
</div>
|
||||||
|
<div v-if="data.store" class="grid grid-cols-1 gap-4">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">机构名称:</span>
|
||||||
|
<span class="value">{{ data.store.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="italic text-gray-500">暂无机构信息</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-6 rounded-lg p-6 shadow-md">
|
||||||
|
<div class="mb-4 flex items-center">
|
||||||
|
<FileTextOutlined class="mr-2 text-xl text-blue-500" />
|
||||||
|
<h2 class="text-xl font-bold">时间线</h2>
|
||||||
|
</div>
|
||||||
|
<Timeline>
|
||||||
|
<TimelineItem>
|
||||||
|
<div class="font-medium">就诊</div>
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
{{ formatTime(registerInfo?.created_at) }}
|
||||||
|
· {{ userPatient?.name || '--' }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-sm">
|
||||||
|
性别:{{ patientSexText }} · 年龄:{{ userPatient?.age ?? '--' }}
|
||||||
|
</div>
|
||||||
|
<div v-if="data.clinical_diagnose" class="mt-1 text-sm">
|
||||||
|
诊断:{{ data.clinical_diagnose }}
|
||||||
|
</div>
|
||||||
|
</TimelineItem>
|
||||||
|
<TimelineItem>
|
||||||
|
<div class="font-medium">开具</div>
|
||||||
|
<div class="text-sm text-gray-500">{{ data.created_at || '--' }}</div>
|
||||||
|
<div class="mt-1 text-sm">
|
||||||
|
医生:{{ doctorInfo?.name || '--' }} · 科室:{{
|
||||||
|
doctorInfo?.depart?.name || '--'
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-sm">
|
||||||
|
处方编号:{{ data.prescription_no }} · 类型:{{ prescriptionTypeText }}
|
||||||
|
</div>
|
||||||
|
</TimelineItem>
|
||||||
|
<TimelineItem>
|
||||||
|
<div class="font-medium">审核</div>
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
{{ data.pharmacist_view_time || '--' }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-sm">
|
||||||
|
状态:
|
||||||
|
<span :class="`text-${statusColor}-500`">{{ statusText }}</span>
|
||||||
|
· 药师:{{ pharmacistInfo?.name || '--' }}
|
||||||
|
</div>
|
||||||
|
<div v-if="data.reject_reason" class="mt-1 text-sm text-red-500">
|
||||||
|
驳回原因:{{ data.reject_reason }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="data.cancel_remark && Number(data.status) === 2"
|
||||||
|
class="mt-1 text-sm text-red-500"
|
||||||
|
>
|
||||||
|
驳回原因:{{ data.cancel_remark }}
|
||||||
|
</div>
|
||||||
|
</TimelineItem>
|
||||||
|
</Timeline>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-6 rounded-lg p-6 shadow-md">
|
||||||
|
<div class="mb-4 flex items-center">
|
||||||
|
<FileTextOutlined class="mr-2 text-xl text-blue-500" />
|
||||||
|
<h2 class="text-xl font-bold">处方基本信息</h2>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">处方编号:</span>
|
||||||
|
<span class="value">{{ data.prescription_no }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">处方类型:</span>
|
||||||
|
<span class="value">{{ prescriptionTypeText }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">处方状态:</span>
|
||||||
|
<span :class="`text-${statusColor}-500`" class="value">{{
|
||||||
|
statusText
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">总金额:</span>
|
||||||
|
<span class="value font-bold text-red-500"
|
||||||
|
>¥{{ data.total_pay_price }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">过期时间:</span>
|
||||||
|
<span class="value">{{ expireTime }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="data.reject_reason" class="info-item">
|
||||||
|
<span class="label">驳回原因:</span>
|
||||||
|
<span class="value text-red-500">{{ data.reject_reason }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-6 rounded-lg p-6 shadow-md">
|
||||||
|
<div class="mb-4 flex items-center">
|
||||||
|
<MedicineBoxOutlined class="mr-2 text-xl text-green-500" />
|
||||||
|
<h2 class="text-xl font-bold">医生信息</h2>
|
||||||
|
</div>
|
||||||
|
<div v-if="doctorInfo" class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">开具医生:</span>
|
||||||
|
<span class="value">{{ doctorInfo.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">开具科室:</span>
|
||||||
|
<span class="value">{{ doctorInfo.depart?.name || '--' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">开具机构:</span>
|
||||||
|
<span class="value">{{ data.store?.name || '--' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">开具时间:</span>
|
||||||
|
<span class="value">{{ data.created_at || '--' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="italic text-gray-500">暂无医生信息</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-lg p-6 shadow-md">
|
||||||
|
<div class="mb-4 flex items-center">
|
||||||
|
<UserOutlined class="mr-2 text-xl text-amber-500" />
|
||||||
|
<h2 class="text-xl font-bold">患者信息</h2>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="userPatient"
|
||||||
|
class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"
|
||||||
|
>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">患者姓名:</span>
|
||||||
|
<span class="value">{{ userPatient.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">年龄:</span>
|
||||||
|
<span class="value">{{ userPatient.age }}岁</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">性别:</span>
|
||||||
|
<span class="value">{{ patientSexText }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="italic text-gray-500">暂无患者信息</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex h-64 items-center justify-center">
|
||||||
|
<div
|
||||||
|
class="h-12 w-12 animate-spin rounded-full border-b-2 border-t-2 border-blue-500"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.prescription-source-container {
|
||||||
|
@apply max-h-[70vh] overflow-auto p-4;
|
||||||
|
}
|
||||||
|
.info-item {
|
||||||
|
@apply flex flex-col rounded-md p-3;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
@apply mb-1 text-sm text-gray-500;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
@apply font-medium;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,451 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 就诊人详情 Modal(患者视角)
|
||||||
|
* 与 Drawer 内容一致:资料 + 挂号/处方/订单;会员管理、订单「就诊人」入口使用
|
||||||
|
*/
|
||||||
|
import { h, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Descriptions,
|
||||||
|
Empty,
|
||||||
|
Spin,
|
||||||
|
Table,
|
||||||
|
Tabs,
|
||||||
|
Tag,
|
||||||
|
message,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
|
||||||
|
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
|
||||||
|
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
||||||
|
import OrderDetail from '#/views/business/order/product-order/components/detail.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getUserPatientOrderListApi,
|
||||||
|
getUserPatientPrescriptionListApi,
|
||||||
|
getUserPatientProfileDetailApi,
|
||||||
|
getUserPatientRegisterListApi,
|
||||||
|
} from './api';
|
||||||
|
import VisitTypeTag from './VisitTypeTag.vue';
|
||||||
|
|
||||||
|
defineOptions({ name: 'WxUserPatientDetailModal' });
|
||||||
|
|
||||||
|
const defaultAvatar = '/img/user-default-avatar.png';
|
||||||
|
|
||||||
|
const upId = ref(0);
|
||||||
|
const loading = ref(false);
|
||||||
|
const activeTab = ref('info');
|
||||||
|
const profile = ref<Record<string, any> | null>(null);
|
||||||
|
|
||||||
|
const registerList = ref<any[]>([]);
|
||||||
|
const prescriptionList = ref<any[]>([]);
|
||||||
|
const orderList = ref<any[]>([]);
|
||||||
|
const registerLoading = ref(false);
|
||||||
|
const prescriptionLoading = ref(false);
|
||||||
|
const orderLoading = ref(false);
|
||||||
|
|
||||||
|
const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
||||||
|
connectedComponent: PrescriptionDetail,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [OrderDetailModal, OrderDetailModalApi] = useVbenModal({
|
||||||
|
connectedComponent: OrderDetail,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
footer: false,
|
||||||
|
class: 'w-[760px]',
|
||||||
|
onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
profile.value = null;
|
||||||
|
registerList.value = [];
|
||||||
|
prescriptionList.value = [];
|
||||||
|
orderList.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = modalApi.getData<{ upId?: number; patientName?: string }>();
|
||||||
|
upId.value = Number(data?.upId || 0);
|
||||||
|
activeTab.value = 'info';
|
||||||
|
const name = String(data?.patientName || '').trim();
|
||||||
|
modalApi.setState({ title: name ? `就诊人:${name}` : '就诊人详情' });
|
||||||
|
if (upId.value > 0) {
|
||||||
|
void loadDetail();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 解析头像 URL */
|
||||||
|
function avatarSrc(raw?: string) {
|
||||||
|
const s = String(raw ?? '').trim();
|
||||||
|
if (!s) return defaultAvatar;
|
||||||
|
return resolveAvatarUrl(s) || defaultAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 既往/过敏/家族:0 无,否则展示 history */
|
||||||
|
function historyText(status?: number, history?: string) {
|
||||||
|
if (Number(status) === 0) return '无';
|
||||||
|
const t = String(history || '').trim();
|
||||||
|
return t || '有';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 肝/肾功能:0 正常,异常时附带指标文案 */
|
||||||
|
function functionText(flag?: number, indexText?: string) {
|
||||||
|
if (Number(flag) === 0) return '正常';
|
||||||
|
const t = String(indexText || '').trim();
|
||||||
|
return t ? `异常 · ${t}` : '异常';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadDetail() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
profile.value = await getUserPatientProfileDetailApi(upId.value);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
message.error('获取用户信息失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadRegisterList() {
|
||||||
|
registerLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getUserPatientRegisterListApi(upId.value);
|
||||||
|
registerList.value = res?.items ?? [];
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
message.error('获取挂号记录失败');
|
||||||
|
} finally {
|
||||||
|
registerLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPrescriptionList() {
|
||||||
|
prescriptionLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getUserPatientPrescriptionListApi(upId.value);
|
||||||
|
prescriptionList.value = res?.items ?? [];
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
message.error('获取处方记录失败');
|
||||||
|
} finally {
|
||||||
|
prescriptionLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadOrderList() {
|
||||||
|
orderLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getUserPatientOrderListApi(upId.value);
|
||||||
|
orderList.value = res?.items ?? [];
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
message.error('获取订单记录失败');
|
||||||
|
} finally {
|
||||||
|
orderLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tab 切换时懒加载对应列表 */
|
||||||
|
function onTabChange(key: string | number) {
|
||||||
|
const k = String(key);
|
||||||
|
activeTab.value = k;
|
||||||
|
if (k === 'register' && registerList.value.length === 0) {
|
||||||
|
void loadRegisterList();
|
||||||
|
} else if (k === 'prescription' && prescriptionList.value.length === 0) {
|
||||||
|
void loadPrescriptionList();
|
||||||
|
} else if (k === 'order' && orderList.value.length === 0) {
|
||||||
|
void loadOrderList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openPrescription(id: number) {
|
||||||
|
PrescriptionDetailModalApi.setData({ values: id });
|
||||||
|
PrescriptionDetailModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openOrder(id: number) {
|
||||||
|
OrderDetailModalApi.setData({ id });
|
||||||
|
OrderDetailModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerColumns = [
|
||||||
|
{ title: '订单编号', dataIndex: 'order_no', key: 'order_no' },
|
||||||
|
{ title: '医生', dataIndex: 'doctor', key: 'doctor' },
|
||||||
|
{ title: '诊所', dataIndex: 'store', key: 'store' },
|
||||||
|
{
|
||||||
|
title: '费用',
|
||||||
|
dataIndex: 'price',
|
||||||
|
key: 'price',
|
||||||
|
customRender: ({ text }: { text: number }) =>
|
||||||
|
`¥${Number(text || 0).toFixed(2)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '支付',
|
||||||
|
dataIndex: 'is_pay',
|
||||||
|
key: 'is_pay',
|
||||||
|
customRender: ({ text }: { text: number }) =>
|
||||||
|
h(Tag, { color: text === 1 ? 'green' : 'red' }, () =>
|
||||||
|
text === 1 ? '已支付' : '未支付',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ title: '状态', dataIndex: 'status_text', key: 'status_text' },
|
||||||
|
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const prescriptionColumns = [
|
||||||
|
{
|
||||||
|
title: '处方编号',
|
||||||
|
dataIndex: 'prescription_no',
|
||||||
|
key: 'prescription_no',
|
||||||
|
customRender: ({ text, record }: { text: string; record: any }) =>
|
||||||
|
h(
|
||||||
|
Button,
|
||||||
|
{ type: 'link', onClick: () => openPrescription(record.id) },
|
||||||
|
() => text,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '就诊类型',
|
||||||
|
dataIndex: 'is_online',
|
||||||
|
key: 'is_online',
|
||||||
|
customRender: ({ text }: { text: number }) =>
|
||||||
|
h(VisitTypeTag, { isOnline: text }),
|
||||||
|
},
|
||||||
|
{ title: '医生', dataIndex: 'doctor', key: 'doctor' },
|
||||||
|
{ title: '诊所', dataIndex: 'store', key: 'store' },
|
||||||
|
{ title: '诊断', dataIndex: 'clinical_diagnose', key: 'clinical_diagnose' },
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
customRender: ({ text }: { text: number }) => {
|
||||||
|
const map: Record<number, string> = {
|
||||||
|
0: '待审核',
|
||||||
|
1: '已通过',
|
||||||
|
2: '未通过',
|
||||||
|
3: '无需审核',
|
||||||
|
};
|
||||||
|
return map[text] || '未知';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const orderColumns = [
|
||||||
|
{
|
||||||
|
title: '订单号',
|
||||||
|
dataIndex: 'order_no',
|
||||||
|
key: 'order_no',
|
||||||
|
customRender: ({ text, record }: { text: string; record: any }) =>
|
||||||
|
h(Button, { type: 'link', onClick: () => openOrder(record.id) }, () => text),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '就诊类型',
|
||||||
|
dataIndex: 'is_online',
|
||||||
|
key: 'is_online',
|
||||||
|
customRender: ({ text }: { text: number }) =>
|
||||||
|
h(VisitTypeTag, { isOnline: text }),
|
||||||
|
},
|
||||||
|
{ title: '医生', dataIndex: 'doctor', key: 'doctor' },
|
||||||
|
{ title: '诊所', dataIndex: 'store', key: 'store' },
|
||||||
|
{
|
||||||
|
title: '实付',
|
||||||
|
dataIndex: 'total_pay_price',
|
||||||
|
key: 'total_pay_price',
|
||||||
|
customRender: ({ text }: { text: number }) =>
|
||||||
|
`¥${Number(text || 0).toFixed(2)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '支付',
|
||||||
|
dataIndex: 'is_pay',
|
||||||
|
key: 'is_pay',
|
||||||
|
customRender: ({ text }: { text: number }) =>
|
||||||
|
h(Tag, { color: text === 1 ? 'green' : 'red' }, () =>
|
||||||
|
text === 1 ? '已支付' : '未支付',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at' },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal>
|
||||||
|
<PrescriptionDetailModal />
|
||||||
|
<OrderDetailModal />
|
||||||
|
<Spin :spinning="loading">
|
||||||
|
<template v-if="profile">
|
||||||
|
<div class="mb-4 flex items-center gap-3 rounded-lg bg-gray-50 p-3 dark:bg-slate-800">
|
||||||
|
<Avatar :size="48" :src="avatarSrc(profile.user?.avatarurl)" />
|
||||||
|
<div class="min-w-0">
|
||||||
|
<div class="text-sm font-medium">
|
||||||
|
{{ profile.user?.nickname || '—' }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500">
|
||||||
|
用户 ID:{{ profile.user?.id ?? '—' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Tabs :active-key="activeTab" @change="onTabChange">
|
||||||
|
<Tabs.TabPane key="info" tab="就诊人信息">
|
||||||
|
<Descriptions :column="2" bordered size="small">
|
||||||
|
<Descriptions.Item label="姓名">
|
||||||
|
{{ profile.patient?.name || '—' }}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="性别">
|
||||||
|
{{
|
||||||
|
profile.patient?.sex === 1
|
||||||
|
? '男'
|
||||||
|
: profile.patient?.sex === 2
|
||||||
|
? '女'
|
||||||
|
: '—'
|
||||||
|
}}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="年龄">
|
||||||
|
{{ profile.patient?.age ? `${profile.patient.age}岁` : '—' }}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="手机号">
|
||||||
|
<SensitiveText
|
||||||
|
v-if="profile.patient"
|
||||||
|
:record="profile.patient"
|
||||||
|
field="mobile"
|
||||||
|
/>
|
||||||
|
<span v-else>—</span>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item
|
||||||
|
v-if="profile.patient?.id_card"
|
||||||
|
label="身份证"
|
||||||
|
:span="2"
|
||||||
|
>
|
||||||
|
<SensitiveText
|
||||||
|
:record="profile.patient"
|
||||||
|
field="id_card"
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
<!-- 健康问诊 / 功能异常(与小程序资料 Tab 一致) -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<div class="mb-2 text-sm font-medium text-gray-700">健康信息</div>
|
||||||
|
<Descriptions
|
||||||
|
v-if="profile.health_inquiry"
|
||||||
|
:column="2"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<Descriptions.Item label="既往史">
|
||||||
|
{{
|
||||||
|
historyText(
|
||||||
|
profile.health_inquiry.person_status,
|
||||||
|
profile.health_inquiry.person_history,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="过敏史">
|
||||||
|
<span
|
||||||
|
:class="
|
||||||
|
Number(profile.health_inquiry.allergic_status) !== 0
|
||||||
|
? 'text-red-600 font-medium'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
historyText(
|
||||||
|
profile.health_inquiry.allergic_status,
|
||||||
|
profile.health_inquiry.allergic_history,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="家族遗传史">
|
||||||
|
{{
|
||||||
|
historyText(
|
||||||
|
profile.health_inquiry.family_status,
|
||||||
|
profile.health_inquiry.family_history,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="肝功能异常">
|
||||||
|
<span
|
||||||
|
:class="
|
||||||
|
Number(profile.health_inquiry.liver_function) !== 0
|
||||||
|
? 'text-red-600 font-medium'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
functionText(
|
||||||
|
profile.health_inquiry.liver_function,
|
||||||
|
profile.health_inquiry.liver_index,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="肾功能异常" :span="2">
|
||||||
|
<span
|
||||||
|
:class="
|
||||||
|
Number(profile.health_inquiry.renal_function) !== 0
|
||||||
|
? 'text-red-600 font-medium'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
functionText(
|
||||||
|
profile.health_inquiry.renal_function,
|
||||||
|
profile.health_inquiry.renal_index,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
<Empty v-else description="暂无健康问诊记录" />
|
||||||
|
</div>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane key="register" tab="挂号记录">
|
||||||
|
<Spin :spinning="registerLoading">
|
||||||
|
<Table
|
||||||
|
v-if="registerList.length"
|
||||||
|
:columns="registerColumns"
|
||||||
|
:data-source="registerList"
|
||||||
|
:pagination="false"
|
||||||
|
row-key="id"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<Empty v-else description="暂无挂号记录" />
|
||||||
|
</Spin>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane key="prescription" tab="处方记录">
|
||||||
|
<Spin :spinning="prescriptionLoading">
|
||||||
|
<Table
|
||||||
|
v-if="prescriptionList.length"
|
||||||
|
:columns="prescriptionColumns"
|
||||||
|
:data-source="prescriptionList"
|
||||||
|
:pagination="false"
|
||||||
|
row-key="id"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<Empty v-else description="暂无处方记录" />
|
||||||
|
</Spin>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane key="order" tab="订单记录">
|
||||||
|
<Spin :spinning="orderLoading">
|
||||||
|
<Table
|
||||||
|
v-if="orderList.length"
|
||||||
|
:columns="orderColumns"
|
||||||
|
:data-source="orderList"
|
||||||
|
:pagination="false"
|
||||||
|
row-key="id"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<Empty v-else description="暂无订单记录" />
|
||||||
|
</Spin>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</template>
|
||||||
|
<Empty v-else-if="!loading" description="暂无数据" />
|
||||||
|
</Spin>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
@@ -300,13 +300,15 @@ const orderColumns = [
|
|||||||
/>
|
/>
|
||||||
<span v-else>—</span>
|
<span v-else>—</span>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="身份证" :span="2">
|
<Descriptions.Item
|
||||||
|
v-if="profile.patient?.id_card"
|
||||||
|
label="身份证"
|
||||||
|
:span="2"
|
||||||
|
>
|
||||||
<SensitiveText
|
<SensitiveText
|
||||||
v-if="profile.patient"
|
|
||||||
:record="profile.patient"
|
:record="profile.patient"
|
||||||
field="id_card"
|
field="id_card"
|
||||||
/>
|
/>
|
||||||
<span v-else>—</span>
|
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 某微信用户下的就诊人列表 Modal
|
||||||
|
* 点行再打开就诊人详情 Modal(由父级或本组件内嵌 DetailModal 处理)
|
||||||
|
*/
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Avatar, Button, Empty, Spin, Table, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
|
||||||
|
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
|
||||||
|
|
||||||
|
import { getPatientListByUserApi } from './api';
|
||||||
|
import WxUserPatientDetailModal from './WxUserPatientDetailModal.vue';
|
||||||
|
|
||||||
|
defineOptions({ name: 'WxUserPatientsModal' });
|
||||||
|
|
||||||
|
const defaultAvatar = '/img/user-default-avatar.png';
|
||||||
|
|
||||||
|
const userId = ref(0);
|
||||||
|
const userNickname = ref('');
|
||||||
|
const userAvatar = ref('');
|
||||||
|
const loading = ref(false);
|
||||||
|
const list = ref<any[]>([]);
|
||||||
|
|
||||||
|
const [DetailModal, DetailModalApi] = useVbenModal({
|
||||||
|
connectedComponent: WxUserPatientDetailModal,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
footer: false,
|
||||||
|
class: 'w-[640px]',
|
||||||
|
onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
list.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = modalApi.getData<{
|
||||||
|
userId?: number;
|
||||||
|
nickname?: string;
|
||||||
|
avatarurl?: string;
|
||||||
|
}>();
|
||||||
|
userId.value = Number(data?.userId || 0);
|
||||||
|
userNickname.value = String(data?.nickname || '');
|
||||||
|
userAvatar.value = String(data?.avatarurl || '');
|
||||||
|
modalApi.setState({
|
||||||
|
title: userNickname.value
|
||||||
|
? `就诊人列表 · ${userNickname.value}`
|
||||||
|
: '就诊人列表',
|
||||||
|
});
|
||||||
|
if (userId.value > 0) {
|
||||||
|
void loadList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function avatarSrc(raw?: string) {
|
||||||
|
const s = String(raw ?? '').trim();
|
||||||
|
if (!s) return defaultAvatar;
|
||||||
|
return resolveAvatarUrl(s) || defaultAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadList() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getPatientListByUserApi(userId.value);
|
||||||
|
list.value = res?.items ?? [];
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
message.error('加载就诊人失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开就诊人详情 Modal(与微信用户列表分离) */
|
||||||
|
function openPatientDetail(row: Record<string, any>) {
|
||||||
|
const upId = Number(row.up_id || 0);
|
||||||
|
if (!upId) {
|
||||||
|
message.warning('缺少就诊人信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DetailModalApi.setData({
|
||||||
|
upId,
|
||||||
|
patientName: row.name || '',
|
||||||
|
});
|
||||||
|
DetailModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: '姓名', key: 'name' },
|
||||||
|
{ title: '性别', key: 'sex', width: 80 },
|
||||||
|
{ title: '手机', key: 'mobile' },
|
||||||
|
{ title: '操作', key: 'action', width: 100 },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal>
|
||||||
|
<DetailModal />
|
||||||
|
<div class="mb-3 flex items-center gap-2">
|
||||||
|
<Avatar :size="36" :src="avatarSrc(userAvatar)" />
|
||||||
|
<div class="min-w-0 text-sm">
|
||||||
|
<div class="font-medium">{{ userNickname || '—' }}</div>
|
||||||
|
<div class="text-xs text-gray-500">用户 ID:{{ userId || '—' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Spin :spinning="loading">
|
||||||
|
<Table
|
||||||
|
v-if="list.length"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="list"
|
||||||
|
:pagination="false"
|
||||||
|
row-key="up_id"
|
||||||
|
size="small"
|
||||||
|
:custom-row="
|
||||||
|
(record) => ({
|
||||||
|
onClick: () => openPatientDetail(record),
|
||||||
|
style: { cursor: 'pointer' },
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'name'">
|
||||||
|
{{ record.name || '—' }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'sex'">
|
||||||
|
<template v-if="record.sex === 1">男</template>
|
||||||
|
<template v-else-if="record.sex === 2">女</template>
|
||||||
|
<template v-else>—</template>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'mobile'">
|
||||||
|
<SensitiveText :record="record" field="mobile" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'action'">
|
||||||
|
<Button type="link" @click.stop="openPatientDetail(record)">
|
||||||
|
详情
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
<Empty v-else-if="!loading" description="暂无就诊人" />
|
||||||
|
</Spin>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
@@ -5,6 +5,31 @@ import { requestClient } from '#/api/request';
|
|||||||
|
|
||||||
const prefix = 'user-patient-profile/';
|
const prefix = 'user-patient-profile/';
|
||||||
|
|
||||||
|
/** 用户管理列表(微信用户 + 就诊人,旧接口) */
|
||||||
|
export async function getUserPatientProfileListApi(params?: {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
keyword?: string;
|
||||||
|
}) {
|
||||||
|
return requestClient.get<any>(`${prefix}list`, { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 会员管理:微信用户分页 */
|
||||||
|
export async function getWxUserListApi(params?: {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
keyword?: string;
|
||||||
|
}) {
|
||||||
|
return requestClient.get<any>(`${prefix}user-list`, { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 某微信用户下的就诊人列表 */
|
||||||
|
export async function getPatientListByUserApi(userId: number) {
|
||||||
|
return requestClient.get<any>(`${prefix}patient-list-by-user`, {
|
||||||
|
params: { user_id: userId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 小程序用户 + 就诊人基础信息 */
|
/** 小程序用户 + 就诊人基础信息 */
|
||||||
export async function getUserPatientProfileDetailApi(upId: number) {
|
export async function getUserPatientProfileDetailApi(upId: number) {
|
||||||
return requestClient.get<any>(`${prefix}detail`, {
|
return requestClient.get<any>(`${prefix}detail`, {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
export { default as WxUserPatientCell } from './WxUserPatientCell.vue';
|
export { default as WxUserPatientCell } from './WxUserPatientCell.vue';
|
||||||
export { default as WxUserPatientDrawer } from './WxUserPatientDrawer.vue';
|
export { default as WxUserPatientDrawer } from './WxUserPatientDrawer.vue';
|
||||||
|
export { default as WxUserPatientDetailModal } from './WxUserPatientDetailModal.vue';
|
||||||
|
export { default as WxUserPatientsModal } from './WxUserPatientsModal.vue';
|
||||||
export { default as VisitTypeTag } from './VisitTypeTag.vue';
|
export { default as VisitTypeTag } from './VisitTypeTag.vue';
|
||||||
export { default as PrescriptionExpireTime } from './PrescriptionExpireTime.vue';
|
export { default as PrescriptionExpireTime } from './PrescriptionExpireTime.vue';
|
||||||
export * from './api';
|
export * from './api';
|
||||||
|
|||||||
@@ -1,81 +1,18 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
/**
|
||||||
|
* 业务处方页处方溯源弹窗
|
||||||
|
*/
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
import { Timeline, TimelineItem } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import {
|
import PrescriptionSourceContent from '#/components/prescription-source/PrescriptionSourceContent.vue';
|
||||||
FileTextOutlined,
|
|
||||||
MedicineBoxOutlined,
|
|
||||||
ShopOutlined,
|
|
||||||
UserOutlined,
|
|
||||||
} from '@ant-design/icons-vue';
|
|
||||||
|
|
||||||
import { getPrescriptionSourceApi } from '../api';
|
import { getPrescriptionSourceApi } from '../api';
|
||||||
|
|
||||||
defineOptions({ name: 'PrescriptionSource' });
|
defineOptions({ name: 'PrescriptionSource' });
|
||||||
|
|
||||||
// 处方溯源信息
|
const data = ref<Record<string, any> | null>(null);
|
||||||
const data = ref();
|
|
||||||
|
|
||||||
// 格式化时间戳
|
|
||||||
const formatTime = (timestamp) => {
|
|
||||||
if (!timestamp) return '--';
|
|
||||||
return new Date(timestamp * 1000).toLocaleString('zh-CN', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
second: '2-digit',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处方类型
|
|
||||||
const prescriptionTypeMap = {
|
|
||||||
1: '西药处方',
|
|
||||||
2: '中成药处方',
|
|
||||||
3: '中药处方',
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处方状态
|
|
||||||
const statusMap = {
|
|
||||||
0: '待审核',
|
|
||||||
1: '已审核',
|
|
||||||
2: '已驳回',
|
|
||||||
3: '已过期',
|
|
||||||
};
|
|
||||||
|
|
||||||
// 计算属性:处方类型文本
|
|
||||||
const prescriptionTypeText = computed(() => {
|
|
||||||
return data.value
|
|
||||||
? prescriptionTypeMap[data.value.prescription_type] || '未知'
|
|
||||||
: '--';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 计算属性:处方状态文本
|
|
||||||
const statusText = computed(() => {
|
|
||||||
return data.value ? statusMap[data.value.status] || '未知' : '--';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 计算属性:状态颜色
|
|
||||||
const statusColor = computed(() => {
|
|
||||||
if (!data.value) return 'gray';
|
|
||||||
|
|
||||||
const statusColors = {
|
|
||||||
0: 'orange',
|
|
||||||
1: 'green',
|
|
||||||
2: 'red',
|
|
||||||
3: 'gray',
|
|
||||||
};
|
|
||||||
|
|
||||||
return statusColors[data.value.status] || 'gray';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 计算属性:过期时间
|
|
||||||
const expireTime = computed(() => {
|
|
||||||
return data.value ? formatTime(data.value.auto_expire_time) : '--';
|
|
||||||
});
|
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
fullscreenButton: false,
|
fullscreenButton: false,
|
||||||
@@ -87,13 +24,13 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
modalApi.close();
|
modalApi.close();
|
||||||
},
|
},
|
||||||
onOpenChange(isOpen: boolean) {
|
onOpenChange(isOpen: boolean) {
|
||||||
const { id } = modalApi.getData<Record<string, any>>();
|
const payload = modalApi.getData<Record<string, any>>();
|
||||||
if (isOpen && id) {
|
if (isOpen && payload?.id) {
|
||||||
getPrescriptionSourceApi({ id }).then((res) => {
|
getPrescriptionSourceApi({ id: payload.id }).then((res) => {
|
||||||
data.value = res;
|
data.value = res;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
data.value = null; // Reset data when modal is closed or id is missing
|
data.value = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -101,212 +38,6 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal class="w-[80%]" title="处方溯源">
|
<Modal class="w-[80%]" title="处方溯源">
|
||||||
<div v-if="data" class="prescription-source-container">
|
<PrescriptionSourceContent :data="data" />
|
||||||
<!-- 药店信息 -->
|
|
||||||
<div
|
|
||||||
class="mb-6 transform rounded-lg p-6 shadow-md transition-all duration-300 hover:shadow-lg"
|
|
||||||
>
|
|
||||||
<div class="mb-4 flex items-center">
|
|
||||||
<ShopOutlined class="mr-2 text-xl text-purple-500" />
|
|
||||||
<h2 class="text-xl font-bold">药店信息</h2>
|
|
||||||
</div>
|
|
||||||
<div v-if="data.store" class="grid grid-cols-1 gap-4">
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">药店名称:</span>
|
|
||||||
<span class="value">{{ data.store.name }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="italic text-gray-500">暂无药店信息</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 时间线 -->
|
|
||||||
<div
|
|
||||||
class="mb-6 transform rounded-lg p-6 shadow-md transition-all duration-300 hover:shadow-lg"
|
|
||||||
>
|
|
||||||
<div class="mb-4 flex items-center">
|
|
||||||
<FileTextOutlined class="mr-2 text-xl text-blue-500" />
|
|
||||||
<h2 class="text-xl font-bold">时间线</h2>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 mt-5">
|
|
||||||
<Timeline>
|
|
||||||
<TimelineItem v-if="data.pharmacist_view_time">
|
|
||||||
{{ data.pharmacist_view_time }}
|
|
||||||
<template v-if="data.pharmacist_info">
|
|
||||||
【{{ data.pharmacist_info.name }}】 审核
|
|
||||||
</template>
|
|
||||||
<template v-else> -- </template>
|
|
||||||
</TimelineItem>
|
|
||||||
<TimelineItem v-else>
|
|
||||||
{{ statusText }}
|
|
||||||
{{ data.cancel_remark }}
|
|
||||||
</TimelineItem>
|
|
||||||
<TimelineItem>
|
|
||||||
{{ data.created_at }}
|
|
||||||
<template v-if="data.doctor_info">
|
|
||||||
【{{ data.doctor_info.name }}】 开方,诊断:{{
|
|
||||||
data.clinical_diagnose
|
|
||||||
}}
|
|
||||||
</template>
|
|
||||||
<template v-else> -- </template>
|
|
||||||
</TimelineItem>
|
|
||||||
<TimelineItem>
|
|
||||||
{{ data.register.created_at }}
|
|
||||||
<template v-if="data.doctor_info">
|
|
||||||
【{{ data.user_patient.name }}】 挂号
|
|
||||||
</template>
|
|
||||||
<template v-else> -- </template>
|
|
||||||
</TimelineItem>
|
|
||||||
</Timeline>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 处方基本信息 -->
|
|
||||||
<div
|
|
||||||
class="mb-6 transform rounded-lg p-6 shadow-md transition-all duration-300 hover:shadow-lg"
|
|
||||||
>
|
|
||||||
<div class="mb-4 flex items-center">
|
|
||||||
<FileTextOutlined class="mr-2 text-xl text-blue-500" />
|
|
||||||
<h2 class="text-xl font-bold">处方基本信息</h2>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">处方编号:</span>
|
|
||||||
<span class="value">{{ data.prescription_no }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">处方类型:</span>
|
|
||||||
<span class="value">{{ prescriptionTypeText }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">处方状态:</span>
|
|
||||||
<span :class="`text-${statusColor}-500`" class="value">{{
|
|
||||||
statusText
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">总金额:</span>
|
|
||||||
<span class="value font-bold text-red-500">¥{{ data.total_pay_price }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">过期时间:</span>
|
|
||||||
<span class="value">{{ expireTime }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 医生信息 -->
|
|
||||||
<div
|
|
||||||
class="mb-6 transform rounded-lg p-6 shadow-md transition-all duration-300 hover:shadow-lg"
|
|
||||||
>
|
|
||||||
<div class="mb-4 flex items-center">
|
|
||||||
<MedicineBoxOutlined class="mr-2 text-xl text-green-500" />
|
|
||||||
<h2 class="text-xl font-bold">医生信息</h2>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="data.doctor_info"
|
|
||||||
class="grid grid-cols-1 gap-4 md:grid-cols-2"
|
|
||||||
>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">医生姓名:</span>
|
|
||||||
<span class="value">{{ data.doctor_info.name }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">所属科室:</span>
|
|
||||||
<span class="value">{{
|
|
||||||
data.doctor_info.depart?.name || '--'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="italic text-gray-500">暂无医生信息</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 患者信息 -->
|
|
||||||
<div
|
|
||||||
class="transform rounded-lg p-6 shadow-md transition-all duration-300 hover:shadow-lg"
|
|
||||||
>
|
|
||||||
<div class="mb-4 flex items-center">
|
|
||||||
<UserOutlined class="mr-2 text-xl text-amber-500" />
|
|
||||||
<h2 class="text-xl font-bold">患者信息</h2>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="data.user_patient"
|
|
||||||
class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"
|
|
||||||
>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">患者姓名:</span>
|
|
||||||
<span class="value">{{ data.user_patient.name }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">年龄:</span>
|
|
||||||
<span class="value">{{ data.user_patient.age }}岁</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">性别:</span>
|
|
||||||
<span class="value">{{
|
|
||||||
data.user_patient.sex === 1 ? '男' : '女'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="italic text-gray-500">暂无患者信息</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 加载状态 -->
|
|
||||||
<div v-else class="flex h-64 items-center justify-center">
|
|
||||||
<div
|
|
||||||
class="h-12 w-12 animate-spin rounded-full border-b-2 border-t-2 border-blue-500"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.prescription-source-container {
|
|
||||||
@apply max-h-[70vh] overflow-auto p-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
@apply flex flex-col rounded-md p-3 transition-all duration-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
@apply mb-1 text-sm text-gray-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
@apply font-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 添加动感效果 */
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.prescription-source-container > div {
|
|
||||||
animation: fadeIn 0.5s ease-out forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prescription-source-container > div:nth-child(1) {
|
|
||||||
animation-delay: 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prescription-source-container > div:nth-child(2) {
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prescription-source-container > div:nth-child(3) {
|
|
||||||
animation-delay: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prescription-source-container > div:nth-child(4) {
|
|
||||||
animation-delay: 0.4s;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,33 +1,31 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VxeGridListeners } from '#/adapter/vxe-table';
|
import type { VxeGridListeners } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
import {
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
Page,
|
|
||||||
useVbenModal,
|
|
||||||
} from '@vben/common-ui';
|
|
||||||
|
|
||||||
import { Button, Tag } from 'ant-design-vue';
|
import { Button, Tabs, Tag } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { TableAction } from '#/components/table-action';
|
import { TableAction } from '#/components/table-action';
|
||||||
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
||||||
import PrescriptionSource from './components/source.vue';
|
|
||||||
|
|
||||||
|
import { getPrescriptionListApi } from './api';
|
||||||
|
import PrescriptionSource from './components/source.vue';
|
||||||
import { formOptions } from './config/search';
|
import { formOptions } from './config/search';
|
||||||
import { gridOptions } from './config/table';
|
import { gridOptions as baseGridOptions } from './config/table';
|
||||||
|
|
||||||
const hasTopTableDropDownActions = ref(false);
|
const hasTopTableDropDownActions = ref(false);
|
||||||
|
/** 审核状态 Tab:0待审核 / 1已通过 / 2已拒绝 */
|
||||||
|
const statusTab = ref('0');
|
||||||
|
|
||||||
const gridEvents: VxeGridListeners<any> = {
|
const gridEvents: VxeGridListeners<any> = {
|
||||||
checkboxChange() {
|
checkboxChange() {
|
||||||
// eslint-disable-next-line no-use-before-define
|
|
||||||
const records = gridApi.grid.getCheckboxRecords();
|
const records = gridApi.grid.getCheckboxRecords();
|
||||||
hasTopTableDropDownActions.value = records.length > 0;
|
hasTopTableDropDownActions.value = records.length > 0;
|
||||||
},
|
},
|
||||||
checkboxAll() {
|
checkboxAll() {
|
||||||
// eslint-disable-next-line no-use-before-define
|
|
||||||
const records = gridApi.grid.getCheckboxRecords();
|
const records = gridApi.grid.getCheckboxRecords();
|
||||||
hasTopTableDropDownActions.value = records.length > 0;
|
hasTopTableDropDownActions.value = records.length > 0;
|
||||||
},
|
},
|
||||||
@@ -35,10 +33,32 @@ const gridEvents: VxeGridListeners<any> = {
|
|||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions,
|
formOptions,
|
||||||
gridOptions,
|
gridOptions: {
|
||||||
|
...baseGridOptions,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getPrescriptionListApi({
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
status: Number(statusTab.value),
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
gridEvents,
|
gridEvents,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(statusTab, () => {
|
||||||
|
gridApi.query();
|
||||||
|
});
|
||||||
|
|
||||||
|
function onStatusTabChange(key: string | number) {
|
||||||
|
statusTab.value = String(key);
|
||||||
|
}
|
||||||
|
|
||||||
const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
||||||
connectedComponent: PrescriptionDetail,
|
connectedComponent: PrescriptionDetail,
|
||||||
});
|
});
|
||||||
@@ -46,36 +66,35 @@ const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
|||||||
const [PrescriptionSourceModal, PrescriptionSourceModalApi] = useVbenModal({
|
const [PrescriptionSourceModal, PrescriptionSourceModalApi] = useVbenModal({
|
||||||
connectedComponent: PrescriptionSource,
|
connectedComponent: PrescriptionSource,
|
||||||
});
|
});
|
||||||
const openPrescriptionDetail = (values) => {
|
|
||||||
// 打开西药处方模态框逻辑
|
const openPrescriptionDetail = (values: number) => {
|
||||||
PrescriptionDetailModalApi.setData({
|
PrescriptionDetailModalApi.setData({ values });
|
||||||
values,
|
|
||||||
});
|
|
||||||
PrescriptionDetailModalApi.open();
|
PrescriptionDetailModalApi.open();
|
||||||
};
|
};
|
||||||
const openPrescriptionSourceModal = (id) => {
|
|
||||||
// 打开西药处方模态框逻辑
|
const openPrescriptionSourceModal = (id: number) => {
|
||||||
PrescriptionSourceModalApi.setData({
|
PrescriptionSourceModalApi.setData({ id });
|
||||||
id,
|
|
||||||
});
|
|
||||||
PrescriptionSourceModalApi.open();
|
PrescriptionSourceModalApi.open();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height title="订单管理">
|
<Page auto-content-height title="处方管理">
|
||||||
<PrescriptionDetailModal />
|
<PrescriptionDetailModal />
|
||||||
<PrescriptionSourceModal />
|
<PrescriptionSourceModal />
|
||||||
|
<div class="mb-3">
|
||||||
|
<Tabs :active-key="statusTab" @change="onStatusTabChange">
|
||||||
|
<Tabs.TabPane key="0" tab="待审核" />
|
||||||
|
<Tabs.TabPane key="1" tab="已通过" />
|
||||||
|
<Tabs.TabPane key="2" tab="已拒绝" />
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
<Grid>
|
<Grid>
|
||||||
<template #toolbar-buttons>
|
<template #toolbar-buttons>
|
||||||
<TableAction
|
<TableAction :actions="[]" :drop-down-actions="[]">
|
||||||
:actions="[]"
|
|
||||||
:drop-down-actions="[]"
|
|
||||||
>
|
|
||||||
<template #more>
|
<template #more>
|
||||||
<Button style="margin-left: 16px">
|
<Button style="margin-left: 16px">
|
||||||
批量操作
|
批量操作
|
||||||
<Icon icon="ant-design:down-outlined" />
|
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
</TableAction>
|
</TableAction>
|
||||||
@@ -104,11 +123,12 @@ const openPrescriptionSourceModal = (id) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<Tag v-if="row.status === 0" color="red">待审核</Tag>
|
<Tag v-if="row.status === 0" color="red">待审核</Tag>
|
||||||
<Tag v-else-if="row.status === 1" color="green">通过审核</Tag>
|
<Tag v-else-if="row.status === 1" color="green">已通过</Tag>
|
||||||
<Tag v-else-if="row.status === 2" color="red">拒绝审核:{{ row.reject_reason }}</Tag>
|
<Tag v-else-if="row.status === 2" color="red"
|
||||||
|
>已拒绝:{{ row.reject_reason }}</Tag
|
||||||
|
>
|
||||||
<Tag v-else-if="row.status === 3" color="purple">无需审核</Tag>
|
<Tag v-else-if="row.status === 3" color="purple">无需审核</Tag>
|
||||||
<Tag v-else-if="row.status === 4" color="green">无需审核</Tag>
|
<Tag v-else-if="row.status === 4" color="green">无需审核</Tag>
|
||||||
<!-- <p>{{ row.created_at }}</p>-->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #is-online="{ row }">
|
<template #is-online="{ row }">
|
||||||
@@ -124,19 +144,16 @@ const openPrescriptionSourceModal = (id) => {
|
|||||||
label: '查看处方',
|
label: '查看处方',
|
||||||
type: 'link',
|
type: 'link',
|
||||||
icon: 'marketeq:eye',
|
icon: 'marketeq:eye',
|
||||||
// auth: ['超级订单', 'sys:user:save'],
|
|
||||||
onClick: openPrescriptionDetail.bind(null, row.id),
|
onClick: openPrescriptionDetail.bind(null, row.id),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '处方溯源',
|
label: '处方溯源',
|
||||||
type: 'link',
|
type: 'link',
|
||||||
icon: 'marketeq:eye',
|
icon: 'marketeq:eye',
|
||||||
// auth: ['超级订单', 'sys:user:save'],
|
|
||||||
onClick: openPrescriptionSourceModal.bind(null, row.id),
|
onClick: openPrescriptionSourceModal.bind(null, row.id),
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
:drop-down-actions="[
|
:drop-down-actions="[]"
|
||||||
]"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -147,28 +164,4 @@ const openPrescriptionSourceModal = (id) => {
|
|||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-list-item {
|
|
||||||
background-color: rgba(64, 158, 255, 0.04);
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(64, 158, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
background-color: #409eff;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 8px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 订单列表:下单用户 / 医生 / 就诊人信息单元
|
||||||
|
* 下单用户 → 该用户就诊人列表 Modal;就诊人 → 详情 Modal
|
||||||
|
*/
|
||||||
import { Avatar, Button } from 'ant-design-vue';
|
import { Avatar, Button } from 'ant-design-vue';
|
||||||
|
|
||||||
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
|
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
row: Record<string, any>;
|
row: Record<string, any>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
openDoctor: [row: Record<string, any>];
|
openDoctor: [row: Record<string, any>];
|
||||||
|
/** 点下单微信用户 → 打开就诊人列表 */
|
||||||
|
openUserPatients: [
|
||||||
|
payload: { userId: number; nickname: string; avatarurl: string },
|
||||||
|
];
|
||||||
|
/** 点就诊人 → 打开详情 */
|
||||||
|
openPatient: [payload: { upId: number; patientName: string }];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
/** 默认头像路径 */
|
/** 默认头像路径 */
|
||||||
@@ -36,26 +46,74 @@ function getDoctorAvatarSrc(row: Record<string, any>) {
|
|||||||
* 格式化就诊人年龄副文本
|
* 格式化就诊人年龄副文本
|
||||||
*/
|
*/
|
||||||
function formatPatientAge(row: Record<string, any>) {
|
function formatPatientAge(row: Record<string, any>) {
|
||||||
const age = row.patient_age;
|
const age = row.patient_age ?? row.user_patient?.age ?? row.userPatient?.age;
|
||||||
if (age == null || age === '') return '';
|
if (age == null || age === '') return '';
|
||||||
return `${age}岁`;
|
return `${age}岁`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 解析就诊人 ID */
|
||||||
|
function resolveUpId(row: Record<string, any>) {
|
||||||
|
return Number(
|
||||||
|
row.up_id ||
|
||||||
|
row.user_patient?.id ||
|
||||||
|
row.userPatient?.id ||
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解析微信用户 ID */
|
||||||
|
function resolveUserId(row: Record<string, any>) {
|
||||||
|
return Number(row.user?.id || row.user_id || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function patientName(row: Record<string, any>) {
|
||||||
|
return (
|
||||||
|
row.user_patient?.name ||
|
||||||
|
row.userPatient?.name ||
|
||||||
|
row.patient ||
|
||||||
|
'—'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开该微信用户下的就诊人列表 */
|
||||||
|
function handleOpenUser() {
|
||||||
|
const userId = resolveUserId(props.row);
|
||||||
|
if (!userId) return;
|
||||||
|
emit('openUserPatients', {
|
||||||
|
userId,
|
||||||
|
nickname: String(props.row.user?.nickname || ''),
|
||||||
|
avatarurl: String(props.row.user?.avatarurl || ''),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开就诊人详情 Modal */
|
||||||
|
function handleOpenPatient() {
|
||||||
|
const upId = resolveUpId(props.row);
|
||||||
|
if (!upId) return;
|
||||||
|
emit('openPatient', {
|
||||||
|
upId,
|
||||||
|
patientName: String(patientName(props.row)),
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="order-user-info">
|
<div class="order-user-info">
|
||||||
<!-- 行1:下单微信用户 -->
|
|
||||||
<div class="order-user-info__row">
|
<div class="order-user-info__row">
|
||||||
<Avatar
|
<Avatar :size="24" :src="getUserAvatarSrc(row)" class="shrink-0" />
|
||||||
:size="24"
|
|
||||||
:src="getUserAvatarSrc(row)"
|
|
||||||
class="shrink-0"
|
|
||||||
/>
|
|
||||||
<div class="order-user-info__text min-w-0">
|
<div class="order-user-info__text min-w-0">
|
||||||
<span class="text-[11px] leading-tight text-gray-400 dark:text-slate-500">
|
<span class="text-[11px] leading-tight text-gray-400 dark:text-slate-500">
|
||||||
下单用户:
|
下单用户:
|
||||||
</span>
|
</span>
|
||||||
<span class="truncate text-xs text-gray-700 dark:text-slate-200">
|
<Button
|
||||||
|
v-if="resolveUserId(row)"
|
||||||
|
class="order-user-info__link-btn !h-auto !px-0 !py-0"
|
||||||
|
type="link"
|
||||||
|
@click.stop="handleOpenUser"
|
||||||
|
>
|
||||||
|
{{ row.user?.nickname || '—' }}
|
||||||
|
</Button>
|
||||||
|
<span v-else class="truncate text-xs text-gray-700 dark:text-slate-200">
|
||||||
{{ row.user?.nickname || '—' }}
|
{{ row.user?.nickname || '—' }}
|
||||||
</span>
|
</span>
|
||||||
<div class="text-[11px] text-gray-500 dark:text-slate-400">
|
<div class="text-[11px] text-gray-500 dark:text-slate-400">
|
||||||
@@ -63,14 +121,9 @@ function formatPatientAge(row: Record<string, any>) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 行2:开方医生(可点击查看档案) -->
|
|
||||||
<div class="order-user-info__row">
|
<div class="order-user-info__row">
|
||||||
<Avatar
|
<Avatar :size="24" :src="getDoctorAvatarSrc(row)" class="shrink-0" />
|
||||||
:size="24"
|
<div class="order-user-info__text min-w-0">
|
||||||
:src="getDoctorAvatarSrc(row)"
|
|
||||||
class="shrink-0"
|
|
||||||
/>
|
|
||||||
<div class="order-user-info__text min-w-0" >
|
|
||||||
<span class="text-[11px] leading-tight text-gray-400 dark:text-slate-500">
|
<span class="text-[11px] leading-tight text-gray-400 dark:text-slate-500">
|
||||||
医生:
|
医生:
|
||||||
</span>
|
</span>
|
||||||
@@ -82,28 +135,25 @@ function formatPatientAge(row: Record<string, any>) {
|
|||||||
>
|
>
|
||||||
{{ row.doctor.name }}
|
{{ row.doctor.name }}
|
||||||
</Button>
|
</Button>
|
||||||
<!-- <template v-if="row.doctor?.name">-->
|
|
||||||
<!-- <Button-->
|
|
||||||
<!-- v-if="row.doctor?.name"-->
|
|
||||||
<!-- class="order-user-info__doctor-btn !h-auto !px-0 !py-0 dark:!text-blue-400"-->
|
|
||||||
<!-- type="link"-->
|
|
||||||
<!-- @click="emit('openDoctor', row)"-->
|
|
||||||
<!-- >-->
|
|
||||||
<!-- {{ row.doctor.name }}-->
|
|
||||||
<!-- </Button>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<div v-else class="text-xs text-gray-700 dark:text-slate-200">—</div>
|
<div v-else class="text-xs text-gray-700 dark:text-slate-200">—</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 行3:就诊人(与上两行保持 Avatar + 文本区结构) -->
|
|
||||||
<div class="order-user-info__row">
|
<div class="order-user-info__row">
|
||||||
<Avatar :size="24" :src="defaultAvatar" class="shrink-0" />
|
<Avatar :size="24" :src="defaultAvatar" class="shrink-0" />
|
||||||
<div class="order-user-info__text min-w-0">
|
<div class="order-user-info__text min-w-0">
|
||||||
<span class="text-[11px] leading-tight text-gray-400 dark:text-slate-500">
|
<span class="text-[11px] leading-tight text-gray-400 dark:text-slate-500">
|
||||||
就诊人:
|
就诊人:
|
||||||
</span>
|
</span>
|
||||||
<span class="truncate text-xs text-gray-700 dark:text-slate-200">
|
<Button
|
||||||
{{ row.patient || '—' }}
|
v-if="resolveUpId(row)"
|
||||||
|
class="order-user-info__link-btn !h-auto !px-0 !py-0"
|
||||||
|
type="link"
|
||||||
|
@click.stop="handleOpenPatient"
|
||||||
|
>
|
||||||
|
{{ patientName(row) }}
|
||||||
|
</Button>
|
||||||
|
<span v-else class="truncate text-xs text-gray-700 dark:text-slate-200">
|
||||||
|
{{ patientName(row) }}
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
v-if="formatPatientAge(row)"
|
v-if="formatPatientAge(row)"
|
||||||
@@ -123,26 +173,23 @@ function formatPatientAge(row: Record<string, any>) {
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-user-info__row {
|
.order-user-info__row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-user-info__text {
|
.order-user-info__text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
.order-user-info__doctor-btn,
|
||||||
.order-user-info__doctor-btn {
|
.order-user-info__link-btn {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
//display: block;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ import type { QuickDiscountOption } from '#/utils/pricePercentAdjust';
|
|||||||
import { normalizeQuickOptions } from '#/utils/pricePercentAdjust';
|
import { normalizeQuickOptions } from '#/utils/pricePercentAdjust';
|
||||||
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
||||||
import DoctorCardModal from '#/views/doctor/doctor/components/DoctorCardModal.vue';
|
import DoctorCardModal from '#/views/doctor/doctor/components/DoctorCardModal.vue';
|
||||||
|
import {
|
||||||
|
WxUserPatientDetailModal,
|
||||||
|
WxUserPatientsModal,
|
||||||
|
} from '#/components/wx-user-patient';
|
||||||
|
|
||||||
import DetailModal from './components/detail.vue';
|
import DetailModal from './components/detail.vue';
|
||||||
import OrderUserInfoCell from './components/cells/OrderUserInfoCell.vue';
|
import OrderUserInfoCell from './components/cells/OrderUserInfoCell.vue';
|
||||||
@@ -288,6 +292,14 @@ const [DoctorCardModals, DoctorCardModalApi] = useVbenModal({
|
|||||||
connectedComponent: DoctorCardModal,
|
connectedComponent: DoctorCardModal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [PatientsModal, PatientsModalApi] = useVbenModal({
|
||||||
|
connectedComponent: WxUserPatientsModal,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [PatientDetailModal, PatientDetailModalApi] = useVbenModal({
|
||||||
|
connectedComponent: WxUserPatientDetailModal,
|
||||||
|
});
|
||||||
|
|
||||||
/** 从订单列表以只读模式打开医生档案 */
|
/** 从订单列表以只读模式打开医生档案 */
|
||||||
function showOrderDoctorCard(row: Record<string, any>) {
|
function showOrderDoctorCard(row: Record<string, any>) {
|
||||||
const doctor = row.doctor;
|
const doctor = row.doctor;
|
||||||
@@ -303,6 +315,37 @@ function showOrderDoctorCard(row: Record<string, any>) {
|
|||||||
DoctorCardModalApi.open();
|
DoctorCardModalApi.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 点下单用户 → 该用户下就诊人列表 Modal */
|
||||||
|
function openOrderUserPatients(payload: {
|
||||||
|
userId: number;
|
||||||
|
nickname: string;
|
||||||
|
avatarurl: string;
|
||||||
|
}) {
|
||||||
|
if (!payload?.userId) {
|
||||||
|
message.warning('缺少用户信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PatientsModalApi.setData({
|
||||||
|
userId: payload.userId,
|
||||||
|
nickname: payload.nickname,
|
||||||
|
avatarurl: payload.avatarurl,
|
||||||
|
});
|
||||||
|
PatientsModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 点就诊人 → 详情 Modal */
|
||||||
|
function openOrderPatient(payload: { upId: number; patientName: string }) {
|
||||||
|
if (!payload?.upId) {
|
||||||
|
message.warning('缺少就诊人信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PatientDetailModalApi.setData({
|
||||||
|
upId: payload.upId,
|
||||||
|
patientName: payload.patientName,
|
||||||
|
});
|
||||||
|
PatientDetailModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
const [TraceDrawer, traceDrawerApi] = useVbenDrawer({
|
const [TraceDrawer, traceDrawerApi] = useVbenDrawer({
|
||||||
connectedComponent: OrderTraceDrawer,
|
connectedComponent: OrderTraceDrawer,
|
||||||
});
|
});
|
||||||
@@ -775,6 +818,8 @@ const openOrderAmountVerify = () => {
|
|||||||
</AntdModal>
|
</AntdModal>
|
||||||
<RefundModal />
|
<RefundModal />
|
||||||
<DoctorCardModals />
|
<DoctorCardModals />
|
||||||
|
<PatientsModal />
|
||||||
|
<PatientDetailModal />
|
||||||
<ExportModal />
|
<ExportModal />
|
||||||
<PrescriptionDetailModal />
|
<PrescriptionDetailModal />
|
||||||
<TraceDrawer />
|
<TraceDrawer />
|
||||||
@@ -822,7 +867,12 @@ const openOrderAmountVerify = () => {
|
|||||||
</TableAction>
|
</TableAction>
|
||||||
</template>
|
</template>
|
||||||
<template #order-user-info="{ row }">
|
<template #order-user-info="{ row }">
|
||||||
<OrderUserInfoCell :row="row" @open-doctor="showOrderDoctorCard" />
|
<OrderUserInfoCell
|
||||||
|
:row="row"
|
||||||
|
@open-doctor="showOrderDoctorCard"
|
||||||
|
@open-user-patients="openOrderUserPatients"
|
||||||
|
@open-patient="openOrderPatient"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #order-store="{ row }">
|
<template #order-store="{ row }">
|
||||||
<div class="leading-snug">
|
<div class="leading-snug">
|
||||||
|
|||||||
177
apps/web-antd/src/views/business/user-patient/index.vue
Normal file
177
apps/web-antd/src/views/business/user-patient/index.vue
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 会员管理:微信用户列表 → 就诊人列表 Modal → 就诊人详情 Modal
|
||||||
|
* 菜单 component 路径:/business/user-patient/index
|
||||||
|
*/
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Avatar, Button, Input, Space, Table, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
|
||||||
|
import { WxUserPatientsModal } from '#/components/wx-user-patient';
|
||||||
|
import { getWxUserListApi } from '#/components/wx-user-patient/api';
|
||||||
|
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
|
||||||
|
|
||||||
|
defineOptions({ name: 'UserPatientManage' });
|
||||||
|
|
||||||
|
const defaultAvatar = '/img/user-default-avatar.png';
|
||||||
|
const loading = ref(false);
|
||||||
|
const keyword = ref('');
|
||||||
|
const list = ref<any[]>([]);
|
||||||
|
const pagination = ref({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [PatientsModal, PatientsModalApi] = useVbenModal({
|
||||||
|
connectedComponent: WxUserPatientsModal,
|
||||||
|
});
|
||||||
|
|
||||||
|
function avatarSrc(raw?: string) {
|
||||||
|
const s = String(raw ?? '').trim();
|
||||||
|
if (!s) return defaultAvatar;
|
||||||
|
return resolveAvatarUrl(s) || defaultAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadList() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
// 无关键字时不带 keyword,避免被序列化成 "undefined"
|
||||||
|
const params: {
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
keyword?: string;
|
||||||
|
} = {
|
||||||
|
page: pagination.value.current,
|
||||||
|
pageSize: pagination.value.pageSize,
|
||||||
|
};
|
||||||
|
const kw = keyword.value.trim();
|
||||||
|
if (kw) {
|
||||||
|
params.keyword = kw;
|
||||||
|
}
|
||||||
|
const res = await getWxUserListApi(params);
|
||||||
|
list.value = res?.items ?? [];
|
||||||
|
pagination.value.total = Number(res?.total || 0);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
message.error('加载会员列表失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSearch() {
|
||||||
|
pagination.value.current = 1;
|
||||||
|
void loadList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPageChange(page: number, pageSize: number) {
|
||||||
|
pagination.value.current = page;
|
||||||
|
pagination.value.pageSize = pageSize;
|
||||||
|
void loadList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 点微信用户 → 打开该用户下就诊人列表 Modal */
|
||||||
|
function openPatients(row: Record<string, any>) {
|
||||||
|
const userId = Number(row.id || 0);
|
||||||
|
if (!userId) {
|
||||||
|
message.warning('缺少用户信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PatientsModalApi.setData({
|
||||||
|
userId,
|
||||||
|
nickname: row.nickname || '',
|
||||||
|
avatarurl: row.avatarurl || '',
|
||||||
|
});
|
||||||
|
PatientsModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: '微信用户', key: 'user', width: 180 },
|
||||||
|
{ title: '用户手机', key: 'mobile', width: 140 },
|
||||||
|
{ title: '就诊人', key: 'patients' },
|
||||||
|
{ title: '用户 ID', key: 'id', width: 90 },
|
||||||
|
{ title: '操作', key: 'action', width: 100 },
|
||||||
|
];
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
void loadList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height title="会员管理">
|
||||||
|
<PatientsModal />
|
||||||
|
<div class="mb-3 flex flex-wrap items-center gap-3">
|
||||||
|
<Input
|
||||||
|
v-model:value="keyword"
|
||||||
|
allow-clear
|
||||||
|
placeholder="昵称 / 手机 / 就诊人"
|
||||||
|
style="width: 280px"
|
||||||
|
@press-enter="onSearch"
|
||||||
|
/>
|
||||||
|
<Button type="primary" @click="onSearch">查询</Button>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
:loading="loading"
|
||||||
|
:data-source="list"
|
||||||
|
:columns="columns"
|
||||||
|
:pagination="{
|
||||||
|
current: pagination.current,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
total: pagination.total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
onChange: onPageChange,
|
||||||
|
}"
|
||||||
|
row-key="id"
|
||||||
|
size="middle"
|
||||||
|
:custom-row="
|
||||||
|
(record) => ({
|
||||||
|
onClick: () => openPatients(record),
|
||||||
|
style: { cursor: 'pointer' },
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'user'">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Avatar :size="24" :src="avatarSrc(record.avatarurl)" />
|
||||||
|
<div class="min-w-0 truncate text-sm">{{ record.nickname || '—' }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'mobile'">
|
||||||
|
<SensitiveText :record="record" field="mobile" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'patients'">
|
||||||
|
<div
|
||||||
|
v-if="record.patients?.length"
|
||||||
|
class="flex flex-col gap-1 text-sm leading-snug"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="p in record.patients"
|
||||||
|
:key="p.up_id"
|
||||||
|
class="flex flex-wrap items-center gap-x-2"
|
||||||
|
>
|
||||||
|
<span>{{ p.name || '—' }}</span>
|
||||||
|
<SensitiveText :record="p" field="mobile" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span v-else class="text-gray-400">—</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'id'">
|
||||||
|
{{ record.id ?? '—' }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'action'">
|
||||||
|
<Space>
|
||||||
|
<Button type="link" @click.stop="openPatients(record)">
|
||||||
|
就诊人
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
@@ -18,6 +18,8 @@ import { getPrescriptionListApi, passApi } from '../api';
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
formValues?: Record<string, any>;
|
formValues?: Record<string, any>;
|
||||||
|
/** 是否显示通过/拒绝(仅待审核 Tab) */
|
||||||
|
canAudit?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -34,6 +36,8 @@ const pageCount = ref(1);
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const loadingMore = ref(false);
|
const loadingMore = ref(false);
|
||||||
const passingId = ref<number | null>(null);
|
const passingId = ref<number | null>(null);
|
||||||
|
/** 请求序号:只应用最后一次 list 响应,避免切 Tab/搜索竞态盖错列表 */
|
||||||
|
let requestSeq = 0;
|
||||||
|
|
||||||
const scrollEl = ref<HTMLElement | null>(null);
|
const scrollEl = ref<HTMLElement | null>(null);
|
||||||
const sentinelEl = ref<HTMLElement | null>(null);
|
const sentinelEl = ref<HTMLElement | null>(null);
|
||||||
@@ -63,7 +67,12 @@ function isUrgent(row: Record<string, any>) {
|
|||||||
return remain > 0 && remain <= 2 * 3600;
|
return remain > 0 && remain <= 2 * 3600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉取一页数据;reset 时清空列表
|
||||||
|
* 用 requestSeq 丢弃过期响应,防止快速切 Tab 时旧 status 结果覆盖新 Tab
|
||||||
|
*/
|
||||||
async function fetchPage(targetPage: number, reset = false) {
|
async function fetchPage(targetPage: number, reset = false) {
|
||||||
|
const seq = ++requestSeq;
|
||||||
if (reset) {
|
if (reset) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -75,16 +84,21 @@ async function fetchPage(targetPage: number, reset = false) {
|
|||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...(props.formValues || {}),
|
...(props.formValues || {}),
|
||||||
});
|
});
|
||||||
|
// 已有更新的请求在飞,丢弃本次结果
|
||||||
|
if (seq !== requestSeq) return;
|
||||||
const list = res?.items ?? [];
|
const list = res?.items ?? [];
|
||||||
page.value = res?.page ?? targetPage;
|
page.value = res?.page ?? targetPage;
|
||||||
pageCount.value = res?.page_count ?? 1;
|
pageCount.value = res?.page_count ?? 1;
|
||||||
items.value = reset ? list : [...items.value, ...list];
|
items.value = reset ? list : [...items.value, ...list];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (seq !== requestSeq) return;
|
||||||
console.error(e);
|
console.error(e);
|
||||||
message.error('加载审方列表失败');
|
message.error('加载审方列表失败');
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
if (seq === requestSeq) {
|
||||||
loadingMore.value = false;
|
loading.value = false;
|
||||||
|
loadingMore.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +196,8 @@ onBeforeUnmount(() => {
|
|||||||
/>
|
/>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<Tag v-if="row.status === 0" color="red">待审核</Tag>
|
<Tag v-if="row.status === 0" color="red">待审核</Tag>
|
||||||
|
<Tag v-else-if="row.status === 1" color="green">已通过</Tag>
|
||||||
|
<Tag v-else-if="row.status === 2" color="red">已拒绝</Tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="audit-card__footer">
|
<div class="audit-card__footer">
|
||||||
@@ -192,21 +208,23 @@ onBeforeUnmount(() => {
|
|||||||
<Button size="small" @click="emit('viewSource', row.id)">
|
<Button size="small" @click="emit('viewSource', row.id)">
|
||||||
处方溯源
|
处方溯源
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<template v-if="canAudit !== false">
|
||||||
size="small"
|
<Button
|
||||||
type="primary"
|
size="small"
|
||||||
:loading="passingId === row.id"
|
type="primary"
|
||||||
@click="handlePass(row)"
|
:loading="passingId === row.id"
|
||||||
>
|
@click="handlePass(row)"
|
||||||
通过
|
>
|
||||||
</Button>
|
通过
|
||||||
<Button
|
</Button>
|
||||||
size="small"
|
<Button
|
||||||
danger
|
size="small"
|
||||||
@click="emit('reject', row.id)"
|
danger
|
||||||
>
|
@click="emit('reject', row.id)"
|
||||||
拒绝
|
>
|
||||||
</Button>
|
拒绝
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,81 +1,18 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
/**
|
||||||
|
* 审方页处方溯源弹窗
|
||||||
|
*/
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
import { Timeline, TimelineItem } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import {
|
import PrescriptionSourceContent from '#/components/prescription-source/PrescriptionSourceContent.vue';
|
||||||
FileTextOutlined,
|
|
||||||
MedicineBoxOutlined,
|
|
||||||
ShopOutlined,
|
|
||||||
UserOutlined,
|
|
||||||
} from '@ant-design/icons-vue';
|
|
||||||
|
|
||||||
import { getPrescriptionSourceApi } from '../api';
|
import { getPrescriptionSourceApi } from '../api';
|
||||||
|
|
||||||
defineOptions({ name: 'PrescriptionSource' });
|
defineOptions({ name: 'PrescriptionSource' });
|
||||||
|
|
||||||
// 处方溯源信息
|
const data = ref<Record<string, any> | null>(null);
|
||||||
const data = ref();
|
|
||||||
|
|
||||||
// 格式化时间戳
|
|
||||||
const formatTime = (timestamp) => {
|
|
||||||
if (!timestamp) return '--';
|
|
||||||
return new Date(timestamp * 1000).toLocaleString('zh-CN', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
second: '2-digit',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处方类型
|
|
||||||
const prescriptionTypeMap = {
|
|
||||||
1: '西药处方',
|
|
||||||
2: '中成药处方',
|
|
||||||
3: '中药处方',
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处方状态
|
|
||||||
const statusMap = {
|
|
||||||
0: '待审核',
|
|
||||||
1: '已审核',
|
|
||||||
2: '已驳回',
|
|
||||||
3: '已过期',
|
|
||||||
};
|
|
||||||
|
|
||||||
// 计算属性:处方类型文本
|
|
||||||
const prescriptionTypeText = computed(() => {
|
|
||||||
return data.value
|
|
||||||
? prescriptionTypeMap[data.value.prescription_type] || '未知'
|
|
||||||
: '--';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 计算属性:处方状态文本
|
|
||||||
const statusText = computed(() => {
|
|
||||||
return data.value ? statusMap[data.value.status] || '未知' : '--';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 计算属性:状态颜色
|
|
||||||
const statusColor = computed(() => {
|
|
||||||
if (!data.value) return 'gray';
|
|
||||||
|
|
||||||
const statusColors = {
|
|
||||||
0: 'orange',
|
|
||||||
1: 'green',
|
|
||||||
2: 'red',
|
|
||||||
3: 'gray',
|
|
||||||
};
|
|
||||||
|
|
||||||
return statusColors[data.value.status] || 'gray';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 计算属性:过期时间
|
|
||||||
const expireTime = computed(() => {
|
|
||||||
return data.value ? formatTime(data.value.auto_expire_time) : '--';
|
|
||||||
});
|
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
fullscreenButton: false,
|
fullscreenButton: false,
|
||||||
@@ -87,13 +24,13 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
modalApi.close();
|
modalApi.close();
|
||||||
},
|
},
|
||||||
onOpenChange(isOpen: boolean) {
|
onOpenChange(isOpen: boolean) {
|
||||||
const { id } = modalApi.getData<Record<string, any>>();
|
const payload = modalApi.getData<Record<string, any>>();
|
||||||
if (isOpen && id) {
|
if (isOpen && payload?.id) {
|
||||||
getPrescriptionSourceApi({ id }).then((res) => {
|
getPrescriptionSourceApi({ id: payload.id }).then((res) => {
|
||||||
data.value = res;
|
data.value = res;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
data.value = null; // Reset data when modal is closed or id is missing
|
data.value = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -101,212 +38,6 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal class="w-[80%]" title="处方溯源">
|
<Modal class="w-[80%]" title="处方溯源">
|
||||||
<div v-if="data" class="prescription-source-container">
|
<PrescriptionSourceContent :data="data" />
|
||||||
<!-- 药店信息 -->
|
|
||||||
<div
|
|
||||||
class="mb-6 transform rounded-lg p-6 shadow-md transition-all duration-300 hover:shadow-lg"
|
|
||||||
>
|
|
||||||
<div class="mb-4 flex items-center">
|
|
||||||
<ShopOutlined class="mr-2 text-xl text-purple-500" />
|
|
||||||
<h2 class="text-xl font-bold">药店信息</h2>
|
|
||||||
</div>
|
|
||||||
<div v-if="data.store" class="grid grid-cols-1 gap-4">
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">药店名称:</span>
|
|
||||||
<span class="value">{{ data.store.name }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="italic text-gray-500">暂无药店信息</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 时间线 -->
|
|
||||||
<div
|
|
||||||
class="mb-6 transform rounded-lg p-6 shadow-md transition-all duration-300 hover:shadow-lg"
|
|
||||||
>
|
|
||||||
<div class="mb-4 flex items-center">
|
|
||||||
<FileTextOutlined class="mr-2 text-xl text-blue-500" />
|
|
||||||
<h2 class="text-xl font-bold">时间线</h2>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 mt-5">
|
|
||||||
<Timeline>
|
|
||||||
<TimelineItem v-if="data.pharmacist_view_time">
|
|
||||||
{{ data.pharmacist_view_time }}
|
|
||||||
<template v-if="data.pharmacist_info">
|
|
||||||
【{{ data.pharmacist_info.name }}】 审核
|
|
||||||
</template>
|
|
||||||
<template v-else> -- </template>
|
|
||||||
</TimelineItem>
|
|
||||||
<TimelineItem v-else>
|
|
||||||
{{ statusText }}
|
|
||||||
{{ data.cancel_remark }}
|
|
||||||
</TimelineItem>
|
|
||||||
<TimelineItem>
|
|
||||||
{{ data.created_at }}
|
|
||||||
<template v-if="data.doctor_info">
|
|
||||||
【{{ data.doctor_info.name }}】 开方,诊断:{{
|
|
||||||
data.clinical_diagnose
|
|
||||||
}}
|
|
||||||
</template>
|
|
||||||
<template v-else> -- </template>
|
|
||||||
</TimelineItem>
|
|
||||||
<TimelineItem>
|
|
||||||
{{ data.register.created_at }}
|
|
||||||
<template v-if="data.doctor_info">
|
|
||||||
【{{ data.user_patient.name }}】 挂号
|
|
||||||
</template>
|
|
||||||
<template v-else> -- </template>
|
|
||||||
</TimelineItem>
|
|
||||||
</Timeline>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 处方基本信息 -->
|
|
||||||
<div
|
|
||||||
class="mb-6 transform rounded-lg p-6 shadow-md transition-all duration-300 hover:shadow-lg"
|
|
||||||
>
|
|
||||||
<div class="mb-4 flex items-center">
|
|
||||||
<FileTextOutlined class="mr-2 text-xl text-blue-500" />
|
|
||||||
<h2 class="text-xl font-bold">处方基本信息</h2>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">处方编号:</span>
|
|
||||||
<span class="value">{{ data.prescription_no }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">处方类型:</span>
|
|
||||||
<span class="value">{{ prescriptionTypeText }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">处方状态:</span>
|
|
||||||
<span :class="`text-${statusColor}-500`" class="value">{{
|
|
||||||
statusText
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">总金额:</span>
|
|
||||||
<span class="value font-bold text-red-500">¥{{ data.total_pay_price }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">过期时间:</span>
|
|
||||||
<span class="value">{{ expireTime }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 医生信息 -->
|
|
||||||
<div
|
|
||||||
class="mb-6 transform rounded-lg p-6 shadow-md transition-all duration-300 hover:shadow-lg"
|
|
||||||
>
|
|
||||||
<div class="mb-4 flex items-center">
|
|
||||||
<MedicineBoxOutlined class="mr-2 text-xl text-green-500" />
|
|
||||||
<h2 class="text-xl font-bold">医生信息</h2>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="data.doctor_info"
|
|
||||||
class="grid grid-cols-1 gap-4 md:grid-cols-2"
|
|
||||||
>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">医生姓名:</span>
|
|
||||||
<span class="value">{{ data.doctor_info.name }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">所属科室:</span>
|
|
||||||
<span class="value">{{
|
|
||||||
data.doctor_info.depart?.name || '--'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="italic text-gray-500">暂无医生信息</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 患者信息 -->
|
|
||||||
<div
|
|
||||||
class="transform rounded-lg p-6 shadow-md transition-all duration-300 hover:shadow-lg"
|
|
||||||
>
|
|
||||||
<div class="mb-4 flex items-center">
|
|
||||||
<UserOutlined class="mr-2 text-xl text-amber-500" />
|
|
||||||
<h2 class="text-xl font-bold">患者信息</h2>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="data.user_patient"
|
|
||||||
class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"
|
|
||||||
>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">患者姓名:</span>
|
|
||||||
<span class="value">{{ data.user_patient.name }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">年龄:</span>
|
|
||||||
<span class="value">{{ data.user_patient.age }}岁</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">性别:</span>
|
|
||||||
<span class="value">{{
|
|
||||||
data.user_patient.sex === 1 ? '男' : '女'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="italic text-gray-500">暂无患者信息</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 加载状态 -->
|
|
||||||
<div v-else class="flex h-64 items-center justify-center">
|
|
||||||
<div
|
|
||||||
class="h-12 w-12 animate-spin rounded-full border-b-2 border-t-2 border-blue-500"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.prescription-source-container {
|
|
||||||
@apply max-h-[70vh] overflow-auto p-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
@apply flex flex-col rounded-md p-3 transition-all duration-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
@apply mb-1 text-sm text-gray-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
@apply font-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 添加动感效果 */
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.prescription-source-container > div {
|
|
||||||
animation: fadeIn 0.5s ease-out forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prescription-source-container > div:nth-child(1) {
|
|
||||||
animation-delay: 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prescription-source-container > div:nth-child(2) {
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prescription-source-container > div:nth-child(3) {
|
|
||||||
animation-delay: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prescription-source-container > div:nth-child(4) {
|
|
||||||
animation-delay: 0.4s;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
*/
|
*/
|
||||||
import type { VxeGridListeners } from '#/adapter/vxe-table';
|
import type { VxeGridListeners } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Segmented, Tag, message } from 'ant-design-vue';
|
import { Segmented, Tabs, Tag, message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
@@ -45,21 +45,39 @@ const cardListRef = ref<InstanceType<typeof AuditPrescriptionCardList> | null>(
|
|||||||
);
|
);
|
||||||
/** 列表与卡片共用的搜索条件 */
|
/** 列表与卡片共用的搜索条件 */
|
||||||
const searchValues = ref<Record<string, any>>({});
|
const searchValues = ref<Record<string, any>>({});
|
||||||
|
/** 审核状态 Tab:0待审核 / 1已通过 / 2已拒绝 */
|
||||||
|
const statusTab = ref('0');
|
||||||
|
|
||||||
|
const mergedSearchValues = computed(() => ({
|
||||||
|
...searchValues.value,
|
||||||
|
status: Number(statusTab.value),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const canAudit = computed(() => Number(statusTab.value) === 0);
|
||||||
|
|
||||||
watch(viewMode, (mode) => {
|
watch(viewMode, (mode) => {
|
||||||
writeAuditViewMode(mode);
|
writeAuditViewMode(mode);
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 顶部统一搜索表单 */
|
/**
|
||||||
|
* 条件变更后刷新列表视图(卡片靠 formValues watch,避免与父级重复请求)
|
||||||
|
* 列表用 reload 回到第 1 页,避免停在旧页码看不到新 status 数据
|
||||||
|
*/
|
||||||
|
function reloadListFromPageOne() {
|
||||||
|
if (viewMode.value !== 'list') return;
|
||||||
|
gridApi.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 顶部统一搜索表单:卡片模式只改 searchValues,由 CardList 的 formValues watch 拉数 */
|
||||||
const [SearchForm] = useVbenForm({
|
const [SearchForm] = useVbenForm({
|
||||||
...formOptions,
|
...formOptions,
|
||||||
handleSubmit: async (values) => {
|
handleSubmit: async (values) => {
|
||||||
searchValues.value = { ...(values || {}) };
|
searchValues.value = { ...(values || {}) };
|
||||||
refreshCurrentView();
|
reloadListFromPageOne();
|
||||||
},
|
},
|
||||||
handleReset: async () => {
|
handleReset: async () => {
|
||||||
searchValues.value = {};
|
searchValues.value = {};
|
||||||
refreshCurrentView();
|
reloadListFromPageOne();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,12 +97,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
...baseGridOptions,
|
...baseGridOptions,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
/** 始终合并顶部统一搜索条件 */
|
/** 始终合并顶部统一搜索条件与状态 Tab */
|
||||||
query: async ({ page }) => {
|
query: async ({ page }) => {
|
||||||
return await getPrescriptionListApi({
|
return await getPrescriptionListApi({
|
||||||
page: page.currentPage,
|
page: page.currentPage,
|
||||||
pageSize: page.pageSize,
|
pageSize: page.pageSize,
|
||||||
...searchValues.value,
|
...mergedSearchValues.value,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -99,6 +117,18 @@ watch(viewMode, (mode, prev) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切状态 Tab:列表主动 query;卡片只改 statusTab → mergedSearchValues,
|
||||||
|
* 由 CardList watch(formValues) 单次 reload,避免 flush:pre 时读到旧 props 再发一次旧 status
|
||||||
|
*/
|
||||||
|
watch(statusTab, () => {
|
||||||
|
reloadListFromPageOne();
|
||||||
|
});
|
||||||
|
|
||||||
|
function onStatusTabChange(key: string | number) {
|
||||||
|
statusTab.value = String(key);
|
||||||
|
}
|
||||||
|
|
||||||
const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
||||||
connectedComponent: PrescriptionDetail,
|
connectedComponent: PrescriptionDetail,
|
||||||
});
|
});
|
||||||
@@ -210,6 +240,13 @@ function onViewModeChange(mode: string | number) {
|
|||||||
>
|
>
|
||||||
<SearchForm />
|
<SearchForm />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<Tabs :active-key="statusTab" @change="onStatusTabChange">
|
||||||
|
<Tabs.TabPane key="0" tab="待审核" />
|
||||||
|
<Tabs.TabPane key="1" tab="已通过" />
|
||||||
|
<Tabs.TabPane key="2" tab="已拒绝" />
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
<div class="mb-3 flex items-center justify-between gap-3">
|
<div class="mb-3 flex items-center justify-between gap-3">
|
||||||
<Segmented
|
<Segmented
|
||||||
:value="viewMode"
|
:value="viewMode"
|
||||||
@@ -249,8 +286,8 @@ function onViewModeChange(mode: string | number) {
|
|||||||
</template>
|
</template>
|
||||||
<template #status="{ row }">
|
<template #status="{ row }">
|
||||||
<Tag v-if="row.status === 0" color="red">待审核</Tag>
|
<Tag v-if="row.status === 0" color="red">待审核</Tag>
|
||||||
<Tag v-else-if="row.status === 1" color="green">通过审核</Tag>
|
<Tag v-else-if="row.status === 1" color="green">已通过</Tag>
|
||||||
<Tag v-else-if="row.status === 2" color="red">拒绝审核</Tag>
|
<Tag v-else-if="row.status === 2" color="red">已拒绝</Tag>
|
||||||
<Tag v-else color="default">—</Tag>
|
<Tag v-else color="default">—</Tag>
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ row }">
|
<template #action="{ row }">
|
||||||
@@ -266,17 +303,21 @@ function onViewModeChange(mode: string | number) {
|
|||||||
type: 'link',
|
type: 'link',
|
||||||
onClick: openPrescriptionSourceModal.bind(null, row.id),
|
onClick: openPrescriptionSourceModal.bind(null, row.id),
|
||||||
},
|
},
|
||||||
{
|
...(canAudit
|
||||||
label: '通过审核',
|
? [
|
||||||
type: 'link',
|
{
|
||||||
loading: passingId === row.id,
|
label: '通过审核',
|
||||||
onClick: pass.bind(null, row.id),
|
type: 'link' as const,
|
||||||
},
|
loading: passingId === row.id,
|
||||||
{
|
onClick: pass.bind(null, row.id),
|
||||||
label: '拒绝审核',
|
},
|
||||||
type: 'link',
|
{
|
||||||
onClick: reject.bind(null, row.id),
|
label: '拒绝审核',
|
||||||
},
|
type: 'link' as const,
|
||||||
|
onClick: reject.bind(null, row.id),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
]"
|
]"
|
||||||
:drop-down-actions="[]"
|
:drop-down-actions="[]"
|
||||||
/>
|
/>
|
||||||
@@ -285,7 +326,8 @@ function onViewModeChange(mode: string | number) {
|
|||||||
<div v-else class="audit-card-wrap">
|
<div v-else class="audit-card-wrap">
|
||||||
<AuditPrescriptionCardList
|
<AuditPrescriptionCardList
|
||||||
ref="cardListRef"
|
ref="cardListRef"
|
||||||
:form-values="searchValues"
|
:form-values="mergedSearchValues"
|
||||||
|
:can-audit="canAudit"
|
||||||
@view-detail="openPrescriptionDetail"
|
@view-detail="openPrescriptionDetail"
|
||||||
@view-source="openPrescriptionSourceModal"
|
@view-source="openPrescriptionSourceModal"
|
||||||
@reject="reject"
|
@reject="reject"
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ const miniprogramEmptyImage = ref('');
|
|||||||
const logisticsShowWhNameUser = ref(false);
|
const logisticsShowWhNameUser = ref(false);
|
||||||
/** 诊所端物流是否显示配送仓名(默认是) */
|
/** 诊所端物流是否显示配送仓名(默认是) */
|
||||||
const logisticsShowWhNameClinic = ref(true);
|
const logisticsShowWhNameClinic = ref(true);
|
||||||
|
/** 业务员是否允许编辑/改价/配置所属诊所(默认否) */
|
||||||
|
const salespersonStoreEditEnabled = ref(false);
|
||||||
|
|
||||||
function readStoredTab() {
|
function readStoredTab() {
|
||||||
const stored = localStorage.getItem(TAB_STORAGE_KEY);
|
const stored = localStorage.getItem(TAB_STORAGE_KEY);
|
||||||
@@ -42,7 +44,8 @@ function readStoredTab() {
|
|||||||
stored === 'price_adjust' ||
|
stored === 'price_adjust' ||
|
||||||
stored === 'input_audit' ||
|
stored === 'input_audit' ||
|
||||||
stored === 'miniprogram' ||
|
stored === 'miniprogram' ||
|
||||||
stored === 'logistics'
|
stored === 'logistics' ||
|
||||||
|
stored === 'salesperson'
|
||||||
) {
|
) {
|
||||||
activeKey.value = stored;
|
activeKey.value = stored;
|
||||||
}
|
}
|
||||||
@@ -104,6 +107,9 @@ async function load() {
|
|||||||
if (row.config_key === 'logistics_show_warehouse_name_clinic') {
|
if (row.config_key === 'logistics_show_warehouse_name_clinic') {
|
||||||
logisticsShowWhNameClinic.value = parseBoolConfig(row.config_value, true);
|
logisticsShowWhNameClinic.value = parseBoolConfig(row.config_value, true);
|
||||||
}
|
}
|
||||||
|
if (row.config_key === 'salesperson_store_edit_enabled') {
|
||||||
|
salespersonStoreEditEnabled.value = parseBoolConfig(row.config_value, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@@ -153,6 +159,14 @@ async function handleSave() {
|
|||||||
config_key: 'logistics_show_warehouse_name_clinic',
|
config_key: 'logistics_show_warehouse_name_clinic',
|
||||||
config_value: logisticsShowWhNameClinic.value ? '1' : '0',
|
config_value: logisticsShowWhNameClinic.value ? '1' : '0',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
config_key: 'salesperson_store_edit_enabled',
|
||||||
|
config_value: salespersonStoreEditEnabled.value ? '1' : '0',
|
||||||
|
value_type: 'bool',
|
||||||
|
config_group: 'salesperson',
|
||||||
|
description: '允许业务员编辑/改价/配置所属诊所',
|
||||||
|
sort: 300,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
message.success('保存成功');
|
message.success('保存成功');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -243,6 +257,20 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
|
||||||
|
<Tabs.TabPane key="salesperson" tab="业务员权限">
|
||||||
|
<div class="py-4">
|
||||||
|
<div class="mb-2 font-medium">允许业务员编辑/改价/配置所属诊所</div>
|
||||||
|
<div class="mb-2 text-sm text-gray-500">
|
||||||
|
关闭时业务员小程序「我的诊所」仅可查看;开启后显示编辑资料、改价、配置开关等操作
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
v-model:checked="salespersonStoreEditEnabled"
|
||||||
|
checked-children="开启"
|
||||||
|
un-checked-children="关闭"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tabs.TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user