625 lines
16 KiB
Vue
625 lines
16 KiB
Vue
<template>
|
|
<view class="pet-records-container">
|
|
<!-- 头部操作栏 -->
|
|
<view class="header-actions">
|
|
<text class="page-title">{{ petInfo.name }}的记录</text>
|
|
<view class="add-btn" @click="addRecord">
|
|
<text class="add-text">添加</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 筛选栏 -->
|
|
<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)
|
|
}
|
|
|
|
return {
|
|
...state,
|
|
filterTabs,
|
|
groupedRecords,
|
|
getFilteredRecords,
|
|
getCategoryName,
|
|
getCategoryIcon,
|
|
getCategoryColor,
|
|
getRecordTags,
|
|
getShareIcon,
|
|
getShareColor,
|
|
formatTime,
|
|
onFilterChange,
|
|
viewRecordDetail,
|
|
addRecord,
|
|
loadMoreRecords
|
|
}
|
|
}
|
|
}
|
|
|
|
// 工具函数
|
|
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: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
|
|
}
|
|
|
|
.header-actions {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(20rpx);
|
|
margin: 20rpx 30rpx;
|
|
border-radius: 24rpx;
|
|
padding: 24rpx;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
|
|
border: 1rpx solid rgba(255, 255, 255, 0.3);
|
|
|
|
.page-title {
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
color: #FF8A80;
|
|
}
|
|
|
|
.add-btn {
|
|
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 100%);
|
|
border-radius: 20rpx;
|
|
padding: 16rpx 24rpx;
|
|
box-shadow: 0 4rpx 16rpx rgba(255, 138, 128, 0.4);
|
|
transition: all 0.3s ease;
|
|
|
|
&:active {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
.add-text {
|
|
font-size: 28rpx;
|
|
color: #ffffff;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
}
|
|
|
|
.filter-bar {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(20rpx);
|
|
margin: 0 30rpx 20rpx 30rpx;
|
|
border-radius: 24rpx;
|
|
padding: 20rpx;
|
|
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
|
|
border: 1rpx solid rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
.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>
|