统一深色模式样式

This commit is contained in:
LeonspaceX
2025-12-06 13:10:45 +08:00
parent f2050fc291
commit daef092653
4 changed files with 84 additions and 95 deletions

View File

@@ -25,3 +25,48 @@
.dark ::-webkit-scrollbar-thumb:hover { .dark ::-webkit-scrollbar-thumb:hover {
background: #3a3a3a; background: #3a3a3a;
} }
/* react-markdown-editor-lite 深色模式适配 */
body.dark-theme .rc-md-editor {
background-color: #292929;
border-color: #3e3e3e;
color: #e0e0e0;
}
body.dark-theme .rc-md-editor .rc-md-navigation {
background-color: #333;
border-bottom-color: #3e3e3e;
}
body.dark-theme .rc-md-editor .rc-md-navigation .button-wrap .button {
color: #e0e0e0;
}
body.dark-theme .rc-md-editor .rc-md-navigation .button-wrap .button:hover {
background-color: #444;
}
body.dark-theme .rc-md-editor .editor-container .section {
border-right-color: #3e3e3e;
}
body.dark-theme .rc-md-editor .editor-container .sec-md .input {
background-color: #292929;
color: #e0e0e0;
}
body.dark-theme .rc-md-editor .editor-container .sec-html {
background-color: #292929;
color: #e0e0e0;
}
/* 预览区域的代码块样式适配 */
body.dark-theme .rc-md-editor .editor-container .sec-html pre {
background-color: #1e1e1e;
border: 1px solid #3e3e3e;
}
body.dark-theme .rc-md-editor .editor-container .sec-html code {
background-color: #1e1e1e;
color: #ce9178;
}

View File

@@ -1,4 +1,4 @@
// 你好感谢你愿意看源代码但是悄悄告诉你代码其实是AI写的所以质量很差喵。抱歉呜呜呜😭。 // 你好感谢你愿意看源代码但是悄悄告诉你代码其实是AI写的所以质量很差喵。抱歉呜呜呜😭。
import React, { useState, useEffect, useRef, useCallback } from 'react'; import React, { useState, useEffect, useRef, useCallback } from 'react';
import { FluentProvider, webLightTheme, webDarkTheme, tokens } from '@fluentui/react-components'; import { FluentProvider, webLightTheme, webDarkTheme, tokens } from '@fluentui/react-components';
@@ -6,10 +6,11 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom';
import PostCard from './components/PostCard'; import PostCard from './components/PostCard';
import MainLayout from './layouts/MainLayout'; import MainLayout from './layouts/MainLayout';
import './App.css'; import './App.css';
import { fetchArticles, getNotice } from './api'; import { fetchArticles } from './api';
import CreatePost from './components/CreatePost'; import CreatePost from './components/CreatePost';
import { ToastContainer, toast } from 'react-toastify'; import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css'; import 'react-toastify/dist/ReactToastify.css';
import { Toaster } from 'react-hot-toast';
import AboutPage from './components/AboutPage'; import AboutPage from './components/AboutPage';
import PostState from './components/PostState'; import PostState from './components/PostState';
import ReportState from './components/ReportState'; import ReportState from './components/ReportState';
@@ -17,10 +18,22 @@ import AdminPage from './components/AdminPage';
import InitPage from './pages/InitPage'; import InitPage from './pages/InitPage';
import NotFound from './pages/NotFound'; import NotFound from './pages/NotFound';
import ImageViewer from './components/ImageViewer'; import ImageViewer from './components/ImageViewer';
import NoticeModal from './components/NoticeModal';
function App() { function App() {
const [isDarkMode, setIsDarkMode] = React.useState(false); const [isDarkMode, setIsDarkMode] = React.useState(() => {
const savedTheme = localStorage.getItem('theme');
return savedTheme === 'dark';
});
useEffect(() => {
localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
if (isDarkMode) {
document.body.classList.add('dark-theme');
} else {
document.body.classList.remove('dark-theme');
}
}, [isDarkMode]);
const [articles, setArticles] = useState<Array<{ const [articles, setArticles] = useState<Array<{
id: number; id: number;
content: string; content: string;
@@ -37,9 +50,6 @@ function App() {
const lastRefreshAtRef = useRef<number>(0); const lastRefreshAtRef = useRef<number>(0);
const REFRESH_COOLDOWN_MS = 5000; // 刷新冷却时间 const REFRESH_COOLDOWN_MS = 5000; // 刷新冷却时间
const [imageViewer, setImageViewer] = useState<{ open: boolean; src?: string; alt?: string }>({ open: false }); const [imageViewer, setImageViewer] = useState<{ open: boolean; src?: string; alt?: string }>({ open: false });
const THEME_PREF_KEY = 'ThemePref';
const userPrefRef = useRef<boolean>(false);
const [noticeModal, setNoticeModal] = useState<{ open: boolean; type?: 'md' | 'url'; content?: string; version?: number }>({ open: false });
const openImageViewer = (src?: string, alt?: string) => { const openImageViewer = (src?: string, alt?: string) => {
if (!src) return; if (!src) return;
@@ -71,16 +81,9 @@ function App() {
if (containerRef.current) containerRef.current.scrollTop = 0; if (containerRef.current) containerRef.current.scrollTop = 0;
}; };
const handleToggleTheme = () => { // 移除触摸下拉刷新逻辑
setIsDarkMode(prev => {
const next = !prev; // 撤销 Pointer 事件回退,恢复为纯 Touch 逻辑
try {
localStorage.setItem(THEME_PREF_KEY, next ? 'dark' : 'light');
userPrefRef.current = true;
} catch {}
return next;
});
};
const onWheel: React.WheelEventHandler<HTMLDivElement> = (e) => { const onWheel: React.WheelEventHandler<HTMLDivElement> = (e) => {
const atTop = (containerRef.current?.scrollTop ?? 0) <= 0; const atTop = (containerRef.current?.scrollTop ?? 0) <= 0;
@@ -89,65 +92,6 @@ function App() {
} }
}; };
useEffect(() => {
const mql = typeof window !== 'undefined' && window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
let handler: ((e: MediaQueryListEvent) => void) | null = null;
try {
const saved = localStorage.getItem(THEME_PREF_KEY);
if (saved === 'dark' || saved === 'light') {
userPrefRef.current = true;
setIsDarkMode(saved === 'dark');
} else {
if (mql) {
setIsDarkMode(mql.matches);
handler = (e: MediaQueryListEvent) => {
if (!userPrefRef.current) {
setIsDarkMode(e.matches);
}
};
if ('addEventListener' in mql) {
mql.addEventListener('change', handler);
} else {
// @ts-ignore
mql.addListener(handler);
}
}
}
} catch {}
return () => {
if (mql && handler) {
if ('removeEventListener' in mql) {
mql.removeEventListener('change', handler);
} else {
// @ts-ignore
mql.removeListener(handler);
}
}
};
}, []);
// 加载公告并根据版本控制显示
useEffect(() => {
const loadNotice = async () => {
try {
const data = await getNotice();
const ver = Number(data.version ?? 0) || 0;
if (ver === 0) return; // 版本为 0 不显示
let storedVer = 0;
try {
const v = localStorage.getItem('notice_ver');
storedVer = v ? Number(v) || 0 : 0;
} catch {}
if (!storedVer || ver > storedVer) {
setNoticeModal({ open: true, type: data.type === 'url' ? 'url' : 'md', content: String(data.content ?? ''), version: ver });
}
} catch {
// 获取失败则不显示公告
}
};
loadNotice();
}, []);
useEffect(() => { useEffect(() => {
const controller = new AbortController(); const controller = new AbortController();
const signal = controller.signal; const signal = controller.signal;
@@ -185,7 +129,7 @@ function App() {
<FluentProvider theme={isDarkMode ? webDarkTheme : webLightTheme}> <FluentProvider theme={isDarkMode ? webDarkTheme : webLightTheme}>
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
<Route path="/" element={<MainLayout isDarkMode={isDarkMode} onToggleTheme={handleToggleTheme} />}> <Route path="/" element={<MainLayout isDarkMode={isDarkMode} onToggleTheme={() => setIsDarkMode(!isDarkMode)} />}>
<Route <Route
index index
element={ element={
@@ -199,7 +143,9 @@ function App() {
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
minHeight: '100%', minHeight: '100%',
// 移除下拉位移动画
}}> }}>
{/* 刷新提示改为 toast不显示顶部灰字 */}
{articles.map((article, index) => { {articles.map((article, index) => {
if (articles.length === index + 1 && hasMore) { if (articles.length === index + 1 && hasMore) {
return ( return (
@@ -246,28 +192,29 @@ function App() {
<Route path="about" element={<AboutPage />} /> <Route path="about" element={<AboutPage />} />
</Route> </Route>
<Route path="/init" element={<InitPage />} /> <Route path="/init" element={<InitPage />} />
<Route path="/admin" element={<AdminPage isDarkMode={isDarkMode} onToggleTheme={handleToggleTheme} />} /> <Route path="/admin" element={<AdminPage isDarkMode={isDarkMode} onToggleTheme={() => setIsDarkMode(!isDarkMode)} />} />
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
<ToastContainer /> <ToastContainer theme={isDarkMode ? 'dark' : 'light'} />
{imageViewer.open && imageViewer.src && ( <Toaster
<ImageViewer src={imageViewer.src!} alt={imageViewer.alt} onClose={closeImageViewer} /> position="top-center"
)} toastOptions={{
{noticeModal.open && ( style: {
<div style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(5px)', display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 999 }}> background: isDarkMode ? '#333' : '#fff',
<NoticeModal color: isDarkMode ? '#fff' : '#333',
data={{ type: noticeModal.type!, content: noticeModal.content || '', version: noticeModal.version || 0 }} },
onClose={() => setNoticeModal(prev => ({ ...prev, open: false }))}
onNeverShow={(version) => {
try { localStorage.setItem('notice_ver', String(version)); } catch {}
setNoticeModal(prev => ({ ...prev, open: false }));
}} }}
/> />
</div> {imageViewer.open && imageViewer.src && (
<ImageViewer src={imageViewer.src!} alt={imageViewer.alt} onClose={closeImageViewer} />
)} )}
</FluentProvider> </FluentProvider>
); );
} }
export default App; export default App;

View File

@@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import { isAdminLoggedIn } from '../admin_api'; import { isAdminLoggedIn } from '../admin_api';
import AdminLogin from './AdminLogin'; import AdminLogin from './AdminLogin';
import AdminDashboard from './AdminDashboard'; import AdminDashboard from './AdminDashboard';
import { Toaster } from 'react-hot-toast';
interface AdminPageProps { interface AdminPageProps {
isDarkMode: boolean; isDarkMode: boolean;
@@ -43,7 +42,6 @@ const AdminPage: React.FC<AdminPageProps> = ({ isDarkMode, onToggleTheme }) => {
) : ( ) : (
<AdminLogin onLoginSuccess={handleLoginSuccess} /> <AdminLogin onLoginSuccess={handleLoginSuccess} />
)} )}
<Toaster position="top-center" />
</> </>
); );
}; };

View File

@@ -12,7 +12,7 @@ import {
import { Dismiss24Regular, ArrowReply24Regular } from '@fluentui/react-icons'; import { Dismiss24Regular, ArrowReply24Regular } from '@fluentui/react-icons';
import { getComments, postComment } from '../api'; import { getComments, postComment } from '../api';
import type { Comment as CommentType } from '../api'; import type { Comment as CommentType } from '../api';
import { toast, Toaster } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
const useStyles = makeStyles({ const useStyles = makeStyles({
container: { container: {
@@ -225,7 +225,6 @@ const CommentSection: React.FC<CommentSectionProps> = ({ postId }) => {
{!loading && comments.length === 0 && <Text></Text>} {!loading && comments.length === 0 && <Text></Text>}
{renderComments()} {renderComments()}
</div> </div>
<Toaster position="top-center" />
</div> </div>
); );
}; };