businessInfoImageUpload.ts 5.9 KB

1
  1. import type { UploadRequestOptions, UploadUserFile } from "element-plus"; import { ElLoading, ElMessage } from "element-plus"; import { uploadFileToOss, isUploadUserCancelledError, isUploadApiErrorAlreadyMessaged } from "@/api/upload.js"; export { isUploadUserCancelledError }; export interface BusinessInfoImageUploadResult { fileUrl: string; /** 与历史 media_id / image_id 字段兼容;OSS 场景下默认与 fileUrl 一致 */ mediaId: string; } let blockingLoadingDepth = 0; let blockingLoadingInst: ReturnType<typeof ElLoading.service> | null = null; /** 页面单独 OCR 审核期间:全屏 loading + 禁止操作 */ export async function withBusinessInfoBlockingLoading<T>( task: () => Promise<T>, text = "识别中,请稍候..." ): Promise<T> { if (blockingLoadingDepth === 0) { blockingLoadingInst = ElLoading.service({ fullscreen: true, lock: true, text, background: "rgba(0, 0, 0, 0.55)" }); } blockingLoadingDepth += 1; try { return await task(); } finally { blockingLoadingDepth -= 1; if (blockingLoadingDepth <= 0 && blockingLoadingInst) { blockingLoadingInst.close(); blockingLoadingInst = null; blockingLoadingDepth = 0; } } } /** * 商家进件等:OSS STS 直传;默认走 finalize 内容审核 * @param options.skipFinalize 营业执照/身份证等:仅直传,审核由页面 OCR 完成 */ export async function uploadBusinessInfoImageToOss( file: File, options: { showUploadOverlay?: boolean; skipFinalize?: boolean; uploadOverlayTitle?: string; uploadSuccessMessage?: string | null; } = {} ): Promise<BusinessInfoImageUploadResult> { const showOverlay = options.showUploadOverlay !== false; const skipFinalize = options.skipFinalize === true; const fileUrl = String( (await uploadFileToOss(file, "image", { skipSimpleUploadOverlay: !showOverlay, skipFinalize, uploadOverlayTitle: options.uploadOverlayTitle ?? (skipFinalize ? "上传中..." : undefined), uploadSuccessMessage: options.uploadSuccessMessage !== undefined ? options.uploadSuccessMessage : skipFinalize ? null : undefined })) || "" ).trim(); if (!fileUrl) { throw new Error("上传失败,未返回地址"); } return { fileUrl, mediaId: fileUrl }; } /** * 营业执照 / 身份证等:OSS 直传(不走 finalize)→ 页面 OCR 审核(全屏 loading) */ export async function uploadBusinessInfoImageWithPageOcr( file: File, runOcr: (file: File) => Promise<void>, options: { showUploadOverlay?: boolean; ocrLoadingText?: string } = {} ): Promise<BusinessInfoImageUploadResult> { const result = await uploadBusinessInfoImageToOss(file, { showUploadOverlay: options.showUploadOverlay !== false, skipFinalize: true }); await withBusinessInfoBlockingLoading( () => runOcr(file), options.ocrLoadingText ?? "识别中,请稍候..." ); ElMessage.success("上传成功"); return result; } export function filterOutUploadUserFileByUid( list: UploadUserFile[], uid?: number ): UploadUserFile[] { if (uid == null) return list; return list.filter(f => f.uid !== uid); } /** 从 file-list 收集已成功上传的服务器地址(排除 blob 本地预览) */ /** 审核失败 / 上传失败:移除列表项并清空业务字段(营业执照、证件照等) */ export function failBusinessInfoUploadCleanup(handlers: { fileList: UploadUserFile[]; setFileList: (list: UploadUserFile[]) => void; uid?: number; onClear?: () => void; error?: unknown; }) { handlers.setFileList(filterOutUploadUserFileByUid(handlers.fileList, handlers.uid)); handlers.onClear?.(); const err = handlers.error; if (err && !isUploadUserCancelledError(err) && !isUploadApiErrorAlreadyMessaged(err)) { const o = err && typeof err === "object" ? (err as Record<string, unknown>) : null; const msg = String( (err instanceof Error ? err.message : "") || o?.msg || o?.message || (typeof err === "string" ? err : "") || "上传失败" ).trim(); if (msg) ElMessage.error(msg); } } export function collectSuccessUploadUrls(list: UploadUserFile[]): string[] { return list .filter(f => f.status === "success") .map(f => { const fu = f as UploadUserFile & { url?: string; response?: { url?: string } }; return String(fu.url ?? fu.response?.url ?? "").trim(); }) .filter(u => u && !/^blob:/i.test(u) && !/^data:/i.test(u)); } type UploadFailHandler = UploadRequestOptions["onError"]; /** * el-upload http-request 通用:OSS 上传,失败/取消时移除列表项 */ export async function runBusinessInfoOssUpload( options: UploadRequestOptions, handlers: { fileList: UploadUserFile[]; setFileList: (list: UploadUserFile[]) => void; onUploaded: (result: BusinessInfoImageUploadResult, file: File) => void | Promise<void>; onFail?: () => void; } ): Promise<void> { const uploadFileItem = options.file as UploadUserFile; const uid = uploadFileItem.uid; const raw = uploadFileItem.raw || uploadFileItem; const file = raw instanceof File ? raw : null; if (!file) { handlers.setFileList(filterOutUploadUserFileByUid(handlers.fileList, uid)); (options.onError as UploadFailHandler)?.(new Error("无效文件") as any); return; } uploadFileItem.status = "uploading"; try { const result = await uploadBusinessInfoImageToOss(file); uploadFileItem.status = "success"; uploadFileItem.url = result.fileUrl; uploadFileItem.response = { media_id: result.mediaId, url: result.fileUrl }; await handlers.onUploaded(result, file); options.onSuccess(result as any); } catch (err) { failBusinessInfoUploadCleanup({ fileList: handlers.fileList, setFileList: handlers.setFileList, uid, onClear: handlers.onFail, error: err }); (options.onError as UploadFailHandler)?.(err as any); } }