feat: 优化提醒功能,修复状态保存问题

- 优化提醒分组逻辑,精确判断过期时间
- 已完成但未过期的提醒仍显示在主列表(划掉状态)
- 修复 checkbox 点击事件处理
- 添加乐观更新,UI 即时响应
- 添加归档页和设置页路由
- 修复后端 is_completed 字段验证问题
This commit is contained in:
ddshi 2026-02-03 13:19:06 +08:00
parent 250c05e85e
commit 9e4b4022bd
31 changed files with 397 additions and 132 deletions

View File

@ -4,6 +4,7 @@ import { IconPlus } from '@tabler/icons-react';
import { AnniversaryCard } from './AnniversaryCard'; import { AnniversaryCard } from './AnniversaryCard';
import { getHolidaysForYear } from '../../constants/holidays'; import { getHolidaysForYear } from '../../constants/holidays';
import { calculateCountdown, formatCountdown } from '../../utils/countdown'; import { calculateCountdown, formatCountdown } from '../../utils/countdown';
import { useAppStore } from '../../stores';
import type { Event } from '../../types'; import type { Event } from '../../types';
interface AnniversaryListProps { interface AnniversaryListProps {
@ -26,9 +27,12 @@ interface BuiltInHolidayEvent {
export function AnniversaryList({ events, onEventClick, onAddClick }: AnniversaryListProps) { export function AnniversaryList({ events, onEventClick, onAddClick }: AnniversaryListProps) {
const anniversaries = events.filter((e) => e.type === 'anniversary'); const anniversaries = events.filter((e) => e.type === 'anniversary');
const showHolidays = useAppStore((state) => state.settings?.showHolidays ?? true);
// 获取内置节假日 // 获取内置节假日
const builtInHolidays = useMemo(() => { const builtInHolidays = useMemo(() => {
if (!showHolidays) return [];
const now = new Date(); const now = new Date();
const year = now.getFullYear(); const year = now.getFullYear();
const holidays = getHolidaysForYear(year); const holidays = getHolidaysForYear(year);
@ -39,13 +43,13 @@ export function AnniversaryList({ events, onEventClick, onAddClick }: Anniversar
(a, b) => a.date.getTime() - b.date.getTime() (a, b) => a.date.getTime() - b.date.getTime()
); );
// 只取未来30天内的节假日 // 只取未来90天内的节假日显示最近3个
const cutoffDate = new Date(now); const cutoffDate = new Date(now);
cutoffDate.setDate(cutoffDate.getDate() + 30); cutoffDate.setDate(cutoffDate.getDate() + 90);
return allHolidays return allHolidays
.filter((h) => h.date >= now && h.date <= cutoffDate) .filter((h) => h.date >= now && h.date <= cutoffDate)
.slice(0, 5) .slice(0, 3)
.map((h): BuiltInHolidayEvent => ({ .map((h): BuiltInHolidayEvent => ({
id: `builtin-${h.id}`, id: `builtin-${h.id}`,
title: h.name, title: h.name,
@ -56,7 +60,7 @@ export function AnniversaryList({ events, onEventClick, onAddClick }: Anniversar
type: 'anniversary', type: 'anniversary',
is_builtin: true, is_builtin: true,
})); }));
}, []); }, [showHolidays]);
// 合并用户纪念日和内置节假日 // 合并用户纪念日和内置节假日
const allAnniversaries = useMemo(() => { const allAnniversaries = useMemo(() => {

View File

@ -1,6 +1,6 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { Paper, Text, Checkbox, Group, Stack, ActionIcon } from '@mantine/core'; import { Paper, Text, Checkbox, Group, Stack, ActionIcon } from '@mantine/core';
import { IconCheck, IconDots } from '@tabler/icons-react'; import { IconDots } from '@tabler/icons-react';
import type { Event } from '../../types'; import type { Event } from '../../types';
interface ReminderCardProps { interface ReminderCardProps {
@ -45,7 +45,6 @@ export function ReminderCard({ event, onToggle, onClick }: ReminderCardProps) {
<Paper <Paper
p="sm" p="sm"
radius={2} radius={2}
onClick={onClick}
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
style={{ style={{
@ -67,12 +66,11 @@ export function ReminderCard({ event, onToggle, onClick }: ReminderCardProps) {
e.stopPropagation(); e.stopPropagation();
onToggle(); onToggle();
}} }}
onClick={(e) => e.stopPropagation()}
size="xs" size="xs"
color="#1a1a1a" color="#1a1a1a"
/> />
<Stack gap={2} style={{ flex: 1, minWidth: 0 }}> <Stack gap={2} style={{ flex: 1, minWidth: 0 }} onClick={onClick}>
{/* Title */} {/* Title */}
<Text <Text
fw={400} fw={400}
@ -105,26 +103,16 @@ export function ReminderCard({ event, onToggle, onClick }: ReminderCardProps) {
{/* Quick actions */} {/* Quick actions */}
<Group gap={4}> <Group gap={4}>
{isHovered && !isCompleted && (
<ActionIcon
size="sm"
variant="subtle"
onClick={(e) => {
e.stopPropagation();
onToggle();
}}
style={{ color: '#666' }}
>
<IconCheck size={12} />
</ActionIcon>
)}
<ActionIcon <ActionIcon
size="sm" size="sm"
variant="subtle" variant="subtle"
color="gray" color="gray"
onClick={(e) => e.stopPropagation()} onClick={(e) => {
e.stopPropagation();
onClick();
}}
style={{ color: '#999' }} style={{ color: '#999' }}
title="编辑"
> >
<IconDots size={12} /> <IconDots size={12} />
</ActionIcon> </ActionIcon>

View File

@ -1,4 +1,4 @@
import { useMemo, useState } from 'react'; import { useMemo } from 'react';
import { import {
Stack, Stack,
Text, Text,
@ -6,16 +6,12 @@ import {
Group, Group,
Button, Button,
Alert, Alert,
ActionIcon,
Tooltip,
} from '@mantine/core'; } from '@mantine/core';
import { import {
IconPlus, IconPlus,
IconAlertCircle, IconAlertCircle,
IconArchive,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { ReminderCard } from './ReminderCard'; import { ReminderCard } from './ReminderCard';
import { ArchiveReminderModal } from './ArchiveReminderModal';
import type { Event } from '../../types'; import type { Event } from '../../types';
interface ReminderListProps { interface ReminderListProps {
@ -35,44 +31,47 @@ export function ReminderList({
onDelete, onDelete,
onRestore, onRestore,
}: ReminderListProps) { }: ReminderListProps) {
const [archiveOpened, setArchiveOpened] = useState(false);
const grouped = useMemo(() => { const grouped = useMemo(() => {
const now = new Date(); const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today); const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setDate(tomorrow.getDate() + 1);
const nextWeek = new Date(today); tomorrow.setHours(0, 0, 0, 0);
nextWeek.setDate(nextWeek.getDate() + 7); const dayAfterTomorrow = new Date(tomorrow);
dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 1);
dayAfterTomorrow.setHours(0, 0, 0, 0);
const reminders = events.filter((e) => e.type === 'reminder'); const reminders = events.filter((e) => e.type === 'reminder');
const result = { const result = {
today: [] as Event[], today: [] as Event[],
tomorrow: [] as Event[], tomorrow: [] as Event[],
thisWeek: [] as Event[],
later: [] as Event[], later: [] as Event[],
missed: [] as Event[], missed: [] as Event[],
completed: [] as Event[],
}; };
reminders.forEach((event) => { reminders.forEach((event) => {
const eventDate = new Date(event.date); const eventDate = new Date(event.date);
// 已完成的放最后 // 已过期且已完成的去归档页
if (event.is_completed) { if (event.is_completed && eventDate < now) {
result.completed.push(event);
return; return;
} }
// 未完成的按时间分组 // 未过期或已完成未过期的,按时间分组
if (eventDate < today) { if (eventDate < now) {
// 已过期未完成
result.missed.push(event); result.missed.push(event);
} else if (eventDate < tomorrow) { } else if (eventDate < tomorrow) {
// 今天
result.today.push(event); result.today.push(event);
} else if (eventDate < nextWeek) { } else if (eventDate < dayAfterTomorrow) {
result.thisWeek.push(event); // 明天
result.tomorrow.push(event);
} else { } else {
// 更久之后
result.later.push(event); result.later.push(event);
} }
}); });
@ -83,10 +82,9 @@ export function ReminderList({
result.today.sort(sortByDate); result.today.sort(sortByDate);
result.tomorrow.sort(sortByDate); result.tomorrow.sort(sortByDate);
result.thisWeek.sort(sortByDate);
result.later.sort(sortByDate); result.later.sort(sortByDate);
result.missed.sort(sortByDate); // 已过期按时间倒序(最近的在上面)
result.completed.sort(sortByDate); result.missed.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
return result; return result;
}, [events]); }, [events]);
@ -94,7 +92,6 @@ export function ReminderList({
const hasActiveReminders = const hasActiveReminders =
grouped.today.length > 0 || grouped.today.length > 0 ||
grouped.tomorrow.length > 0 || grouped.tomorrow.length > 0 ||
grouped.thisWeek.length > 0 ||
grouped.later.length > 0 || grouped.later.length > 0 ||
grouped.missed.length > 0; grouped.missed.length > 0;
@ -146,20 +143,6 @@ export function ReminderList({
</Text> </Text>
)} )}
</Group> </Group>
<Group gap={4}>
{grouped.completed.length > 0 && (
<Tooltip label="查看已归档">
<ActionIcon
size="sm"
variant="subtle"
color="gray"
onClick={() => setArchiveOpened(true)}
style={{ color: '#999' }}
>
<IconArchive size={12} />
</ActionIcon>
</Tooltip>
)}
<Button <Button
variant="subtle" variant="subtle"
size="xs" size="xs"
@ -173,7 +156,6 @@ export function ReminderList({
</Button> </Button>
</Group> </Group>
</Group>
{/* Content */} {/* Content */}
<Stack gap="xs" style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}> <Stack gap="xs" style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}>
@ -245,32 +227,12 @@ export function ReminderList({
</> </>
)} )}
{/* 本周 */} {/* 更久之后 */}
{grouped.thisWeek.length > 0 && (
<>
<Group gap={4}>
<Text size="xs" c="#888" fw={400} style={{ letterSpacing: '0.05em' }}>
</Text>
</Group>
{grouped.thisWeek.map((event) => (
<ReminderCard
key={event.id}
event={event}
onClick={() => onEventClick(event)}
onToggle={() => onToggleComplete(event)}
onDelete={onDelete ? () => onDelete(event) : undefined}
/>
))}
</>
)}
{/* 更久 */}
{grouped.later.length > 0 && ( {grouped.later.length > 0 && (
<> <>
<Group gap={4}> <Group gap={4}>
<Text size="xs" c="#999" fw={400} style={{ letterSpacing: '0.05em' }}> <Text size="xs" c="#999" fw={400} style={{ letterSpacing: '0.05em' }}>
</Text> </Text>
</Group> </Group>
{grouped.later.map((event) => ( {grouped.later.map((event) => (
@ -286,15 +248,6 @@ export function ReminderList({
)} )}
</Stack> </Stack>
</Paper> </Paper>
{/* Archive Modal */}
<ArchiveReminderModal
opened={archiveOpened}
onClose={() => setArchiveOpened(false)}
completedReminders={grouped.completed}
onRestore={onRestore || (() => {})}
onDelete={onDelete || (() => {})}
/>
</> </>
); );
} }

View File

@ -199,7 +199,8 @@ export function getHolidaysForYear(year: number): Array<Holiday & { date: Date }
for (const holiday of HOLIDAYS) { for (const holiday of HOLIDAYS) {
if (holiday.isLunar && holiday.lunarMonth && holiday.lunarDay) { if (holiday.isLunar && holiday.lunarMonth && holiday.lunarDay) {
try { try {
const lunar = Lunar.fromYmd(year, holiday.lunarMonth, holiday.lunarDay); // 使用 Lunar 构造函数创建农历对象,再获取对应的公历日期
const lunar = new Lunar(year, holiday.lunarMonth, holiday.lunarDay);
const solar = lunar.getSolar(); const solar = lunar.getSolar();
result.push({ result.push({
...holiday, ...holiday,

164
src/pages/ArchivePage.tsx Normal file
View File

@ -0,0 +1,164 @@
import { useEffect } from 'react';
import {
Container,
Title,
Text,
Stack,
Paper,
Group,
Button,
ActionIcon,
} from '@mantine/core';
import { IconArrowLeft, IconRotateClockwise, IconTrash, IconArchive } from '@tabler/icons-react';
import { useNavigate } from 'react-router-dom';
import { useAppStore } from '../stores';
import type { Event } from '../types';
export function ArchivePage() {
const navigate = useNavigate();
const events = useAppStore((state) => state.events);
const updateEventById = useAppStore((state) => state.updateEventById);
const deleteEventById = useAppStore((state) => state.deleteEventById);
// 页面加载时检查登录状态
useEffect(() => {
const isAuthenticated = useAppStore.getState().isAuthenticated;
if (!isAuthenticated) {
navigate('/login', { replace: true });
}
}, [navigate]);
// 获取已归档的提醒(已过期且已勾选的)
const archivedReminders = events.filter(
(e) => e.type === 'reminder' && e.is_completed
).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
const handleRestore = async (event: Event) => {
await updateEventById(event.id, { is_completed: false });
};
const handleDelete = async (event: Event) => {
await deleteEventById(event.id);
};
return (
<div
style={{
minHeight: '100vh',
background: '#faf9f7',
}}
>
<Container size="xl" py="md">
{/* Header */}
<Group mb="lg" style={{ flexShrink: 0 }}>
<Button
variant="subtle"
color="gray"
size="xs"
leftSection={<IconArrowLeft size={14} />}
onClick={() => navigate(-1)}
style={{
letterSpacing: '0.1em',
borderRadius: 2,
}}
>
</Button>
<Group gap="sm">
<IconArchive size={20} color="#666" />
<Title
order={2}
style={{
fontWeight: 300,
fontSize: '1.25rem',
letterSpacing: '0.15em',
color: '#1a1a1a',
}}
>
</Title>
</Group>
</Group>
{/* Content */}
<Paper p="md" withBorder radius={4} style={{ maxWidth: 600 }}>
{archivedReminders.length === 0 ? (
<Stack align="center" justify="center" py="xl">
<Text c="#999" size="sm" ta="center" style={{ letterSpacing: '0.05em' }}>
</Text>
<Text size="xs" c="#bbb" ta="center" mt={4}>
</Text>
</Stack>
) : (
<Stack gap="sm">
{archivedReminders.map((event) => (
<Paper
key={event.id}
p="sm"
radius={2}
withBorder
style={{
background: 'rgba(0, 0, 0, 0.02)',
borderColor: 'rgba(0, 0, 0, 0.06)',
}}
>
<Group justify="space-between" wrap="nowrap">
<Stack gap={4} style={{ flex: 1 }}>
<Text
size="sm"
fw={400}
lineClamp={1}
style={{
textDecoration: 'line-through',
color: '#999',
letterSpacing: '0.03em',
}}
>
{event.title}
</Text>
<Text size="xs" c="#bbb">
{new Date(event.date).toLocaleString('zh-CN', {
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</Text>
{event.content && (
<Text size="xs" c="#bbb" lineClamp={1}>
{event.content}
</Text>
)}
</Stack>
<Group gap={4}>
<ActionIcon
size="sm"
variant="subtle"
onClick={() => handleRestore(event)}
style={{ color: '#666' }}
title="恢复"
>
<IconRotateClockwise size={14} />
</ActionIcon>
<ActionIcon
size="sm"
variant="subtle"
onClick={() => handleDelete(event)}
style={{ color: '#999' }}
title="删除"
>
<IconTrash size={14} />
</ActionIcon>
</Group>
</Group>
</Paper>
))}
</Stack>
)}
</Paper>
</Container>
</div>
);
}

View File

@ -14,8 +14,9 @@ import {
Stack, Stack,
} from '@mantine/core'; } from '@mantine/core';
import { DatePickerInput, TimeInput } from '@mantine/dates'; import { DatePickerInput, TimeInput } from '@mantine/dates';
import { IconLogout } from '@tabler/icons-react'; import { IconLogout, IconSettings, IconArchive } from '@tabler/icons-react';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { useNavigate } from 'react-router-dom';
import { useAppStore } from '../stores'; import { useAppStore } from '../stores';
import { AnniversaryList } from '../components/anniversary/AnniversaryList'; import { AnniversaryList } from '../components/anniversary/AnniversaryList';
import { ReminderList } from '../components/reminder/ReminderList'; import { ReminderList } from '../components/reminder/ReminderList';
@ -24,6 +25,7 @@ import { FloatingAIChat } from '../components/ai/FloatingAIChat';
import type { Event, EventType, RepeatType } from '../types'; import type { Event, EventType, RepeatType } from '../types';
export function HomePage() { export function HomePage() {
const navigate = useNavigate();
const user = useAppStore((state) => state.user); const user = useAppStore((state) => state.user);
const logout = useAppStore((state) => state.logout); const logout = useAppStore((state) => state.logout);
const checkAuth = useAppStore((state) => state.checkAuth); const checkAuth = useAppStore((state) => state.checkAuth);
@ -124,10 +126,15 @@ export function HomePage() {
const handleToggleComplete = async (event: Event) => { const handleToggleComplete = async (event: Event) => {
if (event.type !== 'reminder') return; if (event.type !== 'reminder') return;
await updateEventById(event.id, { // 使用当前期望的状态(取反)
is_completed: !event.is_completed, const newCompleted = !event.is_completed;
const result = await updateEventById(event.id, {
is_completed: newCompleted,
}); });
fetchEvents(); if (result.error) {
console.error('更新失败:', result.error);
}
// 乐观更新已处理 UI 响应,无需 fetchEvents
}; };
const handleDelete = async (event: Event) => { const handleDelete = async (event: Event) => {
@ -183,6 +190,34 @@ export function HomePage() {
</Title> </Title>
<Group> <Group>
{/* 归档入口 - 一直显示 */}
<Button
variant="subtle"
color="gray"
size="xs"
leftSection={<IconArchive size={12} />}
onClick={() => navigate('/archive')}
style={{
letterSpacing: '0.1em',
borderRadius: 2,
}}
>
</Button>
{/* 设置入口 */}
<Button
variant="subtle"
color="gray"
size="xs"
leftSection={<IconSettings size={12} />}
onClick={() => navigate('/settings')}
style={{
letterSpacing: '0.1em',
borderRadius: 2,
}}
>
</Button>
<Text <Text
size="xs" size="xs"
c="#888" c="#888"

View File

@ -0,0 +1,92 @@
import { useEffect } from 'react';
import {
Container,
Title,
Text,
Switch,
Stack,
Paper,
Group,
Button,
} from '@mantine/core';
import { IconArrowLeft, IconSettings } from '@tabler/icons-react';
import { useNavigate } from 'react-router-dom';
import { useAppStore } from '../stores';
export function SettingsPage() {
const navigate = useNavigate();
const settings = useAppStore((state) => state.settings);
const updateSettings = useAppStore((state) => state.updateSettings);
// 页面加载时检查登录状态
useEffect(() => {
const isAuthenticated = useAppStore.getState().isAuthenticated;
if (!isAuthenticated) {
navigate('/login', { replace: true });
}
}, [navigate]);
return (
<div
style={{
minHeight: '100vh',
background: '#faf9f7',
}}
>
<Container size="xl" py="md">
{/* Header */}
<Group mb="lg" style={{ flexShrink: 0 }}>
<Button
variant="subtle"
color="gray"
size="xs"
leftSection={<IconArrowLeft size={14} />}
onClick={() => navigate(-1)}
style={{
letterSpacing: '0.1em',
borderRadius: 2,
}}
>
</Button>
<Title
order={2}
style={{
fontWeight: 300,
fontSize: '1.25rem',
letterSpacing: '0.15em',
color: '#1a1a1a',
}}
>
</Title>
</Group>
<Paper p="lg" withBorder radius={4} style={{ maxWidth: 500 }}>
<Stack gap="lg">
{/* 节假日设置 */}
<Group justify="space-between">
<Group gap="sm">
<IconSettings size={18} color="#666" />
<Stack gap={2}>
<Text size="sm" fw={400} style={{ letterSpacing: '0.05em', color: '#1a1a1a' }}>
</Text>
<Text size="xs" c="#999" style={{ letterSpacing: '0.03em' }}>
3
</Text>
</Stack>
</Group>
<Switch
checked={settings.showHolidays}
onChange={(e) => updateSettings({ showHolidays: e.currentTarget.checked })}
size="sm"
color="#1a1a1a"
/>
</Group>
</Stack>
</Paper>
</Container>
</div>
);
}

View File

@ -3,6 +3,8 @@ import { LandingPage } from './pages/LandingPage';
import { LoginPage } from './pages/LoginPage'; import { LoginPage } from './pages/LoginPage';
import { RegisterPage } from './pages/RegisterPage'; import { RegisterPage } from './pages/RegisterPage';
import { HomePage } from './pages/HomePage'; import { HomePage } from './pages/HomePage';
import { SettingsPage } from './pages/SettingsPage';
import { ArchivePage } from './pages/ArchivePage';
import { useAppStore } from './stores'; import { useAppStore } from './stores';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
@ -143,4 +145,20 @@ export const router = createBrowserRouter([
</ProtectedRoute> </ProtectedRoute>
), ),
}, },
{
path: '/settings',
element: (
<ProtectedRoute>
<SettingsPage />
</ProtectedRoute>
),
},
{
path: '/archive',
element: (
<ProtectedRoute>
<ArchivePage />
</ProtectedRoute>
),
},
]); ]);

View File

@ -1,14 +1,28 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist, createJSONStorage } from 'zustand/middleware';
import type { User, Event, Note, AIConversation, EventType } from '../types'; import type { User, Event, Note, AIConversation, EventType } from '../types';
import { api } from '../services/api'; import { api } from '../services/api';
// 应用设置类型
interface AppSettings {
showHolidays: boolean; // 是否显示节假日
}
// 默认设置
const defaultSettings: AppSettings = {
showHolidays: true,
};
interface AppState { interface AppState {
// Auth state // Auth state
user: User | null; user: User | null;
isAuthenticated: boolean; isAuthenticated: boolean;
isLoading: boolean; isLoading: boolean;
// Settings state
settings: AppSettings;
updateSettings: (settings: Partial<AppSettings>) => void;
// Data state // Data state
events: Event[]; events: Event[];
notes: Note | null; notes: Note | null;
@ -48,10 +62,17 @@ export const useAppStore = create<AppState>()(
user: null, user: null,
isAuthenticated: false, isAuthenticated: false,
isLoading: true, isLoading: true,
settings: defaultSettings,
events: [], events: [],
notes: null, notes: null,
conversations: [], conversations: [],
// Settings
updateSettings: (newSettings) =>
set((state) => ({
settings: { ...state.settings, ...newSettings },
})),
// Setters // Setters
setUser: (user) => set({ user, isAuthenticated: !!user }), setUser: (user) => set({ user, isAuthenticated: !!user }),
setLoading: (isLoading) => set({ isLoading }), setLoading: (isLoading) => set({ isLoading }),
@ -113,12 +134,21 @@ export const useAppStore = create<AppState>()(
updateEventById: async (id, event) => { updateEventById: async (id, event) => {
try { try {
const updated = await api.events.update(id, event); // 乐观更新:立即更新本地状态
set((state) => ({ set((state) => ({
events: state.events.map((e) => (e.id === id ? updated : e)), events: state.events.map((e) =>
e.id === id ? { ...e, ...event } : e
),
})); }));
// 发送 API 请求
await api.events.update(id, event);
// 乐观更新已生效,不需要用 API 返回数据覆盖
// 避免 API 返回数据不完整导致状态丢失
return { error: null }; return { error: null };
} catch (error: any) { } catch (error: any) {
// 失败时回滚,重新获取数据
const events = await api.events.list();
set({ events });
return { error: error.message || '更新失败' }; return { error: error.message || '更新失败' };
} }
}, },
@ -143,7 +173,6 @@ export const useAppStore = create<AppState>()(
set({ user, isAuthenticated: true }); set({ user, isAuthenticated: true });
return { error: null }; return { error: null };
} catch (error: any) { } catch (error: any) {
// 确保返回字符串错误信息
const errorMessage = error.message || '登录失败,请检查邮箱和密码'; const errorMessage = error.message || '登录失败,请检查邮箱和密码';
return { error: errorMessage }; return { error: errorMessage };
} }
@ -156,7 +185,6 @@ export const useAppStore = create<AppState>()(
set({ user, isAuthenticated: true }); set({ user, isAuthenticated: true });
return { error: null }; return { error: null };
} catch (error: any) { } catch (error: any) {
// 确保返回字符串错误信息
const errorMessage = error.message || '注册失败,请稍后重试'; const errorMessage = error.message || '注册失败,请稍后重试';
return { error: errorMessage }; return { error: errorMessage };
} }
@ -201,9 +229,11 @@ export const useAppStore = create<AppState>()(
}), }),
{ {
name: 'qia-storage', name: 'qia-storage',
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({ partialize: (state) => ({
user: state.user, user: state.user,
isAuthenticated: state.isAuthenticated, isAuthenticated: state.isAuthenticated,
settings: state.settings,
}), }),
} }
) )

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client

View File

@ -1 +0,0 @@
/e/qia/client