feat(edit): 编辑上报细节优化
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
import { PRODUCT_VARIANT_LIMIT } from './product-limits';
|
||||
|
||||
const uuid = z.uuid();
|
||||
|
||||
@@ -71,7 +72,7 @@ export const collectedProductPayloadSchema = z.object({
|
||||
selections: z.array(z.object({ axisKey: z.string().min(1).max(300), valueKey: z.string().min(1).max(300), valueName: z.string().min(1).max(300) })).max(50),
|
||||
sourcePrice: z.object({ amount: z.string().regex(/^\d+(\.\d{1,4})?$/), currencyCode: z.string().length(3).toUpperCase() }).optional(),
|
||||
confidence: z.enum(['verified', 'partial']), evidenceRefs: z.array(z.string().min(1).max(300)).max(100),
|
||||
}).strict()).max(100),
|
||||
}).strict()).max(PRODUCT_VARIANT_LIMIT, `单个商品最多支持 ${PRODUCT_VARIANT_LIMIT} 个 SKU`),
|
||||
assets: z.array(z.object({
|
||||
url: z.url(), mediaType: z.enum(['image', 'video']).default('image'), posterUrl: z.url().optional(), role: z.enum(['source_main', 'source_sku', 'source_detail']), variantSourceKey: z.string().min(1).max(500).optional(),
|
||||
relation: z.enum(['variant_direct', 'axis_value', 'product_candidate', 'inherited_product_default']), sortOrder: z.number().int().min(0).max(10_000).default(0), refererOrigin: z.url().optional(), evidenceRef: z.string().max(300).optional(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './collection';
|
||||
export * from './product-limits';
|
||||
export * from './auth';
|
||||
export * from './stores';
|
||||
export * from './watermarks';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
import type { Operation } from './stores.js';
|
||||
|
||||
const checksum = z
|
||||
.string()
|
||||
@@ -6,6 +7,7 @@ const checksum = z
|
||||
export const createMediaUploadIntentRequestSchema = z
|
||||
.object({
|
||||
purpose: z.enum(['watermark_image', 'product_image']),
|
||||
product_id: z.uuid().optional(),
|
||||
filename: z.string().trim().min(1).max(255),
|
||||
content_type: z.enum(['image/png', 'image/jpeg', 'image/webp']),
|
||||
byte_size: z
|
||||
@@ -19,7 +21,8 @@ export const createMediaUploadIntentRequestSchema = z
|
||||
.refine(
|
||||
(value) => value.purpose !== 'watermark_image' || value.byte_size <= 5 * 1024 * 1024,
|
||||
{ path: ['byte_size'], message: '水印图片不能超过 5 MiB' },
|
||||
);
|
||||
).refine((value) => value.purpose === 'product_image' ? Boolean(value.product_id) : !value.product_id,
|
||||
{ path: ['product_id'], message: '商品图片必须绑定商品,水印图片不能绑定商品' });
|
||||
export const completeMediaUploadIntentRequestSchema = z
|
||||
.object({
|
||||
checksum_sha256: checksum,
|
||||
@@ -32,6 +35,7 @@ export const createMediaAccessUrlRequestSchema = z
|
||||
.strict();
|
||||
export type CreateMediaAccessUrlRequest = z.infer<typeof createMediaAccessUrlRequestSchema>;
|
||||
export const importProductImageRequestSchema = z.object({
|
||||
product_id: z.uuid(),
|
||||
url: z.url().max(2048).refine((value) => {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
@@ -130,7 +134,7 @@ export type MediaAsset = {
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
/** 工作空间已上传的商品图片,预览地址过期后需重新读取。 */
|
||||
/** 当前商品已上传的图片,预览地址过期后需重新读取。 */
|
||||
export type ProductUploadImage = MediaAsset & {
|
||||
preview_url: string;
|
||||
preview_url_expires_at: string;
|
||||
@@ -195,3 +199,19 @@ export type MediaAccessUrl = {
|
||||
url: string;
|
||||
expires_at: string;
|
||||
};
|
||||
|
||||
|
||||
export const deleteProductImageRequestSchema = z.object({
|
||||
asset_id: z.uuid().optional(),
|
||||
collection_image_id: z.uuid().optional(),
|
||||
}).strict().refine((value) => Boolean(value.asset_id) !== Boolean(value.collection_image_id),
|
||||
{ message: '必须指定一张素材或采集图片' });
|
||||
export type DeleteProductImageRequest = z.infer<typeof deleteProductImageRequestSchema>;
|
||||
export type ProductImageDeletion = {
|
||||
operation: Operation;
|
||||
id: string;
|
||||
status: 'queued' | 'succeeded' | 'failed';
|
||||
asset_id: string | null;
|
||||
collection_image_id: string | null;
|
||||
error_code: string | null;
|
||||
};
|
||||
|
||||
@@ -74,12 +74,16 @@ export type ProductAiTask = {
|
||||
error_message: string | null;
|
||||
};
|
||||
|
||||
export const PRODUCT_AI_HINT_MAX_LENGTH = 1000;
|
||||
|
||||
export const requestProductAiTaskSchema = z
|
||||
.object({
|
||||
hint: z.string().trim().max(1000).optional(),
|
||||
hint: z.string().trim().max(PRODUCT_AI_HINT_MAX_LENGTH).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export type RequestProductAiTask = z.infer<typeof requestProductAiTaskSchema>;
|
||||
|
||||
export const productCopyTranslationSchema = z
|
||||
.object({
|
||||
variant_id: z.uuid().nullable(),
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
type PricingOption,
|
||||
} from './pricing';
|
||||
import { z } from 'zod';
|
||||
import { PRODUCT_VARIANT_LIMIT } from './product-limits';
|
||||
import {
|
||||
productCopyTranslationSchema,
|
||||
type ProductCopyTranslation,
|
||||
@@ -104,6 +105,18 @@ export type ProductAttributeSchema = {
|
||||
export type ProductCatalogCacheStatus =
|
||||
'fresh' | 'stale' | 'expired' | 'missing';
|
||||
|
||||
/** 商品颜色的多选例外由稳定属性 ID 识别,不依赖目录名称的语言或翻译。 */
|
||||
export function isOzonProductColorAttribute(definition: Pick<ProductAttributeSchema, 'platform_attribute_id'>) {
|
||||
return definition.platform_attribute_id === '10096';
|
||||
}
|
||||
|
||||
/** 编辑、保存和发布共用数量规则,避免颜色草稿可保存但发布时被单值校验拒绝。 */
|
||||
export function productAttributeValueRules(definition: Pick<ProductAttributeSchema, 'platform_attribute_id' | 'is_collection' | 'max_value_count'>) {
|
||||
return isOzonProductColorAttribute(definition)
|
||||
? { allowsMultipleValues: true, maxValueCount: 100 }
|
||||
: { allowsMultipleValues: definition.is_collection, maxValueCount: definition.max_value_count };
|
||||
}
|
||||
|
||||
export type ProductCatalogCacheMetadata = {
|
||||
status: ProductCatalogCacheStatus;
|
||||
last_success_at: string | null;
|
||||
@@ -292,6 +305,7 @@ export type ProductEditorSnapshot = {
|
||||
effective_package_spec: PackageSpec;
|
||||
listing: {
|
||||
id: string;
|
||||
store_id: string;
|
||||
offer_id: string | null;
|
||||
status: string;
|
||||
pricing_draft?: PricingParameters | null;
|
||||
@@ -630,7 +644,7 @@ export const saveProductEditorDraftRequestSchema = z
|
||||
}),
|
||||
)
|
||||
.min(1)
|
||||
.max(100)
|
||||
.max(PRODUCT_VARIANT_LIMIT, `单个商品最多支持 ${PRODUCT_VARIANT_LIMIT} 个 SKU`)
|
||||
.refine(
|
||||
(variants) => new Set(variants.map((v) => v.id ? `id:${v.id}` : `client:${v.client_key}`)).size === variants.length,
|
||||
'SKU 标识不能重复',
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/** 单次采集和商品完整草稿保存的 SKU 容量;首次发布批量限制独立校验。 */
|
||||
export const PRODUCT_VARIANT_LIMIT = 1_000;
|
||||
Reference in New Issue
Block a user