[住院医生工作站-检验申请] 列表显示信息不规范:标题术语错误且单据名称未展示具体检验项目
466 [住院医生工作站-检验申请] 申请单界面缺失核心质控字段(申请类型、标本类型、执行时间)及联动逻辑
This commit is contained in:
wangjian963
2026-06-01 14:15:59 +08:00
parent 296e825fbd
commit 76c623ba1d
7 changed files with 205 additions and 67 deletions

View File

@@ -42,4 +42,7 @@ public class SurgeryItemDto {
/** 单位编码字典文本(前端用于显示单位) */ /** 单位编码字典文本(前端用于显示单位) */
private String unitCodeDictText; private String unitCodeDictText;
/** 所需标本编码(来自诊疗目录配置,对应字典 specimen_code 的 dictValue */
private String specimenCode;
} }

View File

@@ -157,9 +157,14 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
} else { } else {
// 根据申请单类型生成不同前缀的单号 // 根据申请单类型生成不同前缀的单号
String dateStr = new java.text.SimpleDateFormat("yyMMdd").format(new Date()); String dateStr = new java.text.SimpleDateFormat("yyMMdd").format(new Date());
AssignSeqEnum seqEnum = ActivityDefCategory.PROCEDURE.getCode().equals(typeCode) AssignSeqEnum seqEnum;
? AssignSeqEnum.SURGERY_APPLY_NO if (ActivityDefCategory.PROCEDURE.getCode().equals(typeCode)) {
: AssignSeqEnum.CHECK_APPLY_NO; seqEnum = AssignSeqEnum.SURGERY_APPLY_NO;
} else if (ActivityDefCategory.PROOF.getCode().equals(typeCode)) {
seqEnum = AssignSeqEnum.LAB_APPLY_NO;
} else {
seqEnum = AssignSeqEnum.CHECK_APPLY_NO;
}
int seq = assignSeqUtil.getSeqNoByDay(seqEnum.getPrefix()); int seq = assignSeqUtil.getSeqNoByDay(seqEnum.getPrefix());
prescriptionNo = seqEnum.getPrefix() + dateStr + String.format("%05d", seq); prescriptionNo = seqEnum.getPrefix() + dateStr + String.format("%05d", seq);
} }

View File

@@ -920,7 +920,8 @@
t2.ID AS charge_item_definition_id, t2.ID AS charge_item_definition_id,
t2.price AS price, t2.price AS price,
t1.permitted_unit_code AS unit_code, t1.permitted_unit_code AS unit_code,
t1.permitted_unit_code AS unit_code_dict_text t1.permitted_unit_code AS unit_code_dict_text,
t1.specimen_code AS specimen_code
FROM wor_activity_definition t1 FROM wor_activity_definition t1
LEFT JOIN adm_charge_item_definition t2 LEFT JOIN adm_charge_item_definition t2
ON t2.instance_id = t1.ID ON t2.instance_id = t1.ID

View File

@@ -278,6 +278,10 @@ public enum AssignSeqEnum {
* 手术申请单号(住院) * 手术申请单号(住院)
*/ */
SURGERY_APPLY_NO("73", "手术申请单号", "SSZ"), SURGERY_APPLY_NO("73", "手术申请单号", "SSZ"),
/**
* 检验申请单号(住院)
*/
LAB_APPLY_NO("74", "检验申请单号", "JYZ"),
/** /**
* b 病历文书 * b 病历文书
*/ */

View File

@@ -130,7 +130,13 @@
width="140" width="140"
> >
<template #default="scope"> <template #default="scope">
<el-tooltip
:content="buildFullName(scope.row)"
placement="top"
:disabled="!scope.row.requestFormDetailList || scope.row.requestFormDetailList.length <= 1"
>
<span>{{ buildApplicationName(scope.row) }}</span> <span>{{ buildApplicationName(scope.row) }}</span>
</el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@@ -639,8 +645,8 @@ const parseSpecimenType = (descJson) => {
if (!descJson) return '-'; if (!descJson) return '-';
try { try {
const obj = JSON.parse(descJson); const obj = JSON.parse(descJson);
// specimenName 或 sampleType 字段 // 优先取标签字段(新格式),其次取码值字段,兼容旧数据 sampleType
return obj.specimenName || obj.sampleType || '-'; return obj.specimenNameLabel || obj.specimenName || obj.sampleType || '-';
} catch (e) { } catch (e) {
console.error('解析 descJson 失败:', e); console.error('解析 descJson 失败:', e);
return '-'; return '-';
@@ -649,8 +655,8 @@ const parseSpecimenType = (descJson) => {
/** /**
* 根据申请单详情构建申请单名称 * 根据申请单详情构建申请单名称
* 单一项目:显示项目名称+数量 * 单一项目:直接显示项目全名(不拼接数量
* 多个项目:显示首个项目名称+数量+"等X项" * 多个项目:显示"项目1 + 项目2 等n项"缩略格式
*/ */
const buildApplicationName = (row) => { const buildApplicationName = (row) => {
const details = row.requestFormDetailList; const details = row.requestFormDetailList;
@@ -658,11 +664,24 @@ const buildApplicationName = (row) => {
return row.name || '-'; return row.name || '-';
} }
if (details.length === 1) { if (details.length === 1) {
const item = details[0]; // 单一项目:直接显示项目全名
return `${item.adviceName}${item.quantity || ''}`; return details[0].adviceName || row.name || '-';
} }
const first = details[0]; // 多个项目:首项 + 第二项 + 等n项
return `${first.adviceName}${first.quantity || ''}${details.length}`; const names = details.map((d) => d.adviceName).filter(Boolean);
if (names.length === 0) return row.name || '-';
const first = names[0];
const second = names.length > 1 ? ` + ${names[1]}` : '';
return `${first}${second}${details.length}`;
};
/**
* 获取申请单完整项目名称列表(用于 tooltip 展示)
*/
const buildFullName = (row) => {
const details = row.requestFormDetailList;
if (!details || details.length === 0) return row.name || '-';
return details.map((d) => d.adviceName).filter(Boolean).join(' + ') || row.name || '-';
}; };
const isFieldMatched = (key) => { const isFieldMatched = (key) => {

View File

@@ -147,8 +147,6 @@ onBeforeMount(() => {});
onMounted(() => {}); onMounted(() => {});
const applicationFormNameRef = ref(); const applicationFormNameRef = ref();
const submitApplicationForm = () => { const submitApplicationForm = () => {
console.log(applicationFormNameRef.value);
if (applicationFormNameRef.value?.submit) { if (applicationFormNameRef.value?.submit) {
applicationFormNameRef.value.submit(); applicationFormNameRef.value.submit();
} }

View File

@@ -31,9 +31,10 @@
<span class="total-count"> {{ totalCount }} </span> <span class="total-count"> {{ totalCount }} </span>
</div> </div>
<el-transfer <el-transfer
v-model="transferValue" :model-value="transferState.selected"
:data="transferData" :data="transferData"
:titles="['未选择', '已选择']" :titles="['未选择', '已选择']"
@change="onTransferChange"
/> />
</div> </div>
<div class="bloodTransfusion-form"> <div class="bloodTransfusion-form">
@@ -182,44 +183,10 @@
style="width: 100%" style="width: 100%"
> >
<el-option <el-option
label="血液" v-for="item in specimenDictOptions"
value="血液" :key="item.value"
/> :label="item.label"
<el-option :value="item.value"
label="尿液"
value="尿液"
/>
<el-option
label="粪便"
value="粪便"
/>
<el-option
label="痰液"
value="痰液"
/>
<el-option
label="咽拭子"
value="咽拭子"
/>
<el-option
label="脑脊液"
value="脑脊液"
/>
<el-option
label="胸腹水"
value="胸腹水"
/>
<el-option
label="关节液"
value="关节液"
/>
<el-option
label="分泌物"
value="分泌物"
/>
<el-option
label="其他"
value="其他"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -247,11 +214,12 @@
</div> </div>
</template> </template>
<script setup name="LaboratoryTests"> <script setup name="LaboratoryTests">
import {getCurrentInstance, nextTick, onMounted, reactive, ref, watch, computed} from 'vue'; import {getCurrentInstance, nextTick, onMounted, reactive, ref, watch, watchEffect, computed} from 'vue';
import {patientInfo} from '../../../store/patient.js'; import {patientInfo} from '../../../store/patient.js';
import {getExaminationPage, saveInspection} from './api'; import {getExaminationPage, saveInspection} from './api';
import {ActivityCategory} from '@/utils/medicalConstants'; import {ActivityCategory} from '@/utils/medicalConstants';
import {getDepartmentList} from '@/api/public.js'; import {getDepartmentList} from '@/api/public.js';
import {getDicts} from '@/api/system/dict/data';
import {getEncounterDiagnosis} from '../../api.js'; import {getEncounterDiagnosis} from '../../api.js';
import {ElMessage} from 'element-plus'; import {ElMessage} from 'element-plus';
@@ -284,6 +252,10 @@ const searchKey = ref('');
const totalCount = ref(0); const totalCount = ref(0);
const skipDeptAutoFill = ref(false); const skipDeptAutoFill = ref(false);
// 标本类型字典
const specimenDictMap = ref({}); // dictValue → dictLabel 映射
const specimenDictOptions = ref([]); // 下拉选项列表
// 将已加载的全部数据转为 transfer 组件所需的格式 // 将已加载的全部数据转为 transfer 组件所需的格式
const buildTransferData = (records) => { const buildTransferData = (records) => {
return records.map((item) => { return records.map((item) => {
@@ -347,7 +319,28 @@ const handleSearch = () => {
}; };
// 编辑初始化标志:避免 applyEditTransferSelection 设置 transferValue 时触发 projectWithDepartment 覆盖 descJson 中的科室值 // 编辑初始化标志:避免 applyEditTransferSelection 设置 transferValue 时触发 projectWithDepartment 覆盖 descJson 中的科室值
const isInitializing = ref(false); const isInitializing = ref(false);
// el-transfer v-model 在某些环境下不触发响应式更新,
// 使用 reactive 数组 + @change 事件双重保障
const transferState = reactive({ selected: [] });
const transferValue = ref([]); const transferValue = ref([]);
// 单向同步:程序设置 transferValue如编辑回显→ transferState.selected
watch(transferValue, (val) => {
if (JSON.stringify(val) !== JSON.stringify(transferState.selected)) {
transferState.selected = [...val];
}
}, { deep: true });
// 格式化当前时间为 yyyy-MM-dd HH:mm:ss
const formatCurrentDateTime = () => {
const now = new Date();
const y = now.getFullYear();
const m = String(now.getMonth() + 1).padStart(2, '0');
const d = String(now.getDate()).padStart(2, '0');
const h = String(now.getHours()).padStart(2, '0');
const min = String(now.getMinutes()).padStart(2, '0');
const s = String(now.getSeconds()).padStart(2, '0');
return `${y}-${m}-${d} ${h}:${min}:${s}`;
};
const form = reactive({ const form = reactive({
// categoryType: '', // 项目类别 // categoryType: '', // 项目类别
targetDepartment: '', // 发往科室 targetDepartment: '', // 发往科室
@@ -358,12 +351,30 @@ const form = reactive({
relatedResult: '', // 相关结果 relatedResult: '', // 相关结果
attention: '', // 注意事项 attention: '', // 注意事项
applicationType: 0, // 申请类型 0-普通 1-急诊 applicationType: 0, // 申请类型 0-普通 1-急诊
specimenName: '血液', // 标本类型 specimenName: '', // 标本类型(由选择诊疗项目后自动带出)
executeTime: null, // 执行时间 executeTime: formatCurrentDateTime(), // 执行时间,默认当前系统时间
primaryDiagnosisList: [], //主诊断目录 primaryDiagnosisList: [], //主诊断目录
otherDiagnosisList: [], //其他断目录 otherDiagnosisList: [], //其他断目录
}); });
const rules = reactive({}); const validateExecuteTime = (rule, value, callback) => {
if (!value) {
callback(new Error('请选择执行时间'));
return;
}
const now = new Date();
const selectedTime = new Date(value);
if (selectedTime < now) {
callback(new Error('执行时间不可早于当前时间'));
return;
}
callback();
};
const rules = reactive({
executeTime: [
{ required: true, message: '请选择执行时间', trigger: 'change' },
{ validator: validateExecuteTime, trigger: 'change' },
],
});
const normalizeOrgTreeIds = (nodes) => { const normalizeOrgTreeIds = (nodes) => {
if (!Array.isArray(nodes)) return []; if (!Array.isArray(nodes)) return [];
@@ -386,10 +397,30 @@ const applyTargetDepartmentEcho = () => {
} }
}; };
/** 加载所需标本字典dictType = specimen_code */
const loadSpecimenDict = async () => {
try {
const res = await getDicts('specimen_code');
if (res?.code === 200 && Array.isArray(res.data)) {
const map = {};
const options = [];
res.data.forEach((item) => {
map[item.dictValue] = item.dictLabel;
options.push({ value: item.dictValue, label: item.dictLabel });
});
specimenDictMap.value = map;
specimenDictOptions.value = options;
}
} catch (e) {
console.warn('加载所需标本字典失败:', e);
}
};
onMounted(() => { onMounted(() => {
getLocationInfo(); getLocationInfo();
getDiagnosisList(); getDiagnosisList();
getList(); getList();
loadSpecimenDict();
}); });
/** /**
* type(1watch监听类型 2:点击保存类型) * type(1watch监听类型 2:点击保存类型)
@@ -469,12 +500,29 @@ const projectWithDepartment = (selectProjectIds, type) => {
} }
return isRelease; return isRelease;
}; };
// 监听选择项目变化 /** el-transfer @change: 穿梭框数据变化(右侧完整列表) */
const onTransferChange = (newRightValue) => {
if (!Array.isArray(newRightValue)) return;
transferState.selected = [...newRightValue];
newRightValue.forEach((id) => {
if (!selectedItemsCache.value.has(id)) {
const item = applicationListAll.value.find((i) => i.adviceDefinitionId == id);
if (item) selectedItemsCache.value.set(id, item);
}
});
if (!isInitializing.value && !skipDeptAutoFill.value) {
projectWithDepartment(newRightValue, 1);
autoFillSpecimenType(newRightValue);
}
};
// 监听 transferValue 变化el-select multiple v-model 可靠)
watch( watch(
() => transferValue.value, () => transferValue.value,
(newValue) => { (newValue) => {
if (skipDeptAutoFill.value) return; console.log('[标本联动] watch 触发, transferValue:', newValue);
if (isInitializing.value) return; if (skipDeptAutoFill.value || isInitializing.value) return;
if (!newValue || newValue.length === 0) return;
newValue.forEach((id) => { newValue.forEach((id) => {
if (!selectedItemsCache.value.has(id)) { if (!selectedItemsCache.value.has(id)) {
const item = applicationListAll.value.find((i) => i.adviceDefinitionId == id); const item = applicationListAll.value.find((i) => i.adviceDefinitionId == id);
@@ -482,9 +530,45 @@ watch(
} }
}); });
projectWithDepartment(newValue, 1); projectWithDepartment(newValue, 1);
autoFillSpecimenType(newValue);
} }
); );
/** 根据选中的检验项目自动带出标本类型(直接使用字典码,与 el-option :value 对齐) */
const autoFillSpecimenType = (selectedIds) => {
if (!selectedIds || selectedIds.length === 0) return;
const specimens = [];
selectedIds.forEach((id) => {
let item = selectedItemsCache.value.get(id);
if (!item) {
item = applicationListAll.value.find((i) => i.adviceDefinitionId == id);
}
if (item?.specimenCode) {
const code = String(item.specimenCode);
if (code && !specimens.includes(code)) {
specimens.push(code);
}
}
});
// 所有选中项目标本类型一致时才自动填充
if (specimens.length === 1) {
form.specimenName = specimens[0];
}
};
/**
* 从可能的历史数据中解析标本字典码
* 旧数据可能存储的是显示名称(如 "血液"),需转换为字典码(如 "1"
*/
const resolveSpecimenCode = (value) => {
if (!value) return '';
if (specimenDictMap.value[value]) return value; // 已经是有效的字典码
for (const opt of specimenDictOptions.value) {
if (opt.label === value) return String(opt.value); // 标签 → 码值
}
return String(value); // 兜底:直接转字符串
};
/** 编辑弹窗:根据申请单明细把右侧「已选择」与 transferValue 对齐(依赖 applicationListAll 已加载) */ /** 编辑弹窗:根据申请单明细把右侧「已选择」与 transferValue 对齐(依赖 applicationListAll 已加载) */
const applyEditTransferSelection = () => { const applyEditTransferSelection = () => {
const newData = props.editData const newData = props.editData
@@ -525,6 +609,10 @@ const applyEditTransferSelection = () => {
skipDeptAutoFill.value = false skipDeptAutoFill.value = false
}) })
isInitializing.value = false isInitializing.value = false
// 编辑初始化后显式填充标本类型watch 被 skipDeptAutoFill 守卫跳过了)
if (uniq.length > 0) {
autoFillSpecimenType(uniq)
}
if (newData.requestFormDetailList.length && uniq.length === 0) { if (newData.requestFormDetailList.length && uniq.length === 0) {
console.warn( console.warn(
'[LaboratoryTests] 申请单明细未能在项目字典中匹配到项,请核对 activityId / 项目名称', '[LaboratoryTests] 申请单明细未能在项目字典中匹配到项,请核对 activityId / 项目名称',
@@ -544,8 +632,13 @@ watch(
const obj = JSON.parse(newData.descJson) const obj = JSON.parse(newData.descJson)
Object.keys(form).forEach((key) => { Object.keys(form).forEach((key) => {
if (obj[key] !== undefined) { if (obj[key] !== undefined) {
// 标本类型:兼容旧数据中存的是显示名称(如 "血液"),转为字典码(如 "1"
if (key === 'specimenName') {
form[key] = resolveSpecimenCode(obj[key])
} else {
form[key] = obj[key] form[key] = obj[key]
} }
}
}) })
applyTargetDepartmentEcho() applyTargetDepartmentEcho()
} catch (e) { } catch (e) {
@@ -591,13 +684,17 @@ watch(
); );
const submit = () => { const submit = () => {
if (transferValue.value.length == 0) { // 使用 transferState.selected来自 el-transfer @change兜底 transferValue
const selected = transferState.selected.length > 0 ? transferState.selected : transferValue.value;
if (selected.length == 0) {
return proxy.$message.error('请选择申请单'); return proxy.$message.error('请选择申请单');
} }
if (!projectWithDepartment(transferValue.value, 2)) { // 提交前自动带出标本类型(必须在 projectWithDepartment 之前,否则科室校验失败直接 return 不会执行)
autoFillSpecimenType(selected);
if (!projectWithDepartment(selected, 2)) {
return; return;
} }
let applicationListAllFilter = transferValue.value.map((id) => { let applicationListAllFilter = selected.map((id) => {
let item = applicationListAll.value.find((i) => i.adviceDefinitionId == id); let item = applicationListAll.value.find((i) => i.adviceDefinitionId == id);
if (!item) { if (!item) {
item = selectedItemsCache.value.get(id); item = selectedItemsCache.value.get(id);
@@ -627,7 +724,11 @@ const submit = () => {
organizationId: patientInfo.value.inHospitalOrgId, // 医疗机构ID organizationId: patientInfo.value.inHospitalOrgId, // 医疗机构ID
requestFormId: isEditMode.value ? props.editData.requestFormId : '', // 申请单ID编辑模式传入新增为空 requestFormId: isEditMode.value ? props.editData.requestFormId : '', // 申请单ID编辑模式传入新增为空
name: '检验申请单', name: '检验申请单',
descJson: JSON.stringify(form), descJson: JSON.stringify({
...form,
// 标本类型显示名称(供申请单查看页使用,避免查看页依赖字典加载)
specimenNameLabel: specimenDictMap.value[form.specimenName] || form.specimenName,
}),
categoryEnum: '21', // 21 检验 22 检查 23 输血 24 手术(避开 adviceType 1-6 碰撞) categoryEnum: '21', // 21 检验 22 检查 23 输血 24 手术(避开 adviceType 1-6 碰撞)
}; };
saveInspection(params).then((res) => { saveInspection(params).then((res) => {
@@ -681,7 +782,14 @@ function getDiagnosisList() {
} }
}); });
} }
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, getList }); // 暴露给父组件:允许父组件在调用 submit 前读取选中项并执行联动
const getSelectedItems = () => transferValue.value;
const getSpecimenMap = () => specimenDictMap.value;
const getApplicationListAll = () => applicationListAll.value;
const fillSpecimenBySelected = () => { autoFillSpecimenType(transferValue.value); };
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, getList,
getSelectedItems, getSpecimenMap, getApplicationListAll, fillSpecimenBySelected });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.LaboratoryTests-container { .LaboratoryTests-container {