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

304 lines
8.7 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.
<!-- 服务记录页面 - uts-android 兼容版 -->
<template>
<view class="service-records">
<view class="header">
<text class="header-title">服务记录</text>
<button class="refresh-btn" @click="refreshData">
<text class="refresh-text">🔄 刷新</text>
</button>
</view>
<view class="filters-section">
<view class="filter-row">
<view class="filter-group">
<text class="filter-label">老人</text>
<button class="picker-btn" @click="showElderActionSheet">
<text class="picker-text">{{ selectedElder?.name ?? '全部' }}</text>
</button>
</view>
<view class="filter-group">
<text class="filter-label">服务类型</text>
<button class="picker-btn" @click="showTypeActionSheet">
<text class="picker-text">{{ selectedType?.label ?? '全部' }}</text>
</button>
</view>
<view class="filter-group">
<text class="filter-label">时间范围</text>
<button class="picker-btn" @click="showTimeRangeActionSheet">
<text class="picker-text">{{ selectedTimeRange?.label ?? '近7天' }}</text>
</button>
</view>
</view>
</view>
<scroll-view class="records-list" direction="vertical" :style="{ height: '500px' }">
<view v-for="record in filteredRecords" :key="record.id" class="record-item" @click="viewDetail(record)">
<view class="record-header">
<text class="elder-name">{{ record.elder_name ?? '未知' }}</text>
<text class="service-type">{{ record.service_type ?? '未知类型' }}</text>
<text class="record-time">{{ formatDateTime(record.created_at ?? '') }}</text>
</view>
<view class="record-content">
<text class="caregiver">护理员: {{ record.caregiver_name ?? '未分配' }}</text>
<text class="notes" v-if="record.notes">备注: {{ record.notes }}</text>
</view>
</view>
<view v-if="filteredRecords.length === 0" class="empty-state">
<text class="empty-text">暂无服务记录</text>
</view>
</scroll-view>
</view>
</template>
<script setup lang="uts">
import { ref, computed, onMounted } from 'vue'
import supa from '@/components/supadb/aksupainstance.uts'
import { formatDateTime as formatDateTimeUtil } from '../types.uts'
type ServiceRecord = {
id: string
task_id: string | null
elder_id: string
caregiver_id: string
elder_name?: string
caregiver_name?: string
start_time: string | null
end_time: string | null
actual_duration: number | null
care_content: string | null
elder_condition: string | null
issues_notes: string | null
photo_urls: string[] | null
status: string
rating: number | null
supervisor_notes: string | null
created_at: string
}
type Elder = { id: string, name: string }
type FilterOption = { value: string, label: string }
const records = ref<ServiceRecord[]>([])
const elders = ref<Elder[]>([])
const selectedElderIndex = ref<number>(-1)
const selectedTypeIndex = ref<number>(-1)
const selectedTimeRangeIndex = ref<number>(1)
const typeOptions = ref<FilterOption[]>([
{ value: 'all', label: '全部' },
{ value: 'nursing', label: '护理' },
{ value: 'meal', label: '餐饮' },
{ value: 'activity', label: '活动' },
{ value: 'cleaning', label: '清洁' }
])
const timeRangeOptions = ref<FilterOption[]>([
{ value: '3days', label: '近3天' },
{ value: '7days', label: '近7天' },
{ value: '30days', label: '近30天' }
])
const elderOptions = computed<Elder[]>(() => [ { id: 'all', name: '全部' }, ...elders.value ])
const selectedElder = computed(() => elderOptions.value[selectedElderIndex.value] ?? elderOptions.value[0])
const selectedType = computed(() => typeOptions.value[selectedTypeIndex.value] ?? typeOptions.value[0])
const selectedTimeRange = computed(() => timeRangeOptions.value[selectedTimeRangeIndex.value] ?? timeRangeOptions.value[1])
const filteredRecords = computed(() => {
let list = records.value
if (selectedElder.value.id !== 'all') {
list = list.filter(r => r.elder_id === selectedElder.value.id)
}
if (selectedType.value.value !== 'all') {
list = list.filter(r => r.service_type === selectedType.value.value)
}
// 时间范围
const now = new Date()
let startDate = new Date()
if (selectedTimeRange.value.value === '3days') startDate.setDate(now.getDate() - 3)
else if (selectedTimeRange.value.value === '7days') startDate.setDate(now.getDate() - 7)
else if (selectedTimeRange.value.value === '30days') startDate.setDate(now.getDate() - 30)
list = list.filter(r => r.created_at >= startDate.toISOString())
return list
})
const formatDateTime = (dt: string) => formatDateTimeUtil(dt)
const refreshData = () => { loadRecords(); loadElders(); }
const loadRecords = async () => {
try {
const result = await supa
.from('ec_care_records')
.select('id, elder_id, ec_care_records_elder_id_fkey(name), record_type, ec_care_records_caregiver_id_fkey(username), created_at,issues_notes, supervisor_notes', {})
.order('created_at', { ascending: false })
.limit(100)
.executeAs<ServiceRecord[]>()
if (result.error == null && result.data != null) {
records.value = result.data
}
} catch (e) { console.error('加载服务记录失败', e) }
}
const loadElders = async () => {
try {
const result = await supa
.from('ec_elders')
.select('id, name', {})
.eq('status', 'active')
.order('name', { ascending: true })
.executeAs<Elder[]>()
if (result.error == null && result.data != null) {
elders.value = result.data
}
} catch (e) { console.error('加载老人列表失败', e) }
}
const showElderActionSheet = () => {
const options = elderOptions.value.map(e => e.name)
uni.showActionSheet({
itemList: options,
success: (res:any) => { selectedElderIndex.value = res.tapIndex }
})
}
const showTypeActionSheet = () => {
const options = typeOptions.value.map(t => t.label)
uni.showActionSheet({
itemList: options,
success: (res:any) => { selectedTypeIndex.value = res.tapIndex }
})
}
const showTimeRangeActionSheet = () => {
const options = timeRangeOptions.value.map(t => t.label)
uni.showActionSheet({
itemList: options,
success: (res:any) => { selectedTimeRangeIndex.value = res.tapIndex }
})
}
const viewDetail = (record: ServiceRecord) => {
uni.navigateTo({ url: `/pages/ec/admin/service-record-detail?id=${record.id}` })
}
onMounted(() => { refreshData() })
</script>
<style lang="scss">
/* uts-android 兼容性重构:
1. 移除所有嵌套选择器、伪类(如 :last-child全部 class 扁平化。
2. 所有间距用 margin-right/margin-bottom 控制,禁止 gap、flex-wrap、嵌套。
3. 所有布局 display: flex禁止 grid、gap、伪类。
4. 组件间距、分隔线全部用 border/margin 控制。
*/
.service-records {
padding: 20px;
background: #f5f5f5;
min-height: 100vh;
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.header-title {
font-size: 22px;
font-weight: bold;
}
.refresh-btn {
padding: 8px 16px;
border-radius: 20px;
border: 1px solid #52c41a;
background-color: #52c41a;
color: white;
}
.filters-section {
background: #fff;
border-radius: 12px;
padding: 16px;
margin-bottom: 20px;
}
.filter-row {
display: flex;
flex-direction: row;
align-items: center;
}
.filter-group {
flex: 1;
margin-right: 15px;
}
.filter-group.is-last {
margin-right: 0;
}
.filter-label {
font-size: 14px;
color: #666;
margin-bottom: 6px;
display: block;
}
.picker-btn {
width: 180rpx;
background: none;
border: none;
padding: 0;
text-align: left;
}
.picker-text {
font-size: 14px;
color: #333;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 8px;
background: #f9f9f9;
display: block;
}
.records-list {
background: #fff;
border-radius: 12px;
min-height: 300px;
margin-bottom: 20px;
}
.record-item {
padding: 16px;
border-bottom: 1px solid #f0f0f0;
}
.record-item.is-last {
border-bottom: none;
}
.record-header {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 8px;
}
.elder-name {
font-size: 16px;
font-weight: bold;
color: #333;
margin-right: 10px;
}
.service-type {
font-size: 14px;
color: #1890ff;
margin-right: 10px;
}
.record-time {
font-size: 12px;
color: #999;
}
.record-content {
font-size: 14px;
color: #666;
margin-top: 4px;
display: flex;
flex-direction: row;
align-items: center;
}
.caregiver {
margin-right: 10px;
}
.notes {
color: #faad14;
}
.empty-state {
padding: 40px 0;
text-align: center;
}
.empty-text {
color: #999;
font-size: 16px;
}
</style>