1. 特色方功能迭代
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
This commit is contained in:
@@ -33,3 +33,18 @@ export async function deleteSpecialPrescription(data: Record<string, any>) {
|
||||
export async function applySpecialPrescriptionApi(data: { register_id: number }) {
|
||||
return requestClient.post<any>(`${prefix}apply`, data);
|
||||
}
|
||||
|
||||
/** 自动均分药品单价 */
|
||||
export async function distributeDrugPricesApi(data: Record<string, any>) {
|
||||
return requestClient.post<any>(`${prefix}distribute-drug-prices`, data);
|
||||
}
|
||||
|
||||
/** 价格配置详情(路由与 cc_auto_route_register 生成的 price-config-detail 对齐) */
|
||||
export async function getSpecialPrescriptionPriceConfig(id: number) {
|
||||
return requestClient.get<any>(`${prefix}price-config-detail`, { params: { id } });
|
||||
}
|
||||
|
||||
/** 保存价格配置 */
|
||||
export async function saveSpecialPrescriptionPriceConfig(data: Record<string, any>) {
|
||||
return requestClient.post<any>(`${prefix}save-price-config`, data);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, ref } from 'vue';
|
||||
import { computed, nextTick, onMounted, ref } from 'vue';
|
||||
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
getProductListDoctorReception,
|
||||
} from '#/views/doctor/doctor-reception/api';
|
||||
|
||||
import PrescriptionDiagnosisOrderCards from './PrescriptionDiagnosisOrderCards.vue';
|
||||
|
||||
export interface ChineseDrugItem {
|
||||
_key?: number;
|
||||
id: number;
|
||||
@@ -29,6 +31,7 @@ export interface ChineseDrugItem {
|
||||
drug_name: string;
|
||||
number: number;
|
||||
price: number;
|
||||
buy_price?: number;
|
||||
way_id: number;
|
||||
}
|
||||
|
||||
@@ -37,11 +40,26 @@ const props = withDefaults(
|
||||
modelValue?: ChineseDrugItem[];
|
||||
dosage?: number;
|
||||
dayDosage?: number;
|
||||
/** 固定价/SKU方案下允许编辑单价 */
|
||||
priceEditable?: boolean;
|
||||
/** 价格配置弹窗:药名/克数/用法只读,仅价格可改 */
|
||||
structureReadonly?: boolean;
|
||||
/** 预选诊断(导入特色方时带入) */
|
||||
clinicalDiagnose?: string;
|
||||
/** 预选医嘱 */
|
||||
doctorOrder?: string;
|
||||
/** 是否展示诊断/医嘱气泡卡(价格配置等场景可关闭) */
|
||||
showDiagnosisOrder?: boolean;
|
||||
}>(),
|
||||
{
|
||||
modelValue: () => [],
|
||||
dosage: 7,
|
||||
dayDosage: 2,
|
||||
priceEditable: false,
|
||||
structureReadonly: false,
|
||||
clinicalDiagnose: '',
|
||||
doctorOrder: '',
|
||||
showDiagnosisOrder: true,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -49,12 +67,33 @@ const emit = defineEmits<{
|
||||
'update:modelValue': [value: ChineseDrugItem[]];
|
||||
'update:dosage': [value: number];
|
||||
'update:dayDosage': [value: number];
|
||||
'update:clinicalDiagnose': [value: string];
|
||||
'update:doctorOrder': [value: string];
|
||||
}>();
|
||||
|
||||
const userStore = useUserStore();
|
||||
const drugList = ref<ChineseDrugItem[]>([]);
|
||||
const dosageLocal = ref(props.dosage);
|
||||
const dayDosageLocal = ref(props.dayDosage);
|
||||
const diagnosisOrderCardsRef = ref<InstanceType<typeof PrescriptionDiagnosisOrderCards>>();
|
||||
|
||||
/** 价格配置只读模式下不展示诊断/医嘱区 */
|
||||
const showDiagnosisOrderPanel = computed(
|
||||
() => props.showDiagnosisOrder && !props.structureReadonly,
|
||||
);
|
||||
|
||||
/** 预选诊断双向绑定 */
|
||||
const clinicalDiagnoseModel = computed({
|
||||
get: () => props.clinicalDiagnose ?? '',
|
||||
set: (val: string) => emit('update:clinicalDiagnose', val),
|
||||
});
|
||||
|
||||
/** 预选医嘱双向绑定 */
|
||||
const doctorOrderModel = computed({
|
||||
get: () => props.doctorOrder ?? '',
|
||||
set: (val: string) => emit('update:doctorOrder', val),
|
||||
});
|
||||
|
||||
const drugUseWay = ref<any[]>([]);
|
||||
const chineseSearchResults = ref<any[]>([]);
|
||||
const newDrugInfo = ref<{
|
||||
@@ -75,6 +114,12 @@ onMounted(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
function getWayName(wayId: number) {
|
||||
if (!wayId) return '煎服';
|
||||
const item = drugUseWay.value.find((w) => Number(w.id) === Number(wayId));
|
||||
return item?.name || '煎服';
|
||||
}
|
||||
|
||||
function syncToParent() {
|
||||
emit('update:modelValue', [...drugList.value]);
|
||||
emit('update:dosage', dosageLocal.value);
|
||||
@@ -200,21 +245,66 @@ function onDosageChange() {
|
||||
syncToParent();
|
||||
}
|
||||
|
||||
function loadDrugs(recipes: any[], dosage?: number, dayDosage?: number) {
|
||||
drugList.value = (recipes || []).map((recipe) => ({
|
||||
_key: Date.now() + Math.random(),
|
||||
id: recipe.drug_id || recipe.id,
|
||||
drug_id: recipe.drug_id || recipe.id,
|
||||
drug_name: recipe.drug_name || recipe.name || '',
|
||||
number: recipe.number || 1,
|
||||
price: recipe.price || 0,
|
||||
way_id: recipe.way_id || 0,
|
||||
}));
|
||||
function loadDrugs(recipes: any[], dosage?: number, dayDosage?: number, drugPrices?: any[]) {
|
||||
const priceMap = new Map<number, { buy_price: number; sell_price: number }>();
|
||||
(drugPrices || []).forEach((item) => {
|
||||
priceMap.set(Number(item.drug_id), {
|
||||
buy_price: Number(item.buy_price || 0),
|
||||
sell_price: Number(item.sell_price || item.price || 0),
|
||||
});
|
||||
});
|
||||
drugList.value = (recipes || []).map((recipe) => {
|
||||
const drugId = recipe.drug_id || recipe.id;
|
||||
const configured = priceMap.get(Number(drugId));
|
||||
return {
|
||||
_key: Date.now() + Math.random(),
|
||||
id: drugId,
|
||||
drug_id: drugId,
|
||||
drug_name: recipe.drug_name || recipe.name || '',
|
||||
number: recipe.number || 1,
|
||||
price: configured?.sell_price ?? recipe.price ?? 0,
|
||||
buy_price: configured?.buy_price ?? recipe.buy_price ?? 0,
|
||||
way_id: recipe.way_id || 0,
|
||||
};
|
||||
});
|
||||
if (dosage !== undefined) dosageLocal.value = dosage;
|
||||
if (dayDosage !== undefined) dayDosageLocal.value = dayDosage;
|
||||
syncToParent();
|
||||
}
|
||||
|
||||
function applyDistributedPrices(items: Array<{ drug_id: number; buy_price: number | string; sell_price: number | string }>) {
|
||||
const priceMap = new Map<number, { buy_price: number; sell_price: number }>();
|
||||
items.forEach((item) => {
|
||||
priceMap.set(Number(item.drug_id), {
|
||||
buy_price: Number(item.buy_price || 0),
|
||||
sell_price: Number(item.sell_price || 0),
|
||||
});
|
||||
});
|
||||
drugList.value.forEach((drug) => {
|
||||
const configured = priceMap.get(Number(drug.drug_id || drug.id));
|
||||
if (configured) {
|
||||
drug.price = configured.sell_price;
|
||||
drug.buy_price = configured.buy_price;
|
||||
}
|
||||
});
|
||||
syncToParent();
|
||||
}
|
||||
|
||||
function getDrugPricesPayload() {
|
||||
return drugList.value.map((drug) => ({
|
||||
drug_id: drug.drug_id || drug.id,
|
||||
buy_price: drug.buy_price ?? 0,
|
||||
sell_price: drug.price ?? 0,
|
||||
}));
|
||||
}
|
||||
|
||||
function getDrugsForDistribute() {
|
||||
return drugList.value.map((drug) => ({
|
||||
drug_id: drug.drug_id || drug.id,
|
||||
number: drug.number || 1,
|
||||
}));
|
||||
}
|
||||
|
||||
function getDrugsPayload() {
|
||||
return drugList.value.map((drug) => ({
|
||||
drug_id: drug.drug_id || drug.id,
|
||||
@@ -227,14 +317,22 @@ function getDrugsPayload() {
|
||||
|
||||
defineExpose({
|
||||
getDrugsPayload,
|
||||
getDrugPricesPayload,
|
||||
getDrugsForDistribute,
|
||||
applyDistributedPrices,
|
||||
loadDrugs,
|
||||
drugList,
|
||||
getDiagnosisOrderPayload: () =>
|
||||
diagnosisOrderCardsRef.value?.getDiagnosisOrderPayload?.() ?? {
|
||||
clinical_diagnose: props.clinicalDiagnose || '',
|
||||
doctor_order: props.doctorOrder || '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="chinese-drug-editor">
|
||||
<Row :gutter="16" class="mb-4">
|
||||
<Row v-if="!structureReadonly" :gutter="16" class="mb-4">
|
||||
<Col :span="12">
|
||||
<FormItem label="剂量(天/剂)">
|
||||
<InputNumber
|
||||
@@ -258,8 +356,17 @@ defineExpose({
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
<div v-else-if="dosageLocal || dayDosageLocal" class="readonly-meta mb-3 text-gray-500">
|
||||
剂量 {{ dosageLocal }} 天/剂 · 频次 {{ dayDosageLocal }} 次/天
|
||||
</div>
|
||||
|
||||
<FormItem label="药品列表" required>
|
||||
<FormItem :label="structureReadonly ? '' : '药品列表'" :required="!structureReadonly">
|
||||
<PrescriptionDiagnosisOrderCards
|
||||
v-if="showDiagnosisOrderPanel"
|
||||
ref="diagnosisOrderCardsRef"
|
||||
v-model:clinical-diagnose="clinicalDiagnoseModel"
|
||||
v-model:doctor-order="doctorOrderModel"
|
||||
/>
|
||||
<Row :gutter="[12, 12]">
|
||||
<Col
|
||||
v-for="(drug, index) in drugList"
|
||||
@@ -272,7 +379,14 @@ defineExpose({
|
||||
<Card size="small" class="chinese-drug-card">
|
||||
<div class="chinese-drug-content">
|
||||
<span class="chinese-drug-index">{{ index + 1 }}、</span>
|
||||
<div class="chinese-drug-form">
|
||||
<div v-if="structureReadonly" class="chinese-drug-readonly">
|
||||
<span class="chinese-drug-name-text">{{ drug.drug_name }}</span>
|
||||
<span class="chinese-drug-comma">,</span>
|
||||
<span>{{ drug.number }}g</span>
|
||||
<span class="chinese-drug-comma">,</span>
|
||||
<span>{{ getWayName(drug.way_id) }}</span>
|
||||
</div>
|
||||
<div v-else class="chinese-drug-form">
|
||||
<Select
|
||||
v-model:value="drug.id"
|
||||
show-search
|
||||
@@ -322,16 +436,50 @@ defineExpose({
|
||||
</Select>
|
||||
</div>
|
||||
<div class="chinese-drug-price">
|
||||
¥{{ ((drug.price || 0) * (drug.number || 1)).toFixed(2) }}
|
||||
<template v-if="priceEditable">
|
||||
<div class="price-edit-row">
|
||||
<span>售</span>
|
||||
<InputNumber
|
||||
v-model:value="drug.price"
|
||||
:min="0"
|
||||
:precision="4"
|
||||
:controls="false"
|
||||
size="small"
|
||||
style="width: 72px"
|
||||
@change="onDrugNumberChange"
|
||||
/>
|
||||
<span>供</span>
|
||||
<InputNumber
|
||||
v-model:value="drug.buy_price"
|
||||
:min="0"
|
||||
:precision="4"
|
||||
:controls="false"
|
||||
size="small"
|
||||
style="width: 72px"
|
||||
@change="onDrugNumberChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="price-line-total">
|
||||
小计 ¥{{ ((drug.price || 0) * (drug.number || 1)).toFixed(2) }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
¥{{ ((drug.price || 0) * (drug.number || 1)).toFixed(2) }}
|
||||
</template>
|
||||
</div>
|
||||
<Button type="link" class="chinese-drug-delete" @click="removeDrug(index)">
|
||||
<Button
|
||||
v-if="!structureReadonly"
|
||||
type="link"
|
||||
class="chinese-drug-delete"
|
||||
@click="removeDrug(index)"
|
||||
>
|
||||
<DeleteTwoTone two-tone-color="#ff4d4f" />
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col :xs="24" :sm="12" :md="8" :lg="8">
|
||||
<Col v-if="!structureReadonly" :xs="24" :sm="12" :md="8" :lg="8">
|
||||
<Card size="small" class="chinese-drug-card chinese-drug-card-new">
|
||||
<div class="chinese-drug-content">
|
||||
<span class="chinese-drug-index">{{ drugList.length + 1 }}、</span>
|
||||
@@ -427,6 +575,16 @@ defineExpose({
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.chinese-drug-readonly {
|
||||
display: inline;
|
||||
padding-left: 24px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.chinese-drug-name-text {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.chinese-drug-comma {
|
||||
@apply text-muted-foreground;
|
||||
}
|
||||
@@ -440,6 +598,19 @@ defineExpose({
|
||||
right: 24px;
|
||||
bottom: 0;
|
||||
@apply text-destructive font-medium;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.price-edit-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.price-line-total {
|
||||
font-size: 12px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.chinese-drug-delete {
|
||||
|
||||
@@ -18,6 +18,8 @@ const prescriptionType = ref<'chinese' | 'west' | 'granular'>('chinese');
|
||||
const chineseDrugEditorRef = ref<InstanceType<typeof ChineseDrugEditor>>();
|
||||
const dosage = ref(7);
|
||||
const dayDosage = ref(2);
|
||||
const clinicalDiagnose = ref('');
|
||||
const doctorOrder = ref('');
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
fullscreenButton: true,
|
||||
@@ -38,12 +40,19 @@ const [Modal, modalApi] = useVbenModal({
|
||||
message.warning('请至少添加一种中药');
|
||||
return;
|
||||
}
|
||||
const diagnosisOrder =
|
||||
chineseDrugEditorRef.value?.getDiagnosisOrderPayload?.() ?? {
|
||||
clinical_diagnose: clinicalDiagnose.value,
|
||||
doctor_order: doctorOrder.value,
|
||||
};
|
||||
const payload = buildSpecialPrescriptionPayload(detailRes.value, {
|
||||
drugs,
|
||||
dosage: dosage.value,
|
||||
day_dosage: dayDosage.value,
|
||||
rule_type: 1,
|
||||
package_method_id: 2,
|
||||
clinical_diagnose: diagnosisOrder.clinical_diagnose ?? '',
|
||||
doctor_order: diagnosisOrder.doctor_order ?? '',
|
||||
});
|
||||
modalApi.setState({ loading: true, confirmLoading: true });
|
||||
updateSpecialPrescription(payload)
|
||||
@@ -65,12 +74,15 @@ const [Modal, modalApi] = useVbenModal({
|
||||
if (!res) return;
|
||||
detailRes.value = res;
|
||||
prescriptionType.value = res.prescription_type || 'chinese';
|
||||
clinicalDiagnose.value = res.clinical_diagnose || '';
|
||||
doctorOrder.value = res.doctor_order || '';
|
||||
if (res.prescription_type === 'chinese' && res.prescription_detail?.length) {
|
||||
const first = res.prescription_detail[0];
|
||||
chineseDrugEditorRef.value?.loadDrugs(
|
||||
res.prescription_detail,
|
||||
first?.dosage ?? 7,
|
||||
first?.consumption ?? 2,
|
||||
res.drug_prices || [],
|
||||
);
|
||||
dosage.value = first?.dosage ?? 7;
|
||||
dayDosage.value = first?.consumption ?? 2;
|
||||
@@ -80,6 +92,8 @@ const [Modal, modalApi] = useVbenModal({
|
||||
});
|
||||
} else {
|
||||
detailRes.value = null;
|
||||
clinicalDiagnose.value = '';
|
||||
doctorOrder.value = '';
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -90,6 +104,8 @@ const [Modal, modalApi] = useVbenModal({
|
||||
<div v-if="prescriptionType === 'chinese'">
|
||||
<ChineseDrugEditor
|
||||
ref="chineseDrugEditorRef"
|
||||
v-model:clinical-diagnose="clinicalDiagnose"
|
||||
v-model:doctor-order="doctorOrder"
|
||||
v-model:dosage="dosage"
|
||||
v-model:day-dosage="dayDosage"
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Card, Col, Row, Textarea } from 'ant-design-vue';
|
||||
|
||||
import DiagnosisModal from '#/views/doctor/doctor-reception/components/DiagnosisModal.vue';
|
||||
import DoctorOrderModal from '#/views/doctor/doctor-reception/components/DoctorOrderModal.vue';
|
||||
|
||||
/** 预选诊断、预选医嘱(与接诊开方交互一致) */
|
||||
const clinicalDiagnose = defineModel<string>('clinicalDiagnose', { default: '' });
|
||||
const doctorOrder = defineModel<string>('doctorOrder', { default: '' });
|
||||
|
||||
const [DiagnosisModals, DiagnosisModalApi] = useVbenModal({
|
||||
connectedComponent: DiagnosisModal,
|
||||
});
|
||||
|
||||
const [DoctorOrderModals, DoctorOrderModalApi] = useVbenModal({
|
||||
connectedComponent: DoctorOrderModal,
|
||||
});
|
||||
|
||||
/** 打开常用诊断弹窗 */
|
||||
function openDiagnosisModal() {
|
||||
DiagnosisModalApi.setData({
|
||||
values: clinicalDiagnose.value,
|
||||
updateDiagnosis,
|
||||
});
|
||||
DiagnosisModalApi.open();
|
||||
}
|
||||
|
||||
/** 常用诊断弹窗回调:写回预选诊断 */
|
||||
function updateDiagnosis(values: string) {
|
||||
clinicalDiagnose.value = values;
|
||||
}
|
||||
|
||||
/** 打开常用医嘱弹窗 */
|
||||
function openDoctorOrderModal() {
|
||||
DoctorOrderModalApi.setData({
|
||||
values: doctorOrder.value,
|
||||
updateDoctorOrder,
|
||||
});
|
||||
DoctorOrderModalApi.open();
|
||||
}
|
||||
|
||||
/** 常用医嘱弹窗回调:写回预选医嘱 */
|
||||
function updateDoctorOrder(values: string) {
|
||||
doctorOrder.value = values;
|
||||
}
|
||||
|
||||
/** 供父组件读取当前值 */
|
||||
const payload = computed(() => ({
|
||||
clinical_diagnose: clinicalDiagnose.value || '',
|
||||
doctor_order: doctorOrder.value || '',
|
||||
}));
|
||||
|
||||
defineExpose({
|
||||
getDiagnosisOrderPayload: () => payload.value,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Row :gutter="[12, 12]" class="prescription-meta-row mb-3">
|
||||
<Col :xs="24" :md="12">
|
||||
<Card size="small" class="chinese-drug-card prescription-meta-card">
|
||||
<div class="prescription-meta-card__title">预选诊断</div>
|
||||
<Button type="primary" class="mb-2" @click="openDiagnosisModal">
|
||||
常用诊断
|
||||
</Button>
|
||||
<Textarea
|
||||
v-model:value="clinicalDiagnose"
|
||||
placeholder="输入诊断结果..."
|
||||
:rows="3"
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col :xs="24" :md="12">
|
||||
<Card size="small" class="chinese-drug-card prescription-meta-card">
|
||||
<div class="prescription-meta-card__title">预选医嘱</div>
|
||||
<Button type="primary" class="mb-2" @click="openDoctorOrderModal">
|
||||
常用医嘱
|
||||
</Button>
|
||||
<Textarea
|
||||
v-model:value="doctorOrder"
|
||||
placeholder="输入医嘱..."
|
||||
:rows="3"
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<DiagnosisModals />
|
||||
<DoctorOrderModals />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chinese-drug-card {
|
||||
position: relative;
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.prescription-meta-card {
|
||||
min-height: 140px;
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.prescription-meta-card__title {
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.prescription-meta-row :deep(textarea) {
|
||||
width: 100%;
|
||||
min-height: 72px;
|
||||
padding: 8px;
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,339 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Col, InputNumber, message, Radio, RadioGroup, Row } from 'ant-design-vue';
|
||||
|
||||
import ChineseDrugEditor from './ChineseDrugEditor.vue';
|
||||
import SkuEditor, { type SkuItem } from './SkuEditor.vue';
|
||||
import {
|
||||
distributeDrugPricesApi,
|
||||
getSpecialPrescriptionPriceConfig,
|
||||
saveSpecialPrescriptionPriceConfig,
|
||||
} from '../api';
|
||||
|
||||
/** 药品分摊价条目 */
|
||||
interface DrugPriceItem {
|
||||
drug_id: number;
|
||||
buy_price: number;
|
||||
sell_price: number;
|
||||
}
|
||||
|
||||
const gridApi = ref<any>();
|
||||
const configId = ref(0);
|
||||
const configName = ref('');
|
||||
const priceCalcScheme = ref(3);
|
||||
const buyPricePerDose = ref(0);
|
||||
const salePricePerDose = ref(0);
|
||||
const skus = ref<SkuItem[]>([]);
|
||||
const chineseDrugEditorRef = ref<InstanceType<typeof ChineseDrugEditor>>();
|
||||
const skuEditorRef = ref<InstanceType<typeof SkuEditor>>();
|
||||
|
||||
/** 当前分摊视图:0=单帖,>0=SKU id */
|
||||
const activeSkuId = ref(0);
|
||||
/** 各 scope 分摊价缓存 sku_id -> prices[] */
|
||||
const drugPricesCache = ref<Record<number, DrugPriceItem[]>>({});
|
||||
const prescriptionDetailCache = ref<any[]>([]);
|
||||
const prescriptionDosage = ref(7);
|
||||
const prescriptionDayDosage = ref(2);
|
||||
|
||||
const showFixedPriceBlock = computed(() => priceCalcScheme.value === 1);
|
||||
|
||||
/** 左侧标题:单帖或 SKU 名称 */
|
||||
const allocationTitle = computed(() => {
|
||||
if (activeSkuId.value <= 0) {
|
||||
return '单帖均摊价格';
|
||||
}
|
||||
const sku = skus.value.find((item) => Number(item.id) === Number(activeSkuId.value));
|
||||
const name = sku?.sku_name || `${sku?.dose_count || ''}贴`;
|
||||
return `${name} 均摊价格`;
|
||||
});
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
fullscreenButton: true,
|
||||
draggable: true,
|
||||
onCancel() {
|
||||
modalApi.close();
|
||||
},
|
||||
onConfirm: async () => {
|
||||
if (configId.value <= 0) {
|
||||
return;
|
||||
}
|
||||
persistCurrentScopePrices();
|
||||
const payload: Record<string, any> = {
|
||||
id: configId.value,
|
||||
price_calc_scheme: priceCalcScheme.value,
|
||||
skus: skuEditorRef.value?.getSkusPayload() || skus.value,
|
||||
};
|
||||
if (priceCalcScheme.value === 1) {
|
||||
payload.buy_price_per_dose = buyPricePerDose.value;
|
||||
payload.sale_price_per_dose = salePricePerDose.value;
|
||||
payload.drug_prices = drugPricesCache.value[0] || [];
|
||||
const skuDrugPrices: Array<{ sku_id: number; drug_prices: DrugPriceItem[] }> = [];
|
||||
Object.keys(drugPricesCache.value).forEach((key) => {
|
||||
const skuId = Number(key);
|
||||
if (skuId > 0 && drugPricesCache.value[skuId]?.length) {
|
||||
skuDrugPrices.push({
|
||||
sku_id: skuId,
|
||||
drug_prices: drugPricesCache.value[skuId],
|
||||
});
|
||||
}
|
||||
});
|
||||
payload.sku_drug_prices = skuDrugPrices;
|
||||
}
|
||||
modalApi.setState({ loading: true, confirmLoading: true });
|
||||
saveSpecialPrescriptionPriceConfig(payload)
|
||||
.then(() => {
|
||||
message.success('价格配置保存成功');
|
||||
gridApi.value?.reload();
|
||||
modalApi.close();
|
||||
})
|
||||
.finally(() => {
|
||||
modalApi.setState({ loading: false, confirmLoading: false });
|
||||
});
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
const { values, gridApi: grid } = modalApi.getData<Record<string, any>>();
|
||||
gridApi.value = grid;
|
||||
configId.value = values?.id || 0;
|
||||
activeSkuId.value = 0;
|
||||
drugPricesCache.value = {};
|
||||
if (!configId.value) {
|
||||
return;
|
||||
}
|
||||
getSpecialPrescriptionPriceConfig(configId.value).then((res: any) => {
|
||||
if (!res) return;
|
||||
configName.value = res.name || '';
|
||||
priceCalcScheme.value = res.price_calc_scheme ?? 3;
|
||||
buyPricePerDose.value = res.buy_price_per_dose ?? 0;
|
||||
salePricePerDose.value = res.sale_price_per_dose ?? res.price_per_dose ?? 0;
|
||||
skus.value = res.skus || [];
|
||||
skuEditorRef.value?.setSkus(res.skus || []);
|
||||
|
||||
prescriptionDetailCache.value = res.prescription_detail || [];
|
||||
const first = prescriptionDetailCache.value[0];
|
||||
prescriptionDosage.value = first?.dosage ?? 7;
|
||||
prescriptionDayDosage.value = first?.consumption ?? 2;
|
||||
|
||||
drugPricesCache.value[0] = (res.drug_prices || []).map((item: any) => ({
|
||||
drug_id: Number(item.drug_id),
|
||||
buy_price: Number(item.buy_price || 0),
|
||||
sell_price: Number(item.sell_price || item.price || 0),
|
||||
}));
|
||||
const bySku = res.drug_prices_by_sku || {};
|
||||
Object.keys(bySku).forEach((skuKey) => {
|
||||
drugPricesCache.value[Number(skuKey)] = (bySku[skuKey] || []).map((item: any) => ({
|
||||
drug_id: Number(item.drug_id),
|
||||
buy_price: Number(item.buy_price || 0),
|
||||
sell_price: Number(item.sell_price || item.price || 0),
|
||||
}));
|
||||
});
|
||||
|
||||
nextTick(() => {
|
||||
switchAllocationView(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
watch(priceCalcScheme, (scheme) => {
|
||||
if (scheme === 1) {
|
||||
nextTick(() => switchAllocationView(activeSkuId.value));
|
||||
}
|
||||
});
|
||||
|
||||
/** 将编辑器当前分摊写入缓存 */
|
||||
function persistCurrentScopePrices() {
|
||||
const prices = chineseDrugEditorRef.value?.getDrugPricesPayload() || [];
|
||||
drugPricesCache.value[activeSkuId.value] = prices.map((item) => ({
|
||||
drug_id: Number(item.drug_id),
|
||||
buy_price: Number(item.buy_price || 0),
|
||||
sell_price: Number(item.sell_price || 0),
|
||||
}));
|
||||
}
|
||||
|
||||
/** 切换单帖 / SKU 分摊视图 */
|
||||
function switchAllocationView(skuId: number) {
|
||||
persistCurrentScopePrices();
|
||||
activeSkuId.value = skuId;
|
||||
const prices = drugPricesCache.value[skuId] || drugPricesCache.value[0] || [];
|
||||
chineseDrugEditorRef.value?.loadDrugs(
|
||||
prescriptionDetailCache.value,
|
||||
prescriptionDosage.value,
|
||||
prescriptionDayDosage.value,
|
||||
prices,
|
||||
);
|
||||
}
|
||||
|
||||
function handleSkuSelectForPrice(skuId: number) {
|
||||
switchAllocationView(skuId);
|
||||
}
|
||||
|
||||
function backToDoseAllocation() {
|
||||
switchAllocationView(0);
|
||||
}
|
||||
|
||||
/** 自动均分:单帖用 per_dose,SKU 用套餐总价 */
|
||||
async function handleDistributePrices() {
|
||||
const drugs = chineseDrugEditorRef.value?.getDrugsForDistribute() || [];
|
||||
if (drugs.length === 0) {
|
||||
message.warning('请先配置特色方药品');
|
||||
return;
|
||||
}
|
||||
let buyPrice = buyPricePerDose.value;
|
||||
let salePrice = salePricePerDose.value;
|
||||
if (activeSkuId.value > 0) {
|
||||
const sku = skus.value.find((item) => Number(item.id) === Number(activeSkuId.value));
|
||||
if (!sku) {
|
||||
message.warning('请先保存 SKU 后再均分');
|
||||
return;
|
||||
}
|
||||
const dose = Math.max(1, Number(sku.dose_count || 1));
|
||||
buyPrice = Number(sku.buy_price || 0) / dose;
|
||||
salePrice = Number(sku.sale_price || 0) / dose;
|
||||
}
|
||||
try {
|
||||
const result = await distributeDrugPricesApi({
|
||||
drugs,
|
||||
buy_price_per_dose: buyPrice,
|
||||
sale_price_per_dose: salePrice,
|
||||
buy_price: buyPrice,
|
||||
sale_price: salePrice,
|
||||
});
|
||||
chineseDrugEditorRef.value?.applyDistributedPrices(result || []);
|
||||
persistCurrentScopePrices();
|
||||
message.success('已自动均分药品单价');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="`价格配置 - ${configName}`" class="w-[90%]">
|
||||
<div class="price-config-modal">
|
||||
<div class="section-title">价格计算方案</div>
|
||||
<RadioGroup v-model:value="priceCalcScheme">
|
||||
<Radio :value="1">固定单价(含 SKU 快捷选贴)</Radio>
|
||||
<Radio :value="3">门店浮动</Radio>
|
||||
</RadioGroup>
|
||||
|
||||
<div v-if="showFixedPriceBlock" class="mt-4">
|
||||
<Row :gutter="16">
|
||||
<Col :span="12">
|
||||
<div class="allocation-panel">
|
||||
<div class="allocation-header">
|
||||
<span class="allocation-title">{{ allocationTitle }}</span>
|
||||
<Button
|
||||
v-if="activeSkuId > 0"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="backToDoseAllocation"
|
||||
>
|
||||
返回单帖均摊
|
||||
</Button>
|
||||
</div>
|
||||
<ChineseDrugEditor
|
||||
ref="chineseDrugEditorRef"
|
||||
:price-editable="true"
|
||||
:structure-readonly="true"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col :span="12">
|
||||
<div class="section-title">固定单价</div>
|
||||
<div class="price-row">
|
||||
<div class="price-field">
|
||||
<span class="label">一贴供货价</span>
|
||||
<InputNumber
|
||||
v-model:value="buyPricePerDose"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
<div class="price-field">
|
||||
<span class="label">一贴销售价</span>
|
||||
<InputNumber
|
||||
v-model:value="salePricePerDose"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
<Button type="primary" ghost @click="handleDistributePrices">
|
||||
自动均分药品单价
|
||||
</Button>
|
||||
</div>
|
||||
<div class="section-title mt-4">SKU 规格(点击行查看分摊)</div>
|
||||
<SkuEditor
|
||||
ref="skuEditorRef"
|
||||
v-model="skus"
|
||||
:selected-price-sku-id="activeSkuId"
|
||||
@select-for-price="handleSkuSelectForPrice"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<div v-else class="mt-4 store-float-tip">
|
||||
门店浮动方案无需配置药品分摊,SKU 仍可配置快捷选贴数。
|
||||
<div class="section-title mt-4">SKU 规格</div>
|
||||
<SkuEditor ref="skuEditorRef" v-model="skus" />
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.section-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.allocation-panel {
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
.allocation-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.allocation-title {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.price-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.price-field {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.price-field .label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.store-float-tip {
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,278 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
InputNumber,
|
||||
Radio,
|
||||
Select,
|
||||
SelectOption,
|
||||
Table,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
export interface SkuItem {
|
||||
id?: number;
|
||||
_key?: number;
|
||||
sku_name: string;
|
||||
dose_count: number;
|
||||
buy_price?: number;
|
||||
sale_price?: number;
|
||||
status: number;
|
||||
is_default?: number;
|
||||
sort: number;
|
||||
store_id?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: SkuItem[];
|
||||
/** 当前查看分摊的 SKU(0=单帖,在 PriceConfigModal 中由父组件控制) */
|
||||
selectedPriceSkuId?: number;
|
||||
}>(),
|
||||
{
|
||||
modelValue: () => [],
|
||||
selectedPriceSkuId: 0,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: SkuItem[]];
|
||||
/** 点击行切换左侧分摊视图 */
|
||||
'select-for-price': [skuId: number];
|
||||
}>();
|
||||
|
||||
const skuList = ref<SkuItem[]>([]);
|
||||
|
||||
const columns = [
|
||||
{ title: '默认', key: 'is_default', width: 70 },
|
||||
{ title: 'SKU名称', key: 'sku_name', width: 120 },
|
||||
{ title: '贴数', key: 'dose_count', width: 90 },
|
||||
{ title: '供货总价', key: 'buy_price', width: 110 },
|
||||
{ title: '销售总价', key: 'sale_price', width: 110 },
|
||||
{ title: '状态', key: 'status', width: 90 },
|
||||
{ title: '排序', key: 'sort', width: 80 },
|
||||
{ title: '操作', key: 'action', width: 70 },
|
||||
];
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
skuList.value = (val || []).map((item, index) => ({
|
||||
...item,
|
||||
_key: item._key ?? item.id ?? Date.now() + index,
|
||||
sort: item.sort ?? index,
|
||||
store_id: item.store_id ?? 0,
|
||||
status: item.status ?? 1,
|
||||
is_default: item.is_default ?? 0,
|
||||
buy_price: item.buy_price ?? 0,
|
||||
sale_price: item.sale_price ?? 0,
|
||||
}));
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
function syncToParent() {
|
||||
emit('update:modelValue', [...skuList.value]);
|
||||
}
|
||||
|
||||
function addSku() {
|
||||
skuList.value.push({
|
||||
_key: Date.now(),
|
||||
sku_name: '',
|
||||
dose_count: 7,
|
||||
buy_price: 0,
|
||||
sale_price: 0,
|
||||
status: 1,
|
||||
is_default: skuList.value.length === 0 ? 1 : 0,
|
||||
sort: skuList.value.length,
|
||||
store_id: 0,
|
||||
});
|
||||
syncToParent();
|
||||
}
|
||||
|
||||
function removeSku(index: number) {
|
||||
const removed = skuList.value[index];
|
||||
const removedDefault = removed?.is_default === 1;
|
||||
const removedId = Number(removed?.id || 0);
|
||||
skuList.value.splice(index, 1);
|
||||
if (removedDefault && skuList.value.length > 0) {
|
||||
skuList.value[0].is_default = 1;
|
||||
}
|
||||
syncToParent();
|
||||
if (removedId > 0 && props.selectedPriceSkuId === removedId) {
|
||||
emit('select-for-price', 0);
|
||||
}
|
||||
}
|
||||
|
||||
function setDefault(index: number) {
|
||||
skuList.value.forEach((item, i) => {
|
||||
item.is_default = i === index ? 1 : 0;
|
||||
});
|
||||
syncToParent();
|
||||
}
|
||||
|
||||
function onFieldChange() {
|
||||
syncToParent();
|
||||
}
|
||||
|
||||
/** 点击行查看该 SKU 分摊(需已保存有 id) */
|
||||
function onRowClick(record: SkuItem) {
|
||||
const skuId = Number(record.id || 0);
|
||||
if (skuId <= 0) {
|
||||
return;
|
||||
}
|
||||
emit('select-for-price', skuId);
|
||||
}
|
||||
|
||||
function customRow(record: SkuItem) {
|
||||
const skuId = Number(record.id || 0);
|
||||
return {
|
||||
class: skuId > 0 && skuId === Number(props.selectedPriceSkuId)
|
||||
? 'sku-row--price-active'
|
||||
: '',
|
||||
onClick: () => onRowClick(record),
|
||||
};
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getSkusPayload: () => skuList.value,
|
||||
setSkus: (skus: SkuItem[]) => {
|
||||
skuList.value = (skus || []).map((item, index) => ({
|
||||
...item,
|
||||
_key: item._key ?? item.id ?? Date.now() + index,
|
||||
sort: item.sort ?? index,
|
||||
store_id: item.store_id ?? 0,
|
||||
is_default: item.is_default ?? 0,
|
||||
buy_price: item.buy_price ?? 0,
|
||||
sale_price: item.sale_price ?? 0,
|
||||
}));
|
||||
syncToParent();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="sku-editor">
|
||||
<div class="sku-editor-header">
|
||||
<span class="font-medium">SKU 规格(快捷选贴数,价格为套餐总价)</span>
|
||||
<Button type="dashed" size="small" @click="addSku">
|
||||
<PlusOutlined />
|
||||
添加 SKU
|
||||
</Button>
|
||||
</div>
|
||||
<Table
|
||||
:columns="columns"
|
||||
:data-source="skuList"
|
||||
:pagination="false"
|
||||
:custom-row="customRow"
|
||||
row-key="_key"
|
||||
size="small"
|
||||
bordered
|
||||
class="mt-2 sku-table"
|
||||
>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'is_default'">
|
||||
<Radio
|
||||
:checked="record.is_default === 1"
|
||||
@click.stop="setDefault(index)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'sku_name'">
|
||||
<Input
|
||||
v-model:value="record.sku_name"
|
||||
placeholder="如 7贴"
|
||||
@click.stop
|
||||
@change="onFieldChange"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dose_count'">
|
||||
<InputNumber
|
||||
v-model:value="record.dose_count"
|
||||
:min="1"
|
||||
:max="99"
|
||||
style="width: 100%"
|
||||
@click.stop
|
||||
@change="onFieldChange"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'buy_price'">
|
||||
<InputNumber
|
||||
v-model:value="record.buy_price"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
@click.stop
|
||||
@change="onFieldChange"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'sale_price'">
|
||||
<InputNumber
|
||||
v-model:value="record.sale_price"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
@click.stop
|
||||
@change="onFieldChange"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<Select
|
||||
v-model:value="record.status"
|
||||
style="width: 100%"
|
||||
@click.stop
|
||||
@change="onFieldChange"
|
||||
>
|
||||
<SelectOption :value="1">上架</SelectOption>
|
||||
<SelectOption :value="0">下架</SelectOption>
|
||||
</Select>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'sort'">
|
||||
<InputNumber
|
||||
v-model:value="record.sort"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
@click.stop
|
||||
@change="onFieldChange"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<Button type="link" danger size="small" @click.stop="removeSku(index)">
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
<div v-if="skuList.length === 0" class="sku-empty-tip">
|
||||
暂无 SKU,用户端将使用手动输入贴数
|
||||
</div>
|
||||
<div v-else class="sku-hint">
|
||||
点击已保存的 SKU 行可在左侧查看/编辑该规格分摊价
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sku-editor-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.sku-empty-tip,
|
||||
.sku-hint {
|
||||
margin-top: 8px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.sku-row--price-active) {
|
||||
background-color: #e6f4ff !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:deep(.sku-table .ant-table-row) {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -22,6 +22,9 @@ const chineseDrugEditorRef = ref<InstanceType<typeof ChineseDrugEditor>>();
|
||||
const dosage = ref(7);
|
||||
const dayDosage = ref(2);
|
||||
const drugsChanged = ref(false);
|
||||
/** 预选诊断/医嘱(在药品列表气泡卡中编辑) */
|
||||
const clinicalDiagnose = ref('');
|
||||
const doctorOrder = ref('');
|
||||
|
||||
const [Form, formApi] = useVbenForm(modalFormProps);
|
||||
|
||||
@@ -64,6 +67,14 @@ const [Modal, modalApi] = useVbenModal({
|
||||
.filter(Boolean),
|
||||
};
|
||||
|
||||
const diagnosisOrder =
|
||||
chineseDrugEditorRef.value?.getDiagnosisOrderPayload?.() ?? {
|
||||
clinical_diagnose: clinicalDiagnose.value,
|
||||
doctor_order: doctorOrder.value,
|
||||
};
|
||||
payload.clinical_diagnose = diagnosisOrder.clinical_diagnose ?? '';
|
||||
payload.doctor_order = diagnosisOrder.doctor_order ?? '';
|
||||
|
||||
if (prescriptionType.value === 'chinese' && drugs.length > 0) {
|
||||
payload.drugs = drugs;
|
||||
payload.dosage = dosage.value;
|
||||
@@ -110,6 +121,8 @@ const [Modal, modalApi] = useVbenModal({
|
||||
intro_text: res.intro_text || '',
|
||||
introduction_images: res.introduction_images || [],
|
||||
});
|
||||
clinicalDiagnose.value = res.clinical_diagnose || '';
|
||||
doctorOrder.value = res.doctor_order || '';
|
||||
if (res.prescription_type === 'chinese' && res.prescription_detail?.length) {
|
||||
const first = res.prescription_detail[0];
|
||||
chineseDrugEditorRef.value?.loadDrugs(
|
||||
@@ -140,6 +153,8 @@ const [Modal, modalApi] = useVbenModal({
|
||||
intro_text: '',
|
||||
introduction_images: [],
|
||||
});
|
||||
clinicalDiagnose.value = '';
|
||||
doctorOrder.value = '';
|
||||
chineseDrugEditorRef.value?.loadDrugs([], 7, 2);
|
||||
}
|
||||
} else {
|
||||
@@ -168,6 +183,8 @@ function bindPrescriptionTypeChange() {
|
||||
<div v-if="prescriptionType === 'chinese'" class="mt-4 border-t pt-4">
|
||||
<ChineseDrugEditor
|
||||
ref="chineseDrugEditorRef"
|
||||
v-model:clinical-diagnose="clinicalDiagnose"
|
||||
v-model:doctor-order="doctorOrder"
|
||||
v-model:dosage="dosage"
|
||||
v-model:day-dosage="dayDosage"
|
||||
@update:model-value="drugsChanged = true"
|
||||
|
||||
@@ -70,7 +70,7 @@ export const modalFormProps: VbenFormProps = {
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '每剂价格',
|
||||
placeholder: '展示兜底价/贴',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
style: { width: '100%' },
|
||||
@@ -106,6 +106,20 @@ export const modalFormProps: VbenFormProps = {
|
||||
formItemClass: 'col-span-6',
|
||||
defaultValue: 1,
|
||||
},
|
||||
{
|
||||
component: 'VbenSelect',
|
||||
componentProps: {
|
||||
placeholder: '是否包邮',
|
||||
options: [
|
||||
{ label: '不包邮', value: 0 },
|
||||
{ label: '包邮', value: 1 },
|
||||
],
|
||||
},
|
||||
fieldName: 'is_free_shipping',
|
||||
label: '是否包邮',
|
||||
formItemClass: 'col-span-6',
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
component: 'VbenSelect',
|
||||
componentProps: {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
updateSpecialPrescriptionStatus,
|
||||
} from './api';
|
||||
import EditPrescriptionModal from './components/EditPrescriptionModal.vue';
|
||||
import PriceConfigModal from './components/PriceConfigModal.vue';
|
||||
import SpecialPrescriptionModal from './components/modal.vue';
|
||||
import { formOptions as searchFormOptions } from './config/search';
|
||||
import { gridOptions } from './config/table';
|
||||
@@ -47,6 +48,10 @@ const [EditPrescriptionModalComp, editPrescriptionModalApi] = useVbenModal({
|
||||
connectedComponent: EditPrescriptionModal,
|
||||
});
|
||||
|
||||
const [PriceConfigModalComp, priceConfigModalApi] = useVbenModal({
|
||||
connectedComponent: PriceConfigModal,
|
||||
});
|
||||
|
||||
const showModal = (data = {}, isUpdate = false) => {
|
||||
formModalApi.setData({
|
||||
values: data,
|
||||
@@ -64,6 +69,14 @@ const showEditPrescriptionModal = (row: any) => {
|
||||
editPrescriptionModalApi.open();
|
||||
};
|
||||
|
||||
const showPriceConfigModal = (row: any) => {
|
||||
priceConfigModalApi.setData({
|
||||
values: row,
|
||||
gridApi,
|
||||
});
|
||||
priceConfigModalApi.open();
|
||||
};
|
||||
|
||||
/** 点击状态 Tag 切换上下架 */
|
||||
const toggleStatus = async (row: any) => {
|
||||
if (statusLoadingId.value === row.id) {
|
||||
@@ -98,6 +111,7 @@ const deleteApi = (row: any) => {
|
||||
<Page auto-content-height title="特色方管理">
|
||||
<FormModal />
|
||||
<EditPrescriptionModalComp />
|
||||
<PriceConfigModalComp />
|
||||
|
||||
<div class="p-4">
|
||||
<TableAction
|
||||
@@ -161,6 +175,10 @@ const deleteApi = (row: any) => {
|
||||
ifShow: row.prescription_sub_type === 1,
|
||||
onClick: () => showEditPrescriptionModal(row),
|
||||
},
|
||||
{
|
||||
label: '价格配置',
|
||||
onClick: () => showPriceConfigModal(row),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
color: 'error',
|
||||
|
||||
@@ -9,6 +9,8 @@ export function buildSpecialPrescriptionPayload(
|
||||
day_dosage?: number;
|
||||
rule_type?: number;
|
||||
package_method_id?: number;
|
||||
clinical_diagnose?: string;
|
||||
doctor_order?: string;
|
||||
},
|
||||
) {
|
||||
const payload: Record<string, any> = {
|
||||
@@ -20,9 +22,12 @@ export function buildSpecialPrescriptionPayload(
|
||||
price_per_dose: detail.price_per_dose ?? 0,
|
||||
sales_count: detail.sales_count ?? 0,
|
||||
status: detail.status ?? 1,
|
||||
is_free_shipping: detail.is_free_shipping ?? 0,
|
||||
prescription_type: detail.prescription_type || 'chinese',
|
||||
intro_text: detail.intro_text || '',
|
||||
introduction_images: detail.introduction_images || [],
|
||||
clinical_diagnose: detail.clinical_diagnose || '',
|
||||
doctor_order: detail.doctor_order || '',
|
||||
};
|
||||
if (drugOverrides?.drugs?.length) {
|
||||
payload.drugs = drugOverrides.drugs;
|
||||
@@ -31,5 +36,11 @@ export function buildSpecialPrescriptionPayload(
|
||||
payload.rule_type = drugOverrides.rule_type ?? 1;
|
||||
payload.package_method_id = drugOverrides.package_method_id ?? 2;
|
||||
}
|
||||
if (drugOverrides?.clinical_diagnose !== undefined) {
|
||||
payload.clinical_diagnose = drugOverrides.clinical_diagnose;
|
||||
}
|
||||
if (drugOverrides?.doctor_order !== undefined) {
|
||||
payload.doctor_order = drugOverrides.doctor_order;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Image, Tag } from 'ant-design-vue';
|
||||
|
||||
/** 患者选方记录(含特色方封面、SKU 等) */
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
record: Record<string, any> | null;
|
||||
purchaseLabel?: string;
|
||||
loading?: boolean;
|
||||
/** 是否可点击导入(有待导入/已应用记录时) */
|
||||
clickable?: boolean;
|
||||
}>(),
|
||||
{
|
||||
purchaseLabel: '',
|
||||
loading: false,
|
||||
clickable: true,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
apply: [];
|
||||
}>();
|
||||
|
||||
/** 封面图 */
|
||||
const coverImage = computed(
|
||||
() => props.record?.special_prescription?.cover_image || '',
|
||||
);
|
||||
/** 药方名称 */
|
||||
const prescriptionName = computed(
|
||||
() => props.record?.special_prescription?.name || '特色方',
|
||||
);
|
||||
/** 状态文案与颜色 */
|
||||
const statusMeta = computed(() => {
|
||||
const status = Number(props.record?.status ?? -1);
|
||||
if (status === 0) return { text: '待导入', color: 'orange' as const };
|
||||
if (status === 1) return { text: '已应用', color: 'green' as const };
|
||||
return { text: '已完成', color: 'default' as const };
|
||||
});
|
||||
|
||||
/**
|
||||
* 点击卡片触发导入
|
||||
* loading 或不可点击时不响应
|
||||
*/
|
||||
function handleCardClick() {
|
||||
if (!props.clickable || props.loading) return;
|
||||
emit('apply');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="record"
|
||||
class="sp-import-card mt-4 rounded-lg border p-3 transition-shadow
|
||||
bg-[#fffaf5] border-[#ffe0c2]
|
||||
dark:bg-[#2c2419] dark:border-[#5c4a32]"
|
||||
:class="{
|
||||
'sp-import-card--clickable cursor-pointer hover:shadow-md dark:hover:shadow-[0_2px_8px_rgba(255,152,0,0.25)]':
|
||||
clickable && !loading,
|
||||
'sp-import-card--loading': loading,
|
||||
}"
|
||||
@click="handleCardClick"
|
||||
>
|
||||
<div
|
||||
class="sp-import-card__hint mb-2.5 text-[13px] text-[#fa8c16] dark:text-[#ffa940]"
|
||||
>
|
||||
点击卡片导入药方
|
||||
</div>
|
||||
<div class="sp-import-card__body flex flex-wrap items-center gap-4">
|
||||
<Image
|
||||
v-if="coverImage"
|
||||
:src="coverImage"
|
||||
:width="80"
|
||||
:height="80"
|
||||
class="sp-import-card__cover shrink-0 rounded object-cover"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="sp-import-card__cover sp-import-card__cover--placeholder flex h-20 w-20 shrink-0 items-center justify-center rounded text-xs
|
||||
bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400"
|
||||
>
|
||||
无封面
|
||||
</div>
|
||||
<div class="sp-import-card__info min-w-0 flex-1">
|
||||
<div
|
||||
class="sp-import-card__name text-base font-semibold text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
{{ prescriptionName }}
|
||||
</div>
|
||||
<div
|
||||
class="sp-import-card__sku mt-1 text-sm text-gray-600 dark:text-gray-400"
|
||||
>
|
||||
规格:{{ purchaseLabel || '单剂' }}
|
||||
</div>
|
||||
<Tag class="sp-import-card__tag mt-2" :color="statusMeta.color">
|
||||
{{ statusMeta.text }}
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sp-import-card--loading {
|
||||
opacity: 0.75;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
withdrawPrescriptionApi,
|
||||
} from '#/views/doctor/doctor-reception/api';
|
||||
|
||||
import SpecialPrescriptionImportCard from './components/SpecialPrescriptionImportCard.vue';
|
||||
import DiagnosisModal from './components/DiagnosisModal.vue';
|
||||
import DoctorOrderModal from './components/DoctorOrderModal.vue';
|
||||
import PrescriptionDetail from './components/PrescriptionDetail.vue';
|
||||
@@ -108,6 +109,8 @@ interface Patient {
|
||||
status: number;
|
||||
special_prescription_id: number;
|
||||
dose_count: number;
|
||||
sku_id?: number;
|
||||
sku_name?: string;
|
||||
special_prescription?: {
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -346,7 +349,40 @@ const hasSpecialPrescriptionRecord = computed(
|
||||
const specialPrescriptionRecord = computed(
|
||||
() => patientInfo.value?.special_prescription_patient_record ?? null,
|
||||
);
|
||||
/** 导入卡片规格:SKU 名或单剂 */
|
||||
function resolveSpecialPrescriptionPurchaseLabel(
|
||||
record?: { sku_id?: number; sku_name?: string } | null,
|
||||
) {
|
||||
if (!record) return '';
|
||||
return Number(record.sku_id) > 0
|
||||
? (record.sku_name || 'SKU套餐')
|
||||
: '单剂';
|
||||
}
|
||||
const specialPrescriptionPurchaseLabel = computed(() =>
|
||||
resolveSpecialPrescriptionPurchaseLabel(specialPrescriptionRecord.value),
|
||||
);
|
||||
const applyingSpecialPrescription = ref(false);
|
||||
/** 医生已导入的特色方 ID,仅 >0 时提交走特色方计价 */
|
||||
const appliedSpecialPrescriptionId = ref(0);
|
||||
/** apply 返回的 SKU/贴数套餐总价,用于底部合计 */
|
||||
const appliedSpecialPrescriptionExpectedTotal = ref<number | null>(null);
|
||||
function clearAppliedSpecialPrescription() {
|
||||
appliedSpecialPrescriptionId.value = 0;
|
||||
appliedSpecialPrescriptionExpectedTotal.value = null;
|
||||
}
|
||||
/** 已应用特色方时锁定药方(不可改删增) */
|
||||
const isSpecialPrescriptionCartLocked = computed(
|
||||
() => appliedSpecialPrescriptionId.value > 0,
|
||||
);
|
||||
const SP_CART_LOCK_MSG = '特色方药方不可修改,请先清空药方';
|
||||
/** 特色方锁定中则提示并返回 true */
|
||||
function guardSpecialPrescriptionCartEdit(): boolean {
|
||||
if (isSpecialPrescriptionCartLocked.value) {
|
||||
message.warning(SP_CART_LOCK_MSG);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const diagnosis = ref('');
|
||||
const medicalAdvice = ref('');
|
||||
const treatmentPrice = ref(0);
|
||||
@@ -398,6 +434,7 @@ function selectProductChange(id) {
|
||||
* @param id
|
||||
*/
|
||||
function selectDrugUseWayChange(id) {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
if (
|
||||
selectProductIndex.value < 0 ||
|
||||
selectProductIndex.value >= currentDrugs.value.length
|
||||
@@ -417,6 +454,14 @@ function selectDrugUseWayChange(id) {
|
||||
* 计算商品总价
|
||||
*/
|
||||
const totalProductCost = computed(() => {
|
||||
const expected = appliedSpecialPrescriptionExpectedTotal.value;
|
||||
if (
|
||||
expected != null
|
||||
&& !Number.isNaN(Number(expected))
|
||||
&& Number(expected) > 0
|
||||
) {
|
||||
return Number(expected);
|
||||
}
|
||||
if (currentDrugs.value.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -509,6 +554,7 @@ const setVisible = (value, instruction = ''): void => {
|
||||
* @param isUpdateTabType
|
||||
*/
|
||||
const selectPatient = (patient: Patient, isUpdateTabType = true) => {
|
||||
clearAppliedSpecialPrescription();
|
||||
selectPatientId.value = patient.id;
|
||||
receptionStatus.value = patient.status;
|
||||
if (isUpdateTabType === true) {
|
||||
@@ -561,12 +607,14 @@ const selectPatient = (patient: Patient, isUpdateTabType = true) => {
|
||||
|
||||
// 数量增加
|
||||
const increment = (index: number) => {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
currentDrugs.value[index].select_number++;
|
||||
saveToLocalStorage();
|
||||
};
|
||||
|
||||
// 数量减少
|
||||
const decrement = (index: number) => {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
if (currentDrugs.value[index].select_number > 1) {
|
||||
currentDrugs.value[index].select_number--;
|
||||
saveToLocalStorage();
|
||||
@@ -575,6 +623,7 @@ const decrement = (index: number) => {
|
||||
|
||||
// 切换药品用法编辑状态
|
||||
const toggleEditDrug = (index: number) => {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
currentDrugs.value[index].isEditing = !currentDrugs.value[index].isEditing;
|
||||
};
|
||||
|
||||
@@ -635,6 +684,7 @@ function clearCartPriceDiscount() {
|
||||
const priceDiscountLabel = computed(() => formatPriceDiscountLabel(priceDiscount.value));
|
||||
|
||||
function openOrderPriceAdjust() {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
if (!priceAdjustEnabled.value || !currentDrugs.value.length) return;
|
||||
currentDrugs.value = snapshotDrugOrigins(currentDrugs.value);
|
||||
const discount = Number(priceDiscount.value);
|
||||
@@ -654,6 +704,7 @@ function openOrderPriceAdjust() {
|
||||
* @param index
|
||||
*/
|
||||
const removeDrug = (index: number) => {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
currentDrugs.value.splice(index, 1);
|
||||
updateLocalStorage();
|
||||
};
|
||||
@@ -790,6 +841,7 @@ const sendPrescription = () => {
|
||||
doctor_second_sign: doctorSecondSign.value,
|
||||
salesperson_transfer_prescription_id: salespersonTransferPrescriptionId.value || undefined,
|
||||
price_discount: priceDiscount.value,
|
||||
special_prescription_id: appliedSpecialPrescriptionId.value || 0,
|
||||
}).then((res) => {
|
||||
message.success('处方已发送');
|
||||
|
||||
@@ -807,17 +859,10 @@ const sendPrescription = () => {
|
||||
}
|
||||
|
||||
// 清空当前数据
|
||||
currentDrugs.value = [];
|
||||
diagnosis.value = '';
|
||||
medicalAdvice.value = '';
|
||||
packageMethodId.value = 2;
|
||||
processRulePrice.value = 0;
|
||||
dosage.value = 7;
|
||||
dayDosage.value = 2;
|
||||
clearPrescriptionCart(false);
|
||||
tabType.value = 1;
|
||||
salespersonTransferPrescriptionId.value = 0;
|
||||
newDrugInfo.value = {};
|
||||
updateLocalStorage();
|
||||
processRulePrice.value = 0;
|
||||
checkSalespersonTransfer();
|
||||
|
||||
// 刷新患者信息
|
||||
@@ -898,7 +943,7 @@ const [DoctorOrderModals, DoctorOrderModalApi] = useVbenModal({
|
||||
});
|
||||
|
||||
const openDoctorOrderModal = () => {
|
||||
// 打开常用诊断模态框逻辑
|
||||
// 打开常用医嘱模态框逻辑
|
||||
DoctorOrderModalApi.setData({
|
||||
values: medicalAdvice.value,
|
||||
updateDoctorOrder,
|
||||
@@ -953,6 +998,7 @@ function openCommonPrescriptionModal() {
|
||||
* @description 选择常用方后,将药品列表填充到当前处方中
|
||||
*/
|
||||
async function handleSelectCommonPrescription(data: any, type: number) {
|
||||
clearAppliedSpecialPrescription();
|
||||
const { prescription, recipes } = data;
|
||||
|
||||
// 根据类型处理药品:整表覆盖为常用方内容,不与当前处方按药品 ID 去重合并
|
||||
@@ -1069,6 +1115,10 @@ async function handleApplySpecialPrescription(switchToPrescription = false) {
|
||||
typeMap[prescriptionType] || 2,
|
||||
);
|
||||
|
||||
appliedSpecialPrescriptionId.value = Number(res.special_prescription_id) || 0;
|
||||
appliedSpecialPrescriptionExpectedTotal.value =
|
||||
res.expected_total_price != null ? Number(res.expected_total_price) : null;
|
||||
|
||||
if (selectPatientId.value) {
|
||||
const value = await getPatientItem(selectPatientId.value);
|
||||
patientInfo.value = value;
|
||||
@@ -1197,6 +1247,7 @@ const cancelSaveCommonPrescription = () => {
|
||||
};
|
||||
|
||||
const openWesternModal = () => {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
// 判断是否为简单产品类型(3,5,6,7)
|
||||
const simpleProductTypes = [3, 5, 6, 7];
|
||||
if (simpleProductTypes.includes(activeCategory.value)) {
|
||||
@@ -1674,6 +1725,7 @@ loadProcessRuleData();
|
||||
* @param id
|
||||
*/
|
||||
function selectProcessRule(id) {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
processRuleId.value = id;
|
||||
loadProcessRuleData(id); // 加载煎法选项
|
||||
}
|
||||
@@ -1683,6 +1735,7 @@ function selectProcessRule(id) {
|
||||
* @param id
|
||||
*/
|
||||
function selectProcessRuleNot(id) {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
childProcessRuleId.value = id;
|
||||
loadProcessRuleData(0, id); // 加载备注选项
|
||||
}
|
||||
@@ -1692,6 +1745,7 @@ function selectProcessRuleNot(id) {
|
||||
* @param id
|
||||
*/
|
||||
function selectProcessRuleNotCommit(id) {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
processRuleNoteId.value = id;
|
||||
}
|
||||
|
||||
@@ -1702,6 +1756,7 @@ const packageMethod = [
|
||||
];
|
||||
|
||||
function selectPackageMethod(id) {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
packageMethodId.value = id;
|
||||
}
|
||||
|
||||
@@ -1709,6 +1764,41 @@ function selectPackageMethod(id) {
|
||||
const dosage = ref(7);
|
||||
const dayDosage = ref(2);
|
||||
|
||||
/**
|
||||
* 清空药方:退出特色方 SKU 计价状态,恢复可编辑
|
||||
* @param showToast 是否提示成功
|
||||
*/
|
||||
function clearPrescriptionCart(showToast = true) {
|
||||
currentDrugs.value = [];
|
||||
diagnosis.value = '';
|
||||
medicalAdvice.value = '';
|
||||
packageMethodId.value = 2;
|
||||
dosage.value = 7;
|
||||
dayDosage.value = 2;
|
||||
newDrugInfo.value = {};
|
||||
clearAppliedSpecialPrescription();
|
||||
priceDiscount.value = 100;
|
||||
updateLocalStorage();
|
||||
if (showToast) {
|
||||
message.success('已清空药方');
|
||||
}
|
||||
}
|
||||
|
||||
/** 清空药方(有药品时二次确认) */
|
||||
function handleClearPrescriptionCart() {
|
||||
if (currentDrugs.value.length === 0 && !appliedSpecialPrescriptionId.value) {
|
||||
message.info('当前无药方内容');
|
||||
return;
|
||||
}
|
||||
AntModal.confirm({
|
||||
title: '清空药方',
|
||||
content: '确定清空当前药方吗?特色方将需重新应用。',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: () => clearPrescriptionCart(),
|
||||
});
|
||||
}
|
||||
|
||||
function updateChineseNumber() {
|
||||
updateLocalStorage();
|
||||
}
|
||||
@@ -1833,6 +1923,7 @@ const selectChineseId = ref(0);
|
||||
* @param id
|
||||
*/
|
||||
function selectOldDrugInfo(id) {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
const check = JSON.parse(
|
||||
localStorage.getItem(getStorageKey(activePatient.value?.id, activeCategory.value)) || '[]',
|
||||
).find((v) => v.id === id);
|
||||
@@ -1923,6 +2014,7 @@ function searchOption(inputValue) {
|
||||
* 添加中药
|
||||
*/
|
||||
function addDrugByChinese() {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
// 如果是新药品输入框,调用添加新药品方法
|
||||
const check = currentDrugs.value.find(
|
||||
(v) => v.id === newDrugInfo.value.id,
|
||||
@@ -1976,6 +2068,7 @@ function selectDrugByNewDrugInfo(event: KeyboardEvent, isNewDrug: boolean) {
|
||||
* @param {object} data - 商品对象
|
||||
*/
|
||||
function addProducts(data) {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
// 查找是否已存在于选择列表中
|
||||
const existItem = currentDrugs.value.find(
|
||||
(item) => item.index_id === data.id,
|
||||
@@ -2046,6 +2139,7 @@ function addProducts(data) {
|
||||
* @param drug 选中的药品数据
|
||||
*/
|
||||
function handleSimpleProductSelect(drug: any) {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
// 检查是否已存在
|
||||
const existItem = currentDrugs.value.find(
|
||||
(item) => item.id === (drug._drugId || drug.drug_id || drug.drug?.id),
|
||||
@@ -2285,52 +2379,13 @@ watch(
|
||||
}}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<Card
|
||||
v-if="specialPrescriptionRecord"
|
||||
class="mt-5"
|
||||
title="特色方"
|
||||
>
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<Image
|
||||
v-if="specialPrescriptionRecord.special_prescription?.cover_image"
|
||||
:src="specialPrescriptionRecord.special_prescription.cover_image"
|
||||
:width="80"
|
||||
:height="80"
|
||||
class="rounded object-cover"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<div class="text-base font-medium">
|
||||
{{ specialPrescriptionRecord.special_prescription?.name || '特色方' }}
|
||||
</div>
|
||||
<div class="mt-1 text-gray-500">
|
||||
剂量:{{ specialPrescriptionRecord.dose_count }} 剂
|
||||
</div>
|
||||
<Tag
|
||||
v-if="specialPrescriptionRecord.status === 0"
|
||||
class="mt-2"
|
||||
color="orange"
|
||||
>
|
||||
待导入
|
||||
</Tag>
|
||||
<Tag
|
||||
v-else-if="specialPrescriptionRecord.status === 1"
|
||||
class="mt-2"
|
||||
color="green"
|
||||
>
|
||||
已应用
|
||||
</Tag>
|
||||
<Tag v-else class="mt-2" color="default">已完成</Tag>
|
||||
</div>
|
||||
<Button
|
||||
v-if="hasSpecialPrescriptionRecord"
|
||||
type="primary"
|
||||
:loading="applyingSpecialPrescription"
|
||||
@click="handleApplySpecialPrescription(true)"
|
||||
>
|
||||
导入
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<SpecialPrescriptionImportCard
|
||||
v-if="specialPrescriptionRecord && hasSpecialPrescriptionRecord"
|
||||
:record="specialPrescriptionRecord"
|
||||
:purchase-label="specialPrescriptionPurchaseLabel"
|
||||
:loading="applyingSpecialPrescription"
|
||||
@apply="handleApplySpecialPrescription(true)"
|
||||
/>
|
||||
<Descriptions
|
||||
v-if="userPatientHealthInquiry"
|
||||
:column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 2, xs: 2 }"
|
||||
@@ -2520,15 +2575,23 @@ watch(
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<SpecialPrescriptionImportCard
|
||||
v-if="hasSpecialPrescriptionRecord"
|
||||
class="mt-5 max-w-xl"
|
||||
:record="specialPrescriptionRecord"
|
||||
:purchase-label="specialPrescriptionPurchaseLabel"
|
||||
:loading="applyingSpecialPrescription"
|
||||
@apply="() => handleApplySpecialPrescription()"
|
||||
/>
|
||||
|
||||
<div class="mt-5">
|
||||
<Button
|
||||
v-if="activeCategory === 2"
|
||||
v-if="activeCategory === 2 && !isSpecialPrescriptionCartLocked"
|
||||
type="primary"
|
||||
@click="openWesternModal"
|
||||
>
|
||||
添加商品
|
||||
</Button>
|
||||
<!-- 常用方按钮(产品服务包/非药品/医疗器械无模板接口) -->
|
||||
<Button
|
||||
v-if="canUseCommonPrescription"
|
||||
type="primary"
|
||||
@@ -2537,15 +2600,6 @@ watch(
|
||||
>
|
||||
选择常用方
|
||||
</Button>
|
||||
<Button
|
||||
v-if="hasSpecialPrescriptionRecord"
|
||||
type="primary"
|
||||
class="ml-3"
|
||||
:loading="applyingSpecialPrescription"
|
||||
@click="() => handleApplySpecialPrescription()"
|
||||
>
|
||||
应用特色方
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canUseCommonPrescription"
|
||||
type="default"
|
||||
@@ -2555,6 +2609,14 @@ watch(
|
||||
<SaveOutlined />
|
||||
保存常用方
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
danger
|
||||
class="ml-3"
|
||||
@click="handleClearPrescriptionCart"
|
||||
>
|
||||
清空药方
|
||||
</Button>
|
||||
<Button
|
||||
v-if="activeCategory === 1 && hasSalespersonTransfer"
|
||||
type="default"
|
||||
@@ -2613,6 +2675,7 @@ watch(
|
||||
<p>
|
||||
<Select
|
||||
v-model:value="drug.id"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
:filter-option="false"
|
||||
class="old-select-drug-name"
|
||||
style="min-width: 100px"
|
||||
@@ -2639,6 +2702,7 @@ watch(
|
||||
,
|
||||
<InputNumber
|
||||
v-model:value="drug.number"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
:class="`w-1/5 old-number-input-${index}`"
|
||||
style="min-width: 100px"
|
||||
:controls="false"
|
||||
@@ -2651,6 +2715,7 @@ watch(
|
||||
<span>
|
||||
<Select
|
||||
:value="drug?.way_id"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
placeholder="用法"
|
||||
@change="selectDrugUseWayChange"
|
||||
@dropdown-visible-change="
|
||||
@@ -2677,6 +2742,7 @@ watch(
|
||||
</span>
|
||||
</p>
|
||||
<Button
|
||||
v-if="!isSpecialPrescriptionCartLocked"
|
||||
class="card-delete"
|
||||
type="link"
|
||||
@click="removeDrug(index)"
|
||||
@@ -2688,6 +2754,7 @@ watch(
|
||||
</Card>
|
||||
</Col>
|
||||
<Col
|
||||
v-if="!isSpecialPrescriptionCartLocked"
|
||||
:lg="12"
|
||||
:md="12"
|
||||
:sm="12"
|
||||
@@ -2943,6 +3010,7 @@ watch(
|
||||
<span>用法:</span>
|
||||
<Select
|
||||
:value="drug.use_type?.id"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
size="small"
|
||||
style="width: 80px"
|
||||
@change="(val) => updateDrugUsage(index, 'use_type', drugUseType.find(item => item.id === val))"
|
||||
@@ -2953,6 +3021,7 @@ watch(
|
||||
</Select>
|
||||
<Select
|
||||
:value="drug.use_frequency?.id"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
size="small"
|
||||
style="width: 100px; margin-left: 4px"
|
||||
@change="(val) => updateDrugUsage(index, 'use_frequency', drugUseFrequency.find(item => item.id === val))"
|
||||
@@ -2963,6 +3032,7 @@ watch(
|
||||
</Select>
|
||||
<Select
|
||||
:value="drug.use_num?.id"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
size="small"
|
||||
style="width: 80px; margin-left: 4px"
|
||||
@change="(val) => updateDrugUsage(index, 'use_num', drugTime.find(item => item.id === val))"
|
||||
@@ -2974,12 +3044,14 @@ watch(
|
||||
<span style="margin-left: 4px">每次</span>
|
||||
<InputNumber
|
||||
v-model:value="drug.number"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
:min="1"
|
||||
size="small"
|
||||
style="width: 60px; margin-left: 4px"
|
||||
/>
|
||||
<Select
|
||||
:value="drug.unit?.id"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
size="small"
|
||||
style="width: 60px; margin-left: 4px"
|
||||
@change="(val) => updateDrugUsage(index, 'unit', drugUnit.find(item => item.id === val))"
|
||||
@@ -3001,15 +3073,15 @@ watch(
|
||||
</InputNumber>
|
||||
</div>
|
||||
<div v-else class="quantity-control">
|
||||
<Button type="primary" @click="decrement(index)">-</Button>
|
||||
<Button type="primary" :disabled="isSpecialPrescriptionCartLocked" @click="decrement(index)">-</Button>
|
||||
<span style="margin: 0 20px">{{ drug.select_number }}</span>
|
||||
<Button type="primary" @click="increment(index)">+</Button>
|
||||
<Button type="primary" :disabled="isSpecialPrescriptionCartLocked" @click="increment(index)">+</Button>
|
||||
</div>
|
||||
</div>
|
||||
<span>{{ drug.price }}</span>
|
||||
<div>
|
||||
<Button
|
||||
v-if="activeCategory !== 1 && !drug.isEditing"
|
||||
v-if="activeCategory !== 1 && !drug.isEditing && !isSpecialPrescriptionCartLocked"
|
||||
type="link"
|
||||
@click="toggleEditDrug(index)"
|
||||
>编辑</Button>
|
||||
@@ -3018,7 +3090,7 @@ watch(
|
||||
type="link"
|
||||
@click="saveEditDrug(index)"
|
||||
>保存</Button>
|
||||
<Button type="link" @click="removeDrug(index)">删除</Button>
|
||||
<Button v-if="!isSpecialPrescriptionCartLocked" type="link" @click="removeDrug(index)">删除</Button>
|
||||
<Button
|
||||
v-if="drug.instruction !== ''"
|
||||
type="link"
|
||||
@@ -3036,6 +3108,7 @@ watch(
|
||||
<!-- 搜索组件 -->
|
||||
<div class="mb-5">
|
||||
<DrugSearchSelect
|
||||
v-if="!isSpecialPrescriptionCartLocked"
|
||||
:type="activeCategory"
|
||||
:store-id="myStoreId"
|
||||
:placeholder="`输入${categories.find(c => c.value === activeCategory)?.label || '商品'}名称搜索`"
|
||||
@@ -3067,14 +3140,14 @@ watch(
|
||||
</div>
|
||||
<div>
|
||||
<div class="quantity-control">
|
||||
<Button type="primary" @click="decrement(index)">-</Button>
|
||||
<Button type="primary" :disabled="isSpecialPrescriptionCartLocked" @click="decrement(index)">-</Button>
|
||||
<span style="margin: 0 20px">{{ drug.select_number }}</span>
|
||||
<Button type="primary" @click="increment(index)">+</Button>
|
||||
<Button type="primary" :disabled="isSpecialPrescriptionCartLocked" @click="increment(index)">+</Button>
|
||||
</div>
|
||||
</div>
|
||||
<span>{{ drug.price }}</span>
|
||||
<div>
|
||||
<Button type="link" @click="removeDrug(index)">删除</Button>
|
||||
<Button v-if="!isSpecialPrescriptionCartLocked" type="link" @click="removeDrug(index)">删除</Button>
|
||||
<Button
|
||||
v-if="drug.instruction !== ''"
|
||||
type="link"
|
||||
@@ -3091,7 +3164,7 @@ watch(
|
||||
<!-- 诊断和医嘱 -->
|
||||
<div class="diagnosis-area">
|
||||
<div v-if="activeCategory === 1" class="mb-5">
|
||||
<RadioGroup v-model:value="ruleType">
|
||||
<RadioGroup v-model:value="ruleType" :disabled="isSpecialPrescriptionCartLocked">
|
||||
<RadioButton :value="1">自制剂</RadioButton>
|
||||
<RadioButton :value="2">委托调剂</RadioButton>
|
||||
</RadioGroup>
|
||||
@@ -3099,6 +3172,7 @@ watch(
|
||||
<!-- 选择包法-->
|
||||
<Select
|
||||
:value="packageMethodId"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
class="mt-3 w-1/5"
|
||||
placeholder="请选择包法"
|
||||
@change="selectPackageMethod"
|
||||
@@ -3116,6 +3190,7 @@ watch(
|
||||
<!-- 选择制剂-->
|
||||
<Select
|
||||
v-model:value="processRuleId"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
class="mt-3 w-1/5"
|
||||
placeholder="请选择制剂"
|
||||
@change="selectProcessRule"
|
||||
@@ -3131,6 +3206,7 @@ watch(
|
||||
<!-- 选择规格-->
|
||||
<Select
|
||||
v-model:value="childProcessRuleId"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
class="ml-3 mt-3 w-1/5"
|
||||
placeholder="请选择煎法"
|
||||
@change="selectProcessRuleNot"
|
||||
@@ -3146,6 +3222,7 @@ watch(
|
||||
<!-- 选择备注-->
|
||||
<Select
|
||||
v-model:value="processRuleNoteId"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
class="ml-3 mt-3 w-1/5"
|
||||
placeholder="请选择备注"
|
||||
@change="selectProcessRuleNotCommit"
|
||||
@@ -3161,17 +3238,17 @@ watch(
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
用量:
|
||||
<InputNumber v-model:value="dosage" class="w-1/5">
|
||||
<InputNumber v-model:value="dosage" :disabled="isSpecialPrescriptionCartLocked" class="w-1/5">
|
||||
<template #addonBefore>
|
||||
<MinusOutlined
|
||||
key="prop"
|
||||
@click="dosage = dosage > 1 ? dosage - 1 : 1"
|
||||
@click="!isSpecialPrescriptionCartLocked && (dosage = dosage > 1 ? dosage - 1 : 1)"
|
||||
/>
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<PlusOutlined
|
||||
key="add"
|
||||
@click="dosage = dosage < 100 ? dosage + 1 : 100"
|
||||
@click="!isSpecialPrescriptionCartLocked && (dosage = dosage < 100 ? dosage + 1 : 100)"
|
||||
/>
|
||||
</template>
|
||||
</InputNumber>
|
||||
@@ -3179,27 +3256,37 @@ watch(
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
频次:
|
||||
<InputNumber v-model:value="dayDosage" class="w-1/5">
|
||||
<InputNumber v-model:value="dayDosage" :disabled="isSpecialPrescriptionCartLocked" class="w-1/5">
|
||||
<template #addonBefore>
|
||||
<MinusOutlined
|
||||
key="prop"
|
||||
@click="dayDosage = dayDosage > 1 ? dayDosage - 1 : 1"
|
||||
@click="!isSpecialPrescriptionCartLocked && (dayDosage = dayDosage > 1 ? dayDosage - 1 : 1)"
|
||||
/>
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<PlusOutlined
|
||||
key="add"
|
||||
@click="dayDosage = dayDosage < 100 ? dayDosage + 1 : 100"
|
||||
@click="!isSpecialPrescriptionCartLocked && (dayDosage = dayDosage < 100 ? dayDosage + 1 : 100)"
|
||||
/>
|
||||
</template>
|
||||
</InputNumber>
|
||||
次/天
|
||||
</div>
|
||||
</div>
|
||||
<Button type="primary" @click="openDiagnosisModal">常用诊断</Button>
|
||||
<Textarea v-model:value="diagnosis" placeholder="输入诊断结果..."/>
|
||||
<Button type="primary" @click="openDoctorOrderModal">常用医嘱</Button>
|
||||
<Textarea v-model:value="medicalAdvice" placeholder="输入医嘱..."/>
|
||||
<Button type="primary" @click="openDiagnosisModal">
|
||||
常用诊断
|
||||
</Button>
|
||||
<Textarea
|
||||
v-model:value="diagnosis"
|
||||
placeholder="输入诊断结果..."
|
||||
/>
|
||||
<Button type="primary" @click="openDoctorOrderModal">
|
||||
常用医嘱
|
||||
</Button>
|
||||
<Textarea
|
||||
v-model:value="medicalAdvice"
|
||||
placeholder="输入医嘱..."
|
||||
/>
|
||||
诊疗费用:
|
||||
<InputNumber
|
||||
v-model:value="treatmentPrice"
|
||||
@@ -3215,7 +3302,7 @@ watch(
|
||||
<div class="total-cost">
|
||||
商品价格:
|
||||
<Button
|
||||
v-if="priceAdjustEnabled && currentDrugs.length"
|
||||
v-if="priceAdjustEnabled && currentDrugs.length && !isSpecialPrescriptionCartLocked"
|
||||
type="link"
|
||||
class="!p-0"
|
||||
@click="openOrderPriceAdjust"
|
||||
@@ -3257,6 +3344,14 @@ watch(
|
||||
>
|
||||
发送处方
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canUseCommonPrescription && currentDrugs.length"
|
||||
type="link"
|
||||
class="mt-2 w-full"
|
||||
@click="openSaveCommonPrescriptionModal"
|
||||
>
|
||||
保存为常用方
|
||||
</Button>
|
||||
</div>
|
||||
</Page>
|
||||
<div v-else-if="tabType === 0" class="prescription-panel">
|
||||
|
||||
Reference in New Issue
Block a user