feat: 优化编辑窗口UI
- 优化日期时间选择器为组合布局(同一行按钮) - 优先级改名为颜色 - 提醒类型移除农历选项 - 使用Popover优化选择器交互
This commit is contained in:
parent
f0cbd0e33c
commit
306cb41516
@ -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;
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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}>
|
||||
{([
|
||||
|
||||
@ -1,33 +1,43 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
// 右键菜单位置类型
|
||||
interface MenuPosition {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
// 右键菜单状态管理
|
||||
interface ContextMenuState {
|
||||
// 当前打开菜单的事件ID,null表示没有菜单打开
|
||||
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),
|
||||
}));
|
||||
},
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user