feat(regional): 区域医疗信息共享
This commit is contained in:
193
healthlink-his-ui/src/views/emr/data-warehouse/index.vue
Normal file
193
healthlink-his-ui/src/views/emr/data-warehouse/index.vue
Normal 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>
|
||||
13
healthlink-his-ui/src/views/esbmanage/regionalshare/api.js
Normal file
13
healthlink-his-ui/src/views/esbmanage/regionalshare/api.js
Normal 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' })
|
||||
}
|
||||
240
healthlink-his-ui/src/views/esbmanage/regionalshare/index.vue
Normal file
240
healthlink-his-ui/src/views/esbmanage/regionalshare/index.vue
Normal 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>
|
||||
Reference in New Issue
Block a user