diff --git a/crop-x/src/app/(auth)/login/components/LoginForm.tsx b/crop-x/src/app/(auth)/login/components/LoginForm.tsx index f8cbcc0..e19eb7c 100644 --- a/crop-x/src/app/(auth)/login/components/LoginForm.tsx +++ b/crop-x/src/app/(auth)/login/components/LoginForm.tsx @@ -28,7 +28,7 @@ import { import { toast } from 'sonner'; import { useAuth } from '@/components/auth/AuthContext'; import { authReducer, initialAuthState, AuthState, AuthAction } from './authReducer'; -import { loginApiV1AuthLoginPost } from '@/lib/api/sdk.gen'; +import { getCaptchaApiV1AuthCaptchaGet, loginApiV1AuthLoginPost } from '@/lib/api/sdk.gen'; import type { CaptchaResponse } from '@/lib/api/types.gen'; interface LoginFormProps { @@ -128,6 +128,8 @@ export function LoginForm({ onRegisterClick }: LoginFormProps) { enterpriseId: response.data.enterprise_id || '', enterpriseName: response.data.enterprise_name || '', createdAt: response.data.created_at || new Date().toISOString(), + // 重要:存储token到用户对象中 + token: response.data.access_token || response.data.token || null, }; // 打印登录成功日志 @@ -137,10 +139,20 @@ export function LoginForm({ onRegisterClick }: LoginFormProps) { timestamp: new Date().toISOString() }); + // 验证token是否正确存储 + if (userData.token) { + console.log('🔑 Token已存储:', userData.token.substring(0, 20) + '...'); + } else { + console.warn('⚠️ 未找到token,请检查API响应格式'); + } + login(userData); - toast.success('登录成功!'); - // 暂时不实现页面跳转 - console.log('✅ 登录流程完成,等待后续页面跳转实现'); + toast.success('登录成功!正在跳转...'); + + // 跳转到个人中心页面 + setTimeout(() => { + window.location.href = '/central-config/personal-center/personal-info'; + }, 1000); } else { dispatch({ type: 'SET_ERROR', payload: '登录失败,请检查用户名和密码' }); toast.error('登录失败,请检查用户名和密码'); diff --git a/crop-x/src/app/layout.tsx b/crop-x/src/app/layout.tsx index 3dda49b..860741e 100644 --- a/crop-x/src/app/layout.tsx +++ b/crop-x/src/app/layout.tsx @@ -1,12 +1,24 @@ +'use client'; + +import { useEffect } from 'react'; +import { AuthProvider } from '@/components/auth/AuthContext'; + export default function AppLayout({ children, }: { children: React.ReactNode }) { + // 使用useEffect确保主题只在客户端设置,避免水合问题 + useEffect(() => { + // 可以在这里添加客户端特定的初始化逻辑 + }, []); + return ( - - - {children} + + + + {children} + ) diff --git a/crop-x/src/app/page.tsx b/crop-x/src/app/page.tsx index 06c5e69..c6e2fda 100644 --- a/crop-x/src/app/page.tsx +++ b/crop-x/src/app/page.tsx @@ -1,12 +1,25 @@ -export default function HomePage({ - children, -}: { - children: React.ReactNode -}) { +'use client'; + +import { useAuth } from '@/components/auth/AuthContext'; +import { LoadingScreen } from '@/components/auth/LoadingScreen'; +import { redirect } from 'next/navigation'; + +export default function HomePage() { + const { user, loading, isAuthenticated } = useAuth(); + + // 如果正在加载,显示加载屏幕 + if (loading) { + return ; + } + console.log('isAuthenticated:',isAuthenticated) + // 如果未认证,重定向到登录页 + if (!isAuthenticated) { + redirect('/login'); + } + + // 用户已认证,显示主页内容 return ( -
- {children} -
- ) +
+ ); } \ No newline at end of file diff --git a/crop-x/src/components/auth/AuthContext.tsx b/crop-x/src/components/auth/AuthContext.tsx index 280973e..e5038ab 100644 --- a/crop-x/src/components/auth/AuthContext.tsx +++ b/crop-x/src/components/auth/AuthContext.tsx @@ -1,6 +1,7 @@ 'use client'; import React, { createContext, useContext, useState, ReactNode } from 'react'; +import { getCurrentUserInfoApiV1AuthMeGet } from '@/lib/api/sdk.gen'; interface User { id: string; @@ -9,6 +10,7 @@ interface User { email?: string; phone?: string; enterpriseId?: string; + token?: string; } interface AuthContextType { @@ -16,6 +18,8 @@ interface AuthContextType { login: (user: User) => void; logout: () => void; isAuthenticated: boolean; + loading: boolean; + validateUser: () => Promise; } const AuthContext = createContext(undefined); @@ -26,29 +30,65 @@ interface AuthProviderProps { export function AuthProvider({ children }: AuthProviderProps) { const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); const login = (userData: User) => { setUser(userData); // 存储到 localStorage localStorage.setItem('user', JSON.stringify(userData)); + setLoading(false); }; const logout = () => { setUser(null); localStorage.removeItem('user'); + setLoading(false); }; - // 初始化时检查 localStorage - React.useEffect(() => { - const storedUser = localStorage.getItem('user'); - if (storedUser) { - try { - setUser(JSON.parse(storedUser)); - } catch (error) { - console.error('Failed to parse stored user data:', error); - localStorage.removeItem('user'); + // 验证当前用户信息 + const validateUser = async () => { + try { + const storedUser = localStorage.getItem('user'); + if (!storedUser) { + setLoading(false); + return; } + + const userData = JSON.parse(storedUser); + + // 使用 SDK 调用 /api/v1/auth/me 验证用户信息 + const response = await getCurrentUserInfoApiV1AuthMeGet({ + headers: { + 'Authorization': `Bearer ${userData.token}`, + }, + }); + + if (response.data) { + // 更新用户信息(可能包含最新的权限、角色等) + setUser({ + ...userData, + ...response.data.data, // 合并最新的用户信息 + }); + console.log('✅ 用户验证成功,最新用户信息:', response.data.data); + } else { + // Token无效,清除用户信息 + console.warn('⚠️ Token验证失败,清除用户信息'); + localStorage.removeItem('user'); + setUser(null); + } + } catch (error: any) { + console.error('❌ 用户验证失败:', error); + // 验证失败时也清除用户信息,避免不一致状态 + localStorage.removeItem('user'); + setUser(null); + } finally { + setLoading(false); } + }; + + // 初始化时检查 localStorage并验证用户 + React.useEffect(() => { + validateUser(); }, []); const value: AuthContextType = { @@ -56,6 +96,8 @@ export function AuthProvider({ children }: AuthProviderProps) { login, logout, isAuthenticated: !!user, + loading, + validateUser, // 暴露验证方法供手动刷新使用 }; return {children}; diff --git a/crop-x/src/components/auth/LoadingScreen.tsx b/crop-x/src/components/auth/LoadingScreen.tsx new file mode 100644 index 0000000..2185478 --- /dev/null +++ b/crop-x/src/components/auth/LoadingScreen.tsx @@ -0,0 +1,32 @@ +'use client'; + +import React from 'react'; +import { Loader2 } from 'lucide-react'; + +interface LoadingScreenProps { + message?: string; +} + +export function LoadingScreen({ message = '正在验证用户身份...' }: LoadingScreenProps) { + return ( +
+
+
+ +
+

智慧农业生产管理系统

+

{message}

+ + {/* 加载进度条 */} +
+
+
+ + {/* 提示信息 */} +
+

正在验证您的身份信息,请稍候...

+
+
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/lib/api/README.md b/crop-x/src/lib/api/README.md new file mode 100644 index 0000000..4b68b7e --- /dev/null +++ b/crop-x/src/lib/api/README.md @@ -0,0 +1,127 @@ +# API SDK 使用指南 + +## 🚀 概述 + +本项目提供了两种API调用方式: + +1. **原始SDK** (`sdk.gen.ts`) - 直接API调用,无认证 +2. **认证SDK** (`authenticated-sdk.ts`) - 自动添加Token的API调用 + +## 📝 使用方法 + +### 1. 无认证的API调用(如登录、获取验证码) + +```typescript +import { api } from '@/lib/api/authenticated-sdk'; + +// 获取验证码 - 不需要token +const captchaResponse = await api.getCaptcha(); + +// 用户登录 - 不需要token +const loginResponse = await api.login({ + body: { + identifier: 'admin', + password: 'password123', + captcha_id: 'captcha-id', + captcha_text: 'ABCD' + } +}); +``` + +### 2. 需要认证的API调用 + +```typescript +import { authenticatedSdk } from '@/lib/api/authenticated-sdk'; + +// 获取当前用户信息 - 自动添加token +const userResponse = await authenticatedSdk.getCurrentUser(); + +// 获取用户列表 - 自动添加token +const usersResponse = await authenticatedSdk.getUsers({ + query: { page: 1, size: 10 } +}); + +// 创建用户 - 自动添加token +const createResponse = await authenticatedSdk.createUser({ + body: { + username: 'newuser', + email: 'user@example.com' + } +}); +``` + +## 🔧 核心特性 + +### 自动Token管理 +- 自动从localStorage获取token +- 自动添加到请求头 `Authorization: Bearer {token}` +- 请求日志记录 + +### 401错误处理 +- 检测401响应自动清除本地认证信息 +- 自动跳转到登录页面 + +### 类型安全 +- 完整的TypeScript类型支持 +- 智能代码提示 + +## 📋 API方法列表 + +### 认证相关 +- `api.getCaptcha()` - 获取验证码 +- `api.login(options)` - 用户登录 +- `api.logout(options)` - 用户登出 +- `authenticatedSdk.getCurrentUser()` - 获取当前用户 +- `authenticatedSdk.refreshToken()` - 刷新token + +### 用户管理 +- `authenticatedSdk.getUsers()` - 获取用户列表 +- `authenticatedSdk.getUser(options)` - 获取单个用户 +- `authenticatedSdk.createUser(options)` - 创建用户 +- `authenticatedSdk.updateUser(options)` - 更新用户 +- `authenticatedSdk.deleteUser(options)` - 删除用户 + +### 部门管理 +- `authenticatedSdk.getDepartments()` - 获取部门列表 +- `authenticatedSdk.getDepartment(options)` - 获取单个部门 +- `authenticatedSdk.createDepartment(options)` - 创建部门 +- `authenticatedSdk.updateDepartment(options)` - 更新部门 +- `authenticatedSdk.deleteDepartment(options)` - 删除部门 + +### 租户管理 +- `authenticatedSdk.getTenants()` - 获取租户列表 +- `authenticatedSdk.getTenant(options)` - 获取单个租户 +- `authenticatedSdk.getCurrentTenant()` - 获取当前租户信息 + +## ⚠️ 重要说明 + +1. **API生成覆盖**: `sdk.gen.ts` 和 `client.gen.ts` 会被 `npm run api:generate` 覆盖 +2. **安全文件**: `authenticated-sdk.ts` 不会被覆盖,可安全使用 +3. **Token存储**: Token存储在localStorage的user对象中 +4. **代理配置**: 所有请求通过Next.js代理到真实API地址 + +## 🔄 迁移指南 + +### 从原始SDK迁移到认证SDK + +```typescript +// ❌ 旧方式 - 可能缺少认证 +import { getUsersApiV1UsersGet } from '@/lib/api/sdk.gen'; +const response = await getUsersApiV1UsersGet(); + +// ✅ 新方式 - 自动添加认证 +import { authenticatedSdk } from '@/lib/api/authenticated-sdk'; +const response = await authenticatedSdk.getUsers(); +``` + +### 混合使用场景 + +```typescript +import { api, authenticatedSdk } from '@/lib/api/authenticated-sdk'; + +// 登录 - 不需要认证 +const loginResponse = await api.login({ body: loginData }); + +// 登录成功后,获取用户信息 - 需要认证 +const userInfo = await authenticatedSdk.getCurrentUser(); +``` \ No newline at end of file diff --git a/crop-x/src/lib/api/authenticated-sdk.ts b/crop-x/src/lib/api/authenticated-sdk.ts new file mode 100644 index 0000000..8fb74d8 --- /dev/null +++ b/crop-x/src/lib/api/authenticated-sdk.ts @@ -0,0 +1,108 @@ +/** + * 认证API SDK包装器 + * 为所有API调用自动添加Token,不会被API生成工具覆盖 + */ + +import { client } from './client.gen'; +import * as api from './sdk.gen'; +import type { Options } from './sdk.gen'; + +// 获取存储的token +const getAuthToken = (): string | null => { + try { + const storedUser = localStorage.getItem('user'); + const user = storedUser ? JSON.parse(storedUser) : null; + return user?.token || null; + } catch (error) { + console.error('获取token失败:', error); + return null; + } +}; + +// 创建带认证的客户端选项 +const createAuthenticatedOptions = ( + originalOptions: Options = {} as Options +): Options => { + const token = getAuthToken(); + + const authenticatedOptions: Options = { + ...originalOptions, + client: client + }; + + // 如果有token,添加到请求头 + if (token) { + authenticatedOptions.headers = { + ...originalOptions.headers, + Authorization: `Bearer ${token}` + }; + console.log('🔑 为API请求添加Token:', (originalOptions as any).url || 'unknown endpoint'); + } else { + console.log('🚫 无Token,API请求可能未授权:', (originalOptions as any).url || 'unknown endpoint'); + } + + return authenticatedOptions; +}; + +// 包装所有API方法,自动添加认证 +export const authenticatedSdk = { + // 认证相关 + getCaptcha: (options?: Options) => api.getCaptchaApiV1AuthCaptchaGet(options), + login: (options: Options) => api.loginApiV1AuthLoginPost(options), + logout: (options?: Options) => api.logoutApiV1AuthLogoutPost(createAuthenticatedOptions(options)), + getCurrentUser: (options?: Options) => api.getCurrentUserInfoApiV1AuthMeGet(createAuthenticatedOptions(options)), + refreshToken: (options?: Options) => api.refreshTokenApiV1AuthRefreshPost(createAuthenticatedOptions(options)), + + // 用户管理 + getUsers: (options?: Options) => api.getUsersApiV1UsersGet(createAuthenticatedOptions(options)), + getUser: (options: Options) => api.getUserApiV1UsersUserIdGet(createAuthenticatedOptions(options)), + createUser: (options: Options) => api.createUserApiV1UsersPost(createAuthenticatedOptions(options)), + updateUser: (options: Options) => api.updateUserApiV1UsersUserIdPut(createAuthenticatedOptions(options)), + deleteUser: (options: Options) => api.deleteUserApiV1UsersUserIdDelete(createAuthenticatedOptions(options)), + + // 系统用户管理 + getSystemUsers: (options?: Options) => api.listSystemUsersApiV1UsersSystemUsersGet(createAuthenticatedOptions(options)), + getSystemUser: (options: Options) => api.getSystemUserApiV1UsersSystemUsersUserIdGet(createAuthenticatedOptions(options)), + createSystemUser: (options: Options) => api.createSystemUserApiV1UsersSystemUsersPost(createAuthenticatedOptions(options)), + updateSystemUser: (options: Options) => api.updateSystemUserApiV1UsersSystemUsersUserIdPut(createAuthenticatedOptions(options)), + deleteSystemUser: (options: Options) => api.deleteSystemUserApiV1UsersSystemUsersUserIdDelete(createAuthenticatedOptions(options)), + + // 部门管理 + getDepartments: (options?: Options) => api.getDepartmentsApiV1DepartmentsDepartmentsGet(createAuthenticatedOptions(options)), + getDepartment: (options: Options) => api.getDepartmentApiV1DepartmentsDepartmentsDepartmentIdGet(createAuthenticatedOptions(options)), + createDepartment: (options: Options) => api.createDepartmentApiV1DepartmentsDepartmentsPost(createAuthenticatedOptions(options)), + updateDepartment: (options: Options) => api.updateDepartmentApiV1DepartmentsDepartmentsDepartmentIdPut(createAuthenticatedOptions(options)), + deleteDepartment: (options: Options) => api.deleteDepartmentApiV1DepartmentsDepartmentsDepartmentIdDelete(createAuthenticatedOptions(options)), + getDepartmentTree: (options?: Options) => api.getDepartmentTreeApiV1DepartmentsDepartmentsTreeGet(createAuthenticatedOptions(options)), + + // 租户管理 + getTenants: (options?: Options) => api.listTenantsApiV1TenantsGet(createAuthenticatedOptions(options)), + getTenant: (options: Options) => api.getTenantApiV1TenantsTenantIdGet(createAuthenticatedOptions(options)), + createTenant: (options: Options) => api.createTenantApiV1TenantsPost(createAuthenticatedOptions(options)), + updateTenant: (options: Options) => api.updateTenantApiV1TenantsTenantIdPut(createAuthenticatedOptions(options)), + deleteTenant: (options: Options) => api.deleteTenantApiV1TenantsTenantIdDelete(createAuthenticatedOptions(options)), + getCurrentTenant: (options?: Options) => api.getCurrentTenantApiV1TenantsMeGet(createAuthenticatedOptions(options)), + + // 消息管理 + sendMessage: (options: Options) => api.sendMessageApiV1MessagesSendPost(createAuthenticatedOptions(options)), + scheduleMessage: (options: Options) => api.scheduleMessageApiV1MessagesSchedulePost(createAuthenticatedOptions(options)), + getMessageLogs: (options?: Options) => api.listMessageLogsApiV1MessagesLogsMessageLogsGet(createAuthenticatedOptions(options)), + getMessageReceipts: (options: Options) => api.getMessageReceiptsApiV1MessagesLogsMessageLogsMessageIdReceiptsGet(createAuthenticatedOptions(options)), + + // 系统信息 + getSystemInfo: (options?: Options) => api.getSystemInfoApiV1SystemInfoGet(createAuthenticatedOptions(options)), + getSystemStats: (options?: Options) => api.getSystemStatsApiV1SystemStatsGet(createAuthenticatedOptions(options)), + getSystemMetrics: (options?: Options) => api.getSystemMetricsApiV1SystemMetricsGet(createAuthenticatedOptions(options)), + healthCheck: (options?: Options) => api.healthCheckApiV1HealthGet(createAuthenticatedOptions(options)), + + // 日志管理 + getLoginLogs: (options?: Options) => api.getLoginLogsApiV1LogsLogsLoginGet(createAuthenticatedOptions(options)), + getOperationLogs: (options?: Options) => api.getOperationLogsApiV1LogsLogsOperationGet(createAuthenticatedOptions(options)), + getNetworkLogs: (options?: Options) => api.getNetworkLogsApiV1LogsLogsNetworkGet(createAuthenticatedOptions(options)), +}; + +// 导出原始API以供特殊情况使用(如登录) +export { api }; + +// 导出类型 +export type { Options }; \ No newline at end of file