import axios from 'axios' import { unwrapApiResponse } from '@/utils/api-response' import { mockFlags } from '@/utils/mock-flags' import { request } from '@/utils/request' import { getMockSigningTask } from './signing' import type { ApiPageResponse, ApiResponseOf, SignArtifactResponseDto, SignArtifactType, SigningArtifact, } from './types' const useMockData = mockFlags.signing type ArtifactCollection = SignArtifactResponseDto[] | ApiPageResponse const artifactLabels: Record = { ORIGINAL_PDF: 'PDF 原件', SIGNATURE_IMAGE: '签名原图', SIGNED_PDF: '签署后 PDF', } 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 getCollectionRecords(collection: ArtifactCollection) { return Array.isArray(collection) ? collection : collection.records } function normalizeArtifact(dto: SignArtifactResponseDto): SigningArtifact { const artifactType: SignArtifactType = dto.artifactType const label = artifactLabels[artifactType] ?? '签署文件' const mimeType = dto.mimeType || 'application/octet-stream' const extension = mimeType === 'application/pdf' ? 'pdf' : mimeType === 'image/png' ? 'png' : 'bin' return { id: dto.id, taskId: dto.taskId, pipelineId: dto.pipelineId, artifactType, label, fileName: `${label}.${extension}`, mimeType, sizeBytes: dto.sizeBytes, sha256: dto.sha256, metadata: dto.metadata ?? {}, createdAt: formatDateTime(dto.createdAt), } } export const isArtifactMockEnabled = useMockData /** * 演示签名原图产物的 id 前缀。 * 演示环境没有文件服务,下载时靠 id 反查任务里的签名图, * 保证「签名原图」在演示里也是真能下载到的文件,而不是占位提示。 */ const MOCK_SIGNATURE_PREFIX = 'mock-signature-' function estimateDataUrlBytes(dataUrl: string) { const payload = dataUrl.split(',')[1] if (!payload) { return 0 } // base64 每 4 个字符还原 3 字节,末尾可能有填充,这里取近似值用于展示 return Math.floor((payload.replace(/=+$/, '').length * 3) / 4) } /** 演示产物:已签署且有签名图时,给出可下载的签名原图 */ function buildMockArtifacts(taskId: string): SigningArtifact[] { const task = getMockSigningTask(taskId) if (!task || task.status !== 'signed' || !task.signatureDataUrl) { return [] } return [ { id: `${MOCK_SIGNATURE_PREFIX}${task.id}`, taskId: task.id, pipelineId: `mock-pipeline-${task.id}`, artifactType: 'SIGNATURE_IMAGE', label: artifactLabels.SIGNATURE_IMAGE ?? '签名原图', fileName: '签名原图.png', mimeType: 'image/png', sizeBytes: estimateDataUrlBytes(task.signatureDataUrl), sha256: '', metadata: {}, createdAt: formatDateTime(task.signedAt ?? task.updatedAt), }, ] } /** 把签名图 dataURL 还原成可下载的二进制 Blob */ function dataUrlToBlob(dataUrl: string): Blob { const [meta, payload] = dataUrl.split(',') const mimeType = /data:([^;]+)/.exec(meta ?? '')?.[1] ?? 'application/octet-stream' if (!payload) { return new Blob([], { type: mimeType }) } const binary = window.atob(payload) const bytes = new Uint8Array(binary.length) for (let index = 0; index < binary.length; index += 1) { bytes[index] = binary.charCodeAt(index) } return new Blob([bytes], { type: mimeType }) } export async function getSigningArtifacts(taskId: string): Promise { if (useMockData) { return buildMockArtifacts(taskId) } try { const response = await request.get>( `/v1/sign-artifacts/task/${encodeURIComponent(taskId)}`, ) return getCollectionRecords(unwrapApiResponse(response)).map(normalizeArtifact) } catch (error) { if (axios.isAxiosError(error) && error.response?.status === 404) { return [] } throw error } } export async function getSigningArtifact( taskId: string, artifactId: string, ): Promise { if (useMockData) { return null } try { const response = await request.get>( `/v1/sign-artifacts/task/${encodeURIComponent(taskId)}/${encodeURIComponent(artifactId)}`, ) return normalizeArtifact(unwrapApiResponse(response)) } catch (error) { if (axios.isAxiosError(error) && error.response?.status === 404) { return null } throw error } } export function downloadSigningArtifact(artifactId: string): Promise { if (useMockData) { // 演示环境没有文件服务,按 id 反查任务里的签名图,返回真实的 PNG if (artifactId.startsWith(MOCK_SIGNATURE_PREFIX)) { const taskId = artifactId.slice(MOCK_SIGNATURE_PREFIX.length) const signatureDataUrl = getMockSigningTask(taskId)?.signatureDataUrl if (signatureDataUrl) { return Promise.resolve(dataUrlToBlob(signatureDataUrl)) } } return Promise.resolve(new Blob(['Mock 签署文件,仅用于界面演示。'], { type: 'text/plain' })) } return request.get(`/v1/sign-artifacts/${encodeURIComponent(artifactId)}/download`, { responseType: 'blob', }) }