feat: 病历模块优化
This commit is contained in:
@@ -65,6 +65,11 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
||||
{ label: '医疗器械', value: 7, icon: '', icon_text: '' },
|
||||
]);
|
||||
const prescriptionTypeDefault = ref(2);
|
||||
/** 开方种类权限校验中(进入开方页先 loading) */
|
||||
const prescriptionTypeLoading = ref(false);
|
||||
/** 当前门店是否有可开方种类 */
|
||||
const prescriptionTypeAllowed = ref(true);
|
||||
const prescriptionTypeDenyMessage = ref('');
|
||||
const diagnosis = ref('');
|
||||
const medicalAdvice = ref('');
|
||||
const treatmentPrice = ref(0);
|
||||
@@ -204,9 +209,10 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 拉取后端处方类型列表与默认选中
|
||||
* 拉取后端处方类型列表与默认选中;无绑定时清空 list 并返回 false
|
||||
*/
|
||||
const loadPrescriptionTypeOptions = async (registerId?: number | string) => {
|
||||
prescriptionTypeLoading.value = true;
|
||||
try {
|
||||
const params: { register_id?: number; store_id?: number } = {};
|
||||
const rid = Number(registerId || currentRegisterId.value);
|
||||
@@ -214,19 +220,34 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
||||
if (myStoreId.value) params.store_id = myStoreId.value;
|
||||
const res = await getPrescriptionTypeOptionsApi(params);
|
||||
const list = Array.isArray(res?.list) ? res.list : [];
|
||||
if (list.length) {
|
||||
categories.value = list.map((item) => ({
|
||||
value: Number(item.value),
|
||||
label: item.label || '',
|
||||
icon: item.icon || '',
|
||||
icon_text: item.icon_text || '',
|
||||
}));
|
||||
categories.value = list.map((item) => ({
|
||||
value: Number(item.value),
|
||||
label: item.label || '',
|
||||
icon: item.icon || '',
|
||||
icon_text: item.icon_text || '',
|
||||
}));
|
||||
const def = Number(res?.default) || 0;
|
||||
prescriptionTypeDefault.value = def;
|
||||
const allowed =
|
||||
res?.allowed !== false && list.length > 0;
|
||||
prescriptionTypeAllowed.value = allowed;
|
||||
prescriptionTypeDenyMessage.value = allowed
|
||||
? ''
|
||||
: (res?.message || '当前门店未开通开方权限');
|
||||
// 当前 Tab 不在 allowed 内时强制切到 default
|
||||
if (allowed && def && !categories.value.some((c) => c.value === activeCategory.value)) {
|
||||
activeCategory.value = def;
|
||||
}
|
||||
const def = Number(res?.default);
|
||||
if (def) prescriptionTypeDefault.value = def;
|
||||
return allowed;
|
||||
} catch (error) {
|
||||
console.error('加载处方类型失败:', error);
|
||||
categories.value = [];
|
||||
prescriptionTypeAllowed.value = false;
|
||||
prescriptionTypeDenyMessage.value = '处方类型加载失败,请稍后重试';
|
||||
message.warning('处方类型加载失败,请稍后重试');
|
||||
return false;
|
||||
} finally {
|
||||
prescriptionTypeLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1249,6 +1270,9 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
||||
activeCategory,
|
||||
categories,
|
||||
prescriptionTypeDefault,
|
||||
prescriptionTypeLoading,
|
||||
prescriptionTypeAllowed,
|
||||
prescriptionTypeDenyMessage,
|
||||
diagnosis,
|
||||
medicalAdvice,
|
||||
treatmentPrice,
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
Row,
|
||||
Select,
|
||||
SelectOption,
|
||||
Spin,
|
||||
Tag,
|
||||
Tabs,
|
||||
TabPane,
|
||||
@@ -180,6 +181,8 @@ watch(allowInsuranceCategory, (allow) => {
|
||||
}, { immediate: true });
|
||||
|
||||
const submittingType = ref(false);
|
||||
/** 弹窗内容区 loading(先校验开方种类权限) */
|
||||
const contentLoading = ref(false);
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
draggable: true,
|
||||
confirmText: '发送处方',
|
||||
@@ -200,22 +203,44 @@ const [Modal, modalApi] = useVbenModal({
|
||||
if (modalData.value?.registerId) {
|
||||
// 获取存储前缀,默认为在线复诊-药店
|
||||
const storagePrefix = modalData.value?.storagePrefix || 'onlineConsultation-';
|
||||
|
||||
// 使用完整初始化,包含患者信息(传递 true 以获取患者信息)
|
||||
await prescriptionStore.initializePrescription(
|
||||
modalData.value.registerId,
|
||||
true,
|
||||
storagePrefix,
|
||||
);
|
||||
prescriptionStore.loadFromLocalStorage();
|
||||
// 开方弹窗打开时再拉一次类型,确保 Network 可见且数据最新
|
||||
await prescriptionStore.loadPrescriptionTypeOptions(modalData.value.registerId);
|
||||
// 获取挂号诊所信息
|
||||
await prescriptionStore.fetchRegisterStoreInfo();
|
||||
|
||||
// 如果恢复的处方类型是中药,检测并显示转诊提示
|
||||
if (prescriptionStore.activeCategory === 1) {
|
||||
await checkAndShowTransferTip();
|
||||
contentLoading.value = true;
|
||||
try {
|
||||
// 先校验开方种类权限,再加载完整开方数据
|
||||
const allowed = await prescriptionStore.loadPrescriptionTypeOptions(
|
||||
modalData.value.registerId,
|
||||
);
|
||||
if (!allowed) {
|
||||
message.error(
|
||||
prescriptionStore.prescriptionTypeDenyMessage ||
|
||||
'当前门店未开通开方权限',
|
||||
);
|
||||
modalApi.close();
|
||||
return;
|
||||
}
|
||||
await prescriptionStore.initializePrescription(
|
||||
modalData.value.registerId,
|
||||
true,
|
||||
storagePrefix,
|
||||
);
|
||||
prescriptionStore.loadFromLocalStorage();
|
||||
// 再拉一次类型,确保与门店一致并校正 Tab
|
||||
await prescriptionStore.loadPrescriptionTypeOptions(
|
||||
modalData.value.registerId,
|
||||
);
|
||||
if (!prescriptionStore.prescriptionTypeAllowed) {
|
||||
message.error(
|
||||
prescriptionStore.prescriptionTypeDenyMessage ||
|
||||
'当前门店未开通开方权限',
|
||||
);
|
||||
modalApi.close();
|
||||
return;
|
||||
}
|
||||
await prescriptionStore.fetchRegisterStoreInfo();
|
||||
if (prescriptionStore.activeCategory === 1) {
|
||||
await checkAndShowTransferTip();
|
||||
}
|
||||
} finally {
|
||||
contentLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
console.warn('未提供registerId');
|
||||
@@ -934,6 +959,7 @@ const cancelSaveCommonPrescription = () => {
|
||||
|
||||
<template>
|
||||
<Modal class="w-[90%]" title="电子处方">
|
||||
<Spin :spinning="contentLoading || prescriptionStore.prescriptionTypeLoading">
|
||||
<!-- 主容器:左右布局 -->
|
||||
<div class="prescription-module flex h-[calc(80vh-60px)] gap-4 overflow-hidden">
|
||||
<!-- 说明书预览 (保持隐藏逻辑) -->
|
||||
@@ -1794,6 +1820,7 @@ const cancelSaveCommonPrescription = () => {
|
||||
</p>
|
||||
</AntModal>
|
||||
</div>
|
||||
</Spin>
|
||||
|
||||
<!-- 子模态框组件 - 按照VbenAdmin的方式声明 -->
|
||||
<WesternDrugModal />
|
||||
|
||||
@@ -119,6 +119,8 @@ export async function getPrescriptionTypeOptionsApi(params?: {
|
||||
icon?: string;
|
||||
icon_text?: string;
|
||||
}>;
|
||||
allowed?: boolean;
|
||||
message?: string;
|
||||
}>(`${prefix}prescription-type-options`, {
|
||||
params,
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
Textarea,
|
||||
Timeline,
|
||||
TimelineItem, notification,
|
||||
Spin,
|
||||
} from 'ant-design-vue';
|
||||
import {debounce} from 'lodash-es'; // 或者使用自定义防抖函数
|
||||
|
||||
@@ -206,27 +207,47 @@ function getMyStoreList() {
|
||||
|
||||
getMyStoreList();
|
||||
|
||||
/** 拉取处方类型列表与默认选中 */
|
||||
/** 开方种类权限校验中 */
|
||||
const prescriptionTypeLoading = ref(false);
|
||||
/** 当前门店是否有可开方种类 */
|
||||
const prescriptionTypeAllowed = ref(true);
|
||||
const prescriptionTypeDenyMessage = ref('');
|
||||
|
||||
/** 拉取处方类型列表与默认选中;无权限时清空并返回 false */
|
||||
async function loadPrescriptionTypeOptions(registerId?: number) {
|
||||
prescriptionTypeLoading.value = true;
|
||||
try {
|
||||
const params: { register_id?: number; store_id?: number } = {};
|
||||
if (registerId) params.register_id = registerId;
|
||||
if (myStoreId.value) params.store_id = myStoreId.value;
|
||||
const res = await getPrescriptionTypeOptionsApi(params);
|
||||
const list = Array.isArray(res?.list) ? res.list : [];
|
||||
if (list.length) {
|
||||
categories.value = list.map((item) => ({
|
||||
value: Number(item.value),
|
||||
label: item.label || '',
|
||||
icon: item.icon || '',
|
||||
icon_text: item.icon_text || '',
|
||||
}));
|
||||
categories.value = list.map((item) => ({
|
||||
value: Number(item.value),
|
||||
label: item.label || '',
|
||||
icon: item.icon || '',
|
||||
icon_text: item.icon_text || '',
|
||||
}));
|
||||
const def = Number(res?.default) || 0;
|
||||
prescriptionTypeDefault.value = def;
|
||||
const allowed = res?.allowed !== false && list.length > 0;
|
||||
prescriptionTypeAllowed.value = allowed;
|
||||
prescriptionTypeDenyMessage.value = allowed
|
||||
? ''
|
||||
: (res?.message || '当前门店未开通开方权限');
|
||||
if (allowed && def && !categories.value.some((c) => c.value === activeCategory.value)) {
|
||||
activeCategory.value = def;
|
||||
}
|
||||
const def = Number(res?.default);
|
||||
if (def) prescriptionTypeDefault.value = def;
|
||||
return allowed;
|
||||
} catch (e) {
|
||||
console.error('加载处方类型失败', e);
|
||||
categories.value = [];
|
||||
prescriptionTypeAllowed.value = false;
|
||||
prescriptionTypeDenyMessage.value = '处方类型加载失败,请稍后重试';
|
||||
message.warning('处方类型加载失败,请稍后重试');
|
||||
return false;
|
||||
} finally {
|
||||
prescriptionTypeLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,7 +690,10 @@ const selectPatient = async (patient: Patient, isUpdateTabType = true) => {
|
||||
|
||||
const registerId = patient.id;
|
||||
// 先拉类型,再恢复草稿/默认 Tab
|
||||
await loadPrescriptionTypeOptions(registerId);
|
||||
const allowed = await loadPrescriptionTypeOptions(registerId);
|
||||
if (!allowed) {
|
||||
message.error(prescriptionTypeDenyMessage.value || '当前门店未开通开方权限');
|
||||
}
|
||||
// 优先恢复完整草稿(诊断、医嘱、诊疗费、中药配置等)
|
||||
const hasDraft = await restoreReceptionDraft(registerId);
|
||||
if (!hasDraft) {
|
||||
@@ -689,6 +713,14 @@ const selectPatient = async (patient: Patient, isUpdateTabType = true) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 恢复的 Tab 若不在权限内,强制切到默认
|
||||
if (
|
||||
allowed &&
|
||||
categories.value.length &&
|
||||
!categories.value.some((c) => c.value === activeCategory.value)
|
||||
) {
|
||||
activeCategory.value = prescriptionTypeDefault.value || categories.value[0].value;
|
||||
}
|
||||
|
||||
// 如果恢复的处方类型是中药,检测并显示转诊提示
|
||||
if (activeCategory.value === 1) {
|
||||
@@ -2535,46 +2567,110 @@ function updateLocalStorageStoreInfo() {
|
||||
tabType.value = 0;
|
||||
getPatientListByReception();
|
||||
}
|
||||
const leftShow = ref(true)
|
||||
// 获取doctorReceptionLeftShow
|
||||
function getDoctorReceptionLeftShow() {
|
||||
const localStorageLeftShow = localStorage.getItem(`doctorReceptionLeftShow`);
|
||||
leftShow.value = localStorageLeftShow !== 'false';
|
||||
/**
|
||||
* 患者列表:默认收起;鼠标悬停展开,移出自动收起;可点击钉住保持展开
|
||||
* leftShow = 钉住 || 悬停
|
||||
*/
|
||||
const leftPinned = ref(false);
|
||||
const leftHover = ref(false);
|
||||
let leftLeaveTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
/** 门店下拉打开时暂不因 mouseleave 收起(下拉挂到 body) */
|
||||
const storeSelectOpen = ref(false);
|
||||
|
||||
const leftShow = computed(() => leftPinned.value || leftHover.value);
|
||||
|
||||
function getDoctorReceptionLeftPinned() {
|
||||
const v = localStorage.getItem('doctorReceptionLeftPinned');
|
||||
leftPinned.value = v === 'true';
|
||||
}
|
||||
getDoctorReceptionLeftShow();
|
||||
// 监听leftShow存入缓存
|
||||
getDoctorReceptionLeftPinned();
|
||||
|
||||
watch(
|
||||
() => leftShow.value,
|
||||
async (enable) => {
|
||||
localStorage.setItem(`doctorReceptionLeftShow`, enable);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
() => leftPinned.value,
|
||||
(enable) => {
|
||||
localStorage.setItem('doctorReceptionLeftPinned', String(!!enable));
|
||||
},
|
||||
);
|
||||
|
||||
/** 侧栏进入:取消待收起并展开 */
|
||||
function onPatientSidebarEnter() {
|
||||
if (leftLeaveTimer) {
|
||||
clearTimeout(leftLeaveTimer);
|
||||
leftLeaveTimer = null;
|
||||
}
|
||||
leftHover.value = true;
|
||||
}
|
||||
|
||||
/** 侧栏离开:短暂延迟后收起,避免点到下拉/滚动条误收 */
|
||||
function onPatientSidebarLeave() {
|
||||
if (leftLeaveTimer) clearTimeout(leftLeaveTimer);
|
||||
leftLeaveTimer = setTimeout(() => {
|
||||
if (!storeSelectOpen.value) {
|
||||
leftHover.value = false;
|
||||
}
|
||||
}, 280);
|
||||
}
|
||||
|
||||
/** 点击钉住/取消钉住(展开时显示) */
|
||||
function toggleLeftPinned() {
|
||||
leftPinned.value = !leftPinned.value;
|
||||
if (leftPinned.value) {
|
||||
leftHover.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** 门店下拉显隐:打开时保持展开;关闭后若未钉住则按离开逻辑收起 */
|
||||
function onStoreSelectOpenChange(open: boolean) {
|
||||
storeSelectOpen.value = open;
|
||||
if (open) {
|
||||
onPatientSidebarEnter();
|
||||
} else if (!leftPinned.value) {
|
||||
onPatientSidebarLeave();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="reception-layout" :class="{ 'is-left-collapsed': !leftShow }">
|
||||
<RefusalOfTreatmentModals />
|
||||
<InfoModalComponent />
|
||||
<!-- 左侧患者列表:宽度过渡动画,收起后保留窄条展开按钮 -->
|
||||
<aside class="patient-sidebar">
|
||||
<div class="patient-sidebar__toggle">
|
||||
<!-- 左侧患者列表:悬停展开、移出收起;收起时左上角显示当前患者名 -->
|
||||
<aside
|
||||
class="patient-sidebar"
|
||||
@mouseenter="onPatientSidebarEnter"
|
||||
@mouseleave="onPatientSidebarLeave"
|
||||
>
|
||||
<!-- 收起态:竖排患者名,提示可悬停展开 -->
|
||||
<div v-show="!leftShow" class="patient-sidebar__collapsed">
|
||||
<div
|
||||
class="patient-sidebar__collapsed-name"
|
||||
:title="(activePatient && activePatient.name) || '悬停展开患者列表'"
|
||||
>
|
||||
{{ (activePatient && activePatient.name) || '患者' }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="leftShow" class="patient-sidebar__toggle">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
class="patient-sidebar__toggle-btn"
|
||||
:title="leftShow ? '收起患者列表' : '展开患者列表'"
|
||||
@click="leftShow = !leftShow"
|
||||
:title="leftPinned ? '取消固定,移出后收起' : '固定展开'"
|
||||
@click="toggleLeftPinned"
|
||||
>
|
||||
<MenuFoldOutlined v-if="leftShow" />
|
||||
<MenuFoldOutlined v-if="leftPinned" />
|
||||
<MenuUnfoldOutlined v-else />
|
||||
<span v-if="leftShow" class="patient-sidebar__toggle-text">收起</span>
|
||||
<span class="patient-sidebar__toggle-text">{{
|
||||
leftPinned ? '取消固定' : '固定'
|
||||
}}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="patient-panel">
|
||||
<Select v-model:value="myStoreId" class="w-full" @change="switchStore">
|
||||
<Select
|
||||
v-model:value="myStoreId"
|
||||
class="w-full"
|
||||
@change="switchStore"
|
||||
@dropdown-visible-change="onStoreSelectOpenChange"
|
||||
>
|
||||
<SelectOption v-for="item in myStoreList" :key="item.id" :value="item.id">
|
||||
{{ item.name }}
|
||||
</SelectOption>
|
||||
@@ -2877,10 +2973,18 @@ watch(
|
||||
</Page>
|
||||
|
||||
<Page v-if="tabType === 2" class="prescription-panel prescription-panel--rx">
|
||||
<Spin :spinning="prescriptionTypeLoading">
|
||||
<div class="prescription-header">
|
||||
<h2>{{ activePatient.name }} 的处方</h2>
|
||||
</div>
|
||||
|
||||
<Empty
|
||||
v-if="!prescriptionTypeLoading && !prescriptionTypeAllowed"
|
||||
:description="prescriptionTypeDenyMessage || '当前门店未开通开方权限'"
|
||||
class="py-20"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<Tabs
|
||||
v-if="canUseMedicalRecord"
|
||||
v-model:active-key="rxMrTab"
|
||||
@@ -3635,6 +3739,8 @@ watch(
|
||||
:user-patient-id="Number(activePatient?.id || 0)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Spin>
|
||||
</Page>
|
||||
<div v-else-if="tabType === 0" class="prescription-panel">
|
||||
<Empty/>
|
||||
@@ -3737,10 +3843,34 @@ watch(
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: hsl(var(--background));
|
||||
z-index: 20;
|
||||
}
|
||||
.reception-layout.is-left-collapsed .patient-sidebar {
|
||||
width: 44px;
|
||||
}
|
||||
.patient-sidebar__collapsed {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 12px 0 16px;
|
||||
min-height: 0;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
.patient-sidebar__collapsed-name {
|
||||
writing-mode: vertical-rl;
|
||||
text-orientation: mixed;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
letter-spacing: 0.12em;
|
||||
line-height: 1.2;
|
||||
max-height: calc(90vh - 48px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.patient-sidebar__toggle {
|
||||
flex-shrink: 0;
|
||||
padding: 8px;
|
||||
|
||||
@@ -358,37 +358,82 @@ function appendCommaText(base: string, add: string) {
|
||||
return parts.join(',');
|
||||
}
|
||||
|
||||
/** 从中文逗号列表移除一项 */
|
||||
function removeCommaText(base: string, remove: string) {
|
||||
if (!base || !remove) return base || '';
|
||||
return (base || '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s && s !== remove)
|
||||
.join(',');
|
||||
}
|
||||
|
||||
/** 从换行文本移除一行(优先整行匹配,否则去掉首次出现) */
|
||||
function removeAppendedText(base: string, remove: string) {
|
||||
if (!base || !remove) return base || '';
|
||||
const lines = base.split('\n');
|
||||
const idx = lines.findIndex((l) => l.trim() === remove.trim());
|
||||
if (idx >= 0) {
|
||||
lines.splice(idx, 1);
|
||||
return lines.join('\n');
|
||||
}
|
||||
const i = base.indexOf(remove);
|
||||
if (i < 0) return base;
|
||||
return `${base.slice(0, i)}${base.slice(i + remove.length)}`
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.replace(/^\n+|\n+$/g, '');
|
||||
}
|
||||
|
||||
/** 字段正文是否已包含该文案 */
|
||||
function fieldContainsText(base: string, text: string) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t) return false;
|
||||
const v = String(base || '');
|
||||
if (!v) return false;
|
||||
if (v.split(',').map((s) => s.trim()).includes(t)) return true;
|
||||
if (v.split('\n').map((s) => s.trim()).includes(t)) return true;
|
||||
return v.includes(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* 选用词条:舌象/脉象覆盖;诊断/医嘱/中医三典逗号拼接;其余换行追加
|
||||
* 选用词条(切换):正文已有则删除对应文案,否则追加
|
||||
* 舌象/脉象:相同则清空,不同则覆盖
|
||||
*/
|
||||
function onEntryPick(fieldCode: string, item: MedicalRecordEntryItem) {
|
||||
const text = item.content || item.title || '';
|
||||
if (!text) return;
|
||||
if (fieldCode === 'tongue' || fieldCode === 'pulse') {
|
||||
(form as any)[fieldCode] = text;
|
||||
const cur = String((form as any)[fieldCode] || '');
|
||||
(form as any)[fieldCode] = cur.trim() === text.trim() ? '' : text;
|
||||
return;
|
||||
}
|
||||
if (fieldCode === 'diagnosis') {
|
||||
diagnosisModel.value = appendCommaText(diagnosisModel.value, text);
|
||||
diagnosisModel.value = fieldContainsText(diagnosisModel.value, text)
|
||||
? removeCommaText(diagnosisModel.value, text)
|
||||
: appendCommaText(diagnosisModel.value, text);
|
||||
return;
|
||||
}
|
||||
if (fieldCode === 'doctor_order') {
|
||||
medicalAdviceModel.value = appendCommaText(medicalAdviceModel.value, text);
|
||||
medicalAdviceModel.value = fieldContainsText(medicalAdviceModel.value, text)
|
||||
? removeCommaText(medicalAdviceModel.value, text)
|
||||
: appendCommaText(medicalAdviceModel.value, text);
|
||||
return;
|
||||
}
|
||||
// 中医疾病/证候/治法与诊断类似,多项用中文逗号拼接
|
||||
if (
|
||||
fieldCode === 'tcm_disease' ||
|
||||
fieldCode === 'tcm_syndrome' ||
|
||||
fieldCode === 'tcm_method'
|
||||
) {
|
||||
(form as any)[fieldCode] = appendCommaText(
|
||||
String((form as any)[fieldCode] || ''),
|
||||
text,
|
||||
);
|
||||
const cur = String((form as any)[fieldCode] || '');
|
||||
(form as any)[fieldCode] = fieldContainsText(cur, text)
|
||||
? removeCommaText(cur, text)
|
||||
: appendCommaText(cur, text);
|
||||
return;
|
||||
}
|
||||
(form as any)[fieldCode] = appendText((form as any)[fieldCode] || '', text);
|
||||
const cur = String((form as any)[fieldCode] || '');
|
||||
(form as any)[fieldCode] = fieldContainsText(cur, text)
|
||||
? removeAppendedText(cur, text)
|
||||
: appendText(cur, text);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -485,7 +530,9 @@ function openFieldExpand(fieldCode: string, label: string) {
|
||||
{
|
||||
source: tcmSource || 'entry',
|
||||
fieldCode: tcmSource ? undefined : fieldCode,
|
||||
showChips: false,
|
||||
// 词条与诊断/医嘱一样,跟随 mr_common_display_mode(中医三典无常用词条表)
|
||||
showChips: !tcmSource && showCommonTags.value,
|
||||
showCommonInBubble: !tcmSource && showCommonInBubble.value,
|
||||
joinMode,
|
||||
},
|
||||
);
|
||||
@@ -669,6 +716,7 @@ const rightFields = [
|
||||
v-if="showCommonTags"
|
||||
:key="`dx_chips_${commonChipsTick}`"
|
||||
source="diagnosis"
|
||||
:field-value="diagnosisModel"
|
||||
:disabled="diagnosisReadonly"
|
||||
@select="(text) => onEntryPick('diagnosis', { id: text, title: text, content: text, remark: '' })"
|
||||
/>
|
||||
@@ -686,7 +734,9 @@ const rightFields = [
|
||||
field-code="tongue"
|
||||
:store-id="storeId"
|
||||
placeholder="舌象词条"
|
||||
:show-common-in-bubble="showCommonInBubble"
|
||||
@select="(item) => onEntryPick('tongue', item)"
|
||||
@common-changed="commonChipsTick += 1"
|
||||
/>
|
||||
</div>
|
||||
<div data-mr-module="tongue" data-mr-role="textarea">
|
||||
@@ -697,6 +747,22 @@ const rightFields = [
|
||||
@dblclick="openFieldExpand('tongue', '舌象')"
|
||||
/>
|
||||
</div>
|
||||
<CommonDxOrderChips
|
||||
v-if="showCommonTags"
|
||||
:key="`entry_chips_tongue_${commonChipsTick}`"
|
||||
source="entry"
|
||||
field-code="tongue"
|
||||
:field-value="form.tongue"
|
||||
@select="
|
||||
(text) =>
|
||||
onEntryPick('tongue', {
|
||||
id: text,
|
||||
title: text,
|
||||
content: text,
|
||||
remark: '',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="mb-1 text-sm text-muted-foreground">脉象</div>
|
||||
@@ -706,7 +772,9 @@ const rightFields = [
|
||||
field-code="pulse"
|
||||
:store-id="storeId"
|
||||
placeholder="脉象词条"
|
||||
:show-common-in-bubble="showCommonInBubble"
|
||||
@select="(item) => onEntryPick('pulse', item)"
|
||||
@common-changed="commonChipsTick += 1"
|
||||
/>
|
||||
</div>
|
||||
<div data-mr-module="pulse" data-mr-role="textarea">
|
||||
@@ -717,6 +785,22 @@ const rightFields = [
|
||||
@dblclick="openFieldExpand('pulse', '脉象')"
|
||||
/>
|
||||
</div>
|
||||
<CommonDxOrderChips
|
||||
v-if="showCommonTags"
|
||||
:key="`entry_chips_pulse_${commonChipsTick}`"
|
||||
source="entry"
|
||||
field-code="pulse"
|
||||
:field-value="form.pulse"
|
||||
@select="
|
||||
(text) =>
|
||||
onEntryPick('pulse', {
|
||||
id: text,
|
||||
title: text,
|
||||
content: text,
|
||||
remark: '',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 2) 辨证 / 病案 -->
|
||||
@@ -726,7 +810,9 @@ const rightFields = [
|
||||
:ref="(el) => setSearchRef('tcm_case', el)"
|
||||
field-code="tcm_case"
|
||||
:store-id="storeId"
|
||||
:show-common-in-bubble="showCommonInBubble"
|
||||
@select="(item) => onEntryPick('tcm_case', item)"
|
||||
@common-changed="commonChipsTick += 1"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3" data-mr-module="tcm_case" data-mr-role="textarea">
|
||||
@@ -738,6 +824,23 @@ const rightFields = [
|
||||
@dblclick="openFieldExpand('tcm_case', '辨证/病案')"
|
||||
/>
|
||||
</div>
|
||||
<CommonDxOrderChips
|
||||
v-if="showCommonTags"
|
||||
:key="`entry_chips_tcm_case_${commonChipsTick}`"
|
||||
class="mb-3"
|
||||
source="entry"
|
||||
field-code="tcm_case"
|
||||
:field-value="form.tcm_case"
|
||||
@select="
|
||||
(text) =>
|
||||
onEntryPick('tcm_case', {
|
||||
id: text,
|
||||
title: text,
|
||||
content: text,
|
||||
remark: '',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<!-- 3) 证候|疾病、治法|空:与舌脉同为一行两列 -->
|
||||
<div
|
||||
v-for="(row, rowIdx) in tcmCaseSubRows"
|
||||
@@ -800,7 +903,9 @@ const rightFields = [
|
||||
:ref="(el) => setSearchRef(f.fieldCode, el)"
|
||||
:field-code="f.fieldCode"
|
||||
:store-id="storeId"
|
||||
:show-common-in-bubble="showCommonInBubble"
|
||||
@select="(item) => onEntryPick(f.fieldCode, item)"
|
||||
@common-changed="commonChipsTick += 1"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="f.fieldCode === 'doctor_order'">
|
||||
@@ -817,6 +922,7 @@ const rightFields = [
|
||||
v-if="showCommonTags"
|
||||
:key="`order_chips_${commonChipsTick}`"
|
||||
source="doctor_order"
|
||||
:field-value="medicalAdviceModel"
|
||||
@select="(text) => onEntryPick('doctor_order', { id: text, title: text, content: text, remark: '' })"
|
||||
/>
|
||||
</template>
|
||||
@@ -833,6 +939,22 @@ const rightFields = [
|
||||
@dblclick="openFieldExpand(f.textKey, f.label)"
|
||||
/>
|
||||
</div>
|
||||
<CommonDxOrderChips
|
||||
v-if="showCommonTags && f.fieldCode !== 'tcm_case' && f.fieldCode !== 'doctor_order'"
|
||||
:key="`entry_chips_${f.fieldCode}_${commonChipsTick}`"
|
||||
source="entry"
|
||||
:field-code="f.fieldCode"
|
||||
:field-value="String((form as any)[f.textKey] || '')"
|
||||
@select="
|
||||
(text) =>
|
||||
onEntryPick(f.fieldCode, {
|
||||
id: text,
|
||||
title: text,
|
||||
content: text,
|
||||
remark: '',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col :span="12">
|
||||
@@ -847,77 +969,78 @@ const rightFields = [
|
||||
:ref="(el) => setSearchRef(f.fieldCode, el)"
|
||||
:field-code="f.fieldCode"
|
||||
:store-id="storeId"
|
||||
:show-common-in-bubble="showCommonInBubble"
|
||||
@select="(item) => onEntryPick(f.fieldCode, item)"
|
||||
@common-changed="commonChipsTick += 1"
|
||||
/>
|
||||
</div>
|
||||
<!-- 体征:一段话内联书写(标签含单位 + 下划线输入) -->
|
||||
<div
|
||||
v-if="f.fieldCode === 'physical_exam'"
|
||||
class="vitals-underline mb-2 flex flex-wrap items-end gap-x-3 gap-y-2 text-sm"
|
||||
class="vitals-para mb-2"
|
||||
>
|
||||
<span class="vital-item">
|
||||
<span class="vital-label">体温</span>
|
||||
<span class="vital-chunk">
|
||||
<span class="vital-label">体温(°C)</span>
|
||||
<InputNumber
|
||||
v-model:value="form.temperature"
|
||||
:min="0"
|
||||
:step="0.1"
|
||||
:bordered="false"
|
||||
class="vital-input"
|
||||
placeholder="—"
|
||||
placeholder=" "
|
||||
/>
|
||||
<span class="vital-unit">℃</span>
|
||||
</span>
|
||||
<span class="vital-item">
|
||||
<span class="vital-label">身高</span>
|
||||
<span class="vital-chunk">
|
||||
<span class="vital-label">身高(cm)</span>
|
||||
<InputNumber
|
||||
v-model:value="form.height"
|
||||
:min="0"
|
||||
:bordered="false"
|
||||
class="vital-input"
|
||||
placeholder="—"
|
||||
placeholder=" "
|
||||
/>
|
||||
<span class="vital-unit">cm</span>
|
||||
</span>
|
||||
<span class="vital-item">
|
||||
<span class="vital-label">体重</span>
|
||||
<span class="vital-chunk">
|
||||
<span class="vital-label">体重(KG)</span>
|
||||
<InputNumber
|
||||
v-model:value="form.weight"
|
||||
:min="0"
|
||||
:step="0.1"
|
||||
:bordered="false"
|
||||
class="vital-input"
|
||||
placeholder="—"
|
||||
placeholder=" "
|
||||
/>
|
||||
<span class="vital-unit">KG</span>
|
||||
</span>
|
||||
<span class="vital-item">
|
||||
<span class="vital-label">呼吸</span>
|
||||
<span class="vital-chunk">
|
||||
<span class="vital-label">呼吸(次/分)</span>
|
||||
<InputNumber
|
||||
v-model:value="form.respiratory_rate"
|
||||
:min="0"
|
||||
:bordered="false"
|
||||
class="vital-input vital-input--sm"
|
||||
placeholder="—"
|
||||
placeholder=" "
|
||||
/>
|
||||
<span class="vital-unit">次/分</span>
|
||||
</span>
|
||||
<span class="vital-item">
|
||||
<span class="vital-label">血压</span>
|
||||
<span class="vital-chunk">
|
||||
<span class="vital-label">高压 mmHg</span>
|
||||
<InputNumber
|
||||
v-model:value="form.bp_systolic"
|
||||
:min="0"
|
||||
:bordered="false"
|
||||
class="vital-input vital-input--sm"
|
||||
placeholder="—"
|
||||
placeholder=" "
|
||||
/>
|
||||
<span class="vital-sep">/</span>
|
||||
</span>
|
||||
<span class="vital-sep">/</span>
|
||||
<span class="vital-chunk">
|
||||
<span class="vital-label">低压 mmHg</span>
|
||||
<InputNumber
|
||||
v-model:value="form.bp_diastolic"
|
||||
:min="0"
|
||||
:bordered="false"
|
||||
class="vital-input vital-input--sm"
|
||||
placeholder="—"
|
||||
placeholder=" "
|
||||
/>
|
||||
<span class="vital-unit">mmHg</span>
|
||||
</span>
|
||||
</div>
|
||||
<div :data-mr-module="f.fieldCode" data-mr-role="textarea">
|
||||
@@ -929,6 +1052,22 @@ const rightFields = [
|
||||
@dblclick="openFieldExpand(f.textKey, f.label)"
|
||||
/>
|
||||
</div>
|
||||
<CommonDxOrderChips
|
||||
v-if="showCommonTags"
|
||||
:key="`entry_chips_r_${f.fieldCode}_${commonChipsTick}`"
|
||||
source="entry"
|
||||
:field-code="f.fieldCode"
|
||||
:field-value="String((form as any)[f.textKey] || '')"
|
||||
@select="
|
||||
(text) =>
|
||||
onEntryPick(f.fieldCode, {
|
||||
id: text,
|
||||
title: text,
|
||||
content: text,
|
||||
remark: '',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -940,36 +1079,55 @@ const rightFields = [
|
||||
.medical-record-panel {
|
||||
padding: 8px 0 24px;
|
||||
}
|
||||
.vitals-underline .vital-item {
|
||||
display: inline-flex;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
}
|
||||
.vitals-underline .vital-label,
|
||||
.vitals-underline .vital-unit,
|
||||
.vitals-underline .vital-sep {
|
||||
/* 体征一段话:标签 + 下划线输入横向流式排布 */
|
||||
.vitals-para {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
column-gap: 14px;
|
||||
row-gap: 10px;
|
||||
line-height: 2;
|
||||
font-size: 13px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
.vitals-para .vital-chunk {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.vitals-underline :deep(.vital-input.ant-input-number),
|
||||
.vitals-underline :deep(.vital-input .ant-input-number) {
|
||||
width: 88px;
|
||||
.vitals-para .vital-label {
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 13px;
|
||||
}
|
||||
.vitals-para .vital-sep {
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin: 0 2px;
|
||||
}
|
||||
.vitals-para :deep(.vital-input.ant-input-number),
|
||||
.vitals-para :deep(.vital-input .ant-input-number) {
|
||||
width: 72px;
|
||||
border: none !important;
|
||||
border-bottom: 1px solid hsl(var(--border)) !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: none !important;
|
||||
background: transparent;
|
||||
}
|
||||
.vitals-underline :deep(.vital-input--sm.ant-input-number),
|
||||
.vitals-underline :deep(.vital-input--sm .ant-input-number) {
|
||||
width: 72px;
|
||||
.vitals-para :deep(.vital-input--sm.ant-input-number),
|
||||
.vitals-para :deep(.vital-input--sm .ant-input-number) {
|
||||
width: 64px;
|
||||
}
|
||||
.vitals-underline :deep(.ant-input-number-focused),
|
||||
.vitals-underline :deep(.ant-input-number:hover) {
|
||||
.vitals-para :deep(.ant-input-number-input) {
|
||||
text-align: center;
|
||||
padding: 0 4px !important;
|
||||
height: 28px !important;
|
||||
}
|
||||
.vitals-para :deep(.ant-input-number-focused),
|
||||
.vitals-para :deep(.ant-input-number:hover) {
|
||||
border-bottom-color: hsl(var(--primary)) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.vitals-underline :deep(.ant-input-number-handler-wrap) {
|
||||
opacity: 0.65;
|
||||
.vitals-para :deep(.ant-input-number-handler-wrap) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,6 +31,24 @@ export async function searchMedicalRecordEntry(data: {
|
||||
return requestClient.get<any>(`${prefix}search-entry`, { params: data });
|
||||
}
|
||||
|
||||
/** 我的常用词条(医生维度) */
|
||||
export async function getMyMedicalRecordEntryList(data: {
|
||||
field_code?: string;
|
||||
limit?: number;
|
||||
}) {
|
||||
return requestClient.get<any>(`${prefix}my-entry-list`, { params: data });
|
||||
}
|
||||
|
||||
/** 加入常用词条 */
|
||||
export async function addMyMedicalRecordEntry(entry_id: number) {
|
||||
return requestClient.post<any>(`${prefix}add-my-entry`, { entry_id });
|
||||
}
|
||||
|
||||
/** 删除常用词条(常用记录 id) */
|
||||
export async function deleteMyMedicalRecordEntry(id: number) {
|
||||
return requestClient.post<any>(`${prefix}delete-my-entry`, { id });
|
||||
}
|
||||
|
||||
export async function getMedicalRecord(data: {
|
||||
register_id: number;
|
||||
store_id?: number;
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 常用诊断 / 常用医嘱快捷标签
|
||||
* 展示在输入框下方,点击即追加到对应字段(与 EntryKeywordBubble 同源数据)
|
||||
* 常用快捷标签(诊断 / 医嘱 / 病历词条)
|
||||
* - 正文已包含该文案时高亮(is-active)
|
||||
* - 点击:已在正文则移除,否则追加(由父级 onEntryPick 统一 toggle)
|
||||
*/
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { Tag } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
getMyMedicalRecordEntryList,
|
||||
} from '#/views/doctor/medical-record/api';
|
||||
import { getDoctorOrderList } from '#/views/doctor/doctor-reception/api';
|
||||
import { getDoctorMyDiseaseListApi } from '#/views/doctor/settings/api';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** diagnosis=我的常用诊断;doctor_order=我的常用医嘱 */
|
||||
source: 'diagnosis' | 'doctor_order';
|
||||
/** 最多展示条数 */
|
||||
/** diagnosis=常用诊断;doctor_order=常用医嘱;entry=常用词条 */
|
||||
source: 'diagnosis' | 'doctor_order' | 'entry';
|
||||
/** entry 时必填 */
|
||||
fieldCode?: string;
|
||||
/** 当前字段正文,用于高亮已选 */
|
||||
fieldValue?: string;
|
||||
limit?: number;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{
|
||||
fieldCode: '',
|
||||
fieldValue: '',
|
||||
limit: 12,
|
||||
disabled: false,
|
||||
},
|
||||
@@ -33,8 +42,28 @@ type ChipItem = { id: string | number; text: string };
|
||||
const chips = ref<ChipItem[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
/** 文案是否已出现在字段值中(逗号分段或整行/包含) */
|
||||
function textInField(fieldValue: string, text: string) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t) return false;
|
||||
const v = String(fieldValue || '');
|
||||
if (!v) return false;
|
||||
if (v.split(',').map((s) => s.trim()).includes(t)) return true;
|
||||
if (v.split('\n').map((s) => s.trim()).includes(t)) return true;
|
||||
return v.includes(t);
|
||||
}
|
||||
|
||||
const activeSet = computed(() => {
|
||||
const set = new Set<string>();
|
||||
const v = props.fieldValue || '';
|
||||
for (const c of chips.value) {
|
||||
if (textInField(v, c.text)) set.add(c.text);
|
||||
}
|
||||
return set;
|
||||
});
|
||||
|
||||
/**
|
||||
* 拉取常用项(诊断取医生常用病;医嘱取我的医嘱,无则带公共前几条)
|
||||
* 拉取常用项
|
||||
*/
|
||||
async function loadChips() {
|
||||
loading.value = true;
|
||||
@@ -51,6 +80,26 @@ async function loadChips() {
|
||||
.slice(0, props.limit) as ChipItem[];
|
||||
return;
|
||||
}
|
||||
if (props.source === 'entry') {
|
||||
if (!props.fieldCode) {
|
||||
chips.value = [];
|
||||
return;
|
||||
}
|
||||
const list = await getMyMedicalRecordEntryList({
|
||||
field_code: props.fieldCode,
|
||||
limit: props.limit,
|
||||
}).catch(() => []);
|
||||
chips.value = (list || [])
|
||||
.map((item: any) => {
|
||||
const text = String(item?.content || item?.title || '').trim();
|
||||
return text
|
||||
? { id: item?.id ?? item?.entry_id ?? text, text }
|
||||
: null;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.slice(0, props.limit) as ChipItem[];
|
||||
return;
|
||||
}
|
||||
const res = await getDoctorOrderList().catch(() => ({ my: [], common: [] }));
|
||||
const my = (res?.my || [])
|
||||
.map((item: any) => {
|
||||
@@ -79,8 +128,14 @@ function onPick(item: ChipItem) {
|
||||
emit('select', item.text);
|
||||
}
|
||||
|
||||
const labelText = computed(() => {
|
||||
if (props.source === 'diagnosis') return '常用诊断';
|
||||
if (props.source === 'doctor_order') return '常用医嘱';
|
||||
return '常用词条';
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.source,
|
||||
() => [props.source, props.fieldCode] as const,
|
||||
() => {
|
||||
loadChips();
|
||||
},
|
||||
@@ -94,16 +149,17 @@ defineExpose({ reload: loadChips });
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="chips.length > 0" class="dx-order-chips">
|
||||
<span class="dx-order-chips__label">
|
||||
{{ source === 'diagnosis' ? '常用诊断' : '常用医嘱' }}
|
||||
</span>
|
||||
<span class="dx-order-chips__label">{{ labelText }}</span>
|
||||
<div class="dx-order-chips__list">
|
||||
<Tag
|
||||
v-for="item in chips"
|
||||
:key="`${item.id}_${item.text}`"
|
||||
class="dx-order-chips__tag"
|
||||
:class="{ 'is-disabled': disabled }"
|
||||
color="processing"
|
||||
:class="{
|
||||
'is-disabled': disabled,
|
||||
'is-active': activeSet.has(item.text),
|
||||
}"
|
||||
:color="activeSet.has(item.text) ? 'success' : 'processing'"
|
||||
@click="onPick(item)"
|
||||
>
|
||||
{{ item.text }}
|
||||
@@ -124,16 +180,17 @@ defineExpose({ reload: loadChips });
|
||||
.dx-order-chips__list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
gap: 8px;
|
||||
}
|
||||
.dx-order-chips__tag {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
max-width: 100%;
|
||||
white-space: normal;
|
||||
height: auto;
|
||||
line-height: 1.4;
|
||||
padding: 2px 8px;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.dx-order-chips__tag.is-active {
|
||||
font-weight: 600;
|
||||
}
|
||||
.dx-order-chips__tag.is-disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
} from '#/views/doctor/settings/api';
|
||||
import { getSystemConfigByKeys } from '#/views/system/system-config/api';
|
||||
|
||||
import { createMedicalRecordEntry, searchMedicalRecordEntry } from '../api';
|
||||
import { createMedicalRecordEntry, searchMedicalRecordEntry, addMyMedicalRecordEntry } from '../api';
|
||||
import { MEDICAL_RECORD_FIELD_OPTIONS } from '../config/constants';
|
||||
|
||||
import CommonDxOrderChips from './CommonDxOrderChips.vue';
|
||||
@@ -101,7 +101,9 @@ const bubbleChipsTick = ref(0);
|
||||
const showBubbleChips = computed(
|
||||
() =>
|
||||
props.showCommonInBubble === true &&
|
||||
(props.source === 'diagnosis' || props.source === 'doctor_order'),
|
||||
(props.source === 'diagnosis' ||
|
||||
props.source === 'doctor_order' ||
|
||||
(props.source === 'entry' && !!props.fieldCode)),
|
||||
);
|
||||
|
||||
const ROW_HEIGHT = 52;
|
||||
@@ -132,7 +134,10 @@ const createForm = reactive({
|
||||
remark: '',
|
||||
});
|
||||
const showActionCol = computed(
|
||||
() => props.source === 'diagnosis' || props.source === 'doctor_order',
|
||||
() =>
|
||||
props.source === 'diagnosis' ||
|
||||
props.source === 'doctor_order' ||
|
||||
(props.source === 'entry' && !!props.fieldCode),
|
||||
);
|
||||
/** 病历词条 / 我的医嘱 均可新增 */
|
||||
const showCreateEntry = computed(
|
||||
@@ -617,6 +622,17 @@ async function handleAddCommon(item: MedicalRecordEntryItem, e?: Event) {
|
||||
message.success('已加入常用医嘱');
|
||||
bubbleChipsTick.value += 1;
|
||||
emit('common-changed');
|
||||
} else if (props.source === 'entry' || (!props.source && props.fieldCode)) {
|
||||
const entryId = Number(item.id);
|
||||
if (!entryId) {
|
||||
message.warning('无法识别词条ID');
|
||||
return;
|
||||
}
|
||||
await addMyMedicalRecordEntry(entryId);
|
||||
item.is_common = true;
|
||||
message.success('已加入常用词条');
|
||||
bubbleChipsTick.value += 1;
|
||||
emit('common-changed');
|
||||
}
|
||||
} catch (err: any) {
|
||||
message.error(err?.message || '添加失败');
|
||||
@@ -893,9 +909,16 @@ defineExpose({
|
||||
<Spin :spinning="loading">
|
||||
<CommonDxOrderChips
|
||||
v-if="showBubbleChips"
|
||||
:key="`bubble_chips_${source}_${bubbleChipsTick}`"
|
||||
:key="`bubble_chips_${source}_${fieldCode}_${bubbleChipsTick}`"
|
||||
class="entry-keyword-bubble__chips"
|
||||
:source="source === 'diagnosis' ? 'diagnosis' : 'doctor_order'"
|
||||
:source="
|
||||
source === 'diagnosis'
|
||||
? 'diagnosis'
|
||||
: source === 'doctor_order'
|
||||
? 'doctor_order'
|
||||
: 'entry'
|
||||
"
|
||||
:field-code="fieldCode"
|
||||
:disabled="disabled"
|
||||
@select="
|
||||
(text) =>
|
||||
|
||||
@@ -99,25 +99,63 @@ function appendCommaText(base: string, add: string) {
|
||||
return parts.join(',');
|
||||
}
|
||||
|
||||
function removeCommaText(base: string, remove: string) {
|
||||
if (!base || !remove) return base || '';
|
||||
return (base || '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s && s !== remove)
|
||||
.join(',');
|
||||
}
|
||||
|
||||
function appendNewline(base: string, add: string) {
|
||||
if (!add) return base || '';
|
||||
if (!base) return add;
|
||||
return `${base}${base.endsWith('\n') ? '' : '\n'}${add}`;
|
||||
}
|
||||
|
||||
/** 气泡/快捷写入草稿 */
|
||||
function removeNewline(base: string, remove: string) {
|
||||
if (!base || !remove) return base || '';
|
||||
const lines = base.split('\n');
|
||||
const idx = lines.findIndex((l) => l.trim() === remove.trim());
|
||||
if (idx >= 0) {
|
||||
lines.splice(idx, 1);
|
||||
return lines.join('\n');
|
||||
}
|
||||
const i = base.indexOf(remove);
|
||||
if (i < 0) return base;
|
||||
return `${base.slice(0, i)}${base.slice(i + remove.length)}`
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.replace(/^\n+|\n+$/g, '');
|
||||
}
|
||||
|
||||
function containsText(base: string, text: string) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t) return false;
|
||||
const v = String(base || '');
|
||||
if (!v) return false;
|
||||
if (v.split(',').map((s) => s.trim()).includes(t)) return true;
|
||||
if (v.split('\n').map((s) => s.trim()).includes(t)) return true;
|
||||
return v.includes(t);
|
||||
}
|
||||
|
||||
/** 气泡/快捷写入草稿(已有则删除,实现选择/取消) */
|
||||
function applyPick(text: string) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t || readonly.value) return;
|
||||
if (joinMode.value === 'replace') {
|
||||
draft.value = t;
|
||||
draft.value = String(draft.value || '').trim() === t ? '' : t;
|
||||
return;
|
||||
}
|
||||
if (joinMode.value === 'comma') {
|
||||
draft.value = appendCommaText(draft.value, t);
|
||||
draft.value = containsText(draft.value, t)
|
||||
? removeCommaText(draft.value, t)
|
||||
: appendCommaText(draft.value, t);
|
||||
return;
|
||||
}
|
||||
draft.value = appendNewline(draft.value, t);
|
||||
draft.value = containsText(draft.value, t)
|
||||
? removeNewline(draft.value, t)
|
||||
: appendNewline(draft.value, t);
|
||||
}
|
||||
|
||||
function onBubbleSelect(item: MedicalRecordEntryItem) {
|
||||
@@ -163,8 +201,21 @@ function confirmAndClose() {
|
||||
@dblclick="confirmAndClose"
|
||||
/>
|
||||
<CommonDxOrderChips
|
||||
v-if="showChips && (source === 'diagnosis' || source === 'doctor_order')"
|
||||
:source="source"
|
||||
v-if="
|
||||
showChips &&
|
||||
(source === 'diagnosis' ||
|
||||
source === 'doctor_order' ||
|
||||
source === 'entry')
|
||||
"
|
||||
:source="
|
||||
source === 'diagnosis'
|
||||
? 'diagnosis'
|
||||
: source === 'doctor_order'
|
||||
? 'doctor_order'
|
||||
: 'entry'
|
||||
"
|
||||
:field-code="fieldCode"
|
||||
:field-value="draft"
|
||||
:disabled="readonly"
|
||||
@select="applyPick"
|
||||
/>
|
||||
|
||||
@@ -45,6 +45,11 @@ import {
|
||||
saveServicePriceApi,
|
||||
} from '#/views/doctor/settings/api';
|
||||
import type { CommonPrescriptionResponse } from '#/views/doctor/settings/api';
|
||||
import {
|
||||
deleteMyMedicalRecordEntry,
|
||||
getMyMedicalRecordEntryList,
|
||||
} from '#/views/doctor/medical-record/api';
|
||||
import { MEDICAL_RECORD_FIELD_OPTIONS } from '#/views/doctor/medical-record/config/constants';
|
||||
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
|
||||
|
||||
const userInfoStore = useUserStore();
|
||||
@@ -63,6 +68,7 @@ const activeTabBar = ref(savedTab ? Number(savedTab) : 1);
|
||||
* - 3: 常用方(新增)
|
||||
* - 4: 常用医嘱
|
||||
* - 5: 常用诊断
|
||||
* - 6: 常用词条
|
||||
*/
|
||||
const tabBar = [
|
||||
{
|
||||
@@ -81,6 +87,10 @@ const tabBar = [
|
||||
value: 5,
|
||||
label: '常用诊断',
|
||||
},
|
||||
{
|
||||
value: 6,
|
||||
label: '常用词条',
|
||||
},
|
||||
];
|
||||
|
||||
// 格式化日期
|
||||
@@ -127,6 +137,28 @@ const getDoctorMyDiseaseList = () => {
|
||||
});
|
||||
};
|
||||
|
||||
/** 常用词条(医生维度) */
|
||||
const myEntryCommonList = ref<any[]>([]);
|
||||
const getMyEntryCommonList = () => {
|
||||
getMyMedicalRecordEntryList({ limit: 200 })
|
||||
.then((res) => {
|
||||
myEntryCommonList.value = Array.isArray(res) ? res : [];
|
||||
})
|
||||
.catch(() => {
|
||||
myEntryCommonList.value = [];
|
||||
});
|
||||
};
|
||||
const fieldCodeLabel = (code: string) => {
|
||||
const hit = MEDICAL_RECORD_FIELD_OPTIONS.find((o) => o.value === code);
|
||||
return hit?.label || code || '-';
|
||||
};
|
||||
const removeMyEntryCommon = (id: number) => {
|
||||
deleteMyMedicalRecordEntry(id).then(() => {
|
||||
notification.success({ message: '已移除常用词条' });
|
||||
getMyEntryCommonList();
|
||||
});
|
||||
};
|
||||
|
||||
// ==================== 常用方相关 ====================
|
||||
|
||||
/**
|
||||
@@ -282,6 +314,11 @@ const tabBarChange = () => {
|
||||
getDoctorMyDiseaseList();
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
// 常用词条
|
||||
getMyEntryCommonList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
// 初始化时加载基础数据和当前tab对应的数据
|
||||
@@ -807,6 +844,29 @@ const handleViewSignature = () => {
|
||||
{{ item.disease.name }}
|
||||
</Button>
|
||||
</Card>
|
||||
<Card v-else-if="data && activeTabBar === 6" title="常用词条">
|
||||
<div class="mb-3 text-sm text-gray-500">
|
||||
在病历接诊气泡中点「加常用」可收藏;此处可移除。点击标签本身无操作。
|
||||
</div>
|
||||
<Empty v-if="!myEntryCommonList.length" description="暂无常用词条" />
|
||||
<div v-else class="flex flex-wrap gap-2">
|
||||
<Tag
|
||||
v-for="item in myEntryCommonList"
|
||||
:key="item.id"
|
||||
color="processing"
|
||||
class="mb-2"
|
||||
>
|
||||
<span class="mr-1 text-xs opacity-70">{{ fieldCodeLabel(item.field_code) }}</span>
|
||||
{{ item.title || item.content }}
|
||||
<Popconfirm
|
||||
title="确定从常用词条中移除吗?"
|
||||
@confirm="removeMyEntryCommon(item.id)"
|
||||
>
|
||||
<a class="ml-2 text-red-500" @click.stop>移除</a>
|
||||
</Popconfirm>
|
||||
</Tag>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- 常用方编辑弹窗 -->
|
||||
<EditCommonPrescriptionModals />
|
||||
|
||||
@@ -37,6 +37,13 @@ export const gridOptions: VxeGridProps<RowType> = {
|
||||
width: 160,
|
||||
slots: { default: 'online_consultation_config' },
|
||||
},
|
||||
{
|
||||
field: 'prescription_types_config',
|
||||
align: 'left',
|
||||
title: '开方种类',
|
||||
width: 200,
|
||||
slots: { default: 'prescription_types_config' },
|
||||
},
|
||||
{
|
||||
field: 'store_config_1',
|
||||
align: 'left',
|
||||
|
||||
@@ -35,6 +35,7 @@ import StoreBasicInfoCell from '#/views/system/store/components/cells/StoreBasic
|
||||
import StoreConfigTogglesCell from '#/views/system/store/components/cells/StoreConfigTogglesCell.vue';
|
||||
// 导入绑定在线复诊配置弹窗(使用store的组件)
|
||||
import BindConsultationModal from '#/views/system/store/components/BindConsultationModal.vue';
|
||||
import BindPrescriptionTypesModal from '#/views/system/store/components/BindPrescriptionTypesModal.vue';
|
||||
import DrugPriceModal from '#/views/system/store/components/DrugPriceModal.vue';
|
||||
import StoreExternalFieldModal from '#/views/system/store/components/StoreExternalFieldModal.vue';
|
||||
import StoreCardModal from '#/components/store-card/StoreCardModal.vue';
|
||||
@@ -46,6 +47,7 @@ import { getStoreBankCardReportDetail } from '#/views/system/store-bank-card/api
|
||||
import { formOptions } from './config/search';
|
||||
// 导入表格配置
|
||||
import { gridOptions } from './config/table';
|
||||
import { prescriptionTypeLabel } from '#/views/system/store/config/prescription-type-constants';
|
||||
import { canManageStoreBankCard } from '#/views/system/admin/_shared/platform-admin-role';
|
||||
|
||||
const userStore = useUserStore();
|
||||
@@ -128,6 +130,10 @@ const [BindConsultationModalComponent, bindConsultationModalApi] = useVbenModal(
|
||||
connectedComponent: BindConsultationModal,
|
||||
});
|
||||
|
||||
const [BindPrescriptionTypesModalComponent, bindPrescriptionTypesModalApi] = useVbenModal({
|
||||
connectedComponent: BindPrescriptionTypesModal,
|
||||
});
|
||||
|
||||
const [StoreExternalFieldModalComponent, storeExternalFieldModalApi] = useVbenModal({
|
||||
connectedComponent: StoreExternalFieldModal,
|
||||
});
|
||||
@@ -247,6 +253,26 @@ const showBindConsultationModal = (row: any) => {
|
||||
bindConsultationModalApi.open();
|
||||
};
|
||||
|
||||
/** 打开绑定开方种类弹窗 */
|
||||
const showBindPrescriptionTypesModal = (row: any) => {
|
||||
bindPrescriptionTypesModalApi.setData({
|
||||
values: row,
|
||||
gridApi,
|
||||
});
|
||||
bindPrescriptionTypesModalApi.open();
|
||||
};
|
||||
|
||||
/** 列表摘要:已绑种类数 + 默认种类名 */
|
||||
function prescriptionTypesSummary(row: any) {
|
||||
const allowed = Array.isArray(row?.allowed_prescription_types)
|
||||
? row.allowed_prescription_types
|
||||
: [];
|
||||
if (!allowed.length) return '未绑定';
|
||||
const def = Number(row?.default_prescription_type) || 0;
|
||||
const defLabel = def ? prescriptionTypeLabel(def) : '';
|
||||
return defLabel ? `${allowed.length}种 · 默认${defLabel}` : `${allowed.length}种`;
|
||||
}
|
||||
|
||||
// 显示表单弹窗(新增或编辑)
|
||||
const showModal = (data = {}, isUpdate = false) => {
|
||||
formModalApi.setData({
|
||||
@@ -391,6 +417,7 @@ const batchSyncDrugPrice = () => {
|
||||
<QrCodePreviewModal />
|
||||
<DrugPriceModalComponent />
|
||||
<BindConsultationModalComponent />
|
||||
<BindPrescriptionTypesModalComponent />
|
||||
<StoreExternalFieldModalComponent />
|
||||
<Grid>
|
||||
<template #toolbar-buttons>
|
||||
@@ -532,6 +559,23 @@ const batchSyncDrugPrice = () => {
|
||||
绑定
|
||||
</Button>
|
||||
</template>
|
||||
<template #prescription_types_config="{ row }">
|
||||
<div
|
||||
class="cursor-pointer hover:text-blue-500"
|
||||
@click="showBindPrescriptionTypesModal(row)"
|
||||
>
|
||||
<Tag
|
||||
:color="
|
||||
Array.isArray(row.allowed_prescription_types) &&
|
||||
row.allowed_prescription_types.length
|
||||
? 'success'
|
||||
: 'default'
|
||||
"
|
||||
>
|
||||
{{ prescriptionTypesSummary(row) }}
|
||||
</Tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #start-time="{ row }">
|
||||
早:<Tag color="success">{{ row.start_time }}</Tag>
|
||||
<br />
|
||||
|
||||
@@ -32,6 +32,17 @@ export async function bindOnlineConsultationApi(data: {
|
||||
return requestClient.post<any>(`${prefix}bind-online-consultation`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定门店可开方药品种类与默认种类
|
||||
*/
|
||||
export async function bindPrescriptionTypesApi(data: {
|
||||
id: number;
|
||||
allowed_prescription_types: number[];
|
||||
default_prescription_type: number;
|
||||
}) {
|
||||
return requestClient.post<any>(`${prefix}bind-prescription-types`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定特色方默认挂号医生
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 绑定门店可开方药品种类与默认种类
|
||||
* 为什么独立弹窗:诊所/药店列表共用,整表替换中间表绑定
|
||||
*/
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Checkbox, CheckboxGroup, Form, FormItem, message, Radio, RadioGroup } from 'ant-design-vue';
|
||||
|
||||
import { bindPrescriptionTypesApi } from '../api';
|
||||
import {
|
||||
STORE_PRESCRIPTION_TYPE_OPTIONS,
|
||||
} from '../config/prescription-type-constants';
|
||||
|
||||
const formState = ref({
|
||||
id: undefined as number | undefined,
|
||||
allowed_prescription_types: [] as number[],
|
||||
default_prescription_type: undefined as number | undefined,
|
||||
});
|
||||
|
||||
const gridApiRef = ref<any>(null);
|
||||
const loading = ref(false);
|
||||
|
||||
/** 默认种类只能从已勾选里选 */
|
||||
const defaultOptions = computed(() =>
|
||||
STORE_PRESCRIPTION_TYPE_OPTIONS.filter((o) =>
|
||||
formState.value.allowed_prescription_types.includes(o.value),
|
||||
),
|
||||
);
|
||||
|
||||
function onAllowedChange(values: number[]) {
|
||||
const next = (values || []) as number[];
|
||||
formState.value.allowed_prescription_types = next;
|
||||
// 默认种类若不在已选中,自动落到第一项
|
||||
if (
|
||||
formState.value.default_prescription_type &&
|
||||
!next.includes(formState.value.default_prescription_type)
|
||||
) {
|
||||
formState.value.default_prescription_type = next[0];
|
||||
}
|
||||
if (!formState.value.default_prescription_type && next.length) {
|
||||
formState.value.default_prescription_type = next[0];
|
||||
}
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
draggable: true,
|
||||
onCancel() {
|
||||
modalApi.close();
|
||||
},
|
||||
onConfirm: async () => {
|
||||
if (!formState.value.id) {
|
||||
message.error('门店ID无效');
|
||||
return;
|
||||
}
|
||||
if (!formState.value.allowed_prescription_types.length) {
|
||||
message.error('请至少选择一种可开方种类');
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!formState.value.default_prescription_type ||
|
||||
!formState.value.allowed_prescription_types.includes(
|
||||
formState.value.default_prescription_type,
|
||||
)
|
||||
) {
|
||||
message.error('请选择默认开方种类');
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
modalApi.setState({ confirmLoading: true });
|
||||
try {
|
||||
await bindPrescriptionTypesApi({
|
||||
id: formState.value.id,
|
||||
allowed_prescription_types: formState.value.allowed_prescription_types,
|
||||
default_prescription_type: formState.value.default_prescription_type,
|
||||
});
|
||||
message.success('绑定成功');
|
||||
gridApiRef.value?.reload();
|
||||
modalApi.close();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
message.error('绑定失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
modalApi.setState({ confirmLoading: false });
|
||||
}
|
||||
},
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) return;
|
||||
const { values, gridApi } = modalApi.getData<Record<string, any>>();
|
||||
gridApiRef.value = gridApi;
|
||||
const allowed = Array.isArray(values?.allowed_prescription_types)
|
||||
? values.allowed_prescription_types.map((n: any) => Number(n))
|
||||
: [];
|
||||
let def = Number(values?.default_prescription_type) || 0;
|
||||
if (def && !allowed.includes(def)) {
|
||||
def = allowed[0] || 0;
|
||||
}
|
||||
formState.value = {
|
||||
id: values?.id,
|
||||
allowed_prescription_types: allowed,
|
||||
default_prescription_type: def || undefined,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal title="绑定开方种类" class="w-[520px]">
|
||||
<Form :model="formState" layout="vertical">
|
||||
<FormItem label="可开方种类" required>
|
||||
<CheckboxGroup
|
||||
v-model:value="formState.allowed_prescription_types"
|
||||
@change="onAllowedChange"
|
||||
>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Checkbox
|
||||
v-for="opt in STORE_PRESCRIPTION_TYPE_OPTIONS"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</CheckboxGroup>
|
||||
</FormItem>
|
||||
<FormItem label="进入开方页默认种类" required>
|
||||
<RadioGroup
|
||||
v-model:value="formState.default_prescription_type"
|
||||
:disabled="!defaultOptions.length"
|
||||
>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Radio
|
||||
v-for="opt in defaultOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</Radio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<div v-if="!defaultOptions.length" class="mt-1 text-gray-400 text-sm">
|
||||
请先勾选可开方种类
|
||||
</div>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 门店开方种类选项(与 ProductTypeEnum 开方 Tab 对齐,不含特色方)
|
||||
*/
|
||||
export const STORE_PRESCRIPTION_TYPE_OPTIONS = [
|
||||
{ label: '中药', value: 1 },
|
||||
{ label: '西(中成)药', value: 2 },
|
||||
{ label: '保健食品', value: 3 },
|
||||
{ label: '产品服务包', value: 5 },
|
||||
{ label: '非药品', value: 6 },
|
||||
{ label: '医疗器械', value: 7 },
|
||||
];
|
||||
|
||||
/** 按 value 取中文名 */
|
||||
export function prescriptionTypeLabel(value: number): string {
|
||||
const hit = STORE_PRESCRIPTION_TYPE_OPTIONS.find((o) => o.value === Number(value));
|
||||
return hit?.label || `类型${value}`;
|
||||
}
|
||||
@@ -51,6 +51,13 @@ export const gridOptions: VxeGridProps<RowType> = {
|
||||
width: 180,
|
||||
slots: { default: 'special_prescription_config' },
|
||||
},
|
||||
{
|
||||
field: 'prescription_types_config',
|
||||
align: 'left',
|
||||
title: '开方种类',
|
||||
width: 200,
|
||||
slots: { default: 'prescription_types_config' },
|
||||
},
|
||||
{
|
||||
field: 'store_config_1',
|
||||
align: 'left',
|
||||
|
||||
@@ -37,6 +37,7 @@ import StoreConfigTogglesCell from './components/cells/StoreConfigTogglesCell.vu
|
||||
import DrugPriceModal from './components/DrugPriceModal.vue';
|
||||
import BindConsultationModal from './components/BindConsultationModal.vue';
|
||||
import BindSpecialPrescriptionDoctorModal from './components/BindSpecialPrescriptionDoctorModal.vue';
|
||||
import BindPrescriptionTypesModal from './components/BindPrescriptionTypesModal.vue';
|
||||
import StoreExternalFieldModal from './components/StoreExternalFieldModal.vue';
|
||||
import SalespersonCommissionDrawer from './components/SalespersonCommissionDrawer.vue';
|
||||
import AddDoctorFromStoreModal from './components/AddDoctorFromStoreModal.vue';
|
||||
@@ -49,6 +50,7 @@ import BankCardStoreEditModal from '#/views/system/store-bank-card/components/Ba
|
||||
import { getStoreBankCardReportDetail } from '#/views/system/store-bank-card/api';
|
||||
import { formOptions } from './config/search';
|
||||
import { gridOptions } from './config/table';
|
||||
import { prescriptionTypeLabel } from './config/prescription-type-constants';
|
||||
import {
|
||||
canAddStoreDoctor,
|
||||
canManageStoreBankCard,
|
||||
@@ -174,6 +176,10 @@ const [BindSpecialPrescriptionDoctorModalComponent, bindSpecialPrescriptionDocto
|
||||
connectedComponent: BindSpecialPrescriptionDoctorModal,
|
||||
});
|
||||
|
||||
const [BindPrescriptionTypesModalComponent, bindPrescriptionTypesModalApi] = useVbenModal({
|
||||
connectedComponent: BindPrescriptionTypesModal,
|
||||
});
|
||||
|
||||
const [StoreExternalFieldModalComponent, storeExternalFieldModalApi] = useVbenModal({
|
||||
connectedComponent: StoreExternalFieldModal,
|
||||
});
|
||||
@@ -310,6 +316,26 @@ const showBindSpecialPrescriptionDoctorModal = (row: any) => {
|
||||
bindSpecialPrescriptionDoctorModalApi.open();
|
||||
};
|
||||
|
||||
/** 打开绑定开方种类弹窗 */
|
||||
const showBindPrescriptionTypesModal = (row: any) => {
|
||||
bindPrescriptionTypesModalApi.setData({
|
||||
values: row,
|
||||
gridApi,
|
||||
});
|
||||
bindPrescriptionTypesModalApi.open();
|
||||
};
|
||||
|
||||
/** 列表摘要:已绑种类数 + 默认种类名 */
|
||||
function prescriptionTypesSummary(row: any) {
|
||||
const allowed = Array.isArray(row?.allowed_prescription_types)
|
||||
? row.allowed_prescription_types
|
||||
: [];
|
||||
if (!allowed.length) return '未绑定';
|
||||
const def = Number(row?.default_prescription_type) || 0;
|
||||
const defLabel = def ? prescriptionTypeLabel(def) : '';
|
||||
return defLabel ? `${allowed.length}种 · 默认${defLabel}` : `${allowed.length}种`;
|
||||
}
|
||||
|
||||
const deleteApi = (row: any) => {
|
||||
let ids = [];
|
||||
if (row) {
|
||||
@@ -468,6 +494,7 @@ const handleSwitchClinicType = (row: any) => {
|
||||
<DrugPriceModalComponent />
|
||||
<BindConsultationModalComponent />
|
||||
<BindSpecialPrescriptionDoctorModalComponent />
|
||||
<BindPrescriptionTypesModalComponent />
|
||||
<StoreExternalFieldModalComponent />
|
||||
<SalespersonCommissionDrawer ref="salespersonDrawerRef" />
|
||||
<Grid>
|
||||
@@ -637,6 +664,23 @@ const handleSwitchClinicType = (row: any) => {
|
||||
绑定
|
||||
</Button>
|
||||
</template>
|
||||
<template #prescription_types_config="{ row }">
|
||||
<div
|
||||
class="cursor-pointer hover:text-blue-500"
|
||||
@click="showBindPrescriptionTypesModal(row)"
|
||||
>
|
||||
<Tag
|
||||
:color="
|
||||
Array.isArray(row.allowed_prescription_types) &&
|
||||
row.allowed_prescription_types.length
|
||||
? 'success'
|
||||
: 'default'
|
||||
"
|
||||
>
|
||||
{{ prescriptionTypesSummary(row) }}
|
||||
</Tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #start-time="{ row }">
|
||||
早:<Tag color="success">{{ row.start_time }}</Tag>
|
||||
<br />
|
||||
|
||||
Reference in New Issue
Block a user