From c0a280132c7bc585c7a13c0e4ac041aa3f5b6161 Mon Sep 17 00:00:00 2001 From: yanlongqi Date: Wed, 5 Nov 2025 10:58:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96UI=E9=A3=8E=E6=A0=BC=E5=B9=B6?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=AE=A1=E7=90=86=E5=91=98=E6=9D=83=E9=99=90?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要更改: - 新增管理员权限系统:添加 AdminAuth 中间件和 AdminRoute 组件,限制题库管理功能仅 yanlongqi 用户可访问 - UI 全面改版为白色毛玻璃风格(macOS 风格):应用毛玻璃效果、优化圆角和阴影、统一配色方案 - 登录页优化:将注册功能改为模态框形式,简化登录界面 - 首页优化:题库管理入口仅对管理员用户显示,优化响应式布局和卡片排列 - 移除底部导航栏:简化布局,改善用户体验 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/middleware/auth.go | 28 ++ main.go | 13 +- web/src/App.tsx | 11 +- web/src/components/AdminRoute.tsx | 47 ++++ web/src/components/TabBarLayout.module.less | 11 +- web/src/components/TabBarLayout.tsx | 52 +--- web/src/pages/About.module.less | 15 +- web/src/pages/Home.module.less | 272 ++++++++++++++++---- web/src/pages/Home.tsx | 114 ++++---- web/src/pages/Login.module.less | 30 ++- web/src/pages/Login.tsx | 213 ++++++++------- web/src/pages/Question.module.less | 27 +- web/src/pages/WrongQuestions.module.less | 38 ++- web/src/utils/request.ts | 8 +- web/vite.config.ts | 11 +- 15 files changed, 591 insertions(+), 299 deletions(-) create mode 100644 web/src/components/AdminRoute.tsx diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 5029025..16d69a4 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -55,3 +55,31 @@ func Auth() gin.HandlerFunc { c.Next() } } + +// AdminAuth 管理员认证中间件(必须在Auth中间件之后使用) +func AdminAuth() gin.HandlerFunc { + return func(c *gin.Context) { + // 获取用户名(由 Auth 中间件设置) + username, exists := c.Get("username") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{ + "success": false, + "message": "未登录", + }) + c.Abort() + return + } + + // 检查是否为管理员用户 + if username != "yanlongqi" { + c.JSON(http.StatusForbidden, gin.H{ + "success": false, + "message": "无权限访问", + }) + c.Abort() + return + } + + c.Next() + } +} diff --git a/main.go b/main.go index 909fc88..df69375 100644 --- a/main.go +++ b/main.go @@ -46,11 +46,6 @@ func main() { auth.POST("/practice/submit", handlers.SubmitPracticeAnswer) // 提交练习答案 auth.GET("/practice/statistics", handlers.GetStatistics) // 获取统计数据 - // 题库管理API(需要认证) - auth.POST("/practice/questions", handlers.CreatePracticeQuestion) // 创建题目 - auth.PUT("/practice/questions/:id", handlers.UpdatePracticeQuestion) // 更新题目 - auth.DELETE("/practice/questions/:id", handlers.DeletePracticeQuestion) // 删除题目 - // 错题本相关API auth.GET("/wrong-questions", handlers.GetWrongQuestions) // 获取错题列表 auth.GET("/wrong-questions/stats", handlers.GetWrongQuestionStats) // 获取错题统计 @@ -59,6 +54,14 @@ func main() { auth.PUT("/wrong-questions/:id/mastered", handlers.MarkWrongQuestionMastered) // 标记已掌握 auth.DELETE("/wrong-questions", handlers.ClearWrongQuestions) // 清空错题本 } + + // 题库管理API(需要管理员权限) + admin := api.Group("", middleware.Auth(), middleware.AdminAuth()) + { + admin.POST("/practice/questions", handlers.CreatePracticeQuestion) // 创建题目 + admin.PUT("/practice/questions/:id", handlers.UpdatePracticeQuestion) // 更新题目 + admin.DELETE("/practice/questions/:id", handlers.DeletePracticeQuestion) // 删除题目 + } } // 静态文件服务(必须在 API 路由之后) diff --git a/web/src/App.tsx b/web/src/App.tsx index 1fe1d88..a3964c2 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -2,6 +2,7 @@ import React from 'react' import { BrowserRouter as Router, Routes, Route } from 'react-router-dom' import TabBarLayout from './components/TabBarLayout' import ProtectedRoute from './components/ProtectedRoute' +import AdminRoute from './components/AdminRoute' import QuestionPage from './pages/Question' import Login from './pages/Login' import Home from './pages/Home' @@ -21,7 +22,15 @@ const App: React.FC = () => { {/* 不带TabBar的页面,但需要登录保护 */} } /> - } /> + + {/* 题库管理页面,需要管理员权限 */} + + + + + + } /> {/* 不带TabBar的页面,不需要登录保护 */} } /> diff --git a/web/src/components/AdminRoute.tsx b/web/src/components/AdminRoute.tsx new file mode 100644 index 0000000..51f7498 --- /dev/null +++ b/web/src/components/AdminRoute.tsx @@ -0,0 +1,47 @@ +import React, { useEffect, useState } from 'react' +import { Navigate } from 'react-router-dom' +import { message } from 'antd' + +interface AdminRouteProps { + children: React.ReactNode +} + +const AdminRoute: React.FC = ({ children }) => { + const [isAdmin, setIsAdmin] = useState(null) + + useEffect(() => { + // 检查用户信息 + const userStr = localStorage.getItem('user') + if (!userStr) { + setIsAdmin(false) + return + } + + try { + const user = JSON.parse(userStr) + if (user.username === 'yanlongqi') { + setIsAdmin(true) + } else { + setIsAdmin(false) + message.error('无权限访问该页面') + } + } catch (e) { + setIsAdmin(false) + } + }, []) + + // 正在检查权限时,不显示任何内容 + if (isAdmin === null) { + return null + } + + // 如果不是管理员,重定向到首页 + if (!isAdmin) { + return + } + + // 是管理员,显示子组件 + return <>{children} +} + +export default AdminRoute diff --git a/web/src/components/TabBarLayout.module.less b/web/src/components/TabBarLayout.module.less index 5e9ffaa..cee6289 100644 --- a/web/src/components/TabBarLayout.module.less +++ b/web/src/components/TabBarLayout.module.less @@ -1,5 +1,6 @@ .layout { min-height: 100vh; + background: #fafafa; } .content { @@ -13,8 +14,14 @@ right: 0; z-index: 1000; padding: 0; - background: white; - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); + background: rgba(255, 255, 255, 0.85); + backdrop-filter: blur(40px) saturate(180%); + -webkit-backdrop-filter: blur(40px) saturate(180%); + box-shadow: + 0 -2px 8px rgba(0, 0, 0, 0.04), + 0 -1px 4px rgba(0, 0, 0, 0.02), + 0 0 0 1px rgba(0, 0, 0, 0.03); + border-top: 0.5px solid rgba(0, 0, 0, 0.04); } .menu { diff --git a/web/src/components/TabBarLayout.tsx b/web/src/components/TabBarLayout.tsx index d83af37..d0211e3 100644 --- a/web/src/components/TabBarLayout.tsx +++ b/web/src/components/TabBarLayout.tsx @@ -1,61 +1,17 @@ -import React, { useState, useEffect } from 'react' -import { useNavigate, useLocation, Outlet } from 'react-router-dom' -import { Layout, Menu } from 'antd' -import { - HomeOutlined, - FileTextOutlined, -} from '@ant-design/icons' +import React from 'react' +import { Outlet } from 'react-router-dom' +import { Layout } from 'antd' import styles from './TabBarLayout.module.less' -const { Footer, Content } = Layout +const { Content } = Layout const TabBarLayout: React.FC = () => { - const navigate = useNavigate() - const location = useLocation() - const [isMobile, setIsMobile] = useState(window.innerWidth <= 768) - - useEffect(() => { - const handleResize = () => { - setIsMobile(window.innerWidth <= 768) - } - - window.addEventListener('resize', handleResize) - return () => window.removeEventListener('resize', handleResize) - }, []) - - const menuItems = [ - { - key: '/', - icon: , - label: '首页', - }, - { - key: '/question', - icon: , - label: '答题', - }, - ] - - const handleMenuClick = (key: string) => { - navigate(key) - } return ( - {isMobile && ( -
- handleMenuClick(key)} - className={styles.menu} - /> -
- )}
) } diff --git a/web/src/pages/About.module.less b/web/src/pages/About.module.less index 747c70a..4546fc7 100644 --- a/web/src/pages/About.module.less +++ b/web/src/pages/About.module.less @@ -1,6 +1,6 @@ .container { min-height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: #fafafa; padding: 0; } @@ -20,8 +20,15 @@ } .card { - border-radius: 16px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + border-radius: 20px; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(40px) saturate(180%); + -webkit-backdrop-filter: blur(40px) saturate(180%); + box-shadow: + 0 2px 16px rgba(0, 0, 0, 0.06), + 0 1px 8px rgba(0, 0, 0, 0.04), + 0 0 0 1px rgba(0, 0, 0, 0.03); + border: 0.5px solid rgba(0, 0, 0, 0.04); margin-bottom: 20px; } @@ -40,7 +47,7 @@ } .card { - border-radius: 12px; + border-radius: 16px; margin-bottom: 16px; } diff --git a/web/src/pages/Home.module.less b/web/src/pages/Home.module.less index 406881a..14279f7 100644 --- a/web/src/pages/Home.module.less +++ b/web/src/pages/Home.module.less @@ -1,7 +1,7 @@ .container { min-height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - padding: 24px; + background: #fafafa; + padding: 20px; padding-bottom: 80px; } @@ -9,29 +9,35 @@ display: flex; justify-content: space-between; align-items: flex-start; - margin-bottom: 32px; - color: white; + margin-bottom: 24px; .headerLeft { flex: 1; } .title { - color: white !important; - margin-bottom: 8px !important; - text-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + color: #1d1d1f !important; + margin-bottom: 4px !important; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.02); + font-weight: 700; } .subtitle { - color: rgba(255, 255, 255, 0.9); + color: #6e6e73; + font-size: 14px; } .userInfo { - background: rgba(255, 255, 255, 0.15); - padding: 12px 20px; - border-radius: 12px; - backdrop-filter: blur(10px); - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); + background: rgba(255, 255, 255, 0.85); + padding: 10px 16px; + border-radius: 16px; + backdrop-filter: blur(30px) saturate(180%); + -webkit-backdrop-filter: blur(30px) saturate(180%); + box-shadow: + 0 2px 8px rgba(0, 0, 0, 0.04), + 0 1px 3px rgba(0, 0, 0, 0.02), + 0 0 0 1px rgba(0, 0, 0, 0.03); + border: 0.5px solid rgba(0, 0, 0, 0.04); .userDetails { display: flex; @@ -39,12 +45,13 @@ gap: 2px; .userNickname { - color: white !important; - font-size: 16px; + color: #1d1d1f !important; + font-size: 15px; + font-weight: 600; } .userUsername { - color: rgba(255, 255, 255, 0.8) !important; + color: #6e6e73 !important; font-size: 12px; } } @@ -52,119 +59,284 @@ } .statsCard { - margin-bottom: 32px; - border-radius: 16px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + margin-bottom: 24px; + border-radius: 20px; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(30px) saturate(180%); + -webkit-backdrop-filter: blur(30px) saturate(180%); + box-shadow: + 0 2px 12px rgba(0, 0, 0, 0.05), + 0 1px 4px rgba(0, 0, 0, 0.03), + 0 0 0 1px rgba(0, 0, 0, 0.03); + border: 0.5px solid rgba(0, 0, 0, 0.04); + + :global { + .ant-statistic-title { + font-size: 13px; + margin-bottom: 4px; + } + } } .typeSection { - margin-bottom: 32px; + margin-bottom: 24px; .sectionTitle { - color: white !important; + color: #1d1d1f !important; margin-bottom: 16px !important; display: flex; align-items: center; gap: 8px; + font-weight: 700; + font-size: 18px !important; } } .typeCard { border-radius: 16px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - transition: all 0.3s ease; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(30px) saturate(180%); + -webkit-backdrop-filter: blur(30px) saturate(180%); + box-shadow: + 0 2px 12px rgba(0, 0, 0, 0.05), + 0 1px 4px rgba(0, 0, 0, 0.03), + 0 0 0 1px rgba(0, 0, 0, 0.03); + border: 0.5px solid rgba(0, 0, 0, 0.04); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); height: 100%; &:hover { transform: translateY(-4px); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); + background: rgba(255, 255, 255, 0.95); + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.08), + 0 4px 16px rgba(0, 0, 0, 0.04), + 0 0 0 1px rgba(0, 0, 0, 0.04); } .typeIcon { - margin-bottom: 12px; + margin-bottom: 10px; } .typeTitle { - margin: 8px 0 !important; + margin: 6px 0 4px 0 !important; + color: #1d1d1f; + font-weight: 600; + font-size: 16px !important; } .typeDesc { margin: 0 !important; + color: #6e6e73; + font-size: 12px; } } .quickStart { + .sectionTitle { + color: #1d1d1f !important; + margin-bottom: 16px !important; + display: flex; + align-items: center; + gap: 8px; + font-weight: 700; + font-size: 18px !important; + } + .quickCard { border-radius: 16px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - transition: all 0.3s ease; - background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(30px) saturate(180%); + -webkit-backdrop-filter: blur(30px) saturate(180%); + box-shadow: + 0 2px 12px rgba(0, 0, 0, 0.05), + 0 1px 4px rgba(0, 0, 0, 0.03), + 0 0 0 1px rgba(0, 0, 0, 0.03); + border: 0.5px solid rgba(0, 0, 0, 0.04); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + height: 100%; &:hover { transform: translateY(-4px); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); + background: rgba(255, 255, 255, 0.95); + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.08), + 0 4px 16px rgba(0, 0, 0, 0.04), + 0 0 0 1px rgba(0, 0, 0, 0.04); } } } -// 响应式设计 - 移动端 +// 响应式设计 - 移动端 (< 768px) @media (max-width: 768px) { .container { - padding: 16px; + padding: 12px; padding-bottom: 70px; } .header { flex-direction: column; align-items: stretch; - margin-bottom: 24px; + margin-bottom: 16px; .headerLeft { - margin-bottom: 16px; + margin-bottom: 12px; } .title { - font-size: 24px !important; + font-size: 22px !important; + } + + .subtitle { + font-size: 13px; } .userInfo { width: 100%; + padding: 8px 12px; + + :global { + .ant-space { + width: 100%; + justify-content: space-between; + } + + .ant-btn { + font-size: 12px; + padding: 4px 8px; + } + } } } .statsCard { - margin-bottom: 24px; + margin-bottom: 16px; + + :global { + .ant-statistic-title { + font-size: 12px; + } + + .ant-statistic-content { + font-size: 20px !important; + } + } } .typeSection { - margin-bottom: 24px; + margin-bottom: 16px; .sectionTitle { - font-size: 18px !important; + font-size: 16px !important; + margin-bottom: 12px !important; + } + } + + .typeCard { + border-radius: 12px; + + .typeIcon { + font-size: 32px !important; + } + + .typeTitle { + font-size: 14px !important; + } + + .typeDesc { + font-size: 11px; + } + } + + .quickStart { + .sectionTitle { + font-size: 16px !important; + margin-bottom: 12px !important; + } + + .quickCard { + border-radius: 12px; + + :global { + .ant-space { + gap: 12px !important; + } + + h5 { + font-size: 14px !important; + } + + .ant-typography { + font-size: 12px !important; + } + } } } } -// 响应式设计 - 平板 +// 响应式设计 - 平板 (769px - 1024px) @media (min-width: 769px) and (max-width: 1024px) { .container { - padding: 32px; - padding-bottom: 80px; - } -} - -// 响应式设计 - PC端 -@media (min-width: 1025px) { - .container { - max-width: 1200px; - margin: 0 auto; - padding: 40px; + padding: 24px; padding-bottom: 80px; } .header { .title { + font-size: 28px !important; + } + } + + .typeCard { + .typeIcon { font-size: 36px !important; } } } + +// 响应式设计 - PC端宽屏 (> 1025px) +@media (min-width: 1025px) { + .container { + max-width: 1400px; + margin: 0 auto; + padding: 32px 40px; + padding-bottom: 80px; + } + + .header { + margin-bottom: 32px; + + .title { + font-size: 32px !important; + } + + .subtitle { + font-size: 15px; + } + } + + .statsCard { + margin-bottom: 32px; + } + + .typeSection { + margin-bottom: 32px; + + .sectionTitle { + font-size: 20px !important; + } + } + + .quickStart { + .sectionTitle { + font-size: 20px !important; + } + } +} + +// 响应式设计 - 超宽屏 (> 1600px) +@media (min-width: 1600px) { + .container { + max-width: 1600px; + } +} diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx index ae1799e..a0f2fb4 100644 --- a/web/src/pages/Home.tsx +++ b/web/src/pages/Home.tsx @@ -208,9 +208,9 @@ const Home: React.FC = () => { <FileTextOutlined /> 选择题型 - + {questionTypes.map(type => ( - + { styles={{ body: { textAlign: 'center', - padding: '24px', + padding: '20px 12px', } }} > -
+
{type.icon}
{type.title} @@ -235,55 +235,67 @@ const Home: React.FC = () => { {/* 快速开始 */}
- navigate('/question')} - > - -
- -
-
- 随机练习 - 从所有题型中随机抽取 -
-
-
+ + <RocketOutlined /> 快速开始 + + + + navigate('/question')} + > + +
+ +
+
+ 随机练习 + 从所有题型中随机抽取 +
+
+
+ - navigate('/wrong-questions')} - style={{ marginTop: '16px' }} - > - -
- -
-
- 错题本 - 复习错题,巩固薄弱知识点 -
-
-
+ + navigate('/wrong-questions')} + > + +
+ +
+
+ 错题本 + 复习错题,巩固薄弱知识点 +
+
+
+ - navigate('/question-management')} - style={{ marginTop: '16px' }} - > - -
- -
-
- 题库管理 - 添加、编辑和删除题目 -
-
-
+ {/* 仅 yanlongqi 用户显示题库管理 */} + {userInfo?.username === 'yanlongqi' && ( + + navigate('/question-management')} + > + +
+ +
+
+ 题库管理 + 添加、编辑和删除题目 +
+
+
+ + )} +
) diff --git a/web/src/pages/Login.module.less b/web/src/pages/Login.module.less index 7bd91b1..5659b62 100644 --- a/web/src/pages/Login.module.less +++ b/web/src/pages/Login.module.less @@ -3,7 +3,7 @@ display: flex; align-items: center; justify-content: center; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: #fafafa; padding: 20px; } @@ -13,8 +13,15 @@ } .card { - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); - border-radius: 16px; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(40px) saturate(180%); + -webkit-backdrop-filter: blur(40px) saturate(180%); + box-shadow: + 0 4px 24px rgba(0, 0, 0, 0.06), + 0 2px 12px rgba(0, 0, 0, 0.04), + 0 0 0 1px rgba(0, 0, 0, 0.03); + border: 0.5px solid rgba(0, 0, 0, 0.04); + border-radius: 24px; :global { .ant-card-body { @@ -30,14 +37,27 @@ .title { margin: 0 0 8px 0 !important; - color: #667eea !important; + color: #007aff !important; font-weight: 800; - letter-spacing: 2px; + letter-spacing: 1px; } .subtitle { font-size: 15px; display: block; + color: #6e6e73; +} + +.registerTip { + text-align: center; + margin-top: 16px; + font-size: 14px; + + :global { + .ant-typography { + margin-right: 4px; + } + } } // 响应式设计 - 移动端 diff --git a/web/src/pages/Login.tsx b/web/src/pages/Login.tsx index 278c7ec..e965a66 100644 --- a/web/src/pages/Login.tsx +++ b/web/src/pages/Login.tsx @@ -1,10 +1,10 @@ import React, { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' -import { Form, Input, Button, Card, Tabs, message, Typography } from 'antd' +import { Form, Input, Button, Card, Modal, message, Typography } from 'antd' import { UserOutlined, LockOutlined, IdcardOutlined } from '@ant-design/icons' import styles from './Login.module.less' -const { Title, Text } = Typography +const { Title, Text, Link } = Typography interface LoginResponse { success: boolean @@ -21,8 +21,8 @@ interface LoginResponse { const Login: React.FC = () => { const navigate = useNavigate() - const [activeTab, setActiveTab] = useState('login') const [loading, setLoading] = useState(false) + const [registerModalVisible, setRegisterModalVisible] = useState(false) const [loginForm] = Form.useForm() const [registerForm] = Form.useForm() @@ -84,6 +84,7 @@ const Login: React.FC = () => { localStorage.setItem('user', JSON.stringify(data.data.user)) message.success('注册成功') + setRegisterModalVisible(false) navigate('/') } else { message.error(data.message || '注册失败') @@ -96,94 +97,6 @@ const Login: React.FC = () => { } } - const loginTabContent = ( -
- - } - placeholder="请输入用户名" - /> - - - - } - placeholder="请输入密码" - /> - - - - - -
- ) - - const registerTabContent = ( -
- - } - placeholder="请输入用户名" - /> - - - - } - placeholder="请输入密码(至少6位)" - /> - - - - } - placeholder="请输入昵称(可选)" - /> - - - - - -
- ) - return (
@@ -193,25 +106,109 @@ const Login: React.FC = () => { 欢迎使用题库系统
- +
+ + } + placeholder="请输入用户名" + /> + + + + } + placeholder="请输入密码" + /> + + + + + + +
+ 还没有账号? + setRegisterModalVisible(true)}>立即注册 +
+
+ + {/* 注册模态框 */} + { + setRegisterModalVisible(false) + registerForm.resetFields() + }} + footer={null} + destroyOnClose + > +
+ + } + placeholder="请输入用户名" + /> + + + + } + placeholder="请输入密码(至少6位)" + /> + + + + } + placeholder="请输入昵称(可选)" + /> + + + + + +
+
) } diff --git a/web/src/pages/Question.module.less b/web/src/pages/Question.module.less index 0bad6f0..71cf997 100644 --- a/web/src/pages/Question.module.less +++ b/web/src/pages/Question.module.less @@ -1,6 +1,6 @@ .container { min-height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: #fafafa; padding: 20px; padding-bottom: 80px; } @@ -17,32 +17,43 @@ margin-bottom: 24px; .title { - color: white !important; + color: #1d1d1f !important; margin: 0 !important; + font-weight: 700; } } .questionCard { - border-radius: 16px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + border-radius: 20px; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(40px) saturate(180%); + -webkit-backdrop-filter: blur(40px) saturate(180%); + box-shadow: + 0 2px 16px rgba(0, 0, 0, 0.06), + 0 1px 8px rgba(0, 0, 0, 0.04), + 0 0 0 1px rgba(0, 0, 0, 0.03); + border: 0.5px solid rgba(0, 0, 0, 0.04); margin-bottom: 24px; } .questionNumber { - color: #666; + color: #6e6e73; margin: 8px 0 16px 0 !important; } .questionContent { font-size: 18px; line-height: 1.6; - color: #333; + color: #1d1d1f; margin-bottom: 16px; } .fillInput { - border-bottom: 2px solid #1677ff !important; + border-bottom: 2px solid #007aff !important; border-radius: 0 !important; + background: rgba(255, 255, 255, 0.7); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); } .buttonGroup { @@ -74,7 +85,7 @@ } .questionCard { - border-radius: 12px; + border-radius: 16px; margin-bottom: 16px; } diff --git a/web/src/pages/WrongQuestions.module.less b/web/src/pages/WrongQuestions.module.less index 6677193..3d7e6cc 100644 --- a/web/src/pages/WrongQuestions.module.less +++ b/web/src/pages/WrongQuestions.module.less @@ -1,6 +1,6 @@ .container { min-height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: #fafafa; padding: 0; } @@ -10,24 +10,32 @@ } .backButton { - color: white; + color: #007aff; margin-bottom: 12px; &:hover { - color: rgba(255, 255, 255, 0.85); + color: #0051d5; } } .title { - color: white !important; + color: #1d1d1f !important; margin: 0 !important; font-size: 28px; + font-weight: 700; } .statsCard { margin: 0 20px 20px; - border-radius: 16px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + border-radius: 20px; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(40px) saturate(180%); + -webkit-backdrop-filter: blur(40px) saturate(180%); + box-shadow: + 0 2px 16px rgba(0, 0, 0, 0.06), + 0 1px 8px rgba(0, 0, 0, 0.04), + 0 0 0 1px rgba(0, 0, 0, 0.03); + border: 0.5px solid rgba(0, 0, 0, 0.04); } .actions { @@ -38,14 +46,21 @@ .listCard { margin: 0 20px 20px; - border-radius: 16px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + border-radius: 20px; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(40px) saturate(180%); + -webkit-backdrop-filter: blur(40px) saturate(180%); + box-shadow: + 0 2px 16px rgba(0, 0, 0, 0.06), + 0 1px 8px rgba(0, 0, 0, 0.04), + 0 0 0 1px rgba(0, 0, 0, 0.03); + border: 0.5px solid rgba(0, 0, 0, 0.04); padding-bottom: 60px; // 为底部导航留空间 } .listItem { padding: 16px 0 !important; - border-bottom: 1px solid #f0f0f0 !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.04) !important; &:last-child { border-bottom: none !important; @@ -54,6 +69,7 @@ .questionContent { margin-top: 8px; + color: #1d1d1f; } // 响应式设计 - 移动端 @@ -68,7 +84,7 @@ .statsCard { margin: 0 16px 16px; - border-radius: 12px; + border-radius: 16px; } .actions { @@ -77,7 +93,7 @@ .listCard { margin: 0 16px 16px; - border-radius: 12px; + border-radius: 16px; } .listItem { diff --git a/web/src/utils/request.ts b/web/src/utils/request.ts index 7a5e475..4a5488d 100644 --- a/web/src/utils/request.ts +++ b/web/src/utils/request.ts @@ -34,8 +34,12 @@ instance.interceptors.response.use( if (error.response) { switch (error.response.status) { case 401: - // 未授权,跳转到登录页 - console.error('未授权,请登录') + // 未授权,清除本地存储并跳转到登录页 + console.error('Token已过期或未授权,请重新登录') + localStorage.removeItem('token') + localStorage.removeItem('user') + // 跳转到登录页 + window.location.href = '/login' break case 403: console.error('没有权限访问') diff --git a/web/vite.config.ts b/web/vite.config.ts index 4dada8b..18dd6b9 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -14,11 +14,14 @@ export default defineConfig({ preprocessorOptions: { less: { javascriptEnabled: true, - // antd 主题定制 + // antd 主题定制 - 白色毛玻璃风格 modifyVars: { - '@primary-color': '#1677ff', // 主色调 - '@link-color': '#1677ff', // 链接色 - '@border-radius-base': '8px', // 组件圆角 + '@primary-color': '#007aff', // macOS 蓝色 + '@link-color': '#007aff', // 链接色 + '@border-radius-base': '12px', // 组件圆角 + '@layout-body-background': '#ffffff', // 白色背景 + '@component-background': 'rgba(255, 255, 255, 0.8)', // 半透明组件背景 + '@border-color-base': 'rgba(0, 0, 0, 0.06)', // 边框色 }, }, },