Compare commits
2 Commits
9791e76d17
...
9898a5ea38
| Author | SHA1 | Date | |
|---|---|---|---|
| 9898a5ea38 | |||
| 73c41b76ab |
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,7 @@ import { useAuth } from '@/components/auth/AuthContext';
|
|||||||
import { authReducer, initialAuthState, AuthState, AuthAction } from './authReducer';
|
import { authReducer, initialAuthState, AuthState, AuthAction } from './authReducer';
|
||||||
import { getCaptchaApiV1AuthCaptchaGet, loginApiV1AuthLoginPost } from '@/lib/api/sdk.gen';
|
import { getCaptchaApiV1AuthCaptchaGet, loginApiV1AuthLoginPost } from '@/lib/api/sdk.gen';
|
||||||
import type { CaptchaResponse } from '@/lib/api/types.gen';
|
import type { CaptchaResponse } from '@/lib/api/types.gen';
|
||||||
|
import {PERSONAL_CELTRAL_PAGE} from "@/config/constants"
|
||||||
interface LoginFormProps {
|
interface LoginFormProps {
|
||||||
onRegisterClick: () => void;
|
onRegisterClick: () => void;
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ export function LoginForm({ onRegisterClick }: LoginFormProps) {
|
|||||||
login(userData);
|
login(userData);
|
||||||
toast.success('登录成功!正在跳转...');
|
toast.success('登录成功!正在跳转...');
|
||||||
// 跳转到个人中心页面
|
// 跳转到个人中心页面
|
||||||
window.location.href = '/central-config/personal-center/personal-info';
|
window.location.href = PERSONAL_CELTRAL_PAGE;
|
||||||
} else {
|
} else {
|
||||||
dispatch({ type: 'SET_ERROR', payload: '登录失败,请检查用户名和密码' });
|
dispatch({ type: 'SET_ERROR', payload: '登录失败,请检查用户名和密码' });
|
||||||
toast.error('登录失败,请检查用户名和密码');
|
toast.error('登录失败,请检查用户名和密码');
|
||||||
@@ -279,6 +279,7 @@ export function LoginForm({ onRegisterClick }: LoginFormProps) {
|
|||||||
value={state.passwordForm.captcha}
|
value={state.passwordForm.captcha}
|
||||||
onChange={(value) => dispatch({ type: 'UPDATE_PASSWORD_FORM', payload: { captcha: value } })}
|
onChange={(value) => dispatch({ type: 'UPDATE_PASSWORD_FORM', payload: { captcha: value } })}
|
||||||
onCaptchaChange={(captchaData) => setPasswordCaptchaData(captchaData)}
|
onCaptchaChange={(captchaData) => setPasswordCaptchaData(captchaData)}
|
||||||
|
instanceId="password-login"
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -357,6 +358,7 @@ export function LoginForm({ onRegisterClick }: LoginFormProps) {
|
|||||||
<CaptchaInput
|
<CaptchaInput
|
||||||
value={state.phoneForm.captcha}
|
value={state.phoneForm.captcha}
|
||||||
onChange={(value) => dispatch({ type: 'UPDATE_PHONE_FORM', payload: { captcha: value } })}
|
onChange={(value) => dispatch({ type: 'UPDATE_PHONE_FORM', payload: { captcha: value } })}
|
||||||
|
instanceId="phone-login"
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -17,13 +17,9 @@ export default function NotFound() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen relative flex items-center justify-center">
|
<Card className="backdrop-blur-md absolute inset-x-6 top-6 bottom-6
|
||||||
|
flex-1 min-h-0">
|
||||||
{/* 主要内容 */}
|
<CardHeader className="text-center p-0">
|
||||||
<div className="relative z-10 w-full max-w-2xl mx-auto p-6">
|
|
||||||
{/* 404 状态卡片 */}
|
|
||||||
<Card className="backdrop-blur-md bg-background/80 border-border/50 shadow-2xl">
|
|
||||||
<CardHeader className="text-center pb-2">
|
|
||||||
<div className="flex justify-center mb-4">
|
<div className="flex justify-center mb-4">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="text-8xl font-bold text-destructive animate-pulse">404</div>
|
<div className="text-8xl font-bold text-destructive animate-pulse">404</div>
|
||||||
@@ -43,7 +39,7 @@ export default function NotFound() {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6 p-0">
|
||||||
{/* 错误信息提示 */}
|
{/* 错误信息提示 */}
|
||||||
<Alert>
|
<Alert>
|
||||||
<Search className="h-4 w-4" />
|
<Search className="h-4 w-4" />
|
||||||
@@ -100,7 +96,7 @@ export default function NotFound() {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<CardFooter className="flex flex-col space-y-4 pt-6 border-t">
|
<CardFooter className="flex flex-col space-y-4 pt-6 border-t p-0">
|
||||||
{/* 帮助信息 */}
|
{/* 帮助信息 */}
|
||||||
<div className="text-center text-sm text-muted-foreground">
|
<div className="text-center text-sm text-muted-foreground">
|
||||||
<p>需要帮助?请联系系统管理员</p>
|
<p>需要帮助?请联系系统管理员</p>
|
||||||
@@ -113,7 +109,5 @@ export default function NotFound() {
|
|||||||
</div>
|
</div>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -236,24 +236,54 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化时检查 localStorage并验证用户
|
// 统一的认证初始化和路由监听
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// 检查是否有存储的用户信息和 token,并设置 cookie
|
// 处理当前路径的逻辑
|
||||||
const storedUser = localStorage.getItem('user');
|
const handleCurrentPath = () => {
|
||||||
if (storedUser) {
|
const currentPath = typeof window !== 'undefined' ? window.location.pathname : '';
|
||||||
try {
|
|
||||||
const userData = JSON.parse(storedUser);
|
|
||||||
if (userData.token && !document.cookie.includes('auth-token')) {
|
|
||||||
setTokenCookie(userData.token);
|
|
||||||
console.log('🔄 初始化时设置cookie');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('解析存储用户信息失败:', error);
|
|
||||||
localStorage.removeItem('user');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validateUser();
|
// 如果在登录页面,跳过验证,直接设置为非加载状态
|
||||||
|
if (currentPath.startsWith('/login')) {
|
||||||
|
console.log('📄 检测到登录页面,跳过用户验证');
|
||||||
|
stopTokenRefresh(); // 停止任何正在进行的 token 刷新
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有存储的用户信息和 token,并设置 cookie
|
||||||
|
const storedUser = localStorage.getItem('user');
|
||||||
|
if (storedUser) {
|
||||||
|
try {
|
||||||
|
const userData = JSON.parse(storedUser);
|
||||||
|
if (userData.token && !document.cookie.includes('auth-token')) {
|
||||||
|
setTokenCookie(userData.token);
|
||||||
|
console.log('🔄 初始化时设置cookie');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析存储用户信息失败:', error);
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有在非登录页面才进行用户验证
|
||||||
|
validateUser();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 立即处理当前路径
|
||||||
|
handleCurrentPath();
|
||||||
|
|
||||||
|
// 监听路由变化
|
||||||
|
const handlePathChange = () => {
|
||||||
|
console.log('🔄 路由变化,重新检查认证状态');
|
||||||
|
handleCurrentPath();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听 popstate 事件(浏览器前进后退)
|
||||||
|
window.addEventListener('popstate', handlePathChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('popstate', handlePathChange);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 清理定时器
|
// 清理定时器
|
||||||
|
|||||||
@@ -52,11 +52,6 @@ export function ClientAuthInterceptor({ children }: ClientAuthInterceptorProps)
|
|||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果未认证且不在认证页面,显示跳转状态
|
|
||||||
if (!isAuthenticated) {
|
|
||||||
return <LoadingScreen variant="redirect" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 认证通过,渲染子组件
|
// 认证通过,渲染子组件
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
@@ -5,10 +5,9 @@ import { Sprout, Map, Clipboard, Package, Brain, Droplets, Settings } from 'luci
|
|||||||
import { MessageBell } from './components/MessageBell';
|
import { MessageBell } from './components/MessageBell';
|
||||||
import { UserProfile } from './components/UserProfile';
|
import { UserProfile } from './components/UserProfile';
|
||||||
import { ThemeToggle } from './ThemeToggle';
|
import { ThemeToggle } from './ThemeToggle';
|
||||||
import { AuthProvider } from './components/auth/AuthContext';
|
|
||||||
import { useElementHeight } from '@/hooks/useElementHeight';
|
import { useElementHeight } from '@/hooks/useElementHeight';
|
||||||
import { useViewHeight } from '@/hooks/useViewHeight';
|
import { useViewHeight } from '@/hooks/useViewHeight';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import { useRef, useEffect, useState } from 'react';
|
import { useRef, useEffect, useState } from 'react';
|
||||||
// 注释掉 Accordion 相关导入,因为不再需要二级菜单
|
// 注释掉 Accordion 相关导入,因为不再需要二级菜单
|
||||||
// import {
|
// import {
|
||||||
@@ -69,6 +68,7 @@ const Navbar1 = ({ navbarData }: Navbar1Props) => {
|
|||||||
const menu = navbarData.menu
|
const menu = navbarData.menu
|
||||||
const auth = navbarData.auth
|
const auth = navbarData.auth
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
const router = useRouter()
|
||||||
const containerStyle = {
|
const containerStyle = {
|
||||||
maxWidth:"100%",marginLeft:"0px",marginRight:"0px",paddingLeft:"1rem",paddingRight:"0rem"
|
maxWidth:"100%",marginLeft:"0px",marginRight:"0px",paddingLeft:"1rem",paddingRight:"0rem"
|
||||||
}
|
}
|
||||||
@@ -107,8 +107,7 @@ const Navbar1 = ({ navbarData }: Navbar1Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthProvider>
|
<section className="py-4" ref={elementRef}>
|
||||||
<section className="py-4" ref={elementRef}>
|
|
||||||
<div className="container" style = {containerStyle}>
|
<div className="container" style = {containerStyle}>
|
||||||
{/* Desktop Menu */}
|
{/* Desktop Menu */}
|
||||||
<nav className="hidden justify-between lg:flex">
|
<nav className="hidden justify-between lg:flex">
|
||||||
@@ -151,7 +150,7 @@ const Navbar1 = ({ navbarData }: Navbar1Props) => {
|
|||||||
`}</style>
|
`}</style>
|
||||||
<NavigationMenu>
|
<NavigationMenu>
|
||||||
<NavigationMenuList className="flex gap-1 min-w-max">
|
<NavigationMenuList className="flex gap-1 min-w-max">
|
||||||
{menu.map((item) => renderMenuItem(item, isMenuActive))}
|
{menu.map((item) => renderMenuItem(item, isMenuActive, router))}
|
||||||
</NavigationMenuList>
|
</NavigationMenuList>
|
||||||
</NavigationMenu>
|
</NavigationMenu>
|
||||||
</div>
|
</div>
|
||||||
@@ -195,7 +194,7 @@ const Navbar1 = ({ navbarData }: Navbar1Props) => {
|
|||||||
<div className="flex flex-col gap-6 p-4">
|
<div className="flex flex-col gap-6 p-4">
|
||||||
{/* 简化移动端菜单,不再使用 Accordion */}
|
{/* 简化移动端菜单,不再使用 Accordion */}
|
||||||
<div className="flex w-full flex-col gap-4">
|
<div className="flex w-full flex-col gap-4">
|
||||||
{menu.map((item) => renderMobileMenuItem(item, isMenuActive))}
|
{menu.map((item) => renderMobileMenuItem(item, isMenuActive, router))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
@@ -216,11 +215,10 @@ const Navbar1 = ({ navbarData }: Navbar1Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</AuthProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderMenuItem = (item: MenuItem, isMenuActive: (url: string) => boolean) => {
|
const renderMenuItem = (item: MenuItem, isMenuActive: (url: string) => boolean, router: any) => {
|
||||||
// 注释掉二级菜单相关代码,项目不需要二级菜单
|
// 注释掉二级菜单相关代码,项目不需要二级菜单
|
||||||
// if (item.items) {
|
// if (item.items) {
|
||||||
// return (
|
// return (
|
||||||
@@ -238,14 +236,20 @@ const renderMenuItem = (item: MenuItem, isMenuActive: (url: string) => boolean)
|
|||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
const handleClick = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
router.push(item.url);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationMenuItem key={item.title}>
|
<NavigationMenuItem key={item.title}>
|
||||||
<NavigationMenuLink
|
<NavigationMenuLink
|
||||||
href={item.url}
|
href={item.url}
|
||||||
|
onClick={handleClick}
|
||||||
data-menu-item="true"
|
data-menu-item="true"
|
||||||
data-menu-url={item.url}
|
data-menu-url={item.url}
|
||||||
className={`
|
className={`
|
||||||
inline-flex h-10 w-max items-center justify-center rounded-md px-4 py-2 text-sm font-medium gap-2 whitespace-nowrap relative
|
inline-flex h-10 w-max items-center justify-center rounded-md px-4 py-2 text-sm font-medium gap-2 whitespace-nowrap relative cursor-pointer
|
||||||
${isMenuActive(item.url)
|
${isMenuActive(item.url)
|
||||||
? 'bg-primary/10'
|
? 'bg-primary/10'
|
||||||
: 'bg-background hover:bg-muted hover:text-accent-foreground'
|
: 'bg-background hover:bg-muted hover:text-accent-foreground'
|
||||||
@@ -275,7 +279,7 @@ const renderMenuItem = (item: MenuItem, isMenuActive: (url: string) => boolean)
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderMobileMenuItem = (item: MenuItem, isMenuActive: (url: string) => boolean) => {
|
const renderMobileMenuItem = (item: MenuItem, isMenuActive: (url: string) => boolean, router: any) => {
|
||||||
// 注释掉移动端二级菜单相关代码
|
// 注释掉移动端二级菜单相关代码
|
||||||
// if (item.items) {
|
// if (item.items) {
|
||||||
// return (
|
// return (
|
||||||
@@ -293,12 +297,17 @@ const renderMobileMenuItem = (item: MenuItem, isMenuActive: (url: string) => boo
|
|||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
const handleClick = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
router.push(item.url);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<div
|
||||||
key={item.title}
|
key={item.title}
|
||||||
href={item.url}
|
onClick={handleClick}
|
||||||
className={`
|
className={`
|
||||||
text-md font-semibold flex items-center gap-2 p-2 rounded-md transition-colors
|
text-md font-semibold flex items-center gap-2 p-2 rounded-md transition-colors cursor-pointer
|
||||||
${isMenuActive(item.url)
|
${isMenuActive(item.url)
|
||||||
? 'bg-primary/10 text-primary'
|
? 'bg-primary/10 text-primary'
|
||||||
: 'hover:bg-muted hover:text-accent-foreground'
|
: 'hover:bg-muted hover:text-accent-foreground'
|
||||||
@@ -316,7 +325,7 @@ const renderMobileMenuItem = (item: MenuItem, isMenuActive: (url: string) => boo
|
|||||||
<span className={isMenuActive(item.url) ? 'text-primary' : ''}>
|
<span className={isMenuActive(item.url) ? 'text-primary' : ''}>
|
||||||
{item.title}
|
{item.title}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export function MainContent({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex flex-col bg-background h-full">
|
<div className="flex-1 flex flex-col bg-background h-full">
|
||||||
<div className="flex-1 p-6 min-h-0 content-container">
|
<div className="flex-1 p-6 min-h-0 content-container relative">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Bell, CheckCircle, X } from 'lucide-react';
|
import { Bell, CheckCircle, X } from 'lucide-react';
|
||||||
import { useAuth } from './auth/AuthContext';
|
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { Badge } from './ui/badge';
|
import { Badge } from './ui/badge';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
@@ -25,7 +24,6 @@ interface MessageBellProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function MessageBell({ onMessageClick }: MessageBellProps) {
|
export function MessageBell({ onMessageClick }: MessageBellProps) {
|
||||||
const { authState } = useAuth();
|
|
||||||
const [showMessages, setShowMessages] = useState(false);
|
const [showMessages, setShowMessages] = useState(false);
|
||||||
const [showMessageDetail, setShowMessageDetail] = useState(false);
|
const [showMessageDetail, setShowMessageDetail] = useState(false);
|
||||||
const [selectedMessage, setSelectedMessage] = useState<Message | null>(null);
|
const [selectedMessage, setSelectedMessage] = useState<Message | null>(null);
|
||||||
|
|||||||
@@ -55,3 +55,6 @@ export const THEME = {
|
|||||||
error: '#ef4444'
|
error: '#ef4444'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 默认有权限的首页 个人中心
|
||||||
|
export const PERSONAL_CELTRAL_PAGE = "central-config/personal-center/personal-info"
|
||||||
Reference in New Issue
Block a user