Initial commit of akmon project
This commit is contained in:
837
pages/ec/family/care-records.uvue
Normal file
837
pages/ec/family/care-records.uvue
Normal file
@@ -0,0 +1,837 @@
|
||||
<template>
|
||||
<scroll-view class="care-records-container">
|
||||
<!-- Header with Filter -->
|
||||
<view class="records-header">
|
||||
<text class="header-title">护理记录</text>
|
||||
<text class="header-subtitle">{{ elderInfo.name }}的详细护理历史</text>
|
||||
<view class="filter-bar">
|
||||
<picker class="filter-picker" mode="selector" :value="selectedTimeFilter" :range="timeFilterLabels" @change="onTimeFilterChange">
|
||||
<view class="picker-item">
|
||||
<text class="picker-text">{{ timeFilterLabels[selectedTimeFilter] }}</text>
|
||||
<text class="picker-arrow">▼</text>
|
||||
</view>
|
||||
</picker>
|
||||
<picker class="filter-picker" mode="selector" :value="selectedTypeFilter" :range="typeFilterLabels" @change="onTypeFilterChange">
|
||||
<view class="picker-item">
|
||||
<text class="picker-text">{{ typeFilterLabels[selectedTypeFilter] }}</text>
|
||||
<text class="picker-arrow">▼</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Records Summary -->
|
||||
<view class="summary-cards">
|
||||
<view class="summary-card">
|
||||
<text class="summary-number">{{ recordsSummary.total }}</text>
|
||||
<text class="summary-label">总记录数</text>
|
||||
</view>
|
||||
<view class="summary-card">
|
||||
<text class="summary-number">{{ recordsSummary.today }}</text>
|
||||
<text class="summary-label">今日记录</text>
|
||||
</view>
|
||||
<view class="summary-card">
|
||||
<text class="summary-number">{{ recordsSummary.this_week }}</text>
|
||||
<text class="summary-label">本周记录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Records Timeline -->
|
||||
<view v-if="filteredRecords.length > 0" class="records-timeline">
|
||||
<view v-for="(dateGroup, dateKey) in groupedRecords" :key="dateKey" class="date-group">
|
||||
<view class="date-header">
|
||||
<text class="date-text">{{ formatDateHeader(dateKey) }}</text>
|
||||
<text class="date-count">{{ dateGroup.length }}条记录</text>
|
||||
</view>
|
||||
|
||||
<view v-for="record in dateGroup" :key="record.id" class="record-item" :class="getRecordTypeClass(record.type)">
|
||||
<view class="record-timeline-marker" :class="getRecordTypeClass(record.type)"></view>
|
||||
<view class="record-content-wrapper">
|
||||
<view class="record-header">
|
||||
<text class="record-type">{{ getCareRecordTypeText(record.type) }}</text>
|
||||
<text class="record-time">{{ formatTime(record.created_at) }}</text>
|
||||
</view>
|
||||
<view class="record-content">
|
||||
<text class="record-notes">{{ record.notes }}</text>
|
||||
<view class="record-details" v-if="record.details">
|
||||
<view class="detail-item" v-for="(value, key) in parseRecordDetails(record.details)" :key="key">
|
||||
<text class="detail-label">{{ getDetailLabel(key) }}:</text>
|
||||
<text class="detail-value">{{ value }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="record-footer">
|
||||
<text class="record-caregiver">护理员:{{ record.caregiver_name }}</text>
|
||||
<view class="record-actions">
|
||||
<button class="action-btn" @tap="viewRecordDetail(record)" v-if="record.type === 'vital_signs'">
|
||||
<text class="btn-text">查看详情</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Empty State -->
|
||||
<view v-else class="empty-records">
|
||||
<text class="empty-icon">📋</text>
|
||||
<text class="empty-title">暂无护理记录</text>
|
||||
<text class="empty-subtitle">选择不同的筛选条件查看记录</text>
|
||||
</view>
|
||||
|
||||
<!-- Record Detail Modal -->
|
||||
<view class="record-modal" v-if="showRecordDetail" @tap="hideRecordDetail">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">{{ getCareRecordTypeText(selectedRecord.type) }}</text>
|
||||
<button class="close-btn" @tap="hideRecordDetail">
|
||||
<text class="close-icon">×</text>
|
||||
</button>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="detail-section">
|
||||
<text class="section-title">基本信息</text>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">记录时间:</text>
|
||||
<text class="detail-value">{{ formatDateTime(selectedRecord.created_at) }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">护理员:</text>
|
||||
<text class="detail-value">{{ selectedRecord.caregiver_name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="detail-section" v-if="selectedRecord.type === 'vital_signs'">
|
||||
<text class="section-title">生命体征</text>
|
||||
<view class="vitals-grid" v-if="selectedRecord.vital_signs">
|
||||
<view class="vital-item" v-for="vital in selectedRecord.vital_signs" :key="vital.type">
|
||||
<text class="vital-label">{{ getVitalLabel(vital.type) }}</text>
|
||||
<text class="vital-value">{{ vital.value }}{{ getVitalUnit(vital.type) }}</text>
|
||||
<text class="vital-status" :class="vital.status">{{ getStatusText(vital.status) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="detail-section" v-if="selectedRecord.notes">
|
||||
<text class="section-title">护理记录</text>
|
||||
<text class="notes-content">{{ selectedRecord.notes }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.care-records-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.records-header {
|
||||
background: white;
|
||||
padding: 40rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.header-subtitle {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.filter-picker {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.picker-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.picker-arrow {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.summary-cards {
|
||||
display: flex;
|
||||
padding: 40rpx;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
flex: 1;
|
||||
background: white;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.summary-number {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #007AFF;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.records-timeline {
|
||||
padding: 0 40rpx 40rpx;
|
||||
}
|
||||
|
||||
.date-group {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.date-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 20rpx;
|
||||
background: white;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.date-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.date-count {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
background: #f0f0f0;
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
margin-bottom: 20rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.record-timeline-marker {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-right: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.record-timeline-marker.vital_signs {
|
||||
background: #ff6b6b;
|
||||
}
|
||||
|
||||
.record-timeline-marker.medication {
|
||||
background: #4ecdc4;
|
||||
}
|
||||
|
||||
.record-timeline-marker.nursing {
|
||||
background: #45b7d1;
|
||||
}
|
||||
|
||||
.record-timeline-marker.meal {
|
||||
background: #f9ca24;
|
||||
}
|
||||
|
||||
.record-timeline-marker.activity {
|
||||
background: #6c5ce7;
|
||||
}
|
||||
|
||||
.record-timeline-marker.incident {
|
||||
background: #fd79a8;
|
||||
}
|
||||
|
||||
.record-content-wrapper {
|
||||
flex: 1;
|
||||
background: white;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.record-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.record-type {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.record-content {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.record-notes {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
display: block;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.record-details {
|
||||
background: #f8f9fa;
|
||||
padding: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.detail-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
width: 160rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.record-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.record-caregiver {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.record-actions {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 12rpx 20rpx;
|
||||
background: #007AFF;
|
||||
color: white;
|
||||
border-radius: 16rpx;
|
||||
border: none;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.empty-records {
|
||||
padding: 120rpx 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
display: block;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 36rpx;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 12rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty-subtitle {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.record-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 24rpx;
|
||||
width: 90%;
|
||||
max-width: 800rpx;
|
||||
max-height: 80%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx 40rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 30rpx;
|
||||
background: #f0f0f0;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
font-size: 40rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 40rpx;
|
||||
max-height: 600rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.detail-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.detail-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.vitals-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.vital-item {
|
||||
flex: 1;
|
||||
min-width: 200rpx;
|
||||
background: #f8f9fa;
|
||||
padding: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vital-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.vital-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.vital-status {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 8rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.vital-status.normal {
|
||||
background: #e8f5e8;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.vital-status.warning {
|
||||
background: #fff3e0;
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.vital-status.danger {
|
||||
background: #ffebee;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.notes-content {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
background: #f8f9fa;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { formatDate, formatTime, formatDateTime, getStatusText } from '../types.uts'
|
||||
import type { ElderInfo, CareRecord, HealthVitals } from '../types.uts'
|
||||
|
||||
// 数据状态
|
||||
const elderInfo = ref<ElderInfo>({
|
||||
id: '',
|
||||
name: '',
|
||||
age: 0,
|
||||
gender: 'male',
|
||||
room_number: '',
|
||||
bed_number: '',
|
||||
admission_date: '',
|
||||
health_status: 'stable',
|
||||
care_level: 1,
|
||||
emergency_contact: '',
|
||||
profile_picture: '',
|
||||
family_contact: ''
|
||||
})
|
||||
|
||||
const allRecords = ref<CareRecord[]>([])
|
||||
const recordsSummary = ref({
|
||||
total: 0,
|
||||
today: 0,
|
||||
this_week: 0
|
||||
})
|
||||
|
||||
const selectedTimeFilter = ref(0)
|
||||
const selectedTypeFilter = ref(0)
|
||||
const showRecordDetail = ref(false)
|
||||
const selectedRecord = ref<CareRecord>({
|
||||
id: '',
|
||||
elder_id: '',
|
||||
type: 'nursing',
|
||||
notes: '',
|
||||
created_at: '',
|
||||
caregiver_id: '',
|
||||
caregiver_name: '',
|
||||
details: null,
|
||||
vital_signs: null
|
||||
})
|
||||
|
||||
// 筛选选项
|
||||
const timeFilters = [
|
||||
{ label: '全部', value: 'all' },
|
||||
{ label: '今天', value: 'today' },
|
||||
{ label: '本周', value: 'week' },
|
||||
{ label: '本月', value: 'month' }
|
||||
]
|
||||
|
||||
const typeFilters = [
|
||||
{ label: '全部类型', value: 'all' },
|
||||
{ label: '生命体征', value: 'vital_signs' },
|
||||
{ label: '用药记录', value: 'medication' },
|
||||
{ label: '护理服务', value: 'nursing' },
|
||||
{ label: '用餐记录', value: 'meal' },
|
||||
{ label: '活动记录', value: 'activity' },
|
||||
{ label: '事件记录', value: 'incident' }
|
||||
]
|
||||
|
||||
const timeFilterLabels = computed(() => timeFilters.map(f => f.label))
|
||||
const typeFilterLabels = computed(() => typeFilters.map(f => f.label))
|
||||
|
||||
// 筛选记录
|
||||
const filteredRecords = computed(() => {
|
||||
let records = [...allRecords.value]
|
||||
|
||||
// 时间筛选
|
||||
const timeFilter = timeFilters[selectedTimeFilter.value]
|
||||
if (timeFilter.value !== 'all') {
|
||||
const now = new Date()
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
|
||||
records = records.filter(record => {
|
||||
const recordDate = new Date(record.created_at)
|
||||
|
||||
switch (timeFilter.value) {
|
||||
case 'today':
|
||||
return recordDate >= today
|
||||
case 'week':
|
||||
const weekStart = new Date(today)
|
||||
weekStart.setDate(today.getDate() - today.getDay())
|
||||
return recordDate >= weekStart
|
||||
case 'month':
|
||||
const monthStart = new Date(today.getFullYear(), today.getMonth(), 1)
|
||||
return recordDate >= monthStart
|
||||
default:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 类型筛选
|
||||
const typeFilter = typeFilters[selectedTypeFilter.value]
|
||||
if (typeFilter.value !== 'all') {
|
||||
records = records.filter(record => record.type === typeFilter.value)
|
||||
}
|
||||
|
||||
return records
|
||||
})
|
||||
|
||||
// 按日期分组记录
|
||||
const groupedRecords = computed(() => {
|
||||
const groups: Record<string, CareRecord[]> = {}
|
||||
|
||||
filteredRecords.value.forEach(record => {
|
||||
const date = new Date(record.created_at).toISOString().split('T')[0]
|
||||
if (!groups[date]) {
|
||||
groups[date] = []
|
||||
}
|
||||
groups[date].push(record)
|
||||
})
|
||||
|
||||
// 按日期排序
|
||||
const sortedGroups: Record<string, CareRecord[]> = {}
|
||||
Object.keys(groups)
|
||||
.sort((a, b) => new Date(b).getTime() - new Date(a).getTime())
|
||||
.forEach(date => {
|
||||
// 每组内按时间排序
|
||||
sortedGroups[date] = groups[date].sort((a, b) =>
|
||||
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
)
|
||||
})
|
||||
|
||||
return sortedGroups
|
||||
})
|
||||
|
||||
// 辅助函数
|
||||
function getCareRecordTypeText(type: string): string {
|
||||
const typeMap = {
|
||||
'vital_signs': '生命体征',
|
||||
'medication': '用药记录',
|
||||
'nursing': '护理服务',
|
||||
'meal': '用餐记录',
|
||||
'activity': '活动记录',
|
||||
'incident': '事件记录'
|
||||
}
|
||||
return typeMap[type] || type
|
||||
}
|
||||
|
||||
function getRecordTypeClass(type: string): string {
|
||||
return type
|
||||
}
|
||||
|
||||
function getVitalLabel(type: string): string {
|
||||
const labels = {
|
||||
'heart_rate': '心率',
|
||||
'blood_pressure': '血压',
|
||||
'temperature': '体温',
|
||||
'blood_sugar': '血糖',
|
||||
'oxygen_saturation': '血氧'
|
||||
}
|
||||
return labels[type] || type
|
||||
}
|
||||
|
||||
function getVitalUnit(type: string): string {
|
||||
const units = {
|
||||
'heart_rate': 'bpm',
|
||||
'blood_pressure': 'mmHg',
|
||||
'temperature': '°C',
|
||||
'blood_sugar': 'mmol/L',
|
||||
'oxygen_saturation': '%'
|
||||
}
|
||||
return units[type] || ''
|
||||
}
|
||||
|
||||
function formatDateHeader(dateStr: string): string {
|
||||
const date = new Date(dateStr)
|
||||
const today = new Date()
|
||||
const yesterday = new Date(today)
|
||||
yesterday.setDate(today.getDate() - 1)
|
||||
|
||||
if (date.toDateString() === today.toDateString()) {
|
||||
return '今天'
|
||||
} else if (date.toDateString() === yesterday.toDateString()) {
|
||||
return '昨天'
|
||||
} else {
|
||||
return formatDate(dateStr)
|
||||
}
|
||||
}
|
||||
|
||||
function parseRecordDetails(details: string | null): Record<string, string> {
|
||||
if (!details) return {}
|
||||
|
||||
try {
|
||||
return JSON.parse(details)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function getDetailLabel(key: string): string {
|
||||
const labels = {
|
||||
'duration': '持续时间',
|
||||
'dosage': '剂量',
|
||||
'location': '位置',
|
||||
'temperature': '温度',
|
||||
'amount': '数量',
|
||||
'notes': '备注'
|
||||
}
|
||||
return labels[key] || key
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
function onTimeFilterChange(e: any) {
|
||||
selectedTimeFilter.value = e.detail.value
|
||||
}
|
||||
|
||||
function onTypeFilterChange(e: any) {
|
||||
selectedTypeFilter.value = e.detail.value
|
||||
}
|
||||
|
||||
function viewRecordDetail(record: CareRecord) {
|
||||
selectedRecord.value = record
|
||||
showRecordDetail.value = true
|
||||
}
|
||||
|
||||
function hideRecordDetail() {
|
||||
showRecordDetail.value = false
|
||||
}
|
||||
|
||||
// 数据加载
|
||||
async function loadElderInfo() {
|
||||
try {
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const elderId = currentPage.$route.query?.elder_id as string
|
||||
|
||||
if (!elderId) {
|
||||
uni.showToast({
|
||||
title: '缺少老人ID',
|
||||
icon: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const supa = (globalThis as any).supa
|
||||
const result = await supa.executeAs('get_elder_info', {
|
||||
elder_id: elderId
|
||||
})
|
||||
|
||||
if (result && result.length > 0) {
|
||||
elderInfo.value = result[0]
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载老人信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCareRecords() {
|
||||
try {
|
||||
const supa = (globalThis as any).supa
|
||||
const result = await supa.executeAs('get_elder_care_records', {
|
||||
elder_id: elderInfo.value.id,
|
||||
limit: 100
|
||||
})
|
||||
|
||||
if (result && result.length > 0) {
|
||||
allRecords.value = result
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载护理记录失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRecordsSummary() {
|
||||
try {
|
||||
const supa = (globalThis as any).supa
|
||||
const result = await supa.executeAs('get_care_records_summary', {
|
||||
elder_id: elderInfo.value.id
|
||||
})
|
||||
|
||||
if (result && result.length > 0) {
|
||||
recordsSummary.value = result[0]
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载记录统计失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
await loadElderInfo()
|
||||
if (elderInfo.value.id) {
|
||||
await Promise.all([
|
||||
loadCareRecords(),
|
||||
loadRecordsSummary()
|
||||
])
|
||||
}
|
||||
})
|
||||
</script>
|
||||
1527
pages/ec/family/communication.uvue
Normal file
1527
pages/ec/family/communication.uvue
Normal file
File diff suppressed because it is too large
Load Diff
829
pages/ec/family/dashboard.uvue
Normal file
829
pages/ec/family/dashboard.uvue
Normal file
@@ -0,0 +1,829 @@
|
||||
<!-- 养老管理系统 - 家属仪表板 (简化版) -->
|
||||
<template>
|
||||
<view class="family-dashboard">
|
||||
<view class="header">
|
||||
<text class="title">家属关怀</text>
|
||||
<text class="welcome">{{ familyName }},{{ currentTime }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 老人状态卡片 -->
|
||||
<view class="elder-status-card">
|
||||
<view class="elder-profile">
|
||||
<view class="elder-avatar">
|
||||
<image class="avatar-image" :src="elderInfo.profile_picture ?? ''" mode="aspectFill"
|
||||
v-if="elderInfo.profile_picture !== null" />
|
||||
<text class="avatar-fallback" v-else>{{ elderInfo.name.charAt(0) }}</text>
|
||||
</view>
|
||||
<view class="elder-basic">
|
||||
<text class="elder-name">{{ elderInfo.name }}</text>
|
||||
<text class="elder-info">{{ elderInfo.age }}岁 · {{ elderInfo.gender === 'male' ? '男' : '女' }}</text>
|
||||
<text class="elder-room">{{ elderInfo.room_number }}{{ elderInfo.bed_number }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="elder-health">
|
||||
<view class="health-status" :class="getHealthStatusClass(elderInfo.health_status)">
|
||||
<text class="status-text">{{ getHealthStatusText(elderInfo.health_status) }}</text>
|
||||
</view>
|
||||
<text class="last-update">最后更新:{{ formatDateTime(elderInfo.updated_at) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 今日护理概览 -->
|
||||
<view class="today-care-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">今日护理</text>
|
||||
<text class="section-more" @click="viewAllCareRecords">查看详情</text>
|
||||
</view>
|
||||
<view class="care-timeline">
|
||||
<view v-for="record in todayCareRecords" :key="record.id" class="timeline-item">
|
||||
<view class="timeline-dot" :class="record.record_type"></view>
|
||||
<view class="timeline-content">
|
||||
<text class="care-title">{{ record.description }}</text>
|
||||
<text class="care-time">{{ formatTime(record.created_at) }}</text>
|
||||
</view>
|
||||
<view class="care-type">
|
||||
<text class="type-text">{{ getRecordTypeText(record.record_type) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 健康监测 -->
|
||||
<view class="health-monitoring-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">健康监测</text>
|
||||
<text class="section-more" @click="viewHealthDetails">查看详情</text>
|
||||
</view>
|
||||
<view class="health-metrics">
|
||||
<view class="metric-item" v-for="vital in recentVitals" :key="vital.id">
|
||||
<view class="metric-icon">{{ getVitalIcon(vital.vital_type) }}</view>
|
||||
<view class="metric-info">
|
||||
<text class="metric-name">{{ getVitalName(vital.vital_type) }}</text>
|
||||
<text class="metric-value">{{ getVitalValue(vital) }}</text>
|
||||
<text class="metric-time">{{ formatDateTime(vital.measured_at) }}</text>
|
||||
</view>
|
||||
<view class="metric-status" :class="vital.is_abnormal ? 'abnormal' : 'normal'">
|
||||
<text class="status-text">{{ vital.is_abnormal ? '异常' : '正常' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 探访安排 -->
|
||||
<view class="visit-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">探访安排</text>
|
||||
<text class="section-more" @click="scheduleVisit">预约探访</text>
|
||||
</view>
|
||||
<view class="visit-info" v-if="nextVisit !== null">
|
||||
<view class="visit-item">
|
||||
<view class="visit-icon"></view>
|
||||
<view class="visit-details">
|
||||
<text class="visit-title">下次探访</text>
|
||||
<text class="visit-time">{{ formatDateTime(nextVisit.visit_time) }}</text>
|
||||
<text class="visit-note">{{ nextVisit.notes ?? '常规探访' }}</text>
|
||||
</view>
|
||||
<button class="visit-btn" @click="modifyVisit">修改</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="no-visit" v-else>
|
||||
<text class="no-visit-text">暂无探访安排</text>
|
||||
<button class="schedule-btn" @click="scheduleVisit">预约探访</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务费用 */
|
||||
<view class="billing-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">本月费用</text>
|
||||
<text class="section-more" @click="viewBillingDetails">查看账单</text>
|
||||
</view>
|
||||
<view class="billing-summary">
|
||||
<view class="billing-item">
|
||||
<text class="billing-label">护理费</text>
|
||||
<text class="billing-amount">¥{{ monthlyBilling.care_fee }}</text>
|
||||
</view>
|
||||
<view class="billing-item">
|
||||
<text class="billing-label">餐费</text>
|
||||
<text class="billing-amount">¥{{ monthlyBilling.meal_fee }}</text>
|
||||
</view>
|
||||
<view class="billing-item">
|
||||
<text class="billing-label">其他费用</text>
|
||||
<text class="billing-amount">¥{{ monthlyBilling.other_fee }}</text>
|
||||
</view>
|
||||
<view class="billing-total">
|
||||
<text class="total-label">本月总计</text>
|
||||
<text class="total-amount">¥{{ monthlyBilling.total_fee }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快速操作 -->
|
||||
<view class="quick-actions">
|
||||
<view class="action-item" @click="contactCaregiver">
|
||||
<view class="action-icon"></view>
|
||||
<text class="action-text">联系护理员</text>
|
||||
</view>
|
||||
<view class="action-item" @click="viewPhotos">
|
||||
<view class="action-icon"></view>
|
||||
<text class="action-text">生活照片</text>
|
||||
</view>
|
||||
<view class="action-item" @click="feedbackSuggestion">
|
||||
<view class="action-icon"></view>
|
||||
<text class="action-text">意见反馈</text>
|
||||
</view>
|
||||
<view class="action-item" @click="emergencyContact">
|
||||
<view class="action-icon"></view>
|
||||
<text class="action-text">紧急联系</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
import type { Elder, CareRecord, VitalSign } from '../types.uts'
|
||||
import { formatDateTime, formatTime, getHealthStatusText, getHealthStatusClass, getRecordTypeText } from '../types.uts'
|
||||
|
||||
// 响应式数据
|
||||
const familyName = ref<string>('家属')
|
||||
const currentTime = ref<string>('')
|
||||
|
||||
// 老人信息
|
||||
const elderInfo = ref<Elder>({
|
||||
id: '',
|
||||
name: '',
|
||||
age: 0,
|
||||
gender: '',
|
||||
room_number: '',
|
||||
bed_number: '',
|
||||
health_status: '',
|
||||
care_level: '',
|
||||
admission_date: '',
|
||||
profile_picture: null,
|
||||
emergency_contact: '',
|
||||
emergency_phone: '',
|
||||
status: '',
|
||||
created_at: '',
|
||||
updated_at: ''
|
||||
})
|
||||
|
||||
// 数据列表
|
||||
const todayCareRecords = ref<Array<CareRecord>>([])
|
||||
const recentVitals = ref<Array<VitalSign>>([])
|
||||
|
||||
// 探访信息
|
||||
const nextVisit = ref<any>(null)
|
||||
|
||||
// 费用信息
|
||||
const monthlyBilling = ref({
|
||||
care_fee: 0,
|
||||
meal_fee: 0,
|
||||
other_fee: 0,
|
||||
total_fee: 0
|
||||
})
|
||||
|
||||
// 更新当前时间
|
||||
const updateCurrentTime = () => {
|
||||
const now = new Date()
|
||||
const hours = now.getHours().toString().padStart(2, '0')
|
||||
const minutes = now.getMinutes().toString().padStart(2, '0')
|
||||
currentTime.value = `今天 ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
// 获取今天的时间范围
|
||||
const getTodayRange = () => {
|
||||
const today = new Date()
|
||||
const start = new Date(today.getFullYear(), today.getMonth(), today.getDate())
|
||||
const end = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1)
|
||||
return {
|
||||
start: start.toISOString(),
|
||||
end: end.toISOString()
|
||||
}
|
||||
}
|
||||
|
||||
// 加载老人基本信息
|
||||
const loadElderInfo = async () => {
|
||||
try {
|
||||
const elderId = 'elder-001' // 替换为实际的老人ID,可以从页面参数获取
|
||||
|
||||
const result = await supa
|
||||
.from('ec_elders')
|
||||
.select(`
|
||||
id,
|
||||
name,
|
||||
age,
|
||||
gender,
|
||||
room_number,
|
||||
bed_number,
|
||||
health_status,
|
||||
care_level,
|
||||
admission_date,
|
||||
profile_picture,
|
||||
emergency_contact,
|
||||
emergency_phone,
|
||||
status,
|
||||
created_at,
|
||||
updated_at
|
||||
`)
|
||||
.eq('id', elderId)
|
||||
.single()
|
||||
.executeAs<Elder>()
|
||||
|
||||
if (result.error === null && result.data !== null) {
|
||||
elderInfo.value = result.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载老人信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载今日护理记录
|
||||
const loadTodayCareRecords = async () => {
|
||||
try {
|
||||
const { start, end } = getTodayRange()
|
||||
const elderId = elderInfo.value.id
|
||||
|
||||
const result = await supa
|
||||
.from('ec_care_records')
|
||||
.select('id, description, record_type, created_at')
|
||||
.eq('elder_id', elderId)
|
||||
.gte('created_at', start)
|
||||
.lt('created_at', end)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(5)
|
||||
.executeAs<Array<CareRecord>>()
|
||||
|
||||
if (result.error === null && result.data !== null) {
|
||||
todayCareRecords.value = result.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载护理记录失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载最近生命体征
|
||||
const loadRecentVitals = async () => {
|
||||
try {
|
||||
const elderId = elderInfo.value.id
|
||||
|
||||
const result = await supa
|
||||
.from('ec_vital_signs')
|
||||
.select(`
|
||||
id,
|
||||
elder_id,
|
||||
elder_name,
|
||||
vital_type,
|
||||
systolic_pressure,
|
||||
diastolic_pressure,
|
||||
heart_rate,
|
||||
temperature,
|
||||
oxygen_saturation,
|
||||
glucose_level,
|
||||
measured_at,
|
||||
is_abnormal
|
||||
`)
|
||||
.eq('elder_id', elderId)
|
||||
.order('measured_at', { ascending: false })
|
||||
.limit(4)
|
||||
.executeAs<Array<VitalSign>>()
|
||||
|
||||
if (result.error === null && result.data !== null) {
|
||||
recentVitals.value = result.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载生命体征失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载下次探访安排
|
||||
const loadNextVisit = async () => {
|
||||
try {
|
||||
const elderId = elderInfo.value.id
|
||||
const now = new Date().toISOString()
|
||||
|
||||
const result = await supa
|
||||
.from('ec_visits')
|
||||
.select('id, visit_time, notes')
|
||||
.eq('elder_id', elderId)
|
||||
.gte('visit_time', now)
|
||||
.order('visit_time', { ascending: true })
|
||||
.limit(1)
|
||||
.executeAs<Array<any>>()
|
||||
|
||||
if (result.error === null && result.data !== null && result.data.length > 0) {
|
||||
nextVisit.value = result.data[0]
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载探访安排失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载本月费用
|
||||
const loadMonthlyBilling = async () => {
|
||||
try {
|
||||
// 模拟费用数据,实际应该从数据库加载
|
||||
monthlyBilling.value = {
|
||||
care_fee: 3500,
|
||||
meal_fee: 1200,
|
||||
other_fee: 300,
|
||||
total_fee: 5000
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载费用信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取生命体征图标
|
||||
const getVitalIcon = (vitalType: string): string => {
|
||||
switch (vitalType) {
|
||||
case 'blood_pressure': return ''
|
||||
case 'heart_rate': return ''
|
||||
case 'temperature': return '️'
|
||||
case 'oxygen': return ''
|
||||
case 'glucose': return ''
|
||||
default: return ''
|
||||
}
|
||||
}
|
||||
|
||||
// 获取生命体征名称
|
||||
const getVitalName = (vitalType: string): string => {
|
||||
switch (vitalType) {
|
||||
case 'blood_pressure': return '血压'
|
||||
case 'heart_rate': return '心率'
|
||||
case 'temperature': return '体温'
|
||||
case 'oxygen': return '血氧'
|
||||
case 'glucose': return '血糖'
|
||||
default: return vitalType
|
||||
}
|
||||
}
|
||||
|
||||
// 获取生命体征值
|
||||
const getVitalValue = (vital: VitalSign): string => {
|
||||
switch (vital.vital_type) {
|
||||
case 'blood_pressure':
|
||||
return `${vital.systolic_pressure ?? 0}/${vital.diastolic_pressure ?? 0} mmHg`
|
||||
case 'heart_rate':
|
||||
return `${vital.heart_rate ?? 0} 次/分`
|
||||
case 'temperature':
|
||||
return `${vital.temperature ?? 0}°C`
|
||||
case 'oxygen':
|
||||
return `${vital.oxygen_saturation ?? 0}%`
|
||||
case 'glucose':
|
||||
return `${vital.glucose_level ?? 0} mmol/L`
|
||||
default:
|
||||
return '未知'
|
||||
}
|
||||
}
|
||||
|
||||
// 导航和操作函数
|
||||
const viewAllCareRecords = () => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/ec/family/care-records?elderId=${elderInfo.value.id}`
|
||||
})
|
||||
}
|
||||
|
||||
const viewHealthDetails = () => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/ec/family/health-monitoring?elderId=${elderInfo.value.id}`
|
||||
})
|
||||
}
|
||||
|
||||
const scheduleVisit = () => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/ec/family/schedule-visit?elderId=${elderInfo.value.id}`
|
||||
})
|
||||
}
|
||||
|
||||
const modifyVisit = () => {
|
||||
if (nextVisit.value !== null) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/ec/family/modify-visit?visitId=${nextVisit.value.id}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const viewBillingDetails = () => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/ec/family/billing?elderId=${elderInfo.value.id}`
|
||||
})
|
||||
}
|
||||
|
||||
// 快速操作
|
||||
const contactCaregiver = () => {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: '13800138000'
|
||||
})
|
||||
}
|
||||
|
||||
const viewPhotos = () => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/ec/family/photos?elderId=${elderInfo.value.id}`
|
||||
})
|
||||
}
|
||||
|
||||
const feedbackSuggestion = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/ec/family/feedback'
|
||||
})
|
||||
}
|
||||
|
||||
const emergencyContact = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: ['联系护理员', '联系医生', '联系管理员', '拨打急救电话'],
|
||||
success: (res) => {
|
||||
switch (res.tapIndex) {
|
||||
case 0:
|
||||
uni.makePhoneCall({ phoneNumber: '13800138000' })
|
||||
break
|
||||
case 1:
|
||||
uni.makePhoneCall({ phoneNumber: '13800138001' })
|
||||
break
|
||||
case 2:
|
||||
uni.makePhoneCall({ phoneNumber: '13800138002' })
|
||||
break
|
||||
case 3:
|
||||
uni.makePhoneCall({ phoneNumber: '120' })
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
updateCurrentTime()
|
||||
loadElderInfo().then(() => {
|
||||
loadTodayCareRecords()
|
||||
loadRecentVitals()
|
||||
loadNextVisit()
|
||||
})
|
||||
loadMonthlyBilling()
|
||||
|
||||
// 定时更新时间
|
||||
setInterval(() => {
|
||||
updateCurrentTime()
|
||||
}, 60000)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* uts-android 兼容性重构:
|
||||
1. 移除所有嵌套选择器、伪类(如 :last-child),全部 class 扁平化。
|
||||
2. 所有间距用 margin-right/margin-bottom 控制,禁止 gap、flex-wrap、嵌套。
|
||||
3. 所有布局 display: flex,禁止 grid、gap、伪类。
|
||||
4. 组件间距、分隔线全部用 border/margin 控制。
|
||||
*/
|
||||
.family-dashboard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.welcome {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.elder-status-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.elder-profile {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.elder-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 40px;
|
||||
margin-right: 20px;
|
||||
overflow: hidden;
|
||||
background-color: #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.avatar-fallback {
|
||||
font-size: 32px;
|
||||
color: #666;
|
||||
font-weight: bold;
|
||||
}
|
||||
.elder-basic {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.elder-name {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.elder-info {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.elder-room {
|
||||
font-size: 16px;
|
||||
color: #1890ff;
|
||||
}
|
||||
.elder-health {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.health-status {
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.health-excellent {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
.health-good {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
.health-fair {
|
||||
background-color: #fff7e6;
|
||||
color: #d48806;
|
||||
}
|
||||
.health-poor {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
.last-update {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
.today-care-section,
|
||||
.health-monitoring-section,
|
||||
.visit-section,
|
||||
.billing-section {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.section-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.section-more {
|
||||
font-size: 14px;
|
||||
color: #1890ff;
|
||||
}
|
||||
.timeline-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.timeline-item.is-last {
|
||||
border-bottom: none;
|
||||
}
|
||||
.timeline-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
margin-right: 15px;
|
||||
background-color: #1890ff;
|
||||
}
|
||||
.timeline-dot.medication {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
.timeline-dot.hygiene {
|
||||
background-color: #722ed1;
|
||||
}
|
||||
.timeline-dot.nutrition {
|
||||
background-color: #fa8c16;
|
||||
}
|
||||
.timeline-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.care-title {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.care-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
.care-type {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.type-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
.metric-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.metric-item.is-last {
|
||||
border-bottom: none;
|
||||
}
|
||||
.metric-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.metric-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.metric-name {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.metric-value {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #1890ff;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.metric-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
.metric-status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.metric-status.normal {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
.metric-status.abnormal {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
.visit-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
background-color: #f0f9ff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.visit-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.visit-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.visit-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.visit-time {
|
||||
font-size: 14px;
|
||||
color: #1890ff;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.visit-note {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
.visit-btn {
|
||||
background-color: #1890ff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.no-visit {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
}
|
||||
.no-visit-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.schedule-btn {
|
||||
background-color: #1890ff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.billing-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.billing-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.billing-item.is-last {
|
||||
border-bottom: none;
|
||||
}
|
||||
.billing-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.billing-amount {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
.billing-total {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 0 10px;
|
||||
border-top: 2px solid #f0f0f0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.total-label {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
.total-amount {
|
||||
font-size: 20px;
|
||||
color: #ff4d4f;
|
||||
font-weight: bold;
|
||||
}
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
}
|
||||
.action-item {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 15px 10px;
|
||||
}
|
||||
.action-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.action-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
1010
pages/ec/family/elder-status.uvue
Normal file
1010
pages/ec/family/elder-status.uvue
Normal file
File diff suppressed because it is too large
Load Diff
28
pages/ec/family/health-monitoring.uvue
Normal file
28
pages/ec/family/health-monitoring.uvue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="health-monitoring-page">
|
||||
<text class="title">健康监测</text>
|
||||
<view class="empty">暂无数据</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 健康监测页面骨架
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.health-monitoring-page {
|
||||
padding: 30px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.empty {
|
||||
color: #aaa;
|
||||
text-align: center;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
||||
28
pages/ec/family/schedule-visit.uvue
Normal file
28
pages/ec/family/schedule-visit.uvue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="schedule-visit-page">
|
||||
<text class="title">预约探访</text>
|
||||
<view class="empty">暂无数据</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 预约探访页面骨架
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.schedule-visit-page {
|
||||
padding: 30px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.empty {
|
||||
color: #aaa;
|
||||
text-align: center;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user