From 3e4157f0219d5cea97aa50971c8e104e9e1cb64d Mon Sep 17 00:00:00 2001 From: handsomezhuzhu <2658601135@qq.com> Date: Thu, 18 Dec 2025 01:59:30 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=99=E9=A2=98=E6=9C=AC=E4=B9=B1=E5=BA=8F?= =?UTF-8?q?=E5=92=8C=E9=A1=BA=E5=BA=8F=E5=88=B7=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.jsx | 109 +++++++------------------ frontend/src/components/Layout.jsx | 20 ++--- frontend/src/pages/AdminPanel.jsx | 53 ++++--------- frontend/src/pages/AdminSettings.jsx | 27 ++----- frontend/src/pages/Dashboard.jsx | 33 +------- frontend/src/pages/ExamDetail.jsx | 23 +++--- frontend/src/pages/ExamList.jsx | 20 +++-- frontend/src/pages/MistakeList.jsx | 22 +++--- frontend/src/pages/MistakePlayer.jsx | 114 ++++++++++++--------------- frontend/src/pages/QuestionBank.jsx | 13 ++- frontend/src/pages/QuizPlayer.jsx | 31 +++----- 11 files changed, 157 insertions(+), 308 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 51de594..b743100 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -3,6 +3,7 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-d import { Toaster } from 'react-hot-toast' import { AuthProvider } from './context/AuthContext' import { ProtectedRoute } from './components/ProtectedRoute' +import Layout from './components/Layout' // Auth Pages import Login from './pages/Login' @@ -56,88 +57,34 @@ function App() { } /> } /> - {/* Protected Routes */} - - - - } - /> + {/* Protected Routes with Layout */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> - - - - } - /> - - - - - } - /> - - - - - } - /> - - - - - } - /> - - - - - } - /> - - - - - } - /> - - {/* Admin Only Routes */} - - - - } - /> - - - - - } - /> + {/* Admin Only Routes */} + + + + } + /> + + + + } + /> + {/* Default Route */} } /> diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index f40bd1f..a1e2f76 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -2,7 +2,7 @@ * Main Layout Component with Navigation */ import React, { useState } from 'react' -import { Link, useNavigate, useLocation } from 'react-router-dom' +import { Link, useNavigate, useLocation, Outlet } from 'react-router-dom' import { useAuth } from '../context/AuthContext' import { BookOpen, @@ -12,10 +12,11 @@ import { Settings, LogOut, Menu, - X + X, + Shield } from 'lucide-react' -export const Layout = ({ children }) => { +export const Layout = () => { const { user, logout, isAdmin } = useAuth() const navigate = useNavigate() const location = useLocation() @@ -33,6 +34,7 @@ export const Layout = ({ children }) => { ] if (isAdmin) { + navigation.push({ name: '管理面板', href: '/admin', icon: Shield }) navigation.push({ name: '系统设置', href: '/admin/settings', icon: Settings }) } @@ -63,11 +65,10 @@ export const Layout = ({ children }) => { key={item.name} to={item.href} onClick={() => setMobileMenuOpen(false)} - className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${ - isActive(item.href) + className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive(item.href) ? 'bg-primary-50 text-primary-600' : 'text-gray-700 hover:bg-gray-100' - }`} + }`} > {item.name} @@ -105,11 +106,10 @@ export const Layout = ({ children }) => { {item.name} @@ -132,7 +132,7 @@ export const Layout = ({ children }) => { {/* Main Content */}
- {children} +
diff --git a/frontend/src/pages/AdminPanel.jsx b/frontend/src/pages/AdminPanel.jsx index 3564bc7..4e7436a 100644 --- a/frontend/src/pages/AdminPanel.jsx +++ b/frontend/src/pages/AdminPanel.jsx @@ -106,42 +106,21 @@ export const AdminPanel = () => { } return ( -
- {/* Header */} -
-
-
-
- - -
-

管理员面板

-

{user?.username}

-
-
- -
-
+
+
+

管理员面板

+

系统统计与用户管理

{/* Tabs */} -
+
{/* Users Table */} -
+
diff --git a/frontend/src/pages/AdminSettings.jsx b/frontend/src/pages/AdminSettings.jsx index 5b484b4..b25943a 100644 --- a/frontend/src/pages/AdminSettings.jsx +++ b/frontend/src/pages/AdminSettings.jsx @@ -117,31 +117,14 @@ export const AdminSettings = () => { } return ( -
- {/* Header */} -
-
-
-
- - -
-

系统设置

-

管理员:{user?.username}

-
-
-
-
+
+
+

系统设置

+

配置系统参数与 AI 接口

{/* Content */} -
+
{/* Basic Settings */}

基础设置

diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index 6df634a..b773cc1 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -5,7 +5,7 @@ import React, { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { examAPI, mistakeAPI } from '../api/client' import { useAuth } from '../context/AuthContext' -import Layout from '../components/Layout' + import { FolderOpen, XCircle, TrendingUp, BookOpen, ArrowRight, Settings, Shield } from 'lucide-react' @@ -58,7 +58,7 @@ export const Dashboard = () => { } return ( - + <>
{/* Welcome */}
@@ -179,35 +179,8 @@ export const Dashboard = () => { )}
- {/* Admin Quick Access */} - {isAdmin && ( -
-
-
-

管理员功能

-

用户管理、系统统计、配置设置

-
-
- - -
-
-
- )}
-
+ ) } diff --git a/frontend/src/pages/ExamDetail.jsx b/frontend/src/pages/ExamDetail.jsx index b4e679b..a175e91 100644 --- a/frontend/src/pages/ExamDetail.jsx +++ b/frontend/src/pages/ExamDetail.jsx @@ -4,7 +4,6 @@ import React, { useState, useEffect, useRef } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { examAPI, questionAPI } from '../api/client' -import Layout from '../components/Layout' import ParsingProgress from '../components/ParsingProgress' import { ArrowLeft, Upload, Play, Loader, FileText, AlertCircle, RefreshCw, ArrowRight @@ -155,22 +154,18 @@ export const ExamDetail = () => { if (loading) { return ( - -
- -
-
+
+ +
) } if (!exam) { return ( - -
- -

题库不存在

-
-
+
+ +

题库不存在

+
) } @@ -180,7 +175,7 @@ export const ExamDetail = () => { const quizProgress = calculateProgress(exam.current_index, exam.total_questions) return ( - + <>
{/* Back Button */}
)} - + ) } diff --git a/frontend/src/pages/ExamList.jsx b/frontend/src/pages/ExamList.jsx index d33283b..d25ec40 100644 --- a/frontend/src/pages/ExamList.jsx +++ b/frontend/src/pages/ExamList.jsx @@ -4,7 +4,6 @@ import React, { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { examAPI } from '../api/client' -import Layout from '../components/Layout' import { Plus, FolderOpen, Loader, AlertCircle, Trash2, Upload } from 'lucide-react' @@ -131,16 +130,14 @@ export const ExamList = () => { if (loading) { return ( - -
- -
-
+
+ +
) } return ( - + <>
{/* Header */}
@@ -150,10 +147,11 @@ export const ExamList = () => {
@@ -337,7 +335,7 @@ export const ExamList = () => {
)} - + ) } diff --git a/frontend/src/pages/MistakeList.jsx b/frontend/src/pages/MistakeList.jsx index 065855b..3f2b90f 100644 --- a/frontend/src/pages/MistakeList.jsx +++ b/frontend/src/pages/MistakeList.jsx @@ -4,9 +4,8 @@ import React, { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { mistakeAPI } from '../api/client' -import Layout from '../components/Layout' import Pagination from '../components/Pagination' -import { XCircle, Loader, Trash2, BookOpen, Play } from 'lucide-react' +import { XCircle, Loader, Trash2, BookOpen, Play, ChevronRight } from 'lucide-react' import toast from 'react-hot-toast' import { getQuestionTypeText, formatRelativeTime } from '../utils/helpers' @@ -62,16 +61,14 @@ export const MistakeList = () => { if (loading) { return ( - -
- -
-
+
+ +
) } return ( - + <>
{/* Header */}
@@ -83,10 +80,11 @@ export const MistakeList = () => { {mistakes.length > 0 && ( )}
@@ -237,7 +235,7 @@ export const MistakeList = () => {
)} - + ) } diff --git a/frontend/src/pages/MistakePlayer.jsx b/frontend/src/pages/MistakePlayer.jsx index 46be56d..0ab8c19 100644 --- a/frontend/src/pages/MistakePlayer.jsx +++ b/frontend/src/pages/MistakePlayer.jsx @@ -4,7 +4,6 @@ import React, { useState, useEffect } from 'react' import { useNavigate, useLocation } from 'react-router-dom' import { mistakeAPI, questionAPI } from '../api/client' -import Layout from '../components/Layout' import { ArrowLeft, ArrowRight, Check, X, Loader, Trash2, AlertCircle } from 'lucide-react' @@ -13,16 +12,17 @@ import { getQuestionTypeText } from '../utils/helpers' export const MistakePlayer = () => { const navigate = useNavigate() - const location = useLocation() const searchParams = new URLSearchParams(location.search) const mode = searchParams.get('mode') || 'sequential' + console.log('MistakePlayer mounted, mode:', mode) + const [loading, setLoading] = useState(true) const [mistake, setMistake] = useState(null) const [currentIndex, setCurrentIndex] = useState(0) const [total, setTotal] = useState(0) - const [randomIds, setRandomIds] = useState([]) // For random mode + const [randomMistakes, setRandomMistakes] = useState([]) // Store full mistake objects const [submitting, setSubmitting] = useState(false) const [userAnswer, setUserAnswer] = useState('') @@ -31,7 +31,7 @@ export const MistakePlayer = () => { useEffect(() => { loadMistake() - }, [currentIndex]) + }, [currentIndex, mode]) const loadMistake = async () => { try { @@ -41,49 +41,26 @@ export const MistakePlayer = () => { if (mode === 'random') { // Random Mode Logic - if (randomIds.length === 0) { - // First load: fetch all mistakes to get IDs - // Note: fetching up to 1000 for now. For larger datasets, we need a specific API to get just IDs. + if (randomMistakes.length === 0) { + // First load: fetch all mistakes const response = await mistakeAPI.getList(0, 1000) const allMistakes = response.data.mistakes setTotal(response.data.total) if (allMistakes.length > 0) { - // Shuffle IDs - const ids = allMistakes.map(m => m.id) - for (let i = ids.length - 1; i > 0; i--) { + // Shuffle mistakes + const shuffled = [...allMistakes] + for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); - [ids[i], ids[j]] = [ids[j], ids[i]]; + [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } - setRandomIds(ids) - - // Get first mistake from shuffled list - // We need to find the full mistake object from our initial fetch - // (Since we fetched all, we have it) - const firstId = ids[0] - currentMistake = allMistakes.find(m => m.id === firstId) + setRandomMistakes(shuffled) + currentMistake = shuffled[0] } } else { - // Subsequent loads: use stored random IDs - // We need to fetch the specific mistake details if we don't have them cached - // But wait, mistakeAPI.getList is pagination based. - // We can't easily "get mistake by ID" using getList without knowing its index. - // However, we have `mistakeAPI.remove` but not `getById`. - // Actually, the `mistake` object contains the `question`. - // If we only have the ID, we might need an API to get mistake by ID. - // But since we fetched ALL mistakes initially (up to 1000), we can just store the whole objects in randomIds? - // No, that's too much memory if many. - - // Let's assume for now we fetched all and stored them in a "cache" or just store the list of objects if < 1000. - // For simplicity and performance on small datasets: - // If randomIds contains objects, use them. - - // REVISION: Let's just store the full shuffled list of mistakes in state if < 1000. - // If > 1000, this approach needs backend support for "random fetch". - // Given the user requirements, let's assume < 1000 for now. - - if (currentIndex < randomIds.length) { - currentMistake = randomIds[currentIndex] + // Subsequent loads: use stored mistakes + if (currentIndex < randomMistakes.length) { + currentMistake = randomMistakes[currentIndex] } } } else { @@ -101,15 +78,10 @@ export const MistakePlayer = () => { currentMistake.question.options = ['A. 正确', 'B. 错误'] } setMistake(currentMistake) + console.log('Mistake loaded:', currentMistake) setResult(null) setUserAnswer('') setMultipleAnswers([]) - - // If we just initialized random mode, update state - if (mode === 'random' && randomIds.length === 0) { - // This part is tricky because state updates are async. - // We handled the initialization above. - } } else { setMistake(null) } @@ -118,6 +90,7 @@ export const MistakePlayer = () => { toast.error('加载错题失败') } finally { setLoading(false) + console.log('Loading finished') } } @@ -176,8 +149,8 @@ export const MistakePlayer = () => { // If we remove the last item, we need to go back one step or show empty if (mode === 'random') { // Remove from random list - const newRandomList = randomIds.filter(m => m.id !== mistake.id) - setRandomIds(newRandomList) + const newRandomList = randomMistakes.filter(m => m.id !== mistake.id) + setRandomMistakes(newRandomList) setTotal(newRandomList.length) if (currentIndex >= newRandomList.length && newRandomList.length > 0) { @@ -219,35 +192,46 @@ export const MistakePlayer = () => { if (loading && !mistake) { return ( - -
- -
-
+
+ +
) } if (!mistake) { return ( - -
- -

错题本为空

- -
-
+
+ +

错题本为空

+ +
) } const question = mistake.question + if (!question) { + return ( +
+ +

题目数据缺失

+ +
+ ) + } + return ( - + <>
{/* Header */}
@@ -421,7 +405,7 @@ export const MistakePlayer = () => {
)}
-
+ ) } diff --git a/frontend/src/pages/QuestionBank.jsx b/frontend/src/pages/QuestionBank.jsx index 9140784..12122b7 100644 --- a/frontend/src/pages/QuestionBank.jsx +++ b/frontend/src/pages/QuestionBank.jsx @@ -3,7 +3,6 @@ */ import React, { useState, useEffect } from 'react' import { questionAPI } from '../api/client' -import Layout from '../components/Layout' import Pagination from '../components/Pagination' import { FileText, Loader, Search } from 'lucide-react' import toast from 'react-hot-toast' @@ -44,16 +43,14 @@ export const QuestionBank = () => { if (loading && questions.length === 0) { return ( - -
- -
-
+
+ +
) } return ( - + <>
{/* Header */}
@@ -152,7 +149,7 @@ export const QuestionBank = () => { }} />
- + ) } diff --git a/frontend/src/pages/QuizPlayer.jsx b/frontend/src/pages/QuizPlayer.jsx index d7200e4..49df908 100644 --- a/frontend/src/pages/QuizPlayer.jsx +++ b/frontend/src/pages/QuizPlayer.jsx @@ -4,7 +4,6 @@ import React, { useState, useEffect } from 'react' import { useParams, useNavigate, useSearchParams } from 'react-router-dom' import { examAPI, questionAPI, mistakeAPI } from '../api/client' -import Layout from '../components/Layout' import { ArrowLeft, ArrowRight, Check, X, Loader, BookmarkPlus, BookmarkX, AlertCircle } from 'lucide-react' @@ -159,27 +158,23 @@ export const QuizPlayer = () => { if (loading) { return ( - -
- -
-
+
+ +
) } if (!question) { return ( - -
- -

没有更多题目了

-
-
+
+ +

没有更多题目了

+
) } return ( - + <>
{/* Header */}
@@ -212,8 +207,8 @@ export const QuizPlayer = () => {
)}
-
+ ) }