pet/pages/profile/reminders.vue

1013 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="reminders-container page-container-with-bg">
<!-- 统计概览卡片 -->
<view class="stats-card">
<view class="stats-grid">
<view class="stat-item">
<view class="stat-number">{{ pendingCount }}</view>
<view class="stat-label">待处理</view>
<view class="stat-icon"></view>
</view>
<view class="stat-item">
<view class="stat-number">{{ todayCount }}</view>
<view class="stat-label">今日</view>
<view class="stat-icon">📅</view>
</view>
<view class="stat-item">
<view class="stat-number">{{ completedCount }}</view>
<view class="stat-label">已完成</view>
<view class="stat-icon"></view>
</view>
</view>
</view>
<!-- 筛选标签 -->
<view class="filter-tabs">
<view
class="filter-tab"
:class="{ active: currentFilter === 'all' }"
@click="setFilter('all')"
>
<text class="tab-text">全部</text>
</view>
<view
class="filter-tab"
:class="{ active: currentFilter === 'pending' }"
@click="setFilter('pending')"
>
<text class="tab-text">待处理</text>
</view>
<view
class="filter-tab"
:class="{ active: currentFilter === 'today' }"
@click="setFilter('today')"
>
<text class="tab-text">今日</text>
</view>
<view
class="filter-tab"
:class="{ active: currentFilter === 'completed' }"
@click="setFilter('completed')"
>
<text class="tab-text">已完成</text>
</view>
</view>
<!-- 提醒列表 -->
<view class="reminders-list" v-if="filteredReminders.length > 0">
<view
class="reminder-item"
v-for="reminder in filteredReminders"
:key="reminder.id"
:class="{ completed: reminder.completed, overdue: isOverdue(reminder) }"
@click="viewReminderDetail(reminder)"
>
<view class="reminder-checkbox" @click.stop="toggleReminder(reminder)">
<view class="checkbox" :class="{ checked: reminder.completed }">
<text class="check-icon" v-if="reminder.completed">✓</text>
</view>
</view>
<view class="reminder-content">
<view class="reminder-title">{{ reminder.title }}</view>
<view class="reminder-meta">
<view class="reminder-time">
<text class="time-icon">🕐</text>
<text class="time-text">{{ formatDateTime(reminder.datetime) }}</text>
</view>
<view class="reminder-pet" v-if="reminder.petName">
<text class="pet-icon">🐱</text>
<text class="pet-text">{{ reminder.petName }}</text>
</view>
</view>
<view class="reminder-desc" v-if="reminder.description">
{{ reminder.description }}
</view>
</view>
<view class="reminder-actions">
<view class="reminder-priority" :class="reminder.priority">
<text class="priority-dot"></text>
</view>
<view class="action-more" @click.stop="showReminderActions(reminder)">
<text class="more-icon">⋯</text>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-else>
<view class="empty-icon">📝</view>
<view class="empty-text">{{ getEmptyText() }}</view>
<view class="empty-action" @click="addReminder" v-if="currentFilter === 'all'">
<text class="action-text">添加第一个提醒</text>
</view>
</view>
<!-- 添加按钮 -->
<view class="add-button" @click="addReminder">
<text class="add-icon">+</text>
</view>
<!-- 添加提醒弹窗 -->
<u-modal
:show="showAddModal"
title="添加提醒"
@confirm="confirmAddReminder"
@cancel="cancelAddReminder"
:show-cancel-button="true"
confirm-text="保存"
>
<view class="add-reminder-form">
<view class="form-item">
<text class="form-label">提醒标题</text>
<u-input
v-model="newReminder.title"
placeholder="请输入提醒标题"
border="none"
:custom-style="inputStyle"
></u-input>
</view>
<view class="form-item">
<text class="form-label">提醒时间</text>
<view class="datetime-picker" @click="showDatetimePicker">
<text class="datetime-text">{{ formatDateTime(newReminder.datetime) || '请选择时间' }}</text>
<text class="picker-arrow">→</text>
</view>
</view>
<view class="form-item">
<text class="form-label">关联宠物</text>
<view class="pet-picker" @click="showPetPicker">
<text class="pet-text">{{ newReminder.petName || '请选择宠物' }}</text>
<text class="picker-arrow">→</text>
</view>
</view>
<view class="form-item">
<text class="form-label">优先级</text>
<view class="priority-options">
<view
class="priority-option"
:class="{ active: newReminder.priority === 'high' }"
@click="setPriority('high')"
>
<view class="priority-dot high"></view>
<text class="priority-text">高</text>
</view>
<view
class="priority-option"
:class="{ active: newReminder.priority === 'medium' }"
@click="setPriority('medium')"
>
<view class="priority-dot medium"></view>
<text class="priority-text">中</text>
</view>
<view
class="priority-option"
:class="{ active: newReminder.priority === 'low' }"
@click="setPriority('low')"
>
<view class="priority-dot low"></view>
<text class="priority-text">低</text>
</view>
</view>
</view>
<view class="form-item">
<text class="form-label">备注说明</text>
<u-textarea
v-model="newReminder.description"
placeholder="添加备注说明(可选)"
:maxlength="100"
:custom-style="textareaStyle"
></u-textarea>
</view>
</view>
</u-modal>
<!-- 日期时间选择器 -->
<u-datetime-picker
:show="showDatetime"
v-model="selectedDatetime"
mode="datetime"
@confirm="onDatetimeConfirm"
@cancel="showDatetime = false"
></u-datetime-picker>
<!-- 宠物选择器 -->
<u-picker
:show="showPet"
:columns="petColumns"
@confirm="onPetConfirm"
@cancel="showPet = false"
></u-picker>
<!-- 操作菜单 -->
<u-action-sheet
:show="showActions"
:actions="actionList"
@close="showActions = false"
@select="onActionSelect"
></u-action-sheet>
</view>
</template>
<script>
import { reactive, ref, onMounted, computed } from 'vue'
export default {
name: 'RemindersPage',
setup() {
// 响应式数据
const reminders = ref([])
const currentFilter = ref('all')
const showAddModal = ref(false)
const showDatetime = ref(false)
const showPet = ref(false)
const showActions = ref(false)
const selectedDatetime = ref(Date.now())
const currentReminder = ref(null)
const newReminder = reactive({
title: '',
datetime: '',
petId: '',
petName: '',
priority: 'medium',
description: ''
})
const petColumns = ref([])
const actionList = ref([
{ name: '编辑', value: 'edit' },
{ name: '删除', value: 'delete', color: '#FF5722' }
])
// 样式配置
const inputStyle = {
fontSize: '28rpx',
color: '#333333'
}
const textareaStyle = {
fontSize: '26rpx',
color: '#333333'
}
// 计算属性
const pendingCount = computed(() => {
return reminders.value.filter(r => !r.completed).length
})
const todayCount = computed(() => {
const today = new Date().toDateString()
return reminders.value.filter(r =>
new Date(r.datetime).toDateString() === today
).length
})
const completedCount = computed(() => {
return reminders.value.filter(r => r.completed).length
})
const filteredReminders = computed(() => {
let filtered = reminders.value
switch (currentFilter.value) {
case 'pending':
filtered = reminders.value.filter(r => !r.completed)
break
case 'today':
const today = new Date().toDateString()
filtered = reminders.value.filter(r =>
new Date(r.datetime).toDateString() === today
)
break
case 'completed':
filtered = reminders.value.filter(r => r.completed)
break
default:
filtered = reminders.value
}
// 按时间排序,未完成的在前
return filtered.sort((a, b) => {
if (a.completed !== b.completed) {
return a.completed ? 1 : -1
}
return new Date(a.datetime) - new Date(b.datetime)
})
})
// 生命周期
onMounted(() => {
loadReminders()
loadPets()
})
// 方法定义
const loadReminders = () => {
try {
const savedReminders = uni.getStorageSync('reminders') || []
reminders.value = savedReminders
} catch (error) {
console.error('加载提醒数据失败:', error)
}
}
const loadPets = () => {
try {
const pets = uni.getStorageSync('pets') || []
petColumns.value = [pets.map(pet => pet.name)]
} catch (error) {
console.error('加载宠物数据失败:', error)
}
}
const saveReminders = () => {
uni.setStorageSync('reminders', reminders.value)
}
const setFilter = (filter) => {
currentFilter.value = filter
}
const isOverdue = (reminder) => {
if (reminder.completed) return false
return new Date(reminder.datetime) < new Date()
}
const formatDateTime = (datetime) => {
if (!datetime) return ''
const date = new Date(datetime)
const now = new Date()
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const reminderDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
let dateStr = ''
if (reminderDate.getTime() === today.getTime()) {
dateStr = '今天'
} else if (reminderDate.getTime() === today.getTime() + 24 * 60 * 60 * 1000) {
dateStr = '明天'
} else {
dateStr = `${date.getMonth() + 1}${date.getDate()}`
}
const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
return `${dateStr} ${timeStr}`
}
const getEmptyText = () => {
switch (currentFilter.value) {
case 'pending':
return '暂无待处理的提醒'
case 'today':
return '今日暂无提醒事项'
case 'completed':
return '暂无已完成的提醒'
default:
return '暂无提醒事项'
}
}
const toggleReminder = (reminder) => {
reminder.completed = !reminder.completed
reminder.completedAt = reminder.completed ? new Date().toISOString() : null
saveReminders()
uni.showToast({
title: reminder.completed ? '已完成' : '已取消完成',
icon: 'success'
})
}
const viewReminderDetail = (reminder) => {
const statusText = reminder.completed ? '已完成' : (isOverdue(reminder) ? '已过期' : '待处理')
const content = `时间:${formatDateTime(reminder.datetime)}\n状态${statusText}${reminder.petName ? `\n宠物${reminder.petName}` : ''}${reminder.description ? `\n备注${reminder.description}` : ''}`
uni.showModal({
title: reminder.title,
content: content,
showCancel: false
})
}
const showReminderActions = (reminder) => {
currentReminder.value = reminder
showActions.value = true
}
const onActionSelect = (action) => {
if (action.value === 'edit') {
editReminder(currentReminder.value)
} else if (action.value === 'delete') {
deleteReminder(currentReminder.value)
}
showActions.value = false
}
const editReminder = (reminder) => {
Object.assign(newReminder, {
id: reminder.id,
title: reminder.title,
datetime: reminder.datetime,
petId: reminder.petId,
petName: reminder.petName,
priority: reminder.priority,
description: reminder.description
})
showAddModal.value = true
}
const deleteReminder = (reminder) => {
uni.showModal({
title: '确认删除',
content: '确定要删除这个提醒吗?',
success: (res) => {
if (res.confirm) {
const index = reminders.value.findIndex(r => r.id === reminder.id)
if (index > -1) {
reminders.value.splice(index, 1)
saveReminders()
uni.showToast({
title: '已删除',
icon: 'success'
})
}
}
}
})
}
const addReminder = () => {
// 重置表单
Object.assign(newReminder, {
id: '',
title: '',
datetime: '',
petId: '',
petName: '',
priority: 'medium',
description: ''
})
showAddModal.value = true
}
const confirmAddReminder = () => {
if (!newReminder.title.trim()) {
uni.showToast({
title: '请输入提醒标题',
icon: 'none'
})
return
}
if (!newReminder.datetime) {
uni.showToast({
title: '请选择提醒时间',
icon: 'none'
})
return
}
const reminder = {
id: newReminder.id || 'reminder_' + Date.now(),
title: newReminder.title.trim(),
datetime: newReminder.datetime,
petId: newReminder.petId,
petName: newReminder.petName,
priority: newReminder.priority,
description: newReminder.description.trim(),
completed: false,
createdAt: new Date().toISOString()
}
if (newReminder.id) {
// 编辑模式
const index = reminders.value.findIndex(r => r.id === newReminder.id)
if (index > -1) {
reminders.value[index] = reminder
}
} else {
// 新增模式
reminders.value.push(reminder)
}
saveReminders()
showAddModal.value = false
uni.showToast({
title: newReminder.id ? '修改成功' : '添加成功',
icon: 'success'
})
}
const cancelAddReminder = () => {
showAddModal.value = false
}
const showDatetimePicker = () => {
selectedDatetime.value = newReminder.datetime ? new Date(newReminder.datetime).getTime() : Date.now()
showDatetime.value = true
}
const onDatetimeConfirm = (e) => {
newReminder.datetime = new Date(e.value).toISOString()
showDatetime.value = false
}
const showPetPicker = () => {
showPet.value = true
}
const onPetConfirm = (e) => {
const pets = uni.getStorageSync('pets') || []
const selectedPet = pets.find(pet => pet.name === e.value[0])
if (selectedPet) {
newReminder.petId = selectedPet.id
newReminder.petName = selectedPet.name
}
showPet.value = false
}
const setPriority = (priority) => {
newReminder.priority = priority
}
return {
reminders,
currentFilter,
showAddModal,
showDatetime,
showPet,
showActions,
selectedDatetime,
newReminder,
petColumns,
actionList,
inputStyle,
textareaStyle,
pendingCount,
todayCount,
completedCount,
filteredReminders,
setFilter,
isOverdue,
formatDateTime,
getEmptyText,
toggleReminder,
viewReminderDetail,
showReminderActions,
onActionSelect,
addReminder,
confirmAddReminder,
cancelAddReminder,
showDatetimePicker,
onDatetimeConfirm,
showPetPicker,
onPetConfirm,
setPriority
}
}
}
</script>
<style lang="scss" scoped>
.reminders-container {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
min-height: 100vh;
padding-bottom: 120rpx;
}
/* 统计概览卡片 */
.stats-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 30rpx 24rpx 30rpx;
border-radius: 24rpx;
padding: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 24rpx;
.stat-item {
text-align: center;
padding: 24rpx 16rpx;
border-radius: 16rpx;
background: rgba(255, 138, 128, 0.05);
.stat-number {
font-size: 40rpx;
font-weight: 700;
color: #FF8A80;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 22rpx;
color: #666666;
margin-bottom: 8rpx;
}
.stat-icon {
font-size: 28rpx;
}
}
}
}
/* 筛选标签 */
.filter-tabs {
display: flex;
margin: 0 30rpx 24rpx 30rpx;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
border-radius: 24rpx;
padding: 8rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
.filter-tab {
flex: 1;
text-align: center;
padding: 16rpx 12rpx;
border-radius: 16rpx;
transition: all 0.3s ease;
&.active {
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
.tab-text {
color: white;
font-weight: 600;
}
}
.tab-text {
font-size: 26rpx;
color: #666666;
}
}
}
/* 提醒列表 */
.reminders-list {
margin: 0 30rpx;
.reminder-item {
display: flex;
align-items: flex-start;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(255, 138, 128, 0.1);
border: 1rpx solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
}
&.completed {
opacity: 0.6;
.reminder-title {
text-decoration: line-through;
color: #999999;
}
}
&.overdue:not(.completed) {
border-left: 6rpx solid #FF5722;
.reminder-title {
color: #FF5722;
}
}
.reminder-checkbox {
margin-right: 16rpx;
margin-top: 4rpx;
.checkbox {
width: 40rpx;
height: 40rpx;
border: 3rpx solid #CCCCCC;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&.checked {
background: #FF8A80;
border-color: #FF8A80;
.check-icon {
color: white;
font-size: 24rpx;
font-weight: bold;
}
}
}
}
.reminder-content {
flex: 1;
.reminder-title {
font-size: 30rpx;
font-weight: 600;
color: #333333;
margin-bottom: 12rpx;
line-height: 1.4;
}
.reminder-meta {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-bottom: 8rpx;
.reminder-time,
.reminder-pet {
display: flex;
align-items: center;
.time-icon,
.pet-icon {
font-size: 20rpx;
margin-right: 6rpx;
}
.time-text,
.pet-text {
font-size: 22rpx;
color: #666666;
}
}
}
.reminder-desc {
font-size: 24rpx;
color: #999999;
line-height: 1.4;
}
}
.reminder-actions {
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
.reminder-priority {
.priority-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
&.high {
background: #FF5722;
}
&.medium {
background: #FF8A80;
}
&.low {
background: #CCCCCC;
}
}
}
.action-more {
padding: 8rpx;
.more-icon {
font-size: 24rpx;
color: #CCCCCC;
}
}
}
}
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 80rpx 40rpx;
margin: 0 30rpx;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
border-radius: 24rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
.empty-icon {
font-size: 80rpx;
margin-bottom: 24rpx;
opacity: 0.5;
}
.empty-text {
font-size: 28rpx;
color: #999999;
margin-bottom: 32rpx;
line-height: 1.5;
}
.empty-action {
display: inline-block;
padding: 16rpx 32rpx;
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
border-radius: 20rpx;
.action-text {
font-size: 26rpx;
color: white;
}
}
}
/* 添加按钮 */
.add-button {
position: fixed;
bottom: 120rpx;
right: 40rpx;
width: 100rpx;
height: 100rpx;
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(255, 138, 128, 0.4);
z-index: 100;
transition: all 0.3s ease;
&:active {
transform: scale(0.9);
}
.add-icon {
font-size: 48rpx;
color: white;
font-weight: 300;
}
}
/* 添加提醒表单 */
.add-reminder-form {
padding: 20rpx 0;
.form-item {
margin-bottom: 32rpx;
.form-label {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #333333;
margin-bottom: 16rpx;
}
.datetime-picker,
.pet-picker {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
background: rgba(255, 138, 128, 0.05);
border-radius: 16rpx;
border: 1rpx solid rgba(255, 138, 128, 0.2);
.datetime-text,
.pet-text {
font-size: 28rpx;
color: #333333;
}
.picker-arrow {
font-size: 24rpx;
color: #FF8A80;
}
}
.priority-options {
display: flex;
gap: 16rpx;
.priority-option {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 16rpx;
border-radius: 16rpx;
background: rgba(255, 138, 128, 0.05);
border: 2rpx solid transparent;
transition: all 0.3s ease;
&.active {
border-color: #FF8A80;
background: rgba(255, 138, 128, 0.1);
}
.priority-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
margin-right: 8rpx;
&.high {
background: #FF5722;
}
&.medium {
background: #FF8A80;
}
&.low {
background: #CCCCCC;
}
}
.priority-text {
font-size: 26rpx;
color: #333333;
}
}
}
}
}
/* 响应式设计 */
@media (max-width: 375px) {
.reminders-container {
.stats-card,
.filter-tabs,
.reminders-list,
.empty-state {
margin-left: 20rpx;
margin-right: 20rpx;
}
.stats-card,
.empty-state {
padding: 24rpx;
}
.stats-grid {
gap: 16rpx;
.stat-item {
padding: 20rpx 12rpx;
.stat-number {
font-size: 36rpx;
}
}
}
.add-button {
right: 30rpx;
bottom: 100rpx;
width: 80rpx;
height: 80rpx;
.add-icon {
font-size: 40rpx;
}
}
}
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.reminders-container > view {
animation: fadeIn 0.5s ease-out;
}
.reminder-item {
animation: fadeIn 0.3s ease-out;
}
.add-button {
animation: fadeIn 0.6s ease-out;
}
</style>