修改前台页面和管理页面统一主题配置
This commit is contained in:
1 parent
8bd4cffec1
commit
f5f7b15eae
4 files changed
+86
-172
No files matched your search
+8
-5
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user