diff --git a/src/components/reminder/ArchiveReminderModal.tsx b/src/components/reminder/ArchiveReminderModal.tsx
index 4c01767..e3179aa 100644
--- a/src/components/reminder/ArchiveReminderModal.tsx
+++ b/src/components/reminder/ArchiveReminderModal.tsx
@@ -19,6 +19,31 @@ interface ArchiveReminderModalProps {
onDelete: (event: Event) => void;
}
+/**
+ * 格式化日期显示,根据是否有时间选择显示格式
+ */
+function formatArchiveDate(dateStr: string): string {
+ const date = new Date(dateStr);
+ const hours = date.getHours();
+ const minutes = date.getMinutes();
+ const hasExplicitTime = hours !== 0 || minutes !== 0;
+
+ if (hasExplicitTime) {
+ return date.toLocaleString('zh-CN', {
+ month: 'numeric',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ hour12: false,
+ });
+ } else {
+ return date.toLocaleString('zh-CN', {
+ month: 'numeric',
+ day: 'numeric',
+ });
+ }
+}
+
export function ArchiveReminderModal({
opened,
onClose,
@@ -91,12 +116,7 @@ export function ArchiveReminderModal({
{event.title}
- {new Date(event.date).toLocaleString('zh-CN', {
- month: 'numeric',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- })}
+ {formatArchiveDate(event.date)}
diff --git a/src/components/reminder/ReminderCard.tsx b/src/components/reminder/ReminderCard.tsx
index 819cbbc..5350d5a 100644
--- a/src/components/reminder/ReminderCard.tsx
+++ b/src/components/reminder/ReminderCard.tsx
@@ -1,7 +1,8 @@
import { useMemo, useState } from 'react';
import { Paper, Text, Checkbox, Group, Stack, ActionIcon, Box } from '@mantine/core';
-import { IconDots, IconArrowForward } from '@tabler/icons-react';
-import type { Event } from '../../types';
+import { IconDots, IconArrowForward, IconRepeat, IconRepeatOff } from '@tabler/icons-react';
+import type { Event, RepeatType } from '../../types';
+import { getRepeatTypeLabel } from '../../utils/repeatCalculator';
interface ReminderCardProps {
event: Event;
@@ -10,9 +11,10 @@ interface ReminderCardProps {
onDelete?: () => void;
onPostpone?: () => void;
isMissed?: boolean;
+ onMissedToggle?: () => void;
}
-export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, isMissed = false }: ReminderCardProps) {
+export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, isMissed = false, onMissedToggle }: ReminderCardProps) {
const isCompleted = event.is_completed ?? false;
const [isHovered, setIsHovered] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
@@ -25,26 +27,16 @@ export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, i
}
const now = new Date();
- // 解析日期,处理本地时间格式(无时区)和ISO格式
- let eventDate: Date;
const dateStr = event.date as string;
- // 如果是本地时间格式(YYYY-MM-DDTHH:mm:ss),添加时区信息避免UTC转换
- if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(dateStr)) {
- // 本地时间格式,直接使用
- eventDate = new Date(dateStr);
- } else {
- // ISO格式,可能有时区
- eventDate = new Date(dateStr);
- }
- // 判断是否是有意设置的时间(不是默认的00:00)
- // 对于本地格式的日期,检查日期字符串中是否包含非00:00的时间
- const timeMatch = dateStr.match(/T(\d{2}):(\d{2})/);
- const hasExplicitTime = timeMatch && (timeMatch[1] !== '00' || timeMatch[2] !== '00');
- // 对于ISO格式,检查小时和分钟
+ // 统一使用 Date 对象处理
+ const eventDate = new Date(dateStr);
+
+ // 检查是否包含有效时间(小时和分钟不全为0)
+ // 对于本地格式(如 "2025-02-05T00:00:00")和 ISO 格式都适用
const hours = eventDate.getHours();
const minutes = eventDate.getMinutes();
- const isoHasTime = hours !== 0 || minutes !== 0;
+ const hasExplicitTime = hours !== 0 || minutes !== 0;
const diff = eventDate.getTime() - now.getTime();
const isPast = diff < 0;
@@ -52,8 +44,7 @@ export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, i
// 格式化显示
let timeStr: string;
- // 优先使用 hasExplicitTime(本地格式),如果没有显式时间才检查 ISO 格式
- if (hasExplicitTime || isoHasTime) {
+ if (hasExplicitTime) {
// 有设置时间,显示日期+时间
timeStr = eventDate.toLocaleString('zh-CN', {
month: 'numeric',
@@ -63,14 +54,14 @@ export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, i
hour12: false,
});
} else {
- // 没有设置时间,只显示日期
+ // 没有设置时间(00:00),只显示日期
timeStr = eventDate.toLocaleString('zh-CN', {
month: 'numeric',
day: 'numeric',
});
}
- return { isPast, isToday, timeStr, diff, hasTime: hasExplicitTime || isoHasTime };
+ return { isPast, isToday, timeStr, diff, hasTime: hasExplicitTime };
}, [event.date]);
// 获取文字颜色
@@ -101,6 +92,18 @@ export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, i
return 'rgba(0, 0, 0, 0.06)';
};
+ // 获取循环图标颜色
+ const getRepeatIconColor = (type: RepeatType) => {
+ const colors: Record = {
+ daily: '#3b82f6', // 蓝色
+ weekly: '#22c55e', // 绿色
+ monthly: '#a855f7', // 紫色
+ yearly: '#f59e0b', // 橙色
+ none: '#999',
+ };
+ return colors[type] || '#999';
+ };
+
return (
- {/* 右侧:日期时间 */}
-
+ {/* 右侧:循环图标 + 日期时间 */}
+
+ {event.repeat_type !== 'none' && (
+
+
+
+ )}
{
setIsAnimating(false);
onToggle();
+ onMissedToggle?.();
}, 300);
} else {
onToggle();
@@ -244,19 +260,34 @@ export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, i
-
- {event.title}
-
+
+
+ {event.title}
+
+ {event.repeat_type !== 'none' && (
+
+
+
+ )}
+
(null);
+ // 分类折叠状态
+ const [collapsed, setCollapsed] = useState({
+ today: false,
+ tomorrow: false,
+ later: false,
+ });
+
+ // 逾期列表展开状态(默认收起)
+ const [missedExpanded, setMissedExpanded] = useState(false);
+
+ // 归档图标抖动动画状态
+ const [archiveShake, setArchiveShake] = useState(false);
+
+ // 归档图标抖动动画样式
+ const shakeAnimationStyle = `
+ @keyframes archiveShake {
+ 0%, 100% { transform: scale(1); }
+ 25% { transform: scale(1.3); }
+ 50% { transform: scale(1); }
+ 75% { transform: scale(1.3); }
+ }
+ `;
+
+ // 触发归档图标抖动动画
+ const triggerArchiveShake = () => {
+ setArchiveShake(true);
+ setTimeout(() => {
+ setArchiveShake(false);
+ }, 400);
+ };
+
+ // 切换分类折叠状态
+ const toggleCollapse = (category: keyof typeof collapsed) => {
+ setCollapsed((prev) => ({
+ ...prev,
+ [category]: !prev[category],
+ }));
+ };
+
// 滚动条样式 - 仅在悬停时显示
const scrollbarStyle = `
.reminder-scroll::-webkit-scrollbar {
@@ -171,12 +216,10 @@ export function ReminderList({
);
}
- // 已过提醒数量提示
- const missedCount = grouped.missed.length;
-
return (
<>
+
{/* Header */}
@@ -184,11 +227,29 @@ export function ReminderList({
提醒
- {missedCount > 0 && (
-
- {missedCount}个逾期
-
- )}
+
+ }
+ onClick={() => navigate('/archive')}
+ style={{
+ color: '#999',
+ borderRadius: 2,
+ padding: '2px 6px',
+ height: 'auto',
+ }}
+ >
+ 归档
+
}
>
- {grouped.missed.slice(0, 3).map((event) => (
- onEventClick(event)}
- onToggle={() => onToggleComplete(event)}
- onDelete={onDelete ? () => onDelete(event) : undefined}
- onPostpone={onPostpone ? () => onPostpone(event) : undefined}
- isMissed={true}
- />
- ))}
- {grouped.missed.length > 3 && (
-
- 还有 {grouped.missed.length - 3} 个逾期提醒...
-
+ {/* 展开时显示所有逾期提醒 */}
+ {missedExpanded ? (
+ <>
+ {grouped.missed.map((event) => (
+ onEventClick(event)}
+ onToggle={() => onToggleComplete(event)}
+ onDelete={onDelete ? () => onDelete(event) : undefined}
+ onPostpone={onPostpone ? () => onPostpone(event) : undefined}
+ onMissedToggle={triggerArchiveShake}
+ isMissed={true}
+ />
+ ))}
+ {/* 收起按钮 */}
+ }
+ onClick={() => setMissedExpanded(false)}
+ style={{
+ color: '#999',
+ borderRadius: 2,
+ marginTop: 4,
+ }}
+ >
+ 收起
+
+ >
+ ) : (
+ <>
+ {/* 收起状态:只显示前3个 */}
+ {grouped.missed.slice(0, 3).map((event) => (
+ onEventClick(event)}
+ onToggle={() => onToggleComplete(event)}
+ onDelete={onDelete ? () => onDelete(event) : undefined}
+ onPostpone={onPostpone ? () => onPostpone(event) : undefined}
+ onMissedToggle={triggerArchiveShake}
+ isMissed={true}
+ />
+ ))}
+ {grouped.missed.length > 3 && (
+ setMissedExpanded(true)}
+ >
+ 还有 {grouped.missed.length - 3} 个逾期提醒...
+
+ )}
+ >
)}
@@ -249,12 +355,21 @@ export function ReminderList({
{/* 今天 */}
{grouped.today.length > 0 && (
<>
-
+ toggleCollapse('today')}
+ >
今天
+ {collapsed.today ? (
+
+ ) : (
+
+ )}
- {grouped.today.map((event) => (
+ {!collapsed.today && grouped.today.map((event) => (
0 && (
<>
-
+ toggleCollapse('tomorrow')}
+ >
明天
+ {collapsed.tomorrow ? (
+
+ ) : (
+
+ )}
- {grouped.tomorrow.map((event) => (
+ {!collapsed.tomorrow && grouped.tomorrow.map((event) => (
0 && (
<>
-
+ toggleCollapse('later')}
+ >
更久之后
+ {collapsed.later ? (
+
+ ) : (
+
+ )}
- {grouped.later.map((event) => (
+ {!collapsed.later && grouped.later.map((event) => (
state.events);
@@ -132,12 +157,7 @@ export function ArchivePage() {
{event.title}
- {new Date(event.date).toLocaleString('zh-CN', {
- month: 'numeric',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- })}
+ {formatArchiveDate(event.date)}
{event.content && (
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx
index ea8409c..c49929e 100644
--- a/src/pages/HomePage.tsx
+++ b/src/pages/HomePage.tsx
@@ -13,7 +13,7 @@ import {
Stack,
} from '@mantine/core';
import { DatePickerInput, TimeInput } from '@mantine/dates';
-import { IconLogout, IconSettings, IconArchive } from '@tabler/icons-react';
+import { IconLogout, IconSettings } from '@tabler/icons-react';
import { useDisclosure } from '@mantine/hooks';
import { useNavigate } from 'react-router-dom';
import { useAppStore } from '../stores';
@@ -22,6 +22,7 @@ import { ReminderList } from '../components/reminder/ReminderList';
import { NoteEditor } from '../components/note/NoteEditor';
import { FloatingAIChat } from '../components/ai/FloatingAIChat';
import type { Event, EventType, RepeatType } from '../types';
+import { calculateNextReminderDate } from '../utils/repeatCalculator';
export function HomePage() {
const navigate = useNavigate();
@@ -56,6 +57,7 @@ export function HomePage() {
const handleLogout = async () => {
await logout();
+ navigate('/landing');
};
const handleEventClick = (event: Event) => {
@@ -111,16 +113,23 @@ export function HomePage() {
dateStr = `${year}-${month}-${day}T00:00:00`;
}
- const eventData = {
+ // 构建事件数据,确保不包含 undefined 值
+ const eventData: Record = {
type: formType,
title: formTitle,
- content: formContent || undefined,
date: dateStr,
is_lunar: formIsLunar,
repeat_type: formRepeatType,
- is_holiday: formIsHoliday || undefined,
+ // 计算下一次提醒日期
+ next_reminder_date: calculateNextReminderDate(dateStr, formRepeatType, undefined),
+ is_holiday: formIsHoliday ?? false,
};
+ // 只有当 content 有值时才包含
+ if (formContent.trim()) {
+ eventData.content = formContent;
+ }
+
if (isEdit && selectedEvent) {
await updateEventById(selectedEvent.id, eventData);
} else {
@@ -220,20 +229,6 @@ export function HomePage() {
掐日子
- {/* 归档入口 - 一直显示 */}
- }
- onClick={() => navigate('/archive')}
- style={{
- letterSpacing: '0.1em',
- borderRadius: 2,
- }}
- >
- 归档
-
{/* 设置入口 */}