467
[住院医生工作站-检验申请] 列表显示信息不规范:标题术语错误且单据名称未展示具体检验项目 466 [住院医生工作站-检验申请] 申请单界面缺失核心质控字段(申请类型、标本类型、执行时间)及联动逻辑
This commit is contained in:
@@ -42,4 +42,7 @@ public class SurgeryItemDto {
|
||||
|
||||
/** 单位编码字典文本(前端用于显示单位) */
|
||||
private String unitCodeDictText;
|
||||
|
||||
/** 所需标本编码(来自诊疗目录配置,对应字典 specimen_code 的 dictValue) */
|
||||
private String specimenCode;
|
||||
}
|
||||
|
||||
@@ -157,9 +157,14 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
||||
} else {
|
||||
// 根据申请单类型生成不同前缀的单号
|
||||
String dateStr = new java.text.SimpleDateFormat("yyMMdd").format(new Date());
|
||||
AssignSeqEnum seqEnum = ActivityDefCategory.PROCEDURE.getCode().equals(typeCode)
|
||||
? AssignSeqEnum.SURGERY_APPLY_NO
|
||||
: AssignSeqEnum.CHECK_APPLY_NO;
|
||||
AssignSeqEnum seqEnum;
|
||||
if (ActivityDefCategory.PROCEDURE.getCode().equals(typeCode)) {
|
||||
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());
|
||||
prescriptionNo = seqEnum.getPrefix() + dateStr + String.format("%05d", seq);
|
||||
}
|
||||
|
||||
@@ -920,7 +920,8 @@
|
||||
t2.ID AS charge_item_definition_id,
|
||||
t2.price AS price,
|
||||
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
|
||||
LEFT JOIN adm_charge_item_definition t2
|
||||
ON t2.instance_id = t1.ID
|
||||
|
||||
@@ -278,6 +278,10 @@ public enum AssignSeqEnum {
|
||||
* 手术申请单号(住院)
|
||||
*/
|
||||
SURGERY_APPLY_NO("73", "手术申请单号", "SSZ"),
|
||||
/**
|
||||
* 检验申请单号(住院)
|
||||
*/
|
||||
LAB_APPLY_NO("74", "检验申请单号", "JYZ"),
|
||||
/**
|
||||
* b 病历文书
|
||||
*/
|
||||
|
||||
@@ -130,7 +130,13 @@
|
||||
width="140"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span>{{ buildApplicationName(scope.row) }}</span>
|
||||
<el-tooltip
|
||||
:content="buildFullName(scope.row)"
|
||||
placement="top"
|
||||
:disabled="!scope.row.requestFormDetailList || scope.row.requestFormDetailList.length <= 1"
|
||||
>
|
||||
<span>{{ buildApplicationName(scope.row) }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -639,8 +645,8 @@ const parseSpecimenType = (descJson) => {
|
||||
if (!descJson) return '-';
|
||||
try {
|
||||
const obj = JSON.parse(descJson);
|
||||
// specimenName 或 sampleType 字段
|
||||
return obj.specimenName || obj.sampleType || '-';
|
||||
// 优先取标签字段(新格式),其次取码值字段,兼容旧数据 sampleType
|
||||
return obj.specimenNameLabel || obj.specimenName || obj.sampleType || '-';
|
||||
} catch (e) {
|
||||
console.error('解析 descJson 失败:', e);
|
||||
return '-';
|
||||
@@ -649,8 +655,8 @@ const parseSpecimenType = (descJson) => {
|
||||
|
||||
/**
|
||||
* 根据申请单详情构建申请单名称
|
||||
* 单一项目:显示项目名称+数量
|
||||
* 多个项目:显示首个项目名称+数量+"等X项"
|
||||
* 单一项目:直接显示项目全名(不拼接数量)
|
||||
* 多个项目:显示"项目1 + 项目2 等n项"缩略格式
|
||||
*/
|
||||
const buildApplicationName = (row) => {
|
||||
const details = row.requestFormDetailList;
|
||||
@@ -658,11 +664,24 @@ const buildApplicationName = (row) => {
|
||||
return row.name || '-';
|
||||
}
|
||||
if (details.length === 1) {
|
||||
const item = details[0];
|
||||
return `${item.adviceName}${item.quantity || ''}`;
|
||||
// 单一项目:直接显示项目全名
|
||||
return details[0].adviceName || row.name || '-';
|
||||
}
|
||||
const first = details[0];
|
||||
return `${first.adviceName}${first.quantity || ''}等${details.length}项`;
|
||||
// 多个项目:首项 + 第二项 + 等n项
|
||||
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) => {
|
||||
|
||||
@@ -147,8 +147,6 @@ onBeforeMount(() => {});
|
||||
onMounted(() => {});
|
||||
const applicationFormNameRef = ref();
|
||||
const submitApplicationForm = () => {
|
||||
console.log(applicationFormNameRef.value);
|
||||
|
||||
if (applicationFormNameRef.value?.submit) {
|
||||
applicationFormNameRef.value.submit();
|
||||
}
|
||||
|
||||
@@ -31,9 +31,10 @@
|
||||
<span class="total-count">共 {{ totalCount }} 项</span>
|
||||
</div>
|
||||
<el-transfer
|
||||
v-model="transferValue"
|
||||
:model-value="transferState.selected"
|
||||
:data="transferData"
|
||||
:titles="['未选择', '已选择']"
|
||||
@change="onTransferChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="bloodTransfusion-form">
|
||||
@@ -182,44 +183,10 @@
|
||||
style="width: 100%"
|
||||
>
|
||||
<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-option
|
||||
label="分泌物"
|
||||
value="分泌物"
|
||||
/>
|
||||
<el-option
|
||||
label="其他"
|
||||
value="其他"
|
||||
v-for="item in specimenDictOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -247,11 +214,12 @@
|
||||
</div>
|
||||
</template>
|
||||
<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 {getExaminationPage, saveInspection} from './api';
|
||||
import {ActivityCategory} from '@/utils/medicalConstants';
|
||||
import {getDepartmentList} from '@/api/public.js';
|
||||
import {getDicts} from '@/api/system/dict/data';
|
||||
import {getEncounterDiagnosis} from '../../api.js';
|
||||
import {ElMessage} from 'element-plus';
|
||||
|
||||
@@ -284,6 +252,10 @@ const searchKey = ref('');
|
||||
const totalCount = ref(0);
|
||||
const skipDeptAutoFill = ref(false);
|
||||
|
||||
// 标本类型字典
|
||||
const specimenDictMap = ref({}); // dictValue → dictLabel 映射
|
||||
const specimenDictOptions = ref([]); // 下拉选项列表
|
||||
|
||||
// 将已加载的全部数据转为 transfer 组件所需的格式
|
||||
const buildTransferData = (records) => {
|
||||
return records.map((item) => {
|
||||
@@ -347,7 +319,28 @@ const handleSearch = () => {
|
||||
};
|
||||
// 编辑初始化标志:避免 applyEditTransferSelection 设置 transferValue 时触发 projectWithDepartment 覆盖 descJson 中的科室值
|
||||
const isInitializing = ref(false);
|
||||
// el-transfer v-model 在某些环境下不触发响应式更新,
|
||||
// 使用 reactive 数组 + @change 事件双重保障
|
||||
const transferState = reactive({ selected: [] });
|
||||
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({
|
||||
// categoryType: '', // 项目类别
|
||||
targetDepartment: '', // 发往科室
|
||||
@@ -358,12 +351,30 @@ const form = reactive({
|
||||
relatedResult: '', // 相关结果
|
||||
attention: '', // 注意事项
|
||||
applicationType: 0, // 申请类型 0-普通 1-急诊
|
||||
specimenName: '血液', // 标本类型
|
||||
executeTime: null, // 执行时间
|
||||
specimenName: '', // 标本类型(由选择诊疗项目后自动带出)
|
||||
executeTime: formatCurrentDateTime(), // 执行时间,默认当前系统时间
|
||||
primaryDiagnosisList: [], //主诊断目录
|
||||
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) => {
|
||||
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(() => {
|
||||
getLocationInfo();
|
||||
getDiagnosisList();
|
||||
getList();
|
||||
loadSpecimenDict();
|
||||
});
|
||||
/**
|
||||
* type(1:watch监听类型 2:点击保存类型)
|
||||
@@ -469,12 +500,29 @@ const projectWithDepartment = (selectProjectIds, type) => {
|
||||
}
|
||||
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(
|
||||
() => transferValue.value,
|
||||
(newValue) => {
|
||||
if (skipDeptAutoFill.value) return;
|
||||
if (isInitializing.value) return;
|
||||
console.log('[标本联动] watch 触发, transferValue:', newValue);
|
||||
if (skipDeptAutoFill.value || isInitializing.value) return;
|
||||
if (!newValue || newValue.length === 0) return;
|
||||
newValue.forEach((id) => {
|
||||
if (!selectedItemsCache.value.has(id)) {
|
||||
const item = applicationListAll.value.find((i) => i.adviceDefinitionId == id);
|
||||
@@ -482,9 +530,45 @@ watch(
|
||||
}
|
||||
});
|
||||
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 已加载) */
|
||||
const applyEditTransferSelection = () => {
|
||||
const newData = props.editData
|
||||
@@ -525,6 +609,10 @@ const applyEditTransferSelection = () => {
|
||||
skipDeptAutoFill.value = false
|
||||
})
|
||||
isInitializing.value = false
|
||||
// 编辑初始化后显式填充标本类型(watch 被 skipDeptAutoFill 守卫跳过了)
|
||||
if (uniq.length > 0) {
|
||||
autoFillSpecimenType(uniq)
|
||||
}
|
||||
if (newData.requestFormDetailList.length && uniq.length === 0) {
|
||||
console.warn(
|
||||
'[LaboratoryTests] 申请单明细未能在项目字典中匹配到项,请核对 activityId / 项目名称',
|
||||
@@ -544,7 +632,12 @@ watch(
|
||||
const obj = JSON.parse(newData.descJson)
|
||||
Object.keys(form).forEach((key) => {
|
||||
if (obj[key] !== undefined) {
|
||||
form[key] = obj[key]
|
||||
// 标本类型:兼容旧数据中存的是显示名称(如 "血液"),转为字典码(如 "1")
|
||||
if (key === 'specimenName') {
|
||||
form[key] = resolveSpecimenCode(obj[key])
|
||||
} else {
|
||||
form[key] = obj[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
applyTargetDepartmentEcho()
|
||||
@@ -591,13 +684,17 @@ watch(
|
||||
);
|
||||
|
||||
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('请选择申请单');
|
||||
}
|
||||
if (!projectWithDepartment(transferValue.value, 2)) {
|
||||
// 提交前自动带出标本类型(必须在 projectWithDepartment 之前,否则科室校验失败直接 return 不会执行)
|
||||
autoFillSpecimenType(selected);
|
||||
if (!projectWithDepartment(selected, 2)) {
|
||||
return;
|
||||
}
|
||||
let applicationListAllFilter = transferValue.value.map((id) => {
|
||||
let applicationListAllFilter = selected.map((id) => {
|
||||
let item = applicationListAll.value.find((i) => i.adviceDefinitionId == id);
|
||||
if (!item) {
|
||||
item = selectedItemsCache.value.get(id);
|
||||
@@ -627,7 +724,11 @@ const submit = () => {
|
||||
organizationId: patientInfo.value.inHospitalOrgId, // 医疗机构ID
|
||||
requestFormId: isEditMode.value ? props.editData.requestFormId : '', // 申请单ID(编辑模式传入,新增为空)
|
||||
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 碰撞)
|
||||
};
|
||||
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>
|
||||
<style lang="scss" scoped>
|
||||
.LaboratoryTests-container {
|
||||
|
||||
Reference in New Issue
Block a user