feat(esb): T8.2 CDA临床文档 - AppService/Controller/Frontend
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
package com.healthlink.his.web.esbmanage.appservice;
|
||||
|
||||
import com.healthlink.his.esb.domain.CdaDocument;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ICdaDocumentAppService {
|
||||
CdaDocument generateCda(Long encounterId, Long patientId, String documentType, String documentTitle, String clinicalData);
|
||||
List<CdaDocument> getCdaDocuments(Long encounterId);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.healthlink.his.web.esbmanage.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.esb.domain.CdaDocument;
|
||||
import com.healthlink.his.esb.service.ICdaDocumentService;
|
||||
import com.healthlink.his.web.esbmanage.appservice.ICdaDocumentAppService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class CdaDocumentAppServiceImpl implements ICdaDocumentAppService {
|
||||
|
||||
private final ICdaDocumentService cdaDocumentService;
|
||||
|
||||
@Override
|
||||
public CdaDocument generateCda(Long encounterId, Long patientId, String documentType, String documentTitle, String clinicalData) {
|
||||
String docId = UUID.randomUUID().toString();
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
||||
|
||||
String cdaXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
||||
"<ClinicalDocument xmlns=\"urn:hl7-org:v3\">\n" +
|
||||
" <typeId root=\"2.16.840.1.113883.1.3\" extension=\"POCD_HD000040\"/>\n" +
|
||||
" <id root=\"" + docId + "\"/>\n" +
|
||||
" <code code=\"" + getDocumentTypeCode(documentType) + "\" codeSystem=\"2.16.840.1.113883.5.4\"/>\n" +
|
||||
" <title>" + escapeXml(documentTitle) + "</title>\n" +
|
||||
" <effectiveTime value=\"" + sdf.format(new Date()).replace("-", "").replace(":", "") + "\"/>\n" +
|
||||
" <recordTarget>\n" +
|
||||
" <patientRole>\n" +
|
||||
" <id extension=\"" + patientId + "\" root=\"2.16.156.10011\"/>\n" +
|
||||
" </patientRole>\n" +
|
||||
" </recordTarget>\n" +
|
||||
" <component>\n" +
|
||||
" <structuredBody>\n" +
|
||||
" <component>\n" +
|
||||
" <section>\n" +
|
||||
" <code code=\"48767-8\" codeSystem=\"2.16.840.1.113883.6.1\"/>\n" +
|
||||
" <text>" + escapeXml(clinicalData) + "</text>\n" +
|
||||
" </section>\n" +
|
||||
" </component>\n" +
|
||||
" </structuredBody>\n" +
|
||||
" </component>\n" +
|
||||
"</ClinicalDocument>";
|
||||
|
||||
CdaDocument doc = new CdaDocument();
|
||||
doc.setDocumentType(documentType);
|
||||
doc.setDocumentTitle(documentTitle);
|
||||
doc.setEncounterId(encounterId);
|
||||
doc.setPatientId(patientId);
|
||||
doc.setCdaXml(cdaXml);
|
||||
doc.setStatus("DRAFT");
|
||||
doc.setVersionId(1);
|
||||
doc.setCreateTime(new Date());
|
||||
cdaDocumentService.save(doc);
|
||||
|
||||
log.info("CDA文档已生成: type={}, title={}, id={}", documentType, documentTitle, doc.getId());
|
||||
return doc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CdaDocument> getCdaDocuments(Long encounterId) {
|
||||
LambdaQueryWrapper<CdaDocument> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(CdaDocument::getEncounterId, encounterId)
|
||||
.orderByDesc(CdaDocument::getCreateTime);
|
||||
return cdaDocumentService.list(wrapper);
|
||||
}
|
||||
|
||||
private String getDocumentTypeCode(String documentType) {
|
||||
switch (documentType) {
|
||||
case "admission": return "34133-9";
|
||||
case "discharge": return "18842-5";
|
||||
case "lab_report": return "11502-2";
|
||||
case "referral": return "57133-2";
|
||||
default: return "34133-9";
|
||||
}
|
||||
}
|
||||
|
||||
private String escapeXml(String text) {
|
||||
if (text == null) return "";
|
||||
return text.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
.replace("\"", """).replace("'", "'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.healthlink.his.web.esbmanage.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.esb.domain.CdaDocument;
|
||||
import com.healthlink.his.web.esbmanage.appservice.ICdaDocumentAppService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* CDA临床文档 Controller — 生成/查询CDA文档
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/esb/cda")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class CdaDocumentController {
|
||||
|
||||
private final ICdaDocumentAppService cdaDocumentAppService;
|
||||
|
||||
@PostMapping("/generate")
|
||||
@PreAuthorize("hasAuthority('infection:esb:edit')")
|
||||
public R<?> generateCda(@RequestBody Map<String, Object> params) {
|
||||
Long encounterId = params.get("encounterId") != null ? Long.valueOf(String.valueOf(params.get("encounterId"))) : null;
|
||||
Long patientId = params.get("patientId") != null ? Long.valueOf(String.valueOf(params.get("patientId"))) : null;
|
||||
String documentType = (String) params.get("documentType");
|
||||
String documentTitle = (String) params.get("documentTitle");
|
||||
String clinicalData = (String) params.get("clinicalData");
|
||||
|
||||
if (encounterId == null || documentType == null) {
|
||||
return R.fail("encounterId和documentType不能为空");
|
||||
}
|
||||
CdaDocument doc = cdaDocumentAppService.generateCda(encounterId, patientId, documentType, documentTitle, clinicalData);
|
||||
return R.ok(doc);
|
||||
}
|
||||
|
||||
@GetMapping("/list/{encounterId}")
|
||||
@PreAuthorize("hasAuthority('infection:esb:list')")
|
||||
public R<?> getCdaDocuments(@PathVariable Long encounterId) {
|
||||
List<CdaDocument> docs = cdaDocumentAppService.getCdaDocuments(encounterId);
|
||||
return R.ok(docs);
|
||||
}
|
||||
}
|
||||
6
healthlink-his-ui/src/views/esbmanage/cdadocument/api.js
Normal file
6
healthlink-his-ui/src/views/esbmanage/cdadocument/api.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import request from '@/utils/request'
|
||||
export function generateCda(data) { return request({ url: '/esb/cda/generate', method: 'post', data }) }
|
||||
export function getCdaDocuments(encounterId) { return request({ url: '/esb/cda/list/' + encounterId, method: 'get' }) }
|
||||
export function getCdaPage(params) { return request({ url: '/fhir-cda/cda/page', method: 'get', params }) }
|
||||
export function createCdaDocument(data) { return request({ url: '/fhir-cda/cda/create', method: 'post', data }) }
|
||||
export function publishCdaDocument(id) { return request({ url: '/fhir-cda/cda/publish', method: 'post', params: { id } }) }
|
||||
121
healthlink-his-ui/src/views/esbmanage/cdadocument/index.vue
Normal file
121
healthlink-his-ui/src/views/esbmanage/cdadocument/index.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">CDA临床文档管理</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="showGenerate = true">生成CDA文档</el-button>
|
||||
<el-button @click="loadPage">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="docList" border stripe>
|
||||
<el-table-column prop="documentType" label="文档类型" width="120">
|
||||
<template #default="{row}">
|
||||
<el-tag size="small">{{ typeMap[row.documentType] || row.documentType }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="documentTitle" label="文档标题" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="encounterId" label="就诊ID" width="100" />
|
||||
<el-table-column prop="patientId" label="患者ID" width="100" />
|
||||
<el-table-column prop="status" label="状态" width="90">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.status==='PUBLISHED'?'success':row.status==='DRAFT'?'warning':'info'" size="small">
|
||||
{{ row.status === 'DRAFT' ? '草稿' : row.status === 'PUBLISHED' ? '已发布' : row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="versionId" label="版本" width="60" align="center" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="160">
|
||||
<template #default="{row}">
|
||||
<el-button type="primary" link size="small" @click="viewCda(row)">查看XML</el-button>
|
||||
<el-button v-if="row.status==='DRAFT'" type="success" link size="small" @click="doPublish(row.id)">发布</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-model:current-page="page.pageNo"
|
||||
v-model:page-size="page.pageSize"
|
||||
style="margin-top:12px;justify-content:flex-end"
|
||||
:total="pageTotal"
|
||||
layout="total,prev,pager,next"
|
||||
@current-change="loadPage"
|
||||
/>
|
||||
|
||||
<el-dialog v-model="showGenerate" title="生成CDA文档" width="600px">
|
||||
<el-form :model="genForm" label-width="100px">
|
||||
<el-form-item label="就诊ID">
|
||||
<el-input v-model="genForm.encounterId" />
|
||||
</el-form-item>
|
||||
<el-form-item label="患者ID">
|
||||
<el-input v-model="genForm.patientId" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文档类型">
|
||||
<el-select v-model="genForm.documentType" style="width:100%">
|
||||
<el-option label="入院记录" value="admission" />
|
||||
<el-option label="出院记录" value="discharge" />
|
||||
<el-option label="检验报告" value="lab_report" />
|
||||
<el-option label="转诊记录" value="referral" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="文档标题">
|
||||
<el-input v-model="genForm.documentTitle" />
|
||||
</el-form-item>
|
||||
<el-form-item label="临床数据">
|
||||
<el-input v-model="genForm.clinicalData" type="textarea" :rows="6" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showGenerate = false">取消</el-button>
|
||||
<el-button type="primary" @click="doGenerate">生成</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="showXml" title="CDA文档XML" width="800px">
|
||||
<el-input v-model="xmlContent" type="textarea" :rows="20" readonly />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { generateCda, getCdaPage, publishCdaDocument } from './api'
|
||||
|
||||
const typeMap = { admission: '入院记录', discharge: '出院记录', lab_report: '检验报告', referral: '转诊记录' }
|
||||
const docList = ref([])
|
||||
const pageTotal = ref(0)
|
||||
const page = ref({ pageNo: 1, pageSize: 20 })
|
||||
const showGenerate = ref(false)
|
||||
const genForm = ref({ encounterId: '', patientId: '', documentType: 'admission', documentTitle: '', clinicalData: '' })
|
||||
const showXml = ref(false)
|
||||
const xmlContent = ref('')
|
||||
|
||||
const loadPage = async () => {
|
||||
const r = await getCdaPage(page.value)
|
||||
docList.value = r.data?.records || []
|
||||
pageTotal.value = r.data?.total || 0
|
||||
}
|
||||
|
||||
const doGenerate = async () => {
|
||||
if (!genForm.value.encounterId || !genForm.value.documentType) {
|
||||
ElMessage.warning('就诊ID和文档类型必填'); return
|
||||
}
|
||||
const r = await generateCda(genForm.value)
|
||||
if (r.code === 200) {
|
||||
ElMessage.success('CDA文档已生成')
|
||||
showGenerate.value = false
|
||||
loadPage()
|
||||
} else {
|
||||
ElMessage.error(r.msg || '生成失败')
|
||||
}
|
||||
}
|
||||
|
||||
const doPublish = async (id) => {
|
||||
const r = await publishCdaDocument(id)
|
||||
if (r.code === 200) { ElMessage.success('已发布'); loadPage() }
|
||||
else ElMessage.error(r.msg || '发布失败')
|
||||
}
|
||||
|
||||
const viewCda = (row) => { xmlContent.value = row.cdaXml || ''; showXml.value = true }
|
||||
|
||||
onMounted(() => { loadPage() })
|
||||
</script>
|
||||
Reference in New Issue
Block a user