Merge pull request 'Feat/ecommerce scenario tabs' (#27) from feat/ecommerce-scenario-tabs into main
Reviewed-on: #27
This commit was merged in pull request #27.
This commit is contained in:
@@ -1559,6 +1559,42 @@ const blobToDataUrl = (blob: Blob): Promise<string> =>
|
|||||||
reader.readAsDataURL(blob);
|
reader.readAsDataURL(blob);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function createLocalImageItems(files: File[], limit: number, prefix: string): CloneImageItem[] {
|
||||||
|
const selectedFiles = Array.from(files).slice(0, limit);
|
||||||
|
const stamp = Date.now();
|
||||||
|
return selectedFiles.map((file, index) => {
|
||||||
|
const localPreviewUrl = URL.createObjectURL(file);
|
||||||
|
const mimeType = normalizeEcommerceImageMime(file.type);
|
||||||
|
return {
|
||||||
|
id: `${prefix}-${stamp}-${index}`,
|
||||||
|
src: localPreviewUrl,
|
||||||
|
name: file.name,
|
||||||
|
file,
|
||||||
|
format: getImageFileFormat(file),
|
||||||
|
mimeType,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadImageItem(item: CloneImageItem): Promise<{ src?: string; ossKey?: string; width?: number; height?: number }> {
|
||||||
|
if (!item.file) return {};
|
||||||
|
const mimeType = normalizeEcommerceImageMime(item.file.type);
|
||||||
|
try {
|
||||||
|
const uploadBlob = item.file.type === mimeType ? item.file : new Blob([item.file], { type: mimeType });
|
||||||
|
const [uploaded, dimensions] = await Promise.all([
|
||||||
|
aiGenerationClient.uploadAssetBinary(uploadBlob, {
|
||||||
|
name: item.file.name,
|
||||||
|
mimeType,
|
||||||
|
scope: ecommerceOssScopes.productSource,
|
||||||
|
}),
|
||||||
|
readImageDimensions(item.src).catch(() => ({})),
|
||||||
|
]);
|
||||||
|
return { src: uploaded.url, ossKey: uploaded.ossKey, ...dimensions };
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function createUploadedImageItems(files: File[], limit: number, prefix: string): Promise<CloneImageItem[]> {
|
async function createUploadedImageItems(files: File[], limit: number, prefix: string): Promise<CloneImageItem[]> {
|
||||||
const selectedFiles = Array.from(files).slice(0, limit);
|
const selectedFiles = Array.from(files).slice(0, limit);
|
||||||
const stamp = Date.now();
|
const stamp = Date.now();
|
||||||
@@ -2553,20 +2589,30 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const addSetImages = async (files: File[]) => {
|
const addSetImages = (files: File[]) => {
|
||||||
if (setImages.length >= 3) return;
|
if (setImages.length >= 3) return;
|
||||||
const imageFiles = notifyRejectedImages(files);
|
const imageFiles = notifyRejectedImages(files);
|
||||||
if (!imageFiles.length) return;
|
if (!imageFiles.length) return;
|
||||||
try {
|
const remainingSlots = 3 - setImages.length;
|
||||||
const nextImages = await createUploadedImageItems(imageFiles, 3 - setImages.length, "set");
|
if (remainingSlots <= 0) return;
|
||||||
setSetImages((current) => {
|
|
||||||
if (current.length >= 3) return current;
|
const localItems = createLocalImageItems(imageFiles, remainingSlots, "set");
|
||||||
return nextImages.length ? [...current, ...nextImages].slice(0, 3) : current;
|
setSetImages((current) => [...current, ...localItems].slice(0, 3));
|
||||||
});
|
|
||||||
setProductSetStatus("ready");
|
setProductSetStatus("ready");
|
||||||
} catch (err) {
|
|
||||||
toast.error(err instanceof Error ? err.message : "商品图上传失败");
|
Promise.all(localItems.map(async (item) => {
|
||||||
}
|
const uploaded = await uploadImageItem(item);
|
||||||
|
if (uploaded.src) URL.revokeObjectURL(item.src);
|
||||||
|
return { id: item.id, uploaded };
|
||||||
|
})).then((results) => {
|
||||||
|
const updateMap = new Map(results.map((result) => [result.id, result.uploaded]));
|
||||||
|
setSetImages((current) => current.map((item) => {
|
||||||
|
const update = updateMap.get(item.id);
|
||||||
|
return update ? { ...item, ...update } : item;
|
||||||
|
}));
|
||||||
|
}).catch(() => {
|
||||||
|
toast.error("套图后台上传失败,请检查网络后重试");
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetUpload = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleSetUpload = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -3651,20 +3697,33 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const addProductImages = async (files: File[]) => {
|
const addProductImages = (files: File[]) => {
|
||||||
const imageFiles = notifyRejectedImages(files);
|
const imageFiles = notifyRejectedImages(files);
|
||||||
if (!imageFiles.length) return;
|
if (!imageFiles.length) return;
|
||||||
try {
|
const remainingSlots = maxCloneProductImages - productImages.length;
|
||||||
const nextImages = await createUploadedImageItems(imageFiles, maxCloneProductImages - productImages.length, "product");
|
if (remainingSlots <= 0) return;
|
||||||
setProductImages((current) => {
|
|
||||||
if (current.length >= maxCloneProductImages) return current;
|
const localItems = createLocalImageItems(imageFiles, remainingSlots, "product");
|
||||||
return nextImages.length ? [...current, ...nextImages].slice(0, maxCloneProductImages) : current;
|
setProductImages((current) => [...current, ...localItems].slice(0, maxCloneProductImages));
|
||||||
});
|
|
||||||
setStatus("ready");
|
setStatus("ready");
|
||||||
setResults([]);
|
setResults([]);
|
||||||
} catch (err) {
|
|
||||||
toast.error(err instanceof Error ? err.message : "商品图上传失败");
|
Promise.all(localItems.map(async (item) => {
|
||||||
|
const uploaded = await uploadImageItem(item);
|
||||||
|
if (uploaded.src) {
|
||||||
|
URL.revokeObjectURL(item.src);
|
||||||
}
|
}
|
||||||
|
return { id: item.id, uploaded };
|
||||||
|
})).then((results) => {
|
||||||
|
const updateMap = new Map(results.map((result) => [result.id, result.uploaded]));
|
||||||
|
setProductImages((current) => current.map((item) => {
|
||||||
|
const update = updateMap.get(item.id);
|
||||||
|
if (!update) return item;
|
||||||
|
return { ...item, ...update };
|
||||||
|
}));
|
||||||
|
}).catch(() => {
|
||||||
|
toast.error("商品图后台上传失败,请检查网络后重试");
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProductUpload = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleProductUpload = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -3718,22 +3777,29 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const addCloneReferenceImages = async (files: File[]) => {
|
const addCloneReferenceImages = (files: File[]) => {
|
||||||
const imageFiles = notifyRejectedImages(files);
|
const imageFiles = notifyRejectedImages(files);
|
||||||
if (!imageFiles.length) return;
|
if (!imageFiles.length) return;
|
||||||
const remainingSlots = maxCloneReferenceImages - cloneReferenceImages.length;
|
const remainingSlots = maxCloneReferenceImages - cloneReferenceImages.length;
|
||||||
if (remainingSlots <= 0) return;
|
if (remainingSlots <= 0) return;
|
||||||
try {
|
|
||||||
const nextImages = await createUploadedImageItems(imageFiles, remainingSlots, "reference");
|
const localItems = createLocalImageItems(imageFiles, remainingSlots, "reference");
|
||||||
if (!nextImages.length) return;
|
setCloneReferenceImages((current) => [...current, ...localItems].slice(0, maxCloneReferenceImages));
|
||||||
setCloneReferenceImages((current) => {
|
hydrateCloneReferenceImageMeta(localItems);
|
||||||
if (current.length >= maxCloneReferenceImages) return current;
|
|
||||||
return nextImages.length ? [...current, ...nextImages].slice(0, maxCloneReferenceImages) : current;
|
Promise.all(localItems.map(async (item) => {
|
||||||
|
const uploaded = await uploadImageItem(item);
|
||||||
|
if (uploaded.src) URL.revokeObjectURL(item.src);
|
||||||
|
return { id: item.id, uploaded };
|
||||||
|
})).then((results) => {
|
||||||
|
const updateMap = new Map(results.map((result) => [result.id, result.uploaded]));
|
||||||
|
setCloneReferenceImages((current) => current.map((item) => {
|
||||||
|
const update = updateMap.get(item.id);
|
||||||
|
return update ? { ...item, ...update } : item;
|
||||||
|
}));
|
||||||
|
}).catch(() => {
|
||||||
|
toast.error("参考图后台上传失败,请检查网络后重试");
|
||||||
});
|
});
|
||||||
hydrateCloneReferenceImageMeta(nextImages);
|
|
||||||
} catch (err) {
|
|
||||||
toast.error(err instanceof Error ? err.message : "参考图上传失败");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeCloneReferenceImage = (imageId: string) => {
|
const removeCloneReferenceImage = (imageId: string) => {
|
||||||
@@ -4189,23 +4255,38 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
};
|
};
|
||||||
}, [openCloneModelSelect]);
|
}, [openCloneModelSelect]);
|
||||||
|
|
||||||
|
const addGarmentImages = (files: File[]) => {
|
||||||
|
const uploadedFiles = notifyRejectedImages(files);
|
||||||
|
if (!uploadedFiles.length) return;
|
||||||
|
const remainingSlots = 5 - garmentImages.length;
|
||||||
|
if (remainingSlots <= 0) return;
|
||||||
|
|
||||||
|
const localItems = createLocalImageItems(uploadedFiles, remainingSlots, "garment");
|
||||||
|
setGarmentImages((current) => [...current, ...localItems].slice(0, 5));
|
||||||
|
setTryOnStatus("ready");
|
||||||
|
|
||||||
|
Promise.all(localItems.map(async (item) => {
|
||||||
|
const uploaded = await uploadImageItem(item);
|
||||||
|
if (uploaded.src) URL.revokeObjectURL(item.src);
|
||||||
|
return { id: item.id, uploaded };
|
||||||
|
})).then((results) => {
|
||||||
|
const updateMap = new Map(results.map((result) => [result.id, result.uploaded]));
|
||||||
|
setGarmentImages((current) => current.map((item) => {
|
||||||
|
const update = updateMap.get(item.id);
|
||||||
|
return update ? { ...item, ...update } : item;
|
||||||
|
}));
|
||||||
|
}).catch(() => {
|
||||||
|
toast.error("服饰图后台上传失败,请检查网络后重试");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleGarmentUpload = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleGarmentUpload = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
const files = event.target.files;
|
const files = event.target.files;
|
||||||
if (!files?.length) return;
|
if (!files?.length) {
|
||||||
const uploadedFiles = notifyRejectedImages(Array.from(files));
|
|
||||||
if (!uploadedFiles.length) {
|
|
||||||
event.target.value = "";
|
event.target.value = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void (async () => {
|
addGarmentImages(Array.from(files));
|
||||||
try {
|
|
||||||
const nextImages = await createUploadedImageItems(uploadedFiles, 5 - garmentImages.length, "garment");
|
|
||||||
setGarmentImages((current) => [...current, ...nextImages].slice(0, 5));
|
|
||||||
setTryOnStatus("ready");
|
|
||||||
} catch (err) {
|
|
||||||
toast.error(err instanceof Error ? err.message : "服饰图上传失败");
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
event.target.value = "";
|
event.target.value = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4241,20 +4322,30 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
reader.readAsDataURL(blob);
|
reader.readAsDataURL(blob);
|
||||||
});
|
});
|
||||||
|
|
||||||
const addDetailImages = async (files: File[]) => {
|
const addDetailImages = (files: File[]) => {
|
||||||
const uploadedFiles = notifyRejectedImages(files);
|
const uploadedFiles = notifyRejectedImages(files);
|
||||||
if (!uploadedFiles.length) return;
|
if (!uploadedFiles.length) return;
|
||||||
try {
|
const remainingSlots = 3 - detailProductImages.length;
|
||||||
const nextImages = await createUploadedImageItems(uploadedFiles, 3 - detailProductImages.length, "detail");
|
if (remainingSlots <= 0) return;
|
||||||
setDetailProductImages((current) => {
|
|
||||||
if (current.length >= 3) return current;
|
const localItems = createLocalImageItems(uploadedFiles, remainingSlots, "detail");
|
||||||
return nextImages.length ? [...current, ...nextImages].slice(0, 3) : current;
|
setDetailProductImages((current) => [...current, ...localItems].slice(0, 3));
|
||||||
});
|
|
||||||
setDetailStatus("ready");
|
setDetailStatus("ready");
|
||||||
setDetailResultUrl(null);
|
setDetailResultUrl(null);
|
||||||
} catch (err) {
|
|
||||||
toast.error(err instanceof Error ? err.message : "详情图上传失败");
|
Promise.all(localItems.map(async (item) => {
|
||||||
}
|
const uploaded = await uploadImageItem(item);
|
||||||
|
if (uploaded.src) URL.revokeObjectURL(item.src);
|
||||||
|
return { id: item.id, uploaded };
|
||||||
|
})).then((results) => {
|
||||||
|
const updateMap = new Map(results.map((result) => [result.id, result.uploaded]));
|
||||||
|
setDetailProductImages((current) => current.map((item) => {
|
||||||
|
const update = updateMap.get(item.id);
|
||||||
|
return update ? { ...item, ...update } : item;
|
||||||
|
}));
|
||||||
|
}).catch(() => {
|
||||||
|
toast.error("详情图后台上传失败,请检查网络后重试");
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadCloneImages = async (images: CloneImageItem[]): Promise<string[]> => {
|
const uploadCloneImages = async (images: CloneImageItem[]): Promise<string[]> => {
|
||||||
|
|||||||
Reference in New Issue
Block a user