修复登录注册验证码bug
This commit is contained in:
@@ -1,11 +1,26 @@
|
||||
import { callPublic } from "./base"
|
||||
import { clearSession, readCookie, setSession } from "./token"
|
||||
import { clearCookie, clearSession, readCookie, setSession } from "./token"
|
||||
|
||||
function parseUserInfo(raw: string | null): unknown {
|
||||
if (!raw) return null
|
||||
let decoded = raw
|
||||
try {
|
||||
decoded = decodeURIComponent(raw)
|
||||
} catch {
|
||||
decoded = raw
|
||||
}
|
||||
try {
|
||||
return JSON.parse(decoded)
|
||||
} catch {
|
||||
return decoded
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiLogin(props: {
|
||||
logincode: string
|
||||
password: string
|
||||
}): Promise<{ success: boolean; message?: string }> {
|
||||
const result = await callPublic("/user/ApiLogin", {
|
||||
const result = await callPublic<unknown>("/user/ApiLogin", {
|
||||
Logincode: props.logincode,
|
||||
Password: props.password,
|
||||
})
|
||||
@@ -14,23 +29,15 @@ export async function apiLogin(props: {
|
||||
return { success: false, message: result.message || "用户名或者密码不正确" }
|
||||
}
|
||||
|
||||
const token = readCookie("token")
|
||||
const userInfoRaw = readCookie("userInfo")
|
||||
// 通过响应头下发,优先读响应头,cookie 兜底
|
||||
const token = result.headers?.token ?? readCookie("token")
|
||||
const userInfoRaw = result.headers?.userinfo ?? readCookie("userInfo")
|
||||
|
||||
if (!token) {
|
||||
return { success: false, message: "登录成功但未获取到 token" }
|
||||
}
|
||||
|
||||
let userInfo: unknown = userInfoRaw
|
||||
if (userInfoRaw) {
|
||||
try {
|
||||
userInfo = JSON.parse(userInfoRaw)
|
||||
} catch {
|
||||
userInfo = userInfoRaw
|
||||
}
|
||||
}
|
||||
|
||||
setSession(token, userInfo)
|
||||
setSession(token, parseUserInfo(userInfoRaw))
|
||||
|
||||
return { success: true }
|
||||
}
|
||||
@@ -59,5 +66,7 @@ export async function apiRegister(props: {
|
||||
|
||||
export async function logout() {
|
||||
clearSession()
|
||||
clearCookie("token")
|
||||
clearCookie("userInfo")
|
||||
window.location.href = "/"
|
||||
}
|
||||
|
||||
108
src/api/base.ts
108
src/api/base.ts
@@ -8,14 +8,31 @@ import { clearSession, getUserToken } from "./token"
|
||||
|
||||
type CallMode = "public" | "device" | "user"
|
||||
|
||||
const DEFAULT_TIMEOUT = 15_000
|
||||
|
||||
function captureHeaders(response: Response): Record<string, string> {
|
||||
const headers: Record<string, string> = {}
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key.toLowerCase()] = value
|
||||
})
|
||||
return headers
|
||||
}
|
||||
|
||||
async function request<R = undefined>(
|
||||
endpoint: string,
|
||||
init: {
|
||||
method: "GET" | "POST"
|
||||
body?: string
|
||||
auth?: string
|
||||
timeout?: number
|
||||
},
|
||||
): Promise<ApiResponse<R>> {
|
||||
const controller = new AbortController()
|
||||
const timer = setTimeout(
|
||||
() => controller.abort(),
|
||||
init.timeout ?? DEFAULT_TIMEOUT,
|
||||
)
|
||||
|
||||
try {
|
||||
const headers: HeadersInit = {}
|
||||
if (init.body !== undefined) {
|
||||
@@ -27,8 +44,10 @@ async function request<R = undefined>(
|
||||
method: init.method,
|
||||
headers,
|
||||
body: init.body,
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
const responseHeaders = captureHeaders(response)
|
||||
const contentType = response.headers.get("Content-Type") ?? "text/plain"
|
||||
|
||||
if (contentType.includes("text/plain")) {
|
||||
@@ -40,7 +59,7 @@ async function request<R = undefined>(
|
||||
message: text || "请求失败",
|
||||
}
|
||||
}
|
||||
return { success: true, data: undefined as R }
|
||||
return { success: true, data: undefined as R, headers: responseHeaders }
|
||||
}
|
||||
|
||||
if (contentType.includes("application/json")) {
|
||||
@@ -65,25 +84,36 @@ async function request<R = undefined>(
|
||||
return {
|
||||
success: true,
|
||||
data: (json.Data ?? json.data ?? undefined) as R,
|
||||
headers: responseHeaders,
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
status: response.status,
|
||||
message: json.Message || "请求失败",
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true, data: json }
|
||||
return { success: true, data: json, headers: responseHeaders }
|
||||
}
|
||||
|
||||
throw new Error(`无法解析响应数据: ${contentType}`)
|
||||
} catch (e) {
|
||||
if (controller.signal.aborted) {
|
||||
return {
|
||||
success: false,
|
||||
status: 408,
|
||||
message: "请求超时,请稍后重试",
|
||||
}
|
||||
}
|
||||
console.error("后端请求异常:", e)
|
||||
return {
|
||||
success: false,
|
||||
status: 500,
|
||||
message: (e as Error).message || "网络错误",
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,40 +171,56 @@ async function call<R = undefined>(
|
||||
const body = data === undefined ? undefined : JSON.stringify(data)
|
||||
|
||||
const run = async (): Promise<ApiResponse<R>> => {
|
||||
let auth: string | undefined
|
||||
if (mode === "user") {
|
||||
const token = getUserToken()
|
||||
if (!token) {
|
||||
return {
|
||||
success: false,
|
||||
status: 401,
|
||||
message: "未登录或会话已过期",
|
||||
}
|
||||
}
|
||||
auth = `Bearer ${token}`
|
||||
} else if (mode === "device") {
|
||||
const token = await acquireDeviceToken()
|
||||
if (!token) {
|
||||
return {
|
||||
success: false,
|
||||
status: 401,
|
||||
message: "设备凭证获取失败",
|
||||
}
|
||||
}
|
||||
auth = `Bearer ${token}`
|
||||
}
|
||||
const maxAttempts = mode === "device" ? 2 : 1
|
||||
|
||||
const resp = await request<R>(endpoint, { method, body, auth })
|
||||
|
||||
if (!resp.success && resp.status === 401) {
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||
let auth: string | undefined
|
||||
if (mode === "user") {
|
||||
clearSession()
|
||||
const token = getUserToken()
|
||||
if (!token) {
|
||||
return {
|
||||
success: false,
|
||||
status: 401,
|
||||
message: "未登录或会话已过期",
|
||||
}
|
||||
}
|
||||
auth = `Bearer ${token}`
|
||||
} else if (mode === "device") {
|
||||
deviceToken = null
|
||||
deviceTokenExpire = 0
|
||||
if (attempt > 1) {
|
||||
// 上次请求 401,强制作废旧 token 再重新获取
|
||||
deviceToken = null
|
||||
deviceTokenExpire = 0
|
||||
}
|
||||
const token = await acquireDeviceToken()
|
||||
if (!token) {
|
||||
return {
|
||||
success: false,
|
||||
status: 401,
|
||||
message: "设备凭证获取失败",
|
||||
}
|
||||
}
|
||||
auth = `Bearer ${token}`
|
||||
}
|
||||
|
||||
const resp = await request<R>(endpoint, { method, body, auth })
|
||||
|
||||
if (!resp.success && resp.status === 401) {
|
||||
if (mode === "user") {
|
||||
clearSession()
|
||||
return resp
|
||||
}
|
||||
if (mode === "device" && attempt < maxAttempts) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
status: 401,
|
||||
message: "请求未授权",
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
if (method === "GET" && data === undefined) {
|
||||
|
||||
43
src/api/linedata.ts
Normal file
43
src/api/linedata.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { ApiResponse } from "@/lib/api"
|
||||
import { callPublic } from "./base"
|
||||
|
||||
export type LineItem = {
|
||||
city: string | number
|
||||
daikuan: string | number
|
||||
name: string | number
|
||||
nasname: string | number
|
||||
online: string | number
|
||||
supply: string | number
|
||||
}
|
||||
|
||||
export type LineData = {
|
||||
count: number | string
|
||||
use_count: number | string
|
||||
data: LineItem[]
|
||||
}
|
||||
|
||||
export type LineSearchResult = {
|
||||
data: LineItem[]
|
||||
}
|
||||
|
||||
export async function fetchLineData(
|
||||
product: number,
|
||||
): Promise<ApiResponse<LineData>> {
|
||||
return await callPublic<LineData>(
|
||||
`/script/linedata/display.php?product=${product}`,
|
||||
undefined,
|
||||
"GET",
|
||||
)
|
||||
}
|
||||
|
||||
export async function searchLineData(
|
||||
productid: number,
|
||||
info: string,
|
||||
): Promise<ApiResponse<LineSearchResult>> {
|
||||
const query = `type=0&productid=${productid}&info=${encodeURIComponent(info)}`
|
||||
return await callPublic<LineSearchResult>(
|
||||
`/script/linedata/search.php?${query}`,
|
||||
undefined,
|
||||
"GET",
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
const AUTH_EVENT = "auth-change"
|
||||
export const AUTH_EVENT = "auth-change"
|
||||
|
||||
const TOKEN_KEY = "auth_token"
|
||||
const USER_KEY = "auth_user"
|
||||
@@ -8,6 +8,11 @@ export function readCookie(name: string): string | null {
|
||||
return match ? decodeURIComponent(match[1]) : null
|
||||
}
|
||||
|
||||
export function clearCookie(name: string) {
|
||||
// biome-ignore lint/suspicious/noDocumentCookie: Cookie Store API 兼容性不足,标准做法是覆盖写入
|
||||
document.cookie = `${name}=; max-age=0; path=/`
|
||||
}
|
||||
|
||||
export function getUserToken(): string | null {
|
||||
return localStorage.getItem(TOKEN_KEY)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user