立项配置版本管理优化(回滚逻辑、UI、下载功能)

This commit is contained in:
Cheng Zhou
2026-03-04 14:49:59 +08:00
parent 6c5e5770c6
commit 0cd03eefa8
16 changed files with 3528 additions and 418 deletions
+73 -5
View File
@@ -107,6 +107,38 @@ const getArrayRowIdentity = (moduleKey: keyof SetupConfigDraft, value: unknown):
return "";
};
type ArrayRowEntry = {
key: string;
value: unknown;
rowIdentity: string;
};
const buildArrayRowEntry = (moduleKey: keyof SetupConfigDraft, value: unknown, index: number): ArrayRowEntry => {
const rowIdentity = getArrayRowIdentity(moduleKey, value);
if (value && typeof value === "object" && !Array.isArray(value)) {
const id = String((value as Record<string, unknown>).id || "").trim();
if (id) {
return {
key: `id:${id}`,
value,
rowIdentity,
};
}
}
if (rowIdentity) {
return {
key: `identity:${rowIdentity}`,
value,
rowIdentity,
};
}
return {
key: `index:${index}`,
value,
rowIdentity: "",
};
};
const formatReadablePath = (moduleKey: keyof SetupConfigDraft, path: string): string => {
let normalized = path;
if (path === moduleKey) {
@@ -247,11 +279,47 @@ const collectDiffRows = (
if (Array.isArray(localValue) || Array.isArray(serverValue)) {
const localArr = Array.isArray(localValue) ? localValue : [];
const serverArr = Array.isArray(serverValue) ? serverValue : [];
const maxLen = Math.max(localArr.length, serverArr.length);
for (let i = 0; i < maxLen; i += 1) {
const rowIdentity = getArrayRowIdentity(moduleKey, localArr[i] ?? serverArr[i]);
const indexLabel = rowIdentity ? `${i}|${rowIdentity}` : `${i}`;
collectDiffRows(moduleKey, moduleLabel, localArr[i], serverArr[i], `${path}[${indexLabel}]`, rows);
const localEntryMap = new Map<string, ArrayRowEntry[]>();
localArr.forEach((item, index) => {
const entry = buildArrayRowEntry(moduleKey, item, index);
const list = localEntryMap.get(entry.key);
if (list) {
list.push(entry);
} else {
localEntryMap.set(entry.key, [entry]);
}
});
const consumeLocalEntry = (key: string): ArrayRowEntry | null => {
const list = localEntryMap.get(key);
if (!list || list.length === 0) return null;
const entry = list.shift() || null;
if (list.length === 0) localEntryMap.delete(key);
return entry;
};
let pairIndex = 0;
serverArr.forEach((item, index) => {
const serverEntry = buildArrayRowEntry(moduleKey, item, index);
const localEntry = consumeLocalEntry(serverEntry.key);
const rowIdentity = (localEntry?.rowIdentity || serverEntry.rowIdentity || "").trim();
const indexLabel = rowIdentity ? `${pairIndex}|${rowIdentity}` : `${pairIndex}`;
collectDiffRows(moduleKey, moduleLabel, localEntry?.value, serverEntry.value, `${path}[${indexLabel}]`, rows);
pairIndex += 1;
});
localEntryMap.forEach((list) => {
list.forEach((localEntry) => {
const rowIdentity = localEntry.rowIdentity.trim();
const indexLabel = rowIdentity ? `${pairIndex}|${rowIdentity}` : `${pairIndex}`;
collectDiffRows(moduleKey, moduleLabel, localEntry.value, undefined, `${path}[${indexLabel}]`, rows);
pairIndex += 1;
});
});
if (localArr.length === 0 && serverArr.length === 0) {
// Keep behavior explicit for empty array pair to avoid extra recursion.
return;
}
return;
}
+2 -2
View File
@@ -6,8 +6,8 @@ export type SetupWorkflowStatus = "UNSAVED_DRAFT" | "SAVED_DRAFT" | "PUBLISHED";
export type SetupWorkflowTagMeta = { label: string; type: "success" | "warning" | "info" };
export const SETUP_WORKFLOW_TAG_META: Record<SetupWorkflowStatus, SetupWorkflowTagMeta> = {
UNSAVED_DRAFT: { label: "草稿未保存", type: "warning" },
SAVED_DRAFT: { label: "草稿已保存(未发布)", type: "info" },
UNSAVED_DRAFT: { label: "未保存", type: "warning" },
SAVED_DRAFT: { label: "已保存", type: "info" },
PUBLISHED: { label: "已发布", type: "success" },
};