feat(empi): T9.1 患者身份合并/拆分 — 后端splitPatients接口+前端拆分管理tab

This commit is contained in:
2026-06-18 14:16:19 +08:00
parent 20934572d2
commit 0c0fd33155
5 changed files with 181 additions and 3 deletions

View File

@@ -15,4 +15,5 @@ public interface IEmpiAppService {
List<Patient> findLinkedPatients(String globalId);
List<Patient> findLinkedPatientsByIdCard(String idCardNo);
List<EmpiPerson> listPersons(String name, String idCardNo);
void splitPatients(Long primaryId, List<Long> secondaryIds);
}

View File

@@ -126,4 +126,25 @@ public class EmpiAppServiceImpl implements IEmpiAppService {
wrapper.orderByDesc(EmpiPerson::getId);
return personService.list(wrapper);
}
@Override
public void splitPatients(Long primaryId, List<Long> secondaryIds) {
EmpiPerson primary = personService.getById(primaryId);
if (primary == null) throw new RuntimeException("主患者不存在");
for (Long secId : secondaryIds) {
EmpiPerson sec = personService.getById(secId);
if (sec == null || !"MERGED".equals(sec.getMergeStatus())) continue;
sec.setMergeStatus("ACTIVE");
personService.updateById(sec);
EmpiMergeLog logRecord = new EmpiMergeLog();
logRecord.setSourcePatientId(primaryId);
logRecord.setTargetPatientId(secId);
logRecord.setMergeType("SPLIT");
logRecord.setMergeReason("EMPI拆分");
logRecord.setMergeBy("system");
logRecord.setMergeTime(new Date());
logRecord.setStatus("SPLIT");
mergeLogService.save(logRecord);
}
}
}

View File

@@ -6,6 +6,7 @@ import com.healthlink.his.web.empi.appservice.IEmpiAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -25,11 +26,20 @@ public class EmpiController {
@Operation(summary = "合并患者")
@PostMapping("/merge")
@PreAuthorize("infection:empi:edit")
public AjaxResult merge(@RequestParam Long primaryId, @RequestParam List<Long> secondaryIds) {
empiAppService.mergePersons(primaryId, secondaryIds);
return AjaxResult.success();
}
@Operation(summary = "拆分患者")
@PostMapping("/split")
@PreAuthorize("infection:empi:edit")
public AjaxResult split(@RequestParam Long primaryId, @RequestParam List<Long> secondaryIds) {
empiAppService.splitPatients(primaryId, secondaryIds);
return AjaxResult.success();
}
@Operation(summary = "按全局ID查询EMPI")
@GetMapping("/person/global/{globalId}")
public AjaxResult findByGlobalId(@PathVariable String globalId) {

View File

@@ -3,6 +3,7 @@ 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 splitPersons(primaryId, secondaryIds) { return request({ url: '/api/v1/empi/split', 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' }) }

View File

@@ -136,12 +136,127 @@
:type="row.status==='MERGED'?'success':'info'"
size="small"
>
{{ row.status==='MERGED'?'已合并':'已撤回' }}
{{ row.status==='MERGED'?'已合并':row.status==='SPLIT'?'已拆分':'已撤回' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane
label="拆分管理"
name="split"
>
<el-alert
type="info"
:closable="false"
style="margin-bottom:12px"
>
选择一个已合并的患者作为"来源患者"再勾选要拆分的患者点击"拆分选中患者"
</el-alert>
<el-form
:inline="true"
class="mb8"
>
<el-form-item label="姓名">
<el-input
v-model="splitSearchName"
placeholder="患者姓名"
clearable
@keyup.enter="loadSplitPatients"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="loadSplitPatients"
>
查询
</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="splitLoading"
:data="splitPatientsList"
border
stripe
row-key="id"
@selection-change="handleSplitSelection"
>
<el-table-column
type="selection"
width="50"
:selectable="(row) => row.mergeStatus === 'MERGED'"
/>
<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"
/>
<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="idCardNo"
label="身份证号"
width="180"
/>
<el-table-column
prop="mergeStatus"
label="状态"
width="80"
>
<template #default="{row}">
<el-tag
:type="row.mergeStatus === 'MERGED' ? 'info' : 'success'"
size="small"
>
{{ row.mergeStatus === 'MERGED' ? '已合并' : '正常' }}
</el-tag>
</template>
</el-table-column>
<el-table-column
label="操作"
width="120"
>
<template #default="{row}">
<el-button
link
type="primary"
size="small"
:type="splitSource && splitSource.id === row.id ? 'success' : ''"
@click="splitSource = row"
>
{{ splitSource && splitSource.id === row.id ? '已选为来源' : '设为来源' }}
</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top:12px;text-align:right">
<el-button
type="warning"
:disabled="!splitSource || splitSelectedRows.length === 0"
@click="handleSplit"
>
拆分选中患者 ({{ splitSelectedRows.length }})
</el-button>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
@@ -149,7 +264,7 @@
<script setup>
import {ref,reactive,onMounted} from 'vue'
import {ElMessage,ElMessageBox} from 'element-plus'
import {getFamilyMembers,addFamilyMember,deleteFamilyMember,getMergeLogPage} from './api'
import {getFamilyMembers,addFamilyMember,deleteFamilyMember,getMergeLogPage,listPersons,splitPersons} from './api'
const tab=ref('family')
const searchPatientId=ref('')
const familyData=ref([]),mergeData=ref([])
@@ -158,5 +273,35 @@ const familyForm=reactive({patientId:null,memberName:'',relationship:'',gender:'
const loadFamily=async()=>{if(!searchPatientId.value)return;const r=await getFamilyMembers({patientId:searchPatientId.value});familyData.value=r.data||[]}
const deleteFamily=async(id)=>{await ElMessageBox.confirm('确认删除?');await deleteFamilyMember(id);ElMessage.success('已删除');loadFamily()}
const loadData=async()=>{const m=await getMergeLogPage({pageNo:1,pageSize:50});mergeData.value=m.data?.records||[]}
onMounted(()=>loadData())
const splitLoading=ref(false)
const splitPatientsList=ref([])
const splitSearchName=ref('')
const splitSource=ref(null)
const splitSelectedRows=ref([])
const loadSplitPatients=async()=>{
splitLoading.value=true
try{
const res=await listPersons({name:splitSearchName.value})
splitPatientsList.value=(res.data||[]).filter(p=>p.mergeStatus==='MERGED')
}catch(e){splitPatientsList.value=[]}
splitLoading.value=false
}
const handleSplitSelection=(selection)=>{splitSelectedRows.value=selection.filter(r=>r.id!==splitSource.value?.id)}
const handleSplit=async()=>{
if(!splitSource.value){ElMessage.warning('请先选择来源患者');return}
if(splitSelectedRows.value.length===0){ElMessage.warning('请勾选要拆分的患者');return}
await ElMessageBox.confirm(`确认拆分 ${splitSelectedRows.value.length} 个患者?`)
try{
await splitPersons(splitSource.value.id,splitSelectedRows.value.map(r=>r.id))
ElMessage.success('拆分成功')
splitSource.value=null
splitSelectedRows.value=[]
loadSplitPatients()
loadData()
}catch(e){ElMessage.error('拆分失败')}
}
onMounted(()=>{loadData();loadSplitPatients()})
</script>