fix(canvas): 画布同步 - 素材缺失时可强制覆盖并恢复文本与连线顺序
云端缺少素材时可用本地版强制覆盖,只清失效媒体引用。文本节点不再把资源地址当正文,合并时保留引用槽位顺序。
This commit is contained in:
@@ -17,6 +17,7 @@ export function CanvasSyncStatus({ projectId, onLoadLatest, onOpenVersions }: {
|
||||
const localPhase = progress?.localPhase;
|
||||
const localError = localPhase === "error";
|
||||
const localPending = localPhase === "pending";
|
||||
const missingResources = progress?.errorKind === "resources";
|
||||
const retryScheduled = cloudError && progress?.message?.includes("自动重试");
|
||||
const saving = phase === "pending" || phase === "saving" || phase === "uploading";
|
||||
const reconciling = phase === "reconciling";
|
||||
@@ -41,7 +42,9 @@ export function CanvasSyncStatus({ projectId, onLoadLatest, onOpenVersions }: {
|
||||
const description = conflict
|
||||
? progress?.message || "本地与云端存在冲突,当前编辑内容保持不变。"
|
||||
: cloudError
|
||||
? progress?.message || "本地内容仍然保留,云端暂未确认。"
|
||||
? missingResources
|
||||
? "检测到云端缺失素材;可以先保留原始草稿,再用本地版强制覆盖并移除失效素材引用。"
|
||||
: progress?.message || "本地内容仍然保留,云端暂未确认。"
|
||||
: localError
|
||||
? progress?.localError || "本地保存没有完成,请立即重试;当前编辑内容保持不变。"
|
||||
: reconciling
|
||||
@@ -140,7 +143,13 @@ export function CanvasSyncStatus({ projectId, onLoadLatest, onOpenVersions }: {
|
||||
) : cloudError ? (
|
||||
<div className="canvas-sync-panel__callout">
|
||||
<strong>{retryScheduled ? "本地内容仍在,正在等待重试" : "本地内容仍在,未覆盖云端"}</strong>
|
||||
<span>{retryScheduled ? "网络恢复后会自动重试;也可以点击“立即重试”。" : "请处理上面的错误后点击“立即重试”;在此之前本地内容不会被覆盖。"}</span>
|
||||
<span>
|
||||
{missingResources
|
||||
? "可点击“用本地版强制覆盖云端”;操作前会自动保留本地草稿,失效图片节点会保留布局和提示词。"
|
||||
: retryScheduled
|
||||
? "网络恢复后会自动重试;也可以点击“立即重试”。"
|
||||
: "请处理上面的错误后点击“立即重试”;在此之前本地内容不会被覆盖。"}
|
||||
</span>
|
||||
</div>
|
||||
) : localError ? (
|
||||
<div className="canvas-sync-panel__callout">
|
||||
@@ -150,19 +159,39 @@ export function CanvasSyncStatus({ projectId, onLoadLatest, onOpenVersions }: {
|
||||
) : null}
|
||||
<div className="canvas-sync-panel__actions">
|
||||
{cloudError ? (
|
||||
<Button
|
||||
type="primary"
|
||||
block
|
||||
icon={<RefreshCw className="size-3.5" />}
|
||||
loading={busy}
|
||||
onClick={() =>
|
||||
void run(() => retryRemoteUserDataSync(projectId))
|
||||
.then(() => setStatusOpen(false))
|
||||
.catch(() => undefined)
|
||||
}
|
||||
>
|
||||
立即重试云端同步
|
||||
</Button>
|
||||
<>
|
||||
{!missingResources ? (
|
||||
<Button
|
||||
type="primary"
|
||||
block
|
||||
icon={<RefreshCw className="size-3.5" />}
|
||||
loading={busy}
|
||||
onClick={() =>
|
||||
void run(() => retryRemoteUserDataSync(projectId))
|
||||
.then(() => setStatusOpen(false))
|
||||
.catch(() => undefined)
|
||||
}
|
||||
>
|
||||
立即重试云端同步
|
||||
</Button>
|
||||
) : null}
|
||||
{missingResources ? (
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
block
|
||||
icon={<UploadCloud className="size-3.5" />}
|
||||
loading={busy}
|
||||
onClick={() =>
|
||||
void run(() => overwriteRemoteCanvasProject(projectId))
|
||||
.then(() => setStatusOpen(false))
|
||||
.catch(() => undefined)
|
||||
}
|
||||
>
|
||||
修复并强制覆盖云端
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
) : localError ? (
|
||||
<Button
|
||||
type="primary"
|
||||
|
||||
@@ -1774,7 +1774,9 @@ function CanvasViewportPage() {
|
||||
|
||||
try {
|
||||
const content = node.metadata?.content?.trim();
|
||||
const resourceId = resourceIdFromStorageKey(node.metadata?.storageKey);
|
||||
const resourceId = [CanvasNodeType.Image, CanvasNodeType.Video, CanvasNodeType.Audio].includes(node.type as CanvasNodeType)
|
||||
? resourceIdFromStorageKey(node.metadata?.storageKey)
|
||||
: "";
|
||||
// Resource-backed media must use the central access contract. This keeps
|
||||
// copy operations on the configured CDN/OSS URL instead of copying the
|
||||
// platform file endpoint or a stale URL persisted in canvas metadata.
|
||||
|
||||
@@ -394,7 +394,7 @@ export function useCanvasProjectLifecycle({
|
||||
try { await persistLocalEdits(); } catch { message.error("本地保存失败,请重试"); return false; }
|
||||
try {
|
||||
const result = await forceOverwriteRemoteCanvasSync(projectId);
|
||||
message.success(result.reboundNodes > 0 ? `已保存,并修复 ${result.reboundNodes} 处媒体与素材的绑定` : "素材关联已核对,画布已保存");
|
||||
message.success(result.clearedResources > 0 ? `已强制保存,移除了 ${result.clearedResources} 个失效素材引用;本地草稿已保留` : result.reboundNodes > 0 ? `已保存,并修复 ${result.reboundNodes} 处媒体与素材的绑定` : "素材关联已核对,画布已保存");
|
||||
} catch (error) {
|
||||
message.error(`修复并保存失败:${error instanceof Error ? error.message : "未知错误"}`);
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user