304 lines
8.7 KiB
Plaintext
304 lines
8.7 KiB
Plaintext
<!-- 服务记录页面 - 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>
|