立项配置数据入库、审计日志数据入库、编辑页UI界面美化、设备管理内容补充、审计日志显示优化

This commit is contained in:
Cheng Zhou
2026-02-27 16:16:26 +08:00
parent fd7e3fc948
commit db2d38edbc
48 changed files with 2936 additions and 909 deletions
+148
View File
@@ -0,0 +1,148 @@
import type { ProjectPublishSnapshot, SetupConfigDraft } from "../types/setupConfig";
import type { SetupDiffRow } from "./setupDiffRows";
export type DraftSyncStatus = "SYNCED" | "LOCAL_ONLY" | "DIRTY_UNSAVED";
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" },
PUBLISHED: { label: "已发布", type: "success" },
};
export const resolveSetupWorkflowStatus = (
draftSyncStatus: DraftSyncStatus,
options: {
canStrictlyDetectNoDiff: boolean;
hasPublishableDiff: boolean;
}
): SetupWorkflowStatus => {
const { canStrictlyDetectNoDiff, hasPublishableDiff } = options;
if (draftSyncStatus !== "SYNCED") return "UNSAVED_DRAFT";
if (canStrictlyDetectNoDiff && !hasPublishableDiff) return "PUBLISHED";
return "SAVED_DRAFT";
};
export const buildSetupModuleDiffLines = (
localDraft: SetupConfigDraft | null,
compareBase: SetupConfigDraft | null,
moduleLabels: Array<{ key: keyof SetupConfigDraft; label: string }>
): string[] => {
if (!localDraft || !compareBase) return [];
const lines: string[] = [];
moduleLabels.forEach((item) => {
const localChunk = JSON.stringify(localDraft[item.key] ?? null);
const baseChunk = JSON.stringify(compareBase[item.key] ?? null);
if (localChunk !== baseChunk) {
lines.push(`${item.label} 存在差异`);
}
});
return lines;
};
export const resolveProjectPublishCompareBase = (
publishedSnapshot: ProjectPublishSnapshot | null,
fallbackSnapshot: ProjectPublishSnapshot | null
): ProjectPublishSnapshot | null => {
return publishedSnapshot || fallbackSnapshot || null;
};
export const hasProjectSnapshotDiff = (
currentSnapshot: ProjectPublishSnapshot,
baseSnapshot: ProjectPublishSnapshot | null
): boolean => {
if (!baseSnapshot) return false;
return JSON.stringify(currentSnapshot) !== JSON.stringify(baseSnapshot);
};
type ResolvePublishedProjectSnapshotInput = {
publishStatus: string;
apiProjectSnapshot: ProjectPublishSnapshot | null;
previousSnapshot: ProjectPublishSnapshot | null;
projectSnapshotAtLoad: ProjectPublishSnapshot | null;
currentProjectSnapshot: ProjectPublishSnapshot;
};
type ResolvePublishedProjectSnapshotResult = {
snapshot: ProjectPublishSnapshot | null;
clearFallback: boolean;
};
export const resolvePublishedProjectSnapshot = (
input: ResolvePublishedProjectSnapshotInput
): ResolvePublishedProjectSnapshotResult => {
if (input.apiProjectSnapshot) {
return { snapshot: input.apiProjectSnapshot, clearFallback: true };
}
if (input.publishStatus === "PUBLISHED") {
return {
snapshot: input.previousSnapshot || input.projectSnapshotAtLoad || input.currentProjectSnapshot,
clearFallback: true,
};
}
return {
// Draft state keeps the previous published baseline when available.
snapshot: input.previousSnapshot,
clearFallback: false,
};
};
export const shouldCaptureProjectCompareFallback = (params: {
hasProjectPendingChanges: boolean;
hasPublishedProjectSnapshot: boolean;
isFirstPublish: boolean;
}): boolean => {
if (!params.hasProjectPendingChanges) return false;
if (params.hasPublishedProjectSnapshot) return false;
if (params.isFirstPublish) return false;
return true;
};
const PROJECT_FIELD_LABEL_MAP: Record<keyof ProjectPublishSnapshot, string> = {
code: "项目编号",
name: "项目名称",
project_full_name: "项目全称",
sponsor: "申办方",
protocol_no: "方案号",
lead_unit: "组长单位",
principal_investigator: "主要研究者",
main_pm: "主PM",
research_analysis: "研究分期",
research_product: "研究产品",
control_product: "对照产品",
indication: "适应症",
research_population: "研究人群",
research_design: "研究设计",
plan_start_date: "计划开始日期",
plan_end_date: "计划结束日期",
planned_site_count: "计划中心数",
planned_enrollment_count: "计划入组数",
summary_note: "方案摘要说明",
objective_note: "研究目标摘要",
status: "项目状态",
visit_interval_days: "访视间隔(天)",
visit_total: "访视总数",
visit_window_start_offset: "窗口期起始偏移",
visit_window_end_offset: "窗口期结束偏移",
};
export const buildProjectDiffRows = (
currentSnapshot: ProjectPublishSnapshot,
baseSnapshot: ProjectPublishSnapshot | null,
serializeValue: (value: unknown) => string
): SetupDiffRow[] => {
if (!baseSnapshot) return [];
const rows: SetupDiffRow[] = [];
(Object.keys(PROJECT_FIELD_LABEL_MAP) as Array<keyof ProjectPublishSnapshot>).forEach((key) => {
if (currentSnapshot[key] === baseSnapshot[key]) return;
rows.push({
moduleLabel: "项目信息",
path: PROJECT_FIELD_LABEL_MAP[key],
changeType: "修改",
localValue: serializeValue(currentSnapshot[key]),
serverValue: serializeValue(baseSnapshot[key]),
});
});
return rows;
};