735 lines
16 KiB
Plaintext
735 lines
16 KiB
Plaintext
<!-- 养老管理系统 - 老人管理页面 (简化版) -->
|
||
<template>
|
||
<view class="elder-management">
|
||
<!-- 顶部搜索和操作区 -->
|
||
<view class="header-section">
|
||
<view class="search-container">
|
||
<input class="search-input" placeholder="搜索老人姓名、房间号..." v-model="searchKeyword" @input="handleSearch" />
|
||
<view class="search-icon"></view>
|
||
</view>
|
||
<view class="header-actions">
|
||
<button class="action-btn primary" @click="addNewElder">
|
||
<text class="btn-icon">➕</text>
|
||
<text class="btn-text">新增老人</text>
|
||
</button>
|
||
<button class="action-btn secondary" @click="exportElders">
|
||
<text class="btn-icon"></text>
|
||
<text class="btn-text">导出</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 统计卡片 -->
|
||
<view class="stats-container">
|
||
<view class="stat-card">
|
||
<view class="stat-number">{{ totalElders }}</view>
|
||
<view class="stat-label">总入住</view>
|
||
<view class="stat-trend positive">+{{ newEldersThisMonth }}</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-number">{{ selfCareElders }}</view>
|
||
<view class="stat-label">自理老人</view>
|
||
<view class="stat-percent">{{ selfCarePercent }}%</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-number">{{ assistedCareElders }}</view>
|
||
<view class="stat-label">半护理</view>
|
||
<view class="stat-percent">{{ assistedCarePercent }}%</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-number">{{ fullCareElders }}</view>
|
||
<view class="stat-label">全护理</view>
|
||
<view class="stat-percent">{{ fullCarePercent }}%</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 筛选器 -->
|
||
<view class="filter-container">
|
||
<scroll-view class="filter-scroll" direction="horizontal" :show-scrollbar="false">
|
||
<view class="filter-item" :class="{ 'is-active': selectedCareLevel == 'all' }" @click="filterByCareLevel('all')">
|
||
全部
|
||
</view>
|
||
<view class="filter-item" :class="{ 'is-active': selectedCareLevel == 'self_care' }" @click="filterByCareLevel('self_care')">
|
||
自理
|
||
</view>
|
||
<view class="filter-item" :class="{ 'is-active': selectedCareLevel == 'assisted' }" @click="filterByCareLevel('assisted')">
|
||
半护理
|
||
</view>
|
||
<view class="filter-item" :class="{ 'is-active': selectedCareLevel == 'full_care' }" @click="filterByCareLevel('full_care')">
|
||
全护理
|
||
</view>
|
||
<view class="filter-item" :class="{ 'is-active': selectedCareLevel == 'dementia' }" @click="filterByCareLevel('dementia')">
|
||
失智护理
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 老人列表 -->
|
||
<view class="elders-section">
|
||
<scroll-view class="elders-list" direction="vertical" :refresher-enabled="true"
|
||
:refresher-triggered="isRefreshing" @refresherrefresh="refreshElders">
|
||
<view class="elder-card" v-for="elder in filteredElders" :key="elder.id" @click="viewElderDetail(elder)">
|
||
<view class="elder-header">
|
||
<view class="elder-avatar">
|
||
<image class="avatar-image" :src="elder.profile_picture ?? ''" mode="aspectFill"
|
||
@error="handleAvatarError" v-if="elder.profile_picture !== null" />
|
||
<text class="avatar-fallback" v-else>{{ elder.name.charAt(0) }}</text>
|
||
</view>
|
||
<view class="elder-basic">
|
||
<text class="elder-name">{{ elder.name }}</text>
|
||
<text class="elder-info">{{ elder.age ?? 0 }}岁 · {{ elder.gender == 'male' ? '男' : '女' }}</text>
|
||
<text class="elder-room">{{ (elder.room_number ?? '') + (elder.bed_number ?? '') }}</text>
|
||
</view>
|
||
<view class="elder-status">
|
||
<view class="health-status" :class="getHealthStatusClass(elder.health_status)">
|
||
<text class="status-text">{{ getHealthStatusText(elder.health_status) }}</text>
|
||
</view>
|
||
<view class="care-level" :class="getCareLevelClass(elder.care_level)">
|
||
<text class="level-text">{{ getCareLevelText(elder.care_level) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="elder-details">
|
||
<view class="detail-row">
|
||
<view class="detail-item">
|
||
<text class="detail-icon"></text>
|
||
<text class="detail-text">入住:{{ formatDate(elder.admission_date) }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-icon">⚕️</text>
|
||
<text class="detail-text">护理员:待分配</text>
|
||
</view>
|
||
</view>
|
||
<view class="detail-row">
|
||
<view class="detail-item">
|
||
<text class="detail-icon"></text>
|
||
<text class="detail-text">联系人:{{ elder.emergency_contact ?? '无' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="elder-actions">
|
||
<button class="action-btn small" @click.stop="viewHealthRecord(elder)">
|
||
<text class="btn-text">健康记录</text>
|
||
</button>
|
||
<button class="action-btn small" @click.stop="viewCareRecord(elder)">
|
||
<text class="btn-text">护理记录</text>
|
||
</button>
|
||
<button class="action-btn small primary" @click.stop="editElder(elder)">
|
||
<text class="btn-text">编辑</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 空状态 -->
|
||
<view class="empty-state" v-if="filteredElders.length == 0 && !isLoading">
|
||
<text class="empty-icon"></text>
|
||
<text class="empty-title">暂无老人信息</text>
|
||
<text class="empty-description">{{ getEmptyStateText() }}</text>
|
||
<button class="empty-action" @click="addNewElder">添加第一位老人</button>
|
||
</view>
|
||
|
||
<!-- 加载状态 -->
|
||
<view class="loading-state" v-if="isLoading">
|
||
<text class="loading-text">加载中...</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 浮动操作按钮 -->
|
||
<view class="fab-container">
|
||
<view class="fab" @click="quickActions">
|
||
<text class="fab-icon">⚡</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted, computed } from 'vue'
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
|
||
import type { Elder } from '../types.uts'
|
||
import { formatDate, getCareLevelText, getHealthStatusText } from '../types.uts'
|
||
|
||
// 响应式数据
|
||
const elders = ref<Array<Elder>>([])
|
||
const filteredElders = ref<Array<Elder>>([])
|
||
const searchKeyword = ref<string>('')
|
||
const selectedCareLevel = ref<string>('all')
|
||
const isLoading = ref<boolean>(false)
|
||
const isRefreshing = ref<boolean>(false)
|
||
|
||
// 统计数据
|
||
const totalElders = ref<number>(0)
|
||
const selfCareElders = ref<number>(0)
|
||
const assistedCareElders = ref<number>(0)
|
||
const fullCareElders = ref<number>(0)
|
||
const newEldersThisMonth = ref<number>(0)
|
||
|
||
// 计算百分比
|
||
const selfCarePercent = computed(() => {
|
||
return totalElders.value > 0 ? Math.round((selfCareElders.value / totalElders.value) * 100) : 0
|
||
})
|
||
|
||
const assistedCarePercent = computed(() => {
|
||
return totalElders.value > 0 ? Math.round((assistedCareElders.value / totalElders.value) * 100) : 0
|
||
})
|
||
|
||
const fullCarePercent = computed(() => {
|
||
return totalElders.value > 0 ? Math.round((fullCareElders.value / totalElders.value) * 100) : 0
|
||
})
|
||
|
||
// 加载老人数据
|
||
const loadElders = async () => {
|
||
try {
|
||
isLoading.value = true
|
||
|
||
const result = await supa
|
||
.from('ec_elders')
|
||
.select(`
|
||
id,
|
||
name,
|
||
age,
|
||
gender,
|
||
room_number,
|
||
bed_number,
|
||
health_status,
|
||
care_level,
|
||
profile_picture,
|
||
emergency_contact,
|
||
emergency_phone,
|
||
admission_date,
|
||
status
|
||
`)
|
||
.eq('status', 'active')
|
||
.order('created_at', { ascending: false })
|
||
.executeAs<Array<Elder>>()
|
||
|
||
if (result.error == null && result.data !== null) {
|
||
elders.value = result.data
|
||
applyFilters()
|
||
updateStatistics()
|
||
}
|
||
} catch (error) {
|
||
console.error('加载老人数据失败:', error)
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 更新统计数据
|
||
const updateStatistics = () => {
|
||
totalElders.value = elders.value.length
|
||
|
||
selfCareElders.value = elders.value.filter(elder => elder.care_level == 'self_care').length
|
||
assistedCareElders.value = elders.value.filter(elder => elder.care_level == 'assisted').length
|
||
fullCareElders.value = elders.value.filter(elder => elder.care_level == 'full_care').length
|
||
|
||
// 计算本月新增老人数
|
||
const thisMonth = new Date()
|
||
thisMonth.setDate(1)
|
||
thisMonth.setHours(0, 0, 0, 0)
|
||
|
||
newEldersThisMonth.value = elders.value.filter(elder => {
|
||
const admissionDate = elder.admission_date
|
||
if (admissionDate !== '') {
|
||
const admission = new Date(admissionDate)
|
||
return admission >= thisMonth
|
||
}
|
||
return false
|
||
}).length
|
||
}
|
||
|
||
// 应用筛选
|
||
const applyFilters = () => {
|
||
let filtered = elders.value
|
||
|
||
// 护理等级筛选
|
||
if (selectedCareLevel.value !== 'all') {
|
||
filtered = filtered.filter(elder => elder.care_level == selectedCareLevel.value)
|
||
}
|
||
|
||
// 搜索关键词筛选
|
||
if (searchKeyword.value !== '') {
|
||
const keyword = searchKeyword.value.toLowerCase()
|
||
filtered = filtered.filter(elder => {
|
||
const name = elder.name.toLowerCase()
|
||
const roomNumber = elder.room_number ?? ''
|
||
return name.includes(keyword) || roomNumber.includes(keyword)
|
||
})
|
||
}
|
||
|
||
filteredElders.value = filtered
|
||
}
|
||
|
||
// 搜索处理
|
||
const handleSearch = () => {
|
||
applyFilters()
|
||
}
|
||
|
||
// 护理等级筛选
|
||
const filterByCareLevel = (level: string) => {
|
||
selectedCareLevel.value = level
|
||
applyFilters()
|
||
}
|
||
|
||
// 刷新数据
|
||
const refreshElders = async () => {
|
||
isRefreshing.value = true
|
||
await loadElders()
|
||
isRefreshing.value = false
|
||
}
|
||
|
||
// 查看老人详情
|
||
const viewElderDetail = (elder: Elder) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/elders/detail?id=${elder.id}`
|
||
})
|
||
}
|
||
|
||
// 查看健康记录
|
||
const viewHealthRecord = (elder: Elder) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/health/records?elderId=${elder.id}`
|
||
})
|
||
}
|
||
|
||
// 查看护理记录
|
||
const viewCareRecord = (elder: Elder) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/care/records?elderId=${elder.id}`
|
||
})
|
||
}
|
||
|
||
// 编辑老人信息
|
||
const editElder = (elder: Elder) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/elders/edit?id=${elder.id}`
|
||
})
|
||
}
|
||
|
||
// 新增老人
|
||
const addNewElder = () => {
|
||
uni.navigateTo({
|
||
url: '/pages/ec/admin/elder-form'
|
||
})
|
||
}
|
||
|
||
// 导出数据
|
||
const exportElders = () => {
|
||
uni.showToast({
|
||
title: '导出功能开发中',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
|
||
// 快速操作
|
||
const quickActions = () => {
|
||
uni.showActionSheet({
|
||
itemList: ['批量操作', '数据同步', '生成报表'],
|
||
success: (res) => {
|
||
console.log('选择了第' + (res.tapIndex + 1) + '个操作')
|
||
}
|
||
})
|
||
}
|
||
|
||
// 头像错误处理
|
||
const handleAvatarError = () => {
|
||
// 头像加载失败时的处理
|
||
}
|
||
|
||
// 获取空状态文本
|
||
const getEmptyStateText = (): string => {
|
||
if (searchKeyword.value !== '') {
|
||
return '没有找到匹配的老人信息'
|
||
}
|
||
if (selectedCareLevel.value !== 'all') {
|
||
return `没有${getCareLevelText(selectedCareLevel.value)}的老人`
|
||
}
|
||
return '还没有老人入住,点击下方按钮添加第一位老人'
|
||
}
|
||
|
||
// 生命周期
|
||
onMounted(() => {
|
||
loadElders()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.elder-management {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 100vh;
|
||
background-color: #f5f5f5;
|
||
padding: 20px;
|
||
}
|
||
|
||
/* 头部区域 */
|
||
.header-section {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.search-container {
|
||
flex: 1;
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.search-input {
|
||
width: 100%;
|
||
height: 40px;
|
||
border: 1px solid #d9d9d9;
|
||
border-radius: 6px;
|
||
padding: 0 40px 0 15px;
|
||
font-size: 14px;
|
||
background-color: #fff;
|
||
}
|
||
|
||
.search-icon {
|
||
font-size: 16px;
|
||
color: #999;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
}
|
||
|
||
.action-btn {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 10px 15px;
|
||
border-radius: 6px;
|
||
border: none;
|
||
margin-left: 10px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background-color: #1890ff;
|
||
color: #fff;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background-color: #fff;
|
||
color: #666;
|
||
border: 1px solid #d9d9d9;
|
||
}
|
||
|
||
.btn-icon {
|
||
margin-right: 5px;
|
||
}
|
||
|
||
/* 统计卡片 */
|
||
.stats-container {
|
||
display: flex;
|
||
flex-direction: row;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.stat-card {
|
||
flex: 1;
|
||
background-color: #fff;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin-right: 15px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-card.is-last {
|
||
margin-right: 0;
|
||
}
|
||
|
||
.stat-number {
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.stat-trend {
|
||
font-size: 12px;
|
||
color: #52c41a;
|
||
}
|
||
|
||
.stat-percent {
|
||
font-size: 12px;
|
||
color: #1890ff;
|
||
}
|
||
|
||
/* 筛选器 */
|
||
.filter-container {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.filter-scroll {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
height: 50px;
|
||
}
|
||
|
||
.filter-item {
|
||
display: inline-block;
|
||
padding: 8px 16px;
|
||
margin-right: 10px;
|
||
background-color: #fff;
|
||
border: 1px solid #d9d9d9;
|
||
border-radius: 20px;
|
||
font-size: 14px;
|
||
color: #666;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.filter-item.is-active {
|
||
background-color: #1890ff;
|
||
color: #fff;
|
||
border-color: #1890ff;
|
||
}
|
||
|
||
/* 老人列表 */
|
||
.elders-section {
|
||
flex: 1;
|
||
}
|
||
|
||
.elders-list {
|
||
height: 100%;
|
||
}
|
||
|
||
.elder-card {
|
||
background-color: #fff;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin-bottom: 15px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.elder-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.elder-avatar {
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 30px;
|
||
margin-right: 15px;
|
||
overflow: hidden;
|
||
background-color: #f0f0f0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.avatar-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.avatar-fallback {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #666;
|
||
}
|
||
|
||
.elder-basic {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.elder-name {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.elder-info {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-bottom: 3px;
|
||
}
|
||
|
||
.elder-room {
|
||
font-size: 14px;
|
||
color: #1890ff;
|
||
}
|
||
|
||
.elder-status {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.health-status {
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.care-level {
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.care-self {
|
||
background-color: #f6ffed;
|
||
color: #52c41a;
|
||
}
|
||
|
||
.care-assisted {
|
||
background-color: #fff7e6;
|
||
color: #d48806;
|
||
}
|
||
|
||
.care-full {
|
||
background-color: #fff2f0;
|
||
color: #ff4d4f;
|
||
}
|
||
|
||
/* 详细信息 */
|
||
.elder-details {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.detail-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.detail-item {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.detail-icon {
|
||
margin-right: 8px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.detail-text {
|
||
font-size: 13px;
|
||
color: #666;
|
||
}
|
||
|
||
/* 操作按钮 */
|
||
.elder-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.btn-small {
|
||
padding: 6px 12px;
|
||
font-size: 12px;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
/* 空状态 */
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 60px 20px;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 64px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.empty-title {
|
||
font-size: 18px;
|
||
color: #333;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.empty-description {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.empty-action {
|
||
background-color: #1890ff;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 12px 24px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 加载状态 */
|
||
.loading-state {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
}
|
||
|
||
.loading-text {
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
|
||
/* 浮动按钮 */
|
||
.fab-container {
|
||
position: fixed;
|
||
right: 20px;
|
||
bottom: 20px;
|
||
}
|
||
|
||
.fab {
|
||
width: 56px;
|
||
height: 56px;
|
||
border-radius: 28px;
|
||
background-color: #1890ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||
}
|
||
|
||
.fab-icon {
|
||
color: #fff;
|
||
font-size: 24px;
|
||
}
|
||
</style>
|