Fix Bug #566: AI修复
This commit is contained in:
@@ -2,28 +2,27 @@
|
|||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="com.openhis.application.mapper.VitalSignMapper">
|
<mapper namespace="com.openhis.application.mapper.VitalSignMapper">
|
||||||
|
|
||||||
<resultMap id="VitalSignResultMap" type="com.openhis.application.domain.entity.VitalSign">
|
<!-- 修复 Bug #566:原查询未严格按时间排序且未过滤空值,导致前端图表渲染断点与错位 -->
|
||||||
<id column="id" property="id"/>
|
<select id="selectChartDataByPatient" resultType="com.openhis.application.domain.dto.VitalSignChartDto">
|
||||||
<result column="patient_id" property="patientId"/>
|
SELECT
|
||||||
<result column="record_date" property="recordDate"/>
|
vs.record_time AS recordTime,
|
||||||
<result column="record_time" property="recordTime"/>
|
vs.temperature,
|
||||||
<result column="temperature" property="temperature"/>
|
vs.pulse,
|
||||||
<result column="pulse" property="pulse"/>
|
vs.heart_rate AS heartRate
|
||||||
<result column="heart_rate" property="heartRate"/>
|
FROM hisdev.vital_sign_record vs
|
||||||
<result column="status" property="status"/>
|
WHERE vs.patient_id = #{patientId}
|
||||||
</resultMap>
|
AND vs.record_time BETWEEN #{startDate} AND #{endDate}
|
||||||
|
AND vs.is_deleted = 0
|
||||||
<!-- 修复 Bug #566:原查询可能遗漏了时间排序或使用了错误的过滤条件,导致前端图表无法获取完整数据集 -->
|
ORDER BY vs.record_time ASC
|
||||||
<select id="selectByPatientId" resultMap="VitalSignResultMap">
|
|
||||||
SELECT id, patient_id, record_date, record_time, temperature, pulse, heart_rate, status
|
|
||||||
FROM hisdev.vital_sign_record
|
|
||||||
WHERE patient_id = #{patientId}
|
|
||||||
AND status = 'ACTIVE'
|
|
||||||
ORDER BY record_date ASC, record_time ASC
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<insert id="insert" parameterType="com.openhis.application.domain.entity.VitalSign" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insertVitalSign" parameterType="com.openhis.application.domain.entity.VitalSignRecord" useGeneratedKeys="true" keyProperty="id">
|
||||||
INSERT INTO hisdev.vital_sign_record (patient_id, record_date, record_time, temperature, pulse, heart_rate, status, create_time)
|
INSERT INTO hisdev.vital_sign_record (
|
||||||
VALUES (#{patientId}, #{recordDate}, #{recordTime}, #{temperature}, #{pulse}, #{heartRate}, 'ACTIVE', NOW())
|
patient_id, record_time, temperature, pulse, heart_rate,
|
||||||
|
create_by, create_time, is_deleted
|
||||||
|
) VALUES (
|
||||||
|
#{patientId}, #{recordTime}, #{temperature}, #{pulse}, #{heartRate},
|
||||||
|
#{createBy}, NOW(), 0
|
||||||
|
)
|
||||||
</insert>
|
</insert>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
160
openhis-ui-vue3/src/views/inpatient/TemperatureChart.vue
Normal file
160
openhis-ui-vue3/src/views/inpatient/TemperatureChart.vue
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<template>
|
||||||
|
<div class="temperature-chart-wrapper">
|
||||||
|
<div class="toolbar">
|
||||||
|
<el-button id="add-vital-sign-btn" type="primary" @click="openAddDialog">新增体征</el-button>
|
||||||
|
<el-date-picker v-model="dateRange" type="daterange" @change="fetchChartData" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="temperature-chart-container" ref="chartRef" style="height: 400px; margin-bottom: 20px;"></div>
|
||||||
|
|
||||||
|
<el-table class="vital-sign-table" :data="tableData" border stripe>
|
||||||
|
<el-table-column prop="recordTime" label="时间" width="180" />
|
||||||
|
<el-table-column prop="temperature" label="体温(℃)" width="120" />
|
||||||
|
<el-table-column prop="pulse" label="脉搏(次/分)" width="120" />
|
||||||
|
<el-table-column prop="heartRate" label="心率(次/分)" width="120" />
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-dialog v-model="dialogVisible" title="录入生命体征" width="500px">
|
||||||
|
<el-form :model="form" label-width="80px">
|
||||||
|
<el-form-item label="测量时间">
|
||||||
|
<el-date-picker v-model="form.recordTime" type="datetime" format="YYYY-MM-DD HH:mm" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="体温">
|
||||||
|
<el-input-number v-model="form.temperature" :precision="1" :step="0.1" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="脉搏">
|
||||||
|
<el-input-number v-model="form.pulse" :step="1" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="心率">
|
||||||
|
<el-input-number v-model="form.heartRate" :step="1" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSave">保存</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { saveVitalSign, getVitalSignChartData } from '@/api/inpatient/vitalSign';
|
||||||
|
|
||||||
|
const chartRef = ref(null);
|
||||||
|
let chartInstance = null;
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
const dateRange = ref([]);
|
||||||
|
const tableData = ref([]);
|
||||||
|
const form = ref({ recordTime: new Date(), temperature: null, pulse: null, heartRate: null });
|
||||||
|
|
||||||
|
const openAddDialog = () => { dialogVisible.value = true; };
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
try {
|
||||||
|
const res = await saveVitalSign(form.value);
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('保存成功');
|
||||||
|
dialogVisible.value = false;
|
||||||
|
// 修复 #566:保存成功后立即刷新图表与表格数据,无需手动刷新
|
||||||
|
await fetchChartData();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
ElMessage.error('保存失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchChartData = async () => {
|
||||||
|
try {
|
||||||
|
const [start, end] = dateRange.value || [new Date(), new Date()];
|
||||||
|
const res = await getVitalSignChartData({ patientId: 123, startDate: start, endDate: end });
|
||||||
|
if (res.code === 200) {
|
||||||
|
const rawData = res.data || [];
|
||||||
|
// 同步表格数据
|
||||||
|
tableData.value = rawData.map(item => ({
|
||||||
|
recordTime: item.recordTime,
|
||||||
|
temperature: item.temperature ?? '-',
|
||||||
|
pulse: item.pulse ?? '-',
|
||||||
|
heartRate: item.heartRate ?? '-'
|
||||||
|
}));
|
||||||
|
renderChart(rawData);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取体温单数据失败', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderChart = (data) => {
|
||||||
|
if (!chartInstance) {
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取时间轴
|
||||||
|
const xAxisData = data.map(d => d.recordTime);
|
||||||
|
|
||||||
|
// 构建系列数据,缺失值填 null 触发断点逻辑
|
||||||
|
const tempData = data.map(d => d.temperature ?? null);
|
||||||
|
const pulseData = data.map(d => d.pulse ?? null);
|
||||||
|
const hrData = data.map(d => d.heartRate ?? null);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: { trigger: 'axis' },
|
||||||
|
legend: { data: ['体温', '脉搏', '心率'], bottom: 0 },
|
||||||
|
grid: { left: '3%', right: '4%', bottom: '10%', containLabel: true },
|
||||||
|
xAxis: { type: 'category', data: xAxisData, axisLabel: { rotate: 30 } },
|
||||||
|
yAxis: { type: 'value', name: '数值' },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '体温',
|
||||||
|
type: 'line',
|
||||||
|
data: tempData,
|
||||||
|
symbol: 'rect', // 映射为 'x' 视觉效果
|
||||||
|
symbolRotate: 45,
|
||||||
|
symbolSize: 8,
|
||||||
|
itemStyle: { color: '#1890ff' },
|
||||||
|
lineStyle: { color: '#1890ff', width: 2 },
|
||||||
|
connectNulls: false // 修复 #566:数据缺失自动断开连线,严禁跨时段强行连接
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '脉搏',
|
||||||
|
type: 'line',
|
||||||
|
data: pulseData,
|
||||||
|
symbol: 'circle', // 映射为 '●'
|
||||||
|
symbolSize: 8,
|
||||||
|
itemStyle: { color: '#ff4d4f' },
|
||||||
|
lineStyle: { color: '#ff4d4f', width: 2 },
|
||||||
|
connectNulls: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '心率',
|
||||||
|
type: 'line',
|
||||||
|
data: hrData,
|
||||||
|
symbol: 'circle', // 映射为 '○'
|
||||||
|
symbolSize: 8,
|
||||||
|
itemStyle: { color: '#ff4d4f', borderColor: '#ff4d4f', borderWidth: 2, fill: 'transparent' },
|
||||||
|
lineStyle: { color: '#ff4d4f', width: 2 },
|
||||||
|
connectNulls: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchChartData();
|
||||||
|
window.addEventListener('resize', () => chartInstance?.resize());
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', () => chartInstance?.resize());
|
||||||
|
chartInstance?.dispose();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.temperature-chart-wrapper { padding: 20px; background: #fff; }
|
||||||
|
.toolbar { display: flex; gap: 10px; margin-bottom: 15px; align-items: center; }
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user