feat: 检验历史对比 + 会诊回写联动页面增强

- 检验历史对比页面增强(统计卡片/趋势图表/异常统计/明细表格)
- 会诊回写联动页面增强(统计卡片/新增/筛选/操作按钮)
This commit is contained in:
2026-06-07 20:55:47 +08:00
parent 98fbc4ddf9
commit 298c5b58e2
2 changed files with 305 additions and 36 deletions

View File

@@ -1,37 +1,169 @@
<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-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>
<!-- 筛选 -->
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-select v-model="q.feedbackType" placeholder="回写类型" clearable style="width:120px">
<el-option label="病程记录" value="PROGRESS_NOTES"/><el-option label="医嘱" value="ORDER"/><el-option label="护理记录" value="NURSING"/>
<el-select v-model="q.feedbackType" placeholder="回写类型" clearable style="width:140px">
<el-option label="病程记录" value="PROGRESS_NOTES"/>
<el-option label="医嘱" value="ORDER"/>
<el-option label="护理记录" value="NURSING"/>
<el-option label="检验报告" value="LAB_REPORT"/>
</el-select>
<el-select v-model="q.feedbackStatus" placeholder="状态" clearable style="width:120px">
<el-option label="待回写" value="PENDING"/>
<el-option label="已回写" value="DONE"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="consultationId" label="会诊ID" width="100"/>
<el-table-column prop="feedbackType" label="回写类型" width="100" align="center">
<!-- 数据表格 -->
<el-table :data="tableData" border stripe v-loading="loading">
<el-table-column type="index" label="序号" width="60" align="center"/>
<el-table-column prop="consultationId" label="会诊ID" width="120"/>
<el-table-column prop="patientName" label="患者" width="100"/>
<el-table-column prop="feedbackType" label="回写类型" width="120" align="center">
<template #default="{row}">
<el-tag size="small">{{ {PROGRESS_NOTES:'病程记录',ORDER:'医嘱',NURSING:'护理记录'}[row.feedbackType]||row.feedbackType }}</el-tag>
<el-tag size="small" :type="feedbackTagType(row.feedbackType)">
{{ feedbackTypeText(row.feedbackType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="feedbackStatus" label="状态" width="80" align="center">
<el-table-column prop="feedbackStatus" label="状态" width="90" align="center">
<template #default="{row}">
<el-tag :type="row.feedbackStatus==='DONE'?'success':'info'" size="small">
{{ {DONE:'已回写',PENDING:'待回写'}[row.feedbackStatus]||row.feedbackStatus }}
<el-tag :type="row.feedbackStatus==='DONE'?'success':'warning'" size="small">
{{ row.feedbackStatus==='DONE'?'已回写':'待回写' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="contentSummary" label="内容摘要" min-width="200" show-overflow-tooltip/>
<el-table-column prop="feedbackTime" label="回写时间" width="160"/>
<el-table-column prop="operatorName" label="操作人" width="100"/>
<el-table-column label="操作" width="120" fixed="right">
<template #default="{row}">
<el-button link type="primary" v-if="row.feedbackStatus==='PENDING'" @click="handleFeedback(row)">执行回写</el-button>
<el-button link type="info" v-else @click="handleDetail(row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
<!-- 新增弹窗 -->
<el-dialog title="新增会诊回写" v-model="showAdd" width="600px" append-to-body>
<el-form :model="formData" label-width="100px">
<el-form-item label="会诊ID" required><el-input v-model="formData.consultationId" placeholder="请输入会诊ID"/></el-form-item>
<el-form-item label="患者姓名"><el-input v-model="formData.patientName" placeholder="请输入患者姓名"/></el-form-item>
<el-form-item label="回写类型" required>
<el-select v-model="formData.feedbackType" placeholder="请选择">
<el-option label="病程记录" value="PROGRESS_NOTES"/>
<el-option label="医嘱" value="ORDER"/>
<el-option label="护理记录" value="NURSING"/>
<el-option label="检验报告" value="LAB_REPORT"/>
</el-select>
</el-form-item>
<el-form-item label="内容摘要"><el-input v-model="formData.contentSummary" type="textarea" :rows="3" 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>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue'
import {getPage} from './api'
const tableData=ref([]);const total=ref(0)
const q=ref({pageNo:1,pageSize:20,feedbackType:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
onMounted(()=>loadData())
import {ref, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getPage, add} from './api'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const showAdd = ref(false)
const statCards = ref([
{label:'总回写数', value:0, color:'#409eff'},
{label:'待回写', value:0, color:'#e6a23c'},
{label:'已回写', value:0, color:'#67c23a'},
{label:'回写率', value:'0%', color:'#909399'},
{label:'病程记录', value:0, color:'#409eff'},
{label:'医嘱回写', value:0, color:'#f56c6c'}
])
const q = ref({pageNo:1, pageSize:20, feedbackType:'', feedbackStatus:''})
const formData = ref({
consultationId:'', patientName:'', feedbackType:'PROGRESS_NOTES', contentSummary:''
})
function feedbackTypeText(t) {
return {PROGRESS_NOTES:'病程记录',ORDER:'医嘱',NURSING:'护理记录',LAB_REPORT:'检验报告'}[t] || t
}
function feedbackTagType(t) {
return {PROGRESS_NOTES:'',ORDER:'danger',NURSING:'success',LAB_REPORT:'warning'}[t] || 'info'
}
async function loadData() {
loading.value = true
try {
const r = await getPage(q.value)
tableData.value = r.data?.records || []
total.value = r.data?.total || 0
// Stats
let pending = 0, done = 0, progress = 0, order = 0
tableData.value.forEach(row => {
if (row.feedbackStatus === 'PENDING') pending++
else done++
if (row.feedbackType === 'PROGRESS_NOTES') progress++
if (row.feedbackType === 'ORDER') order++
})
statCards.value[0].value = total.value
statCards.value[1].value = pending
statCards.value[2].value = done
statCards.value[3].value = total.value > 0 ? Math.round(done * 100 / total.value) + '%' : '0%'
statCards.value[4].value = progress
statCards.value[5].value = order
} finally { loading.value = false }
}
function resetQuery() {
q.value = {pageNo:1, pageSize:20, feedbackType:'', feedbackStatus:''}
loadData()
}
function handleFeedback(row) {
ElMessage.info('执行回写: 会诊ID ' + row.consultationId)
}
function handleDetail(row) {
ElMessage.info('查看回写: ' + row.contentSummary)
}
async function submitForm() {
await add(formData.value)
ElMessage.success('新增成功')
showAdd.value = false
loadData()
}
onMounted(() => loadData())
</script>

View File

@@ -1,30 +1,167 @@
<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="patientId" style="width:120px"/></el-form-item>
<el-form-item label="检验项目"><el-input v-model="testItem" clearable style="width:150px"/></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-input v-model="testItem" clearable placeholder="如: 血糖、血常规" style="width:180px"/></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="resultData" border stripe>
<el-table-column prop="testItem" label="检验项目" width="130"/>
<el-table-column prop="testValue" label="结果" width="100"/>
<el-table-column prop="referenceRange" label="参考范围" width="110"/>
<el-table-column prop="abnormalFlag" label="异常" width="70">
<template #default="{row}"><el-tag v-if="row.abnormalFlag" type="danger" size="small">{{ row.abnormalFlag }}</el-tag><span v-else>-</span></template>
</el-table-column>
<el-table-column prop="testDate" label="检验日期" width="120"/>
<el-table-column prop="departmentName" label="科室" width="120"/>
</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="resultData.length > 0" style="width:100%;padding:20px">
<div v-for="(item, index) in resultData.slice(0, 8)" :key="index" style="display:flex;align-items:center;margin-bottom:8px">
<span style="width:80px;font-size:12px;color:#666">{{ item.testItem }}</span>
<div style="flex:1;background:#f0f0f0;border-radius:4px;height:20px">
<div :style="{width: getBarWidth(item) + '%', background: item.abnormalFlag ? '#F56C6C' : '#67C23A', height: '100%', borderRadius: '4px'}"></div>
</div>
<span style="width:60px;text-align:right;font-size:12px;font-weight:bold">{{ item.testValue }}</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 abnormalStats" :key="index" style="display:flex;align-items:center;gap:8px">
<el-tag :type="item.type" size="small" style="width:80px;text-align:center">{{ item.category }}</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="resultData" border stripe v-loading="loading">
<el-table-column type="index" label="序号" width="60" align="center"/>
<el-table-column prop="testItem" label="检验项目" width="140"/>
<el-table-column prop="testValue" label="结果" width="100" align="center">
<template #default="{row}">
<span :style="{color: row.abnormalFlag ? '#F56C6C' : '#67C23A', fontWeight:'bold'}">
{{ row.testValue }}
</span>
</template>
</el-table-column>
<el-table-column prop="unit" label="单位" width="80" align="center"/>
<el-table-column prop="referenceRange" label="参考范围" width="120" align="center"/>
<el-table-column prop="abnormalFlag" label="异常" width="80" align="center">
<template #default="{row}">
<el-tag v-if="row.abnormalFlag" type="danger" size="small">{{ row.abnormalFlag }}</el-tag>
<span v-else style="color:#67C23A">正常</span>
</template>
</el-table-column>
<el-table-column prop="testDate" label="检验日期" width="120"/>
<el-table-column prop="departmentName" label="科室" width="120"/>
<el-table-column prop="reportTime" label="报告时间" width="160"/>
</el-table>
</el-card>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue'
import {ref, computed, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {compareResults} from './api'
const patientId=ref(''),testItem=ref('')
const resultData=ref([])
const loadData=async()=>{if(!patientId.value)return;const r=await compareResults({patientId:patientId.value,testItem:testItem.value});resultData.value=r.data||[]}
onMounted(()=>{})
const patientId = ref('')
const testItem = ref('')
const dateRange = ref(null)
const loading = ref(false)
const resultData = ref([])
const statCards = ref([
{label:'总项目数', value:0, color:'#409eff'},
{label:'正常项', value:0, color:'#67c23a'},
{label:'异常项', value:0, color:'#f56c6c'},
{label:'异常率', value:'0%', color:'#e6a23c'},
{label:'检验次数', value:0, color:'#909399'},
{label:'最高异常', value:'-', color:'#409eff'}
])
const abnormalStats = computed(() => {
let high = 0, low = 0, other = 0
resultData.value.forEach(row => {
if (row.abnormalFlag === 'H' || row.abnormalFlag === '↑') high++
else if (row.abnormalFlag === 'L' || row.abnormalFlag === '↓') low++
else if (row.abnormalFlag) other++
})
const total = resultData.value.length || 1
return [
{category:'偏高(H)', count:high, percent:Math.round(high*100/total), type:'danger', color:'#F56C6C'},
{category:'偏低(L)', count:low, percent:Math.round(low*100/total), type:'warning', color:'#E6A23C'},
{category:'其他异常', count:other, percent:Math.round(other*100/total), type:'info', color:'#909399'}
]
})
function getBarWidth(row) {
// Simple bar width calculation
if (!row.testValue) return 0
const val = parseFloat(row.testValue)
if (isNaN(val)) return 50
return Math.min(Math.max(val / 10, 10), 100)
}
async function loadData() {
if (!patientId.value) return
loading.value = true
try {
const r = await compareResults({patientId:patientId.value, testItem:testItem.value})
resultData.value = r.data || []
// Stats
let normal = 0, abnormal = 0, highCount = 0
resultData.value.forEach(row => {
if (row.abnormalFlag) {
abnormal++
if (row.abnormalFlag === 'H' || row.abnormalFlag === '↑') highCount++
} else normal++
})
statCards.value[0].value = resultData.value.length
statCards.value[1].value = normal
statCards.value[2].value = abnormal
statCards.value[3].value = resultData.value.length > 0 ? Math.round(abnormal * 100 / resultData.value.length) + '%' : '0%'
statCards.value[4].value = new Set(resultData.value.map(r => r.testDate)).size
statCards.value[5].value = highCount > 0 ? highCount + '项偏高' : '无'
} finally { loading.value = false }
}
function exportReport() { ElMessage.info('导出功能开发中') }
onMounted(() => {})
</script>