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 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;
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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,56 +461,96 @@ 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>
|
||||||
}
|
{/* Date selector */}
|
||||||
placeholder="选择日期"
|
<Popover position="bottom-start" withArrow>
|
||||||
value={formDate}
|
<Popover.Target>
|
||||||
onChange={(value) => setFormDate(value as Date | null)}
|
<Button
|
||||||
required
|
variant="outline"
|
||||||
styles={{
|
size="xs"
|
||||||
input: {
|
leftSection={<IconCalendar size={14} />}
|
||||||
borderRadius: 2,
|
style={{
|
||||||
background: '#faf9f7',
|
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) */}
|
{/* Time selector (only for reminders) */}
|
||||||
{formType === 'reminder' && (
|
{formType === 'reminder' && (
|
||||||
<TimeInput
|
<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={
|
label={
|
||||||
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em', marginBottom: 4 }}>
|
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em' }}>
|
||||||
时间
|
农历日期
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
placeholder="选择时间"
|
checked={formIsLunar}
|
||||||
value={formTime}
|
onChange={(e) => setFormIsLunar(e.currentTarget.checked)}
|
||||||
onChange={(e) => setFormTime(e.target.value)}
|
|
||||||
styles={{
|
|
||||||
input: {
|
|
||||||
borderRadius: 2,
|
|
||||||
background: '#faf9f7',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Lunar switch */}
|
|
||||||
<Switch
|
|
||||||
label={
|
|
||||||
<Text size="xs" c="#666" style={{ letterSpacing: '0.05em' }}>
|
|
||||||
农历日期
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
checked={formIsLunar}
|
|
||||||
onChange={(e) => setFormIsLunar(e.currentTarget.checked)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Repeat type */}
|
{/* Repeat type */}
|
||||||
<Select
|
<Select
|
||||||
label={
|
label={
|
||||||
@ -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}>
|
||||||
{([
|
{([
|
||||||
|
|||||||
@ -1,33 +1,43 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
// 右键菜单位置类型
|
||||||
|
interface MenuPosition {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
// 右键菜单状态管理
|
// 右键菜单状态管理
|
||||||
interface ContextMenuState {
|
interface ContextMenuState {
|
||||||
// 当前打开菜单的事件ID,null表示没有菜单打开
|
// 当前打开菜单的事件ID,null表示没有菜单打开
|
||||||
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),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user