修复项目配置草稿原子保存逻辑

This commit is contained in:
Cheng Zhou
2026-05-11 11:03:06 +08:00
parent 8d061a1520
commit 20d45cfdfe
6 changed files with 333 additions and 59 deletions
+33 -28
View File
@@ -2672,6 +2672,7 @@ const setupWorkflowTagMeta = computed<SetupWorkflowTagMeta>(() => SETUP_WORKFLOW
const setupWorkflowTagLabel = computed(() => setupWorkflowTagMeta.value.label);
const setupWorkflowTagType = computed<"success" | "warning" | "info">(() => setupWorkflowTagMeta.value.type);
const isSetupDraftEffectivelyEmpty = (draft: SetupConfigDraft): boolean => {
if (!isProjectPublishSnapshotEmpty(draft.projectInfo)) return false;
if (draft.projectMilestones.length > 0) return false;
if (draft.siteMilestones.length > 0) return false;
if (draft.siteEnrollmentPlans.length > 0) return false;
@@ -2686,6 +2687,8 @@ const isSetupDraftEffectivelyEmpty = (draft: SetupConfigDraft): boolean => {
if ((plan.stageBreakdown || "").trim()) return false;
return true;
};
const isAtomicSetupDraft = (draft: SetupConfigDraft): boolean =>
!isProjectPublishSnapshotEmpty(draft.projectInfo) || isSetupDraftEffectivelyEmpty(draft);
const shouldShowEmptyDraftBaseInfo = computed(() => isSetupDraftEffectivelyEmpty(setupDraft));
const setupDraftBaseVersionText = computed(() => {
const explicit = (setupActiveDraftBaseVersion.value || "").trim();
@@ -3988,7 +3991,7 @@ const isSetupDraftShape = (value: unknown): value is SetupConfigDraft => {
if (!value || typeof value !== "object") return false;
const data = value as Record<string, unknown>;
return (
(!Object.prototype.hasOwnProperty.call(data, "projectInfo") || isProjectPublishSnapshotShape(data.projectInfo)) &&
isProjectPublishSnapshotShape(data.projectInfo) &&
Array.isArray(data.projectMilestones) &&
!!data.enrollmentPlan &&
Array.isArray(data.siteMilestones) &&
@@ -4009,8 +4012,9 @@ const isProjectPublishSnapshotShape = (value: unknown): value is ProjectPublishS
);
};
const isProjectPublishSnapshotEmpty = (snapshot: ProjectPublishSnapshot): boolean =>
JSON.stringify(snapshot) === JSON.stringify(emptyProjectInfoDraft());
function isProjectPublishSnapshotEmpty(snapshot: ProjectPublishSnapshot): boolean {
return JSON.stringify(snapshot) === JSON.stringify(emptyProjectInfoDraft());
}
const applyProjectInfoDraft = (snapshot: ProjectPublishSnapshot) => {
suppressEnrollmentFieldSync.value = true;
@@ -4047,11 +4051,7 @@ const applySetupDraft = (draft: SetupConfigDraft) => {
setupDraft.siteEnrollmentPlans = normalizeSiteEnrollmentPlans(clone(draft.siteEnrollmentPlans));
setupDraft.monitoringStrategies = clone(draft.monitoringStrategies);
setupDraft.centerConfirm = clone(draft.centerConfirm);
if (!isProjectPublishSnapshotEmpty(projectInfo) || isSetupDraftEffectivelyEmpty(setupDraft)) {
applyProjectInfoDraft(projectInfo);
} else {
setupDraft.projectInfo = buildProjectPublishSnapshot();
}
applyProjectInfoDraft(projectInfo);
formBaselineSnapshot.value = serializeFormForCompare();
suppressDraftWatch.value = false;
};
@@ -4160,6 +4160,10 @@ const loadSetupDraftFromServer = async (): Promise<SetupConfigDraft | null> => {
try {
const { data } = await fetchSetupConfig(project.value.id);
if (!isSetupDraftShape(data?.data)) return null;
const draft = {
...clone(data.data),
projectInfo: isProjectPublishSnapshotShape(data.data.projectInfo) ? clone(data.data.projectInfo) : buildProjectPublishSnapshot(),
};
applySetupResponseMeta(data);
updateLastServerSaveMeta(data.updated_at, data.saved_by_name || resolveSavedByDisplay(data.saved_by));
setupSaveMeta.value = {
@@ -4167,10 +4171,7 @@ const loadSetupDraftFromServer = async (): Promise<SetupConfigDraft | null> => {
savedBy: data.saved_by_name || resolveSavedByDisplay(data.saved_by),
serverSynced: true,
};
return {
...clone(data.data),
projectInfo: isProjectPublishSnapshotShape(data.data.projectInfo) ? clone(data.data.projectInfo) : buildProjectPublishSnapshot(),
};
return draft;
} catch {
return null;
}
@@ -4512,23 +4513,27 @@ const initializeSetupDraft = async () => {
const localDraft = loadLocalSetupDraft();
if (localDraft) {
const shouldRestore = await confirmRestoreLocalDraft("恢复立项配置草稿", localDraft.savedAt, {
expired: localDraft.expired,
conflict: localDraft.conflict,
});
if (shouldRestore) {
applySetupDraft(localDraft.data);
setupDirtySinceLastPersist.value = true;
setupLocalDraftSavedAt.value = localDraft.savedAt;
setupSaveMeta.value = {
updatedAt: localDraft.savedAt || nowString(),
savedBy: authStore.user?.full_name || authStore.user?.username || authStore.user?.email || "当前用户",
serverSynced: false,
};
draftReady.value = true;
return;
if (!isAtomicSetupDraft(localDraft.data)) {
clearLocalSetupDraft();
} else {
const shouldRestore = await confirmRestoreLocalDraft("恢复立项配置草稿", localDraft.savedAt, {
expired: localDraft.expired,
conflict: localDraft.conflict,
});
if (shouldRestore) {
applySetupDraft(localDraft.data);
setupDirtySinceLastPersist.value = true;
setupLocalDraftSavedAt.value = localDraft.savedAt;
setupSaveMeta.value = {
updatedAt: localDraft.savedAt || nowString(),
savedBy: authStore.user?.full_name || authStore.user?.username || authStore.user?.email || "当前用户",
serverSynced: false,
};
draftReady.value = true;
return;
}
clearLocalSetupDraft();
}
clearLocalSetupDraft();
}
applySetupDraft(createDefaultSetupDraft(siteOptions.value));