From e326cc1a93c642c5ed7dd5022639868ffb9f5887 Mon Sep 17 00:00:00 2001 From: xiaozhu <2395895331@qq.com> Date: Wed, 26 Aug 2026 16:48:19 +0800 Subject: [PATCH] =?UTF-8?q?update:Dialog=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/components/common/AppFormDialog.tsx | 90 ++++++++ .../app/components/common/ConfirmDialog.tsx | 74 +++++++ .../app/features/admin/ImportMemberDialog.tsx | 107 ++++----- .../features/admin/PermissionEditDialog.tsx | 89 +++----- .../app/features/admin/ProjectEditDialog.tsx | 164 ++++++-------- .../features/admin/ProjectManagementPage.tsx | 27 ++- .../app/features/admin/RoleEditDialog.tsx | 195 +++++++---------- .../app/features/admin/RoleManagementPage.tsx | 28 ++- .../app/features/admin/UserManagementPage.tsx | 83 +++---- .../features/platform/CreateFolderModal.tsx | 64 +++--- .../features/platform/CreateScriptModal.tsx | 65 +++--- .../platform/DataResourceUploadModal.tsx | 206 +++++++++--------- .../app/features/platform/PublishModal.tsx | 67 +++--- .../features/platform/VersionReceiptModal.tsx | 91 ++++---- frontend/app/features/platform/modalUi.ts | 2 +- .../app/features/schedules/SchedulePage.tsx | 61 ++---- 16 files changed, 738 insertions(+), 675 deletions(-) create mode 100644 frontend/app/components/common/AppFormDialog.tsx create mode 100644 frontend/app/components/common/ConfirmDialog.tsx diff --git a/frontend/app/components/common/AppFormDialog.tsx b/frontend/app/components/common/AppFormDialog.tsx new file mode 100644 index 0000000..4bb7312 --- /dev/null +++ b/frontend/app/components/common/AppFormDialog.tsx @@ -0,0 +1,90 @@ +import type { ReactNode } from "react" + +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "~/components/ui/dialog" +import { cn } from "~/lib/utils" + +/** 与系统管理弹窗 footer 一致的次要按钮样式 */ +export const dialogSecondaryButtonClass = + "h-[37px] gap-[7px] rounded-md border-[#d8e1e9] bg-white px-[15px] text-xs font-[650] text-[#56687c]" + +/** 与系统管理弹窗 footer 一致的主按钮样式 */ +export const dialogPrimaryButtonClass = + "h-[37px] gap-[7px] rounded-md border border-[#126ac3] bg-gradient-to-br from-[#1881e7] to-[#1268c9] px-[15px] text-xs font-[650] text-white shadow-[0_6px_15px_rgb(20_111_202/18%)] enabled:hover:from-[#1175d8] enabled:hover:to-[#0e5bad]" + +const EYEBROW_CLASS = + "text-[9px] font-extrabold tracking-[0.12em] text-[#2d82d4]" + +const TITLE_CLASS = "mt-1 text-[19px] font-normal text-[#1c2d42]" + +type AppFormDialogProps = { + open: boolean + onOpenChange: (open: boolean) => void + eyebrow?: string + title?: ReactNode + titleId?: string + children: ReactNode + footer?: ReactNode + contentClassName?: string + bodyClassName?: string + showCloseButton?: boolean + size?: "compact" | "panel" +} + +function AppFormDialog({ + open, + onOpenChange, + eyebrow, + title, + titleId, + children, + footer, + contentClassName, + bodyClassName, + showCloseButton = true, + size = "compact", +}: AppFormDialogProps) { + const maxWidth = + size === "panel" + ? "sm:max-w-[min(520px,calc(100vw-40px))]" + : "sm:max-w-[min(480px,calc(100vw-40px))]" + + return ( + + + {eyebrow || title ? ( + + {eyebrow ? {eyebrow} : null} + {title ? ( + + {title} + + ) : null} + + ) : null} +
+ {children} +
+ {footer ? ( + + {footer} + + ) : null} +
+
+ ) +} + +export { AppFormDialog } diff --git a/frontend/app/components/common/ConfirmDialog.tsx b/frontend/app/components/common/ConfirmDialog.tsx new file mode 100644 index 0000000..a9791ea --- /dev/null +++ b/frontend/app/components/common/ConfirmDialog.tsx @@ -0,0 +1,74 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "~/components/ui/alert-dialog" +import { cn } from "~/lib/utils" + +import { + dialogPrimaryButtonClass, + dialogSecondaryButtonClass, +} from "./AppFormDialog" + +type ConfirmDialogProps = { + open: boolean + onOpenChange: (open: boolean) => void + title: string + description: string + confirmLabel?: string + cancelLabel?: string + destructive?: boolean + onConfirm: () => void | Promise +} + +function ConfirmDialog({ + open, + onOpenChange, + title, + description, + confirmLabel = "确定", + cancelLabel = "取消", + destructive = false, + onConfirm, +}: ConfirmDialogProps) { + return ( + + + + {title} + + {description} + + + + + {cancelLabel} + + void onConfirm()} + > + {confirmLabel} + + + + + ) +} + +export { ConfirmDialog } diff --git a/frontend/app/features/admin/ImportMemberDialog.tsx b/frontend/app/features/admin/ImportMemberDialog.tsx index d49e15b..bdf38d7 100644 --- a/frontend/app/features/admin/ImportMemberDialog.tsx +++ b/frontend/app/features/admin/ImportMemberDialog.tsx @@ -1,25 +1,16 @@ -import { X } from "lucide-react"; import { type Employee, type Workspace } from "../../services/api"; +import { + AppFormDialog, + dialogPrimaryButtonClass, + dialogSecondaryButtonClass, +} from "~/components/common/AppFormDialog"; import { Button } from "~/components/ui/button"; import { formFieldClass, - modalBackdropClass, - modalCompactClass, - modalEyebrowClass, - modalFooterClass, modalFormClass, - modalHeaderClass, - modalTitleClass, } from "../platform/modalUi"; import { UserMultiSelect } from "./UserMultiSelect"; -const CLOSE_BUTTON_CLASS = - "size-[34px] rounded-[7px] border border-[#dfe6ed] bg-white hover:bg-[#f7faff] hover:border-[#b9c9da]"; -const SECONDARY_BUTTON_CLASS = - "h-[37px] gap-[7px] rounded-md border-[#d8e1e9] bg-white px-[15px] text-xs font-[650] text-[#56687c]"; -const PRIMARY_BUTTON_CLASS = - "h-[37px] gap-[7px] rounded-md border border-[#126ac3] bg-gradient-to-br from-[#1881e7] to-[#1268c9] px-[15px] text-xs font-[650] text-white shadow-[0_6px_15px_rgb(20_111_202/18%)] enabled:hover:from-[#1175d8] enabled:hover:to-[#0e5bad]"; - export function ImportMemberDialog({ open, selectedProject, @@ -27,7 +18,6 @@ export function ImportMemberDialog({ selectedUserIds, existingMemberIds, saving, - onNotify, onChangeSelectedUserIds, onSubmit, onClose, @@ -43,60 +33,49 @@ export function ImportMemberDialog({ onSubmit: () => Promise | void; onClose: () => void; }) { - if (!open || !selectedProject) return null; - return ( -
-
-
-
- IMPORT MEMBER -

导入成员到 {selectedProject?.workspace_name ?? "项目"}

-
+ { + if (!nextOpen) onClose(); + }} + eyebrow="IMPORT MEMBER" + title={`导入成员到 ${selectedProject?.workspace_name ?? "项目"}`} + footer={ + <> -
-
- -
- - -
-
-
-
+ + + } + > +
+ +
+ ); } diff --git a/frontend/app/features/admin/PermissionEditDialog.tsx b/frontend/app/features/admin/PermissionEditDialog.tsx index 74ef8c9..c4902d8 100644 --- a/frontend/app/features/admin/PermissionEditDialog.tsx +++ b/frontend/app/features/admin/PermissionEditDialog.tsx @@ -1,26 +1,15 @@ import { useEffect, useState } from "react"; -import { X } from "lucide-react"; import type { PlatformPermission, Role } from "../../services/api"; -import { Button } from "~/components/ui/button"; import { - modalBackdropClass, - modalCompactClass, - modalEyebrowClass, - modalFooterClass, - modalFormClass, - modalHeaderClass, - modalTitleClass, -} from "../platform/modalUi"; + AppFormDialog, + dialogPrimaryButtonClass, + dialogSecondaryButtonClass, +} from "~/components/common/AppFormDialog"; +import { Button } from "~/components/ui/button"; +import { modalFormClass } from "../platform/modalUi"; import { PermissionCheckboxList } from "./PermissionCheckboxList"; -const CLOSE_BUTTON_CLASS = - "size-[34px] rounded-[7px] border border-[#dfe6ed] bg-white hover:bg-[#f7faff] hover:border-[#b9c9da]"; -const SECONDARY_BUTTON_CLASS = - "h-[37px] gap-[7px] rounded-md border-[#d8e1e9] bg-white px-[15px] text-xs font-[650] text-[#56687c]"; -const PRIMARY_BUTTON_CLASS = - "h-[37px] gap-[7px] rounded-md border border-[#126ac3] bg-gradient-to-br from-[#1881e7] to-[#1268c9] px-[15px] text-xs font-[650] text-white shadow-[0_6px_15px_rgb(20_111_202/18%)] enabled:hover:from-[#1175d8] enabled:hover:to-[#0e5bad]"; - export function PermissionEditDialog({ open, target, @@ -41,7 +30,6 @@ export function PermissionEditDialog({ const [selectedCodes, setSelectedCodes] = useState(() => { if (!open || !target) return []; const initial = [...currentCodes]; - // admin 角色必须保留 system:view(后端守卫);前端锁定勾选兜底。 if (target.role_code === "admin" && !initial.includes("system:view")) { initial.push("system:view"); } @@ -69,45 +57,22 @@ export function PermissionEditDialog({ ); }; - if (!open || !target) return null; - return ( -
-
-
-
- PERMISSIONS -

权限配置 · {target.role_name}

-
- -
-
- {target.role_code === "admin" && ( -

admin 角色必须保留 system:view 权限。

- )} - -
-
+ { + if (!nextOpen) onClose(); + }} + eyebrow="PERMISSIONS" + title={target ? `权限配置 · ${target.role_name}` : "权限配置"} + footer={ + <> @@ -117,12 +82,26 @@ export function PermissionEditDialog({ type="button" disabled={saving} onClick={() => onSubmit(selectedCodes)} - className={PRIMARY_BUTTON_CLASS} + className={dialogPrimaryButtonClass} > {saving ? "保存中…" : "保存权限"} -
-
-
+ + } + > +
+ {target?.role_code === "admin" && ( +

admin 角色必须保留 system:view 权限。

+ )} + {target ? ( + + ) : null} +
+ ); } diff --git a/frontend/app/features/admin/ProjectEditDialog.tsx b/frontend/app/features/admin/ProjectEditDialog.tsx index f03a6ec..410e833 100644 --- a/frontend/app/features/admin/ProjectEditDialog.tsx +++ b/frontend/app/features/admin/ProjectEditDialog.tsx @@ -1,25 +1,17 @@ -import { X } from "lucide-react"; import { type Workspace } from "../../services/api"; +import { + AppFormDialog, + dialogPrimaryButtonClass, + dialogSecondaryButtonClass, +} from "~/components/common/AppFormDialog"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; import { Textarea } from "~/components/ui/textarea"; import { formFieldClass, - modalBackdropClass, - modalCompactClass, - modalEyebrowClass, - modalFooterClass, modalFormClass, - modalHeaderClass, - modalTitleClass, } from "../platform/modalUi"; -const CLOSE_BUTTON_CLASS = - "size-[34px] rounded-[7px] border border-[#dfe6ed] bg-white hover:bg-[#f7faff] hover:border-[#b9c9da]"; -const SECONDARY_BUTTON_CLASS = - "h-[37px] gap-[7px] rounded-md border-[#d8e1e9] bg-white px-[15px] text-xs font-[650] text-[#56687c]"; -const PRIMARY_BUTTON_CLASS = - "h-[37px] gap-[7px] rounded-md border border-[#126ac3] bg-gradient-to-br from-[#1881e7] to-[#1268c9] px-[15px] text-xs font-[650] text-white shadow-[0_6px_15px_rgb(20_111_202/18%)] enabled:hover:from-[#1175d8] enabled:hover:to-[#0e5bad]"; const FORM_INPUT_CLASS = "h-[39px] w-full rounded-md border border-[#d6e0e9] bg-white px-[11px] text-xs text-[#283a4e] outline-none focus:border-[#5b9ddb] focus:shadow-[0_0_0_3px_rgb(38_125_207/8%)] disabled:cursor-not-allowed disabled:opacity-50"; const FORM_TEXTAREA_CLASS = @@ -30,7 +22,6 @@ export function ProjectEditDialog({ editingProject, projectForm, saving, - onNotify, onChangeForm, onSubmit, onClose, @@ -44,91 +35,78 @@ export function ProjectEditDialog({ onSubmit: (event: React.FormEvent) => Promise | void; onClose: () => void; }) { - if (!open) return null; - return ( -
-
-
-
- PROJECT -

{editingProject ? "编辑项目" : "新建项目"}

-
+ { + if (!nextOpen) onClose(); + }} + eyebrow="PROJECT" + title={editingProject ? "编辑项目" : "新建项目"} + > +
void onSubmit(event)}> + {!editingProject && ( + + )} + + +