Files
his/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/prescriptionlist.vue
yangkexiang 8deefd2cb1 bug338:门诊划价新增时未校验当前就诊记录及诊断记录,未接诊患者也可新增划价项目。
bug339:【库存商品明细查询报表】“药房”筛选条件失效,查询结果中包含非选中药房的数据
2026-04-09 18:15:26 +08:00

1201 lines
41 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div style="width: 100%">
<div style="margin-bottom: 5px">
<el-button type="primary" @click="handleAddPrescription()" :disabled="false">
新增
</el-button>
<el-button type="primary" @click="handleSave()" :disabled="handleSaveDisabled"> 签发 </el-button>
<el-button type="warning" plain @click="handleSingOut()" :disabled="handleSingOutDisabled"> 签退 </el-button>
<!-- <el-button type="primary" plain @click="open()" :disabled="false"> 组套 </el-button> -->
<el-button type="danger" plain @click="handleDelete()" :disabled="false"> 删除 </el-button>
</div>
<el-table
max-height="650"
ref="prescriptionRef"
:data="prescriptionList"
row-key="uniqueKey"
border
@row-dblclick="clickRowDb"
:expand-row-keys="expandOrder"
>
<el-table-column type="expand" width="40">
<template #default="scope">
<el-form :model="scope.row" :rules="rowRules" :ref="'formRef' + scope.$index">
<div style="padding: 16px; background: #f8f9fa; border-radius: 8px">
<!-- 药品类型adviceType == 1和耗材类型adviceType == 2使用相同的界面 -->
<template v-if="scope.row.adviceType == 1 || scope.row.adviceType == 2">
<div style="display: flex; align-items: center; margin-bottom: 16px; gap: 16px">
<span style="font-size: 16px; font-weight: 600">
{{
scope.row.adviceName +
' ' +
(scope.row.volume ? scope.row.volume + ' ' : '') +
(scope.row.unitPrice ? scope.row.unitPrice + ' 元/' : '') +
(scope.row.unitCode_dictText || '')
}}
</span>
<div class="form-group">
<!-- 库存不为空时显示批号选择 -->
<el-select
v-if="scope.row.stockList && scope.row.stockList.length > 0"
v-model="scope.row.lotNumber"
style="width: 180px; margin-right: 20px"
placeholder="选择批号"
>
<el-option
v-for="item in scope.row.stockList"
:key="item.lotNumber"
:value="item.lotNumber"
:label="
item.locationName +
' ' +
'批次号: ' +
item.lotNumber +
' ' +
' 库存:' +
(item.quantity / scope.row.partPercent).toFixed(2) +
item.unitCode_dictText +
' 单价:' +
item.price.toFixed(2) +
'/' +
item.unitCode_dictText
"
@click="handleNumberClick(item, scope.$index)"
/>
</el-select>
<!-- 库存为空时显示提示 -->
<span v-else style="color: #f56c6c; margin-right: 20px; font-size: 14px;">
无可用库存
</span>
<el-form-item
label="数量:"
prop="quantity"
class="required-field"
data-prop="quantity"
>
<el-input-number
placeholder="数量"
v-model="scope.row.quantity"
style="width: 70px"
controls-position="right"
:controls="false"
@keyup.enter.prevent="handleEnter('quantity', scope.row, scope.$index)"
@input="calculateTotalPrice(scope.row, scope.$index)"
/>
</el-form-item>
<el-select
v-if="scope.row.unitCodeList && scope.row.unitCodeList.length > 0"
v-model="scope.row.unitCode"
style="width: 70px; margin-right: 20px"
placeholder="单位"
@change="calculateTotalAmount(scope.row, scope.$index)"
>
<template v-for="item in scope.row.unitCodeList" :key="item.value">
<el-option
v-if="item.type != unitMap['dose']"
:value="item.value"
:label="item.label"
/>
</template>
</el-select>
<span class="total-amount">
总金额{{ scope.row.totalPrice ? scope.row.totalPrice + ' 元' : '0.00 元' }}
</span>
</div>
<el-button type="primary" @click="handleSaveSign(scope.row, scope.$index)">
保存
</el-button>
</div>
</template>
<template v-else>
<div style="display: flex; align-items: center; margin-bottom: 16px; gap: 16px">
<span style="font-size: 16px; font-weight: 600">
{{
scope.row.adviceName + ' ' + scope.row.unitPrice
? Number(scope.row.unitPrice).toFixed(2)
: '-' + '元'
}}
</span>
<div class="form-group">
<el-form-item
label="执行次数:"
prop="quantity"
class="required-field"
data-prop="quantity"
>
<el-input-number
placeholder="执行次数"
style="width: 100px; margin: 0 20px"
v-model="scope.row.quantity"
controls-position="right"
:controls="false"
@keyup.enter.prevent="handleEnter('quantity', scope.row, scope.$index)"
@input="calculateTotalPrice(scope.row, scope.$index)"
/>
</el-form-item>
<el-tree-select
clearable
v-model="scope.row.orgId"
:data="organization"
:props="{ value: 'id', label: 'name', children: 'children' }"
value-key="id"
check-strictly
placeholder="请选择执行科室"
style="min-width: 150px; width: auto;"
class="org-select"
/>
<span class="total-amount">
总金额{{ scope.row.totalPrice ? scope.row.totalPrice + ' 元' : '0.00 元' }}
</span>
<span style="font-size: 16px; font-weight: 600">
<!-- 金额: {{ scope.row.priceList[0].price }} -->
</span>
</div>
<el-button type="primary" @click="handleSaveSign(scope.row, scope.$index)">
保存
</el-button>
</div>
</template>
</div>
</el-form>
</template>
</el-table-column>
<el-table-column label="" align="center" prop="groupId" width="60">
<template #default="scope">
<el-checkbox
:disabled = "scope.row.chargeStatus == 5"
v-model="scope.row.check"
placeholder=""
@click.stop=""
@change="changeCheck(scope.row.check,scope.$index,scope.row)"
/>
</template>
<!-- (value) => {
if (value) {
groupIndexList.push(scope.$index);
} else {
groupIndexList.splice(groupIndexList.indexOf(scope.$index), 1);
}
} -->
</el-table-column>
<el-table-column label="项目" align="center" prop="productName" width="400">
<template #default="scope">
<template v-if="getRowDisabled(scope.row)">
<el-select
style="width: 35%; margin-right: 20px"
v-model="scope.row.adviceTypeValue"
:ref="'adviceTypeRef' + scope.$index"
placeholder="选择类型"
@change="
(value) => {
console.log('[类型选择] value:', value);
expandOrder = [];
prescriptionList[scope.$index].adviceName = undefined;
// 根据 value 值直接判断
let adviceType, categoryCode, label;
switch (value) {
case '1':
adviceType = 1;
categoryCode = '2';
label = '西药';
break;
case '2':
adviceType = 1;
categoryCode = '1';
label = '中成药';
break;
case '3':
adviceType = 2;
categoryCode = '';
label = '耗材';
break;
case '4':
adviceType = 3;
categoryCode = '';
label = '诊疗';
break;
default:
adviceType = undefined;
categoryCode = '';
label = '';
}
prescriptionList[scope.$index].adviceType = adviceType;
prescriptionList[scope.$index].adviceType_dictText = label;
prescriptionList[scope.$index].categoryCode = categoryCode;
adviceQueryParams.adviceType = adviceType;
adviceQueryParams.categoryCode = categoryCode;
console.log('[类型选择] 设置后:', { adviceType, categoryCode });
}
"
@clear="
() => {
prescriptionList[scope.$index].adviceName = undefined;
prescriptionList[scope.$index].adviceType = undefined;
prescriptionList[scope.$index].adviceType_dictText = '';
prescriptionList[scope.$index].categoryCode = '';
adviceQueryParams.adviceType = undefined;
adviceQueryParams.categoryCode = '';
}
"
>
<el-option
v-for="item in adviceTypeList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-popover
:popper-style="{ padding: '0' }"
placement="bottom-start"
:visible="scope.row.showPopover"
:width="1200"
>
<adviceBaseList
ref="adviceTableRef"
:popoverVisible="scope.row.showPopover"
:adviceQueryParams="adviceQueryParams"
:patientInfo="props.patientInfo"
@selectAdviceBase="(row) => selectAdviceBase(scope.row.uniqueKey, row)"
/>
<template #reference>
<el-input
:ref="'adviceRef' + scope.$index"
style="width: 50%"
v-model="scope.row.adviceName"
placeholder="请选择项目"
@input="handleChange"
@click="handleFocus(scope.row, scope.$index)"
@keyup.enter.stop="handleFocus(scope.row, scope.$index)"
@keydown="
(e) => {
if (!scope.row.showPopover) return;
// 拦截上下键和回车事件
if (['ArrowUp', 'ArrowDown', 'Enter'].includes(e.key)) {
e.preventDefault();
// 传递事件到弹窗容器
adviceTableRef.handleKeyDown(e);
}
}
"
@blur="handleBlur(scope.row)"
/>
</template>
</el-popover>
</template>
<span v-else>{{ scope.row.adviceName }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="" width="90">
<template #default="scope">
<el-tag v-if="scope.row.chargeStatus == 5" type="success">已收费</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 2" type="success">已签发</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 1" type="">待签发</el-tag>
</template>
</el-table-column>
<el-table-column label="总量" align="center" prop="">
<template #default="scope">
<span v-if="!scope.row.isEdit">
{{ scope.row.quantity ? scope.row.quantity + ' ' + scope.row.unitCode_dictText : '' }}
</span>
</template>
</el-table-column>
<el-table-column label="总金额" align="right" prop="" header-align="center">
<template #default="scope">
<span v-if="!scope.row.isEdit" style="text-align: right">
{{ scope.row.totalPrice ? Number(scope.row.totalPrice).toFixed(2) + ' ' : '-' }}
</span>
</template>
</el-table-column>
<el-table-column label="药房/科室" align="center" prop="" width="240">
<template #default="scope">
<span v-if="!scope.row.isEdit">
{{ scope.row.positionName }}
</span>
</template>
</el-table-column>
<el-table-column label="签发人" align="center" prop="" width="240">
<template #default="scope">
<span v-if="!scope.row.isEdit">
{{ scope.row.requesterId_dictText }}
</span>
</template>
</el-table-column>
<el-table-column label="签发时间" align="center" prop="" width="240">
<template #default="scope">
<span v-if="!scope.row.isEdit">
{{ scope.row.requestTime }}
</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
import {
getOrgTree,
getPrescriptionList,
savePrescription,
savePrescriptionSign,
singOut,
getEmrDetail,
getEncounterDiagnosis,
} from './api';
import adviceBaseList from './adviceBaseList';
import {getCurrentInstance, nextTick, ref, watch} from 'vue';
const emit = defineEmits(['selectDiagnosis']);
const prescriptionList = ref([]);
const form = ref({
prescriptionList: prescriptionList.value,
});
const adviceQueryParams = ref({});
const rowIndex = ref(-1);
const groupIndexList = ref([]);
const nextId = ref(1);
const unitCodeList = ref([]);
const adviceTableRef = ref([]);
const organization = ref([]);
const orgTreeLoaded = ref(false);
const orgTreeLoading = ref(false);
const rowRules = ref({
conditionDefinitionId: [{ required: true, message: '请选择诊断', trigger: 'change' }],
dose: [{ required: true, message: '请输入单次剂量', trigger: 'change' }],
doseQuantity: [{ required: true, message: '请输入单次剂量', trigger: 'change' }],
quantity: [{ required: true, message: '请输入数量', trigger: 'change' }],
dispensePerDuration: [{ required: true, message: '请输入用药天数', trigger: 'change' }],
});
const unitMap = ref({
dose: 'dose',
minUnit: 'minUnit',
unit: 'unit',
});
const props = defineProps({
patientInfo: {
type: Object,
required: true,
},
activeTab: {
type: String,
},
});
const isAdding = ref(false);
const prescriptionRef = ref();
const expandOrder = ref([]); //目前的展开行
const stockList = ref([]);
const groupList = ref([])
const { proxy } = getCurrentInstance();
const inputRefs = ref({}); // 存储输入框实例
const requiredProps = ref([]); // 存储必填项 prop 顺序
const { method_code, unit_code, rate_code, distribution_category_code } = proxy.useDict(
'method_code',
'unit_code',
'rate_code',
'distribution_category_code'
);
const handleSaveDisabled = ref(false) //签发状态
const handleSingOutDisabled = ref(false) //签退状态
const adviceTypeList = ref([
{
label: '西药',
value: '1', // 用字符串
adviceType: 1,
categoryCode: '2',
},
{
label: '中成药',
value: '2', // 用字符串
adviceType: 1,
categoryCode: '1',
},
{
label: '耗材',
value: '3', // 用字符串
adviceType: 2,
categoryCode: '',
},
{
label: '诊疗',
value: '4', // 用字符串
adviceType: 3,
categoryCode: '',
},
{
label: '全部',
value: '',
adviceType: undefined,
categoryCode: '',
},
]);
watch(
() => expandOrder.value,
(newValue) => {
console.log(newValue,"监听·")
if (newValue.length > 0) {
nextTick(() => {
const index = prescriptionList.value.findIndex((row) => row.uniqueKey === newValue[0]);
const items = proxy.$refs['formRef' + index]?.$el?.querySelectorAll('[data-prop]');
requiredProps.value = Array.from(items).map((item) => item.dataset.prop);
});
} else {
requiredProps.value = {};
}
}
);
watch(
() => prescriptionList.value,
(newValue) => {
console.log(prescriptionList.value,"prescriptionList.value")
if(newValue&&newValue.length>0){
let saveList = prescriptionList.value.filter((item) => {
return item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
})
console.log(saveList,"prescriptionList.value")
if (saveList.length == 0) {
handleSaveDisabled.value = true
}else{
handleSaveDisabled.value = false
}
}
},
{ immediate: true, deep: false }
);
function getListInfo(addNewRow) {
isAdding.value = false;
getPrescriptionList(props.patientInfo.encounterId).then((res) => {
// 为每行数据添加 adviceTypeValue 字段,用于类型下拉框显示
prescriptionList.value = (res.data || []).map(item => {
// 根据 adviceType 和 categoryCode 找到对应的 adviceTypeValue
let adviceTypeValue = '';
if (item.adviceType === 1) {
// 药品类型,需要根据 categoryCode 判断是西药还是中成药
if (item.categoryCode === '1') {
adviceTypeValue = '2'; // 中成药
} else {
adviceTypeValue = '1'; // 西药
}
} else if (item.adviceType === 2) {
adviceTypeValue = '3'; // 耗材
} else if (item.adviceType === 3) {
adviceTypeValue = '4'; // 诊疗
}
return { ...item, adviceTypeValue };
});
if (props.activeTab == 'prescription' && addNewRow) {
handleAddPrescription();
}
});
}
function getRowDisabled(row) {
return row.isEdit;
}
/**
* 是否已由医生接诊(非待诊)
* EncounterStatus: 1=待诊 2=在诊 3=暂离 …
*/
function assertEncounterReceived(patientInfo) {
const status = Number(patientInfo.statusEnum)
if (status === 1) {
proxy.$modal.msgWarning('当前就诊尚未接诊,不能进行门诊划价')
return false
}
return true
}
/** 是否存在有效就诊记录(门诊 doc_emr 或住院 doc_record与 emr-detail 一致) */
function assertHasEmrRecord(emrRes) {
if (!emrRes || emrRes.code !== 200) {
proxy.$modal.msgWarning(emrRes?.msg || '获取就诊记录失败,不能进行门诊划价')
return false
}
const row = emrRes.data
if (row == null || row === '') {
proxy.$modal.msgWarning('当前就诊无就诊记录,不能进行门诊划价')
return false
}
if (typeof row === 'object' && row.id == null && row.ID == null) {
proxy.$modal.msgWarning('当前就诊无就诊记录,不能进行门诊划价')
return false
}
return true
}
function assertHasDiagnosis(diagRes) {
if (!diagRes || diagRes.code !== 200) {
proxy.$modal.msgWarning(diagRes?.msg || '获取诊断失败,不能进行门诊划价')
return false
}
const raw = diagRes.data
const list = Array.isArray(raw) ? raw : []
if (list.length === 0) {
proxy.$modal.msgWarning('当前就诊无诊断记录,不能进行门诊划价')
return false
}
return true
}
// 新增医嘱
async function handleAddPrescription() {
// 验证是否已选择患者
if (!props.patientInfo || Object.keys(props.patientInfo).length === 0) {
proxy.$modal.msgWarning('请先选择患者')
return
}
if (!props.patientInfo.encounterId) {
proxy.$modal.msgWarning('当前就诊信息不完整,不能进行门诊划价')
return
}
if (!assertEncounterReceived(props.patientInfo)) {
return
}
try {
const emrRes = await getEmrDetail(props.patientInfo.encounterId)
if (!assertHasEmrRecord(emrRes)) {
return
}
const diagRes = await getEncounterDiagnosis(props.patientInfo.encounterId)
if (!assertHasDiagnosis(diagRes)) {
return
}
} catch (e) {
console.error(e)
proxy.$modal.msgError(e?.message || '校验失败,请稍后重试')
return
}
if (isAdding.value) {
proxy.$modal.msgWarning('请先保存当前医嘱')
return
}
isAdding.value = true
// 在数组最前方添加一行,让新增行显示在最上边
prescriptionList.value.unshift({
uniqueKey: nextId.value++,
showPopover: false,
check: false,
isEdit: true,
statusEnum: 1,
adviceTypeValue: '',
adviceType: undefined,
adviceType_dictText: '',
categoryCode: '',
})
nextTick(() => {
proxy.$refs['adviceRef0'].focus()
})
}
// 行双击打开编辑块,仅待发送的可编辑
function clickRowDb(row) {
if (row.statusEnum == 1) {
row = { ...row, ...JSON.parse(row.contentJson) };
row.isEdit = true;
const index = prescriptionList.value.findIndex((item) => item.uniqueKey === row.uniqueKey);
prescriptionList.value[index] = row;
console.log(prescriptionList.value,"prescriptionList.value")
expandOrder.value = [row.uniqueKey];
}
}
function handleDiagnosisChange(item, row) {
row.diagnosisName = item.name;
row.conditionId = item.conditionId;
}
function handleFocus(row, index) {
rowIndex.value = index;
// 打开当前行弹窗前,先关闭其它行,避免多个弹窗同时存在
prescriptionList.value.forEach((r, i) => {
if (i !== index) r.showPopover = false;
});
// 同步当前行的参数到 adviceQueryParams
adviceQueryParams.value.adviceType = row.adviceType;
adviceQueryParams.value.categoryCode = row.categoryCode || '';
row.showPopover = true;
}
function handleBlur(row) {
// 不能在 input blur 时立刻关闭弹窗:
// 点击弹窗里的表格会先触发 blur导致弹窗瞬间关闭从而“点了项目没反应”
// 弹窗关闭交给 selectAdviceBase()(选中后关闭)以及 handleFocus()(切行时关闭其他行)
}
function handleChange(value) {
adviceQueryParams.value.searchKey = value;
}
/**
* 选择药品/诊疗项目回调
* 这里恢复为之前"能正常工作"的简单逻辑,只做最小必要的修正
*/
async function selectAdviceBase(key, row) {
if (!row) {
console.error('[selectAdviceBase] row 为空');
return;
}
// rowIndex 理论上由 handleFocus 设置;防御一下越界
if (rowIndex.value < 0 || rowIndex.value >= prescriptionList.value.length) {
const foundIndex = prescriptionList.value.findIndex((item) => item.uniqueKey === key);
if (foundIndex === -1) {
console.error('[selectAdviceBase] 找不到对应行key =', key);
return;
}
rowIndex.value = foundIndex;
}
// rowIndex 理论上由 handleFocus 设置;防御一下越界
if (rowIndex.value < 0 || rowIndex.value >= prescriptionList.value.length) {
const foundIndex = prescriptionList.value.findIndex((item) => item.uniqueKey === key);
if (foundIndex === -1) {
console.error('[selectAdviceBase] 找不到对应行key =', key);
return;
}
rowIndex.value = foundIndex;
}
// 关闭当前行弹窗
const currentRow = prescriptionList.value[rowIndex.value];
if (currentRow) {
currentRow.showPopover = false;
}
// 诊疗(adviceType=3) 才需要加载执行科室树,且只加载一次
if (row.adviceType === 3) {
await ensureOrgTreeLoaded();
}
// 构建单位列表(保持原有逻辑)
unitCodeList.value = [];
unitCodeList.value.push({ value: row.unitCode, label: row.unitCode_dictText, type: 'unit' });
if (row.doseUnitCode != row.minUnitCode) {
unitCodeList.value.push({
value: row.doseUnitCode,
label: row.doseUnitCode_dictText,
type: 'dose',
});
}
if (
(row.partAttributeEnum == 1 || row.partAttributeEnum == 3) &&
row.minUnitCode != row.unitCode
) {
unitCodeList.value.push({
value: row.minUnitCode,
label: row.minUnitCode_dictText,
type: 'minUnit',
});
}
// 将选中的基础项“覆盖”到当前处方行(这是之前正常工作的核心逻辑)
prescriptionList.value[rowIndex.value] = {
...prescriptionList.value[rowIndex.value],
...JSON.parse(JSON.stringify(row)),
};
// 后续字段处理保持原样
prescriptionList.value[rowIndex.value].orgId = undefined;
prescriptionList.value[rowIndex.value].dose = undefined;
prescriptionList.value[rowIndex.value].unitCodeList = unitCodeList.value;
prescriptionList.value[rowIndex.value].doseUnitCode =
row.minUnitCode != row.unitCode ? row.minUnitCode : row.unitCode;
prescriptionList.value[rowIndex.value].minUnitCode = JSON.parse(JSON.stringify(row.doseUnitCode));
prescriptionList.value[rowIndex.value].unitCode =
row.partAttributeEnum == 1 ? row.minUnitCode : row.unitCode;
prescriptionList.value[rowIndex.value].definitionId = JSON.parse(
JSON.stringify(row)
).chargeItemDefinitionId;
// 库存列表 + 价格列表拼成批次号的下拉框(非诊疗)
if (row.adviceType != 3) {
let hasInventory = false;
let inventoryWarning = '';
// 检查库存情况
if (row.inventoryList && row.inventoryList.length > 0) {
stockList.value = row.inventoryList.map((item, index) => {
return { ...item, ...row.priceList[index] };
});
prescriptionList.value[rowIndex.value].stockList = stockList.value;
// 检查是否有可用的库存(数量 > 0
const availableStock = stockList.value.filter(item => item.quantity > 0);
if (availableStock.length > 0) {
hasInventory = true;
} else {
inventoryWarning = '该项目所有批次库存不足,请选择其他库房或补充库存';
}
// 获取默认批次号的库存
let stock = stockList.value.filter((item) => {
return item.lotNumber == row.defaultLotNumber;
})[0];
if (stock != {} && stock != undefined) {
if (stock.quantity > 0) {
prescriptionList.value[rowIndex.value].lotNumber = stock.lotNumber;
prescriptionList.value[rowIndex.value].inventoryId = stock.inventoryId;
prescriptionList.value[rowIndex.value].locationId = stock.locationId;
prescriptionList.value[rowIndex.value].unitPrice = stock.price;
prescriptionList.value[rowIndex.value].positionName = stock.locationName;
} else {
// 默认批次库存不足,选择第一个可用批次
const firstAvailable = availableStock[0];
if (firstAvailable) {
prescriptionList.value[rowIndex.value].lotNumber = firstAvailable.lotNumber;
prescriptionList.value[rowIndex.value].inventoryId = firstAvailable.inventoryId;
prescriptionList.value[rowIndex.value].locationId = firstAvailable.locationId;
prescriptionList.value[rowIndex.value].unitPrice = firstAvailable.price;
prescriptionList.value[rowIndex.value].positionName = firstAvailable.locationName;
}
}
}
} else {
inventoryWarning = '该项目无库存记录,请选择其他库房或补充库存';
prescriptionList.value[rowIndex.value].stockList = [];
}
// 统一设置默认值
prescriptionList.value[rowIndex.value].quantity = 1;
if (row.priceList && row.priceList.length > 0) {
if (!prescriptionList.value[rowIndex.value].unitPrice) {
prescriptionList.value[rowIndex.value].unitPrice = row.priceList[0].price;
}
}
calculateTotalPrice(prescriptionList.value[rowIndex.value], rowIndex.value);
// 如果有库存警告,统一提示
if (inventoryWarning) {
console.log('[库存警告]', inventoryWarning, '药品:', row.adviceName);
// 不弹出警告框,只在控制台记录,避免频繁打扰用户
// 用户可以在保存时看到真正的库存检查结果
}
} else {
// 诊疗:设置执行科室和价格
// 🔧 Bug Fix #238: 诊疗项目默认使用患者就诊科室(需确保科室在当前用户权限范围内)
if (!prescriptionList.value[rowIndex.value].orgId && props.patientInfo.orgId) {
// 检查患者就诊科室是否在当前用户的organization树中
const orgIdStr = String(props.patientInfo.orgId);
const findOrgInTree = (tree, targetId) => {
for (const node of tree) {
if (String(node.id) === targetId) return true;
if (node.children && node.children.length > 0) {
if (findOrgInTree(node.children, targetId)) return true;
}
}
return false;
};
if (findOrgInTree(organization.value, orgIdStr)) {
prescriptionList.value[rowIndex.value].orgId = props.patientInfo.orgId;
}
}
if (row.priceList && row.priceList.length > 0) {
prescriptionList.value[rowIndex.value].unitPrice = row.priceList[0].price;
} else {
prescriptionList.value[rowIndex.value].unitPrice = 0;
}
// 设置默认执行次数为1并计算总金额
prescriptionList.value[rowIndex.value].quantity = 1;
calculateTotalPrice(prescriptionList.value[rowIndex.value], rowIndex.value);
}
expandOrder.value = [key];
nextTick(() => {
if (row.adviceType == 1) {
if (row.injectFlag == 1) {
inputRefs.value['executeNum']?.focus();
} else {
inputRefs.value['dose']?.focus();
}
} else {
inputRefs.value['quantity']?.focus();
}
});
}
function ensureOrgTreeLoaded() {
if (orgTreeLoaded.value && organization.value.length > 0) {
return Promise.resolve();
}
if (orgTreeLoading.value) {
// 如果正在加载中,等待加载完成
return new Promise((resolve) => {
const timer = setInterval(() => {
if (!orgTreeLoading.value) {
clearInterval(timer);
resolve();
}
}, 100);
});
}
orgTreeLoading.value = true;
return getOrgTree()
.then((res) => {
// 组织机构树接口通常返回分页 records这里做兼容兜底
organization.value = res?.data?.records ?? res?.data ?? [];
orgTreeLoaded.value = true;
})
.catch(() => {
// 加载失败时允许重试
orgTreeLoaded.value = false;
})
.finally(() => {
orgTreeLoading.value = false;
});
}
function handleDelete() {
// 🔧 修复:使用 groupIndexList 而不是 check 属性
// 因为 watch 监听器会在数据更新时重置 check 为 false
if (groupIndexList.value.length == 0) {
proxy.$modal.msgWarning('请选择要删除的项目');
return;
}
let deleteList = groupIndexList.value.map((index) => {
const item = prescriptionList.value[index];
// 只删除待签发且未收费的项目
if (item.statusEnum != 1 || item.chargeStatus == 5) {
return null;
}
return {
requestId: item.requestId,
dbOpType: '3',
adviceType: item.adviceType,
};
}).filter(item => item !== null); // 过滤掉已签发或已收费的项目
if (deleteList.length == 0) {
proxy.$modal.msgWarning('只能删除待签发且未收费的项目');
return;
}
// 删除逻辑:按索引从大到小排序,避免删除后索引变化
const sortedIndexes = groupIndexList.value.sort((a, b) => b - a);
let hasSavedItem = false;
for (const index of sortedIndexes) {
const item = prescriptionList.value[index];
if (item.statusEnum != 1) {
continue; // 跳过已签发的项目
}
if (!item.requestId) {
// 新增的行(未保存到数据库),直接删除
prescriptionList.value.splice(index, 1);
} else {
hasSavedItem = true;
}
}
if (hasSavedItem) {
// 有已保存的行调用后端API删除
savePrescription({ adviceSaveList: deleteList }).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess('操作成功');
getListInfo(false);
}
});
} else {
// 只有新增行,已经在前端删除完成
proxy.$modal.msgSuccess('操作成功');
}
expandOrder.value = [];
groupIndexList.value = [];
groupList.value = [];
isAdding.value = false;
adviceQueryParams.value.adviceType = undefined;
}
function handleNumberClick(item, index) {
prescriptionList.value[index].unitPrice = item.price;
// prescriptionList.value[index].lotNumber = item.lotNumber;
prescriptionList.value[index].locationId = item.locationId;
prescriptionList.value[index].positionId = item.locationId;
prescriptionList.value[index].positionName = item.locationName;
}
function changeCheck(value,index,row){
if (value) {
if (groupIndexList.value.indexOf(index) === -1) {
groupIndexList.value.push(index)
}
if (groupList.value.indexOf(row) === -1) {
groupList.value.push(row)
}
} else {
const idx1 = groupIndexList.value.indexOf(index)
if (idx1 !== -1) {
groupIndexList.value.splice(idx1, 1)
}
const idx2 = groupList.value.indexOf(row)
if (idx2 !== -1) {
groupList.value.splice(idx2, 1)
}
}
groupList.value.map(k=>{
if(k.check){
if(k.statusEnum == 1){//待签发
if(Number(k.bizRequestFlag)==1||!k.bizRequestFlag){
if(handleSaveDisabled.value&&!handleSingOutDisabled.value&&groupList.value.length>1){
proxy.$modal.msgWarning('请选择相同的状态的项目进行操作')
return
}else{
handleSaveDisabled.value = false
handleSingOutDisabled.value = true
}
}else{
handleSaveDisabled.value = true
handleSingOutDisabled.value = true
return
}
}
if(k.statusEnum == 2){ //已签发
if(Number(k.bizRequestFlag)==1||!k.bizRequestFlag){
if(!handleSaveDisabled.value&&handleSingOutDisabled.value&&groupList.value.length>1){
proxy.$modal.msgWarning('请选择相同的状态的项目进行操作')
return
}else{
handleSaveDisabled.value = true
handleSingOutDisabled.value = false
}
}else{
handleSaveDisabled.value = true
handleSingOutDisabled.value = true
return
}
}
}
})
console.log(groupIndexList.value,"!21")
}
/**
* 保存处方
*/
function handleSave() {
if (expandOrder.value.length > 0) {
proxy.$modal.msgWarning('请先保存当前医嘱');
return;
}
let saveList = prescriptionList.value.filter((item) => {
return item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
});
// let saveList = prescriptionList.value
// .filter((item) => {
// return item.check;
// }).filter((item) => {
// return item.statusEnum == 1&&item.bizRequestFlag==1
// })
// if (saveList.length == 0) {
// proxy.$modal.msgWarning('当前无可签发处方');
// return;
// }
// 此处签发处方和单行保存处方传参相同后台已经将传参存为JSON字符串此处直接转换为JSON即可
let list = saveList.map((item) => {
return {
...JSON.parse(item.contentJson),
requestId: item.requestId,
dbOpType: '1',
groupId: item.groupId,
};
});
savePrescriptionSign({
organizationId: props.patientInfo.orgId,
adviceSaveList: list,
}).then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess('保存成功');
getListInfo(false);
prescriptionList.value.map(k=>{
k.check = false
})
groupIndexList.value = []
groupList.value = []
nextId.value == 1;
}
});
}
// 单行处方保存
function handleSaveSign(row, index) {
// 🔧 Bug Fix #238: 诊疗项目必须选择执行科室
if (row.adviceType === 3 && !row.orgId) {
proxy.$modal.msgWarning('诊疗项目必须选择执行科室');
return;
}
proxy.$refs['formRef' + index].validate((valid) => {
if (valid) {
row.isEdit = false;
isAdding.value = false;
expandOrder.value = [];
row.patientId = props.patientInfo.patientId;
row.encounterId = props.patientInfo.encounterId;
row.accountId = props.patientInfo.accountId;
row.contentJson = JSON.stringify(row);
row.dbOpType = row.requestId ? '2' : '1';
row.minUnitQuantity = row.quantity * row.partPercent;
row.categoryEnum = row.adviceType
// 如果是手术计费,设置生成来源和来源业务单据号
if (props.patientInfo.sourceBillNo) {
row.generateSourceEnum = 6; // 手术计费
row.sourceBillNo = props.patientInfo.sourceBillNo;
}
console.log('row', row)
savePrescription({ adviceSaveList: [row] }).then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess('保存成功');
getListInfo(false);
nextId.value == 1;
// 🔧 Bug Fix #238: 如果诊疗项目缺少执行科室,标记为需要修复的脏数据
if (row.adviceType === 3 && !row.orgId) {
console.warn('Bug #238: 检测到诊疗项目保存时缺少执行科室,请手动编辑修正:', row);
proxy.$modal.msgWarning('诊疗项目执行科室信息不完整,请编辑后重新保存');
}
}
});
}
});
}
// 签退
function handleSingOut() {
let requestIdList = prescriptionList.value
.filter((item) => {
return item.check;
})
.filter((item) => {
return item.statusEnum == 2 && item.chargeStatus != 5 && (Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
})
.map((item) => {
return item.requestId;
});
console.log(requestIdList,"签退")
if (requestIdList.length == 0) {
proxy.$modal.msgWarning('未选择可签退的医嘱(已收费项目不可签退)');
return
}
singOut(requestIdList).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess('操作成功');
getListInfo(false);
console.log( prescriptionList.value," groupIndexList.value")
prescriptionList.value.map(k=>{
k.check = false
})
groupIndexList.value = []
groupList.value = []
}
});
}
// 计算总价
function calculateTotalPrice(row, index) {
nextTick(() => {
// 对于诊疗(adviceType=3)和耗材(adviceType=2)使用unitPrice * quantity计算总价
if (row.adviceType == 3 || row.adviceType == 2) {
// 检查价格是否为有效数字
if (row.unitPrice !== undefined && row.unitPrice !== null && !isNaN(row.unitPrice) && isFinite(row.unitPrice)) {
row.totalPrice = (row.unitPrice * row.quantity).toFixed(2);
} else {
row.totalPrice = '0.00';
}
} else {
// 其他类型(如药品)
if (row.unitCode == row.minUnitCode) {
if (row.minUnitPrice !== undefined && row.minUnitPrice !== null && !isNaN(row.minUnitPrice) && isFinite(row.minUnitPrice)) {
row.totalPrice = (row.minUnitPrice * row.quantity).toFixed(2);
} else {
row.totalPrice = '0.00';
}
} else {
if (row.unitPrice !== undefined && row.unitPrice !== null && !isNaN(row.unitPrice) && isFinite(row.unitPrice)) {
row.totalPrice = (row.unitPrice * row.quantity).toFixed(2);
} else {
row.totalPrice = '0.00';
}
}
}
});
}
defineExpose({ getListInfo });
</script>
<style lang="scss" scoped>
:deep(.el-table__expand-icon) {
display: none !important;
}
.medicine-title {
font-size: 16px;
font-weight: 600;
min-width: 280px;
display: inline-block;
}
.total-amount {
font-size: 16px;
font-weight: 600;
color: #409eff;
white-space: nowrap;
}
.medicine-info {
font-size: 15px;
font-weight: 600;
color: #606266;
white-space: nowrap;
}
.form-group {
display: flex;
align-items: center;
gap: 8px;
background: #fff;
padding: 6px 10px;
border-radius: 4px;
border: 1px solid #ebeef5;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
}
/* 调整element组件默认间距 */
// .el-select,
// .el-input-number {
// margin-right: 0 !important;
// }
.el-input-number .el-input__inner {
text-align: center;
}
.el-table__cell .el-form-item--default {
margin-bottom: 0px;
}
/* 🔧 Bug Fix #238: 科室选择框样式,确保内容完整显示 */
.org-select {
min-width: 150px;
width: auto;
:deep(.el-select__wrapper) {
min-width: 150px;
}
:deep(.el-select__selection) {
min-width: 120px;
}
:deep(.el-select__selected-item) {
max-width: none;
white-space: nowrap;
}
}
</style>