feat: 评估趋势分析 + 影像历史对比页面增强
- 评估趋势分析页面增强(统计卡片/趋势图表/风险分布/评估记录明细) - 影像历史对比页面增强(统计卡片/新增/详情/筛选)
This commit is contained in:
@@ -1,27 +1,179 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">评估趋势</span></div>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">护理评估趋势分析</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="warning" @click="exportReport">导出报告</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查询表单 -->
|
||||
<el-card shadow="never" style="margin-bottom:16px">
|
||||
<el-form inline>
|
||||
<el-form-item label="就诊ID"><el-input v-model="encounterId" style="width:120px"/></el-form-item>
|
||||
<el-form-item><el-button type="primary" @click="loadData">查询</el-button></el-form-item>
|
||||
<el-form-item label="就诊号"><el-input v-model="encounterId" placeholder="请输入就诊号" style="width:180px"/></el-form-item>
|
||||
<el-form-item label="评估类型">
|
||||
<el-select v-model="assessmentType" clearable placeholder="请选择" style="width:140px">
|
||||
<el-option label="压疮评估(Braden)" value="BRADEN"/>
|
||||
<el-option label="跌倒评估(Morse)" value="MORSE"/>
|
||||
<el-option label="营养筛查(NRS2002)" value="NRS2002"/>
|
||||
<el-option label="疼痛评估" value="PAIN"/>
|
||||
<el-option label="管道风险" value="TUBE"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期范围">
|
||||
<el-date-picker v-model="dateRange" type="daterange" start-placeholder="开始" end-placeholder="结束" style="width:240px"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-table :data="trendData" border stripe>
|
||||
<el-table-column prop="assessmentType" label="评估类型" width="110"/>
|
||||
<el-table-column prop="score" label="评分" width="80" align="center"/>
|
||||
<el-table-column prop="riskLevel" label="风险等级" width="90">
|
||||
<template #default="{row}"><el-tag :type="{HIGH:'danger',MEDIUM:'warning',LOW:'success'}[row.riskLevel]" size="small">{{ row.riskLevel }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="assessTime" label="评估时间" width="170"/>
|
||||
<el-table-column prop="assessorName" label="评估人" width="90"/>
|
||||
</el-table>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="4" v-for="item in statCards" :key="item.label">
|
||||
<el-card shadow="hover" :body-style="{padding:'10px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
|
||||
<div style="font-size:12px;color:#999">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 趋势图表区域 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never">
|
||||
<template #header>评估评分趋势</template>
|
||||
<div style="height:300px;display:flex;align-items:center;justify-content:center;flex-direction:column">
|
||||
<div v-if="trendData.length > 0" style="width:100%;padding:20px">
|
||||
<div v-for="(item, index) in trendData.slice(0, 10)" :key="index" style="display:flex;align-items:center;margin-bottom:8px">
|
||||
<span style="width:80px;font-size:12px;color:#666">{{ item.assessmentType }}</span>
|
||||
<div style="flex:1;background:#f0f0f0;border-radius:4px;height:20px">
|
||||
<div :style="{width: Math.min(item.score * 10, 100) + '%', background: item.riskLevel === 'HIGH' ? '#F56C6C' : item.riskLevel === 'MEDIUM' ? '#E6A23C' : '#67C23A', height: '100%', borderRadius: '4px'}"></div>
|
||||
</div>
|
||||
<span style="width:40px;text-align:right;font-size:12px;font-weight:bold">{{ item.score }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="暂无数据"/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never">
|
||||
<template #header>风险等级分布</template>
|
||||
<div style="height:300px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px">
|
||||
<div v-for="(item, index) in riskDistribution" :key="index" style="display:flex;align-items:center;gap:8px">
|
||||
<el-tag :type="item.type" size="small" style="width:60px;text-align:center">{{ item.level }}</el-tag>
|
||||
<div style="width:200px;background:#f0f0f0;border-radius:4px;height:24px">
|
||||
<div :style="{width: item.percent + '%', background: item.color, height: '100%', borderRadius: '4px', transition: 'width 0.5s'}"></div>
|
||||
</div>
|
||||
<span style="font-size:13px;font-weight:bold">{{ item.count }}人 ({{ item.percent }}%)</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 评估记录表 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>评估记录明细</template>
|
||||
<el-table :data="trendData" border stripe v-loading="loading">
|
||||
<el-table-column type="index" label="序号" width="60" align="center"/>
|
||||
<el-table-column prop="assessmentType" label="评估类型" width="120" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag size="small">{{ assessmentTypeText(row.assessmentType) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="patientName" label="患者" width="100"/>
|
||||
<el-table-column prop="score" label="评分" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<span :style="{color: row.riskLevel === 'HIGH' ? '#F56C6C' : row.riskLevel === 'MEDIUM' ? '#E6A23C' : '#67C23A', fontWeight:'bold', fontSize:'16px'}">
|
||||
{{ row.score }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="riskLevel" label="风险等级" width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="{HIGH:'danger',MEDIUM:'warning',LOW:'success',NORMAL:'info'}[row.riskLevel]" size="small">
|
||||
{{ riskLevelText(row.riskLevel) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="assessTime" label="评估时间" width="170"/>
|
||||
<el-table-column prop="assessorName" label="评估人" width="100"/>
|
||||
<el-table-column prop="detail" label="评估备注" min-width="150" show-overflow-tooltip/>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue'
|
||||
import {ref, computed, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {getTrend} from './api'
|
||||
const encounterId=ref('')
|
||||
const trendData=ref([])
|
||||
const loadData=async()=>{if(!encounterId.value)return;const r=await getTrend({encounterId:encounterId.value});trendData.value=r.data||[]}
|
||||
|
||||
const encounterId = ref('')
|
||||
const assessmentType = ref('')
|
||||
const dateRange = ref(null)
|
||||
const loading = ref(false)
|
||||
const trendData = ref([])
|
||||
|
||||
const statCards = ref([
|
||||
{label:'总评估数', value:0, color:'#409eff'},
|
||||
{label:'高危患者', value:0, color:'#f56c6c'},
|
||||
{label:'中危患者', value:0, color:'#e6a23c'},
|
||||
{label:'低危患者', value:0, color:'#67c23a'},
|
||||
{label:'平均分', value:0, color:'#909399'},
|
||||
{label:'评估类型', value:0, color:'#409eff'}
|
||||
])
|
||||
|
||||
const riskDistribution = computed(() => {
|
||||
let high = 0, medium = 0, low = 0, normal = 0
|
||||
trendData.value.forEach(row => {
|
||||
if (row.riskLevel === 'HIGH') high++
|
||||
else if (row.riskLevel === 'MEDIUM') medium++
|
||||
else if (row.riskLevel === 'LOW') low++
|
||||
else normal++
|
||||
})
|
||||
const total = trendData.value.length || 1
|
||||
return [
|
||||
{level:'高危', count:high, percent:Math.round(high*100/total), type:'danger', color:'#F56C6C'},
|
||||
{level:'中危', count:medium, percent:Math.round(medium*100/total), type:'warning', color:'#E6A23C'},
|
||||
{level:'低危', count:low, percent:Math.round(low*100/total), type:'success', color:'#67C23A'},
|
||||
{level:'正常', count:normal, percent:Math.round(normal*100/total), type:'info', color:'#909399'}
|
||||
]
|
||||
})
|
||||
|
||||
function assessmentTypeText(t) {
|
||||
return {BRADEN:'压疮评估',MORSE:'跌倒评估',NRS2002:'营养筛查',PAIN:'疼痛评估',TUBE:'管道风险'}[t] || t
|
||||
}
|
||||
function riskLevelText(l) {
|
||||
return {HIGH:'高危',MEDIUM:'中危',LOW:'低危',NORMAL:'正常'}[l] || l
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
if (!encounterId.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await getTrend({encounterId:encounterId.value, assessmentType:assessmentType.value})
|
||||
trendData.value = r.data || []
|
||||
// Stats
|
||||
let high = 0, medium = 0, low = 0, totalScore = 0, typeSet = new Set()
|
||||
trendData.value.forEach(row => {
|
||||
if (row.riskLevel === 'HIGH') high++
|
||||
else if (row.riskLevel === 'MEDIUM') medium++
|
||||
else low++
|
||||
totalScore += (row.score || 0)
|
||||
typeSet.add(row.assessmentType)
|
||||
})
|
||||
statCards.value[0].value = trendData.value.length
|
||||
statCards.value[1].value = high
|
||||
statCards.value[2].value = medium
|
||||
statCards.value[3].value = low
|
||||
statCards.value[4].value = trendData.value.length > 0 ? (totalScore / trendData.value.length).toFixed(1) : 0
|
||||
statCards.value[5].value = typeSet.size
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function exportReport() { ElMessage.info('导出功能开发中') }
|
||||
</script>
|
||||
|
||||
@@ -1,27 +1,174 @@
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">影像历史对比</span></div>
|
||||
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
|
||||
<span style="font-size:18px;font-weight:bold">影像历史对比</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button type="success" @click="showAdd = true">新增检查</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查询表单 -->
|
||||
<el-card shadow="never" style="margin-bottom:16px">
|
||||
<el-form inline>
|
||||
<el-form-item label="患者ID"><el-input v-model="patientId" style="width:120px"/></el-form-item>
|
||||
<el-form-item><el-button type="primary" @click="loadData">查询</el-button></el-form-item>
|
||||
<el-form-item label="患者ID"><el-input v-model="patientId" placeholder="请输入患者ID" style="width:180px"/></el-form-item>
|
||||
<el-form-item label="检查类型">
|
||||
<el-select v-model="examType" clearable placeholder="请选择" style="width:140px">
|
||||
<el-option label="X光" value="XRAY"/>
|
||||
<el-option label="CT" value="CT"/>
|
||||
<el-option label="MRI" value="MRI"/>
|
||||
<el-option label="超声" value="ULTRASOUND"/>
|
||||
<el-option label="内镜" value="ENDOSCOPY"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期范围">
|
||||
<el-date-picker v-model="dateRange" type="daterange" start-placeholder="开始" end-placeholder="结束" style="width:240px"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-table :data="imageData" border stripe>
|
||||
<el-table-column prop="examinationType" label="检查类型" width="100"/>
|
||||
<el-table-column prop="examinationName" label="检查名称" width="150"/>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="4" v-for="item in statCards" :key="item.label">
|
||||
<el-card shadow="hover" :body-style="{padding:'10px'}">
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
|
||||
<div style="font-size:12px;color:#999">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table :data="imageData" border stripe v-loading="loading">
|
||||
<el-table-column type="index" label="序号" width="60" align="center"/>
|
||||
<el-table-column prop="examinationType" label="检查类型" width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag size="small" :type="examTagType(row.examinationType)">
|
||||
{{ examTypeText(row.examinationType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="examinationName" label="检查名称" width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="bodyPart" label="部位" width="100"/>
|
||||
<el-table-column prop="findingText" label="所见" min-width="200" show-overflow-tooltip/>
|
||||
<el-table-column prop="conclusionText" label="结论" min-width="200" show-overflow-tooltip/>
|
||||
<el-table-column prop="examinationDate" label="检查日期" width="120"/>
|
||||
<el-table-column prop="doctorName" label="医生" width="90"/>
|
||||
<el-table-column prop="doctorName" label="医生" width="100"/>
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button link type="primary" @click="handleDetail(row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 新增弹窗 -->
|
||||
<el-dialog title="新增影像检查记录" v-model="showAdd" width="650px" append-to-body>
|
||||
<el-form :model="formData" label-width="100px">
|
||||
<el-form-item label="检查类型" required>
|
||||
<el-select v-model="formData.examinationType" placeholder="请选择">
|
||||
<el-option label="X光" value="XRAY"/>
|
||||
<el-option label="CT" value="CT"/>
|
||||
<el-option label="MRI" value="MRI"/>
|
||||
<el-option label="超声" value="ULTRASOUND"/>
|
||||
<el-option label="内镜" value="ENDOSCOPY"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="检查名称" required><el-input v-model="formData.examinationName" placeholder="如: 胸部CT平扫"/></el-form-item>
|
||||
<el-form-item label="检查部位"><el-input v-model="formData.bodyPart" placeholder="如: 胸部、腹部"/></el-form-item>
|
||||
<el-form-item label="所见"><el-input v-model="formData.findingText" type="textarea" :rows="3" placeholder="请输入影像所见"/></el-form-item>
|
||||
<el-form-item label="结论"><el-input v-model="formData.conclusionText" type="textarea" :rows="2" placeholder="请输入影像结论"/></el-form-item>
|
||||
<el-form-item label="检查医生"><el-input v-model="formData.doctorName" placeholder="请输入医生姓名"/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAdd = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<el-dialog title="影像检查详情" v-model="detailVisible" width="700px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="检查类型">{{ examTypeText(detailData.examinationType) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="检查名称">{{ detailData.examinationName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="部位">{{ detailData.bodyPart }}</el-descriptions-item>
|
||||
<el-descriptions-item label="检查日期">{{ detailData.examinationDate }}</el-descriptions-item>
|
||||
<el-descriptions-item label="医生">{{ detailData.doctorName }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div style="margin-top:16px">
|
||||
<h4>所见</h4>
|
||||
<div style="padding:12px;background:#f5f7fa;border-radius:4px;min-height:60px;white-space:pre-wrap;">{{ detailData.findingText || '暂无' }}</div>
|
||||
<h4 style="margin-top:12px">结论</h4>
|
||||
<div style="padding:12px;background:#f5f7fa;border-radius:4px;min-height:60px;white-space:pre-wrap;">{{ detailData.conclusionText || '暂无' }}</div>
|
||||
</div>
|
||||
<template #footer><el-button @click="detailVisible = false">关闭</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue'
|
||||
import {compareImages} from './api'
|
||||
const patientId=ref('')
|
||||
const imageData=ref([])
|
||||
const loadData=async()=>{if(!patientId.value)return;const r=await compareImages({patientId:patientId.value});imageData.value=r.data||[]}
|
||||
import {ref, onMounted} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {compareImages, addRecord} from './api'
|
||||
|
||||
const loading = ref(false)
|
||||
const imageData = ref([])
|
||||
const patientId = ref('')
|
||||
const examType = ref('')
|
||||
const dateRange = ref(null)
|
||||
const showAdd = ref(false)
|
||||
const detailVisible = ref(false)
|
||||
const detailData = ref({})
|
||||
|
||||
const statCards = ref([
|
||||
{label:'总检查数', value:0, color:'#409eff'},
|
||||
{label:'X光', value:0, color:'#67c23a'},
|
||||
{label:'CT', value:0, color:'#e6a23c'},
|
||||
{label:'MRI', value:0, color:'#f56c6c'},
|
||||
{label:'超声', value:0, color:'#909399'},
|
||||
{label:'异常率', value:'0%', color:'#409eff'}
|
||||
])
|
||||
|
||||
const formData = ref({
|
||||
examinationType:'CT', examinationName:'', bodyPart:'', findingText:'', conclusionText:'', doctorName:''
|
||||
})
|
||||
|
||||
function examTypeText(t) {
|
||||
return {XRAY:'X光',CT:'CT',MRI:'MRI',ULTRASOUND:'超声',ENDOSCOPY:'内镜'}[t] || t
|
||||
}
|
||||
function examTagType(t) {
|
||||
return {XRAY:'info',CT:'warning',MRI:'danger',ULTRASOUND:'success',ENDOSCOPY:''}[t] || 'info'
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
if (!patientId.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await compareImages({patientId:patientId.value, examType:examType.value})
|
||||
imageData.value = r.data || []
|
||||
// Stats
|
||||
let counts = {XRAY:0, CT:0, MRI:0, ULTRASOUND:0, ENDOSCOPY:0}
|
||||
imageData.value.forEach(row => { if (counts[row.examinationType] !== undefined) counts[row.examinationType]++ })
|
||||
statCards.value[0].value = imageData.value.length
|
||||
statCards.value[1].value = counts.XRAY
|
||||
statCards.value[2].value = counts.CT
|
||||
statCards.value[3].value = counts.MRI
|
||||
statCards.value[4].value = counts.ULTRASOUND
|
||||
statCards.value[5].value = '0%'
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function handleDetail(row) {
|
||||
detailData.value = row
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
await addRecord(formData.value)
|
||||
ElMessage.success('新增成功')
|
||||
showAdd.value = false
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user