From e86a5dd4fab5cf4ec3ddf68e66e4ca992e271607 Mon Sep 17 00:00:00 2001 From: LeonspaceX Date: Sun, 7 Dec 2025 19:11:55 +0800 Subject: [PATCH] =?UTF-8?q?Feat:=E5=A2=9E=E5=8A=A0=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 27 +++- src/components/AdminDashboard.tsx | 23 +++- src/components/DevToolsModal.tsx | 204 ++++++++++++++++++++++++++++++ 3 files changed, 249 insertions(+), 5 deletions(-) create mode 100644 src/components/DevToolsModal.tsx diff --git a/src/App.tsx b/src/App.tsx index b87eaa9..d7a297b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ // 你好,感谢你愿意看源代码,但是悄悄告诉你,代码其实是AI写的所以质量很差喵。抱歉呜呜呜😭。 import React, { useState, useEffect, useRef, useCallback } from 'react'; -import { FluentProvider, webLightTheme, webDarkTheme, tokens } from '@fluentui/react-components'; +import { FluentProvider, webLightTheme, webDarkTheme, tokens, Button } from '@fluentui/react-components'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import PostCard from './components/PostCard'; import MainLayout from './layouts/MainLayout'; @@ -20,6 +20,8 @@ import NotFound from './pages/NotFound'; import ImageViewer from './components/ImageViewer'; import NoticeModal from './components/NoticeModal'; import type { NoticeData } from './components/NoticeModal'; +import DevToolsModal from './components/DevToolsModal'; +import { Bug24Regular } from '@fluentui/react-icons'; function App() { const [isDarkMode, setIsDarkMode] = React.useState(() => { @@ -54,6 +56,13 @@ function App() { const [imageViewer, setImageViewer] = useState<{ open: boolean; src?: string; alt?: string }>({ open: false }); const [noticeData, setNoticeData] = useState(null); const [showNotice, setShowNotice] = useState(false); + const [showDevTools, setShowDevTools] = useState(false); + const [isDebugMode, setIsDebugMode] = useState(false); + + useEffect(() => { + const searchParams = new URLSearchParams(window.location.search); + setIsDebugMode(searchParams.get('debug') === 'true'); + }, []); useEffect(() => { getNotice().then(data => { @@ -258,6 +267,20 @@ function App() { /> )} + + {/* DevTools Trigger */} + {isDebugMode && ( +
+
+ )} + {showDevTools && setShowDevTools(false)} />} ); } @@ -270,3 +293,5 @@ export default App; + + diff --git a/src/components/AdminDashboard.tsx b/src/components/AdminDashboard.tsx index 0e614f6..d263151 100644 --- a/src/components/AdminDashboard.tsx +++ b/src/components/AdminDashboard.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import { makeStyles, Button, @@ -173,6 +173,7 @@ const AdminDashboard: React.FC = ({ onToggleTheme }) => { const navigate = useNavigate(); + const location = useLocation(); const styles = useStyles(); const [activeTab, setActiveTab] = React.useState('systemSettings'); const [postReviewSubTab, setPostReviewSubTab] = React.useState('pending'); @@ -962,10 +963,23 @@ const AdminDashboard: React.FC = ({ + - - 将清理 localStorage、Cookies 及静态资源缓存。 - {/* 确认对话框 */} @@ -1276,3 +1290,4 @@ const AdminDashboard: React.FC = ({ export default AdminDashboard; + diff --git a/src/components/DevToolsModal.tsx b/src/components/DevToolsModal.tsx new file mode 100644 index 0000000..47dffc7 --- /dev/null +++ b/src/components/DevToolsModal.tsx @@ -0,0 +1,204 @@ +import React, { useState, useMemo } from 'react'; +import { + makeStyles, + Button, + tokens, + Text, + Textarea, + Dropdown, + Option, + Label, + Input +} from '@fluentui/react-components'; +import { Dismiss24Regular, Play24Regular } from '@fluentui/react-icons'; +import * as Api from '../api'; +import * as AdminApi from '../admin_api'; +import { API_CONFIG } from '../config'; + +const useStyles = makeStyles({ + modalOverlay: { + position: 'fixed', + top: 0, + left: 0, + width: '100vw', + height: '100vh', + backgroundColor: 'rgba(0, 0, 0, 0.4)', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + zIndex: 10000, + }, + modalContent: { + backgroundColor: tokens.colorNeutralBackground1, + padding: tokens.spacingHorizontalXXL, + borderRadius: tokens.borderRadiusXLarge, + boxShadow: tokens.shadow64, + display: 'flex', + flexDirection: 'column', + gap: tokens.spacingVerticalM, + width: '600px', + maxWidth: '90vw', + maxHeight: '90vh', + position: 'relative', + overflowY: 'auto', + }, + closeButton: { + position: 'absolute', + top: tokens.spacingVerticalS, + right: tokens.spacingHorizontalS, + }, + title: { + fontSize: tokens.fontSizeBase500, + fontWeight: tokens.fontWeightSemibold, + marginBottom: tokens.spacingVerticalS, + }, + section: { + display: 'flex', + flexDirection: 'column', + gap: tokens.spacingVerticalS, + }, + resultBox: { + backgroundColor: tokens.colorNeutralBackground2, + padding: tokens.spacingHorizontalM, + borderRadius: tokens.borderRadiusMedium, + fontFamily: 'monospace', + whiteSpace: 'pre-wrap', + maxHeight: '200px', + overflow: 'auto', + fontSize: tokens.fontSizeBase200, + wordBreak: 'break-all', + }, +}); + +interface DevToolsModalProps { + onClose: () => void; +} + +const DevToolsModal: React.FC = ({ onClose }) => { + const styles = useStyles(); + const [selectedFunc, setSelectedFunc] = useState(''); + const [params, setParams] = useState('[]'); + const [result, setResult] = useState(''); + const [loading, setLoading] = useState(false); + + // Combine APIs and filter functions + const apiFunctions = useMemo(() => { + const all = { ...Api, ...AdminApi }; + return Object.entries(all) + .filter(([_, value]) => typeof value === 'function') + .map(([key]) => key) + .sort(); + }, []); + + const handleExecute = async () => { + if (!selectedFunc) return; + setLoading(true); + setResult('Executing...'); + + try { + // Parse params + let args = []; + try { + const parsed = JSON.parse(params); + if (Array.isArray(parsed)) { + args = parsed; + } else { + throw new Error('Params must be a JSON array, e.g. ["arg1", 123]'); + } + } catch (e: any) { + setResult(`Error parsing params: ${e.message}`); + setLoading(false); + return; + } + + const allApi: any = { ...Api, ...AdminApi }; + const func = allApi[selectedFunc]; + + if (typeof func !== 'function') { + setResult('Selected item is not a function.'); + setLoading(false); + return; + } + + const res = await func(...args); + setResult(JSON.stringify(res, null, 2)); + } catch (e: any) { + console.error(e); + setResult(`Error: ${e.message}\n${JSON.stringify(e, null, 2)}`); + } finally { + setLoading(false); + } + }; + + return ( +
+
+