feat(auth): 接入 MEDISIGN 登录接口
- 新增认证 API、类型与本地会话存储 - 登录页改为真实账号密码登录并移除验证码 - 请求拦截器改用 X-Token 并处理 401 跳转 - 添加 Vite /api 开发代理配置
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import axios from 'axios'
|
||||
|
||||
import { request } from '@/utils/request'
|
||||
import { saveAuthSession } from '@/utils/auth-storage'
|
||||
import type {
|
||||
AuthenticatedUser,
|
||||
CurrentUserResponse,
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
} from '@/types/auth'
|
||||
import type { ApiResponse } from '@/types/common'
|
||||
|
||||
export class ApiResponseError extends Error {
|
||||
readonly code: number | string
|
||||
readonly traceId?: string
|
||||
|
||||
constructor(response: ApiResponse<unknown>) {
|
||||
super(response.message || '接口请求失败')
|
||||
this.name = 'ApiResponseError'
|
||||
this.code = response.code
|
||||
this.traceId = response.traceId
|
||||
}
|
||||
}
|
||||
|
||||
function isSuccessCode(code: number | string) {
|
||||
return code === 0 || code === '0'
|
||||
}
|
||||
|
||||
function unwrapApiResponse<T>(response: ApiResponse<T>): T {
|
||||
if (!isSuccessCode(response.code)) {
|
||||
throw new ApiResponseError(response)
|
||||
}
|
||||
|
||||
if (response.data === null) {
|
||||
throw new Error(response.message || '接口未返回业务数据')
|
||||
}
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
function normalizePermissions(value: unknown): string[] {
|
||||
if (Array.isArray(value)) {
|
||||
return value.filter((item): item is string => typeof item === 'string')
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(value) as unknown
|
||||
return Array.isArray(parsed)
|
||||
? parsed.filter((item): item is string => typeof item === 'string')
|
||||
: []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function normalizeAuthenticatedUser(user: AuthenticatedUser): AuthenticatedUser {
|
||||
return {
|
||||
...user,
|
||||
permissions: normalizePermissions(user.permissions),
|
||||
}
|
||||
}
|
||||
|
||||
export async function login(payload: LoginRequest): Promise<LoginResponse> {
|
||||
const response = await request.post<ApiResponse<LoginResponse>>('/v1/auth/login', payload)
|
||||
const session = unwrapApiResponse(response)
|
||||
|
||||
if (!session.token || !session.user) {
|
||||
throw new Error('登录接口响应缺少 token 或用户信息')
|
||||
}
|
||||
|
||||
const normalizedSession: LoginResponse = {
|
||||
...session,
|
||||
expiresAt: session.expiresAt || '',
|
||||
user: normalizeAuthenticatedUser(session.user),
|
||||
}
|
||||
|
||||
saveAuthSession(normalizedSession)
|
||||
return normalizedSession
|
||||
}
|
||||
|
||||
export async function getCurrentUser(): Promise<CurrentUserResponse> {
|
||||
const response = await request.get<ApiResponse<CurrentUserResponse>>('/v1/auth/me')
|
||||
const user = unwrapApiResponse(response)
|
||||
|
||||
return {
|
||||
...user,
|
||||
permissions: normalizePermissions(user.permissions),
|
||||
}
|
||||
}
|
||||
|
||||
export async function logout(): Promise<void> {
|
||||
const response = await request.post<ApiResponse<Record<string, never>>>('/v1/auth/logout')
|
||||
unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export function getAuthErrorMessage(error: unknown) {
|
||||
if (error instanceof ApiResponseError) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (!error.response) {
|
||||
return '无法连接登录服务,请检查网络或接口地址'
|
||||
}
|
||||
|
||||
if (error.response.status === 401) {
|
||||
return '账号或密码错误'
|
||||
}
|
||||
|
||||
return `登录失败(HTTP ${error.response.status})`
|
||||
}
|
||||
|
||||
if (error instanceof Error && error.message) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
return '登录失败,请稍后重试'
|
||||
}
|
||||
Reference in New Issue
Block a user