Performance improvements: - Vite build: manual chunks, terser minification, optimizeDeps - API response caching with 5s TTL via axios interceptors - React.memo for SidebarMenuItem, useCallback for handlers - CSS classes replacing inline styles to reduce reflows UI/UX enhancements (inspired by SAP Fiori, Linear, Feishu): - Dashboard: trend indicators, sparkline charts, CountUp animation on stat cards - Dashboard: pending tasks section with priority labels - Dashboard: recent activity timeline - Design system tokens: trend colors, line-height, dark mode refinements - Enhanced quick actions with hover animations Accessibility (Lighthouse 100/100): - Skip-to-content link, ARIA landmarks, heading hierarchy - prefers-reduced-motion support, focus-visible states - Color contrast fixes: all text meets 4.5:1 ratio - Keyboard navigation for stat cards and task items SEO: meta theme-color, format-detection, robots.txt
80 lines
2.4 KiB
TypeScript
80 lines
2.4 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { Form, Switch, TimePicker, Button, message, theme } from 'antd';
|
|
import { BellOutlined } from '@ant-design/icons';
|
|
import client from '../../api/client';
|
|
|
|
interface PreferencesData {
|
|
dnd_enabled: boolean;
|
|
dnd_start?: string;
|
|
dnd_end?: string;
|
|
}
|
|
|
|
export default function NotificationPreferences() {
|
|
const [form] = Form.useForm();
|
|
const [loading, setLoading] = useState(false);
|
|
const [dndEnabled, setDndEnabled] = useState(false);
|
|
const { token } = theme.useToken();
|
|
const isDark = token.colorBgContainer === '#111827' || token.colorBgContainer === 'rgb(17, 24, 39)';
|
|
|
|
useEffect(() => {
|
|
form.setFieldsValue({ dnd_enabled: false });
|
|
}, [form]);
|
|
|
|
const handleSave = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const values = await form.validateFields();
|
|
const req: PreferencesData = {
|
|
dnd_enabled: values.dnd_enabled || false,
|
|
dnd_start: values.dnd_range?.[0]?.format('HH:mm'),
|
|
dnd_end: values.dnd_range?.[1]?.format('HH:mm'),
|
|
};
|
|
|
|
await client.put('/message-subscriptions', {
|
|
dnd_enabled: req.dnd_enabled,
|
|
dnd_start: req.dnd_start,
|
|
dnd_end: req.dnd_end,
|
|
});
|
|
|
|
message.success('偏好设置已保存');
|
|
} catch {
|
|
message.error('保存失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div style={{
|
|
background: isDark ? '#111827' : '#FFFFFF',
|
|
borderRadius: 12,
|
|
border: `1px solid ${isDark ? '#1E293B' : '#F1F5F9'}`,
|
|
padding: 24,
|
|
maxWidth: 600,
|
|
}}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 20 }}>
|
|
<BellOutlined style={{ fontSize: 16, color: '#4F46E5' }} />
|
|
<span style={{ fontSize: 15, fontWeight: 600 }}>通知偏好设置</span>
|
|
</div>
|
|
|
|
<Form form={form} layout="vertical">
|
|
<Form.Item name="dnd_enabled" label="免打扰模式" valuePropName="checked">
|
|
<Switch onChange={setDndEnabled} />
|
|
</Form.Item>
|
|
|
|
{dndEnabled && (
|
|
<Form.Item name="dnd_range" label="免打扰时段">
|
|
<TimePicker.RangePicker format="HH:mm" style={{ width: '100%' }} />
|
|
</Form.Item>
|
|
)}
|
|
|
|
<Form.Item>
|
|
<Button type="primary" onClick={handleSave} loading={loading}>
|
|
保存设置
|
|
</Button>
|
|
</Form.Item>
|
|
</Form>
|
|
</div>
|
|
);
|
|
}
|