fix(database): 修复患者首页查询重复数据和关联查询问题

- 在ATDManageAppMapper.xml中添加DISTINCT关键字解决入院患者信息重复问题
- 重构PatientHomeAppMapper.xml中的复杂查询逻辑,使用子查询替代多层JOIN提高性能
- 修复vital signs查询中的字段关联错误,将base_service_req_id改为request_id
- 优化前端implementDepartment组件的数据加载逻辑,添加异步处理和错误捕获
- 为诊疗项目下拉框添加数据加载状态检查,防止空数据导致的界面异常
- 实现防抖机制和数据量限制,提升大数据量下的响应性能
- 添加并行数据加载,减少页面初始化时间
This commit is contained in:
2026-01-20 08:24:07 +08:00
parent a3dce8de60
commit 649f7bcf5b
4 changed files with 314 additions and 207 deletions

View File

@@ -224,7 +224,7 @@
</select> </select>
<select id="selectAdmissionPatientInfo" <select id="selectAdmissionPatientInfo"
resultType="com.openhis.web.inhospitalnursestation.dto.AdmissionPatientInfoDto"> resultType="com.openhis.web.inhospitalnursestation.dto.AdmissionPatientInfoDto">
SELECT ae.id AS encounter_id, SELECT DISTINCT ae.id AS encounter_id,
ae.bus_no, ae.bus_no,
ae.priority_enum, ae.priority_enum,
ae.organization_id, ae.organization_id,

View File

@@ -2,189 +2,250 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openhis.web.inpatientmanage.mapper.PatientHomeAppMapper"> <mapper namespace="com.openhis.web.inpatientmanage.mapper.PatientHomeAppMapper">
<!-- 患者首页信息分页查询--> <!-- 患者首页信息分页查询 -->
<select id="getPage" parameterType="java.util.Map" <select id="getPage" parameterType="java.util.Map"
resultType="com.openhis.web.inpatientmanage.dto.PatientHomeDto"> resultType="com.openhis.web.inpatientmanage.dto.PatientHomeDto">
SELECT SELECT
T1.id, id,
T1.active_flag, active_flag,
T1.temp_flag, temp_flag,
T1."name" AS patientName, -- 患者姓名 patientName,
T1.name_json, name_json,
T1.bus_no AS patientNo, -- 病历号 patientNo,
T1.gender_enum, -- 患者性别 gender_enum,
T1.birth_date, birth_date,
T1.deceased_date, deceased_date,
T1.marital_status_enum, marital_status_enum,
T1.prfs_enum, prfs_enum,
T1.phone, -- 患者手机号 phone,
COALESCE(NULLIF(T1.address, ''), '') AS address, -- 处理空地址 address,
T1.address_province, address_province,
T1.address_city, address_city,
T1.address_district, address_district,
T1.address_street, address_street,
T1.address_json, address_json,
T1.nationality_code, nationality_code,
T1.id_card, id_card,
T1.py_str, py_str,
T1.wb_str, wb_str,
T1.blood_abo, blood_abo,
T1.blood_rh, blood_rh,
T1.work_company, work_company,
T1.native_place, native_place,
T1.country_code, country_code,
T1.link_name, link_name,
T1.link_relation_code, link_relation_code,
T1.link_telcom, link_telcom,
T1.link_jsons, link_jsons,
T1.tenant_id, tenant_id,
T2.bus_no AS hospitalNo, -- 住院号 hospitalNo,
T2.id AS encounterId, -- 就诊ID encounterId,
T2.priority_enum, -- 护理级别 priority_enum,
T2.status_enum, -- 患者状态 status_enum,
T2.organization_id,-- 入院科室 organization_id,
T2.start_time AS admissionDate, -- 入院日期 admissionDate,
T2.end_time AS dischargeDate, -- 出院日期 dischargeDate,
T2.class_enum, -- 就诊类别 class_enum,
Doctor.name AS responsibleDoctor, -- 责任医生 responsibleDoctor,
Nurse.name AS responsibleNurse, -- 责任护士 responsibleNurse,
T6.type_code, -- 费别 type_code,
T7.status_enum AS surgeryStatusEnum, -- 手术状态 surgeryStatusEnum,
T8.start_time AS surgeryStartTime, -- 手术开始时间 surgeryStartTime,
T8.end_time AS surgeryEndTime, -- 手术结束时间 surgeryEndTime,
T9.id AS encounterLocationId, -- 就诊位置ID encounterLocationId,
T9.location_id, -- 床位号 location_id,
T11."name" AS caty, -- 入院科室 caty,
T12."name" AS mainDiagnosis -- 主要诊断 mainDiagnosis
FROM adm_patient AS T1 FROM (
LEFT JOIN adm_encounter T2 SELECT
ON T1.id = T2.patient_id -- 患者ID patient_base.*,
AND T2.delete_flag = '0' ROW_NUMBER() OVER (PARTITION BY patient_base.id ORDER BY patient_base.encounterId) as rn
AND T2.class_enum = '1' -- 仅选择住院患者 FROM (
-- 获取责任医生 SELECT DISTINCT
LEFT JOIN ( T1.id,
SELECT T1.active_flag,
encounter_id, T1.temp_flag,
practitioner_id, T1."name" AS patientName, -- 患者姓名
"name", T1.name_json,
ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY practitioner_id) as rn T1.bus_no AS patientNo, -- 病历号
FROM ( T1.gender_enum, -- 患者性别
SELECT T1.birth_date,
T3.encounter_id, T1.deceased_date,
T4."name", T1.marital_status_enum,
T3.practitioner_id T1.prfs_enum,
FROM adm_encounter_participant T3 T1.phone, -- 患者手机号
LEFT JOIN adm_practitioner T4 COALESCE(NULLIF(T1.address, ''), '') AS address, -- 处理空地址
ON T3.practitioner_id = T4.id T1.address_province,
AND T4.delete_flag = '0' T1.address_city,
WHERE T3.type_code = '9' -- 责任医生类型 T1.address_district,
AND T3.delete_flag = '0' T1.address_street,
) sub T1.address_json,
) Doctor ON T2.id = Doctor.encounter_id AND Doctor.rn = 1 T1.nationality_code,
-- 获取责任护士 T1.id_card,
LEFT JOIN ( T1.py_str,
SELECT T1.wb_str,
encounter_id, T1.blood_abo,
practitioner_id, T1.blood_rh,
"name", T1.work_company,
ROW_NUMBER() OVER (PARTITION BY encounter_id ORDER BY practitioner_id) as rn T1.native_place,
FROM ( T1.country_code,
SELECT T1.link_name,
T3.encounter_id, T1.link_relation_code,
T4."name", T1.link_telcom,
T3.practitioner_id T1.link_jsons,
FROM adm_encounter_participant T3 T1.tenant_id,
LEFT JOIN adm_practitioner T4 T2.bus_no AS hospitalNo, -- 住院号
ON T3.practitioner_id = T4.id T2.id AS encounterId, -- 就诊ID
AND T4.delete_flag = '0' T2.priority_enum, -- 护理级别
WHERE T3.type_code = '2' -- 责任护士类型 T2.status_enum, -- 患者状态
AND T3.delete_flag = '0' T2.organization_id,-- 入院科室
) sub T2.start_time AS admissionDate, -- 入院日期
) Nurse ON T2.id = Nurse.encounter_id AND Nurse.rn = 1 T2.end_time AS dischargeDate, -- 出院日期
LEFT JOIN adm_encounter_diagnosis T5 T2.class_enum, -- 就诊类别
ON T2.id = T5.encounter_id -- 就诊ID -- 获取责任医生(使用子查询确保只返回一个值)
AND T5.maindise_flag = 1 (SELECT p."name"
AND T5.delete_flag = '0' FROM adm_encounter_participant ep
LEFT JOIN adm_account T6 LEFT JOIN adm_practitioner p ON ep.practitioner_id = p.id AND p.delete_flag = '0'
ON T2.id = T6.encounter_id -- 就诊ID WHERE ep.encounter_id = T2.id
AND T6.delete_flag = '0' AND ep.type_code = '9'
LEFT JOIN cli_procedure T7 AND ep.delete_flag = '0'
ON T1.id = T7.patient_id -- 患者ID ORDER BY ep.practitioner_id
AND T7.delete_flag = '0' LIMIT 1) AS responsibleDoctor,
LEFT JOIN cli_procedure_performer T8 -- 获取责任护士(使用子查询确保只返回一个值)
ON T7.id = T8.procedure_id -- 手术ID (SELECT p."name"
AND T8.delete_flag = '0' FROM adm_encounter_participant ep
LEFT JOIN adm_encounter_location T9 LEFT JOIN adm_practitioner p ON ep.practitioner_id = p.id AND p.delete_flag = '0'
ON T2.id = T9.encounter_id -- 就诊ID WHERE ep.encounter_id = T2.id
AND T9.form_enum = 8 AND ep.type_code = '2'
AND T9.delete_flag = '0' AND ep.delete_flag = '0'
LEFT JOIN adm_organization T11 ORDER BY ep.practitioner_id
ON T2.organization_id = T11.id LIMIT 1) AS responsibleNurse,
AND T11.type_enum = 2 T6.type_code, -- 费别
AND T11.delete_flag = '0' T7.status_enum AS surgeryStatusEnum, -- 手术状态
LEFT JOIN cli_condition_definition T12 T8.start_time AS surgeryStartTime, -- 手术开始时间
ON T5.condition_id = T12.id T8.end_time AS surgeryEndTime, -- 手术结束时间
AND T12.delete_flag = '0' T9.id AS encounterLocationId, -- 就诊位置ID
LEFT JOIN wor_service_request T13 T9.location_id, -- 床位号
ON T2.id = T13.encounter_id T11."name" AS caty, -- 入院科室
AND T7.base_service_req_id = T13.id T12."name" AS mainDiagnosis -- 主要诊断
AND T13.delete_flag = '0' FROM adm_patient AS T1
LEFT JOIN wor_service_request_detail T14 INNER JOIN adm_encounter T2 -- 改为INNER JOIN确保患者有就诊记录
ON T13.id = T14.service_req_id ON T1.id = T2.patient_id -- 患者ID
AND T14.type_code = '3' AND T2.delete_flag = '0'
AND T14.delete_flag = '0' AND T2.class_enum = '1' -- 仅选择住院患者
<where> LEFT JOIN adm_encounter_diagnosis T5
T1.delete_flag = '0' ON T2.id = T5.encounter_id -- 就诊ID
-- 患者ID AND T5.maindise_flag = 1
<if test="patientId != null"> AND T5.delete_flag = '0'
AND T1.id = #{patientId} LEFT JOIN adm_account T6
</if> ON T2.id = T6.encounter_id -- 就诊ID
AND T6.delete_flag = '0'
LEFT JOIN (
SELECT
patient_id,
status_enum,
id,
request_id,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY id DESC) as rn
FROM cli_procedure
WHERE delete_flag = '0'
) T7 ON T1.id = T7.patient_id AND T7.rn = 1
LEFT JOIN (
SELECT
procedure_id,
start_time,
end_time,
ROW_NUMBER() OVER (PARTITION BY procedure_id ORDER BY id DESC) as rn
FROM cli_procedure_performer
WHERE delete_flag = '0'
) T8 ON T7.id = T8.procedure_id AND T8.rn = 1
LEFT JOIN adm_encounter_location T9
ON T2.id = T9.encounter_id -- 就诊ID
AND T9.form_enum = 8
AND T9.delete_flag = '0'
LEFT JOIN adm_organization T11
ON T2.organization_id = T11.id
AND T11.type_enum = 2
AND T11.delete_flag = '0'
LEFT JOIN cli_condition_definition T12
ON T5.condition_id = T12.id
AND T12.delete_flag = '0'
WHERE T1.delete_flag = '0'
-- 患者ID
<if test="patientId != null">
AND T1.id = #{patientId}
</if>
-- 在科 -- 在科
<if test="statusEnum == 1"> <if test="statusEnum == 1">
AND T9.status_enum = 2 AND T9.status_enum = 2
</if> </if>
-- 待出院 -- 待出院
<if test="statusEnum == 3"> <if test="statusEnum == 3">
AND T2.status_enum = 4 AND T2.status_enum = 4
</if> </if>
-- 危重 -- 危重
<if test="statusEnum == 4"> <if test="statusEnum == 4">
AND T2.priority_enum = 1 AND T2.priority_enum = 1
</if> </if>
-- 手术 -- 手术
<if test="statusEnum == 5"> <if test="statusEnum == 5">
AND T14.type_code = '3' AND EXISTS (
</if> SELECT 1
FROM wor_service_request_detail wsrd
-- 欠费 INNER JOIN wor_service_request wsr ON wsrd.service_req_id = wsr.id
<if test="statusEnum == 6"> WHERE wsr.encounter_id = T2.id
AND T6.balance_amount &lt; 0 AND wsrd.type_code = '3'
</if> AND wsr.delete_flag = '0'
AND wsrd.delete_flag = '0'
-- 已出院
<if test="statusEnum == 7">
AND T2.status_enum = 5
</if>
<if test="searchKey != null and searchKey != ''">
AND (
-- 住院号
T2.bus_no = #{searchKey}
-- 患者姓名
OR T1.name like concat('%', #{searchKey}, '%')
-- 责任医生
OR Doctor.name like concat('%', #{searchKey}, '%')
-- 责任护士
OR Nurse.name like concat('%', #{searchKey}, '%')
) )
</if> </if>
</where>
ORDER BY T9.location_id ASC -- 欠费
<if test="statusEnum == 6">
AND T6.balance_amount &lt; 0
</if>
-- 已出院
<if test="statusEnum == 7">
AND T2.status_enum = 5
</if>
<if test="searchKey != null and searchKey != ''">
AND (
-- 住院号
T2.bus_no = #{searchKey}
-- 患者姓名
OR T1.name like concat('%', #{searchKey}, '%')
-- 责任医生(在子查询中处理)
OR EXISTS (
SELECT 1
FROM adm_encounter_participant ep
LEFT JOIN adm_practitioner p ON ep.practitioner_id = p.id AND p.delete_flag = '0'
WHERE ep.encounter_id = T2.id
AND ep.type_code = '9'
AND ep.delete_flag = '0'
AND p."name" like concat('%', #{searchKey}, '%')
)
-- 责任护士(在子查询中处理)
OR EXISTS (
SELECT 1
FROM adm_encounter_participant ep
LEFT JOIN adm_practitioner p ON ep.practitioner_id = p.id AND p.delete_flag = '0'
WHERE ep.encounter_id = T2.id
AND ep.type_code = '2'
AND ep.delete_flag = '0'
AND p."name" like concat('%', #{searchKey}, '%')
)
)
</if>
) patient_base
) ranked_result
WHERE rn = 1
ORDER BY location_id ASC
</select> </select>
<!-- 科室空床查询--> <!-- 科室空床查询-->

View File

@@ -46,7 +46,7 @@
AND T6.delete_flag = '0' AND T6.delete_flag = '0'
LEFT JOIN wor_service_request T7 LEFT JOIN wor_service_request T7
ON T1.encounter_id = T7.encounter_id ON T1.encounter_id = T7.encounter_id
AND T5.base_service_req_id = T7.id AND T5.request_id = T7.id
AND T7.delete_flag = '0' AND T7.delete_flag = '0'
LEFT JOIN wor_service_request_detail T8 LEFT JOIN wor_service_request_detail T8
ON T7.id = T8.service_req_id ON T7.id = T8.service_req_id

View File

@@ -236,24 +236,34 @@ const filterNode = (value, data) => {
}; };
// 所有诊疗项目列表 // 所有诊疗项目列表
const allImplementDepartmentList = ref([]); const allImplementDepartmentList = ref([]);
function getAllImplementDepartment() { async function getAllImplementDepartment() {
loading.value = true; loading.value = true;
getAllTreatmentList().then((res) => { try {
const res = await getAllTreatmentList();
allImplementDepartmentList.value = res.data.map((item) => ({ allImplementDepartmentList.value = res.data.map((item) => ({
value: item.activityDefinitionId, value: item.activityDefinitionId,
label: item.activityDefinitionName, label: item.activityDefinitionName,
})); }));
// 为所有现有行初始化过滤选项 // 为所有现有行初始化过滤选项(使用防抖处理,避免频繁更新)
catagoryList.value.forEach(row => { if (catagoryList.value && catagoryList.value.length > 0) {
if (!row.hasOwnProperty('filteredOptions')) { // 使用 setTimeout 将 DOM 更新推迟到下一个事件循环,避免阻塞
row.filteredOptions = allImplementDepartmentList.value.slice(0, 100); // 限制为前100个 setTimeout(() => {
row.loading = false; catagoryList.value.forEach(row => {
} if (!row.hasOwnProperty('filteredOptions')) {
}); row.filteredOptions = allImplementDepartmentList.value.slice(0, 100); // 限制为前100个
row.loading = false;
}
});
}, 0);
}
loading.value = false; loading.value = false;
}); } catch (error) {
console.error('获取诊疗项目列表失败:', error);
loading.value = false;
proxy.$message.error('获取诊疗项目列表失败');
}
} }
/** 选择条数 */ /** 选择条数 */
@@ -269,16 +279,30 @@ function handleRemoteQuery(query, row) {
row.loading = true; row.loading = true;
// 模拟异步延迟 // 模拟异步延迟
setTimeout(() => { setTimeout(() => {
// 过滤选项 // 确保数据已加载
row.filteredOptions = allImplementDepartmentList.value.filter(item => { if (!allImplementDepartmentList.value || allImplementDepartmentList.value.length === 0) {
row.filteredOptions = [];
row.loading = false;
return;
}
// 过滤选项,限制结果数量以提高性能
const filtered = allImplementDepartmentList.value.filter(item => {
return item.label.toLowerCase().includes(query.toLowerCase()) || return item.label.toLowerCase().includes(query.toLowerCase()) ||
item.value.toLowerCase().includes(query.toLowerCase()); item.value.toLowerCase().includes(query.toLowerCase());
}); });
// 限制返回结果数量,避免过多选项导致性能问题
row.filteredOptions = filtered.slice(0, 100); // 限制为前100个匹配项
row.loading = false; row.loading = false;
}, 300); // 300ms 延迟,模拟网络请求 }, 300); // 300ms 延迟,模拟网络请求
} else { } else {
// 如果查询为空,显示所有选项(但限制数量以提高性能) // 如果查询为空,显示所有选项(但限制数量以提高性能)
row.filteredOptions = allImplementDepartmentList.value.slice(0, 100); // 限制为前100个 if (allImplementDepartmentList.value && allImplementDepartmentList.value.length > 0) {
row.filteredOptions = allImplementDepartmentList.value.slice(0, 100); // 限制为前100个
} else {
row.filteredOptions = [];
}
} }
} }
@@ -288,6 +312,15 @@ function handleAddItem() {
proxy.$message.warning('请先保存当前行后再新增!'); proxy.$message.warning('请先保存当前行后再新增!');
return; return;
} }
// 确保 allImplementDepartmentList 已经初始化
if (!allImplementDepartmentList.value || allImplementDepartmentList.value.length === 0) {
proxy.$message.warning('正在加载数据,请稍后再试!');
// 如果数据还未加载完成,尝试重新加载
getAllImplementDepartment();
return;
}
const newRow = { const newRow = {
startTime: '00:00:00', startTime: '00:00:00',
endTime: '23:59:59', endTime: '23:59:59',
@@ -411,26 +444,28 @@ function continueHandleNodeClick(node) {
} }
/** 目录分类查询 */ /** 目录分类查询 */
function getDiseaseTreatmentList() { async function getDiseaseTreatmentList() {
loading.value = true; loading.value = true;
getDiseaseTreatmentInit().then(({ data }) => { try {
loading.value = false; const { data } = await getDiseaseTreatmentInit();
//分类目录初始化获取 // 分类目录初始化获取
catagoryDicts.value = data.diagnosisCategoryOptions.sort((a, b) => { catagoryDicts.value = data.diagnosisCategoryOptions.sort((a, b) => {
return parseInt(a.value) - parseInt(b.value); return parseInt(a.value) - parseInt(b.value);
}); });
}); } catch (error) {
// 诊疗目录分类查询下拉树结d构 console.error('获取疾病治疗初始化数据失败:', error);
loading.value = true; proxy.$message.error('获取分类目录失败');
// 诊疗目录分类查询下拉树结d构 }
getImplDepartList();
// 诊疗目录分类查询下拉树结构
await getImplDepartList();
loading.value = false;
} }
// 诊疗目录分类查询下拉树结d构 // 诊疗目录分类查询下拉树结d构
function getImplDepartList() { async function getImplDepartList() {
loading.value = true; try {
getImplementDepartmentList().then((res) => { const res = await getImplementDepartmentList();
loading.value = false;
if (res.code === 200) { if (res.code === 200) {
if (res.data.records.length > 0) { if (res.data.records.length > 0) {
organization.value = res.data.records.map((res) => { organization.value = res.data.records.map((res) => {
@@ -444,13 +479,24 @@ function getImplDepartList() {
organization.value = []; organization.value = [];
} }
} else { } else {
this.$modal.msgError(res.code); proxy.$modal.msgError(res.code);
} }
}); } catch (error) {
console.error('获取实施部门列表失败:', error);
proxy.$message.error('获取科室信息失败');
}
} }
onMounted(() => { onMounted(async () => {
getAllImplementDepartment(); try {
getDiseaseTreatmentList(); // 并行加载数据,提高效率
await Promise.all([
getAllImplementDepartment(),
getDiseaseTreatmentList()
]);
} catch (error) {
console.error('初始化数据加载失败:', error);
proxy.$message.error('数据加载失败,请稍后重试');
}
}); });
</script> </script>
<style scoped> <style scoped>