Initial commit of akmon project

This commit is contained in:
2026-01-20 08:04:15 +08:00
commit 77a2bab985
1309 changed files with 343305 additions and 0 deletions

View File

@@ -0,0 +1,734 @@
<!-- 养老管理系统 - 老人管理页面 (简化版) -->
<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>