docs(reports): 更新报表接口接入文档与实现说明,报表分析接口接入
This commit is contained in:
@@ -1,72 +1,121 @@
|
||||
import { getCampuses, getDepartments } from './organization'
|
||||
import { getSigningTasks, getSigningTemplates, isSigningMockEnabled } from '@/api/workbench/signing'
|
||||
import type { WorkbenchCampus } from '@/api/workbench/types'
|
||||
import { unwrapApiResponse } from '@/utils/api-response'
|
||||
import { request } from '@/utils/request'
|
||||
import type { ApiResponse } from '@/types/common'
|
||||
|
||||
import type { ReportTaskData } from './types'
|
||||
import type {
|
||||
ReportExportJobResponseDto,
|
||||
ReportExportRequestDto,
|
||||
ReportFilterDto,
|
||||
ReportOptionsResponseDto,
|
||||
ReportOverviewResponseDto,
|
||||
ReportPageResponseDto,
|
||||
ReportTaskResponseDto,
|
||||
ReportTasksQueryDto,
|
||||
} from './types'
|
||||
|
||||
const REPORT_PAGE_SIZE = 200
|
||||
export const isReportsMockEnabled = import.meta.env.VITE_USE_MOCK !== 'false'
|
||||
|
||||
export const isReportsMockEnabled = isSigningMockEnabled
|
||||
function compactQuery(values: object) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(values).filter(
|
||||
([, value]) => value !== undefined && value !== null && value !== '',
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
function buildSigningApiOptions(data: ReportTaskData) {
|
||||
function normalizeReportOptions(data: ReportOptionsResponseDto): ReportOptionsResponseDto {
|
||||
return {
|
||||
templates: data.templates,
|
||||
campusNames: Object.fromEntries(
|
||||
data.campuses
|
||||
.filter((campus) => Boolean(campus.name.trim()))
|
||||
.map((campus) => [campus.id, campus.name]),
|
||||
) as Record<string, WorkbenchCampus>,
|
||||
campuses: data.campuses ?? [],
|
||||
departments: data.departments ?? [],
|
||||
templateCategories: data.templateCategories ?? [],
|
||||
}
|
||||
}
|
||||
|
||||
async function getAllReportTasks(data: ReportTaskData) {
|
||||
const options = buildSigningApiOptions(data)
|
||||
const query = {
|
||||
page: 1,
|
||||
pageSize: REPORT_PAGE_SIZE,
|
||||
status: 'all' as const,
|
||||
dateRange: 'all' as const,
|
||||
campus: 'all' as const,
|
||||
method: 'all' as const,
|
||||
function normalizeReportOverview(data: ReportOverviewResponseDto): ReportOverviewResponseDto {
|
||||
return {
|
||||
timezone: data.timezone ?? 'Asia/Shanghai',
|
||||
generatedAt: data.generatedAt ?? '',
|
||||
snapshotAt: data.snapshotAt ?? '',
|
||||
metrics: {
|
||||
initiatedCount: data.metrics?.initiatedCount ?? 0,
|
||||
signedCount: data.metrics?.signedCount ?? 0,
|
||||
onTimeSignedCount: data.metrics?.onTimeSignedCount ?? 0,
|
||||
onTimeSampleCount: data.metrics?.onTimeSampleCount ?? 0,
|
||||
onTimeRate: data.metrics?.onTimeRate ?? null,
|
||||
averageSignDurationSeconds: data.metrics?.averageSignDurationSeconds ?? null,
|
||||
durationSampleCount: data.metrics?.durationSampleCount ?? 0,
|
||||
},
|
||||
trend: data.trend ?? [],
|
||||
departments: data.departments ?? [],
|
||||
}
|
||||
const firstPage = await getSigningTasks(query, options)
|
||||
const records = [...firstPage.records]
|
||||
const pageSize = Math.max(firstPage.pageSize, 1)
|
||||
const totalPages = Math.ceil(firstPage.total / pageSize)
|
||||
|
||||
for (let page = 2; page <= totalPages; page += 1) {
|
||||
const nextPage = await getSigningTasks({ ...query, page }, options)
|
||||
records.push(...nextPage.records)
|
||||
|
||||
if (!nextPage.records.length) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return records
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询报表所需的全部可见任务。
|
||||
* 当前后端没有聚合报表接口,因此这里按最大页大小读取签署任务;聚合接口上线后只需替换本函数。
|
||||
* ReportFilter/ReportTasksQuery are object query parameters in OpenAPI's default
|
||||
* form/explode representation, so fields are sent flat (createdFrom=..., etc.).
|
||||
*/
|
||||
export async function getReportTaskData(): Promise<ReportTaskData> {
|
||||
if (isReportsMockEnabled) {
|
||||
return { tasks: [], templates: [], campuses: [], departments: [] }
|
||||
}
|
||||
export async function getReportOptions(
|
||||
filter: ReportFilterDto = {},
|
||||
): Promise<ReportOptionsResponseDto> {
|
||||
const response = await request.get<ApiResponse<ReportOptionsResponseDto>>(
|
||||
'/v1/reports/signing/options',
|
||||
{ params: compactQuery(filter) },
|
||||
)
|
||||
|
||||
const [templatesResult, campusesResult, departmentsResult] = await Promise.allSettled([
|
||||
getSigningTemplates(),
|
||||
getCampuses(),
|
||||
getDepartments(),
|
||||
])
|
||||
const data: ReportTaskData = {
|
||||
tasks: [],
|
||||
templates: templatesResult.status === 'fulfilled' ? templatesResult.value : [],
|
||||
campuses: campusesResult.status === 'fulfilled' ? campusesResult.value : [],
|
||||
departments: departmentsResult.status === 'fulfilled' ? departmentsResult.value : [],
|
||||
}
|
||||
|
||||
data.tasks = await getAllReportTasks(data)
|
||||
return data
|
||||
return normalizeReportOptions(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function getReportOverview(
|
||||
filter: ReportFilterDto,
|
||||
): Promise<ReportOverviewResponseDto> {
|
||||
const response = await request.get<ApiResponse<ReportOverviewResponseDto>>(
|
||||
'/v1/reports/signing/overview',
|
||||
{ params: compactQuery(filter) },
|
||||
)
|
||||
|
||||
return normalizeReportOverview(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function getReportTasks(
|
||||
query: ReportTasksQueryDto,
|
||||
): Promise<ReportPageResponseDto<ReportTaskResponseDto>> {
|
||||
const response = await request.get<ApiResponse<ReportPageResponseDto<ReportTaskResponseDto>>>(
|
||||
'/v1/reports/signing/tasks',
|
||||
{ params: compactQuery(query) },
|
||||
)
|
||||
const page = unwrapApiResponse(response)
|
||||
|
||||
return {
|
||||
records: page.records ?? [],
|
||||
page: page.page,
|
||||
size: page.size,
|
||||
total: page.total,
|
||||
pages: page.pages,
|
||||
}
|
||||
}
|
||||
|
||||
export async function createReportExportJob(
|
||||
payload: ReportExportRequestDto,
|
||||
): Promise<ReportExportJobResponseDto> {
|
||||
const response = await request.post<ApiResponse<ReportExportJobResponseDto>>(
|
||||
'/v1/reports/signing/export-jobs',
|
||||
payload,
|
||||
)
|
||||
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export async function getReportExportJob(jobId: string): Promise<ReportExportJobResponseDto> {
|
||||
const response = await request.get<ApiResponse<ReportExportJobResponseDto>>(
|
||||
`/v1/reports/signing/export-jobs/${encodeURIComponent(jobId)}`,
|
||||
)
|
||||
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export async function downloadReportExportFile(jobId: string): Promise<Blob> {
|
||||
return request.get<Blob>(
|
||||
`/v1/reports/signing/export-jobs/${encodeURIComponent(jobId)}/download`,
|
||||
{ responseType: 'blob' },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { PageQuery, PageResult } from '@/types/common'
|
||||
import type { SigningTaskRecord, SigningTemplate } from '@/api/workbench/types'
|
||||
|
||||
export type DocumentStatus = 'draft' | 'published' | 'archived' | 'review'
|
||||
|
||||
@@ -261,15 +260,135 @@ export interface OrganizationResponseDto {
|
||||
departmentName?: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* 报表当前由签署任务列表、模板和组织字典在前端聚合。
|
||||
* 后端提供聚合报表接口后,可在 reports.ts 的边界替换为对应 DTO,页面无需感知。
|
||||
*/
|
||||
export interface ReportTaskData {
|
||||
tasks: SigningTaskRecord[]
|
||||
templates: SigningTemplate[]
|
||||
campuses: CampusRecord[]
|
||||
departments: DepartmentRecord[]
|
||||
export type ReportBackendTaskStatus =
|
||||
'CREATED' | 'WAITING_SIGN' | 'SIGNED' | 'EXPIRED' | 'VOIDED' | 'GENERATING' | 'FAILED'
|
||||
|
||||
export type ReportBackendSignMethod = 'PAD' | 'SMS'
|
||||
|
||||
export type ReportTimeDimension = 'CREATED' | 'SIGNED' | 'EXPIRED'
|
||||
|
||||
export type ReportSortDirection = 'asc' | 'desc'
|
||||
|
||||
export type ReportExportFormat = 'CSV' | 'XLSX'
|
||||
|
||||
/** /api/v1/reports/signing 的公共过滤 DTO;时间区间为左闭右开。 */
|
||||
export interface ReportFilterDto {
|
||||
createdFrom?: string
|
||||
createdTo?: string
|
||||
signedFrom?: string
|
||||
signedTo?: string
|
||||
expiredFrom?: string
|
||||
expiredTo?: string
|
||||
campusId?: string
|
||||
departmentId?: string
|
||||
templateVersionId?: string
|
||||
templateCategory?: string
|
||||
signMethod?: ReportBackendSignMethod
|
||||
keyword?: string
|
||||
timezone?: string
|
||||
}
|
||||
|
||||
export interface ReportTasksQueryDto extends ReportFilterDto {
|
||||
page?: number
|
||||
size?: number
|
||||
timeDimension?: ReportTimeDimension
|
||||
sort?: string
|
||||
direction?: ReportSortDirection
|
||||
}
|
||||
|
||||
export interface ReportExportRequestDto extends ReportTasksQueryDto {
|
||||
format: ReportExportFormat
|
||||
columns?: string[]
|
||||
}
|
||||
|
||||
export interface ReportMetricsDto {
|
||||
initiatedCount: number
|
||||
signedCount: number
|
||||
onTimeSignedCount: number
|
||||
onTimeSampleCount: number
|
||||
onTimeRate: number | null
|
||||
averageSignDurationSeconds: number | null
|
||||
durationSampleCount: number
|
||||
}
|
||||
|
||||
export interface ReportTrendPointDto {
|
||||
date: string
|
||||
label: string
|
||||
signedCount: number
|
||||
}
|
||||
|
||||
export interface ReportDepartmentBreakdownDto {
|
||||
departmentId?: string | null
|
||||
departmentName?: string | null
|
||||
initiatedCount?: number | null
|
||||
signedCount?: number | null
|
||||
onTimeSignedCount?: number | null
|
||||
onTimeSampleCount?: number | null
|
||||
onTimeRate?: number | null
|
||||
}
|
||||
|
||||
export interface ReportOverviewResponseDto {
|
||||
timezone: string
|
||||
generatedAt: string
|
||||
snapshotAt: string
|
||||
metrics: ReportMetricsDto
|
||||
trend: ReportTrendPointDto[]
|
||||
departments: ReportDepartmentBreakdownDto[]
|
||||
}
|
||||
|
||||
export interface ReportTaskResponseDto {
|
||||
taskId: string
|
||||
patientDisplayId: string
|
||||
patientNameMasked: string
|
||||
patientMobileMasked?: string | null
|
||||
status: ReportBackendTaskStatus
|
||||
signMethod: ReportBackendSignMethod
|
||||
createdAt: string
|
||||
deadlineAt?: string | null
|
||||
signStartedAt?: string | null
|
||||
signedAt?: string | null
|
||||
expiredAt?: string | null
|
||||
voidedAt?: string | null
|
||||
signDurationSeconds?: number | null
|
||||
onTime?: boolean | null
|
||||
templateVersionId: string
|
||||
templateVersionNumber?: number | null
|
||||
templateName: string
|
||||
templateCategory: string
|
||||
campusId: string
|
||||
campusName: string
|
||||
departmentId?: string | null
|
||||
departmentName: string
|
||||
}
|
||||
|
||||
export interface ReportOptionsResponseDto {
|
||||
campuses: Array<{ id: string; name: string }>
|
||||
departments: Array<{ id: string; campusId: string; name: string }>
|
||||
templateCategories: string[]
|
||||
}
|
||||
|
||||
export interface ReportPageResponseDto<T> {
|
||||
records: T[]
|
||||
page: number
|
||||
size: number
|
||||
total: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
export type ReportExportJobStatus = 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'EXPIRED'
|
||||
|
||||
export interface ReportExportJobResponseDto {
|
||||
jobId: string
|
||||
status: ReportExportJobStatus
|
||||
format?: ReportExportFormat | string | null
|
||||
requestedAt?: string | null
|
||||
startedAt?: string | null
|
||||
completedAt?: string | null
|
||||
expiresAt?: string | null
|
||||
resultCount?: number | null
|
||||
errorCode?: string | null
|
||||
errorMessage?: string | null
|
||||
downloadPath?: string | null
|
||||
}
|
||||
|
||||
export type UserStatus = 'enabled' | 'disabled'
|
||||
|
||||
Reference in New Issue
Block a user