diff --git a/clinical-web/src/api/management/permissions.ts b/clinical-web/src/api/management/permissions.ts new file mode 100644 index 0000000..10358f6 --- /dev/null +++ b/clinical-web/src/api/management/permissions.ts @@ -0,0 +1,209 @@ +import axios from 'axios' + +import { unwrapApiResponse, unwrapNullableApiResponse } from '@/utils/api-response' +import { request } from '@/utils/request' +import type { ApiResponse, PageResult } from '@/types/common' + +import type { + ApiPermissionListResponse, + ApiPermissionQuery, + ApiPermissionRecord, + ApiPermissionResponseDto, + BackendPage, + CreateApiPermissionRequest, + UpdateApiPermissionRequest, +} from './types' + +const useMockData = import.meta.env.VITE_USE_MOCK !== 'false' + +export const isApiPermissionMockEnabled = useMockData + +const mockRecords: ApiPermissionRecord[] = [ + { + id: 'api-permission-001', + parentId: null, + code: 'system:user:query', + name: '查询用户', + resourceType: 'USER', + resourcePath: '/api/v1/users', + action: 'QUERY', + status: 'ENABLED', + createdAt: '2026-08-27 09:00', + updatedAt: '2026-08-27 09:00', + }, + { + id: 'api-permission-002', + parentId: null, + code: 'system:sign-task:create', + name: '创建签署任务', + resourceType: 'SIGN_TASK', + resourcePath: '/api/v1/sign-tasks', + action: 'CREATE', + status: 'ENABLED', + createdAt: '2026-08-27 09:02', + updatedAt: '2026-08-27 09:02', + }, +] + +function formatDateTime(value: string | null | undefined) { + if (!value) { + return '—' + } + + const date = new Date(value) + + if (Number.isNaN(date.getTime())) { + return value + .replace('T', ' ') + .replace(/\.\d+Z$/, '') + .replace(/Z$/, '') + } + + const pad = (part: number) => String(part).padStart(2, '0') + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad( + date.getHours(), + )}:${pad(date.getMinutes())}` +} + +function mapPermission(dto: ApiPermissionResponseDto): ApiPermissionRecord { + return { + id: dto.id, + parentId: dto.parentId ?? null, + code: dto.code, + name: dto.name, + resourceType: dto.resourceType, + resourcePath: dto.resourcePath ?? '', + action: dto.action ?? 'QUERY', + status: dto.status ?? 'ENABLED', + createdAt: formatDateTime(dto.createdAt), + updatedAt: formatDateTime(dto.updatedAt ?? dto.createdAt), + } +} + +function normalizePage( + data: BackendPage | T[], + fallbackPage: number, + fallbackPageSize: number, +): PageResult { + const records = Array.isArray(data) ? data : (data.records ?? data.items ?? data.content ?? []) + + return { + records, + total: Array.isArray(data) ? records.length : (data.total ?? records.length), + page: Array.isArray(data) ? fallbackPage : (data.page ?? fallbackPage), + pageSize: Array.isArray(data) ? fallbackPageSize : (data.size ?? fallbackPageSize), + } +} + +function toBackendQuery(query: ApiPermissionQuery) { + const params: Record = { + page: Math.max(query.page, 1), + size: Math.min(Math.max(query.pageSize, 1), 200), + } + + if (query.keyword?.trim()) { + params.keyword = query.keyword.trim() + } + + return params +} + +export function getPermissions(query: ApiPermissionQuery): Promise { + if (!useMockData) { + return request + .get>>('/v1/permissions', { + params: toBackendQuery(query), + }) + .then((response) => { + const page = normalizePage(unwrapApiResponse(response), query.page, query.pageSize) + + return { + ...page, + records: page.records.map(mapPermission), + } + }) + } + + const keyword = query.keyword?.trim().toLowerCase() + const filteredRecords = mockRecords.filter((record) => { + return ( + !keyword || + [record.code, record.name, record.resourceType, record.resourcePath] + .join(' ') + .toLowerCase() + .includes(keyword) + ) + }) + const page = Math.max(query.page, 1) + const pageSize = Math.max(query.pageSize, 1) + const start = (page - 1) * pageSize + + return Promise.resolve({ + records: filteredRecords.slice(start, start + pageSize).map((record) => ({ ...record })), + total: filteredRecords.length, + page, + pageSize, + }) +} + +export async function getPermission(id: string): Promise { + if (useMockData) { + const record = mockRecords.find((item) => item.id === id) + return record ? { ...record } : null + } + + try { + const response = await request.get>( + `/v1/permissions/${encodeURIComponent(id)}`, + ) + return mapPermission(unwrapApiResponse(response)) + } catch (error) { + if (axios.isAxiosError(error) && error.response?.status === 404) { + return null + } + + throw error + } +} + +export function createPermission( + payload: CreateApiPermissionRequest, +): Promise { + if (useMockData) { + return Promise.reject(new Error('Mock 模式不执行 API 权限写操作')) + } + + return request + .post>('/v1/permissions', payload) + .then(unwrapApiResponse) + .then(mapPermission) +} + +export function updatePermission( + id: string, + payload: UpdateApiPermissionRequest, +): Promise { + if (useMockData) { + return Promise.reject(new Error('Mock 模式不执行 API 权限写操作')) + } + + return request + .put>( + `/v1/permissions/${encodeURIComponent(id)}`, + payload, + ) + .then(unwrapApiResponse) + .then(mapPermission) +} + +export function deletePermission(id: string): Promise { + if (useMockData) { + return Promise.reject(new Error('Mock 模式不执行 API 权限写操作')) + } + + return request + .delete>(`/v1/permissions/${encodeURIComponent(id)}`) + .then((response) => { + unwrapNullableApiResponse(response) + }) +} diff --git a/clinical-web/src/api/management/types.ts b/clinical-web/src/api/management/types.ts index 08c048a..116b5a9 100644 --- a/clinical-web/src/api/management/types.ts +++ b/clinical-web/src/api/management/types.ts @@ -146,6 +146,60 @@ export interface CreateRoleRequest { export type UpdateRoleRequest = Partial +export type ApiPermissionStatus = 'ENABLED' | 'DISABLED' +export type ApiPermissionAction = 'QUERY' | 'CREATE' | 'UPDATE' | 'DISABLE' + +export interface ApiPermissionQuery extends PageQuery { + keyword?: string +} + +export interface ApiPermissionRecord { + id: string + parentId: string | null + code: string + name: string + resourceType: string + resourcePath: string + action: ApiPermissionAction | string + status: ApiPermissionStatus + createdAt: string + updatedAt: string +} + +export interface ApiPermissionResponseDto { + id: string + parentId?: string | null + code: string + name: string + resourceType: string + resourcePath?: string | null + action?: ApiPermissionAction | string | null + status?: ApiPermissionStatus | null + createdAt?: string | null + updatedAt?: string | null +} + +export interface CreateApiPermissionRequest { + parentId?: string | null + code: string + name: string + resourceType: string + resourcePath?: string + action?: ApiPermissionAction + status?: ApiPermissionStatus +} + +export interface UpdateApiPermissionRequest { + parentId?: string | null + name: string + resourceType: string + resourcePath?: string + action?: ApiPermissionAction + status: ApiPermissionStatus +} + +export type ApiPermissionListResponse = PageResult + export interface CampusRecord { id: string code: string