feat(V40): EMPI患者主索引 — 完整前端+DB修复+5/6 API通过
前端: - Patient页面: 注册/查询(全局ID/身份证)/统计卡片 - Merge页面: 合并操作+合并日志列表+撤销 - Statistics页面: EMPI统计概览 数据库修复: - 创建empi_person表(global_id/patient_name/gender/birth_date/id_card_no等) - 创建empi_id_mapping表 - 修复empi_patient_photo: 添加create_time列 - 修复empi_family_member/merge_log: 添加delete_flag/create_by/update_by列 - empi_person: 添加merge_status列 后端修复: - EmpiPerson实体: name→patient_name列映射修复 测试: 5/6 API通过(注册/查询/照片/家庭/合并日志)
This commit is contained in:
@@ -1,14 +1,37 @@
|
||||
package com.healthlink.his.empi.domain;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import java.util.Date;
|
||||
@Data @TableName("empi_person") @Accessors(chain = true) @EqualsAndHashCode(callSuper = false)
|
||||
|
||||
@Data
|
||||
@TableName("empi_person")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class EmpiPerson extends HisBaseEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID) private Long id;
|
||||
private String globalId; private String idCardNo; private String name;
|
||||
private String gender; private Date birthDate; private String phone;
|
||||
private String mergeStatus; private String sourceSystem;
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
@TableField("global_id")
|
||||
private String globalId;
|
||||
@TableField("id_card_no")
|
||||
private String idCardNo;
|
||||
@TableField("patient_name")
|
||||
private String name;
|
||||
@TableField("gender")
|
||||
private String gender;
|
||||
@TableField("birth_date")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date birthDate;
|
||||
@TableField("phone")
|
||||
private String phone;
|
||||
@TableField("address")
|
||||
private String address;
|
||||
@TableField("merge_status")
|
||||
private String mergeStatus;
|
||||
@TableField("source_system")
|
||||
private String sourceSystem;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import request from '@/utils/request'
|
||||
export function getPhotos(p){return request({url:'/empi-enhanced/photo/list',method:'get',params:p})}
|
||||
export function addPhoto(d){return request({url:'/empi-enhanced/photo/add',method:'post',data:d})}
|
||||
export function getFamilyMembers(p){return request({url:'/empi-enhanced/family/list',method:'get',params:p})}
|
||||
export function addFamilyMember(d){return request({url:'/empi-enhanced/family/add',method:'post',data:d})}
|
||||
export function deleteFamilyMember(id){return request({url:'/empi-enhanced/family/delete',method:'delete',params:{id}})}
|
||||
export function getMergeLogPage(p){return request({url:'/empi-enhanced/merge-log/page',method:'get',params:p})}
|
||||
export function addMergeLog(d){return request({url:'/empi-enhanced/merge-log/add',method:'post',data:d})}
|
||||
|
||||
export function registerPerson(data) { return request({ url: '/healthlink-his/api/v1/empi/person', method: 'post', data }) }
|
||||
export function mergePersons(primaryId, secondaryIds) { return request({ url: '/healthlink-his/api/v1/empi/merge', method: 'post', params: { primaryId, secondaryIds: secondaryIds.join(',') } }) }
|
||||
export function findByGlobalId(globalId) { return request({ url: '/healthlink-his/api/v1/empi/person/global/' + globalId, method: 'get' }) }
|
||||
export function findByIdCard(idCardNo) { return request({ url: '/healthlink-his/api/v1/empi/person/idcard/' + idCardNo, method: 'get' }) }
|
||||
export function getMappings(globalId) { return request({ url: '/healthlink-his/api/v1/empi/mappings/' + globalId, method: 'get' }) }
|
||||
export function getStatistics() { return request({ url: '/healthlink-his/api/v1/empi/statistics', method: 'get' }) }
|
||||
|
||||
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 } }) }
|
||||
export function addFamilyMember(data) { return request({ url: '/empi-enhanced/family/add', method: 'post', data }) }
|
||||
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 }) }
|
||||
|
||||
54
healthlink-his-ui/src/views/empienhanced/merge/index.vue
Normal file
54
healthlink-his-ui/src/views/empienhanced/merge/index.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card>
|
||||
<template #header><span>患者合并管理</span></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>
|
||||
<el-button type="primary" @click="handleMerge">合并</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-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>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { mergePersons, getMergeLogPage, undoMergeLog } from '../api'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const loading = ref(false); const mergeLogs = ref([])
|
||||
const primaryId = ref(''); const secondaryIds = ref('')
|
||||
|
||||
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 handleUndo = async (row) => {
|
||||
await ElMessageBox.confirm('确认撤销合并?', '提示', { type: 'warning' })
|
||||
await undoMergeLog({ id: row.id, operatorName: '管理员' }); ElMessage.success('已撤销'); loadLogs()
|
||||
}
|
||||
onMounted(() => loadLogs())
|
||||
</script>
|
||||
77
healthlink-his-ui/src/views/empienhanced/patient/index.vue
Normal file
77
healthlink-his-ui/src/views/empienhanced/patient/index.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<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.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>
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||
<span>患者主索引管理</span>
|
||||
<el-button type="primary" icon="Plus" @click="handleRegister">注册患者</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="searchForm" :inline="true" class="mb8">
|
||||
<el-form-item label="全局ID"><el-input v-model="searchForm.globalId" placeholder="全局ID" clearable /></el-form-item>
|
||||
<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-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.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>
|
||||
<el-empty v-else description="请输入查询条件" />
|
||||
</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-select v-model="formData.gender"><el-option label="男" value="M" /><el-option label="女" value="F" /></el-select>
|
||||
</el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="出生日期"><el-date-picker v-model="formData.birthDate" type="date" value-format="YYYY-MM-DD" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="身份证号"><el-input v-model="formData.idCardNo" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="手机号"><el-input v-model="formData.phone" /></el-form-item></el-col>
|
||||
<el-col :span="24"><el-form-item label="地址"><el-input v-model="formData.address" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { registerPerson, findByGlobalId, findByIdCard, getStatistics } from '../api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const stats = ref({})
|
||||
const searchForm = reactive({ globalId: '', idCardNo: '' })
|
||||
const patientData = ref(null)
|
||||
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('请输入查询条件') }
|
||||
}
|
||||
const handleRegister = () => { formData.value = {}; dialogVisible.value = true }
|
||||
const submitForm = async () => { await registerPerson(formData.value); ElMessage.success('注册成功'); dialogVisible.value = false; loadStats() }
|
||||
onMounted(() => loadStats())
|
||||
</script>
|
||||
@@ -0,0 +1,35 @@
|
||||
<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.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>
|
||||
<el-card>
|
||||
<template #header><span>EMPI统计概览</span></template>
|
||||
<el-table :data="statsDetails">
|
||||
<el-table-column label="指标" prop="metric" />
|
||||
<el-table-column label="值" prop="value" />
|
||||
<el-table-column label="说明" prop="description" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getStatistics } from '../api'
|
||||
|
||||
const stats = ref({}); const statsDetails = ref([])
|
||||
const loadStats = async () => {
|
||||
const res = await getStatistics(); stats.value = res.data || {}
|
||||
statsDetails.value = [
|
||||
{ metric: '总患者数', value: stats.value.totalPatients || 0, description: 'EMPI中注册的患者总数' },
|
||||
{ metric: '已合并数', value: stats.value.mergedPatients || 0, description: '已执行合并的患者数' },
|
||||
{ metric: '待合并数', value: stats.value.pendingMerges || 0, description: '疑似重复待合并的患者数' },
|
||||
{ metric: '重复率', value: (stats.value.duplicateRate || 0) + '%', description: '疑似重复患者占比' },
|
||||
]
|
||||
}
|
||||
onMounted(() => loadStats())
|
||||
</script>
|
||||
Reference in New Issue
Block a user