/** * OmniCard AI (CRMmp) - 跨端统一图片上传核心业务驱动层 (UTS) * 严格遵循 uts 强类型、零 emoji 与 AGENTS.md 约束 * 支持单图/多图上传、大图预览 (uni.previewImage)、独立删除与上限拦截 */ export type ImageUploaderState = { images: string[]; uploading: boolean; errorMessage: string; }; export type ChooseImageCallbackRes = { tempFilePaths: string[]; }; export type ChooseImageOptions = { count?: number; sizeType?: string[]; sourceType?: string[]; success?: (res: ChooseImageCallbackRes) => void; fail?: (err: unknown) => void; }; export type ImageUploaderOptions = { multiple?: boolean; maxCount?: number; sourceType?: string[]; chooseImageFn?: (options: ChooseImageOptions) => void; }; /** * 创建上传状态 */ export function createImageUploaderState(initialImages?: string[]): ImageUploaderState { return { images: initialImages != null ? initialImages.slice() : [], uploading: false, errorMessage: '', }; } /** * 追加图片 URL,并严格按 maxCount 截断 */ export function addImageUrls( state: ImageUploaderState, urls: string[], maxCount: number = 9 ): void { const nextList: string[] = []; for (let i = 0; i < state.images.length; i++) { nextList.push(state.images[i]); } for (let j = 0; j < urls.length; j++) { const url = urls[j].trim(); if (url !== '') { nextList.push(url); } } state.images = nextList.slice(0, maxCount); } /** * 依据索引删除单张图片 */ export function removeImageByIndex( state: ImageUploaderState, index: number ): void { if (index < 0 || index >= state.images.length) { return; } const nextList: string[] = []; for (let i = 0; i < state.images.length; i++) { if (i !== index) { nextList.push(state.images[i]); } } state.images = nextList; } /** * 清空所有图片 */ export function clearAllImages(state: ImageUploaderState): void { state.images = []; } /** * 获取可用于全屏大图预览的 URL 数组 */ export function resolvePreviewUrls(state: ImageUploaderState): string[] { return state.images.slice(); } /** * 触发调用系统原生全屏大图放大预览 */ export function previewImageAt( state: ImageUploaderState, currentUrlOrIndex: string | number ): void { const urls = resolvePreviewUrls(state); if (urls.length === 0) { return; } let current = ''; if (typeof currentUrlOrIndex === 'number') { if (currentUrlOrIndex >= 0 && currentUrlOrIndex < urls.length) { current = urls[currentUrlOrIndex]; } else { current = urls[0]; } } else { current = currentUrlOrIndex; } // #ifndef UNI-APP-X-TEST try { if (typeof uni !== 'undefined' && typeof uni.previewImage === 'function') { uni.previewImage({ urls, current, }); } } catch (_e) {} // #endif } /** * 选择图片并执行上传流程 */ export async function chooseAndUploadImages( state: ImageUploaderState, options?: ImageUploaderOptions ): Promise { const opt = options != null ? options : {}; const multiple = opt.multiple !== undefined ? opt.multiple : false; const maxCount = opt.maxCount !== undefined ? opt.maxCount : (multiple ? 9 : 1); const remainingCount = maxCount - state.images.length; if (remainingCount <= 0) { state.errorMessage = '已达到上传上限'; return false; } const selectCount = multiple ? remainingCount : 1; const sourceTypes = opt.sourceType != null && opt.sourceType.length > 0 ? opt.sourceType : ['album', 'camera']; state.uploading = true; state.errorMessage = ''; return new Promise((resolve) => { const chooseFn = opt.chooseImageFn != null ? opt.chooseImageFn : (typeof uni !== 'undefined' && typeof uni.chooseImage === 'function' ? uni.chooseImage : null); if (!chooseFn) { state.uploading = false; state.errorMessage = 'chooseImage 不可用'; resolve(false); return; } try { chooseFn({ count: selectCount, sourceType: sourceTypes, success: (res: ChooseImageCallbackRes) => { if (res.tempFilePaths != null && res.tempFilePaths.length > 0) { addImageUrls(state, res.tempFilePaths, maxCount); state.uploading = false; resolve(true); } else { state.uploading = false; resolve(false); } }, fail: (err: unknown) => { state.uploading = false; let msg = '取消选择图片'; if (err instanceof Error) { msg = err.message; } state.errorMessage = msg; resolve(false); }, }); } catch (e) { state.uploading = false; state.errorMessage = e instanceof Error ? e.message : '选择异常'; resolve(false); } }); }