修改前台页面和管理页面统一主题配置

This commit is contained in:
dushixiang committed 2026-01-24 22:17:45 +08:00
1 parent 8bd4cffec1
commit f5f7b15eae
4 files changed
+86 -172

No files matched your search

+8 -5
View File
@@ -2,6 +2,7 @@ import {RouterProvider} from 'react-router-dom';
import {App as AntdApp, ConfigProvider} from 'antd';
import zhCN from 'antd/locale/zh_CN';
import router from './router';
import {ThemeProvider} from '@/portal/contexts/ThemeContext';
import 'dayjs/locale/zh-cn';
import dayjs from 'dayjs';
import './App.css';
@@ -11,11 +12,13 @@ dayjs.locale('zh-cn');
function App() {
return (
<ConfigProvider locale={zhCN}>
<AntdApp>
<RouterProvider router={router}/>
</AntdApp>
</ConfigProvider>
<ThemeProvider>
<ConfigProvider locale={zhCN}>
<AntdApp>
<RouterProvider router={router}/>
</AntdApp>
</ConfigProvider>
</ThemeProvider>
);
}
+19 -39
View File
@@ -22,6 +22,7 @@ import {cn} from '@/lib/utils';
import {getServerVersion, type VersionInfo} from "@/api/version.ts";
import {flushSync} from "react-dom";
import {ScrollArea} from "@radix-ui/react-scroll-area";
import {useTheme} from '@/portal/contexts/ThemeContext';
interface NavItem {
key: string;
@@ -39,8 +40,8 @@ const AdminLayout = () => {
const {message: messageApi, modal} = AntApp.useApp();
const [userInfo, setUserInfo] = useState<User | null>(null);
const [version, setVersion] = useState<VersionInfo>();
const [isDarkMode, setIsDarkMode] = useState(false);
const darkModeButtonRef = useRef<HTMLButtonElement>(null);
const {appliedTheme, setTheme} = useTheme();
const themeButtonRef = useRef<HTMLButtonElement>(null);
const menuItems: NavItem[] = useMemo(
() => [
@@ -105,20 +106,6 @@ const AdminLayout = () => {
});
}, [navigate, location]);
// 初始化暗色主题
useEffect(() => {
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const shouldBeDark = savedTheme === 'dark' || (!savedTheme && prefersDark);
setIsDarkMode(shouldBeDark);
if (shouldBeDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, []);
const handleLogout = () => {
modal.confirm({
title: '确认退出',
@@ -149,37 +136,27 @@ const AdminLayout = () => {
navigate(item.path);
};
// 切换暗色主题的函数,带动画效果
const toggleDarkMode = async (newIsDarkMode: boolean) => {
// 切换主题的函数,带动画效果
const toggleTheme = async () => {
const newTheme = appliedTheme === 'dark' ? 'light' : 'dark';
if (
!darkModeButtonRef.current ||
!themeButtonRef.current ||
!document.startViewTransition ||
window.matchMedia('(prefers-reduced-motion: reduce)').matches
) {
// 如果不支持 View Transition API 或用户偏好减少动画,直接切换
setIsDarkMode(newIsDarkMode);
if (newIsDarkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem('theme', newIsDarkMode ? 'dark' : 'light');
setTheme(newTheme);
return;
}
await document.startViewTransition(() => {
flushSync(() => {
setIsDarkMode(newIsDarkMode);
if (newIsDarkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem('theme', newIsDarkMode ? 'dark' : 'light');
setTheme(newTheme);
});
}).ready;
const {top, left, width, height} = darkModeButtonRef.current.getBoundingClientRect();
const {top, left, width, height} = themeButtonRef.current.getBoundingClientRect();
const x = left + width / 2;
const y = top + height / 2;
const right = window.innerWidth - left;
@@ -207,7 +184,7 @@ const AdminLayout = () => {
return (
<ConfigProvider
theme={{
algorithm: isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
algorithm: appliedTheme === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm,
}}
>
<AntApp>
@@ -250,19 +227,22 @@ const AdminLayout = () => {
>
部署指南
</Button>
{/* 主题切换按钮 */}
<button
ref={darkModeButtonRef}
ref={themeButtonRef}
type="button"
onClick={() => toggleDarkMode(!isDarkMode)}
onClick={toggleTheme}
className="inline-flex h-9 items-center rounded-full p-2 text-white/80 hover:bg-white/10 transition-all"
title={isDarkMode ? "切换到亮色模式" : "切换到暗色模式"}
title={appliedTheme === 'dark' ? "切换到浅色模式" : "切换到暗黑模式"}
>
{isDarkMode ? (
{appliedTheme === 'dark' ? (
<Sun className="h-4 w-4" strokeWidth={2}/>
) : (
<Moon className="h-4 w-4" strokeWidth={2}/>
)}
</button>
<Dropdown menu={{items: userMenuItems}} placement="bottomRight" trigger={['click']}>
<button
type="button"
+31 -97
View File
@@ -1,6 +1,5 @@
import {useEffect, useState} from 'react';
import {Activity, ChevronDown, LogIn, Menu, Monitor, Moon, ServerIcon, Settings, Sun, X} from 'lucide-react';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import {Activity, LogIn, Menu, Moon, ServerIcon, Settings, Sun, X} from 'lucide-react';
import {Link, useLocation} from "react-router-dom";
import {useTheme} from '../contexts/ThemeContext';
import {getCurrentUser} from "@/api/auth.ts";
@@ -9,7 +8,7 @@ const PublicHeader = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [currentTime, setCurrentTime] = useState(new Date());
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const {theme, setTheme} = useTheme();
const {appliedTheme, setTheme} = useTheme();
let location = useLocation();
@@ -135,54 +134,18 @@ const PublicHeader = () => {
</div>
<div className="h-6 w-[1px] bg-slate-300 dark:bg-cyan-900/50 hidden lg:block"></div>
{/* 主题切换下拉框 - Desktop */}
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button
className="flex items-center gap-2 px-3 py-2 cursor-pointer rounded transition-all text-xs font-bold tracking-wider uppercase focus:outline-none focus-visible:outline-none"
>
{theme === 'light' && <Sun className="w-3 h-3"/>}
{theme === 'dark' && <Moon className="w-3 h-3"/>}
{theme === 'auto' && <Monitor className="w-3 h-3"/>}
<span className="hidden xl:inline">
{theme === 'light' ? '浅色主题' : theme === 'dark' ? '暗黑主题' : '跟随系统'}
</span>
<ChevronDown className="w-3 h-3"/>
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="min-w-[140px] bg-white dark:bg-[#0f1016] border border-slate-200 dark:border-cyan-500/30 rounded-lg shadow-lg p-1 z-50"
sideOffset={5}
>
<DropdownMenu.Item
className="flex items-center gap-2 px-3 py-2 text-xs font-medium text-slate-700 dark:text-cyan-300 hover:bg-slate-100 dark:hover:bg-cyan-500/20 rounded cursor-pointer outline-none font-mono"
onSelect={() => setTheme('auto')}
>
<Monitor className="w-3 h-3"/>
<span>跟随系统</span>
{theme === 'auto' && <span className="ml-auto text-cyan-500">✓</span>}
</DropdownMenu.Item>
<DropdownMenu.Item
className="flex items-center gap-2 px-3 py-2 text-xs font-medium text-slate-700 dark:text-cyan-300 hover:bg-slate-100 dark:hover:bg-cyan-500/20 rounded cursor-pointer outline-none font-mono"
onSelect={() => setTheme('light')}
>
<Sun className="w-3 h-3"/>
<span>浅色主题</span>
{theme === 'light' && <span className="ml-auto text-cyan-500">✓</span>}
</DropdownMenu.Item>
<DropdownMenu.Item
className="flex items-center gap-2 px-3 py-2 text-xs font-medium text-slate-700 dark:text-cyan-300 hover:bg-slate-100 dark:hover:bg-cyan-500/20 rounded cursor-pointer outline-none font-mono"
onSelect={() => setTheme('dark')}
>
<Moon className="w-3 h-3"/>
<span>暗黑主题</span>
{theme === 'dark' && <span className="ml-auto text-cyan-500">✓</span>}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
{/* 主题切换按钮 - Desktop */}
<button
onClick={() => setTheme(appliedTheme === 'dark' ? 'light' : 'dark')}
className="flex items-center gap-2 px-3 py-2 cursor-pointer rounded transition-all text-slate-600 dark:text-cyan-400 hover:bg-slate-100 dark:hover:bg-cyan-500/10"
title={appliedTheme === 'dark' ? '切换到浅色模式' : '切换到暗黑模式'}
>
{appliedTheme === 'dark' ? (
<Sun className="w-4 h-4"/>
) : (
<Moon className="w-4 h-4"/>
)}
</button>
{/* 登录/管理后台按钮 - Desktop */}
{isLoggedIn ? (
@@ -252,52 +215,23 @@ const PublicHeader = () => {
{/* Divider */}
<div className="h-[1px] bg-slate-200 dark:bg-cyan-900/50 my-2"></div>
{/* Mobile Theme Toggle DropdownMenu */}
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button
className="w-full flex items-center justify-center gap-3 p-4 bg-cyan-50 dark:bg-cyan-500/10 hover:bg-cyan-100 dark:hover:bg-cyan-500/20 text-cyan-500 rounded-lg transition-all font-bold tracking-wider uppercase focus:outline-none focus-visible:outline-none"
>
{theme === 'light' && <Sun className="w-5 h-5"/>}
{theme === 'dark' && <Moon className="w-5 h-5"/>}
{theme === 'auto' && <Monitor className="w-5 h-5"/>}
<span>主题: {theme === 'light' ? '浅色' : theme === 'dark' ? '暗黑' : '跟随'}</span>
<ChevronDown className="w-5 h-5"/>
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="w-[calc(100vw-2rem)] max-w-md bg-white dark:bg-[#0f1016] border border-slate-200 dark:border-cyan-500/30 rounded-lg shadow-lg p-2 z-50"
sideOffset={5}
>
<DropdownMenu.Item
className="flex items-center gap-3 px-4 py-3 text-sm font-medium text-slate-700 dark:text-cyan-300 hover:bg-slate-100 dark:hover:bg-cyan-500/20 rounded-lg cursor-pointer outline-none"
onSelect={() => setTheme('auto')}
>
<Monitor className="w-5 h-5"/>
<span>跟随系统</span>
{theme === 'auto' && <span className="ml-auto text-cyan-500 text-lg">✓</span>}
</DropdownMenu.Item>
<DropdownMenu.Item
className="flex items-center gap-3 px-4 py-3 text-sm font-medium text-slate-700 dark:text-cyan-300 hover:bg-slate-100 dark:hover:bg-cyan-500/20 rounded-lg cursor-pointer outline-none"
onSelect={() => setTheme('light')}
>
<Sun className="w-5 h-5"/>
<span>浅色主题</span>
{theme === 'light' && <span className="ml-auto text-cyan-500 text-lg">✓</span>}
</DropdownMenu.Item>
<DropdownMenu.Item
className="flex items-center gap-3 px-4 py-3 text-sm font-medium text-slate-700 dark:text-cyan-300 hover:bg-slate-100 dark:hover:bg-cyan-500/20 rounded-lg cursor-pointer outline-none"
onSelect={() => setTheme('dark')}
>
<Moon className="w-5 h-5"/>
<span>暗黑主题</span>
{theme === 'dark' && <span className="ml-auto text-cyan-500 text-lg">✓</span>}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
{/* Mobile Theme Toggle Button */}
<button
onClick={() => setTheme(appliedTheme === 'dark' ? 'light' : 'dark')}
className="w-full flex items-center justify-center gap-3 p-4 bg-cyan-50 dark:bg-cyan-500/10 hover:bg-cyan-100 dark:hover:bg-cyan-500/20 text-cyan-500 rounded-lg transition-all font-bold tracking-wider uppercase"
>
{appliedTheme === 'dark' ? (
<>
<Sun className="w-5 h-5"/>
<span>切换到浅色模式</span>
</>
) : (
<>
<Moon className="w-5 h-5"/>
<span>切换到暗黑模式</span>
</>
)}
</button>
{/* Mobile Login/Admin Button */}
{isLoggedIn ? (
+28 -31
View File
@@ -1,42 +1,39 @@
import {Outlet} from 'react-router-dom';
import PublicHeader from '@portal/components/PublicHeader';
import PublicFooter from '@portal/components/PublicFooter';
import {ThemeProvider} from '../contexts/ThemeContext';
const PublicLayout = () => {
return (
<ThemeProvider>
<div className="bg-[#f0f2f5] dark:bg-[#05050a] text-slate-800 dark:text-slate-200 flex flex-col relative overflow-x-hidden transition-colors duration-500">
{/* 背景网格效果 */}
<div
className="fixed inset-0 pointer-events-none z-0 transition-opacity duration-500"
style={{
backgroundImage: 'linear-gradient(to_right,#cbd5e180_1px,transparent_1px),linear-gradient(to_bottom,#cbd5e180_1px,transparent_1px)',
backgroundSize: '30px 30px',
}}
></div>
{/* 暗色模式网格 */}
<div
className="fixed inset-0 pointer-events-none z-0 opacity-0 dark:opacity-100 transition-opacity duration-500"
style={{
backgroundImage: 'linear-gradient(to_right,#4f4f4f1a_1px,transparent_1px),linear-gradient(to_bottom,#4f4f4f1a_1px,transparent_1px)',
backgroundSize: '30px 30px',
}}
></div>
{/* 顶部发光效果 */}
<div
className="fixed top-0 left-1/2 -translate-x-1/2 w-[1000px] h-[300px] bg-cyan-600/15 blur-[120px] rounded-full pointer-events-none z-0 opacity-40 dark:opacity-100 transition-opacity duration-500"
></div>
<div className="bg-[#f0f2f5] dark:bg-[#05050a] text-slate-800 dark:text-slate-200 flex flex-col relative overflow-x-hidden transition-colors duration-500">
{/* 背景网格效果 */}
<div
className="fixed inset-0 pointer-events-none z-0 transition-opacity duration-500"
style={{
backgroundImage: 'linear-gradient(to_right,#cbd5e180_1px,transparent_1px),linear-gradient(to_bottom,#cbd5e180_1px,transparent_1px)',
backgroundSize: '30px 30px',
}}
></div>
{/* 暗色模式网格 */}
<div
className="fixed inset-0 pointer-events-none z-0 opacity-0 dark:opacity-100 transition-opacity duration-500"
style={{
backgroundImage: 'linear-gradient(to_right,#4f4f4f1a_1px,transparent_1px),linear-gradient(to_bottom,#4f4f4f1a_1px,transparent_1px)',
backgroundSize: '30px 30px',
}}
></div>
{/* 顶部发光效果 */}
<div
className="fixed top-0 left-1/2 -translate-x-1/2 w-[1000px] h-[300px] bg-cyan-600/15 blur-[120px] rounded-full pointer-events-none z-0 opacity-40 dark:opacity-100 transition-opacity duration-500"
></div>
<PublicHeader/>
<div className="relative z-10 flex flex-col min-h-screen pt-[81px]">
<main className="flex-1">
<Outlet/>
</main>
<PublicFooter/>
</div>
<PublicHeader/>
<div className="relative z-10 flex flex-col min-h-screen pt-[81px]">
<main className="flex-1">
<Outlet/>
</main>
<PublicFooter/>
</div>
</ThemeProvider>
</div>
);
};