From 063dc6fe9798afef8fa4e18348d31cbe433fd52b Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 23 Feb 2026 14:36:04 +0800 Subject: [PATCH] feat: Add unsaved changes tracking to PipelineFormComponent --- .../pipeline-form/PipelineFormComponent.tsx | 26 ++++++++++++++++--- web/src/i18n/locales/en-US.ts | 1 + web/src/i18n/locales/ja-JP.ts | 1 + web/src/i18n/locales/zh-Hans.ts | 1 + web/src/i18n/locales/zh-Hant.ts | 1 + 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx index b669e471..517d0f76 100644 --- a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx +++ b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState, useMemo } from 'react'; import { httpClient } from '@/app/infra/http/HttpClient'; import { GetPipelineResponseData, Pipeline } from '@/app/infra/entities/api'; import { @@ -118,6 +118,14 @@ export default function PipelineFormComponent({ }, }); + // Track unsaved changes by comparing current form values against a saved snapshot + const savedSnapshotRef = useRef(''); + const watchedValues = form.watch(); + const hasUnsavedChanges = useMemo(() => { + if (!isEditMode || !savedSnapshotRef.current) return false; + return JSON.stringify(watchedValues) !== savedSnapshotRef.current; + }, [isEditMode, watchedValues]); + useEffect(() => { // get config schema from metadata httpClient.getGeneralPipelineMetadata().then((resp) => { @@ -139,7 +147,7 @@ export default function PipelineFormComponent({ .getPipeline(pipelineId || '') .then((resp: GetPipelineResponseData) => { setIsDefaultPipeline(resp.pipeline.is_default ?? false); - form.reset({ + const loadedValues = { basic: { name: resp.pipeline.name, description: resp.pipeline.description, @@ -149,7 +157,9 @@ export default function PipelineFormComponent({ trigger: resp.pipeline.config.trigger, safety: resp.pipeline.config.safety, output: resp.pipeline.config.output, - }); + }; + form.reset(loadedValues); + savedSnapshotRef.current = JSON.stringify(loadedValues); }); } }, []); @@ -216,6 +226,7 @@ export default function PipelineFormComponent({ httpClient .updatePipeline(pipelineId || '', pipeline) .then(() => { + savedSnapshotRef.current = JSON.stringify(form.getValues()); onFinish(); toast.success(t('pipelines.saveSuccess')); }) @@ -509,7 +520,14 @@ export default function PipelineFormComponent({ {/* 按钮栏移到 Tabs 外部,始终固定底部 */} {showButtons && ( -
+
+ {isEditMode && hasUnsavedChanges && ( +
+ + {t('pipelines.unsavedChanges')} +
+ )} + {isEditMode && !isDefaultPipeline && (