React - 账号密码登录
This commit is contained in:
@@ -2,19 +2,51 @@
|
|||||||
|
|
||||||
import { createContext, useContext, useEffect, useState, type ReactNode } from "react"
|
import { createContext, useContext, useEffect, useState, type ReactNode } from "react"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
|
import { validateToken } from "@/lib/api"
|
||||||
|
|
||||||
|
// 安全的localStorage访问方法
|
||||||
|
const safeLocalStorage = {
|
||||||
|
getItem: (key: string): string | null => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
return localStorage.getItem(key)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
setItem: (key: string, value: string): void => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem(key, value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeItem: (key: string): void => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.removeItem(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
account?: string;
|
||||||
|
avatar?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface AuthContextType {
|
interface AuthContextType {
|
||||||
isAuthenticated: boolean
|
isAuthenticated: boolean
|
||||||
token: string | null
|
token: string | null
|
||||||
login: (token: string) => void
|
user: User | null
|
||||||
|
login: (token: string, userData: User) => void
|
||||||
logout: () => void
|
logout: () => void
|
||||||
|
updateToken: (newToken: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType>({
|
const AuthContext = createContext<AuthContextType>({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
token: null,
|
token: null,
|
||||||
|
user: null,
|
||||||
login: () => {},
|
login: () => {},
|
||||||
logout: () => {},
|
logout: () => {},
|
||||||
|
updateToken: () => {}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useAuth = () => useContext(AuthContext)
|
export const useAuth = () => useContext(AuthContext)
|
||||||
@@ -25,43 +57,82 @@ interface AuthProviderProps {
|
|||||||
|
|
||||||
export function AuthProvider({ children }: AuthProviderProps) {
|
export function AuthProvider({ children }: AuthProviderProps) {
|
||||||
const [token, setToken] = useState<string | null>(null)
|
const [token, setToken] = useState<string | null>(null)
|
||||||
|
const [user, setUser] = useState<User | null>(null)
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||||
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 检查token有效性并初始化认证状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 客户端检查token
|
const initAuth = async () => {
|
||||||
if (typeof window !== "undefined") {
|
setIsLoading(true)
|
||||||
const storedToken = localStorage.getItem("token")
|
const storedToken = safeLocalStorage.getItem("token")
|
||||||
|
|
||||||
if (storedToken) {
|
if (storedToken) {
|
||||||
setToken(storedToken)
|
try {
|
||||||
setIsAuthenticated(true)
|
// 验证token是否有效
|
||||||
} else {
|
const isValid = await validateToken()
|
||||||
setIsAuthenticated(false)
|
|
||||||
// 暂时禁用重定向逻辑,允许访问所有页面
|
if (isValid) {
|
||||||
// 将来需要恢复登录验证时,取消下面注释
|
// 从localStorage获取用户信息
|
||||||
/*
|
const userDataStr = safeLocalStorage.getItem("user")
|
||||||
if (pathname !== "/login") {
|
if (userDataStr) {
|
||||||
router.push("/login")
|
const userData = JSON.parse(userDataStr) as User
|
||||||
|
setToken(storedToken)
|
||||||
|
setUser(userData)
|
||||||
|
setIsAuthenticated(true)
|
||||||
|
} else {
|
||||||
|
// token有效但没有用户信息,清除token
|
||||||
|
handleLogout()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// token无效,清除
|
||||||
|
handleLogout()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("验证token时出错:", error)
|
||||||
|
handleLogout()
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initAuth()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const login = (newToken: string) => {
|
const handleLogout = () => {
|
||||||
localStorage.setItem("token", newToken)
|
safeLocalStorage.removeItem("token")
|
||||||
|
safeLocalStorage.removeItem("user")
|
||||||
|
setToken(null)
|
||||||
|
setUser(null)
|
||||||
|
setIsAuthenticated(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const login = (newToken: string, userData: User) => {
|
||||||
|
safeLocalStorage.setItem("token", newToken)
|
||||||
|
safeLocalStorage.setItem("user", JSON.stringify(userData))
|
||||||
setToken(newToken)
|
setToken(newToken)
|
||||||
|
setUser(userData)
|
||||||
setIsAuthenticated(true)
|
setIsAuthenticated(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
localStorage.removeItem("token")
|
handleLogout()
|
||||||
setToken(null)
|
|
||||||
setIsAuthenticated(false)
|
|
||||||
// 登出后不强制跳转到登录页
|
// 登出后不强制跳转到登录页
|
||||||
// router.push("/login")
|
// router.push("/login")
|
||||||
}
|
}
|
||||||
|
|
||||||
return <AuthContext.Provider value={{ isAuthenticated, token, login, logout }}>{children}</AuthContext.Provider>
|
// 用于刷新 token 的方法
|
||||||
|
const updateToken = (newToken: string) => {
|
||||||
|
safeLocalStorage.setItem("token", newToken)
|
||||||
|
setToken(newToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={{ isAuthenticated, token, user, login, logout, updateToken }}>
|
||||||
|
{isLoading ? <div className="flex h-screen w-screen items-center justify-center">加载中...</div> : children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,19 +11,10 @@ import { useRouter } from "next/navigation"
|
|||||||
import { WeChatIcon } from "@/components/icons/wechat-icon"
|
import { WeChatIcon } from "@/components/icons/wechat-icon"
|
||||||
import { AppleIcon } from "@/components/icons/apple-icon"
|
import { AppleIcon } from "@/components/icons/apple-icon"
|
||||||
import { useToast } from "@/components/ui/use-toast"
|
import { useToast } from "@/components/ui/use-toast"
|
||||||
|
import { useAuth } from "@/app/components/AuthProvider"
|
||||||
|
import { loginApi } from "@/lib/api"
|
||||||
|
|
||||||
// 使用环境变量获取API域名
|
// 定义登录表单类型
|
||||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "https://api.example.com"
|
|
||||||
|
|
||||||
// 定义登录响应类型
|
|
||||||
interface LoginResponse {
|
|
||||||
code: number
|
|
||||||
message: string
|
|
||||||
data?: {
|
|
||||||
token: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoginForm {
|
interface LoginForm {
|
||||||
phone: string
|
phone: string
|
||||||
password: string
|
password: string
|
||||||
@@ -44,6 +35,7 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
const { login, isAuthenticated } = useAuth()
|
||||||
|
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const { name, value } = e.target
|
const { name, value } = e.target
|
||||||
@@ -101,38 +93,40 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
try {
|
try {
|
||||||
// 创建FormData对象
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append("phone", form.phone)
|
|
||||||
|
|
||||||
if (activeTab === "password") {
|
if (activeTab === "password") {
|
||||||
formData.append("password", form.password)
|
// 发送账号密码登录请求
|
||||||
|
const response = await loginApi.login(form.phone, form.password)
|
||||||
|
|
||||||
|
if (response.code === 200 && response.data) {
|
||||||
|
// 获取用户信息和token
|
||||||
|
const { token, token_expired, member } = response.data
|
||||||
|
|
||||||
|
// 保存token和用户信息
|
||||||
|
login(token, {
|
||||||
|
id: member.id,
|
||||||
|
username: member.username || member.account || '',
|
||||||
|
account: member.account,
|
||||||
|
avatar: member.avatar
|
||||||
|
})
|
||||||
|
|
||||||
|
// 显示成功提示
|
||||||
|
toast({
|
||||||
|
title: "登录成功",
|
||||||
|
description: "欢迎回来!",
|
||||||
|
})
|
||||||
|
|
||||||
|
// 跳转到首页
|
||||||
|
router.push("/")
|
||||||
|
} else {
|
||||||
|
throw new Error(response.msg || "登录失败")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
formData.append("verificationCode", form.verificationCode)
|
// 验证码登录逻辑保持原样,未来可以实现
|
||||||
}
|
|
||||||
|
|
||||||
// 发送登录请求
|
|
||||||
const response = await fetch(`${API_BASE_URL}/auth/login`, {
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
// 不需要设置Content-Type,浏览器会自动设置为multipart/form-data并添加boundary
|
|
||||||
})
|
|
||||||
|
|
||||||
const result: LoginResponse = await response.json()
|
|
||||||
|
|
||||||
if (result.code === 10000 && result.data?.token) {
|
|
||||||
// 保存token到localStorage
|
|
||||||
localStorage.setItem("token", result.data.token)
|
|
||||||
|
|
||||||
// 成功后跳转
|
|
||||||
router.push("/profile")
|
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "登录成功",
|
variant: "destructive",
|
||||||
description: "欢迎回来!",
|
title: "功能未实现",
|
||||||
|
description: "验证码登录功能尚未实现,请使用密码登录",
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
throw new Error(result.message || "登录失败")
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
@@ -155,46 +149,19 @@ export default function LoginPage() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(true)
|
toast({
|
||||||
try {
|
variant: "destructive",
|
||||||
// 创建FormData对象
|
title: "功能未实现",
|
||||||
const formData = new FormData()
|
description: "验证码发送功能尚未实现",
|
||||||
formData.append("phone", form.phone)
|
})
|
||||||
|
|
||||||
// 发送验证码请求
|
|
||||||
const response = await fetch(`${API_BASE_URL}/auth/send-code`, {
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await response.json()
|
|
||||||
|
|
||||||
if (result.code === 10000) {
|
|
||||||
toast({
|
|
||||||
title: "验证码已发送",
|
|
||||||
description: "请查看手机短信",
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
throw new Error(result.message || "发送失败")
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "发送失败",
|
|
||||||
description: error instanceof Error ? error.message : "请稍后重试",
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 检查是否已登录
|
// 检查是否已登录,如果已登录则跳转到首页
|
||||||
const token = localStorage.getItem("token")
|
if (isAuthenticated) {
|
||||||
if (token) {
|
router.push("/")
|
||||||
router.push("/profile")
|
|
||||||
}
|
}
|
||||||
}, [router])
|
}, [isAuthenticated, router])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-white text-gray-900 flex flex-col px-4 py-8">
|
<div className="min-h-screen bg-white text-gray-900 flex flex-col px-4 py-8">
|
||||||
@@ -261,25 +228,24 @@ export default function LoginPage() {
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="verification" className="m-0">
|
<TabsContent value="verification" className="m-0">
|
||||||
<div className="flex gap-3">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
name="verificationCode"
|
name="verificationCode"
|
||||||
value={form.verificationCode}
|
value={form.verificationCode}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
placeholder="验证码"
|
placeholder="验证码"
|
||||||
className="border-gray-300 text-gray-900 h-12"
|
className="pr-32 border-gray-300 text-gray-900 h-12"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
<Button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
|
||||||
className="w-32 h-12 border-gray-300 text-gray-600 hover:text-gray-900"
|
|
||||||
onClick={handleSendVerificationCode}
|
onClick={handleSendVerificationCode}
|
||||||
|
className="absolute right-3 top-1/2 -translate-y-1/2 px-4 h-8 bg-blue-50 text-blue-500 rounded text-sm font-medium"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
发送验证码
|
获取验证码
|
||||||
</Button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
@@ -288,65 +254,35 @@ export default function LoginPage() {
|
|||||||
id="terms"
|
id="terms"
|
||||||
checked={form.agreeToTerms}
|
checked={form.agreeToTerms}
|
||||||
onCheckedChange={handleCheckboxChange}
|
onCheckedChange={handleCheckboxChange}
|
||||||
className="border-gray-300 data-[state=checked]:bg-blue-500"
|
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="terms" className="text-sm text-gray-600">
|
<label
|
||||||
已阅读并同意
|
htmlFor="terms"
|
||||||
<a href="#" className="text-blue-500 mx-1">
|
className="text-sm text-gray-500 leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
用户协议
|
>
|
||||||
</a>
|
同意《存客宝用户协议》和《隐私政策》
|
||||||
与
|
|
||||||
<a href="#" className="text-blue-500 ml-1">
|
|
||||||
隐私政策
|
|
||||||
</a>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button type="submit" className="w-full h-12 bg-blue-500 hover:bg-blue-600" disabled={isLoading}>
|
||||||
type="submit"
|
|
||||||
className="w-full h-12 bg-blue-500 hover:bg-blue-600 text-white"
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
{isLoading ? "登录中..." : "登录"}
|
{isLoading ? "登录中..." : "登录"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="relative">
|
<div className="flex items-center space-x-2 justify-center">
|
||||||
<div className="absolute inset-0 flex items-center">
|
<hr className="w-full border-gray-200" />
|
||||||
<span className="w-full border-t border-gray-300"></span>
|
<span className="px-2 text-gray-400 text-sm whitespace-nowrap">其他登录方式</span>
|
||||||
</div>
|
<hr className="w-full border-gray-200" />
|
||||||
<div className="relative flex justify-center text-xs uppercase">
|
|
||||||
<span className="bg-white px-2 text-gray-500">或</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="flex justify-center space-x-6">
|
||||||
<Button
|
<button type="button" className="p-2 text-gray-500 hover:text-gray-700">
|
||||||
type="button"
|
<WeChatIcon className="h-8 w-8" />
|
||||||
variant="outline"
|
</button>
|
||||||
className="w-full h-12 border-gray-300 text-gray-700 hover:bg-gray-50"
|
<button type="button" className="p-2 text-gray-500 hover:text-gray-700">
|
||||||
disabled={isLoading}
|
<AppleIcon className="h-8 w-8" />
|
||||||
>
|
</button>
|
||||||
<WeChatIcon className="w-6 h-6 mr-2 text-[#07C160]" />
|
|
||||||
使用微信登录
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
className="w-full h-12 border-gray-300 text-gray-700 hover:bg-gray-50"
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
<AppleIcon className="w-6 h-6 mr-2" />
|
|
||||||
使用 Apple 登录
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="mt-8 text-center">
|
|
||||||
<a href="#" className="text-sm text-gray-500">
|
|
||||||
联系我们
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
129
Cunkebao/lib/api.ts
Normal file
129
Cunkebao/lib/api.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { handleApiResponse, handleApiError } from "./http-interceptors"
|
||||||
|
|
||||||
|
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://yishi.com';
|
||||||
|
|
||||||
|
// 安全地获取token
|
||||||
|
const getToken = (): string | null => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
return localStorage.getItem('token');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 安全地设置token
|
||||||
|
const setToken = (token: string): void => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem('token', token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建请求头
|
||||||
|
const createHeaders = (withAuth: boolean = true): HeadersInit => {
|
||||||
|
const headers: HeadersInit = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (withAuth) {
|
||||||
|
const token = getToken();
|
||||||
|
if (token) {
|
||||||
|
headers['Authorization'] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 基础请求函数
|
||||||
|
export const request = async <T>(
|
||||||
|
url: string,
|
||||||
|
method: string = 'GET',
|
||||||
|
data?: any,
|
||||||
|
withAuth: boolean = true
|
||||||
|
): Promise<T> => {
|
||||||
|
const options: RequestInit = {
|
||||||
|
method,
|
||||||
|
headers: createHeaders(withAuth),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
||||||
|
options.body = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}${url}`, options);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
// 使用响应拦截器处理响应
|
||||||
|
return handleApiResponse<T>(response, result);
|
||||||
|
} catch (error) {
|
||||||
|
// 使用错误拦截器处理错误
|
||||||
|
return handleApiError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出便捷的请求方法
|
||||||
|
export const api = {
|
||||||
|
get: <T>(url: string, withAuth: boolean = true) =>
|
||||||
|
request<T>(url, 'GET', undefined, withAuth),
|
||||||
|
|
||||||
|
post: <T>(url: string, data: any, withAuth: boolean = true) =>
|
||||||
|
request<T>(url, 'POST', data, withAuth),
|
||||||
|
|
||||||
|
put: <T>(url: string, data: any, withAuth: boolean = true) =>
|
||||||
|
request<T>(url, 'PUT', data, withAuth),
|
||||||
|
|
||||||
|
delete: <T>(url: string, withAuth: boolean = true) =>
|
||||||
|
request<T>(url, 'DELETE', undefined, withAuth),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 登录API
|
||||||
|
export const loginApi = {
|
||||||
|
// 账号密码登录
|
||||||
|
login: (account: string, password: string) =>
|
||||||
|
api.post<ApiResponse>('/v1/auth/login', {
|
||||||
|
account,
|
||||||
|
password,
|
||||||
|
typeId: 1 // 默认使用用户类型1
|
||||||
|
}, false),
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
getUserInfo: () =>
|
||||||
|
api.get<ApiResponse>('/v1/auth/info'),
|
||||||
|
|
||||||
|
// 刷新Token
|
||||||
|
refreshToken: () =>
|
||||||
|
api.post<ApiResponse>('/v1/auth/refresh', {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 验证 Token 是否有效
|
||||||
|
export const validateToken = async (): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await loginApi.getUserInfo();
|
||||||
|
return response.code === 200;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 刷新令牌
|
||||||
|
export const refreshAuthToken = async (): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await loginApi.refreshToken();
|
||||||
|
if (response.code === 200 && response.data?.token) {
|
||||||
|
// 更新本地存储的token
|
||||||
|
setToken(response.data.token);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('刷新Token失败:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义响应类型接口
|
||||||
|
export interface ApiResponse {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data?: any;
|
||||||
|
}
|
||||||
39
Cunkebao/lib/http-interceptors.ts
Normal file
39
Cunkebao/lib/http-interceptors.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
// Token过期处理
|
||||||
|
export const handleTokenExpired = () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
// 清除本地存储
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
|
||||||
|
// 跳转到登录页
|
||||||
|
window.location.href = '/login';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
export const handleApiResponse = <T>(response: Response, result: any): T => {
|
||||||
|
// 处理token过期情况
|
||||||
|
if (result && (result.code === 401 || result.msg?.includes('token'))) {
|
||||||
|
handleTokenExpired();
|
||||||
|
throw new Error(result.msg || '登录已过期,请重新登录');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理API错误
|
||||||
|
export const handleApiError = (error: unknown): never => {
|
||||||
|
console.error('API请求错误:', error);
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
// 如果是未授权错误,可能是token过期
|
||||||
|
if (error.message.includes('401') || error.message.includes('token') || error.message.includes('授权')) {
|
||||||
|
handleTokenExpired();
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('未知错误,请稍后重试');
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user