feat: 优化编辑窗口UI

- 优化日期时间选择器为组合布局(同一行按钮)
- 优先级改名为颜色
- 提醒类型移除农历选项
- 使用Popover优化选择器交互
This commit is contained in:
ddshi 2026-02-06 13:44:07 +08:00
parent f0cbd0e33c
commit 306cb41516
4 changed files with 132 additions and 66 deletions

View File

@ -73,6 +73,7 @@ export function ReminderCard({
// 使用全局状态管理右键菜单
const openEventId = useContextMenuStore((state) => state.openEventId);
const menuPosition = useContextMenuStore((state) => state.menuPosition);
const { openMenu, closeMenu } = useContextMenuStore();
const isMenuOpen = openEventId === event.id;
@ -84,10 +85,10 @@ export function ReminderCard({
// 右键点击处理
const handleContextMenu = useCallback((e: React.MouseEvent) => {
e.preventDefault();
console.log('[ContextMenu] 右键点击事件, eventId:', event.id);
console.log('[ContextMenu] 右键点击事件, eventId:', event.id, 'x:', e.clientX, 'y:', e.clientY);
// 打开菜单
openMenu(event.id);
// 打开菜单,传入鼠标位置
openMenu(event.id, { x: e.clientX, y: e.clientY });
}, [event.id, openMenu]);
// 点击外部关闭菜单
@ -120,25 +121,35 @@ export function ReminderCard({
// 计算菜单位置(带边缘保护)
const getMenuPosition = () => {
if (!cardRef.current) return { left: 0, top: 0 };
const cardRect = cardRef.current.getBoundingClientRect();
const menuWidth = 180; // 菜单宽度
const menuHeight = 150; // 估算菜单高度
const padding = 8; // 边缘padding
// 计算左侧位置
let left = cardRect.left;
// 如果有鼠标位置,使用鼠标位置
let left = padding;
let top = padding;
if (menuPosition) {
// 水平位置跟随鼠标,添加偏移避免遮挡
left = menuPosition.x + 4;
// 垂直位置优先显示在鼠标下方
top = menuPosition.y + 4;
} else if (cardRef.current) {
// 回退到卡片位置
const cardRect = cardRef.current.getBoundingClientRect();
left = cardRect.left;
top = cardRect.bottom + 4;
}
// 边缘保护:确保不超出视口
if (left + menuWidth > window.innerWidth - padding) {
left = window.innerWidth - menuWidth - padding;
}
if (left < padding) left = padding;
// 计算顶部位置(优先显示在下方)
let top = cardRect.bottom + 4;
if (top + menuHeight > window.innerHeight - padding) {
// 如果下方空间不足,显示在上方
top = cardRect.top - menuHeight - 4;
top = menuPosition ? menuPosition.y - menuHeight - 4 : top;
}
if (top < padding) top = padding;

View File

@ -62,8 +62,12 @@ export function ArchivePage() {
if (e.type !== 'reminder' || !e.is_completed) return false;
if (!e.date) return false; // 跳过无日期的
const eventDate = new Date(e.date);
const now = new Date();
return eventDate < now; // 仅已过期的
// 获取今天的开始时间(只比较日期部分)
const today = new Date();
today.setHours(0, 0, 0, 0);
const eventDateOnly = new Date(eventDate.getFullYear(), eventDate.getMonth(), eventDate.getDate());
// 仅显示已过期的(日期早于今天)
return eventDateOnly < today;
}).sort((a, b) => {
if (!a.date || !b.date) return 0;
return new Date(b.date).getTime() - new Date(a.date).getTime();

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect, useState, useRef } from 'react';
import {
Container,
Title,
@ -12,9 +12,10 @@ import {
Select,
Stack,
Box,
Popover,
} from '@mantine/core';
import { DatePickerInput, TimeInput } from '@mantine/dates';
import { IconLogout, IconSettings } from '@tabler/icons-react';
import { IconCalendar, IconClock, IconSettings, IconLogout } from '@tabler/icons-react';
import { useDisclosure } from '@mantine/hooks';
import { useNavigate } from 'react-router-dom';
import { useAppStore } from '../stores';
@ -460,56 +461,96 @@ export function HomePage() {
/>
)}
{/* Date */}
<DatePickerInput
label={
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 4 }}>
</Text>
}
placeholder="选择日期"
value={formDate}
onChange={(value) => setFormDate(value as Date | null)}
required
styles={{
input: {
borderRadius: 2,
background: '#faf9f7',
},
}}
/>
{/* Date & Time - Combined selector */}
<Box>
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 8 }}>
</Text>
<Group gap={8} grow>
{/* Date selector */}
<Popover position="bottom-start" withArrow>
<Popover.Target>
<Button
variant="outline"
size="xs"
leftSection={<IconCalendar size={14} />}
style={{
flex: 1,
borderColor: '#ddd',
color: '#666',
borderRadius: 2,
height: 32,
justifyContent: 'flex-start',
}}
>
{formDate ? new Date(formDate).toLocaleDateString('zh-CN', {
month: 'numeric',
day: 'numeric',
}) : '选择日期'}
</Button>
</Popover.Target>
<Popover.Dropdown style={{ padding: 8 }}>
<DatePickerInput
value={formDate}
onChange={(value) => setFormDate(value as Date | null)}
styles={{
input: {
borderRadius: 2,
},
}}
/>
</Popover.Dropdown>
</Popover>
{/* Time (only for reminders) */}
{formType === 'reminder' && (
<TimeInput
{/* Time selector (only for reminders) */}
{formType === 'reminder' && (
<Popover position="bottom-start" withArrow>
<Popover.Target>
<Button
variant="outline"
size="xs"
leftSection={<IconClock size={14} />}
style={{
flex: 1,
borderColor: '#ddd',
color: '#666',
borderRadius: 2,
height: 32,
justifyContent: 'flex-start',
}}
>
{formTime || '选择时间'}
</Button>
</Popover.Target>
<Popover.Dropdown style={{ padding: 8 }}>
<TimeInput
value={formTime}
onChange={(e) => setFormTime(e.target.value)}
styles={{
input: {
borderRadius: 2,
},
}}
/>
</Popover.Dropdown>
</Popover>
)}
</Group>
</Box>
{/* Lunar switch (only for anniversaries) */}
{formType === 'anniversary' && (
<Switch
label={
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 4 }}>
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em' }}>
</Text>
}
placeholder="选择时间"
value={formTime}
onChange={(e) => setFormTime(e.target.value)}
styles={{
input: {
borderRadius: 2,
background: '#faf9f7',
},
}}
checked={formIsLunar}
onChange={(e) => setFormIsLunar(e.currentTarget.checked)}
/>
)}
{/* Lunar switch */}
<Switch
label={
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em' }}>
</Text>
}
checked={formIsLunar}
onChange={(e) => setFormIsLunar(e.currentTarget.checked)}
/>
{/* Repeat type */}
<Select
label={
@ -534,11 +575,11 @@ export function HomePage() {
}}
/>
{/* Priority (only for reminders) */}
{/* Color (only for reminders) */}
{formType === 'reminder' && (
<Box>
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 8 }}>
</Text>
<Group gap={8}>
{([

View File

@ -1,33 +1,43 @@
import { create } from 'zustand';
// 右键菜单位置类型
interface MenuPosition {
x: number;
y: number;
}
// 右键菜单状态管理
interface ContextMenuState {
// 当前打开菜单的事件IDnull表示没有菜单打开
openEventId: string | null;
// 菜单位置
menuPosition: MenuPosition | null;
// 打开菜单
openMenu: (eventId: string) => void;
openMenu: (eventId: string, position?: MenuPosition) => void;
// 关闭菜单
closeMenu: () => void;
// 切换菜单
toggleMenu: (eventId: string) => void;
toggleMenu: (eventId: string, position?: MenuPosition) => void;
// 点击其他位置关闭菜单
closeOnClickOutside: (eventId: string) => void;
}
export const useContextMenuStore = create<ContextMenuState>((set) => ({
openEventId: null,
menuPosition: null,
openMenu: (eventId: string) => {
set({ openEventId: eventId });
openMenu: (eventId: string, position?: MenuPosition) => {
set({ openEventId: eventId, menuPosition: position || null });
},
closeMenu: () => {
set({ openEventId: null });
set({ openEventId: null, menuPosition: null });
},
toggleMenu: (eventId: string) => {
toggleMenu: (eventId: string, position?: MenuPosition) => {
set((state) => ({
openEventId: state.openEventId === eventId ? null : eventId,
menuPosition: state.openEventId === eventId ? null : (position || null),
}));
},