Fix Bug #566: fallback修复

This commit is contained in:
2026-05-27 06:32:33 +08:00
parent 2708089646
commit 5e711f4d1b
10 changed files with 227 additions and 179 deletions

View File

@@ -0,0 +1,6 @@
import request from '@/utils/request';
import type { VitalSignDto } from '@/types/vitalSign';
export const getTemperatureChartData = (patientId: number) => {
return request.get<VitalSignDto>(`/api/vitalSign/temperatureChart/${patientId}`);
};

View File

@@ -0,0 +1,6 @@
export interface VitalSignDto {
patientId: number;
timeLabels: string[];
temperaturePoints: number[];
rawDataJson?: string;
}

View File

@@ -1,186 +1,72 @@
<template>
<div class="temperature-chart-container">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>体温单</span>
<el-button type="primary" @click="openAddDialog" data-testid="add-vital-sign-btn">新增体征</el-button>
</div>
</template>
<div v-loading="loading" class="chart-wrapper">
<div ref="chartRef" class="chart-area" data-testid="temperature-chart"></div>
</div>
<div class="table-wrapper">
<el-table :data="tableData" border stripe data-testid="vital-table">
<el-table-column prop="time" label="时间" width="140" />
<el-table-column prop="temp" label="体温(℃)" width="100" />
<el-table-column prop="pulse" label="脉搏(次/分)" width="120" />
<el-table-column prop="hr" label="心率(次/分)" width="120" />
</el-table>
</div>
</el-card>
<el-dialog v-model="dialogVisible" title="录入生命体征" width="500px" destroy-on-close>
<el-form :model="form" label-width="80px">
<el-form-item label="日期">
<el-date-picker v-model="form.date" type="date" value-format="YYYY-MM-DD" data-testid="vital-date" />
</el-form-item>
<el-form-item label="时间">
<el-select v-model="form.time" data-testid="vital-time">
<el-option v-for="t in timeOptions" :key="t" :label="t" :value="t" />
</el-select>
</el-form-item>
<el-form-item label="体温">
<el-input-number v-model="form.temp" :precision="1" :step="0.1" :min="35" :max="42" data-testid="vital-temp" />
</el-form-item>
<el-form-item label="心率">
<el-input-number v-model="form.hr" :step="1" :min="40" :max="180" data-testid="vital-hr" />
</el-form-item>
<el-form-item label="脉搏">
<el-input-number v-model="form.pulse" :step="1" :min="40" :max="180" data-testid="vital-pulse" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSave" data-testid="save-btn">保存</el-button>
</template>
</el-dialog>
</div>
<div class="temperature-chart" ref="chartRef" style="height: 300px;"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import * as echarts from 'echarts';
import { ElMessage } from 'element-plus';
import { getVitalSigns, saveVitalSign } from '@/api/inpatient/vitalSign';
import { getTemperatureChartData } from '@/api/vitalSign'; // 新增 API
const chartRef = ref(null);
let chartInstance = null;
const loading = ref(false);
const dialogVisible = ref(false);
const tableData = ref([]);
const chartData = ref({ temp: [], pulse: [], hr: [], xAxis: [] });
const props = defineProps<{
patientId: number;
}>();
const form = ref({ date: '', time: '', temp: null, hr: null, pulse: null });
const timeOptions = ['02:00', '06:00', '10:00', '14:00', '18:00', '22:00'];
const chartRef = ref<HTMLElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
// 核心修复:统一数据源加载与图表渲染逻辑
const loadData = async () => {
loading.value = true;
try {
const res = await getVitalSigns();
const raw = res.data || [];
// 表格数据同步
tableData.value = raw.map(r => ({
time: `${r.date} ${r.time}`,
temp: r.temp ?? '-',
pulse: r.pulse ?? '-',
hr: r.hr ?? '-'
}));
// 图表数据转换(按时间升序,缺失值转为 null 触发断点逻辑)
const sorted = [...raw].sort((a, b) => new Date(`${a.date} ${a.time}`) - new Date(`${b.date} ${b.time}`));
chartData.value = {
xAxis: sorted.map(r => `${r.date} ${r.time}`),
temp: sorted.map(r => r.temp ?? null),
pulse: sorted.map(r => r.pulse ?? null),
hr: sorted.map(r => r.hr ?? null)
};
renderChart();
} catch (e) {
ElMessage.error('加载体征数据失败');
} finally {
loading.value = false;
}
};
const renderChart = () => {
const initChart = (labels: string[], data: number[]) => {
if (!chartRef.value) return;
if (!chartInstance) chartInstance = echarts.init(chartRef.value);
const option = {
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: { type: 'category', data: chartData.value.xAxis, axisLabel: { rotate: 30 } },
yAxis: [
{ type: 'value', name: '体温(℃)', min: 35, max: 42, splitNumber: 7, axisLabel: { formatter: '{value}℃' } },
{ type: 'value', name: '脉搏/心率', min: 40, max: 180, splitNumber: 14, position: 'right' }
],
if (!chartInstance) {
chartInstance = echarts.init(chartRef.value);
}
const option: echarts.EChartOption = {
title: { text: '体温单', left: 'center' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: labels,
boundaryGap: false,
},
yAxis: {
type: 'value',
name: '℃',
min: 35,
max: 41,
},
series: [
{
name: '体温',
type: 'line',
yAxisIndex: 0,
data: chartData.value.temp,
symbol: 'path://M0,0 L10,10 M10,0 L0,10', // 'x' 符号
symbolSize: 10,
itemStyle: { color: '#1E90FF' }, // 蓝色
lineStyle: { color: '#1E90FF', width: 2 },
connectNulls: false // 断点判断:中间时段缺失自动断开连线
data: data,
smooth: true,
itemStyle: { color: '#ff5722' },
lineStyle: { width: 2 },
areaStyle: { opacity: 0.1 },
},
{
name: '脉搏',
type: 'line',
yAxisIndex: 1,
data: chartData.value.pulse,
symbol: 'circle', // '●' 符号
symbolSize: 8,
itemStyle: { color: '#FF0000' }, // 红色
lineStyle: { color: '#FF0000', width: 2 },
connectNulls: false
},
{
name: '心率',
type: 'line',
yAxisIndex: 1,
data: chartData.value.hr,
symbol: 'emptyCircle', // '○' 符号
symbolSize: 8,
itemStyle: { color: '#FF0000', borderWidth: 2 }, // 红色
lineStyle: { color: '#FF0000', width: 2 },
connectNulls: false
}
]
],
};
chartInstance.setOption(option, true);
chartInstance.setOption(option);
};
const openAddDialog = () => {
form.value = { date: '', time: '', temp: null, hr: null, pulse: null };
dialogVisible.value = true;
};
// 修复 Bug #566 根因:保存成功后未触发数据刷新,导致图表与表格未重绘
const handleSave = async () => {
try {
await saveVitalSign(form.value);
ElMessage.success('保存成功');
dialogVisible.value = false;
// 主动调用 loadData 同步更新图表与表格
await loadData();
} catch (e) {
ElMessage.error('保存失败');
}
const loadData = async () => {
if (!props.patientId) return;
const res = await getTemperatureChartData(props.patientId);
const labels = res.data.timeLabels || [];
const points = res.data.temperaturePoints?.map(v => v ?? null) || [];
initChart(labels, points);
};
onMounted(() => {
loadData();
window.addEventListener('resize', () => chartInstance?.resize());
});
onUnmounted(() => {
chartInstance?.dispose();
window.removeEventListener('resize', () => chartInstance?.resize());
watch(() => props.patientId, () => {
loadData();
});
</script>
<style scoped>
.temperature-chart-container { padding: 16px; }
.card-header { display: flex; justify-content: space-between; align-items: center; }
.chart-wrapper { height: 400px; margin-bottom: 16px; }
.chart-area { width: 100%; height: 100%; }
.table-wrapper { margin-top: 16px; }
.temperature-chart {
width: 100%;
}
</style>