pet/pages/pets/pet-records.vue

588 lines
16 KiB
Vue

<template>
<view class="pet-records-container">
<u-navbar :title="`${petInfo.name}的记录`" left-icon="arrow-left" @left-click="goBack">
<template #right>
<u-icon name="plus-circle" size="20" @click="addRecord"></u-icon>
</template>
</u-navbar>
<!-- 筛选栏 -->
<view class="filter-bar">
<u-tabs
:list="filterTabs"
v-model="activeFilter"
@change="onFilterChange"
:scrollable="true"
active-color="#ff6b6b"
inactive-color="#666666"
></u-tabs>
</view>
<!-- 记录列表 -->
<scroll-view class="records-scroll" scroll-y @scrolltolower="loadMoreRecords">
<view class="records-list">
<!-- 时间分组 -->
<view v-for="group in groupedRecords" :key="group.date" class="record-group">
<view class="group-header">
<u-text :text="group.date" size="14" color="#999" bold></u-text>
<view class="group-line"></view>
</view>
<!-- 记录项 -->
<view v-for="record in group.records" :key="record.id" class="record-item" @click="viewRecordDetail(record)">
<view class="record-icon">
<u-icon :name="getCategoryIcon(record.category)" size="20" :color="getCategoryColor(record.category)"></u-icon>
</view>
<view class="record-content">
<view class="record-header">
<u-text :text="getCategoryName(record.category)" size="14" bold></u-text>
<u-text :text="formatTime(record.recordTime)" size="12" color="#999"></u-text>
</view>
<view class="record-text">
<u-text :text="record.content" size="13" color="#666" :lines="2"></u-text>
</view>
<!-- 记录详情标签 -->
<view class="record-tags" v-if="getRecordTags(record).length > 0">
<u-tag
v-for="tag in getRecordTags(record)"
:key="tag"
:text="tag"
type="info"
size="mini"
></u-tag>
</view>
<!-- 图片预览 -->
<view class="record-photos" v-if="record.photos && record.photos.length > 0">
<u-image
v-for="(photo, index) in record.photos.slice(0, 3)"
:key="index"
:src="photo"
width="40px"
height="40px"
border-radius="4px"
></u-image>
<view class="more-photos" v-if="record.photos.length > 3">
<u-text :text="`+${record.photos.length - 3}`" size="12" color="#999"></u-text>
</view>
</view>
</view>
<!-- 分享状态 -->
<view class="record-share">
<u-icon :name="getShareIcon(record.shareLevel)" size="16" :color="getShareColor(record.shareLevel)"></u-icon>
</view>
</view>
</view>
<!-- 加载更多 -->
<view class="load-more" v-if="hasMoreRecords">
<u-loading-icon v-if="loadingMore"></u-loading-icon>
<u-text text="上拉加载更多" type="tips" size="12" v-else></u-text>
</view>
<!-- 空状态 -->
<u-empty v-if="recordsList.length === 0 && !loadingMore" mode="data" text="还没有记录哦">
<template #bottom>
<u-button type="primary" text="添加第一条记录" @click="addRecord"></u-button>
</template>
</u-empty>
</view>
</scroll-view>
<!-- 浮动添加按钮 -->
<view class="fab-button" @click="addRecord">
<u-icon name="plus" size="24" color="#ffffff"></u-icon>
</view>
</view>
</template>
<script>
import { ref, reactive, computed, onMounted } from 'vue'
export default {
name: 'PetRecords',
setup() {
// 响应式数据
const state = reactive({
petId: '',
petInfo: {},
recordsList: [],
activeFilter: 0,
hasMoreRecords: true,
loadingMore: false,
page: 1,
pageSize: 20
})
// 筛选标签
const filterTabs = [
{ name: '全部' },
{ name: '随手记' },
{ name: '大事记' },
{ name: '健康记录' },
{ name: '洗护记录' },
{ name: '清洁记录' },
{ name: '消费记录' }
]
// 记录分类配置
const categoryConfig = {
daily: { name: '随手记', icon: 'edit-pen', color: '#64B5F6' },
milestone: { name: '大事记', icon: 'star', color: '#FFB74D' },
health: { name: '健康记录', icon: 'heart', color: '#81C784' },
grooming: { name: '洗护记录', icon: 'flower', color: '#F06292' },
cleaning: { name: '清洁记录', icon: 'home', color: '#9575CD' },
expense: { name: '消费记录', icon: 'rmb-circle', color: '#FF8A80' }
}
// 生命周期
onMounted(() => {
initPage()
})
// 初始化页面
const initPage = () => {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
state.petId = currentPage.options.petId || '1'
loadPetInfo()
loadRecords()
}
// 加载宠物信息
const loadPetInfo = () => {
// 模拟从本地存储获取宠物信息
const mockPets = [
{
id: '1',
name: '小橘',
breed: '橘猫',
avatar: '/static/cat-avatar.jpg'
},
{
id: '2',
name: '小白',
breed: '金毛',
avatar: '/static/dog-avatar.jpg'
}
]
state.petInfo = mockPets.find(pet => pet.id === state.petId) || mockPets[0]
}
// 加载记录
const loadRecords = () => {
// 模拟记录数据
const mockRecords = [
{
id: '1',
petId: state.petId,
category: 'daily',
recordTime: '2024-01-15 14:30',
content: '小橘今天特别活泼,一直在客厅里跑来跑去,看起来心情很好!',
details: {},
photos: ['/static/cat1.jpg', '/static/cat2.jpg'],
shareLevel: 'family',
createTime: '2024-01-15T14:30:00.000Z'
},
{
id: '2',
petId: state.petId,
category: 'health',
recordTime: '2024-01-14 10:00',
content: '带小橘去宠物医院打疫苗,医生说身体很健康',
details: {
healthType: '疫苗接种',
weight: '4.2',
temperature: '38.5'
},
photos: ['/static/vaccine.jpg'],
shareLevel: 'family',
createTime: '2024-01-14T10:00:00.000Z'
},
{
id: '3',
petId: state.petId,
category: 'milestone',
recordTime: '2024-01-13 16:20',
content: '小橘第一次学会用猫砂盆,真是个聪明的小家伙!',
details: {
milestoneType: '第一次用猫砂'
},
photos: [],
shareLevel: 'public',
createTime: '2024-01-13T16:20:00.000Z'
},
{
id: '4',
petId: state.petId,
category: 'grooming',
recordTime: '2024-01-12 09:15',
content: '给小橘洗澡和剪指甲,全程很乖很配合',
details: {
groomingType: ['洗澡', '剪指甲'],
duration: '45'
},
photos: ['/static/grooming.jpg'],
shareLevel: 'family',
createTime: '2024-01-12T09:15:00.000Z'
},
{
id: '5',
petId: state.petId,
category: 'expense',
recordTime: '2024-01-11 18:30',
content: '购买了新的猫粮和猫砂',
details: {
amount: '268',
expenseType: '用品',
store: '宠物用品店'
},
photos: [],
shareLevel: 'private',
createTime: '2024-01-11T18:30:00.000Z'
}
]
state.recordsList = mockRecords
}
// 按日期分组记录
const groupedRecords = computed(() => {
const filtered = getFilteredRecords()
const groups = {}
filtered.forEach(record => {
const date = formatDate(record.recordTime)
if (!groups[date]) {
groups[date] = []
}
groups[date].push(record)
})
return Object.keys(groups)
.sort((a, b) => new Date(b) - new Date(a))
.map(date => ({
date: formatDateLabel(date),
records: groups[date].sort((a, b) => new Date(b.recordTime) - new Date(a.recordTime))
}))
})
// 获取筛选后的记录
const getFilteredRecords = () => {
if (state.activeFilter === 0) {
return state.recordsList
}
const filterMap = {
1: 'daily',
2: 'milestone',
3: 'health',
4: 'grooming',
5: 'cleaning',
6: 'expense'
}
const targetCategory = filterMap[state.activeFilter]
return state.recordsList.filter(record => record.category === targetCategory)
}
// 获取分类名称
const getCategoryName = (category) => {
return categoryConfig[category]?.name || '未知类型'
}
// 获取分类图标
const getCategoryIcon = (category) => {
return categoryConfig[category]?.icon || 'file-text'
}
// 获取分类颜色
const getCategoryColor = (category) => {
return categoryConfig[category]?.color || '#999999'
}
// 获取记录标签
const getRecordTags = (record) => {
const tags = []
if (record.details) {
// 健康记录标签
if (record.category === 'health') {
if (record.details.healthType) tags.push(record.details.healthType)
if (record.details.weight) tags.push(`${record.details.weight}kg`)
if (record.details.symptoms && Array.isArray(record.details.symptoms)) {
tags.push(...record.details.symptoms.slice(0, 2))
}
}
// 洗护记录标签
if (record.category === 'grooming') {
if (record.details.groomingType && Array.isArray(record.details.groomingType)) {
tags.push(...record.details.groomingType.slice(0, 3))
}
if (record.details.duration) tags.push(`${record.details.duration}分钟`)
}
// 消费记录标签
if (record.category === 'expense') {
if (record.details.amount) tags.push(`¥${record.details.amount}`)
if (record.details.expenseType) tags.push(record.details.expenseType)
}
// 大事记标签
if (record.category === 'milestone') {
if (record.details.milestoneType) tags.push(record.details.milestoneType)
}
// 清洁记录标签
if (record.category === 'cleaning') {
if (record.details.cleaningType && Array.isArray(record.details.cleaningType)) {
tags.push(...record.details.cleaningType.slice(0, 3))
}
}
}
return tags.slice(0, 3) // 最多显示3个标签
}
// 获取分享图标
const getShareIcon = (shareLevel) => {
const iconMap = {
public: 'globe',
family: 'home',
private: 'lock'
}
return iconMap[shareLevel] || 'lock'
}
// 获取分享颜色
const getShareColor = (shareLevel) => {
const colorMap = {
public: '#81C784',
family: '#64B5F6',
private: '#FFB74D'
}
return colorMap[shareLevel] || '#999999'
}
// 格式化时间
const formatTime = (dateTime) => {
const time = dateTime.split(' ')[1]
return time ? time.substring(0, 5) : ''
}
// 筛选变化
const onFilterChange = (index) => {
state.activeFilter = index
}
// 查看记录详情
const viewRecordDetail = (record) => {
// 这里可以跳转到记录详情页面
uni.showToast({
title: '记录详情功能开发中',
icon: 'none'
})
}
// 添加记录
const addRecord = () => {
uni.navigateTo({
url: `/pages/pets/select-record-type?petId=${state.petId}`
})
}
// 加载更多记录
const loadMoreRecords = () => {
if (state.loadingMore || !state.hasMoreRecords) return
state.loadingMore = true
// 模拟加载更多数据
setTimeout(() => {
state.loadingMore = false
// 模拟没有更多数据
if (state.recordsList.length >= 10) {
state.hasMoreRecords = false
}
}, 1000)
}
// 返回上一页
const goBack = () => {
uni.navigateBack()
}
return {
...state,
filterTabs,
groupedRecords,
getFilteredRecords,
getCategoryName,
getCategoryIcon,
getCategoryColor,
getRecordTags,
getShareIcon,
getShareColor,
formatTime,
onFilterChange,
viewRecordDetail,
addRecord,
loadMoreRecords,
goBack
}
}
}
// 工具函数
function formatDate(dateTime) {
return dateTime.split(' ')[0]
}
function formatDateLabel(date) {
const today = new Date()
const recordDate = new Date(date)
const diffTime = today - recordDate
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
if (diffDays === 0) return '今天'
if (diffDays === 1) return '昨天'
if (diffDays === 2) return '前天'
const month = recordDate.getMonth() + 1
const day = recordDate.getDate()
return `${month}${day}`
}
</script>
<style lang="scss" scoped>
.pet-records-container {
height: 100vh;
display: flex;
flex-direction: column;
background-color: #f8f9fa;
}
.filter-bar {
background-color: #ffffff;
padding: 0 20px;
border-bottom: 1px solid #f0f0f0;
}
.records-scroll {
flex: 1;
padding: 0 20px;
}
.records-list {
padding: 20px 0 100px 0;
}
.record-group {
margin-bottom: 30px;
.group-header {
display: flex;
align-items: center;
margin-bottom: 15px;
.group-line {
flex: 1;
height: 1px;
background-color: #f0f0f0;
margin-left: 15px;
}
}
}
.record-item {
display: flex;
align-items: flex-start;
gap: 15px;
padding: 15px;
background-color: #ffffff;
border-radius: 12px;
margin-bottom: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
.record-icon {
width: 40px;
height: 40px;
border-radius: 20px;
background-color: #f8f9fa;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.record-content {
flex: 1;
.record-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.record-text {
margin-bottom: 10px;
}
.record-tags {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin-bottom: 10px;
}
.record-photos {
display: flex;
gap: 5px;
align-items: center;
.more-photos {
width: 40px;
height: 40px;
border-radius: 4px;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
}
}
}
.record-share {
flex-shrink: 0;
}
}
.load-more {
text-align: center;
padding: 20px 0;
}
.fab-button {
position: fixed;
bottom: 100px;
right: 30px;
width: 56px;
height: 56px;
border-radius: 28px;
background-color: #ff6b6b;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3);
z-index: 999;
}
</style>