feat: 编辑页 SKU 图标默认值优化
This commit is contained in:
@@ -429,7 +429,6 @@ const ProductEditPage = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [savingSourceDescription, setSavingSourceDescription] = useState(false);
|
||||
const [materializingTransferId, setMaterializingTransferId] = useState<string | null>(null);
|
||||
const [dirty, setDirty] = useState(false);
|
||||
const [moreFeaturesExpanded, setMoreFeaturesExpanded] = useState(false);
|
||||
const [usesVariantPackageWeight, setUsesVariantPackageWeight] = useState(false);
|
||||
@@ -1268,31 +1267,6 @@ const ProductEditPage = () => {
|
||||
const cursor = dictionaryCursors[attributeId];
|
||||
if (cursor) void loadDictionaryValues(attributeId, query, cursor);
|
||||
};
|
||||
/** 把采集素材转存为可引用资产;完成后刷新采集证据,让对应 SKU 的图片立即可选。 */
|
||||
const materializeCandidate = async (transferId: string) => {
|
||||
if (!snapshot) return;
|
||||
setMaterializingTransferId(transferId);
|
||||
try {
|
||||
const queued = await collectionApi.materializeAssets(snapshot.collection_item_id, {
|
||||
transfer_ids: [transferId],
|
||||
});
|
||||
const completed = await waitForCatalogOperation(queued.operation.id);
|
||||
const refreshed = await collectionApi.inspection(snapshot.collection_item_id);
|
||||
setCollectionRecord(refreshed);
|
||||
if (completed?.status === 'succeeded') {
|
||||
message.success('素材已转存,可选择并随草稿保存');
|
||||
} else if (completed?.status === 'failed') {
|
||||
message.error(completed.error?.message ?? '素材转存失败,请稍后重试');
|
||||
} else {
|
||||
message.info('素材正在转存,稍后重新展开此 SKU 即可选择');
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(apiErrorMessage(error));
|
||||
} finally {
|
||||
setMaterializingTransferId(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (!currentPlatform) return <Alert type="warning" message="正在读取平台上下文" />;
|
||||
if (loading && !snapshot)
|
||||
return (
|
||||
@@ -1449,18 +1423,15 @@ const ProductEditPage = () => {
|
||||
form={form}
|
||||
snapshot={snapshot}
|
||||
collectionRecord={collectionRecord}
|
||||
materializingTransferId={materializingTransferId}
|
||||
activeAttributeSchema={activeAttributeSchema}
|
||||
catalogSyncing={null}
|
||||
dictionaryValues={dictionaryValues}
|
||||
onMaterialize={materializeCandidate}
|
||||
onLoadDictionaryValues={(dictionaryId, query) =>
|
||||
void loadDictionaryValues(dictionaryId, query)
|
||||
}
|
||||
onSyncDictionaryValues={syncDictionaryValues}
|
||||
onOpenDictionaryValues={openDictionaryValues}
|
||||
onLoadMoreDictionaryValues={loadMoreDictionaryValues}
|
||||
onDirty={() => setDirty(true)}
|
||||
usesVariantPackageWeight={usesVariantPackageWeight}
|
||||
usesVariantPackageDimensions={usesVariantPackageDimensions}
|
||||
usesVariantTags={usesVariantTags}
|
||||
|
||||
@@ -240,15 +240,6 @@
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.sku-media-item .ant-btn {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
bottom: 4px;
|
||||
z-index: 1;
|
||||
}
|
||||
.sku-media-item.selected {
|
||||
border: 2px solid #7c3aed;
|
||||
}
|
||||
.sku-media-item .ant-image,
|
||||
.sku-media-item img {
|
||||
width: 100%;
|
||||
|
||||
@@ -117,16 +117,13 @@ type Props = {
|
||||
form: FormInstance<EditorFormValues>;
|
||||
snapshot: ProductEditorSnapshot;
|
||||
collectionRecord: CollectionItemInspection | null;
|
||||
materializingTransferId: string | null;
|
||||
activeAttributeSchema: ProductAttributeSchema[];
|
||||
catalogSyncing: string | null;
|
||||
dictionaryValues: Record<string, ProductAttributeDictionaryValue[]>;
|
||||
onMaterialize: (transferId: string) => void;
|
||||
onLoadDictionaryValues: (dictionaryId: string, query: string) => void;
|
||||
onSyncDictionaryValues: (dictionaryId: string, query: string) => void;
|
||||
onOpenDictionaryValues: (dictionaryId: string) => void;
|
||||
onLoadMoreDictionaryValues: (dictionaryId: string, query: string) => void;
|
||||
onDirty: () => void;
|
||||
usesVariantPackageWeight: boolean;
|
||||
usesVariantPackageDimensions: boolean;
|
||||
usesVariantTags: boolean;
|
||||
@@ -138,25 +135,21 @@ type Props = {
|
||||
|
||||
/**
|
||||
* 「03 变体设置」区块:SKU 卡片(名称/主图/货号/价格摘要)与展开后的
|
||||
* 采集图片选择、SKU 包装覆盖与类目变体属性编辑。
|
||||
* 已选图片、SKU 包装覆盖与类目变体属性编辑。
|
||||
*
|
||||
* 采集证据(collectionRecord)与素材转存由页面处理;本组件维护卡片
|
||||
* 展开状态与图片选择交互。
|
||||
* 采集证据仅用于补齐已选图片的预览地址;采集候选图片留待选择弹窗展示。
|
||||
*/
|
||||
export function ProductVariantSection({
|
||||
form,
|
||||
snapshot,
|
||||
collectionRecord,
|
||||
materializingTransferId,
|
||||
activeAttributeSchema,
|
||||
catalogSyncing,
|
||||
dictionaryValues,
|
||||
onMaterialize,
|
||||
onLoadDictionaryValues,
|
||||
onSyncDictionaryValues,
|
||||
onOpenDictionaryValues,
|
||||
onLoadMoreDictionaryValues,
|
||||
onDirty,
|
||||
usesVariantPackageWeight,
|
||||
usesVariantPackageDimensions,
|
||||
usesVariantTags,
|
||||
@@ -165,7 +158,7 @@ export function ProductVariantSection({
|
||||
onAiCopy,
|
||||
onCompareCopy,
|
||||
}: Props) {
|
||||
// 实时监听变体草稿:驱动主图回显与候选图片的选中态。
|
||||
// 实时监听变体草稿,驱动已选图片和主图回显。
|
||||
const draftVariants = (Form.useWatch('variants', form) ?? []) as VariantValues[];
|
||||
// 展开状态按 Form.List 的 field.key(而非下标)记录,增删/复制 SKU 后不会错位。
|
||||
const [expandedVariantKeys, setExpandedVariantKeys] = useState<Set<number>>(() => new Set());
|
||||
@@ -245,28 +238,19 @@ export function ProductVariantSection({
|
||||
const selectedAssetIds =
|
||||
draftVariants[field.name]?.media_asset_ids ?? [];
|
||||
const selectedImages = selectedAssetIds
|
||||
.map(
|
||||
(assetId) =>
|
||||
.map((assetId) => ({
|
||||
assetId,
|
||||
url:
|
||||
imageCandidates.find(
|
||||
(candidate) => candidate.asset_id === assetId
|
||||
)?.source_url ??
|
||||
original?.media.find(
|
||||
(media) => media.asset_id === assetId
|
||||
)?.source_url
|
||||
)
|
||||
.filter((url): url is string => !!url);
|
||||
// 选中/取消候选图片;setFieldValue 不触发 Form 的
|
||||
// onValuesChange,因此需通过 onDirty 手动标记未保存。
|
||||
const toggleImage = (assetId: string) => {
|
||||
const next = selectedAssetIds.includes(assetId)
|
||||
? selectedAssetIds.filter((id) => id !== assetId)
|
||||
: [...selectedAssetIds, assetId];
|
||||
form.setFieldValue(
|
||||
['variants', field.name, 'media_asset_ids'],
|
||||
next
|
||||
)?.source_url,
|
||||
}))
|
||||
.filter((image): image is { assetId: string; url: string } =>
|
||||
Boolean(image.url)
|
||||
);
|
||||
onDirty();
|
||||
};
|
||||
const copyVariant = () =>
|
||||
add({
|
||||
...variantValue,
|
||||
@@ -311,7 +295,7 @@ export function ProductVariantSection({
|
||||
{selectedImages[0] ? (
|
||||
<Image
|
||||
preview={false}
|
||||
src={selectedImages[0]}
|
||||
src={selectedImages[0].url}
|
||||
alt="SKU 主图"
|
||||
/>
|
||||
) : (
|
||||
@@ -441,106 +425,28 @@ export function ProductVariantSection({
|
||||
<Typography.Title level={5}>
|
||||
SKU 图片
|
||||
</Typography.Title>
|
||||
{imageCandidates.length ? (
|
||||
<div className="sku-media-grid">
|
||||
{imageCandidates.map(
|
||||
(candidate, imageIndex) => {
|
||||
// 仅"转存成功"的候选可直接勾选;待转存图片仅保留预览,
|
||||
// 避免在此处重复提供后续素材选择流程的入口。
|
||||
const selectable =
|
||||
candidate.status ===
|
||||
'succeeded' &&
|
||||
!!candidate.asset_id;
|
||||
const selected =
|
||||
selectable &&
|
||||
selectedAssetIds.includes(
|
||||
candidate.asset_id!
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={`sku-media-item ${imageIndex === 0 ? 'sku-media-item--main' : ''} ${selected ? 'selected' : ''}`}
|
||||
key={candidate.id}
|
||||
>
|
||||
{imageIndex === 0 && (
|
||||
<span className="sku-media-main-badge">
|
||||
主图
|
||||
</span>
|
||||
)}
|
||||
<Image
|
||||
src={
|
||||
candidate.source_url
|
||||
}
|
||||
alt="采集候选图片"
|
||||
/>
|
||||
{selectable ? (
|
||||
<Button
|
||||
size="small"
|
||||
type={
|
||||
selected
|
||||
? 'primary'
|
||||
: 'default'
|
||||
}
|
||||
onClick={() =>
|
||||
toggleImage(
|
||||
candidate.asset_id!
|
||||
)
|
||||
}
|
||||
>
|
||||
{selected
|
||||
? '已选择'
|
||||
: '选择'}
|
||||
</Button>
|
||||
) : candidate.status ===
|
||||
'pending_selection' ? null : (
|
||||
<Button
|
||||
size="small"
|
||||
loading={
|
||||
materializingTransferId ===
|
||||
candidate.id
|
||||
}
|
||||
disabled={
|
||||
candidate.status ===
|
||||
'queued' ||
|
||||
candidate.status ===
|
||||
'running' ||
|
||||
candidate.status ===
|
||||
'failed'
|
||||
}
|
||||
onClick={() =>
|
||||
void onMaterialize(
|
||||
candidate.id
|
||||
)
|
||||
}
|
||||
>
|
||||
{candidate.status ===
|
||||
'failed'
|
||||
? '转存失败'
|
||||
: '转存中'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)}
|
||||
{/* 仅提供素材选择入口的视觉占位,实际选择流程后续接入。 */}
|
||||
<Button
|
||||
className="sku-media-picker"
|
||||
type="dashed"
|
||||
<div className="sku-media-grid">
|
||||
{selectedImages.map((image, imageIndex) => (
|
||||
<div
|
||||
className={`sku-media-item ${imageIndex === 0 ? 'sku-media-item--main' : ''}`}
|
||||
key={image.assetId}
|
||||
>
|
||||
选择图片
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="sku-media-grid">
|
||||
{/* 没有采集图片时仍保留选择素材的入口。 */}
|
||||
<Button
|
||||
className="sku-media-picker sku-media-picker--empty"
|
||||
type="dashed"
|
||||
>
|
||||
选择图片
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{imageIndex === 0 && (
|
||||
<span className="sku-media-main-badge">
|
||||
主图
|
||||
</span>
|
||||
)}
|
||||
<Image src={image.url} alt="已选 SKU 图片" />
|
||||
</div>
|
||||
))}
|
||||
{/* 图片选择交互将在后续接入。 */}
|
||||
<Button
|
||||
className={`sku-media-picker ${selectedImages.length ? '' : 'sku-media-picker--empty'}`}
|
||||
type="dashed"
|
||||
>
|
||||
选择图片
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{(usesVariantPackageWeight ||
|
||||
usesVariantPackageDimensions ||
|
||||
|
||||
Reference in New Issue
Block a user