1201 lines
41 KiB
Vue
1201 lines
41 KiB
Vue
<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> |