feat(auth): 接入 MEDISIGN 登录接口

- 新增认证 API、类型与本地会话存储
- 登录页改为真实账号密码登录并移除验证码
- 请求拦截器改用 X-Token 并处理 401 跳转
- 添加 Vite /api 开发代理配置
This commit is contained in:
yelan
2026-08-31 10:50:01 +08:00
parent 1e2ff79b0c
commit a5e0a80191
10 changed files with 359 additions and 75 deletions
+122
View File
@@ -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 '登录失败,请稍后重试'
}