From c3118f3970260813ffe434cd097ed8d3d1a4abc6 Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Thu, 2 Apr 2026 12:23:46 +0800 Subject: [PATCH] style(ui): redesign UserSelectionDialog and navigation bar Redesign UserSelectionDialog with role-based gradient avatars, staggered card entrance animation, animated selection indicators (left accent bar + check icon), and refined footer buttons. Redesign AuthenticatedAppShell header with deep gradient background, animated sliding pill indicator for navigation tabs, compact status indicators with pulsing dot, and gradient logo icon chip. Both components share a cohesive indigo/violet accent color system. Co-Authored-By: Claude Opus 4.6 --- .../src/components/UserSelectionDialog.tsx | 155 ++++++++++++---- .../components/app/AuthenticatedAppShell.tsx | 167 ++++++++++++------ 2 files changed, 240 insertions(+), 82 deletions(-) diff --git a/src/renderer/src/components/UserSelectionDialog.tsx b/src/renderer/src/components/UserSelectionDialog.tsx index 6b39da3..61d6237 100644 --- a/src/renderer/src/components/UserSelectionDialog.tsx +++ b/src/renderer/src/components/UserSelectionDialog.tsx @@ -7,8 +7,9 @@ * - Return selected user info */ -import React, { useState, useRef } from 'react' +import React, { useState, useRef, useEffect } from 'react' import { Modal } from './ui/Modal' +import { Users, Shield, UserCircle, Check } from 'lucide-react' export interface UserInfo { id: number @@ -37,6 +38,12 @@ export const UserSelectionDialog: React.FC = ({ const [selectedUserId, setSelectedUserId] = useState(null) const dialogRef = useRef(null) + useEffect(() => { + if (isOpen) { + setSelectedUserId(null) + } + }, [isOpen]) + const handleConfirm = () => { if (selectedUserId === null) { return @@ -59,11 +66,6 @@ export const UserSelectionDialog: React.FC = ({ onCancel() } - const userTypeStyles: Record = { - Admin: 'bg-amber-50 text-amber-600', - User: 'bg-blue-50 text-blue-600' - } - if (!isOpen) return null return ( @@ -73,67 +75,158 @@ export const UserSelectionDialog: React.FC = ({ title="选择用户" size="md" triggerRef={triggerRef} - initialFocusSelector={users.length > 0 ? '.user-item:first-child' : undefined} + initialFocusSelector={users.length > 0 ? '.user-card:first-child' : undefined} ariaDescribedBy="user-selection-description" > + +
-
当前登录:{currentUsername}
+ {/* Context header */} +
+
+ +
+
+
+ 当前登录 +
+
+ {currentUsername} +
+
+
+

从列表中选择一个用户,双击可直接确认选择

-
-
- {users.map((user) => ( + {/* User list */} +
+ {users.map((user, index) => { + const isSelected = selectedUserId === user.id + const isAdmin = user.userType === 'Admin' + + return (
setSelectedUserId(user.id)} onDoubleClick={() => handleDoubleClick(user)} tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() setSelectedUserId(user.id) } }} > -
- {user.username} - - {user.userType} - + {/* Left accent bar for selected state */} +
+ + {/* Avatar */} +
+ {user.username.charAt(0).toUpperCase()}
- {user.createTime && ( -
- 创建于:{new Date(user.createTime).toLocaleString('zh-CN')} + + {/* Info */} +
+
+ + {user.username} + + + {isAdmin ? ( + + ) : ( + + )} + {user.userType} +
- )} + {user.createTime && ( +
+ {new Date(user.createTime).toLocaleDateString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + })} +
+ )} +
+ + {/* Check indicator */} +
+ +
- ))} -
+ ) + })}
-
-
+ {/* Footer */} +
+
-
双击用户可直接选择
+
+ 双击用户可直接选择 +
diff --git a/src/renderer/src/components/app/AuthenticatedAppShell.tsx b/src/renderer/src/components/app/AuthenticatedAppShell.tsx index 2a9de29..257356a 100644 --- a/src/renderer/src/components/app/AuthenticatedAppShell.tsx +++ b/src/renderer/src/components/app/AuthenticatedAppShell.tsx @@ -1,14 +1,14 @@ -import React, { Suspense } from 'react' +import React, { Suspense, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' import { ArrowUpCircle, - Database, Download, LayoutDashboard, LoaderCircle, LogOut, Settings, Trash2, - User + User, + Zap } from 'lucide-react' import type { DownloadReleaseRequest, @@ -40,6 +40,12 @@ interface AuthenticatedAppShellProps { logoutButtonRef: React.RefObject } +const navItems = [ + { id: 'extractor' as const, label: '数据提取', icon: Download }, + { id: 'cleaner' as const, label: '物料清理', icon: Trash2 }, + { id: 'settings' as const, label: '系统设置', icon: Settings } +] + export function AuthenticatedAppShell({ currentUser, currentPage, @@ -56,11 +62,31 @@ export function AuthenticatedAppShell({ onLogout, logoutButtonRef }: AuthenticatedAppShellProps): React.JSX.Element { - const navItems = [ - { id: 'extractor' as const, label: '数据提取 (Extractor)', icon: }, - { id: 'cleaner' as const, label: '物料验证与清理 (Cleaner)', icon: }, - { id: 'settings' as const, label: '系统设置 (Settings)', icon: } - ] + const navRef = useRef(null) + const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 }) + const [isMeasured, setIsMeasured] = useState(false) + + const isNavPage = navItems.some((item) => item.id === currentPage) + + const updateIndicator = useCallback(() => { + const el = navRef.current?.querySelector( + `[data-nav="${currentPage}"]` + ) as HTMLElement | null + if (!el) return + setIndicatorStyle({ left: el.offsetLeft, width: el.offsetWidth }) + setIsMeasured(true) + }, [currentPage]) + + useLayoutEffect(() => { + updateIndicator() + }, [updateIndicator]) + + useEffect(() => { + if (!navRef.current) return + const observer = new ResizeObserver(updateIndicator) + observer.observe(navRef.current) + return () => observer.disconnect() + }, [updateIndicator]) const showUpdateEntry = !!updateStatus && @@ -83,77 +109,116 @@ export function AuthenticatedAppShell({ return (
-
-
-
-
-
+ {/* Top accent line */} +
+ + {/* Left: Window dots + Logo */} +
+
+
+
+
-
onNavigate('home')} style={{ WebkitAppRegion: 'no-drag' } as React.CSSProperties} > -
- - ERP Auto +
+
- - {__APP_VERSION__}({__GIT_HASH__}) - -
+
+ + ERP Auto + + + {__APP_VERSION__}({__GIT_HASH__}) + +
+
+ {/* Center: Navigation with sliding pill indicator */} + {/* Right: Status + User */}
{showUpdateEntry && ( )} -
- - 数据库已连接 + +
+
+
+
+
+ 已连接
-
- + +
+
+ +
{currentUser?.username} @@ -162,10 +227,10 @@ export function AuthenticatedAppShell({ )}