Files
akmon/pages/ec/admin/health-monitoring.uvue
2026-01-20 08:04:15 +08:00

1254 lines
30 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 健康监测管理 - uts-android 兼容重构版 -->
<template>
<view class="health-monitoring">
<!-- Header -->
<view class="header">
<text class="header-title">健康监测</text>
<view class="header-actions">
<button class="action-btn" @click="showAddVitalSigns">
<text class="btn-text"> 记录体征</text>
</button>
<button class="action-btn" @click="showBulkEntry">
<text class="btn-text">📊 批量录入</text>
</button>
</view>
</view>
<!-- Stats Cards -->
<view class="stats-section-flex">
<view class="stat-card">
<view class="stat-icon">❤️</view>
<view class="stat-content">
<text class="stat-number">{{ stats.total_records_today }}</text>
<text class="stat-label">今日记录</text>
</view>
</view>
<view class="stat-card stat-card-alert">
<view class="stat-icon">⚠️</view>
<view class="stat-content">
<text class="stat-number">{{ stats.abnormal_readings }}</text>
<text class="stat-label">异常读数</text>
</view>
</view>
<view class="stat-card">
<view class="stat-icon">📈</view>
<view class="stat-content">
<text class="stat-number">{{ stats.pending_reviews }}</text>
<text class="stat-label">待审核</text>
</view>
</view>
<view class="stat-card stat-card-urgent">
<view class="stat-icon">🚨</view>
<view class="stat-content">
<text class="stat-number">{{ stats.critical_alerts }}</text>
<text class="stat-label">危急值</text>
</view>
</view>
</view>
<!-- Filter Section -->
<view class="filters-section">
<view class="filter-row-flex">
<view class="filter-group">
<text class="filter-label">患者筛选</text>
<button class="picker-btn" @click="showElderActionSheet">
<text class="picker-text">{{ selectedElder?.name ?? '全部患者' }}</text>
</button>
</view>
<view class="filter-group">
<text class="filter-label">体征类型</text>
<button class="picker-btn" @click="showVitalTypeActionSheet">
<text class="picker-text">{{ selectedVitalType?.label ?? '全部类型' }}</text>
</button>
</view>
<view class="filter-group">
<text class="filter-label">时间范围</text>
<button class="picker-btn" @click="showTimeRangeActionSheet">
<text class="picker-text">{{ selectedTimeRange?.label ?? '今日' }}</text>
</button>
</view>
</view>
<view class="filter-row-flex">
<view class="filter-toggles-flex">
<button
class="toggle-btn"
:class="showAbnormalOnly ? 'active' : ''"
@click="toggleAbnormalOnly"
>
<text class="toggle-text">仅异常</text>
</button>
<button
class="toggle-btn"
:class="showCriticalOnly ? 'active' : ''"
@click="toggleCriticalOnly"
>
<text class="toggle-text">仅危急</text>
</button>
</view>
<button class="refresh-btn" @click="refreshData">
<text class="refresh-text">刷新</text>
</button>
</view>
</view>
<!-- Vital Signs List -->
<view class="vital-signs-section">
<view class="section-header">
<text class="section-title">生命体征记录 ({{ filteredVitalSigns.length }})</text>
<view class="view-modes">
<button
class="mode-btn"
:class="{ active: viewMode === 'list' }"
@click="setViewMode('list')"
>
<text class="mode-text">📋</text>
</button>
<button
class="mode-btn"
:class="{ active: viewMode === 'chart' }"
@click="setViewMode('chart')"
>
<text class="mode-text">📊</text>
</button>
</view>
</view>
<!-- List View -->
<scroll-view
v-if="viewMode === 'list'"
class="vital-signs-list"
direction="vertical"
:style="{ height: '500px' }"
>
<view
v-for="(vital, idx) in filteredVitalSigns"
:key="vital.id"
class="vital-sign-item"
:class="{
'abnormal': vital.is_abnormal,
'critical': isCriticalReading(vital)
}"
@click="viewVitalDetail(vital)"
>
<view class="vital-header">
<view class="patient-info">
<text class="patient-name">{{ vital.elder_name }}</text>
<text class="record-time">{{ formatDateTime(vital.recorded_at) }}</text>
</view>
<view class="vital-status">
<view v-if="vital.is_abnormal" class="status-badge abnormal">
<text class="badge-text">异常</text>
</view>
<view v-if="isCriticalReading(vital)" class="status-badge critical">
<text class="badge-text">危急</text>
</view>
</view>
</view>
<view class="vital-measurements">
<view v-if="vital.blood_pressure" class="measurement-item" :style="{ 'margin-right': showMeasurementMargin(vital, 'blood_pressure') }">
<text class="measurement-label">血压</text>
<text class="measurement-value" :class="{ abnormal: isBloodPressureAbnormal(vital.blood_pressure) }">
{{ vital.blood_pressure }} mmHg
</text>
</view>
<view v-if="vital.heart_rate" class="measurement-item" :style="{ 'margin-right': showMeasurementMargin(vital, 'heart_rate') }">
<text class="measurement-label">心率</text>
<text class="measurement-value" :class="{ abnormal: isHeartRateAbnormal(vital.heart_rate) }">
{{ vital.heart_rate }} bpm
</text>
</view>
<view v-if="vital.temperature" class="measurement-item" :style="{ 'margin-right': showMeasurementMargin(vital, 'temperature') }">
<text class="measurement-label">体温</text>
<text class="measurement-value" :class="{ abnormal: isTemperatureAbnormal(vital.temperature) }">
{{ vital.temperature }}°C
</text>
</view>
<view v-if="vital.oxygen_saturation" class="measurement-item" :style="{ 'margin-right': showMeasurementMargin(vital, 'oxygen_saturation') }">
<text class="measurement-label">血氧</text>
<text class="measurement-value" :class="{ abnormal: isOxygenAbnormal(vital.oxygen_saturation) }">
{{ vital.oxygen_saturation }}%
</text>
</view>
<view v-if="vital.blood_sugar" class="measurement-item">
<text class="measurement-label">血糖</text>
<text class="measurement-value" :class="{ abnormal: isBloodSugarAbnormal(vital.blood_sugar) }">
{{ vital.blood_sugar }} mmol/L
</text>
</view>
</view>
<view class="vital-footer">
<text class="recorded-by">记录者: {{ vital.recorded_by }}</text>
<view class="vital-actions">
<button class="action-btn-small" @click.stop="editVital(vital)">
<text class="btn-text">编辑</text>
</button>
<button class="action-btn-small" @click.stop="addFollowUp(vital)">
<text class="btn-text">跟进</text>
</button>
</view>
</view>
<view v-if="vital.notes" class="vital-notes">
<text class="notes-text">备注: {{ vital.notes }}</text>
</view>
</view>
<view v-if="filteredVitalSigns.length === 0" class="empty-state">
<text class="empty-text">暂无生命体征记录</text>
<button class="add-btn" @click="showAddVitalSigns">
<text class="btn-text">添加记录</text>
</button>
</view>
</scroll-view>
<!-- Chart View -->
<view v-if="viewMode === 'chart'" class="chart-section">
<view class="chart-filters">
<button
v-for="type in chartTypes"
:key="type.value"
class="chart-type-btn"
:class="{ active: selectedChartType === type.value }"
@click="setChartType(type.value)"
>
<text class="btn-text">{{ type.label }}</text>
</button>
</view>
<view class="chart-container">
<text class="chart-title">{{ getChartTitle() }}</text>
<!-- 这里应该放置图表组件 -->
<view class="chart-placeholder">
<text class="placeholder-text">图表视图开发中...</text>
</view>
</view>
</view>
</view>
<!-- Health Alerts -->
<view class="alerts-section" v-if="healthAlerts.length > 0">
<view class="section-header">
<text class="section-title">健康提醒</text>
<button class="view-all-btn" @click="showAllAlerts">
<text class="btn-text">查看全部</text>
</button>
</view>
<scroll-view class="alerts-list" scroll-y="true" :style="{ height: '200px' }">
<view
v-for="alert in healthAlerts"
:key="alert.id"
class="alert-item"
:class="alert.severity"
@click="handleAlert(alert)"
>
<view class="alert-header">
<text class="alert-title">{{ alert.title }}</text>
<text class="alert-time">{{ formatTime(alert.created_at) }}</text>
</view>
<view class="alert-content">
<text class="alert-description">{{ alert.description }}</text>
<text class="alert-patient">患者: {{ alert.elder_name }}</text>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, computed, onMounted } from 'vue'
import supa from '@/components/supadb/aksupainstance.uts'
import {
formatTime,
formatDate,
formatDateTime,
getTodayStart,
getTodayEnd,
getRecentDate,
getDaysAgo
} from '../types_new.uts'
// 数据类型定义
type HealthStats = {
total_records_today: number
abnormal_readings: number
pending_reviews: number
critical_alerts: number
}
type VitalSign = {
id: string
elder_id: string
elder_name: string
blood_pressure: string
heart_rate: number
temperature: number
blood_sugar: number
weight: number
height: number
oxygen_saturation: number
recorded_by: string
recorded_at: string
notes: string
is_abnormal: boolean
}
type HealthAlert = {
id: string
elder_id: string
elder_name: string
title: string
description: string
severity: string
alert_type: string
status: string
created_at: string
}
type Elder = {
id: string
name: string
room_number: string
bed_number: string
}
type FilterOption = {
value: string
label: string
}
// 响应式数据
const stats = ref<HealthStats>({
total_records_today: 0,
abnormal_readings: 0,
pending_reviews: 0,
critical_alerts: 0
})
const vitalSigns = ref<VitalSign[]>([])
const healthAlerts = ref<HealthAlert[]>([])
const elders = ref<Elder[]>([])
const selectedElderIndex = ref<number>(-1)
const selectedVitalTypeIndex = ref<number>(-1)
const selectedTimeRangeIndex = ref<number>(0)
const showAbnormalOnly = ref<boolean>(false)
const showCriticalOnly = ref<boolean>(false)
const viewMode = ref<string>('list')
const selectedChartType = ref<string>('blood_pressure')
// 筛选选项
const vitalTypeOptions = ref<FilterOption[]>([
{ value: 'all', label: '全部类型' },
{ value: 'blood_pressure', label: '血压' },
{ value: 'heart_rate', label: '心率' },
{ value: 'temperature', label: '体温' },
{ value: 'blood_sugar', label: '血糖' },
{ value: 'oxygen_saturation', label: '血氧' },
{ value: 'weight', label: '体重' }
])
const timeRangeOptions = ref<FilterOption[]>([
{ value: 'today', label: '今日' },
{ value: '3days', label: '近3天' },
{ value: '7days', label: '近7天' },
{ value: '30days', label: '近30天' }
])
const chartTypes = ref<FilterOption[]>([
{ value: 'blood_pressure', label: '血压趋势' },
{ value: 'heart_rate', label: '心率趋势' },
{ value: 'temperature', label: '体温趋势' },
{ value: 'blood_sugar', label: '血糖趋势' },
{ value: 'oxygen_saturation', label: '血氧趋势' }
])
// 计算属性
const elderOptions = computed<Elder[]>(() => {
return [{ id: 'all', name: '全部患者', room_number: '', bed_number: '' } as Elder, ...elders.value]
})
const selectedElder = computed<Elder | null>(() => {
if (selectedElderIndex.value < 0 || selectedElderIndex.value >= elderOptions.value.length) {
return null
}
return elderOptions.value[selectedElderIndex.value]
})
const selectedVitalType = computed<FilterOption | null>(() => {
if (selectedVitalTypeIndex.value < 0 || selectedVitalTypeIndex.value >= vitalTypeOptions.value.length) {
return null
}
return vitalTypeOptions.value[selectedVitalTypeIndex.value]
})
const selectedTimeRange = computed<FilterOption | null>(() => {
if (selectedTimeRangeIndex.value < 0 || selectedTimeRangeIndex.value >= timeRangeOptions.value.length) {
return null
}
return timeRangeOptions.value[selectedTimeRangeIndex.value]
})
const filteredVitalSigns = computed<VitalSign[]>(() => {
let filtered = vitalSigns.value
// 按患者筛选
if (selectedElder.value && selectedElder.value.id !== 'all') {
filtered = filtered.filter(vital => vital.elder_id === selectedElder.value!.id)
}
// 按体征类型筛选
if (selectedVitalType.value && selectedVitalType.value.value !== 'all') {
const type = selectedVitalType.value.value
filtered = filtered.filter(vital => {
switch (type) {
case 'blood_pressure': return vital.blood_pressure !== ''
case 'heart_rate': return vital.heart_rate > 0
case 'temperature': return vital.temperature > 0
case 'blood_sugar': return vital.blood_sugar > 0
case 'oxygen_saturation': return vital.oxygen_saturation > 0
case 'weight': return vital.weight > 0
default: return true
}
})
}
// 按时间范围筛选
if (selectedTimeRange.value) {
const now = new Date()
let startDate: string
switch (selectedTimeRange.value.value) {
case 'today':
startDate = getTodayStart()
break
case '3days':
startDate = getDaysAgo(3)
break
case '7days':
startDate = getDaysAgo(7)
break
case '30days':
startDate = getDaysAgo(30)
break
default:
startDate = getTodayStart()
}
filtered = filtered.filter(vital => vital.recorded_at >= startDate)
}
// 仅显示异常
if (showAbnormalOnly.value) {
filtered = filtered.filter(vital => vital.is_abnormal)
}
// 仅显示危急
if (showCriticalOnly.value) {
filtered = filtered.filter(vital => isCriticalReading(vital))
}
return filtered.sort((a, b) => new Date(b.recorded_at).getTime() - new Date(a.recorded_at).getTime())
})
// 生命周期
onMounted(() => {
loadData()
})
// 加载数据
const loadData = async () => {
await Promise.all([
loadStats(),
loadVitalSigns(),
loadHealthAlerts(),
loadElders()
])
}
// 加载统计数据
const loadStats = async () => {
try {
// 今日记录总数
const todayResult = await supa
.from('ec_vital_signs')
.select('*', { count: 'exact' })
.gte('recorded_at', getTodayStart())
.lte('recorded_at', getTodayEnd())
.executeAs<VitalSign[]>()
// 异常读数
const abnormalResult = await supa
.from('ec_vital_signs')
.select('*', { count: 'exact' })
.eq('is_abnormal', true)
.gte('recorded_at', getDaysAgo(7))
.executeAs<VitalSign[]>()
// 待审核记录
const pendingResult = await supa
.from('ec_vital_signs')
.select('*', { count: 'exact' })
.eq('status', 'pending_review')
.executeAs<VitalSign[]>()
// 危急值提醒
const criticalResult = await supa
.from('ec_health_alerts')
.select('*', { count: 'exact' })
.eq('severity', 'critical')
.eq('status', 'active')
.executeAs<HealthAlert[]>()
stats.value = {
total_records_today: todayResult.count ?? 0,
abnormal_readings: abnormalResult.count ?? 0,
pending_reviews: pendingResult.count ?? 0,
critical_alerts: criticalResult.count ?? 0
}
} catch (error) {
console.error('加载统计数据失败:', error)
}
}
// 加载生命体征记录
const loadVitalSigns = async () => {
try {
const result = await supa
.from('ec_vital_signs')
.select(`
id,
elder_id,
elder_name,
blood_pressure,
heart_rate,
temperature,
blood_sugar,
weight,
height,
oxygen_saturation,
recorded_by,
recorded_at,
notes,
is_abnormal
`)
.gte('recorded_at', getDaysAgo(30))
.order('recorded_at', { ascending: false })
.limit(200)
.executeAs<VitalSign[]>()
if (result.error == null && result.data != null) {
vitalSigns.value = result.data
}
} catch (error) {
console.error('加载生命体征记录失败:', error)
}
}
// 加载健康提醒
const loadHealthAlerts = async () => {
try {
const result = await supa
.from('ec_health_alerts')
.select(`
id,
elder_id,
elder_name,
title,
description,
severity,
alert_type,
status,
created_at
`)
.in('severity', ['high', 'critical'])
.eq('status', 'active')
.order('created_at', { ascending: false })
.limit(10)
.executeAs<HealthAlert[]>()
if (result.error == null && result.data != null) {
healthAlerts.value = result.data
}
} catch (error) {
console.error('加载健康提醒失败:', error)
}
}
// 加载患者列表(采用 supa 查询风格,含 count类型安全、判空、默认值健壮
const loadElders = async () => {
try {
const eldersResult = await supa
.from('ec_elders')
.select('*', { count: 'exact' })
.eq('status', 'active')
.order('room_number', { ascending: true })
.executeAs<Elder>()
if (eldersResult.error == null && eldersResult.data != null) {
// 兼容单条/多条数据返回
if (Array.isArray(eldersResult.data)) {
elders.value = eldersResult.data
} else if (eldersResult.data) {
elders.value = [eldersResult.data]
} else {
elders.value = []
}
} else {
elders.value = []
}
} catch (error) {
elders.value = []
console.error('加载患者列表失败:', error)
}
}
// 辅助函数
const isCriticalReading = (vital: VitalSign): boolean => {
// 定义危急值标准
if (vital.blood_pressure) {
const [systolic, diastolic] = vital.blood_pressure.split('/').map(Number)
if (systolic >= 180 || systolic <= 90 || diastolic >= 110 || diastolic <= 60) {
return true
}
}
if (vital.heart_rate && (vital.heart_rate >= 120 || vital.heart_rate <= 50)) {
return true
}
if (vital.temperature && (vital.temperature >= 39.0 || vital.temperature <= 35.0)) {
return true
}
if (vital.oxygen_saturation && vital.oxygen_saturation <= 90) {
return true
}
if (vital.blood_sugar && (vital.blood_sugar >= 13.9 || vital.blood_sugar <= 3.9)) {
return true
}
return false
}
const isBloodPressureAbnormal = (bp: string): boolean => {
if (!bp) return false
const [systolic, diastolic] = bp.split('/').map(Number)
return systolic >= 140 || systolic <= 90 || diastolic >= 90 || diastolic <= 60
}
const isHeartRateAbnormal = (hr: number): boolean => {
return hr >= 100 || hr <= 60
}
const isTemperatureAbnormal = (temp: number): boolean => {
return temp >= 37.3 || temp <= 36.0
}
const isOxygenAbnormal = (spo2: number): boolean => {
return spo2 <= 95
}
const isBloodSugarAbnormal = (bs: number): boolean => {
return bs >= 11.1 || bs <= 4.0
}
const getChartTitle = (): string => {
const type = chartTypes.value.find(t => t.value === selectedChartType.value)
const patient = selectedElder.value && selectedElder.value.id !== 'all' ? selectedElder.value.name + ' - ' : ''
return patient + (type?.label ?? '趋势图')
}
// 事件处理
const refreshData = () => {
loadData()
uni.showToast({ title: '数据已刷新', icon: 'success' })
}
const onElderChange = (e: any) => {
selectedElderIndex.value = e.detail.value
}
const onVitalTypeChange = (e: any) => {
selectedVitalTypeIndex.value = e.detail.value
}
const onTimeRangeChange = (e: any) => {
selectedTimeRangeIndex.value = e.detail.value
}
const toggleAbnormalOnly = () => {
showAbnormalOnly.value = !showAbnormalOnly.value
if (showAbnormalOnly.value) {
showCriticalOnly.value = false
}
}
const toggleCriticalOnly = () => {
showCriticalOnly.value = !showCriticalOnly.value
if (showCriticalOnly.value) {
showAbnormalOnly.value = false
}
}
const setViewMode = (mode: string) => {
viewMode.value = mode
}
const setChartType = (type: string) => {
selectedChartType.value = type
}
const showAddVitalSigns = () => {
uni.navigateTo({ url: '/pages/ec/nurse/vital-signs-entry' })
}
const showBulkEntry = () => {
uni.navigateTo({ url: '/pages/ec/nurse/bulk-entry' })
}
const showAllAlerts = () => {
uni.navigateTo({ url: '/pages/ec/health/alerts' })
}
const viewVitalDetail = (vital: VitalSign) => {
uni.navigateTo({
url: `/pages/ec/nurse/vital-detail?id=${vital.id}`
})
}
const editVital = (vital: VitalSign) => {
uni.navigateTo({
url: `/pages/ec/nurse/vital-signs-entry?id=${vital.id}`
})
}
const addFollowUp = (vital: VitalSign) => {
uni.navigateTo({
url: `/pages/ec/health/follow-up?vitalId=${vital.id}`
})
}
const handleAlert = (alert: HealthAlert) => {
uni.navigateTo({
url: `/pages/ec/health/alert-detail?id=${alert.id}`
})
}
const showElderActionSheet = () => {
const options = elderOptions.value.map(e => e.name)
uni.showActionSheet({
itemList: options,
success: (res:any) => {
selectedElderIndex.value = res.tapIndex
}
})
}
const showVitalTypeActionSheet = () => {
const options = vitalTypeOptions.value.map(v => v.label)
uni.showActionSheet({
itemList: options,
success: (res:any) => {
selectedVitalTypeIndex.value = res.tapIndex
}
})
}
const showTimeRangeActionSheet = () => {
const options = timeRangeOptions.value.map(t => t.label)
uni.showActionSheet({
itemList: options,
success: (res:any) => {
selectedTimeRangeIndex.value = res.tapIndex
}
})
}
// 兼容 margin 间距(替代 gap/伪类)
const showMeasurementMargin = (vital: VitalSign, type: string): string => {
// 只给前几个加 margin-right最后一个不加
let types = [] as string[]
if (vital.blood_pressure) types.push('blood_pressure')
if (vital.heart_rate) types.push('heart_rate')
if (vital.temperature) types.push('temperature')
if (vital.oxygen_saturation) types.push('oxygen_saturation')
if (vital.blood_sugar) types.push('blood_sugar')
const idx = types.indexOf(type)
return idx !== -1 && idx < types.length - 1 ? '15px' : '0'
}
</script>
<style lang="scss">
.health-monitoring {
padding: 20px;
background-color: #f5f5f5;
min-height: 100vh;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 15px;
color: white;
}
.header-title {
font-size: 24px;
font-weight: bold;
}
.header-actions {
display: flex;
flex-direction: row;
}
.action-btn {
padding: 8px 16px;
border-radius: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
background-color: rgba(255, 255, 255, 0.1);
color: white;
margin-right: 10px;
}
.action-btn:last-child {
margin-right: 0;
}
.btn-text {
font-size: 14px;
font-weight: 500;
}
.stats-section-flex {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-bottom: 30px;
}
.stat-card {
flex: 1;
min-width: 120px;
padding: 20px;
background-color: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
margin-right: 15px;
margin-bottom: 15px;
}
.stat-card:last-child {
margin-right: 0;
}
.stat-card-alert {
background: linear-gradient(135deg, #ffa726 0%, #ff9800 100%);
color: white;
}
.stat-card-urgent {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
color: white;
}
.stat-icon {
font-size: 24px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.1);
border-radius: 50%;
margin-right: 10px;
}
.stat-content {
flex: 1;
}
.stat-number {
font-size: 28px;
font-weight: bold;
display: block;
}
.stat-label {
font-size: 14px;
opacity: 0.7;
margin-top: 5px;
}
.filters-section {
background-color: white;
border-radius: 12px;
padding: 12px 10px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.filter-row-flex {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 8px;
}
.filter-row-flex:last-child {
margin-bottom: 0;
}
.filter-group {
flex: 1;
width: 120px;
min-width: 90px;
margin-right: 8px;
}
.filter-group:last-child {
margin-right: 0;
}
.filter-label {
font-size: 13px;
color: #666;
margin-bottom: 2px;
display: block;
}
.picker-btn {
background: none;
border: none;
padding: 0;
text-align: left;
}
.picker-text {
font-size: 13px;
color: #333;
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 6px;
background-color: #f9f9f9;
display: block;
}
.filter-toggles-flex {
display: flex;
flex-direction: row;
margin-right: 10px;
}
.toggle-btn {
padding: 6px 12px;
border-radius: 12px;
border: 1px solid #ddd;
background-color: #f9f9f9;
color: #666;
margin-right: 10px;
}
.toggle-btn:last-child {
margin-right: 0;
}
.toggle-btn.active {
background: #667eea;
color: white;
border: 1px solid #667eea;
}
.refresh-btn {
padding: 8px 16px;
border-radius: 20px;
border: 1px solid #52c41a;
background-color: #52c41a;
color: white;
}
.refresh-text {
font-size: 14px;
}
.vital-signs-section, .alerts-section {
margin-bottom: 30px;
}
.section-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.view-modes {
display: flex;
flex-direction: row;
}
.mode-btn {
padding: 6px 12px;
border-radius: 12px;
border: 1px solid #ddd;
background-color: #f9f9f9;
color: #666;
margin-right: 8px;
}
.mode-btn:last-child {
margin-right: 0;
}
.mode-btn.active {
background: #667eea;
color: white;
border: 1px solid #667eea;
}
.view-all-btn {
padding: 6px 12px;
border-radius: 15px;
border: 1px solid #ddd;
background-color: white;
color: #666;
}
.vital-signs-list, .alerts-list {
background-color: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.vital-sign-item {
padding: 20px;
border-bottom: 1px solid #f0f0f0;
}
.vital-sign-item:last-child {
border-bottom: none;
}
.abnormal {
background-color: #fff7e6;
border-left: 4px solid #faad14;
}
.critical {
background-color: #fff2f0;
border-left: 4px solid #ff4d4f;
}
.vital-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 15px;
}
.patient-info {
.patient-name {
font-size: 16px;
font-weight: 600;
color: #333;
display: block;
}
.record-time {
font-size: 14px;
color: #666;
margin-top: 5px;
display: block;
}
}
.vital-status {
display: flex;
gap: 8px;
}
.status-badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
}
.abnormal .badge-text {
color: #faad14;
}
.critical .badge-text {
color: #ff4d4f;
}
.vital-measurements {
display: flex;
flex-wrap: wrap;
margin-bottom: 15px;
}
.vital-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.recorded-by {
font-size: 14px;
color: #666;
}
.vital-actions {
display: flex;
gap: 8px;
}
.action-btn-small {
padding: 4px 8px;
border-radius: 12px;
border: 1px solid #ddd;
background-color: white;
color: #666;
}
.notes-text {
font-size: 14px;
color: #666;
font-style: italic;
}
.chart-section {
background-color: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.chart-filters {
display: flex;
margin-bottom: 20px;
flex-wrap: wrap;
}
.chart-type-btn {
padding: 6px 12px;
border-radius: 12px;
border: 1px solid #ddd;
background-color: #f9f9f9;
color: #666;
margin-right: 10px;
}
.chart-type-btn:last-child {
margin-right: 0;
}
.chart-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
display: block;
}
.chart-placeholder {
height: 300px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f9f9f9;
border-radius: 8px;
}
.placeholder-text {
color: #999;
font-size: 16px;
}
.alert-item {
padding: 15px;
border-bottom: 1px solid #f0f0f0;
border-left: 4px solid #ddd;
}
.alert-item:last-child {
border-bottom: none;
}
.high {
border-left-color: #faad14;
background-color: #fff7e6;
}
.critical {
border-left-color: #ff4d4f;
background-color: #fff2f0;
}
.alert-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.alert-title {
font-size: 14px;
font-weight: 600;
color: #333;
}
.alert-time {
font-size: 12px;
color: #666;
}
.alert-content {
.alert-description {
font-size: 14px;
color: #555;
display: block;
margin-bottom: 5px;
}
.alert-patient {
font-size: 12px;
color: #888;
}
}
.empty-state {
padding: 60px 20px;
text-align: center;
}
.empty-text {
font-size: 16px;
color: #999;
margin-bottom: 20px;
display: block;
}
.add-btn {
padding: 12px 24px;
border-radius: 20px;
border: none;
background-color: #1890ff;
color: white;
}
.btn-text {
font-size: 14px;
color: white;
}
</style>