From 3c5e1c1f7dc2c19653f2239db871a9b3c6d4efa1 Mon Sep 17 00:00:00 2001 From: logan Date: Fri, 29 May 2026 17:16:08 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=9F=E4=B8=80=E5=BC=B9=E6=A1=86?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=20=E2=80=94=20ModalOverlay=E5=AF=B9=E9=BD=90?= =?UTF-8?q?EditInstanceDialog=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ModalOverlay移除遮罩层onClick(不响应外部点击关闭) - 移除body.style.overflow=hidden(修复白条问题) - max-h从90vh改为85vh(与EditInstanceDialog一致) - 10个页面手写fixed弹框替换为ModalOverlay组件 - InstanceGroupPage/WorkflowPage/UsersPage/RedisDetailPage/TicketsPage已替换 - dialog.tsx添加onPointerDownOutside/onInteractOutside阻止外部点击 - index.css添加body[data-scroll-locked]覆盖Radix滚动锁定 - Layout.tsx main添加relative定位上下文 --- frontend/src/App.css | 43 +----------------------- frontend/src/components/Layout.tsx | 2 +- frontend/src/components/ModalOverlay.tsx | 40 ++++++++++++++++++++++ frontend/src/components/ui/dialog.tsx | 20 +++++++++-- frontend/src/index.css | 8 +++++ frontend/src/pages/InstanceGroupPage.tsx | 13 +++---- frontend/src/pages/RedisDetailPage.tsx | 13 +++---- frontend/src/pages/TicketsPage.tsx | 8 ++--- frontend/src/pages/UsersPage.tsx | 37 ++++++++++---------- frontend/src/pages/WorkflowPage.tsx | 14 ++++---- 10 files changed, 111 insertions(+), 87 deletions(-) create mode 100644 frontend/src/components/ModalOverlay.tsx diff --git a/frontend/src/App.css b/frontend/src/App.css index b9d355d..a07442b 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,42 +1 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} +/* App styles — Vite template defaults removed */ diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 7725e17..3fca66b 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -122,7 +122,7 @@ export default function Layout() { {/* 主内容区 */} -
+
{/* 顶部栏 */}
{/* Breadcrumb area */} diff --git a/frontend/src/components/ModalOverlay.tsx b/frontend/src/components/ModalOverlay.tsx new file mode 100644 index 0000000..c1afc18 --- /dev/null +++ b/frontend/src/components/ModalOverlay.tsx @@ -0,0 +1,40 @@ +import { useEffect, useCallback } from 'react'; + +interface ModalOverlayProps { + open: boolean; + onClose: () => void; + width?: string; + className?: string; + children: React.ReactNode; +} + +export function ModalOverlay({ open, onClose, width = 'max-w-lg', className = '', children }: ModalOverlayProps) { + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }, + [onClose], + ); + + useEffect(() => { + if (open) { + document.addEventListener('keydown', handleKeyDown); + } + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [open, handleKeyDown]); + + if (!open) return null; + + return ( +
+
e.stopPropagation()} + > + {children} +
+
+ ); +} diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx index 60cc10e..64ba922 100644 --- a/frontend/src/components/ui/dialog.tsx +++ b/frontend/src/components/ui/dialog.tsx @@ -36,7 +36,9 @@ function DialogOverlay({ + { + e.preventDefault() + }} + onInteractOutside={(e) => { + e.preventDefault() + }} className={cn( - "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg", + "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out", + "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", + "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", + "fixed z-[101] grid w-full max-w-[calc(100%-2rem)]", + "top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]", + "gap-4 rounded-lg border p-6 shadow-lg duration-200", + "outline-none sm:max-w-lg", className )} {...props} diff --git a/frontend/src/index.css b/frontend/src/index.css index 8b744c3..f79de83 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -337,6 +337,14 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans SC', sans-serif; transition: background-color 0.3s ease, color 0.3s ease; } + + /* Fix: Radix Dialog scroll-lock adds padding-right to body, causing white bar. + Override to prevent layout shift when dialog opens. */ + body[data-scroll-locked], + body[data-radix-scroll-locked], + body[style*="padding-right"] { + padding-right: 0 !important; + } code, pre, .font-mono { font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; } diff --git a/frontend/src/pages/InstanceGroupPage.tsx b/frontend/src/pages/InstanceGroupPage.tsx index 994d6a8..121cf80 100644 --- a/frontend/src/pages/InstanceGroupPage.tsx +++ b/frontend/src/pages/InstanceGroupPage.tsx @@ -9,6 +9,7 @@ import { instanceApi } from '@/api/instance'; import { userApi } from '@/api/user'; import { useActionPermission } from '@/hooks/useAuth'; import { ConfirmDialog } from '@/components/ConfirmDialog'; +import { ModalOverlay } from '@/components/ModalOverlay'; /* ───────── Constants ───────── */ @@ -389,8 +390,8 @@ export default function InstanceGroupPage() { {/* Form Modal (create & edit) */} {formMode && ( -
-
e.stopPropagation()}> + +

{formMode === 'create' ? '新建权限策略' : '编辑权限策略'}

@@ -433,7 +434,7 @@ export default function InstanceGroupPage() {
-
+ )} -
e.stopPropagation()}> + setShowUserModal(false)} width="max-w-md"> +

关联用户

@@ -474,7 +475,7 @@ export default function InstanceGroupPage() {
{/* end px-6 py-4 */}
- + )} ); diff --git a/frontend/src/pages/RedisDetailPage.tsx b/frontend/src/pages/RedisDetailPage.tsx index b505391..dc394fa 100644 --- a/frontend/src/pages/RedisDetailPage.tsx +++ b/frontend/src/pages/RedisDetailPage.tsx @@ -3,6 +3,7 @@ import { useParams, useNavigate } from 'react-router-dom'; import { ArrowLeft, Key, Activity, Terminal, Gauge, Plus, RefreshCw, Search } from 'lucide-react'; import { redisApi } from '@/api/redis'; import { ConfirmDialog } from '@/components/ConfirmDialog'; +import { ModalOverlay } from '@/components/ModalOverlay'; type TabKey = 'keys' | 'monitor' | 'cli' | 'perf'; @@ -265,8 +266,8 @@ export default function RedisDetailPage() { {/* 键详情弹窗 */} {selectedKey && ( -
-
+ +

键详情

@@ -282,13 +283,13 @@ export default function RedisDetailPage() {
-
+ )} {/* 新增键弹窗 */} {showAddKey && ( -
-
+ +

新增键

@@ -306,7 +307,7 @@ export default function RedisDetailPage() {
-
+ )} +
e.stopPropagation()} + className="w-full max-h-[85vh] overflow-hidden flex flex-col" > {/* Header */}
@@ -787,6 +787,6 @@ function TicketDetailDialog({ ticket, onClose, canApprove, canExecute, onActionD )}
-
+ ); } diff --git a/frontend/src/pages/UsersPage.tsx b/frontend/src/pages/UsersPage.tsx index 13cc73e..d668b7a 100644 --- a/frontend/src/pages/UsersPage.tsx +++ b/frontend/src/pages/UsersPage.tsx @@ -11,6 +11,7 @@ import { instanceGroupApi, type InstanceGroup } from '@/api/instanceGroup'; import { nameToUsername } from '@/utils/pinyin'; import { toast } from '@/hooks/useToast'; import { ConfirmDialog } from '@/components/ConfirmDialog'; +import { ModalOverlay } from '@/components/ModalOverlay'; /* ───────── Types ───────── */ @@ -566,8 +567,8 @@ function CreateUserModal({ onClose, onCreated }: { onClose: () => void; onCreate }; return ( -
-
e.stopPropagation()}> + +

新建用户

@@ -626,7 +627,7 @@ function CreateUserModal({ onClose, onCreated }: { onClose: () => void; onCreate
-
+ ); } @@ -674,8 +675,8 @@ function EditUserModal({ user, onClose, onUpdated }: { user: UserItem; onClose: }; return ( -
-
e.stopPropagation()}> + +

编辑用户

@@ -740,7 +741,7 @@ function EditUserModal({ user, onClose, onUpdated }: { user: UserItem; onClose:
-
+ ); } @@ -771,8 +772,8 @@ function ResetPasswordModal({ user, onClose }: { user: UserItem; onClose: () => }; return ( -
-
e.stopPropagation()}> + +

重置密码

@@ -798,7 +799,7 @@ function ResetPasswordModal({ user, onClose }: { user: UserItem; onClose: () =>
-
+ ); } @@ -807,8 +808,8 @@ function ResetPasswordModal({ user, onClose }: { user: UserItem; onClose: () => function UserDetailModal({ user, onClose }: { user: UserItem; onClose: () => void }) { return ( -
-
e.stopPropagation()}> + +

用户详情

@@ -864,7 +865,7 @@ function UserDetailModal({ user, onClose }: { user: UserItem; onClose: () => voi
-
+ ); } @@ -1033,8 +1034,8 @@ function BatchImportModal({ onClose, onImported }: { onClose: () => void; onImpo ]; return ( -
-
e.stopPropagation()}> + +
{/* Header */}

批量导入用户

@@ -1238,8 +1239,8 @@ function BatchImportModal({ onClose, onImported }: { onClose: () => void; onImpo {/* Create OU sub-dialog */} {showCreateOu && ( -
-
e.stopPropagation()}> + setShowCreateOu(false)} width="max-w-md"> +

创建新的组织单位 (OU)

@@ -1261,9 +1262,9 @@ function BatchImportModal({ onClose, onImported }: { onClose: () => void; onImpo
-
+ )}
-
+
); } diff --git a/frontend/src/pages/WorkflowPage.tsx b/frontend/src/pages/WorkflowPage.tsx index 586703c..58c8873 100644 --- a/frontend/src/pages/WorkflowPage.tsx +++ b/frontend/src/pages/WorkflowPage.tsx @@ -8,6 +8,7 @@ import { Card, CardContent } from '@/components/ui/card'; import { workflowApi } from '@/api/workflow'; import { userApi } from '@/api/user'; import { ConfirmDialog } from '@/components/ConfirmDialog'; +import { ModalOverlay } from '@/components/ModalOverlay'; /* ------------------------------------------------------------------ */ /* Types */ @@ -320,10 +321,9 @@ function WorkflowEditorModal({ workflow, users, onClose, onSaved }: EditorProps) const inputCls = 'w-full px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none transition-colors'; return ( -
+
e.stopPropagation()} + className="bg-[var(--dg-bg-secondary)] rounded-xl shadow-2xl w-full max-h-[90vh] overflow-y-auto" > {/* Header */}
@@ -455,7 +455,7 @@ function WorkflowEditorModal({ workflow, users, onClose, onSaved }: EditorProps)
-
+ ); } @@ -744,8 +744,8 @@ export default function WorkflowPage() { {/* Preview Modal */} {previewWorkflow && ( -
setPreviewWorkflow(null)}> -
e.stopPropagation()}> + setPreviewWorkflow(null)} width="max-w-2xl"> +

{previewWorkflow.name}

@@ -799,7 +799,7 @@ export default function WorkflowPage() {
-
+
)} {/* Editor Modal */}