feat(ui): save and continue button in drawer forms

This commit is contained in:
Fu Diwei 2025-12-23 22:44:19 +08:00
parent 69d528292d
commit 2cd5a1c630
7 changed files with 108 additions and 50 deletions

View File

@ -1,8 +1,8 @@
import { startTransition, useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { IconX } from "@tabler/icons-react";
import { IconChevronDown, IconX } from "@tabler/icons-react";
import { useControllableValue, useGetState } from "ahooks";
import { App, Button, Drawer, Flex, Form } from "antd";
import { App, Button, Drawer, Dropdown, Flex, Form, Space } from "antd";
import { notifyTest } from "@/api/notify";
import AccessProviderPicker from "@/components/provider/AccessProviderPicker";
@ -55,19 +55,7 @@ const AccessEditDrawer = ({ afterSubmit, mode, data, loading, trigger, usage, ..
const [formPending, setFormPending] = useState(false);
const [formChanged, setFormChanged] = useState(false);
const fieldProvider = Form.useWatch<string>("provider", { form: formInst, preserve: true });
const [isTesting, setIsTesting] = useState(false);
const handleProviderPick = (value: string) => {
formInst.setFieldValue("provider", value);
};
const handleFormChange = () => {
setFormChanged(true);
};
const handleOkClick = async () => {
const submitForm = async () => {
let formValues: AccessModel;
setFormPending(true);
@ -108,7 +96,6 @@ const AccessEditDrawer = ({ afterSubmit, mode, data, loading, trigger, usage, ..
}
afterSubmit?.(formValues);
setOpen(false);
} catch (err) {
notification.error({ title: t("common.text.request_error"), description: getErrMsg(err) });
@ -118,6 +105,27 @@ const AccessEditDrawer = ({ afterSubmit, mode, data, loading, trigger, usage, ..
}
};
const fieldProvider = Form.useWatch<string>("provider", { form: formInst, preserve: true });
const [isTesting, setIsTesting] = useState(false);
const handleProviderPick = (value: string) => {
formInst.setFieldValue("provider", value);
};
const handleFormChange = () => {
setFormChanged(true);
};
const handleOkClick = async () => {
await submitForm();
setOpen(false);
};
const handleOkAndContinueClick = async () => {
await submitForm();
};
const handleCancelClick = () => {
if (formPending) return;
@ -157,7 +165,7 @@ const AccessEditDrawer = ({ afterSubmit, mode, data, loading, trigger, usage, ..
fieldProvider ? (
<Flex className="px-2" justify="space-between">
{usage === "notification" ? (
<Button disabled={mode !== "modify" || formChanged} loading={isTesting} onClick={handleTestPushClick}>
<Button className="max-sm:invisible" disabled={mode !== "modify" || formChanged} loading={isTesting} onClick={handleTestPushClick}>
{t("access.action.test_push.button")}
</Button>
) : (
@ -167,9 +175,28 @@ const AccessEditDrawer = ({ afterSubmit, mode, data, loading, trigger, usage, ..
<Button disabled={isTesting} onClick={handleCancelClick}>
{t("common.button.cancel")}
</Button>
<Button disabled={isTesting} loading={formPending} type="primary" onClick={handleOkClick}>
{mode === "modify" ? t("common.button.save") : t("common.button.submit")}
</Button>
<Space.Compact>
<Button disabled={isTesting} loading={formPending} type="primary" onClick={handleOkClick}>
{mode === "modify" ? t("common.button.save") : t("common.button.submit")}
</Button>
<Show when={mode === "modify"}>
<Dropdown
menu={{
items: [
{
key: "save_and_continue",
label: t("common.button.save_and_continue"),
onClick: handleOkAndContinueClick,
},
],
}}
placement="bottomRight"
trigger={["click"]}
>
<Button disabled={formPending || isTesting} icon={<IconChevronDown size="1.25em" />} type="primary" />
</Dropdown>
</Show>
</Space.Compact>
</Flex>
</Flex>
) : (

View File

@ -1,6 +1,6 @@
import { CopyToClipboard } from "react-copy-to-clipboard";
import { useTranslation } from "react-i18next";
import { IconChevronDown, IconClipboard, IconThumbUp } from "@tabler/icons-react";
import { IconClipboard, IconDownload, IconThumbUp } from "@tabler/icons-react";
import { App, Button, Dropdown, Form, Input, Tag, Tooltip } from "antd";
import dayjs from "dayjs";
import { saveAs } from "file-saver";
@ -117,7 +117,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
}}
trigger={["click", "hover"]}
>
<Button icon={<IconChevronDown size="1.25em" />} iconPlacement="end" type="primary">
<Button icon={<IconDownload size="1.25em" />} type="primary">
{t("common.button.download")}
</Button>
</Dropdown>

View File

@ -1,9 +1,9 @@
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { type FlowNodeEntity, useClientContext, useRefresh } from "@flowgram.ai/fixed-layout-editor";
import { IconEye, IconEyeOff, IconX } from "@tabler/icons-react";
import { IconChevronDown, IconEye, IconEyeOff, IconX } from "@tabler/icons-react";
import { useControllableValue } from "ahooks";
import { Anchor, type AnchorProps, App, Button, Drawer, Flex, type FormInstance, Tooltip, Typography } from "antd";
import { Anchor, type AnchorProps, App, Button, Drawer, Dropdown, Flex, type FormInstance, Space, Tooltip, Typography } from "antd";
import { isEqual } from "radash";
import Show from "@/components/Show";
@ -43,6 +43,31 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
const [formPending, setFormPending] = useState(false);
const submitForm = async () => {
let formValues: Record<string, unknown>;
setFormPending(true);
try {
formValues = await formInst.validateFields();
} catch (err) {
message.warning(t("common.errmsg.form_invalid"));
setFormPending(false);
throw err;
}
try {
node.form!.setValueIn("config", formValues);
node.form!.validate();
} catch (err) {
notification.error({ title: t("common.text.request_error"), description: getErrMsg(err) });
throw err;
} finally {
setFormPending(false);
}
};
const nodeRegistry = node?.getNodeRegistry<NodeRegistry>();
const NodeIcon = nodeRegistry?.meta?.icon;
const renderNodeIcon = () =>
@ -80,30 +105,17 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
return;
}
let formValues: Record<string, unknown>;
setFormPending(true);
try {
formValues = await formInst.validateFields();
} catch (err) {
message.warning(t("common.errmsg.form_invalid"));
setFormPending(false);
throw err;
}
try {
node.form!.setValueIn("config", formValues);
node.form!.validate();
await submitForm();
setOpen(false);
};
const handleOkAndContinueClick = async () => {
if (node == null) {
setOpen(false);
} catch (err) {
notification.error({ title: t("common.text.request_error"), description: getErrMsg(err) });
throw err;
} finally {
setFormPending(false);
return;
}
await submitForm();
};
const handleCancelClick = () => {
@ -170,9 +182,26 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
footer ? (
<Flex className="px-2" justify="end" gap="small">
<Button onClick={handleCancelClick}>{t("common.button.cancel")}</Button>
<Button loading={formPending} type="primary" onClick={handleOkClick}>
{t("common.button.save")}
</Button>
<Space.Compact>
<Button loading={formPending} type="primary" onClick={handleOkClick}>
{t("common.button.save")}
</Button>
<Dropdown
menu={{
items: [
{
key: "save_and_continue",
label: t("common.button.save_and_continue"),
onClick: handleOkAndContinueClick,
},
],
}}
placement="bottomRight"
trigger={["click"]}
>
<Button disabled={formPending} icon={<IconChevronDown size="1.25em" />} type="primary" />
</Dropdown>
</Space.Compact>
</Flex>
) : (
<></>

View File

@ -19,7 +19,7 @@
"access.action.delete.modal.content": "Are you sure want to delete this credential? <br>This action cannot be undone.",
"access.action.batch_delete.modal.title": "Delete credentials",
"access.action.batch_delete.modal.content": "Are you sure want to delete these {{count}} selected credentials? <br>This action cannot be undone.",
"access.action.test_push.button": "Test push",
"access.action.test_push.button": "Send test push",
"access.props.name": "Name",
"access.props.provider.usage.dns": "DNS",

View File

@ -13,6 +13,7 @@
"common.button.reload": "Reload",
"common.button.reset": "Reset",
"common.button.save": "Save changes",
"common.button.save_and_continue": "Save and continue",
"common.button.submit": "Submit",
"common.button.view": "View",

View File

@ -19,7 +19,7 @@
"access.action.delete.modal.content": "确定要删除该授权吗?<br>注意此操作不可撤销,请谨慎操作。",
"access.action.batch_delete.modal.title": "删除授权",
"access.action.batch_delete.modal.content": "确定要删除这 {{count}} 个被选中的授权吗?<br>注意此操作不可撤销,请谨慎操作。",
"access.action.test_push.button": "通知测试",
"access.action.test_push.button": "发送测试通知",
"access.props.name": "名称",
"access.props.provider.usage.dns": "DNS 提供商",

View File

@ -13,6 +13,7 @@
"common.button.reload": "重新加载",
"common.button.reset": "重置",
"common.button.save": "保存更改",
"common.button.save_and_continue": "保存并继续",
"common.button.submit": "提交",
"common.button.view": "查看",