Files
ljwx-health/ljwx-bigscreen/bigscreen/bigScreen/templates/personal_bak.html
T
bruno ce2937c9d1 feat: 添加 ljwx-bigscreen/bigscreen/bigScreen 目录所有重要代码文件
- 🐍 添加所有 Python 源码文件(*.py)
- 📄 添加所有 HTML 模板文件(templates/)
- 🗂️ 添加控制器、模型、测试等目录结构
- 📋 添加文档文件(*.md)和配置文件
- ⚙️ 更新 .gitignore 配置,只忽略日志和 SQL 文件
- 🚀 确保所有重要代码都被 Git 跟踪和版本控制

根据要求:ljwx-bigscreen/bigscreen/bigScreen 里面除了 log 和 sql 都需要提交
2025-08-14 21:09:12 +08:00

555 lines
28 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>企业级智能健康监测大屏</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts-gl@2.0.9/dist/echarts-gl.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lottie-web@5.12.2/build/player/lottie.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
* {margin:0;padding:0;box-sizing:border-box;}
body {font-family:'Microsoft YaHei',Arial,sans-serif;background:#000;color:#fff;overflow:hidden;height:100vh;position:relative;}
.dashboard {display:grid;grid-template-columns:320px 1fr 320px;grid-template-rows:100px 1fr 100px;height:100vh;gap:8px;padding:8px;position:relative;z-index:10;}
.bg-canvas {position:fixed;top:0;left:0;width:100%;height:100%;z-index:1;opacity:0.3;}
.header {grid-column:1/4;background:linear-gradient(135deg,#001122 0%,#003366 50%,#001122 100%);border:2px solid #00ccff;display:flex;align-items:center;justify-content:space-between;padding:0 40px;border-radius:12px;position:relative;overflow:hidden;}
.header::before {content:'';position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(0,204,255,0.3),transparent);animation:shine 3s infinite;}
.logo {color:#00ccff;font-size:32px;font-weight:bold;text-shadow:0 0 20px #00ccff;}
.header-stats {display:flex;gap:30px;}
.stat-item {text-align:center;}
.stat-value {font-size:24px;font-weight:bold;color:#00ff88;}
.stat-label {font-size:12px;color:#ccc;}
.left-panel,.right-panel {background:linear-gradient(135deg,#001122 0%,#002244 100%);border:1px solid #00ccff;border-radius:12px;padding:15px;overflow:hidden;position:relative;}
.main-panel {background:linear-gradient(135deg,#000811 0%,#001a33 50%,#000811 100%);border:2px solid #00ccff;border-radius:12px;position:relative;overflow:hidden;}
.footer {grid-column:1/4;background:linear-gradient(135deg,#001122 0%,#003366 50%,#001122 100%);border:2px solid #00ccff;border-radius:12px;display:flex;align-items:center;justify-content:space-around;padding:10px;}
.panel-title {color:#00ccff;font-size:18px;margin-bottom:15px;padding:10px;background:rgba(0,204,255,0.1);border-radius:8px;text-align:center;position:relative;}
.vital-card {background:linear-gradient(135deg,#001a33 0%,#003366 100%);border:1px solid #00ccff;border-radius:12px;padding:20px;margin-bottom:20px;position:relative;overflow:hidden;transition:all 0.3s ease;}
.vital-card:hover {transform:translateY(-5px);box-shadow:0 10px 30px rgba(0,204,255,0.3);}
.vital-card::before {content:'';position:absolute;top:0;left:0;right:0;height:4px;background:linear-gradient(90deg,#00ccff,#00ff88,#ffaa00,#ff4757);animation:gradient 2s linear infinite;}
.vital-title {color:#00ccff;font-size:16px;margin-bottom:15px;display:flex;align-items:center;gap:10px;}
.vital-value {font-size:36px;font-weight:bold;color:#fff;text-shadow:0 0 15px #00ccff;animation:pulse 2s ease-in-out infinite;margin-bottom:10px;}
.vital-unit {font-size:16px;color:#ccc;margin-left:8px;}
.vital-status {font-size:14px;color:#00ff88;margin-bottom:15px;padding:5px 10px;background:rgba(0,255,136,0.2);border-radius:15px;text-align:center;}
.chart-mini {height:80px;position:relative;overflow:hidden;}
.main-display {position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;}
.health-ring {width:280px;height:280px;margin:0 auto 30px;position:relative;}
.ring-svg {width:100%;height:100%;transform:rotate(-90deg);}
.ring-bg {fill:none;stroke:rgba(0,204,255,0.2);stroke-width:8;}
.ring-progress {fill:none;stroke:#00ccff;stroke-width:8;stroke-linecap:round;transition:stroke-dasharray 1s ease;}
.ring-glow {fill:none;stroke:#00ccff;stroke-width:12;opacity:0.5;filter:blur(4px);}
.score-display {position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;}
.score-main {font-size:64px;font-weight:bold;color:#00ccff;text-shadow:0 0 30px #00ccff;margin-bottom:10px;}
.score-label {font-size:18px;color:#ccc;margin-bottom:20px;}
.status-badges {display:flex;justify-content:center;gap:15px;flex-wrap:wrap;}
.status-badge {padding:8px 15px;border-radius:20px;font-size:12px;font-weight:bold;animation:float 3s ease-in-out infinite;}
.badge-excellent {background:linear-gradient(45deg,#00ff88,#00ccff);color:#000;}
.badge-good {background:linear-gradient(45deg,#ffaa00,#ff6b35);color:#000;}
.badge-warning {background:linear-gradient(45deg,#ff4757,#ff3838);color:#fff;}
.data-grid {display:grid;grid-template-columns:1fr 1fr;gap:15px;margin-bottom:20px;}
.data-card {background:linear-gradient(135deg,#001a33,#003366);border:1px solid #00ccff;border-radius:8px;padding:15px;text-align:center;position:relative;overflow:hidden;}
.data-card::before {content:'';position:absolute;top:0;left:0;width:100%;height:2px;background:linear-gradient(90deg,#00ccff,#00ff88);animation:slide 2s linear infinite;}
.data-value {font-size:28px;font-weight:bold;color:#fff;text-shadow:0 0 10px #00ccff;margin-bottom:5px;}
.data-label {font-size:12px;color:#ccc;}
.trend-container {height:200px;margin-bottom:20px;border:1px solid #00ccff;border-radius:8px;overflow:hidden;}
.recommendations {max-height:300px;overflow-y:auto;}
.rec-item {background:linear-gradient(90deg,rgba(0,255,136,0.1),rgba(0,204,255,0.1));border-left:4px solid #00ff88;padding:15px;margin-bottom:10px;border-radius:8px;animation:slideInRight 0.5s ease;}
.rec-title {color:#00ff88;font-size:14px;font-weight:bold;margin-bottom:8px;display:flex;align-items:center;gap:8px;}
.rec-text {color:#ccc;font-size:12px;line-height:1.5;}
.footer-item {text-align:center;color:#00ccff;cursor:pointer;transition:all 0.3s ease;}
.footer-item:hover {color:#00ff88;transform:scale(1.1);}
.footer-value {font-size:20px;font-weight:bold;margin-bottom:5px;}
.footer-label {font-size:12px;}
@keyframes shine {0%{left:-100%;}100%{left:100%;}}
@keyframes pulse {0%,100%{transform:scale(1);opacity:1;}50%{transform:scale(1.05);opacity:0.8;}}
@keyframes gradient {0%{background-position:0% 50%;}50%{background-position:100% 50%;}100%{background-position:0% 50%;}}
@keyframes float {0%,100%{transform:translateY(0);}50%{transform:translateY(-10px);}}
@keyframes slide {0%{transform:translateX(-100%);}100%{transform:translateX(100%);}}
@keyframes slideInRight {from{transform:translateX(20px);opacity:0;}to{transform:translateX(0);opacity:1;}}
.glow-effect {position:absolute;width:100%;height:100%;background:radial-gradient(circle,rgba(0,204,255,0.1) 0%,transparent 70%);animation:breathe 4s ease-in-out infinite;}
@keyframes breathe {0%,100%{opacity:0.3;transform:scale(1);}50%{opacity:0.8;transform:scale(1.1);}}
</style>
</head>
<body>
<canvas class="bg-canvas" id="bgCanvas"></canvas>
<div class="glow-effect"></div>
<div class="dashboard">
<header class="header">
<div class="logo">🏥 企业级智能健康监测大屏</div>
<div class="header-stats">
<div class="stat-item">
<div class="stat-value" id="onlineDevices">156</div>
<div class="stat-label">在线设备</div>
</div>
<div class="stat-item">
<div class="stat-value" id="alertCount">3</div>
<div class="stat-label">实时告警</div>
</div>
<div class="stat-item">
<div class="stat-value" id="currentTime"></div>
<div class="stat-label">当前时间</div>
</div>
</div>
</header>
<div class="left-panel">
<div class="panel-title">🫀 实时生命体征</div>
<div class="vital-card">
<div class="vital-title">💓 心率监测</div>
<div class="vital-value" id="heartRate">82<span class="vital-unit">bpm</span></div>
<div class="vital-status" id="heartRateStatus">正常范围</div>
<div class="chart-mini" id="heartRateChart"></div>
</div>
<div class="vital-card">
<div class="vital-title">🩸 血氧饱和度</div>
<div class="vital-value" id="bloodOxygen">98<span class="vital-unit">%</span></div>
<div class="vital-status" id="bloodOxygenStatus">优秀状态</div>
<div class="chart-mini" id="bloodOxygenChart"></div>
</div>
<div class="vital-card">
<div class="vital-title">🌡️ 体温监测</div>
<div class="vital-value" id="temperature">36.8<span class="vital-unit">°C</span></div>
<div class="vital-status" id="temperatureStatus">正常体温</div>
<div class="chart-mini" id="temperatureChart"></div>
</div>
<div class="vital-card">
<div class="vital-title">💉 血压监测</div>
<div class="vital-value" id="bloodPressure">120/80<span class="vital-unit">mmHg</span></div>
<div class="vital-status" id="bloodPressureStatus">理想血压</div>
<div class="chart-mini" id="bloodPressureChart"></div>
</div>
</div>
<div class="main-panel">
<div class="main-display">
<div class="health-ring">
<svg class="ring-svg">
<circle class="ring-bg" cx="140" cy="140" r="120"></circle>
<circle class="ring-glow" cx="140" cy="140" r="120"></circle>
<circle class="ring-progress" cx="140" cy="140" r="120" id="progressRing"></circle>
</svg>
<div class="score-display">
<div class="score-main" id="overallScore">97.4</div>
<div class="score-label">综合健康指数</div>
<div class="status-badges">
<div class="status-badge badge-excellent" id="healthBadge">健康优秀</div>
</div>
</div>
</div>
</div>
</div>
<div class="right-panel">
<div class="panel-title">📊 健康数据分析</div>
<div class="data-grid">
<div class="data-card">
<div class="data-value" id="sleepHours">7.5</div>
<div class="data-label">睡眠时长(h)</div>
</div>
<div class="data-card">
<div class="data-value" id="steps">8000</div>
<div class="data-label">今日步数</div>
</div>
<div class="data-card">
<div class="data-value" id="stress">15</div>
<div class="data-label">压力指数</div>
</div>
<div class="data-card">
<div class="data-value" id="calories">400</div>
<div class="data-label">消耗热量</div>
</div>
</div>
<div class="panel-title">📈 健康趋势雷达</div>
<div class="trend-container" id="radarChart"></div>
<div class="panel-title">💡 AI智能建议</div>
<div class="recommendations" id="recommendations"></div>
</div>
<footer class="footer">
<div class="footer-item">
<div class="footer-value" id="avgHeartRate">82</div>
<div class="footer-label">平均心率</div>
</div>
<div class="footer-item">
<div class="footer-value" id="maxOxygen">99</div>
<div class="footer-label">最高血氧</div>
</div>
<div class="footer-item">
<div class="footer-value" id="sleepQuality">85%</div>
<div class="footer-label">睡眠质量</div>
</div>
<div class="footer-item">
<div class="footer-value" id="activityLevel">92%</div>
<div class="footer-label">活跃度</div>
</div>
<div class="footer-item">
<div class="footer-value" id="stressLevel">低</div>
<div class="footer-label">压力水平</div>
</div>
</footer>
</div>
<script>
//#全局变量配置
const CONFIG = {
deviceSn: getUrlParameter('deviceSn') || 'CRFTQ23409001890',
userId: getUrlParameter('userId') || '1922983570035261442',
orgId: getUrlParameter('orgId') || '1',
updateInterval: 5000,
maxDataPoints: 30
};
let charts = {};
let vitalData = {heartRate:[],bloodOxygen:[],temperature:[],bloodPressure:[]};
let scene, camera, renderer, particles;
//#获取URL参数
function getUrlParameter(name) {
return new URLSearchParams(window.location.search).get(name);
}
//#初始化3D背景
function init3DBackground() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({canvas: document.getElementById('bgCanvas'), alpha: true});
renderer.setSize(window.innerWidth, window.innerHeight);
//#创建粒子系统
const geometry = new THREE.BufferGeometry();
const particleCount = 1000;
const positions = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount * 3; i++) {
positions[i] = (Math.random() - 0.5) * 100;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const material = new THREE.PointsMaterial({
color: 0x00ccff,
size: 0.5,
transparent: true,
opacity: 0.6
});
particles = new THREE.Points(geometry, material);
scene.add(particles);
camera.position.z = 50;
function animate() {
requestAnimationFrame(animate);
particles.rotation.x += 0.001;
particles.rotation.y += 0.002;
renderer.render(scene, camera);
}
animate();
}
//#更新时间显示
function updateTime() {
const now = new Date();
document.getElementById('currentTime').textContent = now.toLocaleTimeString('zh-CN');
}
//#初始化图表
function initCharts() {
//#心率波形图
charts.heartRate = echarts.init(document.getElementById('heartRateChart'));
charts.heartRate.setOption({
backgroundColor: 'transparent',
grid: {top: 5, bottom: 5, left: 5, right: 5},
xAxis: {type: 'category', show: false},
yAxis: {type: 'value', show: false},
series: [{
type: 'line',
data: [],
smooth: true,
lineStyle: {color: '#ff4757', width: 3},
areaStyle: {color: {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{offset: 0, color: 'rgba(255,71,87,0.8)'},
{offset: 1, color: 'rgba(255,71,87,0.1)'}
]
}},
symbol: 'circle',
symbolSize: 6,
emphasis: {focus: 'series'}
}]
});
//#血氧图表
charts.bloodOxygen = echarts.init(document.getElementById('bloodOxygenChart'));
charts.bloodOxygen.setOption({
backgroundColor: 'transparent',
grid: {top: 5, bottom: 5, left: 5, right: 5},
xAxis: {type: 'category', show: false},
yAxis: {type: 'value', show: false},
series: [{
type: 'line',
data: [],
smooth: true,
lineStyle: {color: '#00ccff', width: 3},
areaStyle: {color: {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{offset: 0, color: 'rgba(0,204,255,0.8)'},
{offset: 1, color: 'rgba(0,204,255,0.1)'}
]
}}
}]
});
//#体温图表
charts.temperature = echarts.init(document.getElementById('temperatureChart'));
charts.temperature.setOption({
backgroundColor: 'transparent',
grid: {top: 5, bottom: 5, left: 5, right: 5},
xAxis: {type: 'category', show: false},
yAxis: {type: 'value', show: false},
series: [{
type: 'line',
data: [],
smooth: true,
lineStyle: {color: '#ffaa00', width: 3},
areaStyle: {color: {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{offset: 0, color: 'rgba(255,170,0,0.8)'},
{offset: 1, color: 'rgba(255,170,0,0.1)'}
]
}}
}]
});
//#血压图表
charts.bloodPressure = echarts.init(document.getElementById('bloodPressureChart'));
charts.bloodPressure.setOption({
backgroundColor: 'transparent',
grid: {top: 5, bottom: 5, left: 5, right: 5},
xAxis: {type: 'category', show: false},
yAxis: {type: 'value', show: false},
series: [
{name: '收缩压', type: 'line', data: [], smooth: true, lineStyle: {color: '#ff6b6b', width: 2}},
{name: '舒张压', type: 'line', data: [], smooth: true, lineStyle: {color: '#4ecdc4', width: 2}}
]
});
//#雷达图
charts.radar = echarts.init(document.getElementById('radarChart'));
charts.radar.setOption({
backgroundColor: 'transparent',
radar: {
indicator: [
{name: '心率', max: 100}, {name: '血氧', max: 100}, {name: '体温', max: 100},
{name: '血压', max: 100}, {name: '睡眠', max: 100}, {name: '压力', max: 100}
],
axisLabel: {color: '#ccc', fontSize: 10},
splitLine: {lineStyle: {color: 'rgba(0,204,255,0.3)'}},
axisLine: {lineStyle: {color: 'rgba(0,204,255,0.5)'}},
splitArea: {areaStyle: {color: ['rgba(0,204,255,0.1)', 'rgba(0,204,255,0.05)']}}
},
series: [{
type: 'radar',
data: [{
value: [85, 98, 95, 90, 88, 85],
areaStyle: {color: 'rgba(0,204,255,0.3)'},
lineStyle: {color: '#00ccff', width: 3},
itemStyle: {color: '#00ccff', borderColor: '#fff', borderWidth: 2}
}]
}]
});
}
//#更新环形进度条
function updateHealthRing(score) {
const ring = document.getElementById('progressRing');
const circumference = 2 * Math.PI * 120;
const offset = circumference - (score / 100) * circumference;
ring.style.strokeDasharray = circumference;
ring.style.strokeDashoffset = offset;
}
//#获取实时体征数据
async function fetchVitalData() {
try {
const response = await fetch(`http://localhost:5001/get_personal_info?deviceSn=${CONFIG.deviceSn}`);
const result = await response.json();
if (result.success && result.data?.health_data) {
updateVitalDisplay(result.data.health_data);
}
} catch (error) {
console.error('获取体征数据失败:', error);
}
}
//#获取健康评分数据
async function fetchHealthScore() {
try {
const today = new Date();
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
const startDate = yesterday.toISOString().split('T')[0];
const endDate = today.toISOString().split('T')[0];
const response = await fetch(`/health_data/score?orgId=${CONFIG.orgId}&userId=${CONFIG.userId}&startDate=${startDate}&endDate=${endDate}`);
const result = await response.json();
if (result.success && result.data?.healthScores) {
updateHealthScore(result.data.healthScores);
}
} catch (error) {
console.error('获取健康评分失败:', error);
}
}
//#更新体征显示
function updateVitalDisplay(health) {
const heartRate = parseInt(health.heart_rate || health.heartRate) || 82;
const bloodOxygen = parseInt(health.blood_oxygen || health.bloodOxygen) || 98;
const temperature = parseFloat(health.temperature) || 36.8;
const pressureHigh = parseInt(health.pressure_high || health.pressureHigh) || (heartRate + 35);
const pressureLow = parseInt(health.pressure_low || health.pressureLow) || Math.max(heartRate - 5, 60);
//#更新显示
document.getElementById('heartRate').innerHTML = `${heartRate}<span class="vital-unit">bpm</span>`;
document.getElementById('bloodOxygen').innerHTML = `${bloodOxygen}<span class="vital-unit">%</span>`;
document.getElementById('temperature').innerHTML = `${temperature.toFixed(1)}<span class="vital-unit">°C</span>`;
document.getElementById('bloodPressure').innerHTML = `${pressureHigh}/${pressureLow}<span class="vital-unit">mmHg</span>`;
//#存储数据
vitalData.heartRate.push(heartRate);
vitalData.bloodOxygen.push(bloodOxygen);
vitalData.temperature.push(temperature);
vitalData.bloodPressure.push([pressureHigh, pressureLow]);
if (vitalData.heartRate.length > CONFIG.maxDataPoints) {
Object.keys(vitalData).forEach(key => vitalData[key].shift());
}
//#更新图表
updateCharts();
}
//#更新图表
function updateCharts() {
Object.keys(charts).forEach(key => {
if (key === 'bloodPressure' && charts[key]) {
const pressureHigh = vitalData.bloodPressure.map(p => p[0]);
const pressureLow = vitalData.bloodPressure.map(p => p[1]);
charts[key].setOption({series: [{data: pressureHigh}, {data: pressureLow}]});
} else if (vitalData[key] && charts[key]) {
charts[key].setOption({series: [{data: vitalData[key]}]});
}
});
}
//#更新健康评分
function updateHealthScore(scoreData) {
const {overall, factors, details} = scoreData;
document.getElementById('overallScore').textContent = overall.toFixed(1);
updateHealthRing(overall);
const status = overall >= 90 ? '健康优秀' : overall >= 80 ? '健康良好' : '需要关注';
document.getElementById('healthBadge').textContent = status;
//#更新详细数据
if (factors.sleep) document.getElementById('sleepHours').textContent = factors.sleep.currentValue;
if (factors.step) document.getElementById('steps').textContent = factors.step.currentValue;
if (factors.stress) document.getElementById('stress').textContent = factors.stress.currentValue;
if (factors.calorie) document.getElementById('calories').textContent = factors.calorie.currentValue;
//#更新雷达图
if (charts.radar) {
const radarValues = [
factors.heart_rate?.score || 85,
factors.blood_oxygen?.score || 98,
factors.temperature?.score || 95,
((factors.pressure_high?.score || 0) + (factors.pressure_low?.score || 0)) / 2 || 90,
factors.sleep?.score || 88,
factors.stress?.score || 85
];
charts.radar.setOption({series: [{data: [{value: radarValues}]}]});
}
//#更新建议
updateRecommendations(details);
}
//#更新建议
function updateRecommendations(details) {
if (!details) return;
const container = document.getElementById('recommendations');
container.innerHTML = '';
const categories = {
heart_rate: {icon: '💓', title: '心率优化'},
blood_oxygen: {icon: '🩸', title: '血氧改善'},
sleep: {icon: '😴', title: '睡眠建议'},
stress: {icon: '🧘', title: '压力管理'}
};
Object.keys(details).slice(0, 4).forEach(key => {
const detail = details[key];
const category = categories[key] || {icon: '💡', title: '健康建议'};
if (detail?.suggestions?.length > 0) {
const item = document.createElement('div');
item.className = 'rec-item';
item.innerHTML = `
<div class="rec-title">${category.icon} ${category.title}</div>
<div class="rec-text">${detail.suggestions.join('、')}</div>
`;
container.appendChild(item);
}
});
}
//#初始化所有功能
function init() {
init3DBackground();
initCharts();
updateTime();
setInterval(updateTime, 1000);
fetchVitalData();
fetchHealthScore();
setInterval(fetchVitalData, CONFIG.updateInterval);
setInterval(fetchHealthScore, 30000);
//#窗口大小变化
window.addEventListener('resize', () => {
if (renderer) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
Object.values(charts).forEach(chart => chart?.resize());
});
}
//#启动应用
document.addEventListener('DOMContentLoaded', () => {
setTimeout(init, 100);
});
</script>
</body>
</html>