feat(api): 接入医签通业务接口与类型
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import { unwrapApiResponse } from '@/utils/api-response'
|
||||
import { request } from '@/utils/request'
|
||||
import type { ApiResponse, PageResult } from '@/types/common'
|
||||
|
||||
import type {
|
||||
AuditLogListResponse,
|
||||
AuditLogQuery,
|
||||
AuditLogRecord,
|
||||
AuditLogResponseDto,
|
||||
BackendCollection,
|
||||
BackendPage,
|
||||
} from './types'
|
||||
|
||||
const useMockData = import.meta.env.VITE_USE_MOCK !== 'false'
|
||||
|
||||
function formatDateTime(value: string) {
|
||||
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 mapAuditLog(dto: AuditLogResponseDto): AuditLogRecord {
|
||||
return {
|
||||
id: dto.id,
|
||||
action: dto.action,
|
||||
resourceType: dto.resourceType,
|
||||
resourceId: dto.resourceId,
|
||||
operatorId: dto.operatorId,
|
||||
clientIp: dto.clientIp ?? '—',
|
||||
userAgent: dto.userAgent ?? '—',
|
||||
detailsJson: dto.detailsJson ?? '',
|
||||
createdAt: formatDateTime(dto.createdAt),
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePage<T>(
|
||||
data: BackendPage<T> | T[],
|
||||
fallbackPage: number,
|
||||
fallbackPageSize: number,
|
||||
): PageResult<T> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
export function getAuditLogs(query: AuditLogQuery): Promise<AuditLogListResponse> {
|
||||
if (useMockData) {
|
||||
return Promise.resolve({ records: [], total: 0, page: query.page, pageSize: query.pageSize })
|
||||
}
|
||||
|
||||
const params: Record<string, string | number> = {
|
||||
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 request
|
||||
.get<ApiResponse<BackendCollection<AuditLogResponseDto>>>('/v1/audit-logs', { params })
|
||||
.then(unwrapApiResponse)
|
||||
.then((data) => {
|
||||
const page = normalizePage(data, query.page, query.pageSize)
|
||||
return { ...page, records: page.records.map(mapAuditLog) }
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,21 @@
|
||||
import { unwrapApiResponse, unwrapNullableApiResponse } from '@/utils/api-response'
|
||||
import { request } from '@/utils/request'
|
||||
import type { ApiResponse } from '@/types/common'
|
||||
|
||||
import type { PermissionListResponse, PermissionQuery, PermissionRecord } from './types'
|
||||
import type {
|
||||
BackendCollection,
|
||||
CreateTemplatePermissionRequest,
|
||||
PermissionListResponse,
|
||||
PermissionQuery,
|
||||
PermissionRecord,
|
||||
TemplatePermissionRecord,
|
||||
TemplatePermissionResponseDto,
|
||||
} from './types'
|
||||
|
||||
const useMockData = import.meta.env.VITE_USE_MOCK !== 'false'
|
||||
|
||||
export const isPermissionMockEnabled = useMockData
|
||||
|
||||
const mockRecords: PermissionRecord[] = [
|
||||
{
|
||||
id: 'permission-001',
|
||||
@@ -25,9 +37,9 @@ const mockRecords: PermissionRecord[] = [
|
||||
|
||||
export function getDocumentPermissions(query: PermissionQuery): Promise<PermissionListResponse> {
|
||||
if (!useMockData) {
|
||||
return request.get<PermissionListResponse>('/management/document-permissions', {
|
||||
params: query,
|
||||
})
|
||||
return Promise.reject(
|
||||
new Error('MEDISIGN 未提供跨模板权限汇总接口,请使用 getTemplatePermissions'),
|
||||
)
|
||||
}
|
||||
|
||||
const keyword = query.keyword?.trim().toLowerCase()
|
||||
@@ -51,3 +63,80 @@ export function getDocumentPermissions(query: PermissionQuery): Promise<Permissi
|
||||
pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
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 mapTemplatePermission(dto: TemplatePermissionResponseDto): TemplatePermissionRecord {
|
||||
return {
|
||||
id: dto.id,
|
||||
templateId: dto.templateId,
|
||||
subjectType: dto.subjectType,
|
||||
subjectId: dto.subjectId,
|
||||
subjectName: dto.subjectId,
|
||||
permissionLevel: dto.permissionLevel,
|
||||
effect: dto.effect,
|
||||
inherited: dto.inherited,
|
||||
createdAt: formatDateTime(dto.createdAt),
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTemplatePermissions(
|
||||
templateId: string,
|
||||
userId?: string,
|
||||
): Promise<TemplatePermissionRecord[]> {
|
||||
if (useMockData) {
|
||||
return []
|
||||
}
|
||||
|
||||
const response = await request.get<ApiResponse<BackendCollection<TemplatePermissionResponseDto>>>(
|
||||
`/v1/templates/${encodeURIComponent(templateId)}/permissions`,
|
||||
{ params: userId ? { userId } : undefined },
|
||||
)
|
||||
const data = unwrapApiResponse(response)
|
||||
const records = Array.isArray(data) ? data : (data.records ?? data.items ?? data.content ?? [])
|
||||
return records.map(mapTemplatePermission)
|
||||
}
|
||||
|
||||
export async function createTemplatePermission(
|
||||
templateId: string,
|
||||
payload: CreateTemplatePermissionRequest,
|
||||
): Promise<TemplatePermissionRecord> {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行权限写操作')
|
||||
}
|
||||
|
||||
const response = await request.post<ApiResponse<TemplatePermissionResponseDto>>(
|
||||
`/v1/templates/${encodeURIComponent(templateId)}/permissions`,
|
||||
payload,
|
||||
)
|
||||
return mapTemplatePermission(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function deleteTemplatePermission(templateId: string, permissionId: string) {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行权限写操作')
|
||||
}
|
||||
|
||||
const response = await request.delete<ApiResponse<null>>(
|
||||
`/v1/templates/${encodeURIComponent(templateId)}/permissions/${encodeURIComponent(permissionId)}`,
|
||||
)
|
||||
unwrapNullableApiResponse(response)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
import { request } from '@/utils/request'
|
||||
import axios from 'axios'
|
||||
|
||||
import type { DocumentListResponse, DocumentQuery, DocumentRecord } from './types'
|
||||
import { unwrapApiResponse } from '@/utils/api-response'
|
||||
import { request } from '@/utils/request'
|
||||
import type { ApiResponse, PageResult } from '@/types/common'
|
||||
|
||||
import type {
|
||||
BackendCollection,
|
||||
BackendPage,
|
||||
BackendTemplateStatus,
|
||||
CreateTemplateRequest,
|
||||
CreateTemplateVersionRequest,
|
||||
DocumentListResponse,
|
||||
DocumentQuery,
|
||||
DocumentRecord,
|
||||
TemplateResponseDto,
|
||||
TemplateVersionResponseDto,
|
||||
} from './types'
|
||||
|
||||
const useMockData = import.meta.env.VITE_USE_MOCK !== 'false'
|
||||
|
||||
export const isDocumentMockEnabled = useMockData
|
||||
|
||||
const mockRecords: DocumentRecord[] = [
|
||||
{
|
||||
id: 'doc-001',
|
||||
@@ -25,11 +42,119 @@ const mockRecords: DocumentRecord[] = [
|
||||
},
|
||||
]
|
||||
|
||||
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 normalizeDocumentStatus(status: BackendTemplateStatus | null | undefined) {
|
||||
switch (status) {
|
||||
case 'PUBLISHED':
|
||||
case 'APPROVED':
|
||||
return 'published' as const
|
||||
case 'ARCHIVED':
|
||||
case 'DISABLED':
|
||||
return 'archived' as const
|
||||
case 'PENDING_REVIEW':
|
||||
case 'REJECTED':
|
||||
return 'review' as const
|
||||
case 'DRAFT':
|
||||
default:
|
||||
return 'draft' as const
|
||||
}
|
||||
}
|
||||
|
||||
function mapDocument(dto: TemplateResponseDto): DocumentRecord {
|
||||
const versionId = dto.currentVersionId ?? undefined
|
||||
const version = dto.versionSequence ? `v${dto.versionSequence}` : '—'
|
||||
|
||||
return {
|
||||
id: dto.id,
|
||||
name: dto.name,
|
||||
code: dto.templateCode,
|
||||
category: dto.category ?? '未分类',
|
||||
version: String(version),
|
||||
status: normalizeDocumentStatus(dto.status),
|
||||
backendStatus: dto.status ?? undefined,
|
||||
updatedBy: dto.updatedBy ?? dto.createdBy ?? '—',
|
||||
updatedAt: formatDateTime(dto.updatedAt ?? dto.createdAt),
|
||||
versionId,
|
||||
departmentId: dto.departmentId,
|
||||
departmentName: dto.departmentId ? undefined : '全院通用',
|
||||
campusId: dto.campusId,
|
||||
description: dto.description ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePage<T>(
|
||||
data: BackendPage<T> | T[],
|
||||
fallbackPage: number,
|
||||
fallbackPageSize: number,
|
||||
): PageResult<T> {
|
||||
const records = Array.isArray(data) ? data : (data.records ?? data.items ?? data.content ?? [])
|
||||
const page = Array.isArray(data) ? fallbackPage : (data.page ?? fallbackPage)
|
||||
const pageSize = Array.isArray(data) ? fallbackPageSize : (data.size ?? fallbackPageSize)
|
||||
|
||||
return {
|
||||
records,
|
||||
total: Array.isArray(data) ? records.length : (data.total ?? records.length),
|
||||
page,
|
||||
pageSize,
|
||||
}
|
||||
}
|
||||
|
||||
function toBackendQuery(query: DocumentQuery) {
|
||||
const params: Record<string, string | number> = {
|
||||
page: Math.max(query.page, 1),
|
||||
size: Math.min(Math.max(query.pageSize, 1), 200),
|
||||
}
|
||||
|
||||
if (query.keyword?.trim()) {
|
||||
params.keyword = query.keyword.trim()
|
||||
}
|
||||
|
||||
if (query.status && query.status !== 'all') {
|
||||
const statusMap: Record<Exclude<DocumentQuery['status'], undefined | 'all'>, string> = {
|
||||
draft: 'DRAFT',
|
||||
published: 'PUBLISHED',
|
||||
archived: 'ARCHIVED',
|
||||
review: 'PENDING_REVIEW',
|
||||
}
|
||||
params.status = statusMap[query.status]
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
export function getDocuments(query: DocumentQuery): Promise<DocumentListResponse> {
|
||||
if (!useMockData) {
|
||||
return request.get<DocumentListResponse>('/management/documents', {
|
||||
params: query,
|
||||
})
|
||||
return request
|
||||
.get<ApiResponse<BackendCollection<TemplateResponseDto>>>('/v1/templates', {
|
||||
params: toBackendQuery(query),
|
||||
})
|
||||
.then((response) => {
|
||||
const page = normalizePage(unwrapApiResponse(response), query.page, query.pageSize)
|
||||
|
||||
return {
|
||||
...page,
|
||||
records: page.records.map(mapDocument),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const keyword = query.keyword?.trim().toLowerCase()
|
||||
@@ -44,17 +169,111 @@ export function getDocuments(query: DocumentQuery): Promise<DocumentListResponse
|
||||
const start = (page - 1) * pageSize
|
||||
|
||||
return Promise.resolve({
|
||||
records: filteredRecords.slice(start, start + pageSize),
|
||||
records: filteredRecords.slice(start, start + pageSize).map((record) => ({ ...record })),
|
||||
total: filteredRecords.length,
|
||||
page,
|
||||
pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
export function getDocumentDetail(id: string): Promise<DocumentRecord | null> {
|
||||
if (!useMockData) {
|
||||
return request.get<DocumentRecord>('/management/documents/' + id)
|
||||
export async function getDocumentDetail(id: string): Promise<DocumentRecord | null> {
|
||||
if (useMockData) {
|
||||
const record = mockRecords.find((item) => item.id === id)
|
||||
return record ? { ...record } : null
|
||||
}
|
||||
|
||||
return Promise.resolve(mockRecords.find((record) => record.id === id) ?? null)
|
||||
try {
|
||||
const response = await request.get<ApiResponse<TemplateResponseDto>>(
|
||||
`/v1/templates/${encodeURIComponent(id)}`,
|
||||
)
|
||||
return mapDocument(unwrapApiResponse(response))
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
||||
return null
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function createTemplate(payload: CreateTemplateRequest): Promise<DocumentRecord> {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行模板写操作')
|
||||
}
|
||||
|
||||
const response = await request.post<ApiResponse<TemplateResponseDto>>('/v1/templates', payload)
|
||||
return mapDocument(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function createTemplateVersion(
|
||||
templateId: string,
|
||||
payload: CreateTemplateVersionRequest,
|
||||
): Promise<TemplateVersionResponseDto> {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行模板写操作')
|
||||
}
|
||||
|
||||
const response = await request.post<ApiResponse<TemplateVersionResponseDto>>(
|
||||
`/v1/templates/${encodeURIComponent(templateId)}/versions`,
|
||||
payload,
|
||||
)
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export async function getTemplateVersions(
|
||||
templateId: string,
|
||||
): Promise<TemplateVersionResponseDto[]> {
|
||||
if (useMockData) {
|
||||
return []
|
||||
}
|
||||
|
||||
const response = await request.get<ApiResponse<BackendCollection<TemplateVersionResponseDto>>>(
|
||||
`/v1/templates/${encodeURIComponent(templateId)}/versions`,
|
||||
)
|
||||
return normalizePage(unwrapApiResponse(response), 1, 200).records
|
||||
}
|
||||
|
||||
export async function getTemplateVersionDetail(
|
||||
templateId: string,
|
||||
versionId: string,
|
||||
): Promise<TemplateVersionResponseDto | null> {
|
||||
if (useMockData) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await request.get<ApiResponse<TemplateVersionResponseDto>>(
|
||||
`/v1/templates/${encodeURIComponent(templateId)}/versions/${encodeURIComponent(versionId)}`,
|
||||
)
|
||||
return unwrapApiResponse(response)
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
||||
return null
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export type TemplateVersionAction =
|
||||
'submit-review' | 'reject' | 'approve' | 'publish' | 'disable' | 'archive'
|
||||
|
||||
export function updateTemplateVersionStatus(
|
||||
templateId: string,
|
||||
versionId: string,
|
||||
action: TemplateVersionAction,
|
||||
reason?: string,
|
||||
): Promise<TemplateVersionResponseDto> {
|
||||
if (useMockData) {
|
||||
return Promise.reject(new Error('Mock 模式不执行模板版本工作流'))
|
||||
}
|
||||
|
||||
const requestData = action === 'reject' && reason?.trim() ? { comment: reason.trim() } : undefined
|
||||
|
||||
return request
|
||||
.post<ApiResponse<TemplateVersionResponseDto>>(
|
||||
`/v1/templates/${encodeURIComponent(templateId)}/versions/${encodeURIComponent(versionId)}/${action}`,
|
||||
requestData,
|
||||
)
|
||||
.then(unwrapApiResponse)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import { unwrapApiResponse, unwrapNullableApiResponse } from '@/utils/api-response'
|
||||
import { request } from '@/utils/request'
|
||||
import type { ApiResponse } from '@/types/common'
|
||||
|
||||
import type {
|
||||
BackendCollection,
|
||||
CampusRecord,
|
||||
CreateCampusRequest,
|
||||
CreateDepartmentRequest,
|
||||
DepartmentRecord,
|
||||
OrganizationResponseDto,
|
||||
UpdateCampusRequest,
|
||||
UpdateDepartmentRequest,
|
||||
} from './types'
|
||||
|
||||
const useMockData = import.meta.env.VITE_USE_MOCK !== 'false'
|
||||
|
||||
function getRecords(data: BackendCollection<OrganizationResponseDto>) {
|
||||
return Array.isArray(data) ? data : (data.records ?? data.items ?? data.content ?? [])
|
||||
}
|
||||
|
||||
export function getCampuses(): Promise<CampusRecord[]> {
|
||||
if (useMockData) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
return request
|
||||
.get<ApiResponse<BackendCollection<OrganizationResponseDto>>>('/v1/campuses')
|
||||
.then(unwrapApiResponse)
|
||||
.then((data) =>
|
||||
getRecords(data).map((item) => ({
|
||||
id: item.id,
|
||||
code: item.code ?? item.id,
|
||||
name: item.name ?? item.campusName ?? item.id,
|
||||
address: item.address ?? undefined,
|
||||
status: item.status ?? 'ENABLED',
|
||||
sortNo: item.sortNo ?? 0,
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
export function getDepartments(campusId?: string): Promise<DepartmentRecord[]> {
|
||||
if (useMockData) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
return request
|
||||
.get<ApiResponse<BackendCollection<OrganizationResponseDto>>>('/v1/departments', {
|
||||
params: campusId ? { campusId } : undefined,
|
||||
})
|
||||
.then(unwrapApiResponse)
|
||||
.then((data) =>
|
||||
getRecords(data).map((item) => ({
|
||||
id: item.id,
|
||||
campusId: item.campusId ?? '',
|
||||
parentId: item.parentId,
|
||||
code: item.code ?? item.id,
|
||||
name: item.name ?? item.departmentName ?? item.id,
|
||||
status: item.status ?? 'ENABLED',
|
||||
sortNo: item.sortNo ?? 0,
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
function mapCampus(data: OrganizationResponseDto): CampusRecord {
|
||||
return {
|
||||
id: data.id,
|
||||
code: data.code ?? data.id,
|
||||
name: data.name ?? data.campusName ?? data.id,
|
||||
address: data.address ?? undefined,
|
||||
status: data.status ?? 'ENABLED',
|
||||
sortNo: data.sortNo ?? 0,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDepartment(data: OrganizationResponseDto): DepartmentRecord {
|
||||
return {
|
||||
id: data.id,
|
||||
campusId: data.campusId ?? '',
|
||||
parentId: data.parentId,
|
||||
code: data.code ?? data.id,
|
||||
name: data.name ?? data.departmentName ?? data.id,
|
||||
status: data.status ?? 'ENABLED',
|
||||
sortNo: data.sortNo ?? 0,
|
||||
}
|
||||
}
|
||||
|
||||
export async function createCampus(payload: CreateCampusRequest): Promise<CampusRecord> {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行院区写操作')
|
||||
}
|
||||
|
||||
const response = await request.post<ApiResponse<OrganizationResponseDto>>('/v1/campuses', payload)
|
||||
return mapCampus(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function updateCampus(
|
||||
id: string,
|
||||
payload: UpdateCampusRequest,
|
||||
): Promise<CampusRecord> {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行院区写操作')
|
||||
}
|
||||
|
||||
const response = await request.put<ApiResponse<OrganizationResponseDto>>(
|
||||
`/v1/campuses/${encodeURIComponent(id)}`,
|
||||
payload,
|
||||
)
|
||||
return mapCampus(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function deleteCampus(id: string): Promise<void> {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行院区写操作')
|
||||
}
|
||||
|
||||
const response = await request.delete<ApiResponse<null>>(`/v1/campuses/${encodeURIComponent(id)}`)
|
||||
unwrapNullableApiResponse(response)
|
||||
}
|
||||
|
||||
export async function createDepartment(
|
||||
payload: CreateDepartmentRequest,
|
||||
): Promise<DepartmentRecord> {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行科室写操作')
|
||||
}
|
||||
|
||||
const response = await request.post<ApiResponse<OrganizationResponseDto>>(
|
||||
'/v1/departments',
|
||||
payload,
|
||||
)
|
||||
return mapDepartment(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function updateDepartment(
|
||||
id: string,
|
||||
payload: UpdateDepartmentRequest,
|
||||
): Promise<DepartmentRecord> {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行科室写操作')
|
||||
}
|
||||
|
||||
const response = await request.put<ApiResponse<OrganizationResponseDto>>(
|
||||
`/v1/departments/${encodeURIComponent(id)}`,
|
||||
payload,
|
||||
)
|
||||
return mapDepartment(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function deleteDepartment(id: string): Promise<void> {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行科室写操作')
|
||||
}
|
||||
|
||||
const response = await request.delete<ApiResponse<null>>(
|
||||
`/v1/departments/${encodeURIComponent(id)}`,
|
||||
)
|
||||
unwrapNullableApiResponse(response)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { unwrapApiResponse } from '@/utils/api-response'
|
||||
import { request } from '@/utils/request'
|
||||
import type { ApiResponse } from '@/types/common'
|
||||
|
||||
import type {
|
||||
BackendCollection,
|
||||
CreateRoleRequest,
|
||||
RoleRecord,
|
||||
RoleResponseDto,
|
||||
UpdateRoleRequest,
|
||||
} from './types'
|
||||
|
||||
const useMockData = import.meta.env.VITE_USE_MOCK !== 'false'
|
||||
|
||||
const fallbackColors = ['#6b5bd2', '#0e6e8c', '#0f9d6c', '#d9821f', '#c65d5d']
|
||||
|
||||
function normalizeRole(dto: RoleResponseDto, index: number): RoleRecord {
|
||||
return {
|
||||
id: dto.id,
|
||||
code: dto.code,
|
||||
name: dto.name,
|
||||
description: dto.description ?? '—',
|
||||
color: fallbackColors[index % fallbackColors.length],
|
||||
userCount: dto.userCount ?? 0,
|
||||
status: dto.status ?? 'ENABLED',
|
||||
}
|
||||
}
|
||||
|
||||
export function getRoles(): Promise<RoleRecord[]> {
|
||||
if (useMockData) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
return request
|
||||
.get<ApiResponse<BackendCollection<RoleResponseDto>>>('/v1/roles')
|
||||
.then(unwrapApiResponse)
|
||||
.then((data) => {
|
||||
const records = Array.isArray(data)
|
||||
? data
|
||||
: (data.records ?? data.items ?? data.content ?? [])
|
||||
return records.map(normalizeRole)
|
||||
})
|
||||
}
|
||||
|
||||
export function createRole(payload: CreateRoleRequest): Promise<RoleRecord> {
|
||||
if (useMockData) {
|
||||
return Promise.reject(new Error('Mock 模式不执行角色写操作'))
|
||||
}
|
||||
|
||||
return request
|
||||
.post<ApiResponse<RoleResponseDto>>('/v1/roles', payload)
|
||||
.then(unwrapApiResponse)
|
||||
.then((role) => normalizeRole(role, 0))
|
||||
}
|
||||
|
||||
export function updateRole(id: string, payload: UpdateRoleRequest): Promise<RoleRecord> {
|
||||
if (useMockData) {
|
||||
return Promise.reject(new Error('Mock 模式不执行角色写操作'))
|
||||
}
|
||||
|
||||
return request
|
||||
.put<ApiResponse<RoleResponseDto>>(`/v1/roles/${encodeURIComponent(id)}`, payload)
|
||||
.then(unwrapApiResponse)
|
||||
.then((role) => normalizeRole(role, 0))
|
||||
}
|
||||
|
||||
export function deleteRole(id: string): Promise<void> {
|
||||
if (useMockData) {
|
||||
return Promise.reject(new Error('Mock 模式不执行角色写操作'))
|
||||
}
|
||||
|
||||
return request
|
||||
.delete<ApiResponse<null>>(`/v1/roles/${encodeURIComponent(id)}`)
|
||||
.then((response) => {
|
||||
if (response.code !== 0 && response.code !== '0') {
|
||||
throw new Error(response.message || '角色删除失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PageQuery, PageResult } from '@/types/common'
|
||||
|
||||
export type DocumentStatus = 'draft' | 'published' | 'archived'
|
||||
export type DocumentStatus = 'draft' | 'published' | 'archived' | 'review'
|
||||
|
||||
export interface DocumentQuery extends PageQuery {
|
||||
keyword?: string
|
||||
@@ -10,15 +10,198 @@ export interface DocumentQuery extends PageQuery {
|
||||
export interface DocumentRecord {
|
||||
id: string
|
||||
name: string
|
||||
code?: string
|
||||
category: string
|
||||
version: string
|
||||
status: DocumentStatus
|
||||
backendStatus?: BackendTemplateStatus
|
||||
updatedBy: string
|
||||
updatedAt: string
|
||||
versionId?: string
|
||||
departmentId?: string | null
|
||||
departmentName?: string
|
||||
campusId?: string | null
|
||||
campusName?: string
|
||||
description?: string
|
||||
contentHtml?: string
|
||||
contentSha256?: string
|
||||
}
|
||||
|
||||
export type DocumentListResponse = PageResult<DocumentRecord>
|
||||
|
||||
export type BackendTemplateStatus =
|
||||
| 'DRAFT'
|
||||
| 'PENDING_REVIEW'
|
||||
| 'REJECTED'
|
||||
| 'APPROVED'
|
||||
| 'PUBLISHED'
|
||||
| 'DISABLED'
|
||||
| 'ARCHIVED'
|
||||
| string
|
||||
|
||||
export interface TemplateResponseDto {
|
||||
id: string
|
||||
templateCode: string
|
||||
name: string
|
||||
description?: string | null
|
||||
campusId: string
|
||||
departmentId?: string | null
|
||||
category?: string | null
|
||||
status?: BackendTemplateStatus | null
|
||||
currentVersionId?: string | null
|
||||
versionSequence?: number | null
|
||||
createdAt?: string | null
|
||||
createdBy?: string | null
|
||||
updatedAt?: string | null
|
||||
updatedBy?: string | null
|
||||
}
|
||||
|
||||
export interface TemplateVersionResponseDto {
|
||||
id: string
|
||||
templateId: string
|
||||
versionNo?: string | null
|
||||
versionNumber?: number | null
|
||||
contentHtml?: string | null
|
||||
content?: string | null
|
||||
signatureFields?: unknown[] | null
|
||||
contentSha256?: string | null
|
||||
requestFingerprint?: string | null
|
||||
fileSizeBytes?: number | null
|
||||
fileMimeType?: string | null
|
||||
fileSha256?: string | null
|
||||
fileMetadata?: Record<string, unknown> | null
|
||||
status?: BackendTemplateStatus | null
|
||||
effectiveAt?: string | null
|
||||
disabledAt?: string | null
|
||||
reviewSubmittedAt?: string | null
|
||||
reviewedAt?: string | null
|
||||
reviewedBy?: string | null
|
||||
reviewComment?: string | null
|
||||
publishedAt?: string | null
|
||||
createdAt?: string | null
|
||||
createdBy?: string | null
|
||||
}
|
||||
|
||||
export interface CreateTemplateRequest {
|
||||
templateCode: string
|
||||
name: string
|
||||
description?: string
|
||||
campusId: string
|
||||
departmentId?: string | null
|
||||
category?: string
|
||||
}
|
||||
|
||||
export interface CreateTemplateVersionRequest {
|
||||
versionNo?: string
|
||||
contentHtml: string
|
||||
signatureFields?: unknown[]
|
||||
fileStorageKey?: string
|
||||
fileRelativePath?: string
|
||||
fileSizeBytes?: number
|
||||
fileMimeType?: string
|
||||
fileSha256?: string
|
||||
fileMetadata?: Record<string, unknown>
|
||||
idempotencyKey?: string
|
||||
}
|
||||
|
||||
export interface BackendPage<T> {
|
||||
records?: T[]
|
||||
items?: T[]
|
||||
content?: T[]
|
||||
page?: number
|
||||
size?: number
|
||||
total?: number
|
||||
pages?: number
|
||||
}
|
||||
|
||||
export type BackendCollection<T> = T[] | BackendPage<T>
|
||||
|
||||
export interface RoleRecord {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
description: string
|
||||
color?: string
|
||||
userCount?: number
|
||||
status: RoleStatus
|
||||
}
|
||||
|
||||
export type RoleStatus = 'ENABLED' | 'DISABLED' | string
|
||||
|
||||
export interface RoleResponseDto {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
description?: string | null
|
||||
status?: RoleStatus | null
|
||||
userCount?: number | null
|
||||
}
|
||||
|
||||
export interface CreateRoleRequest {
|
||||
code: string
|
||||
name: string
|
||||
description?: string
|
||||
status?: RoleStatus
|
||||
}
|
||||
|
||||
export type UpdateRoleRequest = Partial<CreateRoleRequest>
|
||||
|
||||
export interface CampusRecord {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
address?: string
|
||||
status: OrganizationStatus
|
||||
sortNo: number
|
||||
}
|
||||
|
||||
export interface DepartmentRecord {
|
||||
id: string
|
||||
campusId: string
|
||||
parentId?: string | null
|
||||
code: string
|
||||
name: string
|
||||
status: OrganizationStatus
|
||||
sortNo: number
|
||||
}
|
||||
|
||||
export type OrganizationStatus = 'ENABLED' | 'DISABLED' | string
|
||||
|
||||
export interface CreateCampusRequest {
|
||||
code: string
|
||||
name: string
|
||||
address?: string
|
||||
status?: OrganizationStatus
|
||||
sortNo?: number
|
||||
}
|
||||
|
||||
export type UpdateCampusRequest = Partial<CreateCampusRequest>
|
||||
|
||||
export interface CreateDepartmentRequest {
|
||||
campusId: string
|
||||
parentId?: string | null
|
||||
code: string
|
||||
name: string
|
||||
status?: OrganizationStatus
|
||||
sortNo?: number
|
||||
}
|
||||
|
||||
export type UpdateDepartmentRequest = Partial<CreateDepartmentRequest>
|
||||
|
||||
export interface OrganizationResponseDto {
|
||||
id: string
|
||||
code?: string | null
|
||||
name?: string | null
|
||||
address?: string | null
|
||||
status?: OrganizationStatus | null
|
||||
sortNo?: number | null
|
||||
campusId?: string | null
|
||||
parentId?: string | null
|
||||
campusName?: string | null
|
||||
departmentId?: string | null
|
||||
departmentName?: string | null
|
||||
}
|
||||
|
||||
export type ReportPeriod = 'today' | 'week' | 'month' | 'year'
|
||||
|
||||
export interface ReportQuery {
|
||||
@@ -70,10 +253,62 @@ export interface UserRecord {
|
||||
role: string
|
||||
status: UserStatus
|
||||
lastLoginAt: string
|
||||
employeeNo?: string | null
|
||||
phone?: string | null
|
||||
email?: string | null
|
||||
campusId?: string | null
|
||||
campusName?: string
|
||||
departmentId?: string | null
|
||||
departmentName?: string
|
||||
roleNames?: string[]
|
||||
dataScope?: string
|
||||
}
|
||||
|
||||
export type UserListResponse = PageResult<UserRecord>
|
||||
|
||||
export interface UserResponseDto {
|
||||
id: string
|
||||
username: string
|
||||
displayName: string
|
||||
employeeNo?: string | null
|
||||
phone?: string | null
|
||||
email?: string | null
|
||||
campusId?: string | null
|
||||
departmentId?: string | null
|
||||
dataScope?: string | null
|
||||
status?: string | null
|
||||
lastLoginAt?: string | null
|
||||
createdAt?: string | null
|
||||
updatedAt?: string | null
|
||||
}
|
||||
|
||||
export interface CreateUserRequest {
|
||||
username: string
|
||||
password: string
|
||||
displayName: string
|
||||
employeeNo?: string
|
||||
phone?: string
|
||||
email?: string
|
||||
campusId: string
|
||||
departmentId?: string | null
|
||||
status?: BackendUserStatus
|
||||
dataScope?: BackendDataScope
|
||||
}
|
||||
|
||||
export type BackendUserStatus = 'ENABLED' | 'DISABLED' | string
|
||||
export type BackendDataScope = 'ALL' | 'CAMPUS' | 'DEPARTMENT' | 'READ_ONLY_ALL' | string
|
||||
|
||||
export interface UpdateUserRequest {
|
||||
displayName: string
|
||||
employeeNo?: string
|
||||
phone?: string
|
||||
email?: string
|
||||
campusId: string
|
||||
departmentId?: string | null
|
||||
status: BackendUserStatus
|
||||
dataScope: BackendDataScope
|
||||
}
|
||||
|
||||
export type PermissionSubjectType = 'user' | 'role'
|
||||
|
||||
export interface PermissionQuery extends PageQuery {
|
||||
@@ -115,3 +350,67 @@ export interface SystemSettingsResponse {
|
||||
export interface UpdateSystemSettingsRequest {
|
||||
values: Record<string, SettingValue>
|
||||
}
|
||||
|
||||
export type ApiPermissionSubjectType = 'ROLE' | 'DEPARTMENT' | 'USER'
|
||||
export type ApiPermissionLevel = 'VIEW' | 'USE' | 'MAINTAIN'
|
||||
export type ApiPermissionEffect = 'ALLOW' | 'DENY'
|
||||
|
||||
export interface TemplatePermissionResponseDto {
|
||||
id: string
|
||||
templateId: string
|
||||
subjectType: ApiPermissionSubjectType
|
||||
subjectId: string
|
||||
permissionLevel: ApiPermissionLevel
|
||||
effect: ApiPermissionEffect
|
||||
inherited: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface TemplatePermissionRecord {
|
||||
id: string
|
||||
templateId: string
|
||||
subjectType: ApiPermissionSubjectType
|
||||
subjectId: string
|
||||
subjectName: string
|
||||
permissionLevel: ApiPermissionLevel
|
||||
effect: ApiPermissionEffect
|
||||
inherited: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface CreateTemplatePermissionRequest {
|
||||
subjectType: ApiPermissionSubjectType
|
||||
subjectId: string
|
||||
permissionLevel: ApiPermissionLevel
|
||||
effect: ApiPermissionEffect
|
||||
}
|
||||
|
||||
export interface AuditLogQuery extends PageQuery {
|
||||
keyword?: string
|
||||
}
|
||||
|
||||
export interface AuditLogResponseDto {
|
||||
id: string
|
||||
action: string
|
||||
resourceType: string
|
||||
resourceId: string
|
||||
operatorId: string
|
||||
clientIp?: string | null
|
||||
userAgent?: string | null
|
||||
detailsJson?: string | null
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface AuditLogRecord {
|
||||
id: string
|
||||
action: string
|
||||
resourceType: string
|
||||
resourceId: string
|
||||
operatorId: string
|
||||
clientIp: string
|
||||
userAgent: string
|
||||
detailsJson: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type AuditLogListResponse = PageResult<AuditLogRecord>
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
import { request } from '@/utils/request'
|
||||
import axios from 'axios'
|
||||
|
||||
import type { UserListResponse, UserQuery, UserRecord } from './types'
|
||||
import { unwrapApiResponse, unwrapNullableApiResponse } from '@/utils/api-response'
|
||||
import { request } from '@/utils/request'
|
||||
import type { ApiResponse, PageResult } from '@/types/common'
|
||||
|
||||
import type {
|
||||
BackendCollection,
|
||||
BackendPage,
|
||||
CreateUserRequest,
|
||||
UpdateUserRequest,
|
||||
UserListResponse,
|
||||
UserQuery,
|
||||
UserRecord,
|
||||
UserResponseDto,
|
||||
} from './types'
|
||||
|
||||
const useMockData = import.meta.env.VITE_USE_MOCK !== 'false'
|
||||
|
||||
export const isUserMockEnabled = useMockData
|
||||
|
||||
const mockRecords: UserRecord[] = [
|
||||
{
|
||||
id: 'user-001',
|
||||
@@ -34,11 +49,90 @@ const mockRecords: UserRecord[] = [
|
||||
},
|
||||
]
|
||||
|
||||
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 normalizeStatus(value: string | null | undefined): UserRecord['status'] {
|
||||
return value === 'DISABLED' || value === 'INACTIVE' ? 'disabled' : 'enabled'
|
||||
}
|
||||
|
||||
function mapUser(dto: UserResponseDto): UserRecord {
|
||||
return {
|
||||
id: dto.id,
|
||||
name: dto.displayName,
|
||||
account: dto.username,
|
||||
department: dto.departmentId ?? '未指定科室',
|
||||
role: '未分配角色',
|
||||
status: normalizeStatus(dto.status),
|
||||
lastLoginAt: formatDateTime(dto.lastLoginAt),
|
||||
employeeNo: dto.employeeNo,
|
||||
phone: dto.phone,
|
||||
email: dto.email,
|
||||
campusId: dto.campusId,
|
||||
departmentId: dto.departmentId,
|
||||
dataScope: dto.dataScope ?? '—',
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePage<T>(
|
||||
data: BackendPage<T> | T[],
|
||||
fallbackPage: number,
|
||||
fallbackPageSize: number,
|
||||
): PageResult<T> {
|
||||
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: UserQuery) {
|
||||
const params: Record<string, string | number> = {
|
||||
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 getUsers(query: UserQuery): Promise<UserListResponse> {
|
||||
if (!useMockData) {
|
||||
return request.get<UserListResponse>('/management/users', {
|
||||
params: query,
|
||||
})
|
||||
return request
|
||||
.get<ApiResponse<BackendCollection<UserResponseDto>>>('/v1/users', {
|
||||
params: toBackendQuery(query),
|
||||
})
|
||||
.then((response) => {
|
||||
const page = normalizePage(unwrapApiResponse(response), query.page, query.pageSize)
|
||||
|
||||
return {
|
||||
...page,
|
||||
records: page.records.map(mapUser),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const keyword = query.keyword?.trim().toLowerCase()
|
||||
@@ -61,3 +155,57 @@ export function getUsers(query: UserQuery): Promise<UserListResponse> {
|
||||
pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
export async function getUserDetail(id: string): Promise<UserRecord | null> {
|
||||
if (useMockData) {
|
||||
const record = mockRecords.find((item) => item.id === id)
|
||||
return record ? { ...record } : null
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await request.get<ApiResponse<UserResponseDto>>(
|
||||
`/v1/users/${encodeURIComponent(id)}`,
|
||||
)
|
||||
return mapUser(unwrapApiResponse(response))
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
||||
return null
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export function createUser(payload: CreateUserRequest): Promise<UserRecord> {
|
||||
if (useMockData) {
|
||||
return Promise.reject(new Error('Mock 模式不执行用户写操作'))
|
||||
}
|
||||
|
||||
return request
|
||||
.post<ApiResponse<UserResponseDto>>('/v1/users', payload)
|
||||
.then(unwrapApiResponse)
|
||||
.then(mapUser)
|
||||
}
|
||||
|
||||
export function updateUser(id: string, payload: UpdateUserRequest): Promise<UserRecord> {
|
||||
if (useMockData) {
|
||||
return Promise.reject(new Error('Mock 模式不执行用户写操作'))
|
||||
}
|
||||
|
||||
return request
|
||||
.put<ApiResponse<UserResponseDto>>(`/v1/users/${encodeURIComponent(id)}`, payload)
|
||||
.then(unwrapApiResponse)
|
||||
.then(mapUser)
|
||||
}
|
||||
|
||||
export function deleteUser(id: string): Promise<void> {
|
||||
if (useMockData) {
|
||||
return Promise.reject(new Error('Mock 模式不执行用户写操作'))
|
||||
}
|
||||
|
||||
return request
|
||||
.delete<ApiResponse<null>>(`/v1/users/${encodeURIComponent(id)}`)
|
||||
.then((response) => {
|
||||
unwrapNullableApiResponse(response)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user