完善角色权限与数据隔离

This commit is contained in:
yml2213
2026-07-26 14:00:50 +08:00
parent b483cbc26d
commit 16d9ab0e5e
38 changed files with 2847 additions and 292 deletions
+76 -21
View File
@@ -1,9 +1,10 @@
import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from 'react'
import { setToken } from '@/services/request'
import { PERMISSIONS_CHANGED_EVENT, setToken } from '@/services/request'
import {
getMe,
login as loginApi,
updateMyStatus,
mePermissions,
type AgentPresence,
type LoginResult,
} from '@/services/api'
@@ -11,18 +12,24 @@ import {
interface AuthState {
user: LoginResult | null
loading: boolean
permissions: Set<string>
permissionsLoaded: boolean
login: (username: string, password: string) => Promise<LoginResult>
logout: () => void
/** 切换本人在线 / 忙碌 / 离线 */
setPresence: (status: AgentPresence) => Promise<void>
refreshPermissions: () => Promise<void>
}
const AuthContext = createContext<AuthState>({
user: null,
loading: false,
permissions: new Set(),
permissionsLoaded: false,
login: async () => { throw new Error('认证上下文未初始化') },
logout: () => {},
setPresence: async () => {},
refreshPermissions: async () => {},
})
function persistUser(u: LoginResult | null) {
@@ -48,29 +55,62 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return null
})
const [loading, setLoading] = useState(false)
const [permissions, setPermissions] = useState<Set<string>>(new Set())
const [permissionsLoaded, setPermissionsLoaded] = useState(false)
// 刷新后同步服务端真实状态(含 status)
const refreshPermissions = useCallback(async () => {
if (!user?.token) return
try {
const res = await mePermissions()
if (res.data?.permissions) {
setPermissions(new Set(res.data.permissions))
}
} catch {
setPermissions(new Set())
} finally {
setPermissionsLoaded(true)
}
}, [user?.token])
useEffect(() => {
const handlePermissionsChanged = () => { void refreshPermissions() }
window.addEventListener(PERMISSIONS_CHANGED_EVENT, handlePermissionsChanged)
return () => window.removeEventListener(PERMISSIONS_CHANGED_EVENT, handlePermissionsChanged)
}, [refreshPermissions])
// 刷新后同步服务端真实状态(含 status + permissions)
useEffect(() => {
if (!user?.token) return
let cancelled = false
getMe()
.then(res => {
if (cancelled || !res.data) return
setUser(prev => {
if (!prev) return prev
const next = {
...prev,
nickname: res.data.nickname || prev.nickname,
role: res.data.role || prev.role,
status: res.data.status || prev.status || 'offline',
}
persistUser(next)
return next
})
setPermissionsLoaded(false)
Promise.all([getMe(), mePermissions()])
.then(([meRes, permRes]) => {
if (cancelled) return
if (meRes.data) {
setUser(prev => {
if (!prev) return prev
const next = {
...prev,
nickname: meRes.data.nickname || prev.nickname,
role: meRes.data.role || prev.role,
status: meRes.data.status || prev.status || 'offline',
}
persistUser(next)
return next
})
}
if (permRes.data?.permissions) {
setPermissions(new Set(permRes.data.permissions))
}
setPermissionsLoaded(true)
})
.catch(() => { /* 忽略:token 失效会在后续请求里处理 */ })
.catch(() => {
if (!cancelled) {
setPermissions(new Set())
setPermissionsLoaded(true)
}
})
return () => { cancelled = true }
// 仅挂载时拉一次
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user?.token])
@@ -82,11 +122,20 @@ export function AuthProvider({ children }: { children: ReactNode }) {
setToken(u.token)
setUser(u)
persistUser(u)
setPermissionsLoaded(false)
try {
const permissionRes = await mePermissions()
setPermissions(new Set(permissionRes.data?.permissions || []))
} catch {
setPermissions(new Set())
} finally {
setPermissionsLoaded(true)
}
return u
} finally {
setLoading(false)
}
}, [])
}, [])
const setPresence = useCallback(async (status: AgentPresence) => {
const res = await updateMyStatus(status)
@@ -100,18 +149,24 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, [])
const logout = useCallback(() => {
// 尽力把状态置为离线,不阻塞退出
void updateMyStatus('offline').catch(() => {})
setToken('')
setUser(null)
setPermissions(new Set())
setPermissionsLoaded(false)
persistUser(null)
}, [])
return (
<AuthContext.Provider value={{ user, loading, login, logout, setPresence }}>
<AuthContext.Provider value={{ user, loading, permissions, permissionsLoaded, login, logout, setPresence, refreshPermissions }}>
{children}
</AuthContext.Provider>
)
}
export const useAuth = () => useContext(AuthContext)
export function usePermission(code: string): boolean {
const { permissions } = useAuth()
return permissions.has(code)
}