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

735 lines
16 KiB
Plaintext
Raw 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.
<!-- 养老管理系统 - 老人管理页面 (简化版) -->
<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>