feat: 优化提醒卡片样式和列表滚动功能
- 调整正常提醒卡片布局:左侧 checkbox+标题+内容,右侧日期时间 - 移除正常卡片悬停时的编辑按钮 - 纪念日/提醒/便签列表支持独立滚动 - 页面整体禁用滚动,支持 Ctrl+滚轮缩放 - 滚动条样式优化:默认隐藏,悬停时显示淡雅样式 Co-Authored-By: Claude (MiniMax-M2.1) <noreply@anthropic.com>
This commit is contained in:
parent
7b0afbb27b
commit
3fdee5cab4
@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { Stack, Text, Paper, Group, Button } from '@mantine/core';
|
import { Stack, Text, Paper, Group, Button, Box } from '@mantine/core';
|
||||||
import { IconPlus } from '@tabler/icons-react';
|
import { IconPlus } from '@tabler/icons-react';
|
||||||
import { AnniversaryCard } from './AnniversaryCard';
|
import { AnniversaryCard } from './AnniversaryCard';
|
||||||
import { getHolidaysForYear } from '../../constants/holidays';
|
import { getHolidaysForYear } from '../../constants/holidays';
|
||||||
@ -28,6 +28,42 @@ 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 showHolidays = useAppStore((state) => state.settings?.showHolidays ?? true);
|
||||||
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// 滚动条样式 - 仅在悬停时显示
|
||||||
|
const scrollbarStyle = `
|
||||||
|
.anniversary-scroll::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
|
.anniversary-scroll::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.anniversary-scroll::-webkit-scrollbar-thumb {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.anniversary-scroll:hover::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 处理滚轮事件,实现列表独立滚动
|
||||||
|
const handleWheel = (e: React.WheelEvent) => {
|
||||||
|
const container = scrollContainerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = container;
|
||||||
|
// 使用 1px 缓冲避免浮点数精度问题
|
||||||
|
const isAtTop = scrollTop <= 0;
|
||||||
|
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
|
||||||
|
|
||||||
|
// 如果已经滚动到顶部且向下滚动,或者已经滚动到底部且向上滚动,则阻止事件冒泡
|
||||||
|
if ((isAtTop && e.deltaY > 0) || (isAtBottom && e.deltaY < 0)) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
// 如果在滚动范围内,允许事件继续传递以实现正常滚动
|
||||||
|
};
|
||||||
|
|
||||||
// 获取内置节假日
|
// 获取内置节假日
|
||||||
const builtInHolidays = useMemo(() => {
|
const builtInHolidays = useMemo(() => {
|
||||||
@ -88,8 +124,9 @@ export function AnniversaryList({ events, onEventClick, onAddClick }: Anniversar
|
|||||||
// 空状态
|
// 空状态
|
||||||
if (allAnniversaries.total === 0) {
|
if (allAnniversaries.total === 0) {
|
||||||
return (
|
return (
|
||||||
<Paper p="md" withBorder radius={4} h="100%">
|
<Paper p="md" withBorder radius={4} style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
<Stack align="center" justify="center" h="100%">
|
<Stack align="center" justify="center" style={{ flex: 1 }}>
|
||||||
|
<style>{scrollbarStyle}</style>
|
||||||
<Text c="#999" size="sm" style={{ letterSpacing: '0.05em' }}>
|
<Text c="#999" size="sm" style={{ letterSpacing: '0.05em' }}>
|
||||||
暂无纪念日
|
暂无纪念日
|
||||||
</Text>
|
</Text>
|
||||||
@ -112,8 +149,10 @@ export function AnniversaryList({ events, onEventClick, onAddClick }: Anniversar
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper p="md" withBorder radius={4} h="100%">
|
<>
|
||||||
<Group justify="space-between" mb="sm">
|
<style>{scrollbarStyle}</style>
|
||||||
|
<Paper p="md" withBorder radius={4} data-scroll-container="anniversary" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
<Group justify="space-between" mb="sm" style={{ flexShrink: 0 }}>
|
||||||
<Group gap={8}>
|
<Group gap={8}>
|
||||||
<Text fw={500} size="sm" style={{ letterSpacing: '0.1em', color: '#1a1a1a' }}>
|
<Text fw={500} size="sm" style={{ letterSpacing: '0.1em', color: '#1a1a1a' }}>
|
||||||
纪念日
|
纪念日
|
||||||
@ -136,7 +175,13 @@ export function AnniversaryList({ events, onEventClick, onAddClick }: Anniversar
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Stack gap="xs" style={{ maxHeight: 'calc(100% - 40px)', overflowY: 'auto' }}>
|
<Box
|
||||||
|
ref={scrollContainerRef}
|
||||||
|
onWheel={handleWheel}
|
||||||
|
className="anniversary-scroll"
|
||||||
|
style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}
|
||||||
|
>
|
||||||
|
<Stack gap="xs">
|
||||||
{/* 内置节假日 */}
|
{/* 内置节假日 */}
|
||||||
{allAnniversaries.builtIn.length > 0 && (
|
{allAnniversaries.builtIn.length > 0 && (
|
||||||
<>
|
<>
|
||||||
@ -194,6 +239,8 @@ export function AnniversaryList({ events, onEventClick, onAddClick }: Anniversar
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
Paper,
|
Paper,
|
||||||
Textarea,
|
Textarea,
|
||||||
@ -47,6 +47,24 @@ export function NoteEditor({ onSave }: NoteEditorProps) {
|
|||||||
// Auto-save with 3 second debounce
|
// Auto-save with 3 second debounce
|
||||||
const [debouncedContent] = useDebouncedValue(content, 3000);
|
const [debouncedContent] = useDebouncedValue(content, 3000);
|
||||||
|
|
||||||
|
// 滚动条样式 - 仅在悬停时显示
|
||||||
|
const scrollbarStyle = `
|
||||||
|
.note-scroll::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
|
.note-scroll::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.note-scroll::-webkit-scrollbar-thumb {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.note-scroll:hover::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debouncedContent !== undefined && notes && debouncedContent !== content) {
|
if (debouncedContent !== undefined && notes && debouncedContent !== content) {
|
||||||
handleSave(debouncedContent);
|
handleSave(debouncedContent);
|
||||||
@ -87,8 +105,28 @@ export function NoteEditor({ onSave }: NoteEditorProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// 处理滚轮事件,实现列表独立滚动
|
||||||
|
const handleWheel = (e: React.WheelEvent) => {
|
||||||
|
const container = scrollContainerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = container;
|
||||||
|
// 使用 1px 缓冲避免浮点数精度问题
|
||||||
|
const isAtTop = scrollTop <= 0;
|
||||||
|
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
|
||||||
|
|
||||||
|
// 如果已经滚动到顶部且向下滚动,或者已经滚动到底部且向上滚动,则阻止事件冒泡
|
||||||
|
if ((isAtTop && e.deltaY > 0) || (isAtBottom && e.deltaY < 0)) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
// 如果在滚动范围内,允许事件继续传递以实现正常滚动
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper p="md" withBorder radius={4} h="100%" style={{ display: 'flex', flexDirection: 'column' }}>
|
<Paper p="md" withBorder radius={4} style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
<style>{scrollbarStyle}</style>
|
||||||
<Stack gap="sm" style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
<Stack gap="sm" style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group justify="space-between" style={{ flexShrink: 0 }}>
|
<Group justify="space-between" style={{ flexShrink: 0 }}>
|
||||||
@ -119,7 +157,13 @@ export function NoteEditor({ onSave }: NoteEditorProps) {
|
|||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{/* Editor/Preview Area */}
|
{/* Editor/Preview Area */}
|
||||||
<Box style={{ flex: 1, minHeight: 0, display: 'flex' }}>
|
<Box
|
||||||
|
ref={scrollContainerRef}
|
||||||
|
onWheel={handleWheel}
|
||||||
|
data-scroll-container="note"
|
||||||
|
className="note-scroll"
|
||||||
|
style={{ flex: 1, minHeight: 0, display: 'flex', overflowY: 'auto' }}
|
||||||
|
>
|
||||||
{viewMode === 'edit' && (
|
{viewMode === 'edit' && (
|
||||||
<Textarea
|
<Textarea
|
||||||
value={content}
|
value={content}
|
||||||
@ -138,7 +182,11 @@ export function NoteEditor({ onSave }: NoteEditorProps) {
|
|||||||
'&:focus': { outline: 'none' },
|
'&:focus': { outline: 'none' },
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
style={{ flex: 1, width: '100%' }}
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
width: '100%',
|
||||||
|
overflowY: 'auto',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useMemo, useState, useEffect } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { Paper, Text, Checkbox, Group, Stack, ActionIcon } from '@mantine/core';
|
import { Paper, Text, Checkbox, Group, Stack, ActionIcon, Box } from '@mantine/core';
|
||||||
import { IconDots, IconArrowForward } from '@tabler/icons-react';
|
import { IconDots, IconArrowForward } from '@tabler/icons-react';
|
||||||
import type { Event } from '../../types';
|
import type { Event } from '../../types';
|
||||||
|
|
||||||
@ -129,9 +129,94 @@ export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, i
|
|||||||
100% { opacity: 0; transform: translateX(20px); }
|
100% { opacity: 0; transform: translateX(20px); }
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
|
{/* 正常提醒卡片:左右分栏布局 */}
|
||||||
|
{!isMissed ? (
|
||||||
|
<Group justify="space-between" wrap="nowrap" align="flex-start">
|
||||||
|
{/* 左侧:Checkbox + 标题 + 内容 */}
|
||||||
|
<Box
|
||||||
|
style={{ flex: 1, minWidth: 0, cursor: 'pointer' }}
|
||||||
|
onClick={(e) => {
|
||||||
|
// 阻止事件冒泡到 Card,避免重复触发
|
||||||
|
e.stopPropagation();
|
||||||
|
onClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Group gap="sm" wrap="nowrap" align="flex-start">
|
||||||
|
{/* Checkbox - 单独处理,不触发卡片点击 */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
paddingTop: 2,
|
||||||
|
animation: isAnimating ? 'reminder-card-pulse 0.3s ease-out' : 'none',
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
// 阻止事件冒泡,避免触发卡片点击
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={isCompleted}
|
||||||
|
onChange={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onToggle();
|
||||||
|
}}
|
||||||
|
size="xs"
|
||||||
|
color="#1a1a1a"
|
||||||
|
style={{
|
||||||
|
animation: isAnimating ? 'reminder-card-pulse 0.3s ease-out' : 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 标题 */}
|
||||||
|
<Text
|
||||||
|
fw={400}
|
||||||
|
size="xs"
|
||||||
|
lineClamp={1}
|
||||||
|
style={{
|
||||||
|
textDecoration: isCompleted ? 'line-through' : 'none',
|
||||||
|
color: getTextColor(),
|
||||||
|
letterSpacing: '0.03em',
|
||||||
|
transition: 'color 0.2s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event.title}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* 内容在标题下方 */}
|
||||||
|
{event.content && (
|
||||||
|
<Text
|
||||||
|
size="xs"
|
||||||
|
c={isCompleted ? '#bbb' : '#999'}
|
||||||
|
lineClamp={1}
|
||||||
|
style={{
|
||||||
|
marginLeft: 28,
|
||||||
|
opacity: isCompleted ? 0.6 : 1,
|
||||||
|
transition: 'opacity 0.2s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event.content}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 右侧:日期时间 */}
|
||||||
|
<Box style={{ flex: '0 0 auto', minWidth: 0, paddingLeft: 12 }}>
|
||||||
|
<Text
|
||||||
|
size="xs"
|
||||||
|
c={getTimeColor()}
|
||||||
|
style={{ letterSpacing: '0.05em', whiteSpace: 'nowrap' }}
|
||||||
|
>
|
||||||
|
{timeInfo.timeStr}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
|
) : (
|
||||||
|
/* 逾期提醒卡片:保持原有结构 */
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<Group gap="sm" style={{ flex: 1, minWidth: 0 }}>
|
<Group gap="sm" style={{ flex: 1, minWidth: 0 }}>
|
||||||
{/* Checkbox */}
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -144,7 +229,6 @@ export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, i
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (isMissed && !isCompleted) {
|
if (isMissed && !isCompleted) {
|
||||||
// 已过期提醒:先播放动画,动画结束后再触发 toggle
|
|
||||||
setIsAnimating(true);
|
setIsAnimating(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsAnimating(false);
|
setIsAnimating(false);
|
||||||
@ -160,7 +244,6 @@ export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, i
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Stack gap={2} style={{ flex: 1, minWidth: 0 }} onClick={onClick}>
|
<Stack gap={2} style={{ flex: 1, minWidth: 0 }} onClick={onClick}>
|
||||||
{/* Title */}
|
|
||||||
<Text
|
<Text
|
||||||
fw={400}
|
fw={400}
|
||||||
size="xs"
|
size="xs"
|
||||||
@ -174,8 +257,6 @@ export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, i
|
|||||||
>
|
>
|
||||||
{event.title}
|
{event.title}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{/* Time */}
|
|
||||||
<Text
|
<Text
|
||||||
size="xs"
|
size="xs"
|
||||||
c={getTimeColor()}
|
c={getTimeColor()}
|
||||||
@ -183,8 +264,6 @@ export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, i
|
|||||||
>
|
>
|
||||||
{timeInfo.timeStr}
|
{timeInfo.timeStr}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{/* Content preview - 始终显示 */}
|
|
||||||
{event.content && (
|
{event.content && (
|
||||||
<Text
|
<Text
|
||||||
size="xs"
|
size="xs"
|
||||||
@ -201,10 +280,8 @@ export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, i
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{/* Quick actions */}
|
|
||||||
<Group gap={4}>
|
<Group gap={4}>
|
||||||
{/* 顺延按钮 - 仅在逾期状态显示 */}
|
{onPostpone && (
|
||||||
{isMissed && onPostpone && (
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
@ -242,6 +319,7 @@ export function ReminderCard({ event, onToggle, onClick, onDelete, onPostpone, i
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
@ -6,6 +6,7 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
Button,
|
Button,
|
||||||
Alert,
|
Alert,
|
||||||
|
Box,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconPlus,
|
IconPlus,
|
||||||
@ -20,7 +21,6 @@ interface ReminderListProps {
|
|||||||
onToggleComplete: (event: Event) => void;
|
onToggleComplete: (event: Event) => void;
|
||||||
onAddClick: () => void;
|
onAddClick: () => void;
|
||||||
onDelete?: (event: Event) => void;
|
onDelete?: (event: Event) => void;
|
||||||
onRestore?: (event: Event) => void;
|
|
||||||
onPostpone?: (event: Event) => void;
|
onPostpone?: (event: Event) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,9 +30,44 @@ export function ReminderList({
|
|||||||
onToggleComplete,
|
onToggleComplete,
|
||||||
onAddClick,
|
onAddClick,
|
||||||
onDelete,
|
onDelete,
|
||||||
onRestore,
|
|
||||||
onPostpone,
|
onPostpone,
|
||||||
}: ReminderListProps) {
|
}: ReminderListProps) {
|
||||||
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// 滚动条样式 - 仅在悬停时显示
|
||||||
|
const scrollbarStyle = `
|
||||||
|
.reminder-scroll::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
|
.reminder-scroll::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.reminder-scroll::-webkit-scrollbar-thumb {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.reminder-scroll:hover::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 处理滚轮事件,实现列表独立滚动
|
||||||
|
const handleWheel = (e: React.WheelEvent) => {
|
||||||
|
const container = scrollContainerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = container;
|
||||||
|
// 使用 1px 缓冲避免浮点数精度问题
|
||||||
|
const isAtTop = scrollTop <= 0;
|
||||||
|
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
|
||||||
|
|
||||||
|
// 如果已经滚动到顶部且向下滚动,或者已经滚动到底部且向上滚动,则阻止事件冒泡
|
||||||
|
if ((isAtTop && e.deltaY > 0) || (isAtBottom && e.deltaY < 0)) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
// 如果在滚动范围内,允许事件继续传递以实现正常滚动
|
||||||
|
};
|
||||||
|
|
||||||
const grouped = useMemo(() => {
|
const grouped = useMemo(() => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@ -109,8 +144,8 @@ export function ReminderList({
|
|||||||
// 空状态
|
// 空状态
|
||||||
if (!hasActiveReminders) {
|
if (!hasActiveReminders) {
|
||||||
return (
|
return (
|
||||||
<Paper p="md" withBorder radius={4} h="100%">
|
<Paper p="md" withBorder radius={4} style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
<Stack align="center" justify="center" h="100%">
|
<Stack align="center" justify="center" style={{ flex: 1 }}>
|
||||||
<Text c="#999" size="sm" ta="center" style={{ letterSpacing: '0.05em' }}>
|
<Text c="#999" size="sm" ta="center" style={{ letterSpacing: '0.05em' }}>
|
||||||
暂无提醒
|
暂无提醒
|
||||||
</Text>
|
</Text>
|
||||||
@ -141,7 +176,8 @@ export function ReminderList({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Paper p="md" withBorder radius={4} h="100%" style={{ display: 'flex', flexDirection: 'column' }}>
|
<style>{scrollbarStyle}</style>
|
||||||
|
<Paper p="md" withBorder radius={4} data-scroll-container="reminder" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group justify="space-between" mb="sm" style={{ flexShrink: 0 }}>
|
<Group justify="space-between" mb="sm" style={{ flexShrink: 0 }}>
|
||||||
<Group gap={8}>
|
<Group gap={8}>
|
||||||
@ -169,7 +205,17 @@ export function ReminderList({
|
|||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<Stack gap="xs" style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}>
|
<Box
|
||||||
|
ref={scrollContainerRef}
|
||||||
|
onWheel={handleWheel}
|
||||||
|
className="reminder-scroll"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
overflowY: 'auto',
|
||||||
|
minHeight: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack gap="xs">
|
||||||
{/* 逾期提醒 */}
|
{/* 逾期提醒 */}
|
||||||
{grouped.missed.length > 0 && (
|
{grouped.missed.length > 0 && (
|
||||||
<Alert
|
<Alert
|
||||||
@ -260,6 +306,7 @@ export function ReminderList({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
Grid,
|
|
||||||
Title,
|
Title,
|
||||||
Button,
|
Button,
|
||||||
Group,
|
Group,
|
||||||
@ -159,14 +158,6 @@ export function HomePage() {
|
|||||||
fetchEvents();
|
fetchEvents();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRestore = async (event: Event) => {
|
|
||||||
if (event.type !== 'reminder') return;
|
|
||||||
await updateEventById(event.id, {
|
|
||||||
is_completed: false,
|
|
||||||
});
|
|
||||||
fetchEvents();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePostpone = async (event: Event) => {
|
const handlePostpone = async (event: Event) => {
|
||||||
if (event.type !== 'reminder') return;
|
if (event.type !== 'reminder') return;
|
||||||
// 将日期顺延到今天,保留原事件的时间
|
// 将日期顺延到今天,保留原事件的时间
|
||||||
@ -210,9 +201,11 @@ export function HomePage() {
|
|||||||
style={{
|
style={{
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
background: '#faf9f7',
|
background: '#faf9f7',
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: '100vh',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Container size="xl" py="md" h="100vh" style={{ display: 'flex', flexDirection: 'column' }}>
|
<Container size="xl" py="md" h="100vh" style={{ display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Group justify="space-between" mb="md" style={{ flexShrink: 0 }}>
|
<Group justify="space-between" mb="md" style={{ flexShrink: 0 }}>
|
||||||
<Title
|
<Title
|
||||||
@ -279,40 +272,33 @@ export function HomePage() {
|
|||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{/* Main Content - 3 column horizontal layout */}
|
{/* Main Content - 3 column horizontal layout */}
|
||||||
<Grid grow style={{ flex: 1, minHeight: 0 }} gutter="md">
|
<div style={{ flex: 1, minHeight: 0, display: 'flex', gap: 16, overflow: 'hidden' }}>
|
||||||
{/* Left column - Anniversary */}
|
{/* Left column - Anniversary */}
|
||||||
<Grid.Col span={4}>
|
<div style={{ flex: 1, minWidth: 0, height: '100%', overflow: 'hidden' }}>
|
||||||
<div style={{ height: '100%', minHeight: 0 }}>
|
|
||||||
<AnniversaryList
|
<AnniversaryList
|
||||||
events={events}
|
events={events}
|
||||||
onEventClick={handleEventClick}
|
onEventClick={handleEventClick}
|
||||||
onAddClick={() => handleAddClick('anniversary')}
|
onAddClick={() => handleAddClick('anniversary')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Grid.Col>
|
|
||||||
|
|
||||||
{/* Middle column - Reminder */}
|
{/* Middle column - Reminder */}
|
||||||
<Grid.Col span={4}>
|
<div style={{ flex: 1, minWidth: 0, height: '100%', overflow: 'hidden' }}>
|
||||||
<div style={{ height: '100%', minHeight: 0 }}>
|
|
||||||
<ReminderList
|
<ReminderList
|
||||||
events={events}
|
events={events}
|
||||||
onEventClick={handleEventClick}
|
onEventClick={handleEventClick}
|
||||||
onToggleComplete={handleToggleComplete}
|
onToggleComplete={handleToggleComplete}
|
||||||
onAddClick={() => handleAddClick('reminder')}
|
onAddClick={() => handleAddClick('reminder')}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
onRestore={handleRestore}
|
|
||||||
onPostpone={handlePostpone}
|
onPostpone={handlePostpone}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Grid.Col>
|
|
||||||
|
|
||||||
{/* Right column - Note */}
|
{/* Right column - Note */}
|
||||||
<Grid.Col span={4}>
|
<div style={{ flex: 1, minWidth: 0, height: '100%', overflow: 'hidden' }}>
|
||||||
<div style={{ height: '100%', minHeight: 0 }}>
|
|
||||||
<NoteEditor />
|
<NoteEditor />
|
||||||
</div>
|
</div>
|
||||||
</Grid.Col>
|
</div>
|
||||||
</Grid>
|
|
||||||
|
|
||||||
{/* AI Chat - Floating */}
|
{/* AI Chat - Floating */}
|
||||||
<FloatingAIChat onEventCreated={handleAIEventCreated} />
|
<FloatingAIChat onEventCreated={handleAIEventCreated} />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user