feat(edit): 采集插件、编辑页SKU 属性动态展示、颜色展示开发

This commit is contained in:
R524809
2026-09-22 10:11:57 +08:00
parent 05747d6f22
commit 2b06ef0692
12 changed files with 390 additions and 11 deletions
@@ -416,6 +416,7 @@ const ProductEditPage = () => {
const [catalogRefresh, setCatalogRefresh] = useState(0);
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);
@@ -849,6 +850,68 @@ const ProductEditPage = () => {
});
setDirty(true);
};
/** 独立持久化采集描述,避免要求页面上其他未完成字段一并通过整页校验。 */
const saveSourceDescription = async (description: string) => {
if (!productId || !currentPlatform || !snapshot) return;
setSavingSourceDescription(true);
try {
const saved = await productEditorApi.saveSourceDescription(
productId,
currentPlatform.id,
{ product: snapshot.product.revision, profile: snapshot.profile.revision },
{
description,
source_snapshot_revision:
(sourceDraft ?? snapshot.source_material).source_snapshot_revision,
}
);
setSnapshot((current) =>
current
? {
...current,
product: {
...current.product,
revision: saved.product_revision,
},
profile: {
...current.profile,
revision: saved.profile_revision,
},
source_material: saved.source_material,
source_material_state: saved.source_material_state,
ai_input: {
...current.ai_input,
source_material: saved.source_material,
},
}
: current
);
setSourceDraft((current) =>
current
? {
...current,
description: saved.source_material.description,
source_snapshot_revision:
saved.source_material.source_snapshot_revision,
}
: saved.source_material
);
setSourceOverriddenFields((current) => {
const next = new Set(current);
if (saved.source_material_state.overridden_fields.includes('description')) {
next.add('description');
} else {
next.delete('description');
}
return [...next];
});
message.success('商品描述已保存');
} catch (error) {
message.error(`商品描述保存失败:${apiErrorMessage(error)}`);
} finally {
setSavingSourceDescription(false);
}
};
const restoreSourceField = (field: Extract<ProductAiSourceField, 'title' | 'description'>) => {
if (!snapshot) return;
setSourceDraft({
@@ -1130,6 +1193,8 @@ const ProductEditPage = () => {
overriddenFields={sourceOverriddenFields}
onRestore={restoreSourceField}
onUpdate={updateSourceField}
savingDescription={savingSourceDescription}
onSaveDescription={(description) => void saveSourceDescription(description)}
onShowDifferences={showSourceDifferences}
onAcceptLatest={acceptLatestSource}
/>
@@ -28,6 +28,8 @@ type Props = {
field: Extract<ProductAiSourceField, 'title' | 'description'>,
value: string
) => void;
savingDescription: boolean;
onSaveDescription: (value: string) => void;
onShowDifferences: () => void;
onAcceptLatest: () => void;
};
@@ -48,6 +50,8 @@ export function CollectedProductInfoPanel({
overriddenFields,
onRestore,
onUpdate,
savingDescription,
onSaveDescription,
onShowDifferences,
onAcceptLatest,
}: Props) {
@@ -137,14 +141,16 @@ export function CollectedProductInfoPanel({
<Input.TextArea
value={descriptionDraft}
rows={12}
disabled={savingDescription}
placeholder="本次采集未取得商品描述。可补充后用于 AI 生成。"
onChange={(event) => setDescriptionDraft(event.target.value)}
/>
<div className="collected-description-actions">
<Button
type="primary"
loading={savingDescription}
disabled={descriptionDraft === sourceDescription}
onClick={() => onUpdate('description', descriptionDraft)}
onClick={() => onSaveDescription(descriptionDraft)}
>
保存描述
</Button>