feat: 检验历史对比 + 会诊回写联动页面增强
- 检验历史对比页面增强(统计卡片/趋势图表/异常统计/明细表格) - 会诊回写联动页面增强(统计卡片/新增/筛选/操作按钮)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user