feat(admin): art-design-pro integration with local session auth

P1b Task 9: apps/admin based on art-design-pro v3.0.2 (tag, commit
d224dd4; source recorded in apps/admin/SOURCE.md), trimmed to
layout/login/i18n/request/table-form core. Login wired to real
POST /v1/auth/password (single-token session), Bearer header on all
requests, 401 interceptor logout+redirect, request-driven sliding
renewal (refresh at ~80% TTL, single-flight), best-effort
POST /v1/auth/logout on user-menu logout. One protected stub page
(相册管理). Dev port 5176 strictPort + /v1 proxy to 8080. Admin keeps
its own eslint9 flat config; root eslint ignores apps/admin/**.
vitest unit tests for renewal pacing + Playwright e2e (guard redirect,
localized wrong-password error, login lands on stub, token persists
across reload, logout clears session).

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-18 11:47:32 +08:00
parent 90fe64f637
commit a6cc190751
273 changed files with 32565 additions and 48 deletions
+64
View File
@@ -0,0 +1,64 @@
/**
* 认证接口(对齐 @photo/contracts 的 /v1/auth/* 端点)
*
* - 登录:POST /v1/auth/password {account, password} → {sessionToken, expiresAt}
* - 续期:POST /v1/auth/refresh(Bearer)→ {expiresAt}(单令牌滑动续期,令牌本身不变)
* - 注销:POST /v1/auth/logout(Bearer)→ 204 无响应体
*
* 用户信息无后端端点(契约未定义 /users/me),由本地拼装:
* 登录时把账号写入 user store,fetchGetUserInfo 直接读 store,
* 避免路由守卫为拿用户信息打一个不存在的接口。
*/
import request from '@/utils/http'
import { useUserStore } from '@/store/modules/user'
/**
* 账号密码登录
* @param params 登录参数(account + password)
* @returns 会话令牌与过期时间
*/
export function fetchLogin(params: Api.Auth.LoginParams) {
return request.post<Api.Auth.LoginResponse>({
url: '/v1/auth/password',
data: params,
// 凭据错误也是 401,但不能触发「会话过期→登出」分支,标记豁免
skipUnauthorizedLogout: true,
showErrorMessage: false
})
}
/**
* 滑动续期(需 Authorization: Bearer)
* @returns 新的过期时间(令牌不变)
*/
export function fetchRefreshSession() {
return request.post<Api.Auth.RefreshResponse>({
url: '/v1/auth/refresh',
showErrorMessage: false
})
}
/**
* 注销会话(需 Authorization: Bearer),204 无响应体
* 尽力而为:失败也继续本地登出
*/
export async function fetchLogout(): Promise<void> {
await request.del<void>({ url: '/v1/auth/logout', showErrorMessage: false })
}
/**
* 获取用户信息(本地)
* 模板守卫在动态路由初始化时调用;后端无对应端点,从 user store
* 读取登录时保存的账号拼装,角色约定为 R_ADMIN。
*/
export async function fetchGetUserInfo(): Promise<Api.Auth.UserInfo> {
const userStore = useUserStore()
const account = userStore.info?.userName || 'admin'
return {
userId: 0,
userName: account,
email: '',
buttons: [],
roles: ['R_ADMIN']
}
}