From f440103fcea9e35281689270cd146fab32c32ea7 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 13 Aug 2025 20:51:15 +0800 Subject: [PATCH] fix(ui): lost animation effect when the drawer closes --- ui/src/components/DrawerForm.tsx | 15 +++--- ui/src/components/access/AccessEditDrawer.tsx | 50 ++++++++++++------- .../certificate/CertificateDetailDrawer.tsx | 48 +++++++++++------- .../workflow/WorkflowRunDetailDrawer.tsx | 47 ++++++++++------- .../workflow/designer/NodeDrawer.tsx | 45 ++++++++++------- .../forms/BizApplyNodeConfigDrawer.tsx | 1 + .../forms/BizDeployNodeConfigDrawer.tsx | 1 + .../forms/BizMonitorNodeConfigDrawer.tsx | 1 + .../forms/BizNotifyNodeConfigDrawer.tsx | 1 + .../forms/BizUploadNodeConfigDrawer.tsx | 1 + .../forms/BranchBlockNodeConfigDrawer.tsx | 1 + .../designer/forms/StartNodeConfigDrawer.tsx | 1 + .../workflow/designer/forms/_shared.tsx | 6 ++- ui/src/pages/accesses/AccessList.tsx | 8 ++- ui/src/pages/certificates/CertificateList.tsx | 5 +- ui/src/pages/dashboard/Dashboard.tsx | 5 +- ui/src/pages/workflows/WorkflowDetailRuns.tsx | 7 ++- 17 files changed, 148 insertions(+), 95 deletions(-) diff --git a/ui/src/components/DrawerForm.tsx b/ui/src/components/DrawerForm.tsx index 25fc6cf34..7c2c1a4fe 100644 --- a/ui/src/components/DrawerForm.tsx +++ b/ui/src/components/DrawerForm.tsx @@ -58,7 +58,7 @@ const DrawerForm = = any>({ form: formInst, formPending, formProps, - submit, + submit: submitForm, } = useAntdForm({ form, onSubmit: (values) => { @@ -75,25 +75,22 @@ const DrawerForm = = any>({ const mergedDrawerProps: DrawerProps = { ...drawerProps, closeIcon: false, - afterOpenChange: (open) => { - if (!open && !mergedFormProps.preserve) { - formInst.resetFields(); - } - - drawerProps?.afterOpenChange?.(open); - }, onClose: async (e) => { if (formPending) return; // 关闭 Drawer 时 Promise.reject 阻止关闭 await drawerProps?.onClose?.(e); setOpen(false); + + if (!mergedFormProps.preserve) { + formInst.resetFields(); + } }, }; const handleOkClick = async () => { // 提交表单返回 Promise.reject 时不关闭 Drawer - await submit(); + await submitForm(); setOpen(false); }; diff --git a/ui/src/components/access/AccessEditDrawer.tsx b/ui/src/components/access/AccessEditDrawer.tsx index 08cf1a69b..c091f3144 100644 --- a/ui/src/components/access/AccessEditDrawer.tsx +++ b/ui/src/components/access/AccessEditDrawer.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { startTransition, useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { IconX } from "@tabler/icons-react"; import { useControllableValue } from "ahooks"; @@ -12,6 +12,8 @@ import { getErrMsg } from "@/utils/error"; import AccessForm, { type AccessFormInstance, type AccessFormProps } from "./AccessForm"; export interface AccessEditDrawerProps { + afterClose?: () => void; + afterSubmit?: (record: AccessModel) => void; data?: AccessFormProps["initialValues"]; loading?: boolean; mode: AccessFormProps["mode"]; @@ -19,10 +21,9 @@ export interface AccessEditDrawerProps { trigger?: React.ReactNode; usage?: AccessFormProps["usage"]; onOpenChange?: (open: boolean) => void; - afterSubmit?: (record: AccessModel) => void; } -const AccessEditDrawer = ({ mode, data, loading, trigger, usage, afterSubmit, ...props }: AccessEditDrawerProps) => { +const AccessEditDrawer = ({ afterClose, afterSubmit, mode, data, loading, trigger, usage, ...props }: AccessEditDrawerProps) => { const { t } = useTranslation(); const { notification } = App.useApp(); @@ -99,7 +100,8 @@ const AccessEditDrawer = ({ mode, data, loading, trigger, usage, afterSubmit, .. {triggerEl} !open && afterClose?.()} + autoFocus closeIcon={false} destroyOnHidden footer={ @@ -141,29 +143,41 @@ const AccessEditDrawer = ({ mode, data, loading, trigger, usage, afterSubmit, .. ); }; -const useProps = () => { - const [data, setData] = useState(); +const useDrawer = () => { + type DataType = AccessEditDrawerProps["data"]; + const [data, setData] = useState(); const [open, setOpen] = useState(false); - const onOpenChange = (open: boolean) => { + const onOpenChange = useCallback((open: boolean) => { setOpen(open); - - if (!open) { - setData(void 0); - } - }; + }, []); return { - data, - open, - setData, - setOpen, - onOpenChange, + drawerProps: { + afterClose: () => { + startTransition(() => { + if (!open) { + setData(void 0); + } + }); + }, + data, + open, + onOpenChange, + }, + + open: (data: NonNullable) => { + setData(data); + setOpen(true); + }, + close: () => { + setOpen(false); + }, }; }; const _default = Object.assign(AccessEditDrawer, { - useProps, + useDrawer, }); export default _default; diff --git a/ui/src/components/certificate/CertificateDetailDrawer.tsx b/ui/src/components/certificate/CertificateDetailDrawer.tsx index e050d057a..7a48724bc 100644 --- a/ui/src/components/certificate/CertificateDetailDrawer.tsx +++ b/ui/src/components/certificate/CertificateDetailDrawer.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { startTransition, useCallback, useState } from "react"; import { IconX } from "@tabler/icons-react"; import { useControllableValue } from "ahooks"; import { Button, Drawer, Flex } from "antd"; @@ -9,6 +9,7 @@ import { useTriggerElement } from "@/hooks"; import CertificateDetail from "./CertificateDetail"; export interface CertificateDetailDrawerProps { + afterClose?: () => void; data?: CertificateModel; loading?: boolean; open?: boolean; @@ -16,7 +17,7 @@ export interface CertificateDetailDrawerProps { onOpenChange?: (open: boolean) => void; } -const CertificateDetailDrawer = ({ data, loading, trigger, ...props }: CertificateDetailDrawerProps) => { +const CertificateDetailDrawer = ({ afterClose, data, loading, trigger, ...props }: CertificateDetailDrawerProps) => { const [open, setOpen] = useControllableValue(props, { valuePropName: "open", defaultValuePropName: "defaultOpen", @@ -30,7 +31,8 @@ const CertificateDetailDrawer = ({ data, loading, trigger, ...props }: Certifica {triggerEl} !open && afterClose?.()} + autoFocus closeIcon={false} destroyOnHidden open={open} @@ -60,29 +62,41 @@ const CertificateDetailDrawer = ({ data, loading, trigger, ...props }: Certifica ); }; -const useProps = () => { - const [data, setData] = useState(); +const useDrawer = () => { + type DataType = CertificateDetailDrawerProps["data"]; + const [data, setData] = useState(); const [open, setOpen] = useState(false); - const onOpenChange = (open: boolean) => { + const onOpenChange = useCallback((open: boolean) => { setOpen(open); - - if (!open) { - setData(void 0); - } - }; + }, []); return { - data, - open, - setData, - setOpen, - onOpenChange, + drawerProps: { + afterClose: () => { + startTransition(() => { + if (!open) { + setData(void 0); + } + }); + }, + data, + open, + onOpenChange, + }, + + open: (data: NonNullable) => { + setData(data); + setOpen(true); + }, + close: () => { + setOpen(false); + }, }; }; const _default = Object.assign(CertificateDetailDrawer, { - useProps, + useDrawer, }); export default _default; diff --git a/ui/src/components/workflow/WorkflowRunDetailDrawer.tsx b/ui/src/components/workflow/WorkflowRunDetailDrawer.tsx index 67982b1d6..4419062cc 100644 --- a/ui/src/components/workflow/WorkflowRunDetailDrawer.tsx +++ b/ui/src/components/workflow/WorkflowRunDetailDrawer.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { startTransition, useCallback, useState } from "react"; import { IconX } from "@tabler/icons-react"; import { useControllableValue } from "ahooks"; import { Button, Drawer, Flex } from "antd"; @@ -10,6 +10,7 @@ import { useTriggerElement } from "@/hooks"; import WorkflowRunDetail from "./WorkflowRunDetail"; export interface WorkflowRunDetailDrawerProps { + afterClose?: () => void; data?: WorkflowRunModel; loading?: boolean; open?: boolean; @@ -17,7 +18,7 @@ export interface WorkflowRunDetailDrawerProps { onOpenChange?: (open: boolean) => void; } -const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowRunDetailDrawerProps) => { +const WorkflowRunDetailDrawer = ({ afterClose, data, loading, trigger, ...props }: WorkflowRunDetailDrawerProps) => { const [open, setOpen] = useControllableValue(props, { valuePropName: "open", defaultValuePropName: "defaultOpen", @@ -31,7 +32,7 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR {triggerEl} !open && afterClose?.()} closeIcon={false} destroyOnHidden open={open} @@ -61,29 +62,41 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR ); }; -const useProps = () => { - const [data, setData] = useState(); +const useDrawer = () => { + type DataType = WorkflowRunDetailDrawerProps["data"]; + const [data, setData] = useState(); const [open, setOpen] = useState(false); - const onOpenChange = (open: boolean) => { + const onOpenChange = useCallback((open: boolean) => { setOpen(open); - - if (!open) { - setData(undefined); - } - }; + }, []); return { - data, - open, - setData, - setOpen, - onOpenChange, + drawerProps: { + afterClose: () => { + startTransition(() => { + if (!open) { + setData(void 0); + } + }); + }, + data, + open, + onOpenChange, + }, + + open: (data: NonNullable) => { + setData(data); + setOpen(true); + }, + close: () => { + setOpen(false); + }, }; }; const _default = Object.assign(WorkflowRunDetailDrawer, { - useProps, + useDrawer, }); export default _default; diff --git a/ui/src/components/workflow/designer/NodeDrawer.tsx b/ui/src/components/workflow/designer/NodeDrawer.tsx index 0c6e26dfa..c7e6ade74 100644 --- a/ui/src/components/workflow/designer/NodeDrawer.tsx +++ b/ui/src/components/workflow/designer/NodeDrawer.tsx @@ -1,4 +1,4 @@ -import { startTransition, useMemo, useState } from "react"; +import { startTransition, useCallback, useMemo, useState } from "react"; import { type FlowNodeEntity } from "@flowgram.ai/fixed-layout-editor"; import { useControllableValue } from "ahooks"; @@ -14,6 +14,7 @@ import StartNodeConfigDrawer from "./forms/StartNodeConfigDrawer"; import { NodeType } from "./nodes/typings"; export interface NodeDrawerProps { + afterClose?: () => void; children?: React.ReactNode; loading?: boolean; node?: FlowNodeEntity; @@ -68,31 +69,41 @@ const NodeDrawer = ({ node, trigger, ...props }: NodeDrawerProps) => { ); }; -const useProps = () => { - const [node, setNode] = useState(); +const useDrawer = () => { + type NodeDataType = NodeDrawerProps["node"]; + const [node, setNode] = useState(); const [open, setOpen] = useState(false); - const onOpenChange = (open: boolean) => { + const onOpenChange = useCallback((open: boolean) => { setOpen(open); - - startTransition(() => { - if (!open) { - setNode(void 0); - } - }); - }; + }, []); return { - node, - open, - setNode, - setOpen, - onOpenChange, + drawerProps: { + afterClose: () => { + startTransition(() => { + if (!open) { + setNode(void 0); + } + }); + }, + node, + open, + onOpenChange, + }, + + open: (node: NonNullable) => { + setNode(node); + setOpen(true); + }, + close: () => { + setOpen(false); + }, }; }; const _default = Object.assign(NodeDrawer, { - useProps, + useDrawer, }); export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigDrawer.tsx index 6d736aa83..6781808b6 100644 --- a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigDrawer.tsx +++ b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigDrawer.tsx @@ -7,6 +7,7 @@ import BizApplyNodeConfigForm from "./BizApplyNodeConfigForm"; import { NodeType } from "../nodes/typings"; export interface BizApplyNodeConfigDrawerProps { + afterClose?: () => void; loading?: boolean; node: FlowNodeEntity; open?: boolean; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigDrawer.tsx index 8e43fbc3a..bab398d4c 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigDrawer.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigDrawer.tsx @@ -7,6 +7,7 @@ import BizDeployNodeConfigForm from "./BizDeployNodeConfigForm"; import { NodeType } from "../nodes/typings"; export interface BizDeployNodeConfigDrawerProps { + afterClose?: () => void; loading?: boolean; node: FlowNodeEntity; open?: boolean; diff --git a/ui/src/components/workflow/designer/forms/BizMonitorNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/BizMonitorNodeConfigDrawer.tsx index 3ef0b4cd1..fa7175192 100644 --- a/ui/src/components/workflow/designer/forms/BizMonitorNodeConfigDrawer.tsx +++ b/ui/src/components/workflow/designer/forms/BizMonitorNodeConfigDrawer.tsx @@ -7,6 +7,7 @@ import BizMonitorNodeConfigForm from "./BizMonitorNodeConfigForm"; import { NodeType } from "../nodes/typings"; export interface BizMonitorNodeConfigDrawerProps { + afterClose?: () => void; loading?: boolean; node: FlowNodeEntity; open?: boolean; diff --git a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigDrawer.tsx index a88c08a77..f5b3e247f 100644 --- a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigDrawer.tsx +++ b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigDrawer.tsx @@ -7,6 +7,7 @@ import BizNotifyNodeConfigForm from "./BizNotifyNodeConfigForm"; import { NodeType } from "../nodes/typings"; export interface BizNotifyNodeConfigDrawerProps { + afterClose?: () => void; loading?: boolean; node: FlowNodeEntity; open?: boolean; diff --git a/ui/src/components/workflow/designer/forms/BizUploadNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/BizUploadNodeConfigDrawer.tsx index 826024b8f..2f571a767 100644 --- a/ui/src/components/workflow/designer/forms/BizUploadNodeConfigDrawer.tsx +++ b/ui/src/components/workflow/designer/forms/BizUploadNodeConfigDrawer.tsx @@ -7,6 +7,7 @@ import BizUploadNodeConfigForm from "./BizUploadNodeConfigForm"; import { NodeType } from "../nodes/typings"; export interface BizUploadNodeConfigDrawerProps { + afterClose?: () => void; loading?: boolean; node: FlowNodeEntity; open?: boolean; diff --git a/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigDrawer.tsx index 985301014..4ebe79873 100644 --- a/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigDrawer.tsx +++ b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigDrawer.tsx @@ -7,6 +7,7 @@ import BranchBlockNodeConfigForm from "./BranchBlockNodeConfigForm"; import { NodeType } from "../nodes/typings"; export interface BranchBlockNodeConfigDrawerProps { + afterClose?: () => void; loading?: boolean; node: FlowNodeEntity; open?: boolean; diff --git a/ui/src/components/workflow/designer/forms/StartNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/StartNodeConfigDrawer.tsx index 0f02fe6c3..cc7cdd23b 100644 --- a/ui/src/components/workflow/designer/forms/StartNodeConfigDrawer.tsx +++ b/ui/src/components/workflow/designer/forms/StartNodeConfigDrawer.tsx @@ -7,6 +7,7 @@ import StartNodeConfigForm from "./StartNodeConfigForm"; import { NodeType } from "../nodes/typings"; export interface StartNodeConfigDrawerProps { + afterClose?: () => void; loading?: boolean; node: FlowNodeEntity; open?: boolean; diff --git a/ui/src/components/workflow/designer/forms/_shared.tsx b/ui/src/components/workflow/designer/forms/_shared.tsx index 6ca67a85e..7dab502c4 100644 --- a/ui/src/components/workflow/designer/forms/_shared.tsx +++ b/ui/src/components/workflow/designer/forms/_shared.tsx @@ -12,6 +12,7 @@ import { type NodeRegistry } from "../nodes/typings"; export interface NodeConfigDrawerProps { children: React.ReactNode; + afterClose?: () => void; anchor?: Pick, "items"> | false; footer?: boolean; form: FormInstance; @@ -21,7 +22,7 @@ export interface NodeConfigDrawerProps { onOpenChange?: (open: boolean) => void; } -export const NodeConfigDrawer = ({ children, anchor, footer = true, form: formInst, loading, node, ...props }: NodeConfigDrawerProps) => { +export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true, form: formInst, loading, node, ...props }: NodeConfigDrawerProps) => { const { t } = useTranslation(); const { modal, notification } = App.useApp(); @@ -134,7 +135,8 @@ export const NodeConfigDrawer = ({ children, anchor, footer = true, form: formIn paddingBottom: anchor ? 0 : void 0, }, }} - afterOpenChange={setOpen} + afterOpenChange={(open) => !open && afterClose?.()} + autoFocus closeIcon={false} destroyOnHidden footer={ diff --git a/ui/src/pages/accesses/AccessList.tsx b/ui/src/pages/accesses/AccessList.tsx index 2a28ad758..5fdccdb1d 100644 --- a/ui/src/pages/accesses/AccessList.tsx +++ b/ui/src/pages/accesses/AccessList.tsx @@ -281,19 +281,17 @@ const AccessList = () => { navigate(`/accesses/new?usage=${filters["usage"]}`); }; - const { setData: setDetailRecord, setOpen: setDetailOpen, ...detailDrawerProps } = AccessEditDrawer.useProps(); + const { drawerProps: detailDrawerProps, ...detailDrawer } = AccessEditDrawer.useDrawer(); const [detailMode, setDetailMode] = useState("create"); const handleRecordDetailClick = (access: AccessModel) => { - setDetailRecord(access); setDetailMode("edit"); - setDetailOpen(true); + detailDrawer.open(access); }; const handleRecordDuplicateClick = (access: AccessModel) => { - setDetailRecord({ ...access, id: void 0, name: `${access.name}-copy` }); setDetailMode("create"); - setDetailOpen(true); + detailDrawer.open({ ...access, id: void 0, name: `${access.name}-copy` }); }; const handleRecordDeleteClick = async (access: AccessModel) => { diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx index c4a01765b..8f4ee9d06 100644 --- a/ui/src/pages/certificates/CertificateList.tsx +++ b/ui/src/pages/certificates/CertificateList.tsx @@ -285,11 +285,10 @@ const CertificateList = () => { setPageSize(pageSize); }; - const { setData: setDetailRecord, setOpen: setDetailOpen, ...detailDrawerProps } = CertificateDetailDrawer.useProps(); + const { drawerProps: detailDrawerProps, ...detailDrawer } = CertificateDetailDrawer.useDrawer(); const handleRecordDetailClick = (certificate: CertificateModel) => { - setDetailRecord(certificate); - setDetailOpen(true); + detailDrawer.open(certificate); }; const handleRecordDeleteClick = (certificate: CertificateModel) => { diff --git a/ui/src/pages/dashboard/Dashboard.tsx b/ui/src/pages/dashboard/Dashboard.tsx index 426fd48a9..980be604f 100644 --- a/ui/src/pages/dashboard/Dashboard.tsx +++ b/ui/src/pages/dashboard/Dashboard.tsx @@ -329,11 +329,10 @@ const WorkflowRunHistoryTable = ({ className, style }: { className?: string; sty refreshData(); }; - const { setData: setDetailRecord, setOpen: setDetailOpen, ...detailDrawerProps } = WorkflowRunDetailDrawer.useProps(); + const { drawerProps: detailDrawerProps, ...detailDrawer } = WorkflowRunDetailDrawer.useDrawer(); const handleRecordDetailClick = (workflowRun: WorkflowRunModel) => { - setDetailRecord(workflowRun); - setDetailOpen(true); + detailDrawer.open(workflowRun); }; return ( diff --git a/ui/src/pages/workflows/WorkflowDetailRuns.tsx b/ui/src/pages/workflows/WorkflowDetailRuns.tsx index b2a2469c6..730ead466 100644 --- a/ui/src/pages/workflows/WorkflowDetailRuns.tsx +++ b/ui/src/pages/workflows/WorkflowDetailRuns.tsx @@ -236,7 +236,7 @@ const WorkflowDetailRuns = () => { }); if (cb.record.id === detailDrawerProps.data?.id) { - setDetailRecord({ ...detailDrawerProps.data, ...cb.record }); + detailDrawerProps.data = { ...detailDrawerProps.data, ...cb.record }; } if (cb.record.status !== WORKFLOW_RUN_STATUSES.PENDING && cb.record.status !== WORKFLOW_RUN_STATUSES.RUNNING) { @@ -257,11 +257,10 @@ const WorkflowDetailRuns = () => { setPageSize(pageSize); }; - const { setData: setDetailRecord, setOpen: setDetailOpen, ...detailDrawerProps } = WorkflowRunDetailDrawer.useProps(); + const { drawerProps: detailDrawerProps, ...detailDrawer } = WorkflowRunDetailDrawer.useDrawer(); const handleRecordDetailClick = (workflowRun: WorkflowRunModel) => { - setDetailRecord(workflowRun); - setDetailOpen(true); + detailDrawer.open(workflowRun); }; const handleRecordCancelClick = (workflowRun: WorkflowRunModel) => {