feat: 病历模块优化

This commit is contained in:
李琦
2026-08-05 16:22:46 +08:00
parent 029b45d0bb
commit bd5b0b2de1
17 changed files with 971 additions and 140 deletions

View File

@@ -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',

View File

@@ -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 />

View File

@@ -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);
}
/**
* 绑定特色方默认挂号医生
*/

View File

@@ -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>

View File

@@ -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}`;
}

View File

@@ -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',

View File

@@ -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 />