145 lines
4.7 KiB
TypeScript
145 lines
4.7 KiB
TypeScript
'use client'
|
|
|
|
import { chunkUpload } from '@/lib/chunk-upload'
|
|
import { preparePerformanceSurveyImagesInBrowser } from '@/lib/performance-survey-image-browser'
|
|
import type {
|
|
WorkReviewBoardData,
|
|
WorkReviewProcessedImage,
|
|
WorkReviewProject,
|
|
WorkReviewReviewInput,
|
|
WorkReviewStatus,
|
|
WorkReviewSubmissionInput,
|
|
} from '@/types/work-review'
|
|
|
|
const API_BASE = '/api/work-review'
|
|
|
|
export class WorkReviewRequestError extends Error {
|
|
status: number
|
|
|
|
constructor(message: string, status: number) {
|
|
super(message)
|
|
this.status = status
|
|
}
|
|
}
|
|
|
|
async function workReviewRequest<T>(path: string, options?: RequestInit): Promise<T> {
|
|
let response: Response
|
|
try {
|
|
response = await fetch(`${API_BASE}${path}`, {
|
|
...options,
|
|
cache: options?.method && options.method !== 'GET' ? options.cache : 'no-store',
|
|
headers: {
|
|
...(options?.body instanceof FormData ? {} : { 'Content-Type': 'application/json' }),
|
|
...(options?.headers || {}),
|
|
},
|
|
})
|
|
} catch (error) {
|
|
if (error instanceof DOMException && error.name === 'AbortError') throw error
|
|
throw new WorkReviewRequestError('网络连接失败,请检查网络后重试', 0)
|
|
}
|
|
|
|
const contentType = response.headers.get('content-type') || ''
|
|
const payload = contentType.includes('application/json')
|
|
? await response.json().catch(() => null)
|
|
: await response.text().catch(() => '')
|
|
|
|
if (!response.ok || (payload && typeof payload === 'object' && payload.success === false)) {
|
|
const message = payload && typeof payload === 'object'
|
|
? String(payload.error || payload.message || '请求失败')
|
|
: String(payload || '请求失败')
|
|
throw new WorkReviewRequestError(message, response.status)
|
|
}
|
|
|
|
return (payload && typeof payload === 'object' && 'data' in payload ? payload.data : payload) as T
|
|
}
|
|
|
|
export function getWorkReviewBoard(
|
|
input: {
|
|
query?: string
|
|
status?: WorkReviewStatus | ''
|
|
project?: string
|
|
page?: number
|
|
pageSize?: number
|
|
},
|
|
signal?: AbortSignal,
|
|
) {
|
|
const params = new URLSearchParams()
|
|
if (input.query?.trim()) params.set('query', input.query.trim())
|
|
if (input.status) params.set('status', input.status)
|
|
if (input.project?.trim()) params.set('project', input.project.trim())
|
|
params.set('page', String(input.page || 1))
|
|
params.set('pageSize', String(input.pageSize || 20))
|
|
return workReviewRequest<WorkReviewBoardData>(`?${params.toString()}`, { signal })
|
|
}
|
|
|
|
export function createWorkReviewProject(input: WorkReviewSubmissionInput) {
|
|
return workReviewRequest<WorkReviewProject>('/projects', {
|
|
method: 'POST',
|
|
body: JSON.stringify(input),
|
|
})
|
|
}
|
|
|
|
export function createWorkReviewVersion(projectId: string, input: WorkReviewSubmissionInput) {
|
|
return workReviewRequest<WorkReviewProject>(`/projects/${encodeURIComponent(projectId)}/versions`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(input),
|
|
})
|
|
}
|
|
|
|
export function submitWorkReview(versionId: string, input: WorkReviewReviewInput) {
|
|
return workReviewRequest<WorkReviewProject>(`/versions/${encodeURIComponent(versionId)}/reviews`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(input),
|
|
})
|
|
}
|
|
|
|
export async function uploadWorkReviewImages(
|
|
files: File[],
|
|
onProgress?: (message: string, percent?: number) => void,
|
|
): Promise<WorkReviewProcessedImage[]> {
|
|
if (files.length === 0) throw new Error('请至少选择 1 张作品图片')
|
|
|
|
const processed = await preparePerformanceSurveyImagesInBrowser(
|
|
files,
|
|
(message) => {
|
|
onProgress?.(message)
|
|
},
|
|
{ profile: 'work_review' },
|
|
)
|
|
const uploaded: WorkReviewProcessedImage[] = []
|
|
|
|
for (let index = 0; index < processed.length; index += 1) {
|
|
const image = processed[index]
|
|
onProgress?.(`正在上传图片 ${index + 1}/${processed.length}`, Math.round((index / processed.length) * 100))
|
|
const result = await chunkUpload(
|
|
image.file,
|
|
{
|
|
materialType: '作品过稿看板',
|
|
fileName: image.file.name,
|
|
},
|
|
{
|
|
skipComplete: true,
|
|
onProgress: (fileProgress) => {
|
|
const totalProgress = ((index + fileProgress / 100) / processed.length) * 100
|
|
onProgress?.(`正在上传图片 ${index + 1}/${processed.length}`, Math.round(totalProgress))
|
|
},
|
|
},
|
|
)
|
|
if (!result?.uploadId) throw new Error(`图片「${image.sourceName}」上传失败,请重试`)
|
|
uploaded.push({
|
|
uploadId: result.uploadId,
|
|
mimeType: image.mimeType,
|
|
width: image.width,
|
|
height: image.height,
|
|
kind: image.kind,
|
|
sourceName: image.sourceName,
|
|
sourceIndex: image.sourceIndex,
|
|
sliceIndex: image.sliceIndex,
|
|
sliceCount: image.sliceCount,
|
|
})
|
|
}
|
|
|
|
onProgress?.('图片上传完成,正在保存作品', 100)
|
|
return uploaded
|
|
}
|