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

This commit is contained in:
李琦
2026-07-03 16:50:16 +08:00
parent c8b15cd9dd
commit 7907c7cbf2
12 changed files with 1313 additions and 104 deletions

View File

@@ -33,3 +33,18 @@ export async function deleteSpecialPrescription(data: Record<string, any>) {
export async function applySpecialPrescriptionApi(data: { register_id: number }) { export async function applySpecialPrescriptionApi(data: { register_id: number }) {
return requestClient.post<any>(`${prefix}apply`, data); 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);
}

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, ref } from 'vue'; import { computed, nextTick, onMounted, ref } from 'vue';
import { useUserStore } from '@vben/stores'; import { useUserStore } from '@vben/stores';
@@ -22,6 +22,8 @@ import {
getProductListDoctorReception, getProductListDoctorReception,
} from '#/views/doctor/doctor-reception/api'; } from '#/views/doctor/doctor-reception/api';
import PrescriptionDiagnosisOrderCards from './PrescriptionDiagnosisOrderCards.vue';
export interface ChineseDrugItem { export interface ChineseDrugItem {
_key?: number; _key?: number;
id: number; id: number;
@@ -29,6 +31,7 @@ export interface ChineseDrugItem {
drug_name: string; drug_name: string;
number: number; number: number;
price: number; price: number;
buy_price?: number;
way_id: number; way_id: number;
} }
@@ -37,11 +40,26 @@ const props = withDefaults(
modelValue?: ChineseDrugItem[]; modelValue?: ChineseDrugItem[];
dosage?: number; dosage?: number;
dayDosage?: number; dayDosage?: number;
/** 固定价/SKU方案下允许编辑单价 */
priceEditable?: boolean;
/** 价格配置弹窗:药名/克数/用法只读,仅价格可改 */
structureReadonly?: boolean;
/** 预选诊断(导入特色方时带入) */
clinicalDiagnose?: string;
/** 预选医嘱 */
doctorOrder?: string;
/** 是否展示诊断/医嘱气泡卡(价格配置等场景可关闭) */
showDiagnosisOrder?: boolean;
}>(), }>(),
{ {
modelValue: () => [], modelValue: () => [],
dosage: 7, dosage: 7,
dayDosage: 2, dayDosage: 2,
priceEditable: false,
structureReadonly: false,
clinicalDiagnose: '',
doctorOrder: '',
showDiagnosisOrder: true,
}, },
); );
@@ -49,12 +67,33 @@ const emit = defineEmits<{
'update:modelValue': [value: ChineseDrugItem[]]; 'update:modelValue': [value: ChineseDrugItem[]];
'update:dosage': [value: number]; 'update:dosage': [value: number];
'update:dayDosage': [value: number]; 'update:dayDosage': [value: number];
'update:clinicalDiagnose': [value: string];
'update:doctorOrder': [value: string];
}>(); }>();
const userStore = useUserStore(); const userStore = useUserStore();
const drugList = ref<ChineseDrugItem[]>([]); const drugList = ref<ChineseDrugItem[]>([]);
const dosageLocal = ref(props.dosage); const dosageLocal = ref(props.dosage);
const dayDosageLocal = ref(props.dayDosage); 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 drugUseWay = ref<any[]>([]);
const chineseSearchResults = ref<any[]>([]); const chineseSearchResults = ref<any[]>([]);
const newDrugInfo = ref<{ 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() { function syncToParent() {
emit('update:modelValue', [...drugList.value]); emit('update:modelValue', [...drugList.value]);
emit('update:dosage', dosageLocal.value); emit('update:dosage', dosageLocal.value);
@@ -200,21 +245,66 @@ function onDosageChange() {
syncToParent(); syncToParent();
} }
function loadDrugs(recipes: any[], dosage?: number, dayDosage?: number) { function loadDrugs(recipes: any[], dosage?: number, dayDosage?: number, drugPrices?: any[]) {
drugList.value = (recipes || []).map((recipe) => ({ const priceMap = new Map<number, { buy_price: number; sell_price: number }>();
_key: Date.now() + Math.random(), (drugPrices || []).forEach((item) => {
id: recipe.drug_id || recipe.id, priceMap.set(Number(item.drug_id), {
drug_id: recipe.drug_id || recipe.id, buy_price: Number(item.buy_price || 0),
drug_name: recipe.drug_name || recipe.name || '', sell_price: Number(item.sell_price || item.price || 0),
number: recipe.number || 1, });
price: recipe.price || 0, });
way_id: recipe.way_id || 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 (dosage !== undefined) dosageLocal.value = dosage;
if (dayDosage !== undefined) dayDosageLocal.value = dayDosage; if (dayDosage !== undefined) dayDosageLocal.value = dayDosage;
syncToParent(); 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() { function getDrugsPayload() {
return drugList.value.map((drug) => ({ return drugList.value.map((drug) => ({
drug_id: drug.drug_id || drug.id, drug_id: drug.drug_id || drug.id,
@@ -227,14 +317,22 @@ function getDrugsPayload() {
defineExpose({ defineExpose({
getDrugsPayload, getDrugsPayload,
getDrugPricesPayload,
getDrugsForDistribute,
applyDistributedPrices,
loadDrugs, loadDrugs,
drugList, drugList,
getDiagnosisOrderPayload: () =>
diagnosisOrderCardsRef.value?.getDiagnosisOrderPayload?.() ?? {
clinical_diagnose: props.clinicalDiagnose || '',
doctor_order: props.doctorOrder || '',
},
}); });
</script> </script>
<template> <template>
<div class="chinese-drug-editor"> <div class="chinese-drug-editor">
<Row :gutter="16" class="mb-4"> <Row v-if="!structureReadonly" :gutter="16" class="mb-4">
<Col :span="12"> <Col :span="12">
<FormItem label="剂量(天/剂)"> <FormItem label="剂量(天/剂)">
<InputNumber <InputNumber
@@ -258,8 +356,17 @@ defineExpose({
</FormItem> </FormItem>
</Col> </Col>
</Row> </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]"> <Row :gutter="[12, 12]">
<Col <Col
v-for="(drug, index) in drugList" v-for="(drug, index) in drugList"
@@ -272,7 +379,14 @@ defineExpose({
<Card size="small" class="chinese-drug-card"> <Card size="small" class="chinese-drug-card">
<div class="chinese-drug-content"> <div class="chinese-drug-content">
<span class="chinese-drug-index">{{ index + 1 }}、</span> <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 <Select
v-model:value="drug.id" v-model:value="drug.id"
show-search show-search
@@ -322,16 +436,50 @@ defineExpose({
</Select> </Select>
</div> </div>
<div class="chinese-drug-price"> <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> </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" /> <DeleteTwoTone two-tone-color="#ff4d4f" />
</Button> </Button>
</div> </div>
</Card> </Card>
</Col> </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"> <Card size="small" class="chinese-drug-card chinese-drug-card-new">
<div class="chinese-drug-content"> <div class="chinese-drug-content">
<span class="chinese-drug-index">{{ drugList.length + 1 }}、</span> <span class="chinese-drug-index">{{ drugList.length + 1 }}、</span>
@@ -427,6 +575,16 @@ defineExpose({
max-width: 120px; 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 { .chinese-drug-comma {
@apply text-muted-foreground; @apply text-muted-foreground;
} }
@@ -440,6 +598,19 @@ defineExpose({
right: 24px; right: 24px;
bottom: 0; bottom: 0;
@apply text-destructive font-medium; @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 { .chinese-drug-delete {

View File

@@ -18,6 +18,8 @@ const prescriptionType = ref<'chinese' | 'west' | 'granular'>('chinese');
const chineseDrugEditorRef = ref<InstanceType<typeof ChineseDrugEditor>>(); const chineseDrugEditorRef = ref<InstanceType<typeof ChineseDrugEditor>>();
const dosage = ref(7); const dosage = ref(7);
const dayDosage = ref(2); const dayDosage = ref(2);
const clinicalDiagnose = ref('');
const doctorOrder = ref('');
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
fullscreenButton: true, fullscreenButton: true,
@@ -38,12 +40,19 @@ const [Modal, modalApi] = useVbenModal({
message.warning('请至少添加一种中药'); message.warning('请至少添加一种中药');
return; return;
} }
const diagnosisOrder =
chineseDrugEditorRef.value?.getDiagnosisOrderPayload?.() ?? {
clinical_diagnose: clinicalDiagnose.value,
doctor_order: doctorOrder.value,
};
const payload = buildSpecialPrescriptionPayload(detailRes.value, { const payload = buildSpecialPrescriptionPayload(detailRes.value, {
drugs, drugs,
dosage: dosage.value, dosage: dosage.value,
day_dosage: dayDosage.value, day_dosage: dayDosage.value,
rule_type: 1, rule_type: 1,
package_method_id: 2, package_method_id: 2,
clinical_diagnose: diagnosisOrder.clinical_diagnose ?? '',
doctor_order: diagnosisOrder.doctor_order ?? '',
}); });
modalApi.setState({ loading: true, confirmLoading: true }); modalApi.setState({ loading: true, confirmLoading: true });
updateSpecialPrescription(payload) updateSpecialPrescription(payload)
@@ -65,12 +74,15 @@ const [Modal, modalApi] = useVbenModal({
if (!res) return; if (!res) return;
detailRes.value = res; detailRes.value = res;
prescriptionType.value = res.prescription_type || 'chinese'; 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) { if (res.prescription_type === 'chinese' && res.prescription_detail?.length) {
const first = res.prescription_detail[0]; const first = res.prescription_detail[0];
chineseDrugEditorRef.value?.loadDrugs( chineseDrugEditorRef.value?.loadDrugs(
res.prescription_detail, res.prescription_detail,
first?.dosage ?? 7, first?.dosage ?? 7,
first?.consumption ?? 2, first?.consumption ?? 2,
res.drug_prices || [],
); );
dosage.value = first?.dosage ?? 7; dosage.value = first?.dosage ?? 7;
dayDosage.value = first?.consumption ?? 2; dayDosage.value = first?.consumption ?? 2;
@@ -80,6 +92,8 @@ const [Modal, modalApi] = useVbenModal({
}); });
} else { } else {
detailRes.value = null; detailRes.value = null;
clinicalDiagnose.value = '';
doctorOrder.value = '';
} }
}, },
}); });
@@ -90,6 +104,8 @@ const [Modal, modalApi] = useVbenModal({
<div v-if="prescriptionType === 'chinese'"> <div v-if="prescriptionType === 'chinese'">
<ChineseDrugEditor <ChineseDrugEditor
ref="chineseDrugEditorRef" ref="chineseDrugEditorRef"
v-model:clinical-diagnose="clinicalDiagnose"
v-model:doctor-order="doctorOrder"
v-model:dosage="dosage" v-model:dosage="dosage"
v-model:day-dosage="dayDosage" v-model:day-dosage="dayDosage"
/> />

View File

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

View File

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

View File

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

View File

@@ -22,6 +22,9 @@ const chineseDrugEditorRef = ref<InstanceType<typeof ChineseDrugEditor>>();
const dosage = ref(7); const dosage = ref(7);
const dayDosage = ref(2); const dayDosage = ref(2);
const drugsChanged = ref(false); const drugsChanged = ref(false);
/** 预选诊断/医嘱(在药品列表气泡卡中编辑) */
const clinicalDiagnose = ref('');
const doctorOrder = ref('');
const [Form, formApi] = useVbenForm(modalFormProps); const [Form, formApi] = useVbenForm(modalFormProps);
@@ -64,6 +67,14 @@ const [Modal, modalApi] = useVbenModal({
.filter(Boolean), .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) { if (prescriptionType.value === 'chinese' && drugs.length > 0) {
payload.drugs = drugs; payload.drugs = drugs;
payload.dosage = dosage.value; payload.dosage = dosage.value;
@@ -110,6 +121,8 @@ const [Modal, modalApi] = useVbenModal({
intro_text: res.intro_text || '', intro_text: res.intro_text || '',
introduction_images: res.introduction_images || [], introduction_images: res.introduction_images || [],
}); });
clinicalDiagnose.value = res.clinical_diagnose || '';
doctorOrder.value = res.doctor_order || '';
if (res.prescription_type === 'chinese' && res.prescription_detail?.length) { if (res.prescription_type === 'chinese' && res.prescription_detail?.length) {
const first = res.prescription_detail[0]; const first = res.prescription_detail[0];
chineseDrugEditorRef.value?.loadDrugs( chineseDrugEditorRef.value?.loadDrugs(
@@ -140,6 +153,8 @@ const [Modal, modalApi] = useVbenModal({
intro_text: '', intro_text: '',
introduction_images: [], introduction_images: [],
}); });
clinicalDiagnose.value = '';
doctorOrder.value = '';
chineseDrugEditorRef.value?.loadDrugs([], 7, 2); chineseDrugEditorRef.value?.loadDrugs([], 7, 2);
} }
} else { } else {
@@ -168,6 +183,8 @@ function bindPrescriptionTypeChange() {
<div v-if="prescriptionType === 'chinese'" class="mt-4 border-t pt-4"> <div v-if="prescriptionType === 'chinese'" class="mt-4 border-t pt-4">
<ChineseDrugEditor <ChineseDrugEditor
ref="chineseDrugEditorRef" ref="chineseDrugEditorRef"
v-model:clinical-diagnose="clinicalDiagnose"
v-model:doctor-order="doctorOrder"
v-model:dosage="dosage" v-model:dosage="dosage"
v-model:day-dosage="dayDosage" v-model:day-dosage="dayDosage"
@update:model-value="drugsChanged = true" @update:model-value="drugsChanged = true"

View File

@@ -70,7 +70,7 @@ export const modalFormProps: VbenFormProps = {
{ {
component: 'InputNumber', component: 'InputNumber',
componentProps: { componentProps: {
placeholder: '每剂价格', placeholder: '展示兜底价/贴',
min: 0, min: 0,
precision: 2, precision: 2,
style: { width: '100%' }, style: { width: '100%' },
@@ -106,6 +106,20 @@ export const modalFormProps: VbenFormProps = {
formItemClass: 'col-span-6', formItemClass: 'col-span-6',
defaultValue: 1, 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', component: 'VbenSelect',
componentProps: { componentProps: {

View File

@@ -15,6 +15,7 @@ import {
updateSpecialPrescriptionStatus, updateSpecialPrescriptionStatus,
} from './api'; } from './api';
import EditPrescriptionModal from './components/EditPrescriptionModal.vue'; import EditPrescriptionModal from './components/EditPrescriptionModal.vue';
import PriceConfigModal from './components/PriceConfigModal.vue';
import SpecialPrescriptionModal from './components/modal.vue'; import SpecialPrescriptionModal from './components/modal.vue';
import { formOptions as searchFormOptions } from './config/search'; import { formOptions as searchFormOptions } from './config/search';
import { gridOptions } from './config/table'; import { gridOptions } from './config/table';
@@ -47,6 +48,10 @@ const [EditPrescriptionModalComp, editPrescriptionModalApi] = useVbenModal({
connectedComponent: EditPrescriptionModal, connectedComponent: EditPrescriptionModal,
}); });
const [PriceConfigModalComp, priceConfigModalApi] = useVbenModal({
connectedComponent: PriceConfigModal,
});
const showModal = (data = {}, isUpdate = false) => { const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({ formModalApi.setData({
values: data, values: data,
@@ -64,6 +69,14 @@ const showEditPrescriptionModal = (row: any) => {
editPrescriptionModalApi.open(); editPrescriptionModalApi.open();
}; };
const showPriceConfigModal = (row: any) => {
priceConfigModalApi.setData({
values: row,
gridApi,
});
priceConfigModalApi.open();
};
/** 点击状态 Tag 切换上下架 */ /** 点击状态 Tag 切换上下架 */
const toggleStatus = async (row: any) => { const toggleStatus = async (row: any) => {
if (statusLoadingId.value === row.id) { if (statusLoadingId.value === row.id) {
@@ -98,6 +111,7 @@ const deleteApi = (row: any) => {
<Page auto-content-height title="特色方管理"> <Page auto-content-height title="特色方管理">
<FormModal /> <FormModal />
<EditPrescriptionModalComp /> <EditPrescriptionModalComp />
<PriceConfigModalComp />
<div class="p-4"> <div class="p-4">
<TableAction <TableAction
@@ -161,6 +175,10 @@ const deleteApi = (row: any) => {
ifShow: row.prescription_sub_type === 1, ifShow: row.prescription_sub_type === 1,
onClick: () => showEditPrescriptionModal(row), onClick: () => showEditPrescriptionModal(row),
}, },
{
label: '价格配置',
onClick: () => showPriceConfigModal(row),
},
{ {
label: '删除', label: '删除',
color: 'error', color: 'error',

View File

@@ -9,6 +9,8 @@ export function buildSpecialPrescriptionPayload(
day_dosage?: number; day_dosage?: number;
rule_type?: number; rule_type?: number;
package_method_id?: number; package_method_id?: number;
clinical_diagnose?: string;
doctor_order?: string;
}, },
) { ) {
const payload: Record<string, any> = { const payload: Record<string, any> = {
@@ -20,9 +22,12 @@ export function buildSpecialPrescriptionPayload(
price_per_dose: detail.price_per_dose ?? 0, price_per_dose: detail.price_per_dose ?? 0,
sales_count: detail.sales_count ?? 0, sales_count: detail.sales_count ?? 0,
status: detail.status ?? 1, status: detail.status ?? 1,
is_free_shipping: detail.is_free_shipping ?? 0,
prescription_type: detail.prescription_type || 'chinese', prescription_type: detail.prescription_type || 'chinese',
intro_text: detail.intro_text || '', intro_text: detail.intro_text || '',
introduction_images: detail.introduction_images || [], introduction_images: detail.introduction_images || [],
clinical_diagnose: detail.clinical_diagnose || '',
doctor_order: detail.doctor_order || '',
}; };
if (drugOverrides?.drugs?.length) { if (drugOverrides?.drugs?.length) {
payload.drugs = drugOverrides.drugs; payload.drugs = drugOverrides.drugs;
@@ -31,5 +36,11 @@ export function buildSpecialPrescriptionPayload(
payload.rule_type = drugOverrides.rule_type ?? 1; payload.rule_type = drugOverrides.rule_type ?? 1;
payload.package_method_id = drugOverrides.package_method_id ?? 2; 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; return payload;
} }

View File

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

View File

@@ -52,6 +52,7 @@ import {
withdrawPrescriptionApi, withdrawPrescriptionApi,
} from '#/views/doctor/doctor-reception/api'; } from '#/views/doctor/doctor-reception/api';
import SpecialPrescriptionImportCard from './components/SpecialPrescriptionImportCard.vue';
import DiagnosisModal from './components/DiagnosisModal.vue'; import DiagnosisModal from './components/DiagnosisModal.vue';
import DoctorOrderModal from './components/DoctorOrderModal.vue'; import DoctorOrderModal from './components/DoctorOrderModal.vue';
import PrescriptionDetail from './components/PrescriptionDetail.vue'; import PrescriptionDetail from './components/PrescriptionDetail.vue';
@@ -108,6 +109,8 @@ interface Patient {
status: number; status: number;
special_prescription_id: number; special_prescription_id: number;
dose_count: number; dose_count: number;
sku_id?: number;
sku_name?: string;
special_prescription?: { special_prescription?: {
id: number; id: number;
name: string; name: string;
@@ -346,7 +349,40 @@ const hasSpecialPrescriptionRecord = computed(
const specialPrescriptionRecord = computed( const specialPrescriptionRecord = computed(
() => patientInfo.value?.special_prescription_patient_record ?? null, () => 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); 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 diagnosis = ref('');
const medicalAdvice = ref(''); const medicalAdvice = ref('');
const treatmentPrice = ref(0); const treatmentPrice = ref(0);
@@ -398,6 +434,7 @@ function selectProductChange(id) {
* @param id * @param id
*/ */
function selectDrugUseWayChange(id) { function selectDrugUseWayChange(id) {
if (guardSpecialPrescriptionCartEdit()) return;
if ( if (
selectProductIndex.value < 0 || selectProductIndex.value < 0 ||
selectProductIndex.value >= currentDrugs.value.length selectProductIndex.value >= currentDrugs.value.length
@@ -417,6 +454,14 @@ function selectDrugUseWayChange(id) {
* 计算商品总价 * 计算商品总价
*/ */
const totalProductCost = computed(() => { 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) { if (currentDrugs.value.length === 0) {
return 0; return 0;
} }
@@ -509,6 +554,7 @@ const setVisible = (value, instruction = ''): void => {
* @param isUpdateTabType * @param isUpdateTabType
*/ */
const selectPatient = (patient: Patient, isUpdateTabType = true) => { const selectPatient = (patient: Patient, isUpdateTabType = true) => {
clearAppliedSpecialPrescription();
selectPatientId.value = patient.id; selectPatientId.value = patient.id;
receptionStatus.value = patient.status; receptionStatus.value = patient.status;
if (isUpdateTabType === true) { if (isUpdateTabType === true) {
@@ -561,12 +607,14 @@ const selectPatient = (patient: Patient, isUpdateTabType = true) => {
// 数量增加 // 数量增加
const increment = (index: number) => { const increment = (index: number) => {
if (guardSpecialPrescriptionCartEdit()) return;
currentDrugs.value[index].select_number++; currentDrugs.value[index].select_number++;
saveToLocalStorage(); saveToLocalStorage();
}; };
// 数量减少 // 数量减少
const decrement = (index: number) => { const decrement = (index: number) => {
if (guardSpecialPrescriptionCartEdit()) return;
if (currentDrugs.value[index].select_number > 1) { if (currentDrugs.value[index].select_number > 1) {
currentDrugs.value[index].select_number--; currentDrugs.value[index].select_number--;
saveToLocalStorage(); saveToLocalStorage();
@@ -575,6 +623,7 @@ const decrement = (index: number) => {
// 切换药品用法编辑状态 // 切换药品用法编辑状态
const toggleEditDrug = (index: number) => { const toggleEditDrug = (index: number) => {
if (guardSpecialPrescriptionCartEdit()) return;
currentDrugs.value[index].isEditing = !currentDrugs.value[index].isEditing; currentDrugs.value[index].isEditing = !currentDrugs.value[index].isEditing;
}; };
@@ -635,6 +684,7 @@ function clearCartPriceDiscount() {
const priceDiscountLabel = computed(() => formatPriceDiscountLabel(priceDiscount.value)); const priceDiscountLabel = computed(() => formatPriceDiscountLabel(priceDiscount.value));
function openOrderPriceAdjust() { function openOrderPriceAdjust() {
if (guardSpecialPrescriptionCartEdit()) return;
if (!priceAdjustEnabled.value || !currentDrugs.value.length) return; if (!priceAdjustEnabled.value || !currentDrugs.value.length) return;
currentDrugs.value = snapshotDrugOrigins(currentDrugs.value); currentDrugs.value = snapshotDrugOrigins(currentDrugs.value);
const discount = Number(priceDiscount.value); const discount = Number(priceDiscount.value);
@@ -654,6 +704,7 @@ function openOrderPriceAdjust() {
* @param index * @param index
*/ */
const removeDrug = (index: number) => { const removeDrug = (index: number) => {
if (guardSpecialPrescriptionCartEdit()) return;
currentDrugs.value.splice(index, 1); currentDrugs.value.splice(index, 1);
updateLocalStorage(); updateLocalStorage();
}; };
@@ -790,6 +841,7 @@ const sendPrescription = () => {
doctor_second_sign: doctorSecondSign.value, doctor_second_sign: doctorSecondSign.value,
salesperson_transfer_prescription_id: salespersonTransferPrescriptionId.value || undefined, salesperson_transfer_prescription_id: salespersonTransferPrescriptionId.value || undefined,
price_discount: priceDiscount.value, price_discount: priceDiscount.value,
special_prescription_id: appliedSpecialPrescriptionId.value || 0,
}).then((res) => { }).then((res) => {
message.success('处方已发送'); message.success('处方已发送');
@@ -807,17 +859,10 @@ const sendPrescription = () => {
} }
// 清空当前数据 // 清空当前数据
currentDrugs.value = []; clearPrescriptionCart(false);
diagnosis.value = '';
medicalAdvice.value = '';
packageMethodId.value = 2;
processRulePrice.value = 0;
dosage.value = 7;
dayDosage.value = 2;
tabType.value = 1; tabType.value = 1;
salespersonTransferPrescriptionId.value = 0; salespersonTransferPrescriptionId.value = 0;
newDrugInfo.value = {}; processRulePrice.value = 0;
updateLocalStorage();
checkSalespersonTransfer(); checkSalespersonTransfer();
// 刷新患者信息 // 刷新患者信息
@@ -898,7 +943,7 @@ const [DoctorOrderModals, DoctorOrderModalApi] = useVbenModal({
}); });
const openDoctorOrderModal = () => { const openDoctorOrderModal = () => {
// 打开常用诊断模态框逻辑 // 打开常用医嘱模态框逻辑
DoctorOrderModalApi.setData({ DoctorOrderModalApi.setData({
values: medicalAdvice.value, values: medicalAdvice.value,
updateDoctorOrder, updateDoctorOrder,
@@ -953,6 +998,7 @@ function openCommonPrescriptionModal() {
* @description 选择常用方后,将药品列表填充到当前处方中 * @description 选择常用方后,将药品列表填充到当前处方中
*/ */
async function handleSelectCommonPrescription(data: any, type: number) { async function handleSelectCommonPrescription(data: any, type: number) {
clearAppliedSpecialPrescription();
const { prescription, recipes } = data; const { prescription, recipes } = data;
// 根据类型处理药品:整表覆盖为常用方内容,不与当前处方按药品 ID 去重合并 // 根据类型处理药品:整表覆盖为常用方内容,不与当前处方按药品 ID 去重合并
@@ -1069,6 +1115,10 @@ async function handleApplySpecialPrescription(switchToPrescription = false) {
typeMap[prescriptionType] || 2, 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) { if (selectPatientId.value) {
const value = await getPatientItem(selectPatientId.value); const value = await getPatientItem(selectPatientId.value);
patientInfo.value = value; patientInfo.value = value;
@@ -1197,6 +1247,7 @@ const cancelSaveCommonPrescription = () => {
}; };
const openWesternModal = () => { const openWesternModal = () => {
if (guardSpecialPrescriptionCartEdit()) return;
// 判断是否为简单产品类型(3,5,6,7) // 判断是否为简单产品类型(3,5,6,7)
const simpleProductTypes = [3, 5, 6, 7]; const simpleProductTypes = [3, 5, 6, 7];
if (simpleProductTypes.includes(activeCategory.value)) { if (simpleProductTypes.includes(activeCategory.value)) {
@@ -1674,6 +1725,7 @@ loadProcessRuleData();
* @param id * @param id
*/ */
function selectProcessRule(id) { function selectProcessRule(id) {
if (guardSpecialPrescriptionCartEdit()) return;
processRuleId.value = id; processRuleId.value = id;
loadProcessRuleData(id); // 加载煎法选项 loadProcessRuleData(id); // 加载煎法选项
} }
@@ -1683,6 +1735,7 @@ function selectProcessRule(id) {
* @param id * @param id
*/ */
function selectProcessRuleNot(id) { function selectProcessRuleNot(id) {
if (guardSpecialPrescriptionCartEdit()) return;
childProcessRuleId.value = id; childProcessRuleId.value = id;
loadProcessRuleData(0, id); // 加载备注选项 loadProcessRuleData(0, id); // 加载备注选项
} }
@@ -1692,6 +1745,7 @@ function selectProcessRuleNot(id) {
* @param id * @param id
*/ */
function selectProcessRuleNotCommit(id) { function selectProcessRuleNotCommit(id) {
if (guardSpecialPrescriptionCartEdit()) return;
processRuleNoteId.value = id; processRuleNoteId.value = id;
} }
@@ -1702,6 +1756,7 @@ const packageMethod = [
]; ];
function selectPackageMethod(id) { function selectPackageMethod(id) {
if (guardSpecialPrescriptionCartEdit()) return;
packageMethodId.value = id; packageMethodId.value = id;
} }
@@ -1709,6 +1764,41 @@ function selectPackageMethod(id) {
const dosage = ref(7); const dosage = ref(7);
const dayDosage = ref(2); 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() { function updateChineseNumber() {
updateLocalStorage(); updateLocalStorage();
} }
@@ -1833,6 +1923,7 @@ const selectChineseId = ref(0);
* @param id * @param id
*/ */
function selectOldDrugInfo(id) { function selectOldDrugInfo(id) {
if (guardSpecialPrescriptionCartEdit()) return;
const check = JSON.parse( const check = JSON.parse(
localStorage.getItem(getStorageKey(activePatient.value?.id, activeCategory.value)) || '[]', localStorage.getItem(getStorageKey(activePatient.value?.id, activeCategory.value)) || '[]',
).find((v) => v.id === id); ).find((v) => v.id === id);
@@ -1923,6 +2014,7 @@ function searchOption(inputValue) {
* 添加中药 * 添加中药
*/ */
function addDrugByChinese() { function addDrugByChinese() {
if (guardSpecialPrescriptionCartEdit()) return;
// 如果是新药品输入框,调用添加新药品方法 // 如果是新药品输入框,调用添加新药品方法
const check = currentDrugs.value.find( const check = currentDrugs.value.find(
(v) => v.id === newDrugInfo.value.id, (v) => v.id === newDrugInfo.value.id,
@@ -1976,6 +2068,7 @@ function selectDrugByNewDrugInfo(event: KeyboardEvent, isNewDrug: boolean) {
* @param {object} data - 商品对象 * @param {object} data - 商品对象
*/ */
function addProducts(data) { function addProducts(data) {
if (guardSpecialPrescriptionCartEdit()) return;
// 查找是否已存在于选择列表中 // 查找是否已存在于选择列表中
const existItem = currentDrugs.value.find( const existItem = currentDrugs.value.find(
(item) => item.index_id === data.id, (item) => item.index_id === data.id,
@@ -2046,6 +2139,7 @@ function addProducts(data) {
* @param drug 选中的药品数据 * @param drug 选中的药品数据
*/ */
function handleSimpleProductSelect(drug: any) { function handleSimpleProductSelect(drug: any) {
if (guardSpecialPrescriptionCartEdit()) return;
// 检查是否已存在 // 检查是否已存在
const existItem = currentDrugs.value.find( const existItem = currentDrugs.value.find(
(item) => item.id === (drug._drugId || drug.drug_id || drug.drug?.id), (item) => item.id === (drug._drugId || drug.drug_id || drug.drug?.id),
@@ -2285,52 +2379,13 @@ watch(
}} }}
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
<Card <SpecialPrescriptionImportCard
v-if="specialPrescriptionRecord" v-if="specialPrescriptionRecord && hasSpecialPrescriptionRecord"
class="mt-5" :record="specialPrescriptionRecord"
title="特色方" :purchase-label="specialPrescriptionPurchaseLabel"
> :loading="applyingSpecialPrescription"
<div class="flex flex-wrap items-center gap-4"> @apply="handleApplySpecialPrescription(true)"
<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>
<Descriptions <Descriptions
v-if="userPatientHealthInquiry" v-if="userPatientHealthInquiry"
:column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 2, xs: 2 }" :column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 2, xs: 2 }"
@@ -2520,15 +2575,23 @@ watch(
</button> </button>
</div> </div>
<SpecialPrescriptionImportCard
v-if="hasSpecialPrescriptionRecord"
class="mt-5 max-w-xl"
:record="specialPrescriptionRecord"
:purchase-label="specialPrescriptionPurchaseLabel"
:loading="applyingSpecialPrescription"
@apply="() => handleApplySpecialPrescription()"
/>
<div class="mt-5"> <div class="mt-5">
<Button <Button
v-if="activeCategory === 2" v-if="activeCategory === 2 && !isSpecialPrescriptionCartLocked"
type="primary" type="primary"
@click="openWesternModal" @click="openWesternModal"
> >
添加商品 添加商品
</Button> </Button>
<!-- 常用方按钮(产品服务包/非药品/医疗器械无模板接口) -->
<Button <Button
v-if="canUseCommonPrescription" v-if="canUseCommonPrescription"
type="primary" type="primary"
@@ -2537,15 +2600,6 @@ watch(
> >
选择常用方 选择常用方
</Button> </Button>
<Button
v-if="hasSpecialPrescriptionRecord"
type="primary"
class="ml-3"
:loading="applyingSpecialPrescription"
@click="() => handleApplySpecialPrescription()"
>
应用特色方
</Button>
<Button <Button
v-if="canUseCommonPrescription" v-if="canUseCommonPrescription"
type="default" type="default"
@@ -2555,6 +2609,14 @@ watch(
<SaveOutlined /> <SaveOutlined />
保存常用方 保存常用方
</Button> </Button>
<Button
type="default"
danger
class="ml-3"
@click="handleClearPrescriptionCart"
>
清空药方
</Button>
<Button <Button
v-if="activeCategory === 1 && hasSalespersonTransfer" v-if="activeCategory === 1 && hasSalespersonTransfer"
type="default" type="default"
@@ -2613,6 +2675,7 @@ watch(
<p> <p>
<Select <Select
v-model:value="drug.id" v-model:value="drug.id"
:disabled="isSpecialPrescriptionCartLocked"
:filter-option="false" :filter-option="false"
class="old-select-drug-name" class="old-select-drug-name"
style="min-width: 100px" style="min-width: 100px"
@@ -2639,6 +2702,7 @@ watch(
, ,
<InputNumber <InputNumber
v-model:value="drug.number" v-model:value="drug.number"
:disabled="isSpecialPrescriptionCartLocked"
:class="`w-1/5 old-number-input-${index}`" :class="`w-1/5 old-number-input-${index}`"
style="min-width: 100px" style="min-width: 100px"
:controls="false" :controls="false"
@@ -2651,6 +2715,7 @@ watch(
<span> <span>
<Select <Select
:value="drug?.way_id" :value="drug?.way_id"
:disabled="isSpecialPrescriptionCartLocked"
placeholder="用法" placeholder="用法"
@change="selectDrugUseWayChange" @change="selectDrugUseWayChange"
@dropdown-visible-change=" @dropdown-visible-change="
@@ -2677,6 +2742,7 @@ watch(
</span> </span>
</p> </p>
<Button <Button
v-if="!isSpecialPrescriptionCartLocked"
class="card-delete" class="card-delete"
type="link" type="link"
@click="removeDrug(index)" @click="removeDrug(index)"
@@ -2688,6 +2754,7 @@ watch(
</Card> </Card>
</Col> </Col>
<Col <Col
v-if="!isSpecialPrescriptionCartLocked"
:lg="12" :lg="12"
:md="12" :md="12"
:sm="12" :sm="12"
@@ -2943,6 +3010,7 @@ watch(
<span>用法:</span> <span>用法:</span>
<Select <Select
:value="drug.use_type?.id" :value="drug.use_type?.id"
:disabled="isSpecialPrescriptionCartLocked"
size="small" size="small"
style="width: 80px" style="width: 80px"
@change="(val) => updateDrugUsage(index, 'use_type', drugUseType.find(item => item.id === val))" @change="(val) => updateDrugUsage(index, 'use_type', drugUseType.find(item => item.id === val))"
@@ -2953,6 +3021,7 @@ watch(
</Select> </Select>
<Select <Select
:value="drug.use_frequency?.id" :value="drug.use_frequency?.id"
:disabled="isSpecialPrescriptionCartLocked"
size="small" size="small"
style="width: 100px; margin-left: 4px" style="width: 100px; margin-left: 4px"
@change="(val) => updateDrugUsage(index, 'use_frequency', drugUseFrequency.find(item => item.id === val))" @change="(val) => updateDrugUsage(index, 'use_frequency', drugUseFrequency.find(item => item.id === val))"
@@ -2963,6 +3032,7 @@ watch(
</Select> </Select>
<Select <Select
:value="drug.use_num?.id" :value="drug.use_num?.id"
:disabled="isSpecialPrescriptionCartLocked"
size="small" size="small"
style="width: 80px; margin-left: 4px" style="width: 80px; margin-left: 4px"
@change="(val) => updateDrugUsage(index, 'use_num', drugTime.find(item => item.id === val))" @change="(val) => updateDrugUsage(index, 'use_num', drugTime.find(item => item.id === val))"
@@ -2974,12 +3044,14 @@ watch(
<span style="margin-left: 4px">每次</span> <span style="margin-left: 4px">每次</span>
<InputNumber <InputNumber
v-model:value="drug.number" v-model:value="drug.number"
:disabled="isSpecialPrescriptionCartLocked"
:min="1" :min="1"
size="small" size="small"
style="width: 60px; margin-left: 4px" style="width: 60px; margin-left: 4px"
/> />
<Select <Select
:value="drug.unit?.id" :value="drug.unit?.id"
:disabled="isSpecialPrescriptionCartLocked"
size="small" size="small"
style="width: 60px; margin-left: 4px" style="width: 60px; margin-left: 4px"
@change="(val) => updateDrugUsage(index, 'unit', drugUnit.find(item => item.id === val))" @change="(val) => updateDrugUsage(index, 'unit', drugUnit.find(item => item.id === val))"
@@ -3001,15 +3073,15 @@ watch(
</InputNumber> </InputNumber>
</div> </div>
<div v-else class="quantity-control"> <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> <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>
</div> </div>
<span>{{ drug.price }}</span> <span>{{ drug.price }}</span>
<div> <div>
<Button <Button
v-if="activeCategory !== 1 && !drug.isEditing" v-if="activeCategory !== 1 && !drug.isEditing && !isSpecialPrescriptionCartLocked"
type="link" type="link"
@click="toggleEditDrug(index)" @click="toggleEditDrug(index)"
>编辑</Button> >编辑</Button>
@@ -3018,7 +3090,7 @@ watch(
type="link" type="link"
@click="saveEditDrug(index)" @click="saveEditDrug(index)"
>保存</Button> >保存</Button>
<Button type="link" @click="removeDrug(index)">删除</Button> <Button v-if="!isSpecialPrescriptionCartLocked" type="link" @click="removeDrug(index)">删除</Button>
<Button <Button
v-if="drug.instruction !== ''" v-if="drug.instruction !== ''"
type="link" type="link"
@@ -3036,6 +3108,7 @@ watch(
<!-- 搜索组件 --> <!-- 搜索组件 -->
<div class="mb-5"> <div class="mb-5">
<DrugSearchSelect <DrugSearchSelect
v-if="!isSpecialPrescriptionCartLocked"
:type="activeCategory" :type="activeCategory"
:store-id="myStoreId" :store-id="myStoreId"
:placeholder="`输入${categories.find(c => c.value === activeCategory)?.label || '商品'}名称搜索`" :placeholder="`输入${categories.find(c => c.value === activeCategory)?.label || '商品'}名称搜索`"
@@ -3067,14 +3140,14 @@ watch(
</div> </div>
<div> <div>
<div class="quantity-control"> <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> <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>
</div> </div>
<span>{{ drug.price }}</span> <span>{{ drug.price }}</span>
<div> <div>
<Button type="link" @click="removeDrug(index)">删除</Button> <Button v-if="!isSpecialPrescriptionCartLocked" type="link" @click="removeDrug(index)">删除</Button>
<Button <Button
v-if="drug.instruction !== ''" v-if="drug.instruction !== ''"
type="link" type="link"
@@ -3091,7 +3164,7 @@ watch(
<!-- 诊断和医嘱 --> <!-- 诊断和医嘱 -->
<div class="diagnosis-area"> <div class="diagnosis-area">
<div v-if="activeCategory === 1" class="mb-5"> <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="1">自制剂</RadioButton>
<RadioButton :value="2">委托调剂</RadioButton> <RadioButton :value="2">委托调剂</RadioButton>
</RadioGroup> </RadioGroup>
@@ -3099,6 +3172,7 @@ watch(
<!-- 选择包法--> <!-- 选择包法-->
<Select <Select
:value="packageMethodId" :value="packageMethodId"
:disabled="isSpecialPrescriptionCartLocked"
class="mt-3 w-1/5" class="mt-3 w-1/5"
placeholder="请选择包法" placeholder="请选择包法"
@change="selectPackageMethod" @change="selectPackageMethod"
@@ -3116,6 +3190,7 @@ watch(
<!-- 选择制剂--> <!-- 选择制剂-->
<Select <Select
v-model:value="processRuleId" v-model:value="processRuleId"
:disabled="isSpecialPrescriptionCartLocked"
class="mt-3 w-1/5" class="mt-3 w-1/5"
placeholder="请选择制剂" placeholder="请选择制剂"
@change="selectProcessRule" @change="selectProcessRule"
@@ -3131,6 +3206,7 @@ watch(
<!-- 选择规格--> <!-- 选择规格-->
<Select <Select
v-model:value="childProcessRuleId" v-model:value="childProcessRuleId"
:disabled="isSpecialPrescriptionCartLocked"
class="ml-3 mt-3 w-1/5" class="ml-3 mt-3 w-1/5"
placeholder="请选择煎法" placeholder="请选择煎法"
@change="selectProcessRuleNot" @change="selectProcessRuleNot"
@@ -3146,6 +3222,7 @@ watch(
<!-- 选择备注--> <!-- 选择备注-->
<Select <Select
v-model:value="processRuleNoteId" v-model:value="processRuleNoteId"
:disabled="isSpecialPrescriptionCartLocked"
class="ml-3 mt-3 w-1/5" class="ml-3 mt-3 w-1/5"
placeholder="请选择备注" placeholder="请选择备注"
@change="selectProcessRuleNotCommit" @change="selectProcessRuleNotCommit"
@@ -3161,17 +3238,17 @@ watch(
</div> </div>
<div class="mt-5"> <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> <template #addonBefore>
<MinusOutlined <MinusOutlined
key="prop" key="prop"
@click="dosage = dosage > 1 ? dosage - 1 : 1" @click="!isSpecialPrescriptionCartLocked && (dosage = dosage > 1 ? dosage - 1 : 1)"
/> />
</template> </template>
<template #addonAfter> <template #addonAfter>
<PlusOutlined <PlusOutlined
key="add" key="add"
@click="dosage = dosage < 100 ? dosage + 1 : 100" @click="!isSpecialPrescriptionCartLocked && (dosage = dosage < 100 ? dosage + 1 : 100)"
/> />
</template> </template>
</InputNumber> </InputNumber>
@@ -3179,27 +3256,37 @@ watch(
</div> </div>
<div class="mt-5"> <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> <template #addonBefore>
<MinusOutlined <MinusOutlined
key="prop" key="prop"
@click="dayDosage = dayDosage > 1 ? dayDosage - 1 : 1" @click="!isSpecialPrescriptionCartLocked && (dayDosage = dayDosage > 1 ? dayDosage - 1 : 1)"
/> />
</template> </template>
<template #addonAfter> <template #addonAfter>
<PlusOutlined <PlusOutlined
key="add" key="add"
@click="dayDosage = dayDosage < 100 ? dayDosage + 1 : 100" @click="!isSpecialPrescriptionCartLocked && (dayDosage = dayDosage < 100 ? dayDosage + 1 : 100)"
/> />
</template> </template>
</InputNumber> </InputNumber>
次/天 次/天
</div> </div>
</div> </div>
<Button type="primary" @click="openDiagnosisModal">常用诊断</Button> <Button type="primary" @click="openDiagnosisModal">
<Textarea v-model:value="diagnosis" placeholder="输入诊断结果..."/> 常用诊断
<Button type="primary" @click="openDoctorOrderModal">常用医嘱</Button> </Button>
<Textarea v-model:value="medicalAdvice" placeholder="输入医嘱..."/> <Textarea
v-model:value="diagnosis"
placeholder="输入诊断结果..."
/>
<Button type="primary" @click="openDoctorOrderModal">
常用医嘱
</Button>
<Textarea
v-model:value="medicalAdvice"
placeholder="输入医嘱..."
/>
诊疗费用: 诊疗费用:
<InputNumber <InputNumber
v-model:value="treatmentPrice" v-model:value="treatmentPrice"
@@ -3215,7 +3302,7 @@ watch(
<div class="total-cost"> <div class="total-cost">
商品价格: 商品价格:
<Button <Button
v-if="priceAdjustEnabled && currentDrugs.length" v-if="priceAdjustEnabled && currentDrugs.length && !isSpecialPrescriptionCartLocked"
type="link" type="link"
class="!p-0" class="!p-0"
@click="openOrderPriceAdjust" @click="openOrderPriceAdjust"
@@ -3257,6 +3344,14 @@ watch(
> >
发送处方 发送处方
</Button> </Button>
<Button
v-if="canUseCommonPrescription && currentDrugs.length"
type="link"
class="mt-2 w-full"
@click="openSaveCommonPrescriptionModal"
>
保存为常用方
</Button>
</div> </div>
</Page> </Page>
<div v-else-if="tabType === 0" class="prescription-panel"> <div v-else-if="tabType === 0" class="prescription-panel">