feat: rbac接入

This commit is contained in:
Blizzard
2026-04-28 23:47:48 +08:00
parent ccb36fa59c
commit 3ed0b76fc2
8 changed files with 76 additions and 133 deletions
+23 -9
View File
@@ -5,7 +5,8 @@ import AdminLayout from '@/layouts/AdminLayout'
import LoginPage from '@/pages/LoginPage' import LoginPage from '@/pages/LoginPage'
import ErrorBoundary from '@/components/ErrorBoundary' import ErrorBoundary from '@/components/ErrorBoundary'
import { Suspense, useMemo, lazy, useEffect } from 'react' import { Suspense, useMemo, lazy, useEffect } from 'react'
import { Loader2 } from 'lucide-react' import { Loader2, Shield } from 'lucide-react'
import { Button } from '@/components/ui/button'
import type { SystemMenu } from '@/api/system' import type { SystemMenu } from '@/api/system'
const pages = import.meta.glob('./pages/**/*.tsx') const pages = import.meta.glob('./pages/**/*.tsx')
@@ -25,14 +26,27 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
function PublicRoute({ children }: { children: React.ReactNode }) { function PublicRoute({ children }: { children: React.ReactNode }) {
const isAuthenticated = useAuthStore(s => s.isAuthenticated) const isAuthenticated = useAuthStore(s => s.isAuthenticated)
if (isAuthenticated) return <Navigate to="/dashboard" replace /> if (isAuthenticated) return <Navigate to="/" replace />
return <>{children}</> return <>{children}</>
} }
function NoPermission() {
const logout = useAuthStore(s => s.logout)
return (
<div className="flex flex-col items-center justify-center min-h-[60vh] text-center w-full">
<Shield className="h-16 w-16 text-muted-foreground/30 mb-4" />
<h2 className="text-2xl font-bold mb-2 text-foreground">访</h2>
<p className="text-muted-foreground mb-6 max-w-md"></p>
<Button onClick={logout} variant="default" className="w-32">退</Button>
</div>
)
}
function AppRoutes() { function AppRoutes() {
const menus = useAuthStore(s => s.menus) const menus = useAuthStore(s => s.menus)
const isAuthenticated = useAuthStore(s => s.isAuthenticated) const isAuthenticated = useAuthStore(s => s.isAuthenticated)
const refreshMenus = useAuthStore(s => s.refreshMenus) const refreshMenus = useAuthStore(s => s.refreshMenus)
const hasFetchedMenus = useAuthStore(s => s.hasFetchedMenus)
useEffect(() => { useEffect(() => {
if (isAuthenticated && menus.length === 0) refreshMenus() if (isAuthenticated && menus.length === 0) refreshMenus()
@@ -59,17 +73,17 @@ function AppRoutes() {
<Routes> <Routes>
<Route path="/login" element={<PublicRoute><LoginPage /></PublicRoute>} /> <Route path="/login" element={<PublicRoute><LoginPage /></PublicRoute>} />
<Route path="/" element={<ProtectedRoute><AdminLayout /></ProtectedRoute>}> <Route path="/" element={<ProtectedRoute><AdminLayout /></ProtectedRoute>}>
<Route index element={<Navigate to="/dashboard" replace />} /> <Route index element={
hasFetchedMenus ? (
dynamicRoutes.length > 0 ? <Navigate to={dynamicRoutes[0].path} replace /> : <Navigate to="/403" replace />
) : Loading
} />
{dynamicRoutes.map(({ path, Component }) => ( {dynamicRoutes.map(({ path, Component }) => (
<Route key={path} path={path.startsWith('/') ? path.substring(1) : path} <Route key={path} path={path.startsWith('/') ? path.substring(1) : path}
element={<ErrorBoundary><Suspense fallback={Loading}><Component /></Suspense></ErrorBoundary>} /> element={<ErrorBoundary><Suspense fallback={Loading}><Component /></Suspense></ErrorBoundary>} />
))} ))}
{!dynamicRoutes.some(r => r.path === '/dashboard') && dynamicComponentMap['/dashboard'] && ( {hasFetchedMenus && dynamicRoutes.length === 0 && (
<Route path="dashboard" element={ <Route path="*" element={<NoPermission />} />
<ErrorBoundary><Suspense fallback={Loading}>
{(() => { const D = dynamicComponentMap['/dashboard']; return <D /> })()}
</Suspense></ErrorBoundary>
} />
)} )}
</Route> </Route>
<Route path="*" element={<Navigate to="/" replace />} /> <Route path="*" element={<Navigate to="/" replace />} />
+6 -12
View File
@@ -1,13 +1,13 @@
import { get, post } from '@/lib/request' import { get, post } from '@/lib/request'
import { USE_MOCK, delay, mockResponse } from '@/mock'
import { mockUsers } from '@/mock/system/users'
// ==================== Types ==================== // ==================== Types ====================
export interface SystemUser { export interface SystemUser {
id: string; account: string; name: string; nickName?: string; phone?: string id: string; account: string; name: string; nickName?: string; phone?: string
avatar?: SystemOss; avatarId?: string; clientId?: string; tenantId?: string avatar?: SystemOss; avatarId?: string; clientId?: string; tenantId?: string
createdAt: string; updatedAt: string; roles?: SystemRole[] createdAt: string; updatedAt: string; roles?: string[] // Adjusted based on backend info
menus?: any[]
gender?: number
} }
export interface SystemRole { export interface SystemRole {
@@ -44,26 +44,20 @@ export interface OperationLog {
createdAt: string createdAt: string
} }
export interface CaptchaRes { captcha: string; captchaId: string } export interface CaptchaRes { captchaImg: string; captchaId: string }
export interface LoginParams { account: string; password: string; captcha: string; captchaId: string } export interface LoginParams { account: string; password: string; captcha: string; captchaId: string }
export interface LoginResponse { token: string; expiresAt: number; user: SystemUser } export interface LoginResponse { token: string; userInfo: SystemUser }
// ==================== Auth ==================== // ==================== Auth ====================
export async function getCaptcha() { export async function getCaptcha() {
if (USE_MOCK) { await delay(200); return mockResponse({ captcha: 'data:image/svg+xml;base64,PHN2Zz48L3N2Zz4=', captchaId: 'mock-captcha-id' }) }
return get<{ code: number; data: CaptchaRes; msg: string }>('/auth/captcha') return get<{ code: number; data: CaptchaRes; msg: string }>('/auth/captcha')
} }
export async function login(data: LoginParams) { export async function login(data: LoginParams) {
if (USE_MOCK) { await delay(500); return mockResponse({ token: 'mock-token-xxx', expiresAt: Date.now() + 7200000, user: mockUsers[0] }, '登录成功') }
return post<{ code: number; data: LoginResponse; msg: string }>('/auth/login', data) return post<{ code: number; data: LoginResponse; msg: string }>('/auth/login', data)
} }
export async function logout() { export async function logout() {
if (USE_MOCK) { await delay(100); return mockResponse(null, '已登出') } return get<{ code: number; data: null; msg: string }>('/auth/logout') // If backend doesn't have it, we just clear local token
return get<{ code: number; data: null; msg: string }>('/auth/logout')
} }
// ==================== 以下在各自文件中实现 ====================
// 这里只导出类型,具体 CRUD 在 api/system/*.ts 子文件中
+31 -99
View File
@@ -1,188 +1,120 @@
import { get, post, type PageResult, type PageParams } from '@/lib/request' import { get, post, type PageResult, type PageParams } from '@/lib/request'
import { USE_MOCK, delay, paginate, mockId, mockResponse } from '@/mock'
import { mockUsers } from '@/mock/system/users'
import { mockRoles } from '@/mock/system/roles'
import { mockMenuTree } from '@/mock/system/menus'
import { mockClients } from '@/mock/system/clients'
import { mockFiles } from '@/mock/system/files'
import { mockLogs } from '@/mock/system/logs'
import type { SystemUser, SystemRole, SystemMenu, SystemClient, SystemOss, OperationLog } from './system' import type { SystemUser, SystemRole, SystemMenu, SystemClient, SystemOss, OperationLog } from './system'
// ==================== User ==================== // ==================== User ====================
export async function getUserList(data: PageParams & { account?: string; phone?: string }) { export async function getUserList(data: PageParams & { account?: string; phone?: string; name?: string }) {
if (USE_MOCK) { // map keyword to name or account if needed, or backend can handle it
await delay() const reqData = { ...data, name: data.keyword, account: data.keyword }
let filtered = [...mockUsers] return post<{ data: PageResult<SystemUser> }>('/sys/user/list', reqData)
if (data.keyword) filtered = filtered.filter(u => u.name.includes(data.keyword!) || u.account.includes(data.keyword!))
return mockResponse(paginate(filtered, data.current, data.pageSize))
}
return post<{ data: PageResult<SystemUser> }>('/user/getUserList', data)
} }
export async function saveUser(data: Partial<SystemUser>) { export async function saveUser(data: Partial<SystemUser>) {
if (USE_MOCK) { await delay(); mockUsers.push({ ...data, id: mockId(), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() } as SystemUser); return mockResponse(null, '创建成功') } return post<{ msg: string }>('/sys/user/create', data)
return post<{ msg: string }>('/user/save', data)
} }
export async function updateUser(data: Partial<SystemUser>) { export async function updateUser(data: Partial<SystemUser>) {
if (USE_MOCK) { await delay(); const i = mockUsers.findIndex(u => u.id === data.id); if (i >= 0) Object.assign(mockUsers[i], data); return mockResponse(null, '更新成功') } return post<{ msg: string }>('/sys/user/update', data)
return post<{ msg: string }>('/user/update', data)
} }
export async function deleteUser(ids: string[]) { export async function deleteUser(ids: string[]) {
if (USE_MOCK) { await delay(); ids.forEach(id => { const i = mockUsers.findIndex(u => u.id === id); if (i >= 0) mockUsers.splice(i, 1) }); return mockResponse(null, '删除成功') } return post<{ msg: string }>('/sys/user/delete', { ids })
return post<{ msg: string }>('/user/delete', { ids })
} }
export async function changePassword(data: { id: string; newPwd: string }) { export async function changePassword(data: { id: string; newPwd: string }) {
if (USE_MOCK) { await delay(); return mockResponse(null, '密码修改成功') } // admin reset password
return post<{ msg: string }>('/user/changePassword', data) return post<{ msg: string }>('/sys/user/resetPassword', { id: data.id, password: data.newPwd })
} }
export async function grantRole(data: { userId: string; roleIds: string[] }) { export async function grantRole(data: { userId: string; roleIds: string[] }) {
if (USE_MOCK) { // backend UserUpdateReq has RoleIds
await delay() return post<{ msg: string }>('/sys/user/update', { id: data.userId, roleIds: data.roleIds })
const user = mockUsers.find(u => u.id === data.userId)
if (user) user.roles = mockRoles.filter(r => data.roleIds.includes(r.id)).map(r => ({ ...r }))
return mockResponse(null, '角色分配成功')
}
return post<{ msg: string }>('/user/grantRole', data)
} }
// ==================== Role ==================== // ==================== Role ====================
export async function getRoleList(data: PageParams & { name?: string }) { export async function getRoleList(data: PageParams & { name?: string }) {
if (USE_MOCK) { return post<{ data: PageResult<SystemRole> }>('/sys/role/list', { ...data, name: data.keyword })
await delay()
let filtered = [...mockRoles]
if (data.keyword) filtered = filtered.filter(r => r.name.includes(data.keyword!))
return mockResponse(paginate(filtered, data.current, data.pageSize))
}
return post<{ data: PageResult<SystemRole> }>('/role/getRoleList', data)
} }
export async function getAllRoles() { export async function getAllRoles() {
if (USE_MOCK) { await delay(100); return mockResponse([...mockRoles]) } return post<{ data: { list: SystemRole[] } }>('/sys/role/list', { current: 1, pageSize: 1000 })
return post<{ data: SystemRole[] }>('/role/getAllRoles', {})
} }
export async function saveRole(data: Partial<SystemRole>) { export async function saveRole(data: Partial<SystemRole>) {
if (USE_MOCK) { await delay(); mockRoles.push({ ...data, id: mockId(), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() } as SystemRole); return mockResponse(null, '创建成功') } return post<{ msg: string }>('/sys/role/create', data)
return post<{ msg: string }>('/role/save', data)
} }
export async function updateRole(data: Partial<SystemRole>) { export async function updateRole(data: Partial<SystemRole>) {
if (USE_MOCK) { await delay(); const i = mockRoles.findIndex(r => r.id === data.id); if (i >= 0) Object.assign(mockRoles[i], data); return mockResponse(null, '更新成功') } return post<{ msg: string }>('/sys/role/update', data)
return post<{ msg: string }>('/role/update', data)
} }
export async function deleteRole(ids: string[]) { export async function deleteRole(ids: string[]) {
if (USE_MOCK) { await delay(); ids.forEach(id => { const i = mockRoles.findIndex(r => r.id === id); if (i >= 0) mockRoles.splice(i, 1) }); return mockResponse(null, '删除成功') } return post<{ msg: string }>('/sys/role/delete', { ids })
return post<{ msg: string }>('/role/delete', { ids })
} }
export async function grantMenu(data: { roleId: string; menuIds: string[] }) { export async function grantMenu(data: { roleId: string; menuIds: string[] }) {
if (USE_MOCK) { return post<{ msg: string }>('/sys/role/update', { id: data.roleId, menuIds: data.menuIds })
await delay()
const role = mockRoles.find(r => r.id === data.roleId)
if (role) {
const flatMenus = (items: SystemMenu[]): SystemMenu[] => items.flatMap(m => [m, ...(m.children ? flatMenus(m.children) : [])])
role.menus = flatMenus(mockMenuTree).filter(m => data.menuIds.includes(m.id))
}
return mockResponse(null, '菜单授权成功')
}
return post<{ msg: string }>('/role/grantMenu', data)
} }
// ==================== Menu ==================== // ==================== Menu ====================
export async function getAllMenuTree() { export async function getAllMenuTree() {
if (USE_MOCK) { await delay(); return mockResponse(mockMenuTree) } return get<{ data: { list: SystemMenu[] } }>('/sys/menu/list')
return post<{ data: SystemMenu[] }>('/menu/getAllMenuTree', {})
} }
export async function getUserMenuTree() { export async function getUserMenuTree() {
if (USE_MOCK) { await delay(); return mockResponse(mockMenuTree) } // Get current user info (including menus)
return get<{ data: SystemMenu[] }>('/menu/getUserMenuTree') return get<{ data: SystemUser }>('/auth/info')
} }
export async function saveMenu(data: Partial<SystemMenu>) { export async function saveMenu(data: Partial<SystemMenu>) {
if (USE_MOCK) { await delay(); return mockResponse(null, '创建成功') } return post<{ msg: string }>('/sys/menu/create', data)
return post<{ msg: string }>('/menu/save', data)
} }
export async function updateMenu(data: Partial<SystemMenu>) { export async function updateMenu(data: Partial<SystemMenu>) {
if (USE_MOCK) { await delay(); return mockResponse(null, '更新成功') } return post<{ msg: string }>('/sys/menu/update', data)
return post<{ msg: string }>('/menu/update', data)
} }
export async function deleteMenu(id: string) { export async function deleteMenu(id: string) {
if (USE_MOCK) { await delay(); return mockResponse(null, '删除成功') } return post<{ msg: string }>('/sys/menu/delete', { ids: [id] })
return get<{ msg: string }>('/menu/delete', { id })
} }
// ==================== Client ==================== // ==================== Client ====================
export async function getClientList(data: PageParams & { clientId?: string; name?: string }) { export async function getClientList(data: PageParams & { clientId?: string; name?: string }) {
if (USE_MOCK) { return post<{ data: PageResult<SystemClient> }>('/sys/client/list', { ...data, name: data.keyword })
await delay()
let filtered = [...mockClients]
if (data.keyword) filtered = filtered.filter(c => c.name.includes(data.keyword!) || c.clientId.includes(data.keyword!))
return mockResponse(paginate(filtered, data.current, data.pageSize))
}
return post<{ data: PageResult<SystemClient> }>('/client/getClientList', data)
} }
export async function saveClient(data: Partial<SystemClient>) { export async function saveClient(data: Partial<SystemClient>) {
if (USE_MOCK) { await delay(); mockClients.push({ ...data, id: mockId(), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() } as SystemClient); return mockResponse(null, '创建成功') } return post<{ msg: string }>('/sys/client/create', data)
return post<{ msg: string }>('/client/save', data)
} }
export async function updateClient(data: Partial<SystemClient>) { export async function updateClient(data: Partial<SystemClient>) {
if (USE_MOCK) { await delay(); const i = mockClients.findIndex(c => c.id === data.id); if (i >= 0) Object.assign(mockClients[i], data); return mockResponse(null, '更新成功') } return post<{ msg: string }>('/sys/client/update', data)
return post<{ msg: string }>('/client/update', data)
} }
export async function deleteClient(ids: string[]) { export async function deleteClient(ids: string[]) {
if (USE_MOCK) { await delay(); ids.forEach(id => { const i = mockClients.findIndex(c => c.id === id); if (i >= 0) mockClients.splice(i, 1) }); return mockResponse(null, '删除成功') } return post<{ msg: string }>('/sys/client/delete', { ids })
return post<{ msg: string }>('/client/delete', { ids })
} }
// ==================== File ==================== // ==================== File ====================
export async function getFileList(data: PageParams & { name?: string }) { export async function getFileList(data: PageParams & { name?: string }) {
if (USE_MOCK) { return post<{ data: PageResult<SystemOss> }>('/file/oss/getFileList', data)
await delay()
let filtered = [...mockFiles]
if (data.keyword) filtered = filtered.filter(f => f.name.includes(data.keyword!))
return mockResponse(paginate(filtered, data.current, data.pageSize))
}
return post<{ data: PageResult<SystemOss> }>('/oss/getFileList', data)
} }
export async function uploadFile(_file: File) { export async function uploadFile(_file: File) {
if (USE_MOCK) { await delay(800); const f: SystemOss = { id: mockId(), name: _file.name, key: `uploads/${_file.name}`, url: `https://picsum.photos/seed/${Date.now()}/400/300`, suffix: _file.name.split('.').pop() || '', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; mockFiles.unshift(f); return mockResponse({ file: f }, '上传成功') }
const formData = new FormData(); formData.append('file', _file) const formData = new FormData(); formData.append('file', _file)
return post<{ data: { file: SystemOss }; msg: string }>('/oss/upload', formData) return post<{ data: { file: SystemOss }; msg: string }>('/file/oss/upload', formData)
} }
export async function deleteFile(ids: string[]) { export async function deleteFile(ids: string[]) {
if (USE_MOCK) { await delay(); ids.forEach(id => { const i = mockFiles.findIndex(f => f.id === id); if (i >= 0) mockFiles.splice(i, 1) }); return mockResponse(null, '删除成功') } return post<{ msg: string }>('/file/oss/delete', { ids })
return post<{ msg: string }>('/oss/delete', { ids })
} }
// ==================== Operation Log ==================== // ==================== Operation Log ====================
export async function getOperationLogList(data: PageParams & { clientId?: string; method?: string; statusCode?: number }) { export async function getOperationLogList(data: PageParams & { clientId?: string; method?: string; statusCode?: number }) {
if (USE_MOCK) { return post<{ data: PageResult<OperationLog> }>('/sys/log/list', data)
await delay()
let filtered = [...mockLogs]
if (data.clientId) filtered = filtered.filter(l => l.clientId === data.clientId)
if (data.method) filtered = filtered.filter(l => l.method === data.method)
if (data.statusCode !== undefined) filtered = filtered.filter(l => l.statusCode === data.statusCode)
if (data.keyword) filtered = filtered.filter(l => l.path.includes(data.keyword!) || l.title.includes(data.keyword!) || l.operatorName.includes(data.keyword!))
return mockResponse(paginate(filtered, data.current, data.pageSize))
}
return post<{ data: PageResult<OperationLog> }>('/log/getOperationLogList', data)
} }
+3 -3
View File
@@ -80,7 +80,7 @@ function convertMenuToNavItem(menu: SystemMenu): NavItem {
href: menu.path || menu.code || `/${menu.name.toLowerCase()}`, href: menu.path || menu.code || `/${menu.name.toLowerCase()}`,
icon: getIcon(menu.icon), icon: getIcon(menu.icon),
permission: menu.permission, permission: menu.permission,
children: menu.children?.map(convertMenuToNavItem), children: menu.children?.filter(c => c.category !== 2).length ? menu.children.filter(c => c.category !== 2).map(convertMenuToNavItem) : undefined,
} }
} }
@@ -191,8 +191,8 @@ export default function AdminLayout() {
const navigate = useNavigate() const navigate = useNavigate()
const navItems = useMemo(() => { const navItems = useMemo(() => {
if (menus?.length) return menus.map(convertMenuToNavItem) if (menus?.length) return menus.filter(m => m.category !== 2).map(convertMenuToNavItem)
return [{ title: '仪表盘', href: '/dashboard', icon: <LayoutDashboard className="h-4 w-4" /> }] return []
}, [menus]) }, [menus])
const handleLogout = async () => { await logout(); navigate('/login') } const handleLogout = async () => { await logout(); navigate('/login') }
+1
View File
@@ -12,6 +12,7 @@ const request = axios.create({
request.interceptors.request.use( request.interceptors.request.use(
(config: InternalAxiosRequestConfig) => { (config: InternalAxiosRequestConfig) => {
config.headers['X-Client-Id'] = 'sundynix-admin'
const isWhitelisted = AUTH_WHITELIST.some(path => config.url?.startsWith(path)) const isWhitelisted = AUTH_WHITELIST.some(path => config.url?.startsWith(path))
if (!isWhitelisted) { if (!isWhitelisted) {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
+2 -4
View File
@@ -35,7 +35,7 @@ export default function LoginPage() {
const res = await getCaptcha() const res = await getCaptcha()
const d = (res as any).data const d = (res as any).data
setCaptchaId(d.captchaId) setCaptchaId(d.captchaId)
setCaptchaImg(d.captcha) setCaptchaImg(d.captchaImg)
} catch { /* ignore */ } } catch { /* ignore */ }
} }
@@ -48,7 +48,7 @@ export default function LoginPage() {
try { try {
const res = await apiLogin({ account, password, captcha, captchaId }) const res = await apiLogin({ account, password, captcha, captchaId })
const d = (res as any).data const d = (res as any).data
loginStore(d.user, d.token) loginStore(d.userInfo, d.token)
navigate('/dashboard', { replace: true }) navigate('/dashboard', { replace: true })
} catch (err: any) { } catch (err: any) {
setError(err?.message || '登录失败') setError(err?.message || '登录失败')
@@ -171,8 +171,6 @@ export default function LoginPage() {
: '登 录'} : '登 录'}
</Button> </Button>
</form> </form>
<p className="text-center text-xs text-slate-400 mt-6">Mock · </p>
</div> </div>
</div> </div>
</div> </div>
+10 -5
View File
@@ -12,6 +12,7 @@ interface AuthState {
isAuthenticated: boolean isAuthenticated: boolean
menus: SystemMenu[] menus: SystemMenu[]
permissions: string[] permissions: string[]
hasFetchedMenus: boolean
login: (user: SystemUser, token: string) => void login: (user: SystemUser, token: string) => void
logout: () => Promise<void> logout: () => Promise<void>
refreshMenus: () => Promise<void> refreshMenus: () => Promise<void>
@@ -47,27 +48,31 @@ export const useAuthStore = create<AuthState>((set, get) => ({
isAuthenticated: initial.isAuthenticated, isAuthenticated: initial.isAuthenticated,
menus: [], menus: [],
permissions: [], permissions: [],
hasFetchedMenus: false,
login: (user, token) => { login: (user, token) => {
localStorage.setItem(TOKEN_KEY, token) localStorage.setItem(TOKEN_KEY, token)
localStorage.setItem(USER_KEY, JSON.stringify(user)) localStorage.setItem(USER_KEY, JSON.stringify(user))
set({ user, token, isAuthenticated: true, menus: [], permissions: [] }) set({ user, token, isAuthenticated: true, menus: [], permissions: [], hasFetchedMenus: false })
}, },
logout: async () => { logout: async () => {
try { await apiLogout() } catch { /* ignore */ } try { await apiLogout() } catch { /* ignore */ }
localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(TOKEN_KEY)
localStorage.removeItem(USER_KEY) localStorage.removeItem(USER_KEY)
set({ user: null, token: null, isAuthenticated: false, menus: [], permissions: [] }) set({ user: null, token: null, isAuthenticated: false, menus: [], permissions: [], hasFetchedMenus: false })
}, },
refreshMenus: async () => { refreshMenus: async () => {
if (!get().isAuthenticated) return if (!get().isAuthenticated) return
try { try {
const res = await getUserMenuTree() const res = await getUserMenuTree()
const menus = res.data || [] const menus = (res.data as any).menus || []
set({ menus, permissions: extractPermissions(menus) }) set({ menus, permissions: extractPermissions(menus), hasFetchedMenus: true })
} catch (e) { console.error('获取菜单失败:', e) } } catch (e) {
console.error('获取菜单失败:', e)
set({ hasFetchedMenus: true })
}
}, },
hasPermission: (permission) => { hasPermission: (permission) => {
-1
View File
@@ -17,7 +17,6 @@ export default defineConfig({
'/api': { '/api': {
target: 'http://127.0.0.1:8888', target: 'http://127.0.0.1:8888',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
}, },
}, },
}, },