feat: 设置中添加节假日配置功能
- 扩展设置选项:显示数量选择(1/3/5/10个) - 添加仅显示法定节假日开关 - 添加节假日筛选功能:可选择关注特定节假日 - 更新 AnniversaryList 使用新设置进行过滤 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d73a87709c
commit
e7b6864b42
@ -27,7 +27,10 @@ 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 settings = useAppStore((state) => state.settings);
|
||||||
|
const showHolidays = settings?.showHolidays ?? true;
|
||||||
|
const holidayDisplayCount = settings?.holidayDisplayCount ?? 3;
|
||||||
|
const showStatutoryOnly = settings?.showStatutoryOnly ?? false;
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const [bottomPadding, setBottomPadding] = useState(0);
|
const [bottomPadding, setBottomPadding] = useState(0);
|
||||||
|
|
||||||
@ -108,17 +111,29 @@ export function AnniversaryList({ events, onEventClick, onAddClick }: Anniversar
|
|||||||
const nextYear = getHolidaysForYear(year + 1);
|
const nextYear = getHolidaysForYear(year + 1);
|
||||||
|
|
||||||
// 合并今年和明年的节假日,按日期排序
|
// 合并今年和明年的节假日,按日期排序
|
||||||
const allHolidays = [...holidays, ...nextYear].sort(
|
let allHolidays = [...holidays, ...nextYear].sort(
|
||||||
(a, b) => a.date.getTime() - b.date.getTime()
|
(a, b) => a.date.getTime() - b.date.getTime()
|
||||||
);
|
);
|
||||||
|
|
||||||
// 只取未来90天内的节假日,显示最近3个
|
// 应用过滤规则
|
||||||
|
// 1. 如果 enabledHolidays 有内容,只显示选中的节假日
|
||||||
|
const enabledHolidays = settings?.enabledHolidays;
|
||||||
|
if (enabledHolidays && enabledHolidays.length > 0) {
|
||||||
|
allHolidays = allHolidays.filter((h) => enabledHolidays.includes(h.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 如果开启了仅显示法定节假日
|
||||||
|
if (showStatutoryOnly) {
|
||||||
|
allHolidays = allHolidays.filter((h) => h.isStatutory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只取未来90天内的节假日
|
||||||
const cutoffDate = new Date(now);
|
const cutoffDate = new Date(now);
|
||||||
cutoffDate.setDate(cutoffDate.getDate() + 90);
|
cutoffDate.setDate(cutoffDate.getDate() + 90);
|
||||||
|
|
||||||
return allHolidays
|
return allHolidays
|
||||||
.filter((h) => h.date >= now && h.date <= cutoffDate)
|
.filter((h) => h.date >= now && h.date <= cutoffDate)
|
||||||
.slice(0, 3)
|
.slice(0, holidayDisplayCount)
|
||||||
.map((h): BuiltInHolidayEvent => ({
|
.map((h): BuiltInHolidayEvent => ({
|
||||||
id: `builtin-${h.id}`,
|
id: `builtin-${h.id}`,
|
||||||
title: h.name,
|
title: h.name,
|
||||||
@ -129,7 +144,7 @@ export function AnniversaryList({ events, onEventClick, onAddClick }: Anniversar
|
|||||||
type: 'anniversary',
|
type: 'anniversary',
|
||||||
is_builtin: true,
|
is_builtin: true,
|
||||||
}));
|
}));
|
||||||
}, [showHolidays]);
|
}, [showHolidays, holidayDisplayCount, showStatutoryOnly, settings]);
|
||||||
|
|
||||||
// 合并用户纪念日和内置节假日
|
// 合并用户纪念日和内置节假日
|
||||||
const allAnniversaries = useMemo(() => {
|
const allAnniversaries = useMemo(() => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
Title,
|
Title,
|
||||||
@ -9,13 +9,18 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
Button,
|
Button,
|
||||||
Loader,
|
Loader,
|
||||||
|
Select,
|
||||||
|
Divider,
|
||||||
|
Checkbox,
|
||||||
|
ScrollArea,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconArrowLeft, IconSettings, IconBell, IconRefresh, IconBellCheck } from '@tabler/icons-react';
|
import { IconArrowLeft, IconSettings, IconBell, IconRefresh, IconBellCheck, IconChevronDown } from '@tabler/icons-react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useAppStore } from '../stores';
|
import { useAppStore } from '../stores';
|
||||||
import { requestNotificationPermission, getNotificationPermission, isNotificationSupported } from '../services/notification';
|
import { requestNotificationPermission, getNotificationPermission, isNotificationSupported } from '../services/notification';
|
||||||
import { syncRemindersToSW, triggerSWCheck } from '../services/swSync';
|
import { syncRemindersToSW, triggerSWCheck } from '../services/swSync';
|
||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
|
import { getBuiltInHolidays } from '../constants/holidays';
|
||||||
|
|
||||||
export function SettingsPage() {
|
export function SettingsPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -23,6 +28,88 @@ export function SettingsPage() {
|
|||||||
const updateSettings = useAppStore((state) => state.updateSettings);
|
const updateSettings = useAppStore((state) => state.updateSettings);
|
||||||
const [permissionStatus, setPermissionStatus] = useState<'granted' | 'denied' | 'default'>('default');
|
const [permissionStatus, setPermissionStatus] = useState<'granted' | 'denied' | 'default'>('default');
|
||||||
const [isRequesting, setIsRequesting] = useState(false);
|
const [isRequesting, setIsRequesting] = useState(false);
|
||||||
|
const [holidayFilterExpanded, setHolidayFilterExpanded] = useState(false);
|
||||||
|
|
||||||
|
// 获取所有节假日列表
|
||||||
|
const allHolidays = useMemo(() => getBuiltInHolidays(), []);
|
||||||
|
|
||||||
|
// 法定节假日
|
||||||
|
const statutoryHolidays = useMemo(
|
||||||
|
() => allHolidays.filter((h) => h.isStatutory),
|
||||||
|
[allHolidays]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 非法定节假日
|
||||||
|
const nonStatutoryHolidays = useMemo(
|
||||||
|
() => allHolidays.filter((h) => !h.isStatutory),
|
||||||
|
[allHolidays]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 处理节假日开关
|
||||||
|
const handleHolidayToggle = (checked: boolean) => {
|
||||||
|
updateSettings({ showHolidays: checked });
|
||||||
|
if (!checked) {
|
||||||
|
// 关闭时也可以收起筛选面板
|
||||||
|
setHolidayFilterExpanded(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理显示数量变化
|
||||||
|
const handleDisplayCountChange = (value: string | null) => {
|
||||||
|
updateSettings({ holidayDisplayCount: value ? parseInt(value, 10) : 3 });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理仅显示法定节假日变化
|
||||||
|
const handleStatutoryOnlyChange = (checked: boolean) => {
|
||||||
|
updateSettings({ showStatutoryOnly: checked });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理特定节假日选择
|
||||||
|
const handleHolidaySelection = (holidayId: string, checked: boolean) => {
|
||||||
|
const currentEnabled = settings.enabledHolidays || [];
|
||||||
|
let newEnabled: string[];
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
newEnabled = [...currentEnabled, holidayId];
|
||||||
|
} else {
|
||||||
|
newEnabled = currentEnabled.filter((id) => id !== holidayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSettings({ enabledHolidays: newEnabled });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查节假日是否被选中
|
||||||
|
const isHolidaySelected = (holidayId: string) => {
|
||||||
|
const enabled = settings.enabledHolidays || [];
|
||||||
|
return enabled.includes(holidayId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 全选/取消全选
|
||||||
|
const handleSelectAll = (checked: boolean) => {
|
||||||
|
if (checked) {
|
||||||
|
updateSettings({ enabledHolidays: allHolidays.map((h) => h.id) });
|
||||||
|
} else {
|
||||||
|
updateSettings({ enabledHolidays: [] });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择所有法定节假日
|
||||||
|
const handleSelectStatutory = (checked: boolean) => {
|
||||||
|
if (checked) {
|
||||||
|
const statutoryIds = statutoryHolidays.map((h) => h.id);
|
||||||
|
const currentEnabled = settings.enabledHolidays || [];
|
||||||
|
// 合并现有选择和法定节假日
|
||||||
|
const newEnabled = [...new Set([...currentEnabled, ...statutoryIds])];
|
||||||
|
updateSettings({ enabledHolidays: newEnabled });
|
||||||
|
} else {
|
||||||
|
// 移除所有法定节假日
|
||||||
|
const statutoryIds = new Set(statutoryHolidays.map((h) => h.id));
|
||||||
|
const newEnabled = (settings.enabledHolidays || []).filter(
|
||||||
|
(id) => !statutoryIds.has(id)
|
||||||
|
);
|
||||||
|
updateSettings({ enabledHolidays: newEnabled });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 页面加载时检查登录状态
|
// 页面加载时检查登录状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -170,18 +257,145 @@ export function SettingsPage() {
|
|||||||
显示节假日
|
显示节假日
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="xs" c="#999" style={{ letterSpacing: '0.03em' }}>
|
<Text size="xs" c="#999" style={{ letterSpacing: '0.03em' }}>
|
||||||
在纪念日列表中显示即将到来的节假日(最近3个)
|
在纪念日列表中显示即将到来的节假日
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
<Switch
|
<Switch
|
||||||
checked={settings.showHolidays}
|
checked={settings.showHolidays}
|
||||||
onChange={(e) => updateSettings({ showHolidays: e.currentTarget.checked })}
|
onChange={(e) => handleHolidayToggle(e.currentTarget.checked)}
|
||||||
size="sm"
|
size="sm"
|
||||||
color="#1a1a1a"
|
color="#1a1a1a"
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
{/* 节假日显示数量 */}
|
||||||
|
{settings.showHolidays && (
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text size="sm" c="#666" style={{ letterSpacing: '0.03em' }}>
|
||||||
|
显示数量
|
||||||
|
</Text>
|
||||||
|
<Select
|
||||||
|
value={String(settings.holidayDisplayCount)}
|
||||||
|
onChange={handleDisplayCountChange}
|
||||||
|
data={[
|
||||||
|
{ value: '1', label: '1个' },
|
||||||
|
{ value: '3', label: '3个' },
|
||||||
|
{ value: '5', label: '5个' },
|
||||||
|
{ value: '10', label: '10个' },
|
||||||
|
]}
|
||||||
|
size="xs"
|
||||||
|
style={{ width: 100 }}
|
||||||
|
styles={{
|
||||||
|
input: {
|
||||||
|
background: 'transparent',
|
||||||
|
borderColor: '#e0e0e0',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 仅显示法定节假日 */}
|
||||||
|
{settings.showHolidays && (
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text size="sm" c="#666" style={{ letterSpacing: '0.03em' }}>
|
||||||
|
仅显示法定节假日
|
||||||
|
</Text>
|
||||||
|
<Switch
|
||||||
|
checked={settings.showStatutoryOnly}
|
||||||
|
onChange={(e) => handleStatutoryOnlyChange(e.currentTarget.checked)}
|
||||||
|
size="sm"
|
||||||
|
color="#1a1a1a"
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 节假日筛选 */}
|
||||||
|
{settings.showHolidays && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text size="sm" fw={400} style={{ letterSpacing: '0.05em', color: '#1a1a1a' }}>
|
||||||
|
筛选节假日
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
size="xs"
|
||||||
|
rightSection={<IconChevronDown size={14} />}
|
||||||
|
onClick={() => setHolidayFilterExpanded(!holidayFilterExpanded)}
|
||||||
|
style={{
|
||||||
|
color: '#666',
|
||||||
|
letterSpacing: '0.03em',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{holidayFilterExpanded ? '收起' : '展开'}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{holidayFilterExpanded && (
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Group gap="lg">
|
||||||
|
<Checkbox
|
||||||
|
label="全选"
|
||||||
|
checked={(settings.enabledHolidays || []).length === allHolidays.length}
|
||||||
|
onChange={(e) => handleSelectAll(e.currentTarget.checked)}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label="法定节假日"
|
||||||
|
checked={statutoryHolidays.every((h) => isHolidaySelected(h.id))}
|
||||||
|
indeterminate={
|
||||||
|
statutoryHolidays.some((h) => isHolidaySelected(h.id)) &&
|
||||||
|
!statutoryHolidays.every((h) => isHolidaySelected(h.id))
|
||||||
|
}
|
||||||
|
onChange={(e) => handleSelectStatutory(e.currentTarget.checked)}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Divider my="xs" />
|
||||||
|
|
||||||
|
<Text size="xs" c="#888" fw={400}>
|
||||||
|
法定节日
|
||||||
|
</Text>
|
||||||
|
<ScrollArea h={120}>
|
||||||
|
<Group gap="xs">
|
||||||
|
{statutoryHolidays.map((holiday) => (
|
||||||
|
<Checkbox
|
||||||
|
key={holiday.id}
|
||||||
|
label={holiday.name}
|
||||||
|
checked={isHolidaySelected(holiday.id)}
|
||||||
|
onChange={(e) => handleHolidaySelection(holiday.id, e.currentTarget.checked)}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
<Text size="xs" c="#888" fw={400} mt="xs">
|
||||||
|
传统节日
|
||||||
|
</Text>
|
||||||
|
<ScrollArea h={100}>
|
||||||
|
<Group gap="xs">
|
||||||
|
{nonStatutoryHolidays.map((holiday) => (
|
||||||
|
<Checkbox
|
||||||
|
key={holiday.id}
|
||||||
|
label={holiday.name}
|
||||||
|
checked={isHolidaySelected(holiday.id)}
|
||||||
|
onChange={(e) => handleHolidaySelection(holiday.id, e.currentTarget.checked)}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
</ScrollArea>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 浏览器通知设置 */}
|
{/* 浏览器通知设置 */}
|
||||||
{isNotificationSupported() && (
|
{isNotificationSupported() && (
|
||||||
<Group justify="space-between" style={{ marginTop: 16 }}>
|
<Group justify="space-between" style={{ marginTop: 16 }}>
|
||||||
|
|||||||
@ -8,12 +8,18 @@ import { calculateNextReminderDate, findNextValidReminderDate, isDuplicateRemind
|
|||||||
// 应用设置类型
|
// 应用设置类型
|
||||||
interface AppSettings {
|
interface AppSettings {
|
||||||
showHolidays: boolean; // 是否显示节假日
|
showHolidays: boolean; // 是否显示节假日
|
||||||
|
holidayDisplayCount: number; // 节假日显示数量
|
||||||
|
showStatutoryOnly: boolean; // 仅显示法定节假日
|
||||||
|
enabledHolidays: string[]; // 用户关注的节假日ID列表(空表示全部)
|
||||||
browserNotifications: boolean; // 是否启用浏览器通知
|
browserNotifications: boolean; // 是否启用浏览器通知
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认设置
|
// 默认设置
|
||||||
const defaultSettings: AppSettings = {
|
const defaultSettings: AppSettings = {
|
||||||
showHolidays: true,
|
showHolidays: true,
|
||||||
|
holidayDisplayCount: 3,
|
||||||
|
showStatutoryOnly: false,
|
||||||
|
enabledHolidays: [],
|
||||||
browserNotifications: false,
|
browserNotifications: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user