refactor: 对接 duoqi-api 管理员登录规范
- ApiResponse 改为标准 { success, data, error } 格式
- 登录响应使用 accessToken/refreshToken 字段
- AdminRole 新增 super_admin 角色
- auth-store 支持 refreshToken 存储
- 所有 API 调用处理 data 可能为 null 的情况
This commit is contained in:
parent
2c2fc952f9
commit
66fc078b3c
@ -117,9 +117,11 @@ export function ImportQuestionsDialog({
|
|||||||
setImporting(true)
|
setImporting(true)
|
||||||
try {
|
try {
|
||||||
const res = await importQuestions(parsedQuestions)
|
const res = await importQuestions(parsedQuestions)
|
||||||
|
if (res.data) {
|
||||||
setResult(res.data)
|
setResult(res.data)
|
||||||
setStep("result")
|
setStep("result")
|
||||||
if (res.data.imported > 0) onSuccess()
|
if (res.data.imported > 0) onSuccess()
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
setParseError("导入失败,请检查网络或联系管理员")
|
setParseError("导入失败,请检查网络或联系管理员")
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -70,7 +70,7 @@ export function EventConfigTab() {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const res = await fetchEvents()
|
const res = await fetchEvents()
|
||||||
setEvents(res.data)
|
setEvents(res.data ?? [])
|
||||||
} catch {
|
} catch {
|
||||||
setEvents([])
|
setEvents([])
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -79,7 +79,7 @@ export function GeneralSettingsTab() {
|
|||||||
try {
|
try {
|
||||||
const res = await fetchSettings("general")
|
const res = await fetchSettings("general")
|
||||||
const settingsMap: Record<string, string> = {}
|
const settingsMap: Record<string, string> = {}
|
||||||
res.data.forEach((s) => {
|
res.data?.forEach((s) => {
|
||||||
settingsMap[s.key] = s.value
|
settingsMap[s.key] = s.value
|
||||||
})
|
})
|
||||||
setSettings(settingsMap)
|
setSettings(settingsMap)
|
||||||
|
|||||||
@ -77,7 +77,7 @@ export function PushTemplateTab() {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const res = await fetchPushTemplates()
|
const res = await fetchPushTemplates()
|
||||||
setTemplates(res.data)
|
setTemplates(res.data ?? [])
|
||||||
} catch {
|
} catch {
|
||||||
setTemplates([])
|
setTemplates([])
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -1,33 +1,81 @@
|
|||||||
import { apiClient } from "@/lib/api-client"
|
import { apiClient } from "@/lib/api-client"
|
||||||
import type { ApiResponse } from "@/types/api"
|
import type { ApiResponse, LoginResponse, RefreshTokenResponse } from "@/types/api"
|
||||||
import type { Admin, AdminLoginForm, AdminSession, CreateAdminForm } from "@/types/admin"
|
import type { Admin, AdminLoginForm, CreateAdminForm } from "@/types/admin"
|
||||||
|
|
||||||
// 认证
|
// ==================== 认证相关 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员登录(用户名密码)
|
||||||
|
* POST /admin/auth/login
|
||||||
|
*/
|
||||||
export async function loginAdmin(
|
export async function loginAdmin(
|
||||||
credentials: AdminLoginForm
|
credentials: AdminLoginForm
|
||||||
): Promise<ApiResponse<AdminSession>> {
|
): Promise<ApiResponse<LoginResponse>> {
|
||||||
return apiClient.post("auth/login", { json: credentials }).json<ApiResponse<AdminSession>>()
|
return apiClient.post("auth/login", { json: credentials }).json<ApiResponse<LoginResponse>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token 登录(向后兼容)
|
||||||
|
* POST /admin/auth
|
||||||
|
*/
|
||||||
|
export async function loginWithToken(
|
||||||
|
token: string
|
||||||
|
): Promise<ApiResponse<LoginResponse>> {
|
||||||
|
return apiClient.post("auth", { json: { token } }).json<ApiResponse<LoginResponse>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新访问令牌
|
||||||
|
* POST /admin/auth/refresh
|
||||||
|
*/
|
||||||
|
export async function refreshAccessToken(
|
||||||
|
refreshToken: string
|
||||||
|
): Promise<ApiResponse<RefreshTokenResponse>> {
|
||||||
|
return apiClient
|
||||||
|
.post("auth/refresh", { json: { refreshToken } })
|
||||||
|
.json<ApiResponse<RefreshTokenResponse>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前管理员信息
|
||||||
|
* GET /admin/auth/me
|
||||||
|
*/
|
||||||
export async function fetchMe(): Promise<ApiResponse<Admin>> {
|
export async function fetchMe(): Promise<ApiResponse<Admin>> {
|
||||||
return apiClient.get("auth/me").json<ApiResponse<Admin>>()
|
return apiClient.get("auth/me").json<ApiResponse<Admin>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 管理员管理
|
// ==================== 管理员管理 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取管理员列表
|
||||||
|
* GET /admin/admins
|
||||||
|
*/
|
||||||
export async function fetchAdmins(): Promise<ApiResponse<Admin[]>> {
|
export async function fetchAdmins(): Promise<ApiResponse<Admin[]>> {
|
||||||
return apiClient.get("admins").json<ApiResponse<Admin[]>>()
|
return apiClient.get("admins").json<ApiResponse<Admin[]>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个管理员详情
|
||||||
|
* GET /admin/admins/:id
|
||||||
|
*/
|
||||||
export async function fetchAdmin(id: string): Promise<ApiResponse<Admin>> {
|
export async function fetchAdmin(id: string): Promise<ApiResponse<Admin>> {
|
||||||
return apiClient.get(`admins/${id}`).json<ApiResponse<Admin>>()
|
return apiClient.get(`admins/${id}`).json<ApiResponse<Admin>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建管理员
|
||||||
|
* POST /admin/admins
|
||||||
|
*/
|
||||||
export async function createAdmin(
|
export async function createAdmin(
|
||||||
data: CreateAdminForm
|
data: CreateAdminForm
|
||||||
): Promise<ApiResponse<Admin>> {
|
): Promise<ApiResponse<Admin>> {
|
||||||
return apiClient.post("admins", { json: data }).json<ApiResponse<Admin>>()
|
return apiClient.post("admins", { json: data }).json<ApiResponse<Admin>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新管理员信息
|
||||||
|
* PUT /admin/admins/:id
|
||||||
|
*/
|
||||||
export async function updateAdmin(
|
export async function updateAdmin(
|
||||||
id: string,
|
id: string,
|
||||||
data: Partial<CreateAdminForm>
|
data: Partial<CreateAdminForm>
|
||||||
@ -35,10 +83,18 @@ export async function updateAdmin(
|
|||||||
return apiClient.put(`admins/${id}`, { json: data }).json<ApiResponse<Admin>>()
|
return apiClient.put(`admins/${id}`, { json: data }).json<ApiResponse<Admin>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除管理员
|
||||||
|
* DELETE /admin/admins/:id
|
||||||
|
*/
|
||||||
export async function deleteAdmin(id: string): Promise<ApiResponse<{ id: string }>> {
|
export async function deleteAdmin(id: string): Promise<ApiResponse<{ id: string }>> {
|
||||||
return apiClient.delete(`admins/${id}`).json<ApiResponse<{ id: string }>>()
|
return apiClient.delete(`admins/${id}`).json<ApiResponse<{ id: string }>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置管理员密码
|
||||||
|
* POST /admin/admins/:id/reset-password
|
||||||
|
*/
|
||||||
export async function resetAdminPassword(
|
export async function resetAdminPassword(
|
||||||
id: string,
|
id: string,
|
||||||
newPassword: string
|
newPassword: string
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { AUTH_STORAGE_KEY } from "./constants"
|
import { AUTH_STORAGE_KEY } from "./constants"
|
||||||
|
|
||||||
const CURRENT_ADMIN_ID_KEY = "duoqi_admin_current_id"
|
const CURRENT_ADMIN_ID_KEY = "duoqi_admin_current_id"
|
||||||
|
const REFRESH_TOKEN_KEY = "duoqi_admin_refresh_token"
|
||||||
|
|
||||||
|
// Access Token 操作
|
||||||
export function getStoredToken(): string | null {
|
export function getStoredToken(): string | null {
|
||||||
return localStorage.getItem(AUTH_STORAGE_KEY)
|
return localStorage.getItem(AUTH_STORAGE_KEY)
|
||||||
}
|
}
|
||||||
@ -10,11 +12,23 @@ export function setStoredToken(token: string): void {
|
|||||||
localStorage.setItem(AUTH_STORAGE_KEY, token)
|
localStorage.setItem(AUTH_STORAGE_KEY, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh Token 操作
|
||||||
|
export function getRefreshToken(): string | null {
|
||||||
|
return localStorage.getItem(REFRESH_TOKEN_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setRefreshToken(token: string): void {
|
||||||
|
localStorage.setItem(REFRESH_TOKEN_KEY, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除所有认证信息
|
||||||
export function removeStoredToken(): void {
|
export function removeStoredToken(): void {
|
||||||
localStorage.removeItem(AUTH_STORAGE_KEY)
|
localStorage.removeItem(AUTH_STORAGE_KEY)
|
||||||
|
localStorage.removeItem(REFRESH_TOKEN_KEY)
|
||||||
localStorage.removeItem(CURRENT_ADMIN_ID_KEY)
|
localStorage.removeItem(CURRENT_ADMIN_ID_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 当前管理员 ID
|
||||||
export function setCurrentAdminId(id: string): void {
|
export function setCurrentAdminId(id: string): void {
|
||||||
localStorage.setItem(CURRENT_ADMIN_ID_KEY, id)
|
localStorage.setItem(CURRENT_ADMIN_ID_KEY, id)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,6 +105,7 @@ export const SETTING_CATEGORY_LABELS: Record<SettingCategory, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ADMIN_ROLE_LABELS: Record<AdminRole, string> = {
|
export const ADMIN_ROLE_LABELS: Record<AdminRole, string> = {
|
||||||
|
super_admin: "超级管理员",
|
||||||
admin: "管理员",
|
admin: "管理员",
|
||||||
moderator: "审核员",
|
moderator: "审核员",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,11 +42,13 @@ import { ADMIN_ROLE_LABELS } from "@/lib/constants"
|
|||||||
import type { Admin, AdminRole, CreateAdminForm } from "@/types/admin"
|
import type { Admin, AdminRole, CreateAdminForm } from "@/types/admin"
|
||||||
|
|
||||||
const roleIcons = {
|
const roleIcons = {
|
||||||
|
super_admin: Shield,
|
||||||
admin: Shield,
|
admin: Shield,
|
||||||
moderator: ShieldAlert,
|
moderator: ShieldAlert,
|
||||||
}
|
}
|
||||||
|
|
||||||
const roleBadgeVariants: Record<AdminRole, "default" | "secondary"> = {
|
const roleBadgeVariants: Record<AdminRole, "default" | "secondary"> = {
|
||||||
|
super_admin: "default",
|
||||||
admin: "default",
|
admin: "default",
|
||||||
moderator: "secondary",
|
moderator: "secondary",
|
||||||
}
|
}
|
||||||
@ -77,7 +79,7 @@ export default function AdminsPage() {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const res = await fetchAdmins()
|
const res = await fetchAdmins()
|
||||||
setAdmins(res.data)
|
setAdmins(res.data ?? [])
|
||||||
} catch {
|
} catch {
|
||||||
setAdmins([])
|
setAdmins([])
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -66,7 +66,7 @@ export default function KnowledgeCardsPage() {
|
|||||||
const [detailCard, setDetailCard] = useState<KnowledgeCardItem | null>(null)
|
const [detailCard, setDetailCard] = useState<KnowledgeCardItem | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchCategories({}).then((res) => setCategories(res.data))
|
fetchCategories({}).then((res) => setCategories(res.data ?? []))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const loadCards = useCallback(async () => {
|
const loadCards = useCallback(async () => {
|
||||||
@ -76,7 +76,7 @@ export default function KnowledgeCardsPage() {
|
|||||||
search: search || undefined,
|
search: search || undefined,
|
||||||
status: statusFilter,
|
status: statusFilter,
|
||||||
})
|
})
|
||||||
setCards(res.data)
|
setCards(res.data ?? [])
|
||||||
} catch {
|
} catch {
|
||||||
setCards([])
|
setCards([])
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -5,15 +5,13 @@ import { z } from "zod/v4"
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { Eye, EyeOff } from "lucide-react"
|
import { Eye, EyeOff } from "lucide-react"
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||||
import { apiClient } from "@/lib/api-client"
|
import { loginAdmin, loginWithToken } from "@/lib/api/admin-api"
|
||||||
import { loginAdmin } from "@/lib/api/admin-api"
|
|
||||||
import { useAuth } from "@/hooks/use-auth"
|
import { useAuth } from "@/hooks/use-auth"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import type { LoginResponse } from "@/types/api"
|
import type { LoginResponse, ApiResponse } from "@/types/api"
|
||||||
import type { AdminSession } from "@/types/admin"
|
|
||||||
|
|
||||||
// Token 登录表单
|
// Token 登录表单
|
||||||
const tokenLoginSchema = z.object({
|
const tokenLoginSchema = z.object({
|
||||||
@ -25,7 +23,7 @@ type TokenLoginForm = z.infer<typeof tokenLoginSchema>
|
|||||||
// 用户名密码登录表单
|
// 用户名密码登录表单
|
||||||
const passwordLoginSchema = z.object({
|
const passwordLoginSchema = z.object({
|
||||||
username: z.string().min(1, "请输入用户名"),
|
username: z.string().min(1, "请输入用户名"),
|
||||||
password: z.string().min(1, "请输入密码"),
|
password: z.string().min(8, "密码至少8个字符"),
|
||||||
})
|
})
|
||||||
|
|
||||||
type PasswordLoginForm = z.infer<typeof passwordLoginSchema>
|
type PasswordLoginForm = z.infer<typeof passwordLoginSchema>
|
||||||
@ -53,15 +51,17 @@ export default function LoginPage() {
|
|||||||
async function handleTokenLogin(data: TokenLoginForm) {
|
async function handleTokenLogin(data: TokenLoginForm) {
|
||||||
setError("")
|
setError("")
|
||||||
try {
|
try {
|
||||||
const response = await apiClient
|
const response: ApiResponse<LoginResponse> = await loginWithToken(data.token)
|
||||||
.post("auth/login", { json: { token: data.token } })
|
|
||||||
.json<LoginResponse>()
|
|
||||||
|
|
||||||
login(response.jwt, response.admin)
|
if (response.success && response.data) {
|
||||||
|
login(response.data.accessToken, response.data.refreshToken, response.data.admin)
|
||||||
navigate("/")
|
navigate("/")
|
||||||
|
} else if (response.error) {
|
||||||
|
setError(response.error.message)
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// 后端不可用时,回退到离线模式:直接用 token 登录
|
// 后端不可用时,回退到离线模式:直接用 token 登录
|
||||||
login(`offline_${data.token}`, {
|
login(`offline_${data.token}`, `offline_refresh_${data.token}`, {
|
||||||
id: "offline-admin",
|
id: "offline-admin",
|
||||||
username: "admin",
|
username: "admin",
|
||||||
role: "admin",
|
role: "admin",
|
||||||
@ -74,20 +74,17 @@ export default function LoginPage() {
|
|||||||
async function handlePasswordLogin(data: PasswordLoginForm) {
|
async function handlePasswordLogin(data: PasswordLoginForm) {
|
||||||
setError("")
|
setError("")
|
||||||
try {
|
try {
|
||||||
const response = await loginAdmin(data)
|
const response: ApiResponse<LoginResponse> = await loginAdmin(data)
|
||||||
const session: AdminSession = response.data
|
|
||||||
|
|
||||||
const legacyAdmin = {
|
if (response.success && response.data) {
|
||||||
id: session.admin.id,
|
login(response.data.accessToken, response.data.refreshToken, response.data.admin)
|
||||||
username: session.admin.username,
|
|
||||||
role: session.admin.role,
|
|
||||||
}
|
|
||||||
|
|
||||||
login(session.token, legacyAdmin)
|
|
||||||
navigate("/")
|
navigate("/")
|
||||||
|
} else if (response.error) {
|
||||||
|
setError(response.error.message)
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// 后端不可用时,回退到离线模式
|
// 后端不可用时,回退到离线模式
|
||||||
login(`offline_${data.username}`, {
|
login(`offline_${data.username}`, `offline_refresh_${data.username}`, {
|
||||||
id: "offline-admin",
|
id: "offline-admin",
|
||||||
username: data.username,
|
username: data.username,
|
||||||
role: "admin",
|
role: "admin",
|
||||||
@ -158,7 +155,7 @@ export default function LoginPage() {
|
|||||||
<Input
|
<Input
|
||||||
id="password"
|
id="password"
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码(至少8个字符)"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
{...passwordForm.register("password")}
|
{...passwordForm.register("password")}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -35,11 +35,11 @@ export default function UserDetailPage() {
|
|||||||
Promise.all([
|
Promise.all([
|
||||||
fetchUserDetail(id).then((res) => res.data),
|
fetchUserDetail(id).then((res) => res.data),
|
||||||
fetchUserChapterProgress(id)
|
fetchUserChapterProgress(id)
|
||||||
.then((res) => res.data)
|
.then((res) => res.data ?? [])
|
||||||
.catch(() => []),
|
.catch(() => []),
|
||||||
])
|
])
|
||||||
.then(([userDetail, chapterData]) => {
|
.then(([userDetail, chapterData]) => {
|
||||||
setUser(userDetail)
|
if (userDetail) setUser(userDetail)
|
||||||
setChapters(chapterData)
|
setChapters(chapterData)
|
||||||
})
|
})
|
||||||
.catch(() => navigate("/users"))
|
.catch(() => navigate("/users"))
|
||||||
@ -57,7 +57,10 @@ export default function UserDetailPage() {
|
|||||||
const handleTierChange = async (tier: UserTier) => {
|
const handleTierChange = async (tier: UserTier) => {
|
||||||
if (!id) return
|
if (!id) return
|
||||||
const res = await updateUserTier(id, tier)
|
const res = await updateUserTier(id, tier)
|
||||||
setUser((prev) => (prev ? { ...prev, tier: res.data.tier } : prev))
|
const newTier = res.data?.tier
|
||||||
|
if (newTier) {
|
||||||
|
setUser((prev) => (prev ? { ...prev, tier: newTier } : prev))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,28 +1,37 @@
|
|||||||
import { create } from "zustand"
|
import { create } from "zustand"
|
||||||
import { getStoredToken, setStoredToken, removeStoredToken, setCurrentAdminId } from "@/lib/auth"
|
import {
|
||||||
|
getStoredToken,
|
||||||
|
setStoredToken,
|
||||||
|
setRefreshToken,
|
||||||
|
removeStoredToken,
|
||||||
|
setCurrentAdminId,
|
||||||
|
} from "@/lib/auth"
|
||||||
import type { AdminUser } from "@/types/api"
|
import type { AdminUser } from "@/types/api"
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
token: string | null
|
token: string | null
|
||||||
|
refreshToken: string | null
|
||||||
admin: AdminUser | null
|
admin: AdminUser | null
|
||||||
isAuthenticated: boolean
|
isAuthenticated: boolean
|
||||||
login: (token: string, admin: AdminUser) => void
|
login: (accessToken: string, refreshToken: string, admin: AdminUser) => void
|
||||||
logout: () => void
|
logout: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuthStore = create<AuthState>((set) => ({
|
export const useAuthStore = create<AuthState>((set) => ({
|
||||||
token: getStoredToken(),
|
token: getStoredToken(),
|
||||||
|
refreshToken: null,
|
||||||
admin: null,
|
admin: null,
|
||||||
isAuthenticated: !!getStoredToken(),
|
isAuthenticated: !!getStoredToken(),
|
||||||
|
|
||||||
login: (token, admin) => {
|
login: (accessToken, refreshToken, admin) => {
|
||||||
setStoredToken(token)
|
setStoredToken(accessToken)
|
||||||
|
setRefreshToken(refreshToken)
|
||||||
setCurrentAdminId(admin.id)
|
setCurrentAdminId(admin.id)
|
||||||
set({ token, admin, isAuthenticated: true })
|
set({ token: accessToken, refreshToken, admin, isAuthenticated: true })
|
||||||
},
|
},
|
||||||
|
|
||||||
logout: () => {
|
logout: () => {
|
||||||
removeStoredToken()
|
removeStoredToken()
|
||||||
set({ token: null, admin: null, isAuthenticated: false })
|
set({ token: null, refreshToken: null, admin: null, isAuthenticated: false })
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -1,3 +1,9 @@
|
|||||||
|
import type { AdminUser } from "./api"
|
||||||
|
|
||||||
|
// 管理员角色类型(匹配 duoqi-api 规范)
|
||||||
|
export type AdminRole = "super_admin" | "admin" | "moderator"
|
||||||
|
|
||||||
|
// 完整的管理员信息
|
||||||
export interface Admin {
|
export interface Admin {
|
||||||
id: string
|
id: string
|
||||||
username: string
|
username: string
|
||||||
@ -6,18 +12,20 @@ export interface Admin {
|
|||||||
lastLoginAt?: string
|
lastLoginAt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminRole = "admin" | "moderator"
|
// 登录表单(用户名密码)
|
||||||
|
|
||||||
export interface AdminLoginForm {
|
export interface AdminLoginForm {
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 管理员会话(包含 access token 和 refresh token)
|
||||||
export interface AdminSession {
|
export interface AdminSession {
|
||||||
admin: Admin
|
admin: AdminUser
|
||||||
token: string
|
accessToken: string
|
||||||
|
refreshToken: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建管理员表单
|
||||||
export interface CreateAdminForm {
|
export interface CreateAdminForm {
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
|
|||||||
@ -1,30 +1,58 @@
|
|||||||
export interface ApiResponse<T> {
|
// API 响应错误结构
|
||||||
data: T
|
export interface ApiError {
|
||||||
message?: string
|
code: string
|
||||||
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 统一 API 响应格式(匹配 duoqi-api 规范)
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
success: boolean
|
||||||
|
data: T | null
|
||||||
|
error: ApiError | null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页元数据
|
||||||
export interface PaginationMeta {
|
export interface PaginationMeta {
|
||||||
total: number
|
total: number
|
||||||
page: number
|
page: number
|
||||||
limit: number
|
limit: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分页响应(额外包含 pagination)
|
||||||
export interface PaginatedResponse<T> {
|
export interface PaginatedResponse<T> {
|
||||||
|
success: boolean
|
||||||
data: T[]
|
data: T[]
|
||||||
pagination: PaginationMeta
|
pagination: PaginationMeta
|
||||||
|
error: null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 管理员用户信息
|
||||||
export interface AdminUser {
|
export interface AdminUser {
|
||||||
id: string
|
id: string
|
||||||
username: string
|
username: string
|
||||||
role: "admin" | "moderator"
|
role: "super_admin" | "admin" | "moderator"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Token 登录请求
|
||||||
export interface LoginRequest {
|
export interface LoginRequest {
|
||||||
token: string
|
token: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 密码登录请求
|
||||||
|
export interface PasswordLoginRequest {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录响应(匹配 duoqi-api 规范)
|
||||||
export interface LoginResponse {
|
export interface LoginResponse {
|
||||||
jwt: string
|
accessToken: string
|
||||||
|
refreshToken: string
|
||||||
admin: AdminUser
|
admin: AdminUser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Token 刷新响应
|
||||||
|
export interface RefreshTokenResponse {
|
||||||
|
accessToken: string
|
||||||
|
refreshToken: string
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user