diff --git a/app/globals.css b/app/globals.css index 0b22c47..1a2903d 100644 --- a/app/globals.css +++ b/app/globals.css @@ -71,6 +71,12 @@ /* 卡片阴影 */ --card-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.3); --card-shadow-hover: 0 4px 12px 0 rgba(0, 0, 0, 0.5); + + /* Ant Design 变量覆盖 - 解决暗黑模式闪烁问题 */ + --ant-color-bg-container: #141414; + --ant-color-bg-layout: #000000; + --ant-color-text: rgba(255, 255, 255, 0.85); + --ant-color-text-base: #fff; } /* 基础样式 */ diff --git a/components/Providers.tsx b/components/Providers.tsx index 2df2232..bbc111c 100644 --- a/components/Providers.tsx +++ b/components/Providers.tsx @@ -30,6 +30,7 @@ function AntdThemeProvider({ children }: { children: React.ReactNode }) { locale={zhCN} theme={{ algorithm: isDark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm, + cssVar: true, token: { colorPrimary: '#1890ff', borderRadius: 8, diff --git a/components/layout/ThemeToggle.tsx b/components/layout/ThemeToggle.tsx index fb29e0b..4a4bb3e 100644 --- a/components/layout/ThemeToggle.tsx +++ b/components/layout/ThemeToggle.tsx @@ -23,12 +23,15 @@ export default function ThemeToggle() { setMounted(true); }, []); - // 同步 Redux 主题到 next-themes + // 同步 next-themes 主题到 Redux + // next-themes 是单一事实来源,Redux 只是为了保持状态一致性以便其他组件使用 useEffect(() => { - if (reduxTheme && nextTheme !== reduxTheme) { - setNextTheme(reduxTheme); + if (nextTheme && (nextTheme === 'light' || nextTheme === 'dark' || nextTheme === 'system')) { + if (reduxTheme !== nextTheme) { + dispatch(setTheme(nextTheme as ThemeMode)); + } } - }, [reduxTheme, nextTheme, setNextTheme]); + }, [nextTheme, reduxTheme, dispatch]); // 确定当前实际显示的主题 const currentTheme = nextTheme === 'system' ? systemTheme : nextTheme; @@ -55,13 +58,10 @@ export default function ThemeToggle() { */ const toggleTheme = () => { // 在 light 和 dark 之间切换 - const newTheme: ThemeMode = isDark ? 'light' : 'dark'; - - // 更新 next-themes + const newTheme = isDark ? 'light' : 'dark'; + + // 只更新 next-themes,useEffect 会自动同步到 Redux setNextTheme(newTheme); - - // 更新 Redux store(会自动保存到 LocalStorage) - dispatch(setTheme(newTheme)); }; /** @@ -76,7 +76,7 @@ export default function ThemeToggle() { = ({ onDelete, renderIcon, }) => { - const { - attributes, - listeners, - setNodeRef, - transform, - transition, - isDragging, - } = useSortable({ + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: category.id, }); @@ -90,22 +88,17 @@ const DraggableCategoryItem: React.FC = ({ ]; return ( -
- +
+
{ @@ -116,12 +109,18 @@ const DraggableCategoryItem: React.FC = ({ }} > {/* 分类图标 */} -
+
{renderIcon(category.icon)}
- + {/* 分类名称 */} -
+
{category.name}
@@ -177,24 +176,30 @@ const CategorySidebarBase: React.FC = ({ className, style }, []); // 处理分类切换 - const handleCategoryChange = useCallback((categoryName: string) => { - dispatch(setCurrentCategory(categoryName)); - }, [dispatch]); + const handleCategoryChange = useCallback( + (categoryName: string) => { + dispatch(setCurrentCategory(categoryName)); + }, + [dispatch] + ); // 处理拖拽结束 - const handleDragEnd = useCallback((event: DragEndEvent) => { - const { active, over } = event; + const handleDragEnd = useCallback( + (event: DragEndEvent) => { + const { active, over } = event; - if (over && active.id !== over.id) { - const oldIndex = categories.findIndex((cat) => cat.id === active.id); - const newIndex = categories.findIndex((cat) => cat.id === over.id); + if (over && active.id !== over.id) { + const oldIndex = categories.findIndex((cat) => cat.id === active.id); + const newIndex = categories.findIndex((cat) => cat.id === over.id); - if (oldIndex !== -1 && newIndex !== -1) { - dispatch(reorderCategories({ fromIndex: oldIndex, toIndex: newIndex })); - showSuccess('分类排序已更新'); + if (oldIndex !== -1 && newIndex !== -1) { + dispatch(reorderCategories({ fromIndex: oldIndex, toIndex: newIndex })); + showSuccess('分类排序已更新'); + } } - } - }, [categories, dispatch]); + }, + [categories, dispatch] + ); // 处理添加分类 const handleAddCategory = useCallback(() => { @@ -209,79 +214,92 @@ const CategorySidebarBase: React.FC = ({ className, style }, []); // 处理删除分类 - const handleDeleteCategory = useCallback((category: Category) => { - // 查找该分类下的链接数量 - const linksInCategory = links.filter(link => link.category === category.name); - - showConfirm({ - title: '确认删除分类', - content: linksInCategory.length > 0 - ? `该分类下有 ${linksInCategory.length} 个链接,删除后这些链接的分类将被清空。确定要删除吗?` - : '确定要删除这个分类吗?', - okText: '删除', - cancelText: '取消', - okType: 'danger', - onOk: () => { - // 将该分类下的所有链接的分类字段置空 - if (linksInCategory.length > 0) { - linksInCategory.forEach(link => { - dispatch(updateLink({ + const handleDeleteCategory = useCallback( + (category: Category) => { + // 查找该分类下的链接数量 + const linksInCategory = links.filter((link) => link.category === category.name); + + showConfirm({ + title: '确认删除分类', + content: + linksInCategory.length > 0 + ? `该分类下有 ${linksInCategory.length} 个链接,删除后这些链接的分类将被清空。确定要删除吗?` + : '确定要删除这个分类吗?', + okText: '删除', + cancelText: '取消', + okType: 'danger', + onOk: () => { + // 将该分类下的所有链接的分类字段置空 + if (linksInCategory.length > 0) { + linksInCategory.forEach((link) => { + dispatch( + updateLink({ + id: link.id, + category: '', + }) + ); + }); + } + + // 删除分类 + dispatch(deleteCategory(category.id)); + + // 如果删除的是当前选中的分类,切换到第一个分类 + if (currentCategory === category.name) { + const remainingCategories = categories.filter((cat) => cat.id !== category.id); + const sortedCategories = [...remainingCategories].sort((a, b) => a.order - b.order); + const nextCategory = sortedCategories[0]?.name || '主页'; + dispatch(setCurrentCategory(nextCategory)); + } + + showSuccess('分类已删除'); + }, + }); + }, + [dispatch, links, currentCategory, categories] + ); + + // 处理分类编辑提交 + const handleCategorySubmit = useCallback( + (data: { name: string; icon: string }) => { + if (editingCategory) { + // 更新分类 + const oldName = editingCategory.name; + dispatch( + updateCategory({ + id: editingCategory.id, + ...data, + }) + ); + + // 更新所有使用该分类的链接 + const linksToUpdate = links.filter((link) => link.category === oldName); + linksToUpdate.forEach((link) => { + dispatch( + updateLink({ id: link.id, - category: '', - })); - }); - } - - // 删除分类 - dispatch(deleteCategory(category.id)); - - // 如果删除的是当前选中的分类,切换到第一个分类 - if (currentCategory === category.name) { - const remainingCategories = categories.filter(cat => cat.id !== category.id); - const sortedCategories = [...remainingCategories].sort((a, b) => a.order - b.order); - const nextCategory = sortedCategories[0]?.name || '主页'; - dispatch(setCurrentCategory(nextCategory)); + category: data.name, + }) + ); + }); + + // 如果修改的是当前选中的分类,更新当前分类 + if (currentCategory === oldName) { + dispatch(setCurrentCategory(data.name)); } - - showSuccess('分类已删除'); - }, - }); - }, [dispatch, links, currentCategory, categories]); - // 处理分类编辑提交 - const handleCategorySubmit = useCallback((data: { name: string; icon: string }) => { - if (editingCategory) { - // 更新分类 - const oldName = editingCategory.name; - dispatch(updateCategory({ - id: editingCategory.id, - ...data, - })); - - // 更新所有使用该分类的链接 - const linksToUpdate = links.filter(link => link.category === oldName); - linksToUpdate.forEach(link => { - dispatch(updateLink({ - id: link.id, - category: data.name, - })); - }); - - // 如果修改的是当前选中的分类,更新当前分类 - if (currentCategory === oldName) { - dispatch(setCurrentCategory(data.name)); + showSuccess('分类已更新'); + } else { + // 添加新分类 + dispatch(addCategory(data)); + showSuccess('分类已添加'); } - - showSuccess('分类已更新'); - } else { - // 添加新分类 - dispatch(addCategory(data)); - showSuccess('分类已添加'); - } - - setEditModalOpen(false); - setEditingCategory(null); - }, [dispatch, editingCategory, links, currentCategory]); + + setEditModalOpen(false); + setEditingCategory(null); + }, + [dispatch, editingCategory, links, currentCategory] + ); // 渲染图标 const renderIcon = useCallback((iconName: string) => { @@ -290,14 +308,16 @@ const CategorySidebarBase: React.FC = ({ className, style }, []); // 计算未分类链接数量 - const uncategorizedCount = useMemo(() => - links.filter(link => !link.category || link.category === '').length - , [links]); + const uncategorizedCount = useMemo( + () => links.filter((link) => !link.category || link.category === '').length, + [links] + ); // 排序后的分类列表 - const sortedCategories = useMemo(() => - [...categories].sort((a, b) => a.order - b.order) - , [categories]); + const sortedCategories = useMemo( + () => [...categories].sort((a, b) => a.order - b.order), + [categories] + ); // 处理点击未分类按钮 const handleUncategorizedClick = useCallback(() => { @@ -307,12 +327,7 @@ const CategorySidebarBase: React.FC = ({ className, style // 在挂载前不渲染菜单,避免 hydration 不匹配 if (!mounted) { return ( -