338 lines
11 KiB
Plaintext
338 lines
11 KiB
Plaintext
|
|
<template>
|
|
<view class="all-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">{{ serviceTypeLabel(record.service_type) }}</text>
|
|
<text class="record-time">{{ formatDateTime(record.created_at ?? '') }}</text>
|
|
</view>
|
|
<view class="record-content">
|
|
<text v-if="record.caregiver_name">护理员: {{ record.caregiver_name }}</text>
|
|
<text v-if="record.doctor_name">医生: {{ record.doctor_name }}</text>
|
|
<text v-if="record.meal_type">餐次: {{ record.meal_type }}</text>
|
|
<text v-if="record.activity_name">活动: {{ record.activity_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 AggregatedServiceRecord = {
|
|
id: string
|
|
service_type: 'nursing' | 'medical' | 'meal' | 'activity'
|
|
elder_id: string
|
|
elder_name?: string
|
|
caregiver_name?: string
|
|
doctor_name?: string
|
|
meal_type?: string
|
|
activity_name?: string
|
|
created_at: string
|
|
notes?: string
|
|
}
|
|
type Elder = { id: string, name: string }
|
|
type FilterOption = { value: string, label: string }
|
|
|
|
const records = ref<AggregatedServiceRecord[]>([])
|
|
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: 'medical', label: '医疗' },
|
|
{ value: 'meal', label: '餐饮' },
|
|
{ value: 'activity', 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 serviceTypeLabel = (type: string) => {
|
|
const map: Record<string, string> = {
|
|
nursing: '护理',
|
|
medical: '医疗',
|
|
meal: '餐饮',
|
|
activity: '活动'
|
|
}
|
|
return map[type] ?? type
|
|
}
|
|
|
|
const refreshData = () => { loadRecords(); loadElders(); }
|
|
|
|
const loadRecords = async () => {
|
|
try {
|
|
// 聚合查询:分别查四个表,合并后排序
|
|
const [nursing, medical, meal, activity] = await Promise.all([
|
|
supa.from('ec_care_records').select('id, elder_id, ec_care_records_elder_id_fkey(name), ec_care_records_caregiver_id_fkey(username), created_at, issues_notes', {}).order('created_at', { ascending: false }).limit(50).executeAs<any[]>(),
|
|
supa.from('ec_medical_records').select('id, elder_id, ec_medical_records_elder_id_fkey(name), doctor_id, created_at, diagnosis, notes', {}).order('created_at', { ascending: false }).limit(50).executeAs<any[]>(),
|
|
supa.from('ec_meal_records').select('id, elder_id, ec_meal_records_elder_id_fkey(name), meal_type, created_at, notes', {}).order('created_at', { ascending: false }).limit(50).executeAs<any[]>(),
|
|
supa.from('ec_activity_participations').select('id, elder_id, ec_activity_participations_elder_id_fkey(name), activity_id, created_at, behavior_notes', {}).order('created_at', { ascending: false }).limit(50).executeAs<any[]>()
|
|
])
|
|
const nList = (nursing.data ?? []).map(r => ({
|
|
id: r.id,
|
|
service_type: 'nursing',
|
|
elder_id: r.elder_id,
|
|
elder_name: r.ec_care_records_elder_id_fkey?.name,
|
|
caregiver_name: r.ec_care_records_caregiver_id_fkey?.username,
|
|
created_at: r.created_at,
|
|
notes: r.issues_notes
|
|
}))
|
|
const mList = (medical.data ?? []).map(r => ({
|
|
id: r.id,
|
|
service_type: 'medical',
|
|
elder_id: r.elder_id,
|
|
elder_name: r.ec_medical_records_elder_id_fkey?.name,
|
|
doctor_name: r.doctor_id, // 可进一步 join doctor name
|
|
created_at: r.created_at,
|
|
notes: r.diagnosis || r.notes
|
|
}))
|
|
const mealList = (meal.data ?? []).map(r => ({
|
|
id: r.id,
|
|
service_type: 'meal',
|
|
elder_id: r.elder_id,
|
|
elder_name: r.ec_meal_records_elder_id_fkey?.name,
|
|
meal_type: r.meal_type,
|
|
created_at: r.created_at,
|
|
notes: r.notes
|
|
}))
|
|
const aList = (activity.data ?? []).map(r => ({
|
|
id: r.id,
|
|
service_type: 'activity',
|
|
elder_id: r.elder_id,
|
|
elder_name: r.ec_activity_participations_elder_id_fkey?.name,
|
|
activity_name: r.activity_id, // 可进一步 join activity name
|
|
created_at: r.created_at,
|
|
notes: r.behavior_notes
|
|
}))
|
|
// 合并并按时间排序
|
|
const all = [...nList, ...mList, ...mealList, ...aList].sort((a, b) => b.created_at.localeCompare(a.created_at))
|
|
records.value = all
|
|
} 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: AggregatedServiceRecord) => {
|
|
// 可根据 service_type 跳转不同详情页
|
|
uni.navigateTo({ url: `/pages/ec/admin/service-record-detail?id=${record.id}&type=${record.service_type}` })
|
|
}
|
|
onMounted(() => { refreshData() })
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.all-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;
|
|
}
|
|
.notes {
|
|
color: #faad14;
|
|
}
|
|
.empty-state {
|
|
padding: 40px 0;
|
|
text-align: center;
|
|
}
|
|
.empty-text {
|
|
color: #999;
|
|
font-size: 16px;
|
|
}
|
|
</style>
|