diff --git a/src/api/system.ts b/src/api/system.ts index 5b99dd1..ebf8d95 100644 --- a/src/api/system.ts +++ b/src/api/system.ts @@ -10,7 +10,7 @@ export interface SystemUser { phone?: string avatarId?: string gender?: number // 0=未知 1=男 2=女 - roles?: string[] // 角色 code 列表(仅 /auth/info 返回) + roles?: any[] // 可能是 string[] 也可能是 SystemRole[] menus?: SystemMenu[] // 菜单树(仅 /auth/info 返回) createdAt?: number // Unix 时间戳(秒) roleIds?: string[] // 关联角色 ID(用户管理接口) @@ -22,6 +22,7 @@ export interface SystemRole { code: string sort?: number menuIds?: string[] // 关联菜单 ID 列表 + createdAt?: number // Unix 时间戳(秒) } export interface SystemMenu { @@ -45,6 +46,21 @@ export interface SystemOss { createdAt: string; updatedAt: string } +export interface StorageConfig { + id: string + type: string + name: string + endpoint: string + accessKeyId: string + accessKeySecret: string + bucketName: string + bucketUrl: string + region?: string + isDefault: number + status: number + remark?: string +} + export interface SystemClient { id: string clientId: string diff --git a/src/api/system/file.ts b/src/api/system/file.ts index 9c80ab9..0058a88 100644 --- a/src/api/system/file.ts +++ b/src/api/system/file.ts @@ -1,9 +1,9 @@ import { post } from '@/lib/request' import { USE_MOCK, delay, paginate, mockId } from '@/mock' -import type { SystemOss } from '../system' +import type { SystemOss, StorageConfig } from '../system' import type { PageResult } from '@/lib/request' -const BASE_URL = '/file/oss' +const BASE_URL = '/file' const mockFiles: SystemOss[] = [ { id: '1', name: 'avatar.jpg', key: 'avatars/1.jpg', url: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=800&q=80', suffix: 'jpg', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, @@ -17,7 +17,7 @@ export async function getFileList(params: { current: number; pageSize: number; k if (params.keyword) list = list.filter(f => f.name.includes(params.keyword!)) return paginate(list, params.current, params.pageSize) } - return post>(`${BASE_URL}/getFileList`, params) + return post>(`${BASE_URL}/list`, params) } export async function uploadFile(_file: File) { @@ -47,3 +47,34 @@ export async function deleteFile(ids: string[]) { } return post(`${BASE_URL}/delete`, { ids }) } + +// ==================== Storage Config APIs ==================== +const CONFIG_URL = '/file/config' + +export async function getStorageConfigList(params: { current: number; pageSize: number; type?: string; name?: string }) { + if (USE_MOCK) { + await delay() + return paginate([], params.current, params.pageSize) + } + return post>(`${CONFIG_URL}/list`, params) +} + +export async function createStorageConfig(data: Partial) { + if (USE_MOCK) return delay() + return post(`${CONFIG_URL}/create`, data) +} + +export async function updateStorageConfig(data: Partial) { + if (USE_MOCK) return delay() + return post(`${CONFIG_URL}/update`, data) +} + +export async function deleteStorageConfig(ids: string[]) { + if (USE_MOCK) return delay() + return post(`${CONFIG_URL}/delete`, { ids }) +} + +export async function setDefaultStorageConfig(id: string) { + if (USE_MOCK) return delay() + return post(`${CONFIG_URL}/setDefault`, { id }) +} diff --git a/src/api/system/role.ts b/src/api/system/role.ts index 8e66ffc..48ff34a 100644 --- a/src/api/system/role.ts +++ b/src/api/system/role.ts @@ -1,4 +1,4 @@ -import { post } from '@/lib/request' +import { get, post } from '@/lib/request' import { USE_MOCK, delay, paginate } from '@/mock' import type { SystemRole } from '../system' import type { PageResult } from '@/lib/request' @@ -37,3 +37,13 @@ export async function deleteRole(ids: string[]) { if (USE_MOCK) { await delay(); return null } return post(`${BASE_URL}/delete`, { ids }) } + +export async function getRoleDetail(id: string) { + if (USE_MOCK) { await delay(); return null } + return get(`${BASE_URL}/detail`, { id }) +} + +export async function assignRoleMenus(data: { roleId: string; menuIds: string[] }) { + if (USE_MOCK) { await delay(); return null } + return post(`${BASE_URL}/assignMenus`, data) +} diff --git a/src/api/system/user.ts b/src/api/system/user.ts index 55f6ecc..6fcf66a 100644 --- a/src/api/system/user.ts +++ b/src/api/system/user.ts @@ -1,4 +1,4 @@ -import { post } from '@/lib/request' +import { get, post } from '@/lib/request' import { USE_MOCK, delay, paginate } from '@/mock' import { mockUsers } from '@/mock/system/users' import type { SystemUser } from '../system' @@ -36,3 +36,13 @@ export async function resetPassword(data: { id: string; password: string }) { if (USE_MOCK) { await delay(); return null } return post(`${BASE_URL}/resetPassword`, data) } + +export async function getUserDetail(id: string) { + if (USE_MOCK) { await delay(); return null } + return get(`${BASE_URL}/detail`, { id }) +} + +export async function assignUserRoles(data: { userId: string; roleIds: string[] }) { + if (USE_MOCK) { await delay(); return null } + return post(`${BASE_URL}/assignRoles`, data) +} diff --git a/src/lib/request.ts b/src/lib/request.ts index 6ea37c7..22e9d4e 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -7,7 +7,7 @@ const AUTH_WHITELIST = ['/auth/captcha', '/auth/login'] const request = axios.create({ baseURL: API_BASE_URL, timeout: 30000, - headers: { 'Content-Type': 'application/json' }, + // Let Axios automatically set Content-Type based on payload type }) request.interceptors.request.use( @@ -18,9 +18,7 @@ request.interceptors.request.use( const token = localStorage.getItem('token') if (token) config.headers.Authorization = `Bearer ${token}` } - if (config.data instanceof FormData) { - config.headers.delete('Content-Type') - } + // Remove any custom handling, Axios natively handles FormData correctly without global Content-Type return config }, (error: AxiosError) => Promise.reject(error) diff --git a/src/pages/system/Logs.tsx b/src/pages/system/Logs.tsx index f7e72be..9a398cc 100644 --- a/src/pages/system/Logs.tsx +++ b/src/pages/system/Logs.tsx @@ -236,9 +236,23 @@ export default function Logs() {
操作者ID: {activeLog.userId}
客户端: {activeLog.clientId}
IP地址: {activeLog.ip}
+
+ 响应耗时: + 200000000 ? 'text-amber-500' : 'text-emerald-600'}`}> + {(activeLog.latency / 1000000).toFixed(0)}ms + +
+
+ 状态码: + + {activeLog.status} + +
+
操作时间: {new Date(activeLog.createdAt * 1000).toLocaleString('zh-CN')}
User-Agent: {activeLog.agent}
+ {activeLog.body && (

Request Payload

diff --git a/src/pages/system/Roles.tsx b/src/pages/system/Roles.tsx index 8d1a650..92827b6 100644 --- a/src/pages/system/Roles.tsx +++ b/src/pages/system/Roles.tsx @@ -9,7 +9,7 @@ import { Label } from '@/components/ui/label' import { Checkbox } from '@/components/ui/checkbox' import { ScrollArea } from '@/components/ui/scroll-area' -import { getRoleList, createRole, updateRole, deleteRole } from '@/api/system/role' +import { getRoleList, createRole, updateRole, deleteRole, getRoleDetail, assignRoleMenus } from '@/api/system/role' import { getMenuTree } from '@/api/system/menu' import type { SystemRole, SystemMenu } from '@/api/system' @@ -82,24 +82,62 @@ export default function Roles() { } } - const openPermDialog = (role: SystemRole) => { + const openPermDialog = async (role: SystemRole) => { setActiveRole(role) - // Actually get the role's menus - setSelectedMenuIds(role.menuIds || []) + const detail = await getRoleDetail(role.id) + if (detail) { + setSelectedMenuIds(detail.menuIds || []) + } else { + setSelectedMenuIds([]) + } setPermDialogOpen(true) } const handleSavePerms = async () => { if (activeRole) { - await updateRole({ id: activeRole.id, menuIds: selectedMenuIds }) + await assignRoleMenus({ roleId: activeRole.id, menuIds: selectedMenuIds }) fetchRoles() } setPermDialogOpen(false) } - const toggleMenu = (id: string, checked: boolean) => { - if (checked) setSelectedMenuIds(prev => [...prev, id]) - else setSelectedMenuIds(prev => prev.filter(i => i !== id)) + const toggleMenu = (menu: SystemMenu, checked: boolean) => { + setSelectedMenuIds(prev => { + const nextIds = new Set(prev) + + if (checked) { + // 向下级联:勾选所有子孙节点 + const checkDescendants = (m: SystemMenu) => { + nextIds.add(m.id) + m.children?.forEach(checkDescendants) + } + checkDescendants(menu) + + // 向上级联:勾选所有父辈节点 + const findAndAddParents = (nodes: SystemMenu[], targetId: string, parents: string[]): boolean => { + for (const node of nodes) { + if (node.id === targetId) { + parents.forEach(p => nextIds.add(p)) + return true + } + if (node.children && findAndAddParents(node.children, targetId, [...parents, node.id])) { + return true + } + } + return false + } + findAndAddParents(allMenus, menu.id, []) + } else { + // 向下级联:取消勾选所有子孙节点 + const uncheckDescendants = (m: SystemMenu) => { + nextIds.delete(m.id) + m.children?.forEach(uncheckDescendants) + } + uncheckDescendants(menu) + } + + return Array.from(nextIds) + }) } const renderMenuTree = (menus: SystemMenu[], depth = 0) => { @@ -109,7 +147,7 @@ export default function Roles() { toggleMenu(menu.id, c as boolean)} + onCheckedChange={c => toggleMenu(menu, c as boolean)} />