feat(regional): 区域医疗信息共享

This commit is contained in:
2026-06-18 17:29:50 +08:00
parent cba192401e
commit f0e189ca8e
3 changed files with 446 additions and 0 deletions

View File

@@ -0,0 +1,193 @@
<template>
<div class="app-container">
<el-card shadow="hover" class="mb8">
<template #header>
<span>结构化数据提取</span>
</template>
<el-form :inline="true" :model="extractForm">
<el-form-item label="病历ID">
<el-input v-model="extractForm.emrId" placeholder="请输入病历ID" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" :loading="extractLoading" @click="handleExtract">
提取数据
</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="hover" class="mb8">
<template #header>
<span>结构化数据查看</span>
</template>
<el-form :inline="true" :model="queryForm">
<el-form-item label="就诊ID">
<el-input v-model="queryForm.encounterId" placeholder="请输入就诊ID" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" :loading="queryLoading" @click="handleQuery">
查询
</el-button>
</el-form-item>
</el-form>
<el-table v-loading="queryLoading" :data="structuredDataList" border stripe>
<el-table-column label="数据类型" prop="dataType" width="120">
<template #default="scope">
<el-tag>{{ dataTypeMap[scope.row.dataType] || scope.row.dataType }}</el-tag>
</template>
</el-table-column>
<el-table-column label="数据键" prop="dataKey" width="180" />
<el-table-column label="数据值" prop="dataValue" min-width="150" />
<el-table-column label="单位" prop="dataUnit" width="100" />
<el-table-column label="记录时间" prop="recordTime" width="180" />
</el-table>
</el-card>
<el-card shadow="hover" class="mb8">
<template #header>
<span>质控评分</span>
</template>
<el-form :inline="true" :model="qualityForm">
<el-form-item label="就诊ID">
<el-input v-model="qualityForm.encounterId" placeholder="请输入就诊ID" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Check" :loading="qualityLoading" @click="handleCalculate">
计算评分
</el-button>
<el-button type="success" icon="Search" :loading="scoreLoading" @click="handleLoadScores">
查询历史
</el-button>
</el-form-item>
</el-form>
<el-row v-if="currentScore" :gutter="20" class="mb8">
<el-col :span="6">
<el-card shadow="hover">
<el-statistic title="总分" :value="currentScore.totalScore" />
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<el-statistic title="完整性" :value="currentScore.completenessScore" />
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<el-statistic title="及时性" :value="currentScore.timelinessScore" />
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<el-statistic title="准确性" :value="currentScore.accuracyScore" />
</el-card>
</el-col>
</el-row>
<el-table v-if="scoreList.length > 0" v-loading="scoreLoading" :data="scoreList" border stripe>
<el-table-column label="评分时间" prop="checkTime" width="180" />
<el-table-column label="总分" prop="totalScore" width="100" />
<el-table-column label="完整性" prop="completenessScore" width="100" />
<el-table-column label="及时性" prop="timelinessScore" width="100" />
<el-table-column label="准确性" prop="accuracyScore" width="100" />
<el-table-column label="病历类型" prop="emrType" width="120" />
</el-table>
</el-card>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { extractStructuredData, getStructuredData, calculateQualityScore, getQualityScores } from '@/api/emr'
import { ElMessage } from 'element-plus'
const extractLoading = ref(false)
const queryLoading = ref(false)
const qualityLoading = ref(false)
const scoreLoading = ref(false)
const extractForm = reactive({ emrId: '' })
const queryForm = reactive({ encounterId: '' })
const qualityForm = reactive({ encounterId: '' })
const structuredDataList = ref([])
const currentScore = ref(null)
const scoreList = ref([])
const dataTypeMap = {
vital_signs: '生命体征',
lab_results: '检验结果',
diagnosis: '诊断',
medication: '用药'
}
const handleExtract = async () => {
if (!extractForm.emrId) {
ElMessage.warning('请输入病历ID')
return
}
extractLoading.value = true
try {
const res = await extractStructuredData(extractForm.emrId)
const data = res.data || res
ElMessage.success('提取完成,共 ' + (Array.isArray(data) ? data.length : 0) + ' 条数据')
} catch (e) {
ElMessage.error('提取失败')
} finally {
extractLoading.value = false
}
}
const handleQuery = async () => {
if (!queryForm.encounterId) {
ElMessage.warning('请输入就诊ID')
return
}
queryLoading.value = true
try {
const res = await getStructuredData(queryForm.encounterId)
structuredDataList.value = res.data || res || []
} catch (e) {
ElMessage.error('查询失败')
} finally {
queryLoading.value = false
}
}
const handleCalculate = async () => {
if (!qualityForm.encounterId) {
ElMessage.warning('请输入就诊ID')
return
}
qualityLoading.value = true
try {
const res = await calculateQualityScore(qualityForm.encounterId)
currentScore.value = res.data || res
ElMessage.success('评分计算完成')
} catch (e) {
ElMessage.error('计算失败')
} finally {
qualityLoading.value = false
}
}
const handleLoadScores = async () => {
if (!qualityForm.encounterId) {
ElMessage.warning('请输入就诊ID')
return
}
scoreLoading.value = true
try {
const res = await getQualityScores(qualityForm.encounterId)
scoreList.value = res.data || res || []
} catch (e) {
ElMessage.error('查询失败')
} finally {
scoreLoading.value = false
}
}
</script>
<style scoped>
.mb8 { margin-bottom: 8px; }
</style>

View File

@@ -0,0 +1,13 @@
import request from '@/utils/request'
export function sharePatientData(params) {
return request({ url: '/regional/share', method: 'post', params })
}
export function getShareRecords(encounterId) {
return request({ url: '/regional/share/records/' + encounterId, method: 'get' })
}
export function getShareStats() {
return request({ url: '/regional/share/stats', method: 'get' })
}

View File

@@ -0,0 +1,240 @@
<template>
<div class="app-container">
<el-row
:gutter="16"
class="stat-row"
>
<el-col :span="6">
<div class="stat-card blue">
<div class="stat-icon">
<el-icon :size="28">
<Share />
</el-icon>
</div><div class="stat-info">
<div class="stat-value">
{{ stats.total || 0 }}
</div><div class="stat-label">
总共享数
</div>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="stat-card orange">
<div class="stat-icon">
<el-icon :size="28">
<Clock />
</el-icon>
</div><div class="stat-info">
<div class="stat-value">
{{ stats.pending || 0 }}
</div><div class="stat-label">
待处理
</div>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="stat-card green">
<div class="stat-icon">
<el-icon :size="28">
<CircleCheck />
</el-icon>
</div><div class="stat-info">
<div class="stat-value">
{{ stats.success || 0 }}
</div><div class="stat-label">
成功
</div>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="stat-card red">
<div class="stat-icon">
<el-icon :size="28">
<Warning />
</el-icon>
</div><div class="stat-info">
<div class="stat-value">
{{ stats.failed || 0 }}
</div><div class="stat-label">
失败
</div>
</div>
</div>
</el-col>
</el-row>
<el-card shadow="never">
<template #header>
<span class="card-title">发起共享</span>
</template>
<el-form
:model="shareForm"
inline
>
<el-form-item label="就诊ID">
<el-input
v-model="shareForm.encounterId"
placeholder="请输入就诊ID"
style="width: 200px"
/>
</el-form-item>
<el-form-item label="目标系统">
<el-input
v-model="shareForm.targetSystem"
placeholder="如: 区域卫生平台"
style="width: 200px"
/>
</el-form-item>
<el-form-item>
<el-button
:loading="shareLoading"
type="primary"
@click="handleShare"
>
共享
</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card
shadow="never"
style="margin-top: 16px"
>
<template #header>
<span class="card-title">共享记录</span>
</template>
<vxe-table
:data="records"
border
height="400"
>
<vxe-column
field="id"
title="ID"
width="80"
/>
<vxe-column
field="encounterId"
title="就诊ID"
width="100"
/>
<vxe-column
field="targetSystem"
title="目标系统"
width="150"
/>
<vxe-column
field="shareType"
title="共享类型"
width="120"
/>
<vxe-column
field="shareStatus"
title="状态"
width="100"
align="center"
>
<template #default="{ row }">
<el-tag
:type="row.shareStatus === 'SUCCESS' ? 'success' : row.shareStatus === 'FAILED' ? 'danger' : 'warning'"
size="small"
>
{{ row.shareStatus }}
</el-tag>
</template>
</vxe-column>
<vxe-column
field="retryCount"
title="重试次数"
width="90"
align="center"
/>
<vxe-column
field="errorMessage"
title="错误信息"
min-width="150"
show-overflow
/>
<vxe-column
field="createTime"
title="创建时间"
width="160"
/>
</vxe-table>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { sharePatientData, getShareRecords, getShareStats } from './api'
const stats = ref({})
const records = ref([])
const shareLoading = ref(false)
const shareForm = ref({ encounterId: '', targetSystem: '' })
const loadStats = () => {
getShareStats().then(res => { stats.value = res.data || {} })
}
const loadRecords = () => {
if (shareForm.value.encounterId) {
getShareRecords(shareForm.value.encounterId).then(res => { records.value = res.data || [] })
}
}
const handleShare = () => {
if (!shareForm.value.encounterId) {
ElMessage.warning('请输入就诊ID')
return
}
if (!shareForm.value.targetSystem) {
ElMessage.warning('请输入目标系统')
return
}
shareLoading.value = true
sharePatientData({
encounterId: shareForm.value.encounterId,
targetSystem: shareForm.value.targetSystem
}).then(res => {
if (res.code === 200) {
ElMessage.success('共享请求已提交')
loadStats()
loadRecords()
} else {
ElMessage.error(res.msg || '共享失败')
}
}).catch(() => {
ElMessage.error('共享请求异常')
}).finally(() => {
shareLoading.value = false
})
}
onMounted(() => {
loadStats()
})
</script>
<style lang="scss" scoped>
.stat-row { margin-bottom: 16px; }
.stat-card { background: #fff; border-radius: 8px; padding: 20px; border: 1px solid #ebeef5; display: flex; align-items: center; gap: 16px; }
.stat-card.blue { border-left: 4px solid #409eff; }
.stat-card.orange { border-left: 4px solid #e6a23c; }
.stat-card.green { border-left: 4px solid #67c23a; }
.stat-card.red { border-left: 4px solid #f56c6c; }
.stat-icon { width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: #f0f2f5; }
.stat-card.blue .stat-icon { color: #409eff; }
.stat-card.orange .stat-icon { color: #e6a23c; }
.stat-card.green .stat-icon { color: #67c23a; }
.stat-card.red .stat-icon { color: #f56c6c; }
.stat-info .stat-value { font-size: 24px; font-weight: bold; color: #303133; }
.stat-info .stat-label { font-size: 13px; color: #909399; margin-top: 4px; }
.card-title { font-weight: bold; font-size: 15px; }
</style>