调整图样式

This commit is contained in:
Flow 2025-07-17 16:57:39 +08:00
parent b8f8fb0f53
commit bcfed5c9c8
2 changed files with 537 additions and 97 deletions

View File

@ -204,7 +204,7 @@
.scatter-chart-item .chart {
width: 100%;
height: 240px;
height: 320px;
border: 1px solid #000;
}
@ -219,7 +219,7 @@
}
.data-table-container {
max-height: calc(100vh - 200px);
max-height: 1000px;
overflow-y: auto;
margin: 20px 0;
height: auto;
@ -234,8 +234,9 @@
.data-table th,
.data-table td {
border: 1px solid #000;
padding: 5px;
padding: 3px;
text-align: center;
line-height: 1.2;
}
.data-table th {
@ -1639,52 +1640,290 @@
function renderTrendChart() {
const chart = echarts.init(document.getElementById('trend-chart'));
const timeLabels = chartDataTable.map(item =>
new Date(item.originalTime).toLocaleDateString('zh-CN', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
);
// 完全参考analysis.vue的实现使用value类型的X轴显示精确时间位置
const createBloodPressureXAxis = () => {
if (chartDataTable.length === 0) return { xAxisTicks: [], xAxisLabels: [], seriesMap: {} };
// 1. 取所有时间戳
const times = chartDataTable.map(item => new Date(item.originalTime).getTime());
const minTime = Math.min(...times);
const maxTime = Math.max(...times);
// 2. 计算起止整点
const start = new Date(minTime);
start.setMinutes(0, 0, 0);
const end = new Date(maxTime);
if (end.getMinutes() > 0 || end.getSeconds() > 0) {
end.setHours(end.getHours() + 1);
}
end.setMinutes(0, 0, 0);
// 3. 生成刻度(整点+半点)
const xAxisTicks = [];
const xAxisLabels = [];
let cur = new Date(start);
while (cur <= end) {
xAxisTicks.push(cur.getTime());
xAxisLabels.push(formatTimeLabel(cur));
// 半点
const half = new Date(cur);
half.setMinutes(30);
if (half < end) {
xAxisTicks.push(half.getTime());
xAxisLabels.push(''); // 半点不显示label
}
cur.setHours(cur.getHours() + 1);
cur.setMinutes(0);
}
// 4. 生成series数据每个数据点都有精确的时间戳位置
const getSeriesData = (key) => {
return chartDataTable.map(item => ({
value: [new Date(item.originalTime).getTime(), item[key] || null],
itemTime: item.originalTime
}));
};
return {
xAxisTicks,
xAxisLabels,
seriesMap: {
systolic: getSeriesData('systolic'),
diastolic: getSeriesData('diastolic'),
heartRate: getSeriesData('heartRate')
}
};
};
const formatTimeLabel = (date) => {
const day = date.getDate();
const hour = date.getHours();
return `${hour.toString().padStart(2, '0')}`;
};
const { xAxisTicks, xAxisLabels, seriesMap } = createBloodPressureXAxis();
// 计算夜间遮罩区域每一天的22点到次日8点使用时间戳方式
const calculateNightAreas = () => {
const nightAreas = [];
const startTime = new Date(Math.min(...chartDataTable.map(item => new Date(item.originalTime).getTime())));
const endTime = new Date(Math.max(...chartDataTable.map(item => new Date(item.originalTime).getTime())));
// 从第一天开始循环,为每一天生成夜间区域
let currentDate = new Date(startTime);
currentDate.setHours(0, 0, 0, 0); // 设置为当天0点
while (currentDate <= endTime) {
// 当天22点
const nightStart = new Date(currentDate);
nightStart.setHours(22, 0, 0, 0);
// 次日8点
const nightEnd = new Date(currentDate);
nightEnd.setDate(nightEnd.getDate() + 1);
nightEnd.setHours(8, 0, 0, 0);
// 只有当夜间时段与数据时间范围有交集时才添加
if (nightStart <= endTime && nightEnd >= startTime) {
nightAreas.push([
{ xAxis: Math.max(nightStart.getTime(), startTime.getTime()) },
{ xAxis: Math.min(nightEnd.getTime(), endTime.getTime()) }
]);
}
// 移动到下一天
currentDate.setDate(currentDate.getDate() + 1);
}
return nightAreas;
};
const nightAreas = calculateNightAreas();
// 完全参考analysis.vue的图表配置使用value类型X轴
const option = {
tooltip: { trigger: 'axis' },
legend: { data: ['收缩压', '舒张压', '心率'], top: 10 },
xAxis: { type: 'category', data: timeLabels },
grid: {
left: 50,
right: 50,
bottom: 60,
top: 50,
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
backgroundColor: 'rgba(50,50,50,0.7)',
borderWidth: 0,
textStyle: {
color: '#fff'
},
formatter: function(params) {
if (params && params.length > 0) {
let html = '';
params.forEach((param) => {
if (param.data && param.data.itemTime) {
const time = new Date(param.data.itemTime);
const timeStr = time.toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
html += `${param.seriesName}: ${param.data.value[1] || '--'}<br/>`;
if (param.seriesIndex === 0) {
html += `时间: ${timeStr}<br/>`;
}
}
});
return html;
}
return '';
}
},
legend: {
data: ['收缩压', '舒张压', '心率'],
top: 10,
left: 'center'
},
xAxis: {
type: 'value',
min: xAxisTicks[0],
max: xAxisTicks[xAxisTicks.length - 1],
interval: (60 * 60 * 1000) / 2, // 半小时
minInterval: (60 * 60 * 1000) / 2, // 半小时
axisLabel: {
formatter: (value) => {
const date = new Date(value);
// 只在整点显示
if (date.getMinutes() === 0) {
return `${date.getHours().toString().padStart(2, '0')}`;
}
return '';
},
rotate: 0,
color: '#666'
},
axisLine: {
lineStyle: {
color: '#e0e0e0'
}
},
axisTick: {
show: false
},
splitLine: {
show: false,
lineStyle: {
color: '#e0e0e0',
type: 'solid'
}
}
},
yAxis: [
{ type: 'value', name: 'mmHg', position: 'left', min: 0, max: 200 },
{ type: 'value', name: '次/分', position: 'right', min: 50, max: 120 }
{
type: 'value',
name: 'mmHg',
position: 'left',
min: 0,
max: 200,
interval: 20,
axisLabel: {
color: '#666'
},
axisLine: {
lineStyle: {
color: '#e0e0e0'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#f0f0f0',
type: 'dashed'
}
}
},
{
type: 'value',
name: '次/分',
position: 'right',
min: 50,
max: 120,
interval: 10,
axisLabel: {
color: '#666'
},
axisLine: {
lineStyle: {
color: '#e0e0e0'
}
},
splitLine: {
show: false
}
}
],
series: [
{
name: '收缩压',
type: 'line',
symbol: 'none',
symbol: 'circle',
symbolSize: 6,
lineStyle: { width: 2 },
data: chartDataTable.map(item => item.systolic),
itemStyle: { color: '#409eff' }
connectNulls: false,
smooth: false,
data: seriesMap.systolic,
itemStyle: {
color: '#409eff',
borderWidth: 1,
borderColor: '#fff'
},
markArea: {
silent: true,
itemStyle: {
color: 'rgba(128, 128, 128, 0.2)',
borderWidth: 0
},
data: nightAreas,
label: {
show: false
}
}
},
{
name: '舒张压',
type: 'line',
symbol: 'none',
symbol: 'circle',
symbolSize: 6,
lineStyle: { width: 2 },
data: chartDataTable.map(item => item.diastolic),
itemStyle: { color: '#67c23a' }
connectNulls: false,
smooth: false,
data: seriesMap.diastolic,
itemStyle: {
color: '#67c23a',
borderWidth: 1,
borderColor: '#fff'
}
},
{
name: '心率',
type: 'line',
symbol: 'none',
symbol: 'circle',
symbolSize: 6,
lineStyle: { width: 2 },
connectNulls: false,
smooth: false,
yAxisIndex: 1,
data: chartDataTable.map(item => item.heartRate),
itemStyle: { color: '#e6a23c' }
data: seriesMap.heartRate,
itemStyle: {
color: '#e6a23c',
borderWidth: 1,
borderColor: '#fff'
}
}
]
};
chart.setOption(option);
}
@ -1826,31 +2065,59 @@
let normalCount, abnormalCount;
if (field === 'heartRate') {
normalCount = data.filter(item => item[field] >= 60 && item[field] <= 100).length;
normalCount = data.filter(item => item[field] >=60 && item[field] <= 100).length;
abnormalCount = data.length - normalCount;
} else {
normalCount = data.filter(item => item[field] < threshold).length;
abnormalCount = data.length - normalCount;
}
const normalPercent = data.length > 0 ? Math.round((normalCount / data.length) * 100) : 0;
const abnormalPercent = 100 - normalPercent;
// 构建数据数组,只包含有数据的类别
const pieData = [];
// 只有当正常数据存在时才添加
if (normalCount > 0) pieData.push({
value: normalCount,
name: '正常',
itemStyle: { color: '#7fc7ff' }
});
// 只有当异常数据存在时才添加
if (abnormalCount > 0) pieData.push({
value: abnormalCount,
name: '异常',
itemStyle: { color: '#ff7f7f' }
});
// 如果没有任何数据,显示提示
if (pieData.length === 0) pieData.push({
value: 1,
name: '暂无数据',
itemStyle: { color: '#f0f0f0' }
});
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c}次 ({d}%)'
formatter: function(params) {
if (params.name === '暂无数据') {
return '暂无数据';
}
return params.name + ': ' + params.value + '次 (' + params.percent + '%)';
}
},
series: [{
type: 'pie',
radius: '60%',
data: [
{ value: normalCount, itemStyle: { color: '#7fc7ff' } },
{ value: abnormalCount, itemStyle: { color: '#ff7f7f' } }
],
data: pieData,
label: {
show: true,
formatter: '{b}\n{c}次\n{d}%',
formatter: function(params) {
if (params.name === '暂无数据') {
return '暂无数据';
}
return params.name + '\n' + params.value + '次\n' + params.percent + '%';
},
fontSize: 10,
position: 'inside'
},
@ -2063,23 +2330,41 @@
name: '收缩压',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 6, // 增加点的大小
data: hourlyData.map(d => d.systolic),
itemStyle: { color: '#409eff' }
itemStyle: {
color: '#409eff',
borderWidth: 1,
borderColor: '#fff' // 添加白色边框使点更明显
}
},
{
name: '舒张压',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 6, // 增加点的大小
data: hourlyData.map(d => d.diastolic),
itemStyle: { color: '#67c23a' }
itemStyle: {
color: '#67c23a',
borderWidth: 1,
borderColor: '#fff' // 添加白色边框使点更明显
}
},
{
name: '心率',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 6, // 增加点的大小
yAxisIndex: 1,
data: hourlyData.map(d => d.heartRate),
itemStyle: { color: '#e6a23c' }
itemStyle: {
color: '#e6a23c',
borderWidth: 1,
borderColor: '#fff' // 添加白色边框使点更明显
}
}
]
};
@ -2356,4 +2641,4 @@
</script>
</body>
</html>
</html>

View File

@ -127,7 +127,7 @@
<el-tab-pane label="数据统计" name="data">
<div class="data-table-container">
<el-table :data="chartDataTable" border style="width: 100%" height="550" :fit="true">
<el-table-column prop="time" label="测量时间" width="120" align="center" />
<el-table-column prop="time" label="测量时间" width="200" align="center" />
<el-table-column prop="systolic" label="收缩压 (mmHg)" width="140" align="center" />
<el-table-column
prop="diastolic"
@ -139,9 +139,7 @@
<el-table-column prop="avgBP" label="平均血压 (mmHg)" width="180" align="center" />
<el-table-column prop="status" label="血压等级" align="center">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.status)">
{{ scope.row.status }}
</el-tag>
{{ scope.row.status }}
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right">
@ -359,7 +357,72 @@ const updatePatientData = (rowData: any) => {
patientData.orgname = rowData.orgname || '未知'
}
//
// Xseries
function createBloodPressureXAxis(
data: { time: string; systolic: number; diastolic: number; heartRate?: number }[]
) {
// 1.
const times = data.map((item) => new Date(item.time).getTime())
if (times.length === 0) return { xAxisTicks: [], xAxisLabels: [], seriesMap: {} }
// 2.
let minTime = Math.min(...times)
let maxTime = Math.max(...times)
//
const start = new Date(minTime)
start.setMinutes(0, 0, 0)
//
const end = new Date(maxTime)
if (end.getMinutes() > 0 || end.getSeconds() > 0) {
end.setHours(end.getHours() + 1)
}
end.setMinutes(0, 0, 0)
// 3. +
const xAxisTicks: number[] = []
const xAxisLabels: string[] = []
let cur = new Date(start)
while (cur <= end) {
xAxisTicks.push(cur.getTime())
xAxisLabels.push(formatTimeLabel(cur))
//
const half = new Date(cur)
half.setMinutes(30)
if (half < end) {
xAxisTicks.push(half.getTime())
xAxisLabels.push('') // label
}
cur.setHours(cur.getHours() + 1)
cur.setMinutes(0)
}
// 4. series
function getSeriesData(key: 'systolic' | 'diastolic' | 'heartRate') {
return data.map((item) => ({
value: [new Date(item.time).getTime(), item[key] ?? null],
itemTime: item.time
}))
}
return {
xAxisTicks,
xAxisLabels,
seriesMap: {
systolic: getSeriesData('systolic'),
diastolic: getSeriesData('diastolic'),
heartRate: getSeriesData('heartRate')
}
}
}
function formatTimeLabel(date: Date) {
const day = date.getDate()
const hour = date.getHours()
return `${day}\n${hour.toString().padStart(2, '0')}:00`
}
// processData X series
const processData = (rawList: any[], existingAnalysisResult?: string) => {
//
const sortedList = [...rawList].sort((a, b) => {
@ -368,26 +431,98 @@ const processData = (rawList: any[], existingAnalysisResult?: string) => {
return timeA - timeB
})
// 1.
const timeLabels = sortedList.map((item) =>
dayjs(item.measuretime || item.weartime).format('DD日HH:mm')
)
const systolicData = sortedList.map((item) => Number(item.systolicpressure) || 0)
const diastolicData = sortedList.map((item) => Number(item.diastolicpressure) || 0)
const heartRateData = sortedList.map((item) => Number(item.heartrate) || 0)
// 1.
const bloodPressureData = sortedList.map((item) => ({
time: dayjs(item.measuretime || item.weartime).format('YYYY-MM-DD HH:mm:ss'),
systolic: Number(item.systolicpressure) || 0,
diastolic: Number(item.diastolicpressure) || 0,
heartRate: Number(item.heartrate) || 0
}))
const { xAxisTicks, xAxisLabels, seriesMap } = createBloodPressureXAxis(bloodPressureData)
const chartData = {
timeLabels,
systolicData,
diastolicData,
heartRateData
// 2. type: 'value'
chartOption.value = {
...chartOption.value,
xAxis: {
type: 'value',
min: xAxisTicks[0],
max: xAxisTicks[xAxisTicks.length - 1],
interval: (60 * 60 * 1000) / 2, //
minInterval: (60 * 60 * 1000) / 2, //
axisLabel: {
formatter: (value: number) => {
const date = new Date(value)
//
if (date.getMinutes() === 0) {
return `${date.getDate()}\n${date.getHours().toString().padStart(2, '0')}:00`
}
return ''
},
rotate: 0,
color: '#666'
},
axisTick: {
show: false
},
splitLine: {
show: false, // 线
lineStyle: {
color: '#e0e0e0',
type: 'solid'
}
}
},
series: [
{
name: '收缩压统计',
type: 'line',
data: seriesMap.systolic,
smooth: false,
symbol: 'circle',
itemStyle: { color: '#409eff' },
lineStyle: { width: 2 }
},
{
name: '舒张压统计',
type: 'line',
data: seriesMap.diastolic,
smooth: false,
symbol: 'circle',
itemStyle: { color: '#67c23a' },
lineStyle: { width: 2 }
},
{
name: '心率统计',
type: 'line',
data: seriesMap.heartRate,
smooth: false,
symbol: 'circle',
itemStyle: { color: '#e6a23c' },
lineStyle: { width: 2 }
}
],
legend: {
data: ['收缩压统计', '舒张压统计', '心率统计'],
top: 10,
left: 'center'
},
tooltip: {
trigger: 'axis',
formatter: function (params: any) {
let html = ''
params.forEach((param: any) => {
html += `${param.seriesName}: ${param.data.value[1]}<br/>`
})
html += `时间: ${params[0].data.itemTime}`
return html
}
}
}
updateChartData(chartData)
// 2.
// 3.
chartDataTable.value = sortedList.map((item) => ({
id: item.id, // id
time: dayjs(item.measuretime || item.weartime).format('DD日HH:mm'),
time: dayjs(item.measuretime || item.weartime).format('YYYY-MM-DD HH:mm:ss'),
originalTime: item.measuretime || item.weartime, //
systolic: Number(item.systolicpressure) || 0,
diastolic: Number(item.diastolicpressure) || 0,
@ -396,16 +531,35 @@ const processData = (rawList: any[], existingAnalysisResult?: string) => {
status: getBPStatus(Number(item.systolicpressure) || 0, Number(item.diastolicpressure) || 0)
}))
// 3.
// 4.
updatePatientStatistics(sortedList)
// 4. 使
// 5. 使
if (existingAnalysisResult && existingAnalysisResult.trim()) {
analysisResult.value = existingAnalysisResult
} else {
analysisResult.value = generateAnalysisReport(sortedList)
}
// ... processData
const allYValues = [
...sortedList.map((item) => Number(item.systolicpressure) || 0),
...sortedList.map((item) => Number(item.diastolicpressure) || 0),
...sortedList.map((item) => Number(item.heartrate) || 0)
]
const maxY = Math.max(...allYValues)
const yAxisMax = Math.ceil(maxY / 10) * 10 + 10
chartOption.value = {
...chartOption.value,
yAxis: {
...chartOption.value.yAxis,
max: yAxisMax,
min: 0,
interval: 10
}
}
//
nextTick(() => {
setTimeout(() => {
@ -848,30 +1002,21 @@ const chartOption = ref<any>({
left: 'center'
},
xAxis: {
type: 'category',
data: [],
axisTick: {
show: false
},
axisLabel: {
color: '#666',
rotate: 45
},
axisLine: {
lineStyle: {
color: '#e0e0e0'
}
}
},
yAxis: {
type: 'value',
min: 0,
max: 260,
axisTick: {
show: false
},
interval: (60 * 60 * 1000) / 2, //
minInterval: (60 * 60 * 1000) / 2, //
axisLabel: {
color: '#666'
color: '#666',
rotate: 0,
formatter: (value: number) => {
const date = new Date(value)
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
return `${day}\n${hour}:${minute.toString().padStart(2, '0')}`
}
},
axisLine: {
lineStyle: {
@ -879,6 +1024,32 @@ const chartOption = ref<any>({
}
},
splitLine: {
show: false, // 线
lineStyle: {
color: '#e0e0e0',
type: 'solid'
}
}
},
yAxis: {
type: 'value',
min: 0,
max: 160,
interval: 10,
axisTick: {
show: false
},
axisLabel: {
color: '#666',
rotate: 0
},
axisLine: {
lineStyle: {
color: '#e0e0e0'
}
},
splitLine: {
show: true, // 线
lineStyle: {
color: '#f0f0f0',
type: 'dashed'
@ -957,22 +1128,6 @@ const getHRColor = (value: number) => {
return '#f56c6c' // -
}
//
const getStatusType = (status: string) => {
switch (status) {
case '正常血压':
return 'success'
case '轻度高血压':
return 'warning'
case '中度高血压':
return 'danger'
case '重度高血压':
return 'danger'
default:
return 'info'
}
}
//
const refreshAllDataAfterDelete = () => {
if (chartDataTable.value.length === 0) {