Compare commits
14 Commits
9ed35448ce
...
ae746cdd37
| Author | SHA1 | Date | |
|---|---|---|---|
| ae746cdd37 | |||
| b2c60ab76f | |||
|
|
8b6265801d | ||
| fec6e928d8 | |||
|
|
bf5a9674df | ||
| 471bf2b823 | |||
| 954462272e | |||
| 7b42e94b85 | |||
| 9a5d772c72 | |||
| d861c20d5e | |||
| 488573a51b | |||
| e35bdb5b9e | |||
|
|
259a5946c2 | ||
| d0d6cf3533 |
@@ -1,7 +1,10 @@
|
||||
package com.healthlink.his.web.empi.appservice;
|
||||
|
||||
import com.healthlink.his.administration.domain.Patient;
|
||||
import com.healthlink.his.empi.domain.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IEmpiAppService {
|
||||
EmpiPerson registerPerson(EmpiPerson p);
|
||||
void mergePersons(Long primaryId, List<Long> secondaryIds);
|
||||
@@ -9,4 +12,7 @@ public interface IEmpiAppService {
|
||||
EmpiPerson findByIdCard(String idCardNo);
|
||||
List<EmpiPersonIdMapping> getMappings(String globalId);
|
||||
Map<String, Object> getStatistics();
|
||||
}
|
||||
List<Patient> findLinkedPatients(String globalId);
|
||||
List<Patient> findLinkedPatientsByIdCard(String idCardNo);
|
||||
List<EmpiPerson> listPersons(String name, String idCardNo);
|
||||
}
|
||||
@@ -1,54 +1,129 @@
|
||||
package com.healthlink.his.web.empi.appservice.impl;
|
||||
import com.healthlink.his.empi.domain.*;
|
||||
import com.healthlink.his.empi.service.*;
|
||||
import com.healthlink.his.web.empi.appservice.IEmpiAppService;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.administration.domain.Patient;
|
||||
import com.healthlink.his.administration.service.IPatientService;
|
||||
import com.healthlink.his.empi.domain.EmpiPerson;
|
||||
import com.healthlink.his.empi.domain.EmpiPersonIdMapping;
|
||||
import com.healthlink.his.empi.domain.EmpiMergeLog;
|
||||
import com.healthlink.his.empi.service.IEmpiMergeLogService;
|
||||
import com.healthlink.his.empi.service.IEmpiPersonIdMappingService;
|
||||
import com.healthlink.his.empi.service.IEmpiPersonService;
|
||||
import com.healthlink.his.web.empi.appservice.IEmpiAppService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class EmpiAppServiceImpl implements IEmpiAppService {
|
||||
@Autowired private IEmpiPersonService personService;
|
||||
@Autowired private IEmpiPersonIdMappingService mappingService;
|
||||
@Autowired private IPatientService patientService;
|
||||
@Autowired private IEmpiMergeLogService mergeLogService;
|
||||
|
||||
@Override
|
||||
public EmpiPerson registerPerson(EmpiPerson p) {
|
||||
p.setGlobalId(UUID.randomUUID().toString().replace("-", "").substring(0, 16).toUpperCase());
|
||||
p.setMergeStatus("ACTIVE"); personService.save(p); return p;
|
||||
p.setMergeStatus("ACTIVE");
|
||||
personService.save(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergePersons(Long primaryId, List<Long> secondaryIds) {
|
||||
EmpiPerson primary = personService.getById(primaryId);
|
||||
for (Long secId : secondaryIds) {
|
||||
EmpiPerson sec = personService.getById(secId);
|
||||
if (sec == null) continue;
|
||||
List<EmpiPersonIdMapping> mappings = mappingService.list(new LambdaQueryWrapper<EmpiPersonIdMapping>()
|
||||
.eq(EmpiPersonIdMapping::getGlobalId, personService.getById(secId).getGlobalId()));
|
||||
.eq(EmpiPersonIdMapping::getGlobalId, sec.getGlobalId()));
|
||||
for (EmpiPersonIdMapping m : mappings) {
|
||||
m.setGlobalId(primary.getGlobalId());
|
||||
mappingService.updateById(m);
|
||||
}
|
||||
EmpiPerson sec = personService.getById(secId);
|
||||
sec.setMergeStatus("MERGED"); personService.updateById(sec);
|
||||
sec.setMergeStatus("MERGED");
|
||||
personService.updateById(sec);
|
||||
|
||||
EmpiMergeLog logRecord = new EmpiMergeLog();
|
||||
logRecord.setSourcePatientId(primaryId);
|
||||
logRecord.setTargetPatientId(secId);
|
||||
logRecord.setMergeType("MERGE");
|
||||
logRecord.setMergeReason("EMPI合并");
|
||||
logRecord.setMergeBy("system");
|
||||
logRecord.setMergeTime(new java.util.Date());
|
||||
logRecord.setStatus("MERGED");
|
||||
mergeLogService.save(logRecord);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmpiPerson findByGlobalId(String globalId) {
|
||||
return personService.getOne(new LambdaQueryWrapper<EmpiPerson>().eq(EmpiPerson::getGlobalId, globalId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmpiPerson findByIdCard(String idCardNo) {
|
||||
return personService.getOne(new LambdaQueryWrapper<EmpiPerson>().eq(EmpiPerson::getIdCardNo, idCardNo));
|
||||
return personService.getOne(new LambdaQueryWrapper<EmpiPerson>().eq(EmpiPerson::getIdCardNo, idCardNo).last("LIMIT 1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EmpiPersonIdMapping> getMappings(String globalId) {
|
||||
return mappingService.list(new LambdaQueryWrapper<EmpiPersonIdMapping>().eq(EmpiPersonIdMapping::getGlobalId, globalId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getStatistics() {
|
||||
Map<String, Object> r = new HashMap<>();
|
||||
r.put("totalPersons", personService.count());
|
||||
r.put("activePersons", personService.count(new LambdaQueryWrapper<EmpiPerson>().eq(EmpiPerson::getMergeStatus, "ACTIVE")));
|
||||
r.put("mergedPersons", personService.count(new LambdaQueryWrapper<EmpiPerson>().eq(EmpiPerson::getMergeStatus, "MERGED")));
|
||||
long total = personService.count();
|
||||
long activeCount = personService.count(new LambdaQueryWrapper<EmpiPerson>().eq(EmpiPerson::getMergeStatus, "ACTIVE"));
|
||||
long mergedCount = personService.count(new LambdaQueryWrapper<EmpiPerson>().eq(EmpiPerson::getMergeStatus, "MERGED"));
|
||||
r.put("totalPersons", total);
|
||||
r.put("activePersons", activeCount);
|
||||
r.put("mergedPersons", mergedCount);
|
||||
r.put("totalMappings", mappingService.count());
|
||||
r.put("pendingMerges", total > 0 ? total - activeCount - mergedCount : 0);
|
||||
r.put("duplicateRate", total > 0 ? Math.round((double) mergedCount / total * 10000) / 100.0 : 0);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过全局ID查询关联的院内患者记录
|
||||
*/
|
||||
public List<Patient> findLinkedPatients(String globalId) {
|
||||
List<EmpiPersonIdMapping> mappings = mappingService.list(
|
||||
new LambdaQueryWrapper<EmpiPersonIdMapping>().eq(EmpiPersonIdMapping::getGlobalId, globalId));
|
||||
if (mappings.isEmpty()) return Collections.emptyList();
|
||||
|
||||
List<Long> patientIds = mappings.stream()
|
||||
.map(EmpiPersonIdMapping::getLocalPatientId)
|
||||
.collect(Collectors.toList());
|
||||
return patientService.listByIds(patientIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过身份证号查询关联的院内患者记录
|
||||
*/
|
||||
public List<Patient> findLinkedPatientsByIdCard(String idCardNo) {
|
||||
EmpiPerson person = findByIdCard(idCardNo);
|
||||
if (person == null) return Collections.emptyList();
|
||||
return findLinkedPatients(person.getGlobalId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询EMPI患者列表
|
||||
*/
|
||||
@Override
|
||||
public List<EmpiPerson> listPersons(String name, String idCardNo) {
|
||||
LambdaQueryWrapper<EmpiPerson> wrapper = new LambdaQueryWrapper<>();
|
||||
if (name != null && !name.isEmpty()) {
|
||||
wrapper.like(EmpiPerson::getName, name);
|
||||
}
|
||||
if (idCardNo != null && !idCardNo.isEmpty()) {
|
||||
wrapper.like(EmpiPerson::getIdCardNo, idCardNo);
|
||||
}
|
||||
wrapper.orderByDesc(EmpiPerson::getId);
|
||||
return personService.list(wrapper);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
package com.healthlink.his.web.empi.controller;
|
||||
|
||||
import com.core.common.core.domain.AjaxResult;
|
||||
import com.healthlink.his.empi.domain.*;
|
||||
import com.healthlink.his.web.empi.appservice.IEmpiAppService;
|
||||
@@ -7,19 +8,69 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
@Tag(name = "患者主索引(EMPI)") @RestController @RequestMapping("/api/v1/empi")
|
||||
|
||||
@Tag(name = "患者主索引(EMPI)")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/empi")
|
||||
public class EmpiController {
|
||||
@Autowired private IEmpiAppService empiAppService;
|
||||
@Operation(summary = "注册患者") @PostMapping("/person")
|
||||
public AjaxResult register(@RequestBody EmpiPerson p) { return AjaxResult.success(empiAppService.registerPerson(p)); }
|
||||
@Operation(summary = "合并患者") @PostMapping("/merge")
|
||||
public AjaxResult merge(@RequestParam Long primaryId, @RequestParam List<Long> secondaryIds) { empiAppService.mergePersons(primaryId, secondaryIds); return AjaxResult.success(); }
|
||||
@Operation(summary = "按全局ID查询") @GetMapping("/person/global/{globalId}")
|
||||
public AjaxResult findByGlobalId(@PathVariable String globalId) { return AjaxResult.success(empiAppService.findByGlobalId(globalId)); }
|
||||
@Operation(summary = "按身份证查询") @GetMapping("/person/idcard/{idCardNo}")
|
||||
public AjaxResult findByIdCard(@PathVariable String idCardNo) { return AjaxResult.success(empiAppService.findByIdCard(idCardNo)); }
|
||||
@Operation(summary = "ID映射") @GetMapping("/mappings/{globalId}")
|
||||
public AjaxResult mappings(@PathVariable String globalId) { return AjaxResult.success(empiAppService.getMappings(globalId)); }
|
||||
@Operation(summary = "统计") @GetMapping("/statistics")
|
||||
public AjaxResult statistics() { return AjaxResult.success(empiAppService.getStatistics()); }
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private IEmpiAppService empiAppService;
|
||||
|
||||
@Operation(summary = "注册患者")
|
||||
@PostMapping("/person")
|
||||
public AjaxResult register(@RequestBody EmpiPerson p) {
|
||||
return AjaxResult.success(empiAppService.registerPerson(p));
|
||||
}
|
||||
|
||||
@Operation(summary = "合并患者")
|
||||
@PostMapping("/merge")
|
||||
public AjaxResult merge(@RequestParam Long primaryId, @RequestParam List<Long> secondaryIds) {
|
||||
empiAppService.mergePersons(primaryId, secondaryIds);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "按全局ID查询EMPI")
|
||||
@GetMapping("/person/global/{globalId}")
|
||||
public AjaxResult findByGlobalId(@PathVariable String globalId) {
|
||||
return AjaxResult.success(empiAppService.findByGlobalId(globalId));
|
||||
}
|
||||
|
||||
@Operation(summary = "按身份证查询EMPI")
|
||||
@GetMapping("/person/idcard/{idCardNo}")
|
||||
public AjaxResult findByIdCard(@PathVariable String idCardNo) {
|
||||
return AjaxResult.success(empiAppService.findByIdCard(idCardNo));
|
||||
}
|
||||
|
||||
@Operation(summary = "ID映射")
|
||||
@GetMapping("/mappings/{globalId}")
|
||||
public AjaxResult mappings(@PathVariable String globalId) {
|
||||
return AjaxResult.success(empiAppService.getMappings(globalId));
|
||||
}
|
||||
|
||||
@Operation(summary = "统计")
|
||||
@GetMapping("/statistics")
|
||||
public AjaxResult statistics() {
|
||||
return AjaxResult.success(empiAppService.getStatistics());
|
||||
}
|
||||
|
||||
@Operation(summary = "通过全局ID查询关联的院内患者记录")
|
||||
@GetMapping("/linked-patients/global/{globalId}")
|
||||
public AjaxResult findLinkedPatientsByGlobalId(@PathVariable String globalId) {
|
||||
return AjaxResult.success(empiAppService.findLinkedPatients(globalId));
|
||||
}
|
||||
|
||||
@Operation(summary = "通过身份证号查询关联的院内患者记录")
|
||||
@GetMapping("/linked-patients/idcard/{idCardNo}")
|
||||
public AjaxResult findLinkedPatientsByIdCard(@PathVariable String idCardNo) {
|
||||
return AjaxResult.success(empiAppService.findLinkedPatientsByIdCard(idCardNo));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询EMPI患者列表")
|
||||
@GetMapping("/persons")
|
||||
public AjaxResult listPersons(
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(required = false) String idCardNo) {
|
||||
return AjaxResult.success(empiAppService.listPersons(name, idCardNo));
|
||||
}
|
||||
}
|
||||
@@ -366,7 +366,15 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
|
||||
|
||||
// 根据执行状态过滤医嘱列表
|
||||
List<InpatientAdviceDto> filteredList = new ArrayList<>();
|
||||
if (EventStatus.COMPLETED.getValue().equals(exeStatus)) {
|
||||
if (EventStatus.PREPARATION.getValue().equals(exeStatus)) {
|
||||
// 待执行 - 过滤出没有已执行记录的医嘱(Bug #663修复)
|
||||
filteredList = inpatientAdviceList.stream().filter(
|
||||
advice -> advice.getExePerformRecordList() == null || advice.getExePerformRecordList().isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
// 更新分页数据
|
||||
inpatientAdvicePage.setRecords(filteredList);
|
||||
inpatientAdvicePage.setTotal(filteredList.size());
|
||||
} else if (EventStatus.COMPLETED.getValue().equals(exeStatus)) {
|
||||
// 已执行 - 过滤出有执行记录的医嘱
|
||||
filteredList = inpatientAdviceList.stream().filter(
|
||||
advice -> advice.getExePerformRecordList() != null && !advice.getExePerformRecordList().isEmpty())
|
||||
|
||||
@@ -29,7 +29,9 @@ import com.healthlink.his.web.patientmanage.dto.PatientBaseInfoDto;
|
||||
import com.healthlink.his.web.patientmanage.dto.PatientIdInfoDto;
|
||||
import com.healthlink.his.web.patientmanage.dto.PatientInfoInitDto;
|
||||
import com.healthlink.his.web.patientmanage.mapper.PatientManageMapper;
|
||||
import com.healthlink.his.empi.event.PatientSavedEvent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -54,6 +56,9 @@ public class PatientInformationServiceImpl implements IPatientInformationService
|
||||
@Autowired
|
||||
PatientManageMapper patientManageMapper;
|
||||
|
||||
@Autowired
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@Autowired
|
||||
private IPatientService patientService;
|
||||
|
||||
@@ -379,9 +384,13 @@ public class PatientInformationServiceImpl implements IPatientInformationService
|
||||
.set(Patient::getEducationLevel, patient.getEducationLevel())
|
||||
.set(Patient::getCompanyAddress, patient.getCompanyAddress());
|
||||
patientService.update(updateWrapper);
|
||||
// 发布患者保存事件,触发EMPI同步
|
||||
eventPublisher.publishEvent(new PatientSavedEvent(this, patient, false));
|
||||
} else {
|
||||
// 新增操作
|
||||
patientService.save(patient);
|
||||
// 发布患者保存事件,触发EMPI同步
|
||||
eventPublisher.publishEvent(new PatientSavedEvent(this, patient, true));
|
||||
}
|
||||
|
||||
return patient;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
-- V2026_0616_1: EMPI核心表 — empi_person + empi_person_id_mapping
|
||||
-- 补充 V20 中遗漏的两张EMPI核心表
|
||||
|
||||
-- 1. EMPI主索引表(全局患者主记录)
|
||||
CREATE TABLE IF NOT EXISTS empi_person (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
global_id VARCHAR(32) NOT NULL,
|
||||
id_card_no VARCHAR(20),
|
||||
patient_name VARCHAR(50),
|
||||
gender VARCHAR(10),
|
||||
birth_date DATE,
|
||||
phone VARCHAR(20),
|
||||
address TEXT,
|
||||
merge_status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
source_system VARCHAR(50),
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id INT DEFAULT 0,
|
||||
delete_flag VARCHAR(1) NOT NULL DEFAULT '0'
|
||||
);
|
||||
COMMENT ON TABLE empi_person IS 'EMPI患者主索引';
|
||||
COMMENT ON COLUMN empi_person.global_id IS '全局唯一患者ID';
|
||||
COMMENT ON COLUMN empi_person.merge_status IS '合并状态(ACTIVE/MERGED)';
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_ep_global ON empi_person(global_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ep_idcard ON empi_person(id_card_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_ep_name ON empi_person(patient_name);
|
||||
|
||||
-- 2. EMPI ID映射表(全局ID与院内系统患者ID的映射关系)
|
||||
CREATE TABLE IF NOT EXISTS empi_person_id_mapping (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
global_id VARCHAR(32) NOT NULL,
|
||||
local_patient_id BIGINT NOT NULL,
|
||||
source_system VARCHAR(50) NOT NULL,
|
||||
id_type VARCHAR(20) NOT NULL,
|
||||
id_value VARCHAR(100) NOT NULL,
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id INT DEFAULT 0,
|
||||
delete_flag VARCHAR(1) NOT NULL DEFAULT '0'
|
||||
);
|
||||
COMMENT ON TABLE empi_person_id_mapping IS 'EMPI患者ID映射表';
|
||||
COMMENT ON COLUMN empi_person_id_mapping.global_id IS 'EMPI全局患者ID';
|
||||
COMMENT ON COLUMN empi_person_id_mapping.local_patient_id IS '院内患者ID';
|
||||
COMMENT ON COLUMN empi_person_id_mapping.source_system IS '来源系统编码';
|
||||
COMMENT ON COLUMN empi_person_id_mapping.id_type IS '标识类型(MRN/INSURANCE/CARD等)';
|
||||
COMMENT ON COLUMN empi_person_id_mapping.id_value IS '标识值';
|
||||
CREATE INDEX IF NOT EXISTS idx_epim_global ON empi_person_id_mapping(global_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_epim_local ON empi_person_id_mapping(local_patient_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_epim_source ON empi_person_id_mapping(source_system);
|
||||
@@ -0,0 +1,22 @@
|
||||
-- V45: Create lab_activity_def_device_def table
|
||||
-- 检验活动定义与耗材/设备定义关联表
|
||||
|
||||
CREATE TABLE IF NOT EXISTS lab_activity_def_device_def (
|
||||
id BIGINT PRIMARY KEY,
|
||||
activity_definition_id BIGINT,
|
||||
device_definition_id BIGINT,
|
||||
device_definition_name VARCHAR(255),
|
||||
instrument_id BIGINT,
|
||||
instrument_name VARCHAR(255),
|
||||
device_quantity INTEGER DEFAULT 0,
|
||||
delete_flag VARCHAR(1) DEFAULT '0',
|
||||
activity_definition_name VARCHAR(255),
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP,
|
||||
tenant_id INTEGER
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_lab_act_dev_act_def_id ON lab_activity_def_device_def(activity_definition_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_lab_act_dev_tenant ON lab_activity_def_device_def(tenant_id);
|
||||
@@ -44,6 +44,7 @@
|
||||
ON T1.organization_id = T4.id
|
||||
AND T4.delete_flag = '0'
|
||||
WHERE T1.delete_flag = '0'
|
||||
AND T1.status_enum IN (4, 5)
|
||||
GROUP BY T1.id,
|
||||
T1.bus_no,
|
||||
T1.patient_id,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.healthlink.his.empi.event;
|
||||
|
||||
import com.healthlink.his.administration.domain.Patient;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
public class PatientSavedEvent extends ApplicationEvent {
|
||||
private final Patient patient;
|
||||
private final boolean isNew;
|
||||
|
||||
public PatientSavedEvent(Object source, Patient patient, boolean isNew) {
|
||||
super(source);
|
||||
this.patient = patient;
|
||||
this.isNew = isNew;
|
||||
}
|
||||
|
||||
public Patient getPatient() { return patient; }
|
||||
public boolean isNew() { return isNew; }
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.healthlink.his.empi.listener;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.administration.domain.Patient;
|
||||
import com.healthlink.his.empi.domain.EmpiPerson;
|
||||
import com.healthlink.his.empi.domain.EmpiPersonIdMapping;
|
||||
import com.healthlink.his.empi.event.PatientSavedEvent;
|
||||
import com.healthlink.his.empi.service.IEmpiPersonIdMappingService;
|
||||
import com.healthlink.his.empi.service.IEmpiPersonService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class EmpiSyncListener {
|
||||
|
||||
@Autowired
|
||||
private IEmpiPersonService empiPersonService;
|
||||
|
||||
@Autowired
|
||||
private IEmpiPersonIdMappingService empiPersonIdMappingService;
|
||||
|
||||
@EventListener
|
||||
public void onPatientSaved(PatientSavedEvent event) {
|
||||
Patient patient = event.getPatient();
|
||||
try {
|
||||
if (event.isNew()) {
|
||||
syncNewPatient(patient);
|
||||
} else {
|
||||
syncUpdatedPatient(patient);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("EMPI同步失败, patientId={}, error={}", patient.getId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void syncNewPatient(Patient patient) {
|
||||
String idCard = patient.getIdCard();
|
||||
EmpiPerson existingPerson = null;
|
||||
if (idCard != null && !idCard.isEmpty()) {
|
||||
existingPerson = empiPersonService.getOne(
|
||||
new LambdaQueryWrapper<EmpiPerson>()
|
||||
.eq(EmpiPerson::getIdCardNo, idCard)
|
||||
.eq(EmpiPerson::getMergeStatus, "ACTIVE"));
|
||||
}
|
||||
|
||||
if (existingPerson == null) {
|
||||
EmpiPerson person = new EmpiPerson();
|
||||
person.setGlobalId(UUID.randomUUID().toString().replace("-", "").substring(0, 16).toUpperCase());
|
||||
person.setIdCardNo(idCard);
|
||||
person.setName(patient.getName());
|
||||
person.setGender(patient.getGenderEnum() != null && patient.getGenderEnum() == 1 ? "M" : "F");
|
||||
person.setBirthDate(patient.getBirthDate());
|
||||
person.setPhone(patient.getPhone());
|
||||
person.setAddress(patient.getAddress());
|
||||
person.setMergeStatus("ACTIVE");
|
||||
person.setSourceSystem("HIS");
|
||||
empiPersonService.save(person);
|
||||
existingPerson = person;
|
||||
log.info("EMPI主索引创建: globalId={}, patientId={}", existingPerson.getGlobalId(), patient.getId());
|
||||
}
|
||||
|
||||
createMappingIfNeeded(existingPerson.getGlobalId(), patient);
|
||||
}
|
||||
|
||||
private void syncUpdatedPatient(Patient patient) {
|
||||
EmpiPersonIdMapping mapping = empiPersonIdMappingService.getOne(
|
||||
new LambdaQueryWrapper<EmpiPersonIdMapping>()
|
||||
.eq(EmpiPersonIdMapping::getLocalPatientId, patient.getId()));
|
||||
|
||||
if (mapping != null) {
|
||||
EmpiPerson person = empiPersonService.getOne(
|
||||
new LambdaQueryWrapper<EmpiPerson>()
|
||||
.eq(EmpiPerson::getGlobalId, mapping.getGlobalId()));
|
||||
if (person != null) {
|
||||
person.setName(patient.getName());
|
||||
person.setGender(patient.getGenderEnum() != null && patient.getGenderEnum() == 1 ? "M" : "F");
|
||||
person.setBirthDate(patient.getBirthDate());
|
||||
person.setPhone(patient.getPhone());
|
||||
person.setAddress(patient.getAddress());
|
||||
empiPersonService.updateById(person);
|
||||
}
|
||||
} else {
|
||||
syncNewPatient(patient);
|
||||
}
|
||||
}
|
||||
|
||||
private void createMappingIfNeeded(String globalId, Patient patient) {
|
||||
EmpiPersonIdMapping existing = empiPersonIdMappingService.getOne(
|
||||
new LambdaQueryWrapper<EmpiPersonIdMapping>()
|
||||
.eq(EmpiPersonIdMapping::getGlobalId, globalId)
|
||||
.eq(EmpiPersonIdMapping::getLocalPatientId, patient.getId()));
|
||||
|
||||
if (existing == null) {
|
||||
EmpiPersonIdMapping mapping = new EmpiPersonIdMapping();
|
||||
mapping.setGlobalId(globalId);
|
||||
mapping.setLocalPatientId(patient.getId());
|
||||
mapping.setSourceSystem("HIS");
|
||||
mapping.setIdType("MRN");
|
||||
mapping.setIdValue(patient.getBusNo() != null ? patient.getBusNo() : String.valueOf(patient.getId()));
|
||||
empiPersonIdMappingService.save(mapping);
|
||||
log.info("EMPI映射创建: globalId={}, patientId={}", globalId, patient.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="props.openAddDiagnosisDialog"
|
||||
v-model="openDialog"
|
||||
title="添加中医诊断"
|
||||
width="1300px"
|
||||
teleported
|
||||
@@ -153,6 +153,11 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const openDialog = ref(false);
|
||||
watch(() => props.openAddDiagnosisDialog, (val) => {
|
||||
openDialog.value = val;
|
||||
}, { immediate: true });
|
||||
|
||||
const conditionList = ref([]);
|
||||
const syndromeList = ref([]);
|
||||
const tcmDiagonsisList = ref([]);
|
||||
@@ -260,7 +265,8 @@ function handleOpen() {
|
||||
}
|
||||
|
||||
// 点击诊断列表处理,点击以后才显示证候列表
|
||||
function handleClickRow(row) {
|
||||
// vxe-table v4 cell-click 事件参数为 { row, column, rowIndex, ... },需解构获取实际行数据
|
||||
function handleClickRow({ row }) {
|
||||
if (syndromeSelected.value || tcmDiagonsisList.value.length == 0) {
|
||||
syndromeSelected.value = false;
|
||||
selectedDisease.value = true;
|
||||
@@ -286,7 +292,8 @@ function handleClickRow(row) {
|
||||
}
|
||||
}
|
||||
|
||||
function clickSyndromeRow(row) {
|
||||
// vxe-table v4 cell-click 事件参数为 { row, column, rowIndex, ... },需解构获取实际行数据
|
||||
function clickSyndromeRow({ row }) {
|
||||
// 检查是否已存在完全相同的诊断和证候
|
||||
let flag = true;
|
||||
const currentConditionName = tcmDiagonsisList.value[tcmDiagonsisList.value.length - 1].conditionName;
|
||||
|
||||
@@ -219,6 +219,7 @@
|
||||
<div class="diagnosis-popover-body">
|
||||
<diagnosislist
|
||||
:diagnosis-searchkey="diagnosisSearchkey"
|
||||
:med-type-code="scope.row.medTypeCode"
|
||||
@select-diagnosis="(row) => handleSelectDiagnosis(row, scope.row, scope.rowIndex)"
|
||||
/>
|
||||
</div>
|
||||
@@ -810,8 +811,18 @@ async function handleSaveDiagnosis() {
|
||||
// 开始加载状态,防止重复提交
|
||||
saveLoading.value = true;
|
||||
|
||||
// 保存前按排序号排序,并转换日期格式为后端期望的格式 yyyy/M/d HH:mm:ss
|
||||
const diagnosisChildList = form.value.diagnosisList.map(item => ({
|
||||
// 保存前按排序号排序,排除中医诊断(已通过中医对话框独立保存),并转换日期格式
|
||||
const westernOnlyList = form.value.diagnosisList
|
||||
.filter(item => item.typeName !== '中医诊断');
|
||||
|
||||
// 如果仅有中医诊断无西医诊断,直接刷新列表即可(中医已通过对话框独立保存)
|
||||
if (westernOnlyList.length === 0) {
|
||||
saveLoading.value = false;
|
||||
proxy.$modal.msgWarning('没有需要保存的西医诊断');
|
||||
return;
|
||||
}
|
||||
|
||||
const diagnosisChildList = westernOnlyList.map(item => ({
|
||||
...item,
|
||||
onsetDate: item.onsetDate ? formatDateStr(item.onsetDate, 'YYYY/M/D HH:mm:ss') : null,
|
||||
diagnosisTime: item.diagnosisTime ? formatDateStr(item.diagnosisTime, 'YYYY/M/D HH:mm:ss') : null
|
||||
|
||||
@@ -70,6 +70,11 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
/** 当前行的诊断类型编码,用于按分类过滤诊断列表 */
|
||||
medTypeCode: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['selectDiagnosis']);
|
||||
@@ -82,6 +87,18 @@ const queryParams = ref({
|
||||
});
|
||||
const diagnosisDefinitionList = ref([]);
|
||||
|
||||
/**
|
||||
* 将 medTypeCode 映射为后端 typeCode 参数
|
||||
* '1' (西医诊断) → typeCode='1'
|
||||
* '2','3' (中医主病/主证) → typeCode='2'
|
||||
* 其他 → 不过滤
|
||||
*/
|
||||
function mapMedTypeToTypeCode(medTypeCode) {
|
||||
if (medTypeCode === '1') return '1';
|
||||
if (medTypeCode === '2' || medTypeCode === '3') return '2';
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 监听外部传入的搜索关键字
|
||||
watch(
|
||||
() => props.diagnosisSearchkey,
|
||||
@@ -94,9 +111,22 @@ watch(
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 监听诊断类型变化(切换不同分类的行时重新加载)
|
||||
watch(
|
||||
() => props.medTypeCode,
|
||||
() => {
|
||||
queryParams.value.pageNo = 1;
|
||||
getList();
|
||||
}
|
||||
);
|
||||
|
||||
// 获取诊断列表
|
||||
function getList() {
|
||||
getDiagnosisDefinitionList(queryParams.value).then((res) => {
|
||||
const params = {
|
||||
...queryParams.value,
|
||||
typeCode: mapMedTypeToTypeCode(props.medTypeCode),
|
||||
};
|
||||
getDiagnosisDefinitionList(params).then((res) => {
|
||||
diagnosisDefinitionList.value = res.data.records || [];
|
||||
total.value = res.data.total || 0;
|
||||
});
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
<el-dialog
|
||||
v-model="open"
|
||||
:title="title"
|
||||
width="1000px"
|
||||
width="1200px"
|
||||
teleported
|
||||
:close-on-click-modal="false"
|
||||
@close="cancel"
|
||||
@@ -397,7 +397,7 @@
|
||||
</el-row>
|
||||
|
||||
<!-- 次要手术表格 -->
|
||||
<el-row v-if="form.secondarySurgeries && form.secondarySurgeries.length > 0">
|
||||
<el-row v-if="form.secondarySurgeries && form.secondarySurgeries.length > 0" style="margin-top: 12px;">
|
||||
<el-col
|
||||
:span="24"
|
||||
style="margin-bottom: 20px;"
|
||||
@@ -407,10 +407,12 @@
|
||||
border
|
||||
stripe
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
:column-config="{ resizable: true }"
|
||||
>
|
||||
<vxe-column
|
||||
title="手术名称"
|
||||
min-width="200"
|
||||
min-width="250"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-select
|
||||
@@ -518,7 +520,7 @@
|
||||
</vxe-column>
|
||||
<vxe-column
|
||||
title="操作"
|
||||
width="100"
|
||||
width="80"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">
|
||||
@@ -1815,4 +1817,11 @@ defineExpose({
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
/* Bug #770: 确保对话框表单内容可滚动,防止操作按钮遮盖字段 */
|
||||
:deep(.el-dialog__body) {
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -193,13 +193,13 @@
|
||||
/>
|
||||
</div>
|
||||
<vxe-table
|
||||
ref="prescriptionRef"
|
||||
:ref="(el) => { if (el) tableRefs[pIndex] = el }"
|
||||
v-loading="loading"
|
||||
:data="prescription.prescriptionList"
|
||||
:row-config="{ keyField: 'uniqueKey', expandRowKeys: prescription.expandOrder }"
|
||||
border
|
||||
@cell-click="clickRow"
|
||||
@cell-dblclick="(row) => clickRowDb(row, pIndex)"
|
||||
@cell-dblclick="({ row }) => clickRowDb(row, pIndex)"
|
||||
>
|
||||
<vxe-column
|
||||
type="expand"
|
||||
@@ -217,8 +217,8 @@
|
||||
style="padding: 16px; background: #f8f9fa; border-radius: 8px"
|
||||
>
|
||||
<template v-if="scope.row.adviceType == 1">
|
||||
<div style="display: flex; align-items: center; margin-bottom: 16px; gap: 8px">
|
||||
<span class="medicine-title">
|
||||
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: nowrap">
|
||||
<span class="medicine-title" style="flex-shrink: 0">
|
||||
{{
|
||||
scope.row.adviceName +
|
||||
' ' +
|
||||
@@ -230,119 +230,45 @@
|
||||
']'
|
||||
}}
|
||||
</span>
|
||||
<el-form-item
|
||||
prop="lotNumber"
|
||||
label="药房:"
|
||||
>
|
||||
<el-select
|
||||
v-model="scope.row.inventoryId"
|
||||
style="width: 380px; margin-right: 20px"
|
||||
placeholder="药房"
|
||||
>
|
||||
<el-form-item prop="lotNumber" label="药房:" style="margin-bottom: 0; flex-shrink: 0">
|
||||
<el-select v-model="scope.row.inventoryId" style="width: 280px" placeholder="药房">
|
||||
<el-option
|
||||
v-for="item in scope.row.stockList"
|
||||
:key="item.inventoryId"
|
||||
:value="item.inventoryId"
|
||||
:label="
|
||||
item.locationName +
|
||||
' ' +
|
||||
'批次号: ' +
|
||||
item.lotNumber +
|
||||
' ' +
|
||||
' 库存:' +
|
||||
item.quantity / scope.row.partPercent +
|
||||
item.unitCode_dictText +
|
||||
' 单价:' +
|
||||
item.price.toFixed(2) +
|
||||
'/' +
|
||||
item.unitCode_dictText
|
||||
"
|
||||
:label="item.locationName + ' 库存:' + item.quantity / scope.row.partPercent + item.unitCode_dictText + ' 单价:' + item.price.toFixed(2) + '/' + item.unitCode_dictText"
|
||||
@click="handleNumberClick(item, scope.rowIndex, pIndex)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div class="form-group">
|
||||
<!-- 单次剂量 -->
|
||||
<el-form-item
|
||||
label="数量:"
|
||||
prop="minUnitQuantity"
|
||||
class="required-field"
|
||||
data-prop="minUnitQuantity"
|
||||
>
|
||||
<el-input-number
|
||||
:ref="(el) => (inputRefs.minUnitQuantity = el)"
|
||||
v-model="scope.row.minUnitQuantity"
|
||||
:min="0"
|
||||
controls-position="right"
|
||||
:controls="false"
|
||||
style="width: 70px; margin-right: 20px"
|
||||
@input="
|
||||
() => {
|
||||
nextTick(() => {
|
||||
scope.row.totalPrice =
|
||||
scope.row.minUnitQuantity * scope.row.unitPrice;
|
||||
});
|
||||
}
|
||||
"
|
||||
@keyup.enter.prevent="
|
||||
handleEnter('minUnitQuantity', scope.row, scope.rowIndex, pIndex)
|
||||
"
|
||||
<el-form-item label="数量:" prop="minUnitQuantity" class="required-field" data-prop="minUnitQuantity" style="margin-bottom: 0; flex-shrink: 0">
|
||||
<el-input-number
|
||||
:ref="(el) => (inputRefs.minUnitQuantity = el)"
|
||||
v-model="scope.row.minUnitQuantity"
|
||||
:min="0" controls-position="right" :controls="false" style="width: 75px"
|
||||
@input="() => { nextTick(() => { scope.row.totalPrice = scope.row.minUnitQuantity * scope.row.unitPrice; }); }"
|
||||
@keyup.enter.prevent="handleEnter('minUnitQuantity', scope.row, scope.rowIndex, pIndex)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-select v-model="scope.row.minUnitCode" style="width: 75px; flex-shrink: 0" placeholder=" ">
|
||||
<template v-for="item in scope.row.unitCodeList" :key="item.value">
|
||||
<el-option
|
||||
v-if="scope.row.unitCodeList.length == 3 ? item.type == unitMap['minUnit'] : item.type == unitMap['unit']"
|
||||
:value="item.value" :label="item.label"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- 剂量单位 -->
|
||||
<el-select
|
||||
v-model="scope.row.minUnitCode"
|
||||
style="width: 80px; margin-right: 20px"
|
||||
placeholder=" "
|
||||
>
|
||||
<template
|
||||
v-for="item in scope.row.unitCodeList"
|
||||
:key="item.value"
|
||||
>
|
||||
<el-option
|
||||
v-if="
|
||||
scope.row.unitCodeList.length == 3
|
||||
? item.type == unitMap['minUnit']
|
||||
: item.type == unitMap['unit']
|
||||
"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</template>
|
||||
</el-select>
|
||||
<el-form-item label="特殊煎法:" prop="dosageInstruction" class="required-field" data-prop="dosageInstruction" style="margin-bottom: 0; flex-shrink: 0">
|
||||
<el-select v-model="scope.row.dosageInstruction" style="width: 95px" placeholder=" ">
|
||||
<template v-for="item in dosage_instruction" :key="item.value">
|
||||
<el-option :value="item.value" :label="item.label" />
|
||||
</template>
|
||||
</el-select>
|
||||
<!-- 单次剂量 -->
|
||||
<el-form-item
|
||||
label="特殊煎法:"
|
||||
prop="dosageInstruction"
|
||||
class="required-field"
|
||||
data-prop="dosageInstruction"
|
||||
>
|
||||
<el-select
|
||||
v-model="scope.row.dosageInstruction"
|
||||
style="width: 90px; margin-right: 20px"
|
||||
placeholder=" "
|
||||
>
|
||||
<template
|
||||
v-for="item in dosage_instruction"
|
||||
:key="item.value"
|
||||
>
|
||||
<el-option
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</template>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<span class="total-amount">
|
||||
总金额:{{
|
||||
scope.row.totalPrice ? scope.row.totalPrice + ' 元' : '0.00 元'
|
||||
}}
|
||||
</el-form-item>
|
||||
<span class="total-amount" style="flex-shrink: 0; white-space: nowrap">
|
||||
总金额:{{ scope.row.totalPrice ? scope.row.totalPrice + ' 元' : '0.00 元' }}
|
||||
</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleSaveSign(scope.row, scope.rowIndex, pIndex)"
|
||||
>
|
||||
<el-button type="primary" style="flex-shrink: 0" @click="handleSaveSign(scope.row, scope.rowIndex, pIndex)">
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -394,7 +320,7 @@
|
||||
:width="1200"
|
||||
>
|
||||
<tcmMedicineList
|
||||
ref="adviceTableRef"
|
||||
:ref="(el) => { if (el) activeAdviceTable = el }"
|
||||
:popover-visible="scope.row.showPopover"
|
||||
:advice-query-params="adviceQueryParams"
|
||||
:patient-info="props.patientInfo"
|
||||
@@ -416,7 +342,7 @@
|
||||
if (['ArrowUp', 'ArrowDown', 'Enter'].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
// 传递事件到弹窗容器
|
||||
adviceTableRef.handleKeyDown(e);
|
||||
activeAdviceTable?.handleKeyDown(e);
|
||||
}
|
||||
}
|
||||
"
|
||||
@@ -602,7 +528,7 @@ const tcmPrescriptionList = ref([
|
||||
},
|
||||
]);
|
||||
const unitCodeList = ref([]);
|
||||
const adviceTableRef = ref([]);
|
||||
const activeAdviceTable = ref(null);
|
||||
const organization = ref([]);
|
||||
const loading = ref(false);
|
||||
const rowRules = ref({
|
||||
@@ -625,7 +551,7 @@ const props = defineProps({
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
const prescriptionRef = ref();
|
||||
const tableRefs = ref({});
|
||||
const stockList = ref([]);
|
||||
const contractList = ref([]);
|
||||
const tcmDiagnosisList = ref([]);
|
||||
@@ -842,7 +768,7 @@ function handleAddMedicine(pIndex) {
|
||||
/**
|
||||
* 点击行赋值
|
||||
*/
|
||||
function clickRow(row) {
|
||||
function clickRow({ row }) {
|
||||
emit('selectDiagnosis', row);
|
||||
}
|
||||
|
||||
@@ -856,7 +782,7 @@ function clickRowDb(row, pIndex) {
|
||||
const index = prescription.prescriptionList.findIndex(
|
||||
(item) => item.uniqueKey === row.uniqueKey
|
||||
);
|
||||
prescription.prescriptionList[index] = row;
|
||||
prescription.prescriptionList.splice(index, 1, row);
|
||||
prescription.expandOrder = [row.uniqueKey];
|
||||
}
|
||||
}
|
||||
@@ -873,8 +799,14 @@ function handleFocus(row, index, pIndex) {
|
||||
row.showPopover = true;
|
||||
}
|
||||
|
||||
let blurTimer = null;
|
||||
|
||||
function handleBlur(row) {
|
||||
row.showPopover = false;
|
||||
if (blurTimer) clearTimeout(blurTimer);
|
||||
blurTimer = setTimeout(() => {
|
||||
row.showPopover = false;
|
||||
blurTimer = null;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function handleChange(value) {
|
||||
@@ -995,96 +927,59 @@ function getPrescriptionMedicineCount(prescriptionIndex) {
|
||||
* 选择药品回调
|
||||
*/
|
||||
function selectAdviceBase(key, row, pIndex) {
|
||||
// ... 保持原有逻辑,但使用对应处方的数据 ...
|
||||
const prescription = tcmPrescriptionList.value[pIndex];
|
||||
getOrgList();
|
||||
unitCodeList.value = [];
|
||||
unitCodeList.value.push({ value: row.unitCode, label: row.unitCode_dictText, type: 'unit' });
|
||||
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',
|
||||
});
|
||||
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',
|
||||
});
|
||||
if ((row.partAttributeEnum == 1 || row.partAttributeEnum == 3) && row.minUnitCode != row.unitCode) {
|
||||
unitCodeList.value.push({ value: row.minUnitCode, label: row.minUnitCode_dictText, type: "minUnit" });
|
||||
}
|
||||
if (row.adviceType == 2 && row.minUnitCode != row.unitCode) {
|
||||
unitCodeList.value.push({
|
||||
value: row.minUnitCode,
|
||||
label: row.minUnitCode_dictText,
|
||||
type: 'minUnit',
|
||||
});
|
||||
unitCodeList.value.push({ value: row.minUnitCode, label: row.minUnitCode_dictText, type: "minUnit" });
|
||||
}
|
||||
// vxe-table v4: keep row ref, mutate in-place (ref prescriptionlist.vue setValue)
|
||||
const existingRow = prescription.prescriptionList[rowIndex.value];
|
||||
if (!existingRow) return;
|
||||
const pk = existingRow.uniqueKey, pe = existingRow.isEdit, pg = existingRow.groupId;
|
||||
Object.keys(existingRow).forEach(k => { delete existingRow[k]; });
|
||||
const rowData = JSON.parse(JSON.stringify(row));
|
||||
prescription.prescriptionList[rowIndex.value] = {
|
||||
...prescription.prescriptionList[rowIndex.value], // 保留原有属性(如 uniqueKey, groupId)
|
||||
...rowData, // 覆盖药品信息
|
||||
statusEnum: 1, // 【关键修复】强制状态为“待签发/草稿”,确保能被保存过滤器捕获
|
||||
adviceDefinitionId: rowData.adviceDefinitionId // 确保这个字段有值,后端会根据这个字段过滤空行
|
||||
};
|
||||
prescription.prescriptionList[rowIndex.value].orgId = undefined;
|
||||
prescription.prescriptionList[rowIndex.value].dose = undefined;
|
||||
prescription.prescriptionList[rowIndex.value].unitCodeList = unitCodeList.value;
|
||||
prescription.prescriptionList[rowIndex.value].doseUnitCode = row.doseUnitCode;
|
||||
prescription.prescriptionList[rowIndex.value].minUnitCode = row.minUnitCode;
|
||||
prescription.prescriptionList[rowIndex.value].unitCode =
|
||||
row.partAttributeEnum == 1 ? row.minUnitCode : row.unitCode;
|
||||
prescription.prescriptionList[rowIndex.value].definitionId = JSON.parse(
|
||||
JSON.stringify(row)
|
||||
).chargeItemDefinitionId;
|
||||
|
||||
// 库存列表 + 价格列表拼成批次号的下拉框
|
||||
Object.assign(existingRow, rowData);
|
||||
existingRow.uniqueKey = pk; existingRow.isEdit = pe; existingRow.groupId = pg;
|
||||
existingRow.statusEnum = 1; existingRow.showPopover = false;
|
||||
existingRow.adviceDefinitionId = rowData.adviceDefinitionId;
|
||||
existingRow.orgId = undefined; existingRow.dose = undefined;
|
||||
existingRow.unitCodeList = unitCodeList.value;
|
||||
existingRow.doseUnitCode = row.doseUnitCode;
|
||||
existingRow.minUnitCode = row.minUnitCode;
|
||||
existingRow.unitCode = row.partAttributeEnum == 1 ? row.minUnitCode : row.unitCode;
|
||||
existingRow.definitionId = JSON.parse(JSON.stringify(row)).chargeItemDefinitionId;
|
||||
if (row.adviceType == 1 || row.adviceType == 2) {
|
||||
if (row.inventoryList && row.inventoryList.length == 0) {
|
||||
prescription.expandOrder = [];
|
||||
proxy.$modal.msgWarning('该项目无库存');
|
||||
return;
|
||||
prescription.expandOrder = []; proxy.$modal.msgWarning("无库存"); return;
|
||||
}
|
||||
stockList.value = row.inventoryList.map((item, index) => {
|
||||
return { ...item, ...row.priceList[index] };
|
||||
});
|
||||
prescription.prescriptionList[rowIndex.value].stockList = stockList.value;
|
||||
// 获取默认批次号的库存,如果没有让医生重新选
|
||||
let stock = stockList.value.filter((item) => {
|
||||
return item.lotNumber == row.defaultLotNumber;
|
||||
})[0];
|
||||
if (stock != {} && stock != undefined) {
|
||||
if (stock.quantity <= 0) {
|
||||
proxy.$modal.msgWarning('该项目库存不足,请选择其它库房');
|
||||
// return;
|
||||
}
|
||||
prescription.prescriptionList[rowIndex.value].lotNumber = stock.lotNumber;
|
||||
prescription.prescriptionList[rowIndex.value].inventoryId = stock.inventoryId;
|
||||
prescription.prescriptionList[rowIndex.value].locationId = stock.locationId;
|
||||
prescription.prescriptionList[rowIndex.value].unitPrice = stock.price;
|
||||
prescription.prescriptionList[rowIndex.value].positionName = stock.locationName;
|
||||
stockList.value = row.inventoryList.map((item, i) => ({ ...item, ...row.priceList[i] }));
|
||||
existingRow.stockList = stockList.value;
|
||||
const s = stockList.value.filter(it => it.lotNumber == row.defaultLotNumber)[0];
|
||||
if (s && Object.keys(s).length > 0) {
|
||||
if (s.quantity <= 0) proxy.$modal.msgWarning("库存不足");
|
||||
existingRow.lotNumber = s.lotNumber; existingRow.inventoryId = s.inventoryId;
|
||||
existingRow.locationId = s.locationId; existingRow.unitPrice = s.price;
|
||||
existingRow.positionName = s.locationName;
|
||||
}
|
||||
} else {
|
||||
prescription.prescriptionList[rowIndex.value].orgId = JSON.parse(
|
||||
JSON.stringify(row)
|
||||
).positionId;
|
||||
prescription.prescriptionList[rowIndex.value].unitPrice = row.priceList[0].price;
|
||||
existingRow.orgId = JSON.parse(JSON.stringify(row)).positionId;
|
||||
existingRow.unitPrice = row.priceList[0].price;
|
||||
}
|
||||
prescription.expandOrder = [key];
|
||||
nextTick(() => {
|
||||
const t = tableRefs.value[pIndex];
|
||||
if (t && t.setRowExpand) t.setRowExpand([existingRow], true);
|
||||
if (row.adviceType == 1) {
|
||||
if (row.injectFlag == 1) {
|
||||
inputRefs.value['executeNum']?.focus();
|
||||
} else {
|
||||
inputRefs.value['dose']?.focus();
|
||||
}
|
||||
} else {
|
||||
inputRefs.value['quantity']?.focus();
|
||||
}
|
||||
row.injectFlag == 1 ? inputRefs.value.executeNum?.focus() : inputRefs.value.dose?.focus();
|
||||
} else { inputRefs.value.quantity?.focus(); }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1246,6 +1141,12 @@ function handleSaveSign(row, index, pIndex) {
|
||||
}
|
||||
|
||||
row.contentJson = JSON.stringify(row);
|
||||
// 强制刷新数组引用,触发 vxe-table 重渲染(切换只读模式 + 关闭展开行)
|
||||
prescription.prescriptionList = [...prescription.prescriptionList];
|
||||
nextTick(() => {
|
||||
const t = tableRefs.value[pIndex];
|
||||
if (t && t.clearRowExpand) t.clearRowExpand();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1345,6 +1246,8 @@ function escKeyListener(e) {
|
||||
}
|
||||
prescription.prescriptionList.shift();
|
||||
prescription.isAdding = false;
|
||||
// 强制刷新数组引用,触发 vxe-table 重渲染
|
||||
prescription.prescriptionList = [...prescription.prescriptionList];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// EMPI基础操作
|
||||
export function registerPerson(data) { return request({ url: '/api/v1/empi/person', method: 'post', data }) }
|
||||
export function mergePersons(primaryId, secondaryIds) { return request({ url: '/api/v1/empi/merge', method: 'post', params: { primaryId, secondaryIds: secondaryIds.join(',') } }) }
|
||||
export function findByGlobalId(globalId) { return request({ url: '/api/v1/empi/person/global/' + globalId, method: 'get' }) }
|
||||
export function findByIdCard(idCardNo) { return request({ url: '/api/v1/empi/person/idcard/' + idCardNo, method: 'get' }) }
|
||||
export function getMappings(globalId) { return request({ url: '/api/v1/empi/mappings/' + globalId, method: 'get' }) }
|
||||
export function getStatistics() { return request({ url: '/api/v1/empi/statistics', method: 'get' }) }
|
||||
export function listPersons(params) { return request({ url: '/api/v1/empi/persons', method: 'get', params }) }
|
||||
|
||||
// 关联院内患者查询
|
||||
export function findLinkedPatientsByGlobalId(globalId) { return request({ url: '/api/v1/empi/linked-patients/global/' + globalId, method: 'get' }) }
|
||||
export function findLinkedPatientsByIdCard(idCardNo) { return request({ url: '/api/v1/empi/linked-patients/idcard/' + idCardNo, method: 'get' }) }
|
||||
|
||||
// EMPI增强功能
|
||||
export function getPhotos(patientId) { return request({ url: '/empi-enhanced/photo/list', method: 'get', params: { patientId } }) }
|
||||
export function addPhoto(data) { return request({ url: '/empi-enhanced/photo/add', method: 'post', data }) }
|
||||
export function getFamilyMembers(patientId) { return request({ url: '/empi-enhanced/family/list', method: 'get', params: { patientId } }) }
|
||||
@@ -14,4 +21,4 @@ export function addFamilyMember(data) { return request({ url: '/empi-enhanced/fa
|
||||
export function deleteFamilyMember(id) { return request({ url: '/empi-enhanced/family/delete', method: 'delete', params: { id } }) }
|
||||
export function getMergeLogPage(params) { return request({ url: '/empi-enhanced/merge-log/page', method: 'get', params }) }
|
||||
export function addMergeLog(data) { return request({ url: '/empi-enhanced/merge-log/add', method: 'post', data }) }
|
||||
export function undoMergeLog(data) { return request({ url: '/empi-enhanced/merge-log/undo', method: 'post', data }) }
|
||||
export function undoMergeLog(data) { return request({ url: '/empi-enhanced/merge-log/undo', method: 'post', data }) }
|
||||
@@ -1,29 +1,105 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card>
|
||||
<template #header><span>患者合并管理</span></template>
|
||||
<template #header>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||
<span>患者合并管理</span>
|
||||
<el-button type="primary" :disabled="!primaryPatient" @click="handleMerge">
|
||||
合并选中患者 ({{ selectedRows.length }})
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :inline="true" class="mb8">
|
||||
<el-form-item label="主患者ID"><el-input v-model="primaryId" placeholder="主患者ID" /></el-form-item>
|
||||
<el-form-item label="待合并ID"><el-input v-model="secondaryIds" placeholder="逗号分隔多个ID" /></el-form-item>
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="searchName" placeholder="患者姓名" clearable @keyup.enter="loadPatients" />
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证号">
|
||||
<el-input v-model="searchIdCard" placeholder="身份证号" clearable @keyup.enter="loadPatients" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleMerge">合并</el-button>
|
||||
<el-button type="primary" @click="loadPatients">查询</el-button>
|
||||
<el-button @click="searchName=''; searchIdCard=''; loadPatients()">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="mt8">
|
||||
<template #header><span>合并日志</span></template>
|
||||
<el-table v-loading="loading" :data="mergeLogs">
|
||||
<el-table-column label="主患者" prop="primaryPatientName" width="120" />
|
||||
<el-table-column label="被合并患者" prop="secondaryPatientName" width="120" />
|
||||
<el-table-column label="合并原因" prop="mergeReason" width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作人" prop="operatorName" width="100" />
|
||||
<el-table-column label="合并时间" prop="mergeTime" width="170" />
|
||||
<el-table-column label="状态" prop="status" width="90">
|
||||
<template #default="s"><el-tag :type="s.row.status==='ACTIVE'?'success':'info'">{{ s.row.status === 'ACTIVE' ? '有效' : '已撤销' }}</el-tag></template>
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:12px">
|
||||
先点击"设为主患者"选一个保留的,再勾选要合并的其他患者,最后点右上角合并按钮
|
||||
</el-alert>
|
||||
<el-table v-loading="loading" :data="patientList" border stripe @selection-change="handleSelectionChange" row-key="id">
|
||||
<el-table-column type="selection" width="50" :selectable="canSelect" />
|
||||
<el-table-column prop="id" label="ID" width="60" />
|
||||
<el-table-column prop="globalId" label="全局ID" width="160" />
|
||||
<el-table-column prop="name" label="姓名" width="100">
|
||||
<template #default="{row}">
|
||||
<span :style="primaryPatient && primaryPatient.id === row.id ? 'color:#409eff;font-weight:bold' : ''">
|
||||
{{ row.name }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="gender" label="性别" width="60">
|
||||
<template #default="{row}">{{ row.gender === 'M' ? '男' : row.gender === 'F' ? '女' : row.gender }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="birthDate" label="出生日期" width="110">
|
||||
<template #default="{row}">{{ row.birthDate ? row.birthDate.substring(0,10) : '' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="idCardNo" label="身份证号" width="180" />
|
||||
<el-table-column prop="phone" label="电话" width="130" />
|
||||
<el-table-column prop="mergeStatus" label="状态" width="80">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.mergeStatus === 'ACTIVE' ? 'success' : row.mergeStatus === 'MERGED' ? 'info' : 'warning'" size="small">
|
||||
{{ row.mergeStatus === 'ACTIVE' ? '正常' : row.mergeStatus === 'MERGED' ? '已合并' : '待处理' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="s">
|
||||
<el-button link type="warning" v-if="s.row.status==='ACTIVE'" @click="handleUndo(s.row)">撤销</el-button>
|
||||
<template #default="{row}">
|
||||
<el-button link type="primary" size="small"
|
||||
:type="primaryPatient && primaryPatient.id === row.id ? 'success' : ''"
|
||||
@click="setPrimary(row)">
|
||||
{{ primaryPatient && primaryPatient.id === row.id ? '已选为主' : '设为主患者' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="confirmVisible" title="确认合并" width="500px">
|
||||
<el-descriptions title="主患者(保留)" :column="2" border size="small">
|
||||
<el-descriptions-item label="姓名">{{ primaryPatient?.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="全局ID">{{ primaryPatient?.globalId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证">{{ primaryPatient?.idCardNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="电话">{{ primaryPatient?.phone }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-divider />
|
||||
<p style="font-weight:bold;margin-bottom:8px">将被合并的患者({{ selectedRows.length }}人):</p>
|
||||
<el-table :data="selectedRows" border size="small" max-height="200">
|
||||
<el-table-column prop="name" label="姓名" width="80" />
|
||||
<el-table-column prop="globalId" label="全局ID" width="160" />
|
||||
<el-table-column prop="idCardNo" label="身份证号" width="180" />
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="confirmVisible = false">取消</el-button>
|
||||
<el-button type="danger" @click="doMerge">确认合并</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-card style="margin-top:16px">
|
||||
<template #header><span>合并日志</span></template>
|
||||
<el-table v-loading="logLoading" :data="mergeLogs" border stripe>
|
||||
<el-table-column label="主患者" prop="sourcePatientId" width="120">
|
||||
<template #default="{row}">{{ getPatientName(row.sourcePatientId) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="被合并" prop="targetPatientId" width="120">
|
||||
<template #default="{row}">{{ getPatientName(row.targetPatientId) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" prop="mergeType" width="80" />
|
||||
<el-table-column label="原因" prop="mergeReason" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="操作人" prop="mergeBy" width="90" />
|
||||
<el-table-column label="时间" prop="mergeTime" width="170" />
|
||||
<el-table-column label="状态" prop="status" width="80">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.status==='MERGED'?'success':'info'" size="small">
|
||||
{{ row.status === 'MERGED' ? '已合并' : '已撤回' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -33,22 +109,73 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { mergePersons, getMergeLogPage, undoMergeLog } from '../api'
|
||||
import { mergePersons, getMergeLogPage, listPersons } from '../api'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const loading = ref(false); const mergeLogs = ref([])
|
||||
const primaryId = ref(''); const secondaryIds = ref('')
|
||||
const loading = ref(false)
|
||||
const logLoading = ref(false)
|
||||
const patientList = ref([])
|
||||
const mergeLogs = ref([])
|
||||
const searchName = ref('')
|
||||
const searchIdCard = ref('')
|
||||
const primaryPatient = ref(null)
|
||||
const selectedRows = ref([])
|
||||
const confirmVisible = ref(false)
|
||||
const tableRef = ref(null)
|
||||
|
||||
const loadLogs = async () => { loading.value = true; const res = await getMergeLogPage({ pageNo: 1, pageSize: 20 }); mergeLogs.value = res.data?.records || []; loading.value = false }
|
||||
const handleMerge = async () => {
|
||||
if (!primaryId.value || !secondaryIds.value) { ElMessage.warning('请填写ID'); return }
|
||||
await ElMessageBox.confirm('确认合并?', '提示', { type: 'warning' })
|
||||
await mergePersons(primaryId.value, secondaryIds.value.split(',').map(Number))
|
||||
ElMessage.success('合并成功'); primaryId.value = ''; secondaryIds.value = ''; loadLogs()
|
||||
const loadPatients = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await listPersons({ name: searchName.value, idCardNo: searchIdCard.value })
|
||||
patientList.value = res.data || []
|
||||
} catch (e) {
|
||||
patientList.value = []
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
const handleUndo = async (row) => {
|
||||
await ElMessageBox.confirm('确认撤销合并?', '提示', { type: 'warning' })
|
||||
await undoMergeLog({ id: row.id, operatorName: '管理员' }); ElMessage.success('已撤销'); loadLogs()
|
||||
|
||||
const loadLogs = async () => {
|
||||
logLoading.value = true
|
||||
const res = await getMergeLogPage({ pageNo: 1, pageSize: 20 })
|
||||
mergeLogs.value = res.data?.records || []
|
||||
logLoading.value = false
|
||||
}
|
||||
onMounted(() => loadLogs())
|
||||
</script>
|
||||
|
||||
const canSelect = (row) => row.mergeStatus !== 'MERGED'
|
||||
|
||||
const handleSelectionChange = (selection) => {
|
||||
selectedRows.value = selection.filter(r => r.id !== primaryPatient.value?.id)
|
||||
}
|
||||
|
||||
const setPrimary = (row) => {
|
||||
primaryPatient.value = row
|
||||
ElMessage.info('主患者已设为: ' + row.name)
|
||||
}
|
||||
|
||||
const handleMerge = () => {
|
||||
if (!primaryPatient.value) { ElMessage.warning('请先选择主患者'); return }
|
||||
if (selectedRows.value.length === 0) { ElMessage.warning('请勾选要合并的患者'); return }
|
||||
confirmVisible.value = true
|
||||
}
|
||||
|
||||
const doMerge = async () => {
|
||||
try {
|
||||
await mergePersons(primaryPatient.value.id, selectedRows.value.map(r => r.id))
|
||||
ElMessage.success('合并成功')
|
||||
confirmVisible.value = false
|
||||
primaryPatient.value = null
|
||||
selectedRows.value = []
|
||||
loadPatients()
|
||||
loadLogs()
|
||||
} catch (e) {
|
||||
ElMessage.error('合并失败')
|
||||
}
|
||||
}
|
||||
|
||||
const getPatientName = (id) => {
|
||||
const p = patientList.value.find(x => x.id === id)
|
||||
return p ? p.name : id
|
||||
}
|
||||
|
||||
onMounted(() => { loadPatients(); loadLogs() })
|
||||
</script>
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="总患者数" :value="stats.totalPatients || 0" /></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="已合并" :value="stats.mergedPatients || 0" /></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="总患者数" :value="stats.totalPersons || 0" /></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="已合并" :value="stats.mergedPersons || 0" /></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="待合并" :value="stats.pendingMerges || 0" /></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="重复率" :value="stats.duplicateRate || 0" suffix="%" /></el-card></el-col>
|
||||
</el-row>
|
||||
@@ -18,25 +18,66 @@
|
||||
<el-form-item label="身份证号"><el-input v-model="searchForm.idCardNo" placeholder="身份证号" clearable /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="searchForm={};patientData=null">重置</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-descriptions v-if="patientData" :column="2" border>
|
||||
<el-descriptions-item label="全局ID">{{ patientData.globalId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ patientData.patientName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ patientData.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ patientData.gender === 'M' ? '男' : '女' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出生日期">{{ patientData.birthDate }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">{{ patientData.idCardNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">{{ patientData.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="地址" :span="2">{{ patientData.address }}</el-descriptions-item>
|
||||
<el-descriptions-item label="来源系统">{{ patientData.sourceSystem }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="patientData.mergeStatus === 'ACTIVE' ? 'success' : 'warning'" size="small">
|
||||
{{ patientData.mergeStatus === 'ACTIVE' ? '正常' : '已合并' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-empty v-else description="请输入查询条件" />
|
||||
</el-card>
|
||||
|
||||
<!-- 关联院内患者记录 -->
|
||||
<el-card v-if="linkedPatients.length > 0" style="margin-top:16px">
|
||||
<template #header>
|
||||
<span>关联院内患者记录 ({{ linkedPatients.length }})</span>
|
||||
</template>
|
||||
<el-table :data="linkedPatients" border stripe>
|
||||
<el-table-column prop="id" label="院内ID" width="100" />
|
||||
<el-table-column prop="busNo" label="病历号" width="140" />
|
||||
<el-table-column prop="name" label="姓名" width="100" />
|
||||
<el-table-column prop="genderEnum" label="性别" width="60">
|
||||
<template #default="{row}">{{ row.genderEnum === 1 ? '男' : '女' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="birthDate" label="出生日期" width="120">
|
||||
<template #default="{row}">{{ row.birthDate ? row.birthDate.substring(0,10) : '' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="phone" label="电话" width="130" />
|
||||
<el-table-column prop="idCard" label="身份证号" width="180" />
|
||||
<el-table-column prop="address" label="地址" min-width="150" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- ID映射列表 -->
|
||||
<el-card v-if="mappings.length > 0" style="margin-top:16px">
|
||||
<template #header>
|
||||
<span>ID映射关系</span>
|
||||
</template>
|
||||
<el-table :data="mappings" border stripe>
|
||||
<el-table-column prop="globalId" label="全局ID" width="180" />
|
||||
<el-table-column prop="localPatientId" label="院内患者ID" width="120" />
|
||||
<el-table-column prop="sourceSystem" label="来源系统" width="100" />
|
||||
<el-table-column prop="idType" label="标识类型" width="100" />
|
||||
<el-table-column prop="idValue" label="标识值" min-width="150" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog title="注册患者" v-model="dialogVisible" width="600px">
|
||||
<el-form :model="formData" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12"><el-form-item label="姓名"><el-input v-model="formData.patientName" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="姓名"><el-input v-model="formData.name" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="性别">
|
||||
<el-select v-model="formData.gender"><el-option v-for="d in sys_user_sex" :key="d.value" :label="d.label" :value="d.value" /></el-select>
|
||||
</el-form-item></el-col>
|
||||
@@ -57,23 +98,58 @@
|
||||
<script setup>
|
||||
import { useDict } from '@/utils/dict'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { registerPerson, findByGlobalId, findByIdCard, getStatistics } from '../api'
|
||||
import {
|
||||
registerPerson, findByGlobalId, findByIdCard, getStatistics,
|
||||
findLinkedPatientsByGlobalId, findLinkedPatientsByIdCard, getMappings
|
||||
} from '../api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const { sys_user_sex } = useDict('sys_user_sex')
|
||||
const stats = ref({})
|
||||
const searchForm = reactive({ globalId: '', idCardNo: '' })
|
||||
const patientData = ref(null)
|
||||
const linkedPatients = ref([])
|
||||
const mappings = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
const formData = ref({})
|
||||
|
||||
const loadStats = async () => { const res = await getStatistics(); stats.value = res.data || {} }
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (searchForm.globalId) { const res = await findByGlobalId(searchForm.globalId); patientData.value = res.data }
|
||||
else if (searchForm.idCardNo) { const res = await findByIdCard(searchForm.idCardNo); patientData.value = res.data }
|
||||
else { ElMessage.warning('请输入查询条件') }
|
||||
linkedPatients.value = []
|
||||
mappings.value = []
|
||||
if (searchForm.globalId) {
|
||||
const res = await findByGlobalId(searchForm.globalId)
|
||||
patientData.value = res.data
|
||||
if (res.data) {
|
||||
const lp = await findLinkedPatientsByGlobalId(searchForm.globalId)
|
||||
linkedPatients.value = lp.data || []
|
||||
const mp = await getMappings(searchForm.globalId)
|
||||
mappings.value = mp.data || []
|
||||
}
|
||||
} else if (searchForm.idCardNo) {
|
||||
const res = await findByIdCard(searchForm.idCardNo)
|
||||
patientData.value = res.data
|
||||
if (res.data) {
|
||||
const lp = await findLinkedPatientsByIdCard(searchForm.idCardNo)
|
||||
linkedPatients.value = lp.data || []
|
||||
const mp = await getMappings(res.data.globalId)
|
||||
mappings.value = mp.data || []
|
||||
}
|
||||
} else {
|
||||
ElMessage.warning('请输入查询条件')
|
||||
}
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchForm.globalId = ''
|
||||
searchForm.idCardNo = ''
|
||||
patientData.value = null
|
||||
linkedPatients.value = []
|
||||
mappings.value = []
|
||||
}
|
||||
|
||||
const handleRegister = () => { formData.value = {}; dialogVisible.value = true }
|
||||
const submitForm = async () => { await registerPerson(formData.value); ElMessage.success('注册成功'); dialogVisible.value = false; loadStats() }
|
||||
onMounted(() => loadStats())
|
||||
</script>
|
||||
</script>
|
||||
Reference in New Issue
Block a user