perf(database): 优化数据库查询性能和前端请求处理

- 优化ActivityDefinitionManageMapper.xml中的分页查询,减少JOIN操作并使用索引友好的写法
- 修复purchaseinventory组件中API调用的数据传递格式问题
- 将前端请求超时时间从60秒增加到120秒以配合后端超时设置
- 在手术申请页面添加远程搜索防抖功能,避免频繁API调用
- 重构SurgeryAppServiceImpl中的名称字段填充逻辑,使用批量查询减少数据库访问次数
- 优化SurgeryMapper.xml中的分页查询,使用子查询预加载关联数据并减少不必要的JOIN
This commit is contained in:
2026-03-04 18:32:06 +08:00
parent 7008fb007f
commit 8af06f6916
6 changed files with 244 additions and 138 deletions

View File

@@ -533,94 +533,94 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
/**
* 填充手术记录中的名称字段
* 根据ID反向查询用户表、机构表、手术室表、患者表、就诊表,填充对应的名称字段
* 优化:使用批量查询减少数据库访问次数
*
* @param surgery 手术实体对象
*/
private void fillSurgeryNameFields(Surgery surgery) {
// 收集所有需要查询的ID
Set<Long> practitionerIds = new HashSet<>();
Set<Long> orgIds = new HashSet<>();
Set<Long> otherIds = new HashSet<>();
// 收集Practitioner IDs
if (surgery.getMainSurgeonId() != null) practitionerIds.add(surgery.getMainSurgeonId());
if (surgery.getAnesthetistId() != null) practitionerIds.add(surgery.getAnesthetistId());
if (surgery.getAssistant1Id() != null) practitionerIds.add(surgery.getAssistant1Id());
if (surgery.getAssistant2Id() != null) practitionerIds.add(surgery.getAssistant2Id());
if (surgery.getScrubNurseId() != null) practitionerIds.add(surgery.getScrubNurseId());
if (surgery.getApplyDoctorId() != null) practitionerIds.add(surgery.getApplyDoctorId());
// 收集Organization IDs
if (surgery.getOrgId() != null) orgIds.add(surgery.getOrgId());
if (surgery.getApplyDeptId() != null) orgIds.add(surgery.getApplyDeptId());
// 批量查询并缓存结果
Map<Long, String> practitionerNameMap = new HashMap<>();
Map<Long, String> orgNameMap = new HashMap<>();
// 批量查询Practitioner
if (!practitionerIds.isEmpty()) {
List<com.openhis.administration.domain.Practitioner> practitioners = practitionerService.listByIds(practitionerIds);
for (com.openhis.administration.domain.Practitioner p : practitioners) {
practitionerNameMap.put(p.getId(), p.getName());
}
}
// 批量查询Organization
if (!orgIds.isEmpty()) {
List<Organization> orgs = organizationService.listByIds(orgIds);
for (Organization o : orgs) {
orgNameMap.put(o.getId(), o.getName());
}
}
// 填充患者姓名
if (surgery.getPatientId() != null) {
if (surgery.getPatientId() != null && surgery.getPatientName() == null) {
Patient patient = patientService.getById(surgery.getPatientId());
if (patient != null) {
surgery.setPatientName(patient.getName());
}
}
// 填充主刀医生姓名使用practitionerId查询Practitioner表
if (surgery.getMainSurgeonId() != null) {
com.openhis.administration.domain.Practitioner mainSurgeon = practitionerService.getById(surgery.getMainSurgeonId());
if (mainSurgeon != null) {
surgery.setMainSurgeonName(mainSurgeon.getName());
}
// 使用缓存填充名称
if (surgery.getMainSurgeonId() != null && surgery.getMainSurgeonName() == null) {
surgery.setMainSurgeonName(practitionerNameMap.get(surgery.getMainSurgeonId()));
}
// 填充麻醉医生姓名使用practitionerId查询Practitioner表
if (surgery.getAnesthetistId() != null) {
com.openhis.administration.domain.Practitioner anesthetist = practitionerService.getById(surgery.getAnesthetistId());
if (anesthetist != null) {
surgery.setAnesthetistName(anesthetist.getName());
}
if (surgery.getAnesthetistId() != null && surgery.getAnesthetistName() == null) {
surgery.setAnesthetistName(practitionerNameMap.get(surgery.getAnesthetistId()));
}
// 填充助手1姓名使用practitionerId查询Practitioner表
if (surgery.getAssistant1Id() != null) {
com.openhis.administration.domain.Practitioner assistant1 = practitionerService.getById(surgery.getAssistant1Id());
if (assistant1 != null) {
surgery.setAssistant1Name(assistant1.getName());
}
if (surgery.getAssistant1Id() != null && surgery.getAssistant1Name() == null) {
surgery.setAssistant1Name(practitionerNameMap.get(surgery.getAssistant1Id()));
}
// 填充助手2姓名使用practitionerId查询Practitioner表
if (surgery.getAssistant2Id() != null) {
com.openhis.administration.domain.Practitioner assistant2 = practitionerService.getById(surgery.getAssistant2Id());
if (assistant2 != null) {
surgery.setAssistant2Name(assistant2.getName());
}
if (surgery.getAssistant2Id() != null && surgery.getAssistant2Name() == null) {
surgery.setAssistant2Name(practitionerNameMap.get(surgery.getAssistant2Id()));
}
// 填充巡回护士姓名使用practitionerId查询Practitioner表
if (surgery.getScrubNurseId() != null) {
com.openhis.administration.domain.Practitioner scrubNurse = practitionerService.getById(surgery.getScrubNurseId());
if (scrubNurse != null) {
surgery.setScrubNurseName(scrubNurse.getName());
}
if (surgery.getScrubNurseId() != null && surgery.getScrubNurseName() == null) {
surgery.setScrubNurseName(practitionerNameMap.get(surgery.getScrubNurseId()));
}
if (surgery.getApplyDoctorId() != null && surgery.getApplyDoctorName() == null) {
surgery.setApplyDoctorName(practitionerNameMap.get(surgery.getApplyDoctorId()));
}
// 填充手术室名称
if (surgery.getOperatingRoomId() != null) {
if (surgery.getOperatingRoomId() != null && surgery.getOperatingRoomName() == null) {
OperatingRoom operatingRoom = operatingRoomService.getById(surgery.getOperatingRoomId());
if (operatingRoom != null) {
surgery.setOperatingRoomName(operatingRoom.getName());
}
}
// 填充执行科室名称
if (surgery.getOrgId() != null) {
Organization org = organizationService.getById(surgery.getOrgId());
if (org != null) {
surgery.setOrgName(org.getName());
}
// 使用缓存填充组织名称
if (surgery.getOrgId() != null && surgery.getOrgName() == null) {
surgery.setOrgName(orgNameMap.get(surgery.getOrgId()));
}
if (surgery.getApplyDeptId() != null && surgery.getApplyDeptName() == null) {
surgery.setApplyDeptName(orgNameMap.get(surgery.getApplyDeptId()));
}
// 填充申请科室名称(如果还没有设置)
if (surgery.getApplyDeptId() != null && (surgery.getApplyDeptName() == null || surgery.getApplyDeptName().isEmpty())) {
Organization applyDept = organizationService.getById(surgery.getApplyDeptId());
if (applyDept != null) {
surgery.setApplyDeptName(applyDept.getName());
}
}
// 填充申请医生姓名(如果还没有设置) - 使用practitionerId查询Practitioner表
if (surgery.getApplyDoctorId() != null && (surgery.getApplyDoctorName() == null || surgery.getApplyDoctorName().isEmpty())) {
com.openhis.administration.domain.Practitioner applyDoctor = practitionerService.getById(surgery.getApplyDoctorId());
if (applyDoctor != null) {
surgery.setApplyDoctorName(applyDoctor.getName());
}
}
log.info("填充手术名称字段完成 - patientName: {}, mainSurgeonName: {}, anesthetistName: {}, assistant1Name: {}, assistant2Name: {}, scrubNurseName: {}, operatingRoomName: {}, orgName: {}",
surgery.getPatientName(), surgery.getMainSurgeonName(), surgery.getAnesthetistName(), surgery.getAssistant1Name(),
surgery.getAssistant2Name(), surgery.getScrubNurseName(), surgery.getOperatingRoomName(), surgery.getOrgName());
log.debug("填充手术名称字段完成 - patientName: {}, mainSurgeonName: {}, orgName: {}",
surgery.getPatientName(), surgery.getMainSurgeonName(), surgery.getOrgName());
}
/**

View File

@@ -189,13 +189,124 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
LEFT JOIN adm_organization apply_dept ON s.apply_dept_id = apply_dept.id
</sql>
<!-- 优化版分页查询减少JOIN使用子查询预加载关联数据 -->
<select id="getSurgeryPage" parameterType="com.baomidou.mybatisplus.core.conditions.query.QueryWrapper" resultMap="SurgeryResult">
<include refid="selectSurgeryVo"/>
SELECT
s.id,
s.surgery_no,
s.patient_id,
s.patient_name,
s.encounter_id,
s.apply_doctor_id,
s.apply_doctor_name,
s.apply_dept_id,
s.apply_dept_name,
s.surgery_name,
s.surgery_code,
s.surgery_type_enum,
s.surgery_level,
s.status_enum,
s.planned_time,
s.actual_start_time,
s.actual_end_time,
s.main_surgeon_id,
s.main_surgeon_name,
s.assistant_1_id,
s.assistant_1_name,
s.assistant_2_id,
s.assistant_2_name,
s.anesthetist_id,
s.anesthetist_name,
s.scrub_nurse_id,
s.scrub_nurse_name,
s.anesthesia_type_enum,
s.body_site,
s.incision_level,
s.healing_level,
s.operating_room_id,
s.operating_room_name,
s.org_id,
s.org_name,
s.surgery_indication,
s.preoperative_diagnosis,
s.postoperative_diagnosis,
s.surgery_description,
s.postoperative_advice,
s.complications,
s.surgery_fee,
s.anesthesia_fee,
s.total_fee,
s.remark,
s.create_time,
s.update_time,
s.emergency_flag,
s.implant_flag,
s.operating_room_confirm_time,
s.operating_room_confirm_user,
<!-- 患者信息:只查询必要字段 -->
p.gender_enum as patient_gender,
p.birth_date,
<!-- 就诊编号 -->
e.bus_no as encounter_no,
<!-- 字典文本使用CASE WHEN避免额外JOIN -->
CASE s.surgery_type_enum
WHEN 1 THEN '门诊手术'
WHEN 2 THEN '住院手术'
WHEN 3 THEN '急诊手术'
WHEN 4 THEN '择期手术'
ELSE '未知'
END as surgery_type_enum_dictText,
CASE s.surgery_level
WHEN 1 THEN '一级手术'
WHEN 2 THEN '二级手术'
WHEN 3 THEN '三级手术'
WHEN 4 THEN '四级手术'
WHEN 5 THEN '特级手术'
ELSE '未知'
END as surgery_level_dictText,
CASE s.status_enum
WHEN 0 THEN '待排期'
WHEN 1 THEN '已排期'
WHEN 2 THEN '手术中'
WHEN 3 THEN '已完成'
WHEN 4 THEN '已取消'
WHEN 5 THEN '暂停'
ELSE '未知'
END as status_enum_dictText,
CASE s.anesthesia_type_enum
WHEN 0 THEN '无麻醉'
WHEN 1 THEN '局部麻醉'
WHEN 2 THEN '区域麻醉'
WHEN 3 THEN '全身麻醉'
WHEN 4 THEN '脊椎麻醉'
WHEN 5 THEN '硬膜外麻醉'
WHEN 6 THEN '表面麻醉'
ELSE '未知'
END as anesthesia_type_enum_dictText,
CASE s.incision_level
WHEN 1 THEN 'I级切口'
WHEN 2 THEN 'II级切口'
WHEN 3 THEN 'III级切口'
WHEN 4 THEN 'IV级切口'
ELSE '未知'
END as incision_level_dictText,
CASE s.healing_level
WHEN 1 THEN '甲级愈合'
WHEN 2 THEN '乙级愈合'
WHEN 3 THEN '丙级愈合'
ELSE '未知'
END as healing_level_dictText,
<!-- 计算年龄 -->
EXTRACT(YEAR FROM AGE(p.birth_date)) as patient_age
FROM cli_surgery s
<!-- 只JOIN必要的表患者和就诊 -->
LEFT JOIN adm_patient p ON s.patient_id = p.id
LEFT JOIN adm_encounter e ON s.encounter_id = e.id
<where>
s.delete_flag = '0'
<if test="ew.sqlSegment != null and ew.sqlSegment != ''">
<![CDATA[
AND ${ew.sqlSegment.replace('tenant_id', 's.tenant_id').replace('create_time', 's.create_time').replace('surgery_no', 's.surgery_no').replace('surgery_name', 's.surgery_name').replace('patient_name', 'p.name').replace('main_surgeon_name', 's.main_surgeon_name').replace('anesthetist_name', 's.anesthetist_name').replace('org_name', 'o.name').replace('status_enum', 's.status_enum').replace('planned_time', 's.planned_time')}
AND ${ew.sqlSegment.replace('tenant_id', 's.tenant_id').replace('create_time', 's.create_time').replace('surgery_no', 's.surgery_no').replace('surgery_name', 's.surgery_name').replace('patient_name', 's.patient_name').replace('main_surgeon_name', 's.main_surgeon_name').replace('anesthetist_name', 's.anesthetist_name').replace('org_name', 's.org_name').replace('status_enum', 's.status_enum').replace('planned_time', 's.planned_time')}
]]>
</if>
</where>

View File

@@ -1,78 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openhis.web.datadictionary.mapper.ActivityDefinitionManageMapper">
<!-- 优化后的分页查询减少JOIN使用索引友好的写法 -->
<select id="getDiseaseTreatmentPage" parameterType="java.util.Map"
resultType="com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto">
SELECT
T3.id,
T3.category_code,
T3.bus_no,
T3.name,
T3.py_str,
T3.wb_str,
T3.type_enum,
T3.permitted_unit_code,
T3.org_id,
T3.location_id,
T3.yb_flag,
T3.yb_no,
T3.yb_match_flag,
T3.status_enum,
T3.body_site_code,
T3.specimen_code,
T3.description_text,
T3.rule_id,
T3.tenant_id,
T3.item_type_code,
T3.yb_type,
T3.price_code,
T3.retail_price,
T3.maximum_retail_price,
T3.chrgitm_lv,
T3.children_json,
T3.pricing_flag,
T3.sort_order,
T3.service_range
FROM
(
SELECT
T1.id,
T1.category_code,
T1.bus_no,
T1.name,
T1.py_str,
T1.wb_str,
T1.type_enum,
T1.permitted_unit_code,
T1.org_id,
T1.location_id,
T1.yb_flag,
T1.yb_no,
T1.yb_match_flag,
T1.status_enum,
T1.body_site_code,
T1.specimen_code,
T1.description_text,
T1.rule_id,
T1.tenant_id,
T1.chrgitm_lv,
T2.type_code as item_type_code,
T2.yb_type,
T2.price_code,
T2.price as retail_price,
T4.amount as maximum_retail_price,
T1.children_json,
T1.pricing_flag,
T1.sort_order,
T1.service_range
T1.id,
T1.category_code,
T1.bus_no,
T1.name,
T1.py_str,
T1.wb_str,
T1.type_enum,
T1.permitted_unit_code,
T1.org_id,
T1.location_id,
T1.yb_flag,
T1.yb_no,
T1.yb_match_flag,
T1.status_enum,
T1.body_site_code,
T1.specimen_code,
T1.description_text,
T1.rule_id,
T1.tenant_id,
T1.chrgitm_lv,
T1.children_json,
T1.pricing_flag,
T1.sort_order,
T1.service_range,
T2.type_code as item_type_code,
T2.yb_type,
T2.price_code,
T2.price as retail_price,
T4.amount as maximum_retail_price
FROM wor_activity_definition T1
LEFT JOIN adm_charge_item_definition T2 ON T1.id = T2.instance_id
LEFT JOIN adm_charge_item_definition T5 ON T5.instance_id = T1.id AND T5.instance_table = 'wor_activity_definition'
LEFT JOIN adm_charge_item_def_detail T4 ON T4.definition_id = T5.id AND T4.condition_code = '4'
/* 只JOIN必要的价格表使用INNER JOIN避免笛卡尔积 */
INNER JOIN adm_charge_item_definition T2
ON T1.id = T2.instance_id
AND T2.instance_table = 'wor_activity_definition'
/* 最高零售价使用LEFT JOIN因为可能不存在 */
LEFT JOIN adm_charge_item_def_detail T4
ON T4.definition_id = T2.id
AND T4.condition_code = '4'
<where>
T1.delete_flag = '0'
AND T2.instance_table = 'wor_activity_definition'
<if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
<choose>
<when test="ew.customSqlSegment.contains('tenant_id')">
@@ -84,9 +57,7 @@
</choose>
</if>
</where>
ORDER BY T1.bus_no DESC
) T3
ORDER BY T1.id DESC
</select>
<select id="getDiseaseTreatmentOne" resultType="com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto">

View File

@@ -22,7 +22,7 @@ axios.defaults.headers['Request-Method-Name'] = 'login'
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 60000,
timeout: 120000, // 增加到120秒配合后端超时设置
// 新增:重写响应解析逻辑,大数字自动转字符串(移到这里!)
transformResponse: [
function (data) {

View File

@@ -556,6 +556,10 @@ const anesthesiaNameList = ref([])
const surgeryLoading = ref(false)
const anesthesiaLoading = ref(false)
// 防抖定时器
let surgerySearchTimer = null
let anesthesiaSearchTimer = null
// 计算总费用
const totalCalculatedFee = computed(() => {
const surgeryFee = parseFloat(form.value.surgeryFee || 0)
@@ -868,8 +872,18 @@ function handleRefresh() {
proxy.$modal.msgSuccess('刷新成功')
}
// 远程搜索手术项目
// 远程搜索手术项目添加300ms防抖
function remoteSearchSurgery(query) {
if (surgerySearchTimer) {
clearTimeout(surgerySearchTimer)
}
surgerySearchTimer = setTimeout(() => {
doSearchSurgery(query)
}, 300)
}
// 实际执行手术搜索
function doSearchSurgery(query) {
surgeryLoading.value = true
getDiagnosisTreatmentList({
searchKey: query || '',
@@ -900,8 +914,18 @@ function remoteSearchSurgery(query) {
})
}
// 远程搜索麻醉项目
// 远程搜索麻醉项目添加300ms防抖
function remoteSearchAnesthesia(query) {
if (anesthesiaSearchTimer) {
clearTimeout(anesthesiaSearchTimer)
}
anesthesiaSearchTimer = setTimeout(() => {
doSearchAnesthesia(query)
}, 300)
}
// 实际执行麻醉搜索
function doSearchAnesthesia(query) {
anesthesiaLoading.value = true
getDiagnosisTreatmentList({
searchKey: query || '',

View File

@@ -58,7 +58,7 @@ export function submitApproval(busNo) {
return request({
url: '/inventory-manage/purchase/submit-approval',
method: 'put',
data: busNo
data: { busNo: busNo }
})
}
@@ -67,7 +67,7 @@ export function withdrawApproval(busNo) {
return request({
url: '/inventory-manage/purchase/withdraw-approval',
method: 'put',
data: busNo
data: { busNo: busNo }
})
}
@@ -127,7 +127,7 @@ export function submitApprovalReturn(busNo) {
return request({
url: '/inventory-manage/return/return-submit-approval',
method: 'put',
data: busNo
data: { busNo: busNo }
})
}