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 openEventId = useContextMenuStore((state) => state.openEventId);
const menuPosition = useContextMenuStore((state) => state.menuPosition);
const { openMenu, closeMenu } = useContextMenuStore(); const { openMenu, closeMenu } = useContextMenuStore();
const isMenuOpen = openEventId === event.id; const isMenuOpen = openEventId === event.id;
@ -84,10 +85,10 @@ export function ReminderCard({
// 右键点击处理 // 右键点击处理
const handleContextMenu = useCallback((e: React.MouseEvent) => { const handleContextMenu = useCallback((e: React.MouseEvent) => {
e.preventDefault(); 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]); }, [event.id, openMenu]);
// 点击外部关闭菜单 // 点击外部关闭菜单
@ -120,25 +121,35 @@ export function ReminderCard({
// 计算菜单位置(带边缘保护) // 计算菜单位置(带边缘保护)
const getMenuPosition = () => { const getMenuPosition = () => {
if (!cardRef.current) return { left: 0, top: 0 };
const cardRect = cardRef.current.getBoundingClientRect();
const menuWidth = 180; // 菜单宽度 const menuWidth = 180; // 菜单宽度
const menuHeight = 150; // 估算菜单高度 const menuHeight = 150; // 估算菜单高度
const padding = 8; // 边缘padding 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) { if (left + menuWidth > window.innerWidth - padding) {
left = window.innerWidth - menuWidth - padding; left = window.innerWidth - menuWidth - padding;
} }
if (left < padding) left = padding; if (left < padding) left = padding;
// 计算顶部位置(优先显示在下方)
let top = cardRect.bottom + 4;
if (top + menuHeight > window.innerHeight - padding) { if (top + menuHeight > window.innerHeight - padding) {
// 如果下方空间不足,显示在上方 // 如果下方空间不足,显示在上方
top = cardRect.top - menuHeight - 4; top = menuPosition ? menuPosition.y - menuHeight - 4 : top;
} }
if (top < padding) top = padding; 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.type !== 'reminder' || !e.is_completed) return false;
if (!e.date) return false; // 跳过无日期的 if (!e.date) return false; // 跳过无日期的
const eventDate = new Date(e.date); 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) => { }).sort((a, b) => {
if (!a.date || !b.date) return 0; if (!a.date || !b.date) return 0;
return new Date(b.date).getTime() - new Date(a.date).getTime(); 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 { import {
Container, Container,
Title, Title,
@ -12,9 +12,10 @@ import {
Select, Select,
Stack, Stack,
Box, Box,
Popover,
} from '@mantine/core'; } from '@mantine/core';
import { DatePickerInput, TimeInput } from '@mantine/dates'; 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 { useDisclosure } from '@mantine/hooks';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useAppStore } from '../stores'; import { useAppStore } from '../stores';
@ -460,46 +461,85 @@ export function HomePage() {
/> />
)} )}
{/* Date */} {/* Date & Time - Combined selector */}
<DatePickerInput <Box>
label={ <Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 8 }}>
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 4 }}>
</Text> </Text>
} <Group gap={8} grow>
placeholder="选择日期" {/* 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} value={formDate}
onChange={(value) => setFormDate(value as Date | null)} onChange={(value) => setFormDate(value as Date | null)}
required
styles={{ styles={{
input: { input: {
borderRadius: 2, borderRadius: 2,
background: '#faf9f7',
}, },
}} }}
/> />
</Popover.Dropdown>
</Popover>
{/* Time (only for reminders) */} {/* Time selector (only for reminders) */}
{formType === 'reminder' && ( {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 <TimeInput
label={
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 4 }}>
</Text>
}
placeholder="选择时间"
value={formTime} value={formTime}
onChange={(e) => setFormTime(e.target.value)} onChange={(e) => setFormTime(e.target.value)}
styles={{ styles={{
input: { input: {
borderRadius: 2, borderRadius: 2,
background: '#faf9f7',
}, },
}} }}
/> />
</Popover.Dropdown>
</Popover>
)} )}
</Group>
</Box>
{/* Lunar switch */} {/* Lunar switch (only for anniversaries) */}
{formType === 'anniversary' && (
<Switch <Switch
label={ label={
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em' }}> <Text size="xs" c="#666" style={{ letterSpacing: '0.05em' }}>
@ -509,6 +549,7 @@ export function HomePage() {
checked={formIsLunar} checked={formIsLunar}
onChange={(e) => setFormIsLunar(e.currentTarget.checked)} onChange={(e) => setFormIsLunar(e.currentTarget.checked)}
/> />
)}
{/* Repeat type */} {/* Repeat type */}
<Select <Select
@ -534,11 +575,11 @@ export function HomePage() {
}} }}
/> />
{/* Priority (only for reminders) */} {/* Color (only for reminders) */}
{formType === 'reminder' && ( {formType === 'reminder' && (
<Box> <Box>
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 8 }}> <Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 8 }}>
</Text> </Text>
<Group gap={8}> <Group gap={8}>
{([ {([

View File

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