|
|
<template> <div class="health-dashboard"> <!-- 主内容区域 --> <div class="dashboard-main"> <!-- 左侧:驼圈列表 --> <div class="col-left"> <div class="card pen-list-card"> <div class="card-header"> <span class="title">🐫 驼圈列表</span> <span class="sub">共 {{ pens.length }} 个圈舍</span> </div> <div class="pen-grid"> <div v-for="pen in pens" :key="pen.name" class="pen-item" :class="{ active: selectedPen === pen.name }" @click="selectPen(pen.name)" > <div class="pen-name">{{ pen.name }}</div> <div class="pen-count">{{ pen.count }}峰</div> </div> </div> </div>
<div class="card stats-card"> <div class="card-header"> <span class="title">📊 健康统计总览</span> </div> <div class="stats-grid"> <div class="stat-item"> <div class="stat-value"> <animate-number :from="0" :to="healthStats.totalCamels" duration="3000" /> <span class="unit">峰</span> </div> <div class="stat-label">总骆驼数</div> </div> <div class="stat-item"> <div class="stat-value"> <animate-number :from="0" :to="healthStats.healthyRate" duration="3000" /> <span class="unit">%</span> </div> <div class="stat-label">健康率</div> <div class="stat-trend up">↑ 2.3%</div> </div> <div class="stat-item"> <div class="stat-value"> <animate-number :from="0" :to="healthStats.avgTemp" duration="3000" :decimals="1" /> <span class="unit">°C</span> </div> <div class="stat-label">平均体温</div> </div> <div class="stat-item"> <div class="stat-value"> <animate-number :from="0" :to="healthStats.warningCount" duration="3000" /> <span class="unit">峰</span> </div> <div class="stat-label">异常预警</div> <div class="stat-trend danger">需关注</div> </div> </div> </div>
<div class="card alert-card"> <div class="card-header"> <span class="title">⚠️ 健康预警</span> <span class="badge">{{ healthAlerts.length }}</span> </div> <div class="alert-list"> <div v-for="(alert, idx) in healthAlerts" :key="idx" class="alert-item"> <span class="alert-level" :class="alert.level">{{ alert.levelText }}</span> <div class="alert-content"> <div class="alert-title">{{ alert.title }}</div> <div class="alert-desc">{{ alert.desc }}</div> </div> <span class="alert-time">{{ alert.time }}</span> </div> </div> </div> </div>
<!-- 中间:驼圈详情 + 健康指标趋势 --> <div class="col-center"> <div class="card pen-detail-card"> <div class="card-header"> <span class="title">📍 {{ selectedPen }} · 圈舍详情</span> <span class="sub">{{ getPenCount(selectedPen) }}峰骆驼</span> </div> <div class="pen-detail"> <div class="detail-item"> <div class="detail-label">平均体温</div> <div class="detail-value">{{ penHealthData.avgTemp }}<span class="unit">°C</span></div> <div class="detail-status" :class="getTempStatus(penHealthData.avgTemp)">{{ getTempStatusText(penHealthData.avgTemp) }}</div> </div> <div class="detail-item"> <div class="detail-label">空气质量</div> <div class="detail-value">{{ penHealthData.airQuality }}<span class="unit">AQI</span></div> <div class="detail-status" :class="getAirStatus(penHealthData.airQuality)">{{ getAirStatusText(penHealthData.airQuality) }}</div> </div> <div class="detail-item"> <div class="detail-label">湿度</div> <div class="detail-value">{{ penHealthData.humidity }}<span class="unit">%</span></div> <div class="detail-status" :class="getHumidityStatus(penHealthData.humidity)">{{ getHumidityStatusText(penHealthData.humidity) }}</div> </div> <div class="detail-item"> <div class="detail-label">通风指数</div> <div class="detail-value">{{ penHealthData.ventilation }}<span class="unit">级</span></div> <div class="detail-status normal">良好</div> </div> </div> </div>
<!-- 健康指标趋势图 --> <div class="card chart-card"> <div class="card-header"> <span class="title">📈 健康指标趋势 (7天)</span> <div class="chart-tabs"> <button v-for="metric in chartMetrics" :key="metric.key" :class="{ active: activeMetric === metric.key }" @click="switchMetric(metric.key)" > {{ metric.name }} </button> </div> </div> <div ref="healthChart" class="chart-container"></div> </div>
<!-- 个体骆驼健康数据 --> <div class="card camel-list-card"> <div class="card-header"> <span class="title">🐪 个体骆驼健康数据</span> <span class="sub">点击骆驼查看详细健康报告</span> </div> <div class="camel-search"> <input type="text" v-model="camelSearch" placeholder="搜索骆驼编号..." class="search-input" /> </div> <div class="camel-grid"> <div v-for="camel in filteredCamels" :key="camel.id" class="camel-item" :class="{ warning: camel.healthStatus !== 'healthy' }" @click="selectCamel(camel)" > <div class="camel-avatar"> <span class="camel-icon">🐪</span> <span v-if="camel.healthStatus !== 'healthy'" class="warning-dot"></span> </div> <div class="camel-info"> <div class="camel-id">{{ camel.id }}</div> <div class="camel-temp">{{ camel.temperature }}°C</div> </div> <div class="camel-status" :class="camel.healthStatus"> {{ getHealthStatusText(camel.healthStatus) }} </div> </div> </div> </div> </div>
<!-- 右侧:个体骆驼详细健康报告 --> <div class="col-right"> <div class="card health-report-card"> <div class="card-header"> <span class="title">📋 个体健康报告</span> <span v-if="selectedCamel" class="sub">{{ selectedCamel.id }}</span> </div> <div v-if="selectedCamel" class="health-report"> <div class="report-header"> <div class="camel-avatar-large">🐪</div> <div class="camel-basic"> <div class="camel-name">{{ selectedCamel.id }}</div> <div class="camel-location">圈舍: {{ selectedCamel.pen }}</div> </div> <div class="health-badge" :class="selectedCamel.healthStatus"> {{ getHealthStatusText(selectedCamel.healthStatus) }} </div> </div> <div class="report-metrics"> <div class="metric-row"> <div class="metric"> <div class="metric-label">体温</div> <div class="metric-value" :class="getTempWarning(selectedCamel.temperature)"> {{ selectedCamel.temperature }}<span class="unit">°C</span> </div> <div class="metric-range">正常范围: 36.5-38.5°C</div> </div> <div class="metric"> <div class="metric-label">心率</div> <div class="metric-value">{{ selectedCamel.heartRate }}<span class="unit">bpm</span></div> <div class="metric-range">正常范围: 40-60 bpm</div> </div> <div class="metric"> <div class="metric-label">呼吸频率</div> <div class="metric-value">{{ selectedCamel.respRate }}<span class="unit">次/分</span></div> <div class="metric-range">正常范围: 8-16次/分</div> </div> </div> <div class="metric-row"> <div class="metric"> <div class="metric-label">活动量</div> <div class="metric-value">{{ selectedCamel.activity }}<span class="unit">步</span></div> <div class="metric-progress"> <div class="progress-bar" :style="{ width: (selectedCamel.activity / 8000 * 100) + '%' }"></div> </div> </div> <div class="metric"> <div class="metric-label">饮水次数</div> <div class="metric-value">{{ selectedCamel.waterIntake }}<span class="unit">次/日</span></div> </div> <div class="metric"> <div class="metric-label">采食量</div> <div class="metric-value">{{ selectedCamel.feedIntake }}<span class="unit">kg</span></div> </div> </div> </div> <div class="report-timeline"> <div class="timeline-title">📅 近期健康记录</div> <div class="timeline-list"> <div v-for="record in selectedCamel.healthRecords" :key="record.date" class="timeline-item"> <div class="timeline-date">{{ record.date }}</div> <div class="timeline-content"> <div class="timeline-event">{{ record.event }}</div> <div class="timeline-detail">{{ record.detail }}</div> </div> </div> </div> </div> </div> <div v-else class="no-selection"> <span class="placeholder-icon">🐪</span> <p>点击左侧骆驼查看详细健康报告</p> </div> </div>
<!-- 健康建议 --> <div class="card advice-card"> <div class="card-header"> <span class="title">💡 健康管理建议</span> </div> <div class="advice-list"> <div class="advice-item" v-for="(advice, idx) in healthAdvice" :key="idx"> <span class="advice-icon">{{ advice.icon }}</span> <div class="advice-content"> <div class="advice-title">{{ advice.title }}</div> <div class="advice-desc">{{ advice.desc }}</div> </div> </div> </div> </div> </div> </div> </div></template>
<script>import * as echarts from 'echarts';import dayjs from 'dayjs';import 'dayjs/locale/zh-cn';
export default { name: 'HealthDashboard', data() { return { // 驼圈数据 (根据Excel)
pens: [ { name: 'A1圈', count: 35 }, { name: 'A2圈', count: 35 }, { name: 'B1圈', count: 35 }, { name: 'B2圈', count: 35 }, { name: 'C1圈', count: 35 }, { name: 'C2圈', count: 35 }, { name: 'D1圈', count: 35 }, { name: 'D2圈', count: 35 }, { name: 'E1圈', count: 35 }, { name: 'E2圈', count: 35 }, { name: 'F1圈', count: 35 }, { name: 'F2圈', count: 35 }, { name: 'G1圈', count: 35 }, { name: 'G2圈', count: 35 }, { name: 'H1圈', count: 35 }, { name: 'H2圈', count: 35 }, { name: 'I1圈', count: 35 }, { name: 'I2圈', count: 35 }, { name: 'K1圈', count: 35 }, { name: 'K2圈', count: 35 } ], selectedPen: 'A1圈', // 健康统计数据
healthStats: { totalCamels: 700, healthyRate: 94.2, avgTemp: 37.6, warningCount: 42 }, // 健康预警
healthAlerts: [ { level: 'high', levelText: '紧急', title: 'A1圈骆驼体温异常', desc: '编号A1-023体温39.2°C', time: '09:32' }, { level: 'medium', levelText: '警告', title: 'B2圈空气质量下降', desc: '氨气浓度超标', time: '09:15' }, { level: 'low', levelText: '提示', title: 'C1圈活动量偏低', desc: '3峰骆驼活动量低于正常值', time: '08:47' }, { level: 'medium', levelText: '警告', title: 'E2圈饮水异常', desc: '2峰骆驼饮水次数减少', time: '07:22' } ], // 圈舍健康数据
penHealthData: { avgTemp: 37.5, airQuality: 68, humidity: 58, ventilation: 3 }, // 图表配置
chartMetrics: [ { key: 'temperature', name: '体温' }, { key: 'activity', name: '活动量' }, { key: 'healthRate', name: '健康率' } ], activeMetric: 'temperature', // 骆驼数据 (每个圈舍35峰骆驼)
camels: [], camelSearch: '', selectedCamel: null, // 趋势数据
trendData: { temperature: [37.2, 37.3, 37.4, 37.5, 37.6, 37.5, 37.4], activity: [5200, 5400, 5600, 5800, 5900, 6100, 6000], healthRate: [92.5, 93.1, 93.6, 93.8, 94.0, 94.1, 94.2] }, // 健康建议
healthAdvice: [ { icon: '🌡️', title: '体温监控', desc: 'A1圈有2峰骆驼体温偏高,建议隔离观察' }, { icon: '💨', title: '通风改善', desc: 'B2圈空气质量下降,建议开启通风设备' }, { icon: '💧', title: '饮水管理', desc: 'E2圈饮水异常,检查饮水设备是否故障' }, { icon: '🏃', title: '活动促进', desc: 'C1圈活动量偏低,建议增加驱赶活动' } ], // ECharts实例
healthChartIns: null, // 时间标签
timeLabels: [] }; }, computed: { filteredCamels() { if (!this.camelSearch) { return this.camels.filter(c => c.pen === this.selectedPen); } return this.camels.filter(c => c.pen === this.selectedPen && c.id.toLowerCase().includes(this.camelSearch.toLowerCase()) ); } }, mounted() { this.getCurrentDateTime(); this.initTimeLabels(); this.initCamelData(); this.initHealthChart(); window.addEventListener('resize', this.handleResize); }, beforeDestroy() { if (this.healthChartIns) this.healthChartIns.dispose(); window.removeEventListener('resize', this.handleResize); }, methods: { getCurrentDateTime() { const dateDom = document.querySelector('#cloudDate'); const timeDom = document.querySelector('#cloudTime'); const update = () => { const now = dayjs().locale('zh-cn'); if (dateDom) dateDom.innerHTML = now.format('MM月DD日 dddd'); if (timeDom) timeDom.innerHTML = now.format('HH:mm:ss'); }; update(); setInterval(update, 1000); }, initTimeLabels() { for (let i = 6; i >= 0; i--) { this.timeLabels.push(dayjs().subtract(i, 'day').format('MM/DD')); } }, initCamelData() { // 为每个圈舍生成35峰骆驼数据
const allCamels = []; const statuses = ['healthy', 'warning', 'critical']; const statusWeights = [0.85, 0.12, 0.03]; this.pens.forEach(pen => { for (let i = 1; i <= pen.count; i++) { const random = Math.random(); let healthStatus = 'healthy'; if (random < statusWeights[0]) healthStatus = 'healthy'; else if (random < statusWeights[0] + statusWeights[1]) healthStatus = 'warning'; else healthStatus = 'critical'; const baseTemp = 37.5; const tempOffset = healthStatus === 'healthy' ? (Math.random() - 0.5) * 0.8 : healthStatus === 'warning' ? 0.8 + Math.random() * 0.5 : 1.3 + Math.random() * 0.7; allCamels.push({ id: `${pen.name}-${String(i).padStart(3, '0')}`, pen: pen.name, temperature: +(baseTemp + (healthStatus !== 'healthy' ? tempOffset : tempOffset)).toFixed(1), heartRate: Math.floor(45 + (healthStatus !== 'healthy' ? Math.random() * 15 : (Math.random() - 0.5) * 10)), respRate: Math.floor(10 + (healthStatus !== 'healthy' ? Math.random() * 8 : (Math.random() - 0.5) * 4)), activity: Math.floor(4000 + Math.random() * 4000), waterIntake: Math.floor(3 + Math.random() * 5), feedIntake: +(8 + Math.random() * 4).toFixed(1), healthStatus: healthStatus, healthRecords: this.generateHealthRecords(healthStatus) }); } }); this.camels = allCamels; }, generateHealthRecords(status) { const records = []; const dates = [0, 1, 2, 3, 4, 5, 6]; for (let i = 0; i < 5; i++) { const date = dayjs().subtract(dates[i], 'day').format('MM/DD'); if (status === 'healthy') { records.push({ date: date, event: '日常巡检', detail: '体温正常,精神状态良好,采食饮水正常' }); } else if (status === 'warning') { records.push({ date: date, event: i === 0 ? '体温偏高' : '持续观察', detail: i === 0 ? '体温38.9°C,建议隔离观察' : '体温略高,精神状态尚可' }); } else { records.push({ date: date, event: i === 0 ? '异常告警' : '医疗干预', detail: i === 0 ? '体温39.5°C,食欲不振,精神萎靡' : '已用药治疗,继续观察' }); } } return records; }, selectPen(penName) { this.selectedPen = penName; this.selectedCamel = null; // 更新圈舍健康数据 (模拟)
this.penHealthData = { avgTemp: +(37.2 + Math.random() * 0.8).toFixed(1), airQuality: Math.floor(40 + Math.random() * 50), humidity: Math.floor(40 + Math.random() * 30), ventilation: Math.floor(2 + Math.random() * 3) }; }, getPenCount(penName) { const pen = this.pens.find(p => p.name === penName); return pen ? pen.count : 0; }, selectCamel(camel) { this.selectedCamel = camel; }, getHealthStatusText(status) { const map = { healthy: '健康', warning: '亚健康', critical: '异常' }; return map[status] || '未知'; }, getTempStatus(temp) { if (temp < 36.5) return 'warning'; if (temp > 38.5) return 'danger'; return 'normal'; }, getTempStatusText(temp) { if (temp < 36.5) return '偏低'; if (temp > 38.5) return '偏高'; return '正常'; }, getAirStatus(airQuality) { if (airQuality > 100) return 'danger'; if (airQuality > 70) return 'warning'; return 'normal'; }, getAirStatusText(airQuality) { if (airQuality > 100) return '差'; if (airQuality > 70) return '一般'; return '良好'; }, getHumidityStatus(humidity) { if (humidity > 75) return 'warning'; if (humidity < 40) return 'warning'; return 'normal'; }, getHumidityStatusText(humidity) { if (humidity > 75) return '偏高'; if (humidity < 40) return '偏低'; return '适宜'; }, getTempWarning(temp) { if (temp < 36.5 || temp > 38.5) return 'warning'; return ''; }, initHealthChart() { const chartDom = this.$refs.healthChart; this.healthChartIns = echarts.init(chartDom); this.updateHealthChart(); }, updateHealthChart() { const currentData = this.trendData[this.activeMetric]; const unitMap = { temperature: '°C', activity: '步', healthRate: '%' }; const nameMap = { temperature: '平均体温', activity: '平均活动量', healthRate: '健康率' }; const yAxisMin = this.activeMetric === 'healthRate' ? 90 : null; this.healthChartIns.setOption({ tooltip: { trigger: 'axis', backgroundColor: 'rgba(0,0,0,0.7)', borderColor: '#3498db', textStyle: { color: '#fff' } }, xAxis: { type: 'category', data: this.timeLabels, axisLabel: { color: '#fff' }, axisLine: { lineStyle: { color: '#5dade2' } } }, yAxis: { type: 'value', name: `${nameMap[this.activeMetric]} (${unitMap[this.activeMetric]})`, nameTextStyle: { color: '#fff' }, axisLabel: { color: '#fff' }, splitLine: { lineStyle: { color: 'rgba(93, 173, 226, 0.3)' } }, axisLine: { lineStyle: { color: '#5dade2' } }, min: yAxisMin }, series: [{ data: currentData, type: 'line', smooth: true, lineStyle: { width: 3, color: '#5dade2' }, areaStyle: { opacity: 0.2, color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#3498db' }, { offset: 1, color: 'rgba(52, 152, 219, 0.1)' } ]) }, symbol: 'circle', symbolSize: 8, itemStyle: { color: '#3498db' } }], grid: { containLabel: true, bottom: 20, top: 30, right: 20, left: 55 } }); }, switchMetric(metric) { this.activeMetric = metric; this.updateHealthChart(); },
handleResize() { if (this.healthChartIns) this.healthChartIns.resize(); } }};</script>
<style scoped lang="scss">.health-dashboard { width: 100%; height: calc(100vh - 100px); background: transparent; padding: 20px 24px 30px; box-sizing: border-box; font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', sans-serif; overflow-y: auto; overflow-x: auto;}
.dashboard-main { display: flex; gap: 20px; flex-wrap: wrap; align-items: stretch; .col-left, .col-right { flex: 1.1; min-width: 280px; display: flex; flex-direction: column; gap: 20px; } .col-center { flex: 1.8; min-width: 420px; display: flex; flex-direction: column; gap: 20px; }}
.card { background: transparent; backdrop-filter: blur(0); border-radius: 16px; border: 1.5px solid rgba(52, 152, 219, 0.7); overflow: hidden; transition: all 0.3s ease; display: flex; flex-direction: column; &:hover { border-color: #5dade2; box-shadow: 0 4px 20px rgba(52, 152, 219, 0.25); background: rgba(52, 152, 219, 0.05); } .card-header { padding: 14px 20px; border-bottom: 1px solid rgba(52, 152, 219, 0.4); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; flex-shrink: 0; .title { font-size: 16px; font-weight: 600; color: #fff; letter-spacing: 0.5px; text-shadow: 0 0 5px rgba(52, 152, 219, 0.5); } .badge { background: #e74c3c; border-radius: 30px; padding: 2px 10px; font-size: 12px; color: white; } .sub { font-size: 11px; color: rgba(255, 255, 255, 0.7); } .chart-tabs { display: flex; gap: 8px; button { background: rgba(52, 152, 219, 0.2); border: 1px solid rgba(52, 152, 219, 0.4); border-radius: 20px; padding: 4px 12px; font-size: 11px; color: rgba(255, 255, 255, 0.8); cursor: pointer; transition: all 0.2s; &:hover, &.active { background: #3498db; border-color: #3498db; color: white; } } } }}
// 驼圈列表
.pen-list-card { .pen-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; padding: 16px; .pen-item { background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(52, 152, 219, 0.4); border-radius: 12px; padding: 12px 8px; text-align: center; cursor: pointer; transition: all 0.2s; &:hover, &.active { border-color: #5dade2; background: rgba(52, 152, 219, 0.15); transform: translateY(-2px); } .pen-name { font-size: 14px; font-weight: 500; color: #fff; margin-bottom: 4px; } .pen-count { font-size: 11px; color: rgba(255, 255, 255, 0.6); } } }}
// 统计卡片
.stats-card { .stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; padding: 20px; .stat-item { text-align: center; .stat-value { font-size: 28px; font-weight: bold; color: #5dade2; .unit { font-size: 12px; margin-left: 2px; color: rgba(255, 255, 255, 0.6); } } .stat-label { font-size: 11px; color: rgba(255, 255, 255, 0.7); margin: 4px 0; } .stat-trend { font-size: 10px; &.up { color: #2ecc71; } &.danger { color: #e74c3c; } } } }}
// 预警列表
.alert-card { flex: 1; display: flex; flex-direction: column; .alert-list { padding: 8px 16px; flex: 1; overflow-y: auto; .alert-item { display: flex; align-items: flex-start; gap: 12px; border-bottom: 1px solid rgba(52, 152, 219, 0.2); padding: 12px 4px; .alert-level { font-size: 10px; padding: 2px 8px; border-radius: 20px; min-width: 40px; text-align: center; &.high { background: rgba(231, 76, 60, 0.2); color: #e74c3c; } &.medium { background: rgba(243, 156, 18, 0.2); color: #f39c12; } &.low { background: rgba(52, 152, 219, 0.2); color: #3498db; } } .alert-content { flex: 1; .alert-title { font-size: 13px; font-weight: 500; color: rgba(255, 255, 255, 0.9); } .alert-desc { font-size: 11px; color: rgba(255, 255, 255, 0.6); margin-top: 2px; } } .alert-time { font-size: 10px; color: rgba(255, 255, 255, 0.4); } } }}
// 圈舍详情
.pen-detail-card { .pen-detail { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; padding: 20px; .detail-item { text-align: center; .detail-label { font-size: 11px; color: rgba(255, 255, 255, 0.7); margin-bottom: 6px; } .detail-value { font-size: 22px; font-weight: bold; color: #5dade2; .unit { font-size: 11px; margin-left: 2px; color: rgba(255, 255, 255, 0.5); } } .detail-status { font-size: 10px; margin-top: 4px; &.normal { color: #2ecc71; } &.warning { color: #f39c12; } &.danger { color: #e74c3c; } } } }}
// 图表容器
.chart-container { width: 100%; height: 240px; padding: 8px;}
// 骆驼列表
.camel-list-card { display: flex; flex-direction: column; .camel-search { padding: 12px 16px; flex-shrink: 0; .search-input { width: 100%; background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(52, 152, 219, 0.5); border-radius: 20px; padding: 8px 16px; color: white; font-size: 12px; outline: none; &:focus { border-color: #5dade2; } &::placeholder { color: rgba(255, 255, 255, 0.4); } } } .camel-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; padding: 16px; flex: 1; overflow-y: auto; max-height: 280px; .camel-item { background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(52, 152, 219, 0.4); border-radius: 12px; padding: 10px; display: flex; align-items: center; gap: 10px; cursor: pointer; transition: all 0.2s; &:hover { border-color: #5dade2; background: rgba(52, 152, 219, 0.1); } &.warning { border-color: rgba(243, 156, 18, 0.6); } .camel-avatar { position: relative; .camel-icon { font-size: 28px; } .warning-dot { position: absolute; top: -2px; right: -2px; width: 10px; height: 10px; background: #f39c12; border-radius: 50%; animation: pulse 1.5s infinite; } } .camel-info { flex: 1; .camel-id { font-size: 12px; font-weight: 500; color: #fff; } .camel-temp { font-size: 10px; color: rgba(255, 255, 255, 0.6); } } .camel-status { font-size: 10px; padding: 2px 6px; border-radius: 10px; &.healthy { background: rgba(46, 204, 113, 0.2); color: #2ecc71; } &.warning { background: rgba(243, 156, 18, 0.2); color: #f39c12; } &.critical { background: rgba(231, 76, 60, 0.2); color: #e74c3c; } } } }}
// 健康报告
.health-report-card { flex: 1; display: flex; flex-direction: column; .health-report { flex: 1; display: flex; flex-direction: column; .report-header { display: flex; align-items: center; gap: 16px; padding: 20px; background: rgba(0, 0, 0, 0.2); border-bottom: 1px solid rgba(52, 152, 219, 0.3); flex-shrink: 0; .camel-avatar-large { font-size: 48px; } .camel-basic { flex: 1; .camel-name { font-size: 18px; font-weight: bold; color: #fff; } .camel-location { font-size: 11px; color: rgba(255, 255, 255, 0.6); margin-top: 4px; } } .health-badge { padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 500; &.healthy { background: rgba(46, 204, 113, 0.2); color: #2ecc71; } &.warning { background: rgba(243, 156, 18, 0.2); color: #f39c12; } &.critical { background: rgba(231, 76, 60, 0.2); color: #e74c3c; } } } .report-metrics { padding: 16px; flex-shrink: 0; .metric-row { display: flex; gap: 20px; margin-bottom: 16px; .metric { flex: 1; .metric-label { font-size: 11px; color: rgba(255, 255, 255, 0.6); margin-bottom: 4px; } .metric-value { font-size: 20px; font-weight: bold; color: #5dade2; &.warning { color: #e74c3c; } .unit { font-size: 11px; margin-left: 2px; color: rgba(255, 255, 255, 0.5); } } .metric-range { font-size: 10px; color: rgba(255, 255, 255, 0.4); margin-top: 4px; } .metric-progress { margin-top: 8px; background: rgba(52, 152, 219, 0.3); border-radius: 10px; height: 6px; .progress-bar { height: 6px; background: #5dade2; border-radius: 10px; } } } } } .report-timeline { padding: 16px; border-top: 1px solid rgba(52, 152, 219, 0.3); flex: 1; overflow-y: auto; .timeline-title { font-size: 13px; font-weight: 500; color: #fff; margin-bottom: 12px; } .timeline-list { .timeline-item { display: flex; gap: 12px; margin-bottom: 12px; .timeline-date { font-size: 10px; color: rgba(255, 255, 255, 0.5); min-width: 50px; } .timeline-content { flex: 1; .timeline-event { font-size: 12px; color: rgba(255, 255, 255, 0.9); } .timeline-detail { font-size: 10px; color: rgba(255, 255, 255, 0.5); } } } } } } .no-selection { text-align: center; padding: 60px 20px; flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; .placeholder-icon { font-size: 64px; opacity: 0.5; display: block; margin-bottom: 16px; } p { color: rgba(255, 255, 255, 0.5); font-size: 13px; } }}
// 健康建议
.advice-card { .advice-list { padding: 16px; .advice-item { display: flex; gap: 12px; margin-bottom: 16px; &:last-child { margin-bottom: 0; } .advice-icon { font-size: 20px; } .advice-content { flex: 1; .advice-title { font-size: 13px; font-weight: 500; color: #fff; } .advice-desc { font-size: 11px; color: rgba(255, 255, 255, 0.6); margin-top: 2px; } } } }}
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(1.2); }}</style>
|