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,303 @@
<!-- 服务记录页面 - 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>