diff --git a/frontend/src/routes/user-management/Page.tsx b/frontend/src/routes/user-management/Page.tsx index 23f70e6..f04e967 100644 --- a/frontend/src/routes/user-management/Page.tsx +++ b/frontend/src/routes/user-management/Page.tsx @@ -1,6 +1,6 @@ import type { FormEvent } from 'react' import { useCallback, useEffect, useState } from 'react' -import { Check, Info, Plus, X } from 'lucide-react' +import { ArrowUpRight, Check, Info, Plus, X } from 'lucide-react' import { statusLabel } from '../../constants/user' import type { User, UserForm, Status } from '../../interfaces/user/model' @@ -9,11 +9,13 @@ import styles from './index.module.scss' import DirectoryToolbar from './components/DirectoryToolbar' import SummaryCards from './components/SummaryCards' import UserDrawer from './components/UserDrawer' +import UserProfilePanel from './components/UserProfilePanel' import UserTable from './components/UserTable' export default function UserManagementPage() { const [keyword, setKeyword] = useState('') const [editingUser, setEditingUser] = useState(null) + const [selectedUserId, setSelectedUserId] = useState(null) const [isFormOpen, setIsFormOpen] = useState(false) const [notice, setNotice] = useState('') const { @@ -30,6 +32,16 @@ export default function UserManagementPage() { useEffect(() => { void refreshData() }, [refreshData, search, roleFilter, statusFilter]) + useEffect(() => { + if (users.length === 0) { + setSelectedUserId(null) + return + } + if (!selectedUserId || !users.some((user) => user.id === selectedUserId)) { + setSelectedUserId(users[0].id) + } + }, [selectedUserId, users]) + useEffect(() => { if (!notice) return undefined const timer = window.setTimeout(() => setNotice(''), 3200) @@ -88,25 +100,29 @@ export default function UserManagementPage() { const allSelected = users.length > 0 && selectedIds.size === users.length const activeFilters = Boolean(search || roleFilter || statusFilter) const combinedError = error + const selectedUser = users.find((user) => user.id === selectedUserId) ?? users[0] ?? null return ( -
-
U

云端工作台

组织管理中心

系统运行正常管理员视图
-
-
工作台/用户管理
-

成员目录 / DIRECTORY

用户管理

集中管理组织成员、角色和访问状态。

+
+
U云端工作台组织管理中心
系统运行正常管理员视图
+
+
工作台/用户管理
+

ROSTER / MEMBER RECORDS

用户管理

先找到成员,再查看档案和访问范围。

-

成员列表

查看并更新组织内的账号信息。

共 {total} 位用户
-
- - {selectedIds.size > 0 &&
已选择 {selectedIds.size} 位用户
} - {combinedError &&
{combinedError}
} -
- {isLoading ?
正在加载用户列表
: users.length === 0 ?
没有找到匹配用户试试其他关键词,或直接新增一个用户。{activeFilters && }
: void removeUser(user)} />} -
-
+
+
+

DIRECTORY / 目录

成员列表

共 {total} 位用户
+ + {selectedIds.size > 0 &&
已选择 {selectedIds.size}
} + {combinedError &&
{combinedError}
} +
+ {isLoading ?
正在加载用户列表
: users.length === 0 ?
没有找到匹配用户试试其他关键词,或直接新增一个用户。{activeFilters && }
: void removeUser(user)} />} +
+
+ +
- {notice &&
{notice}
} + {notice &&
{notice}
} setIsFormOpen(false)} onSubmit={(form) => void submitForm(form)} />
) diff --git a/frontend/src/routes/user-management/components/DirectoryToolbar/index.module.scss b/frontend/src/routes/user-management/components/DirectoryToolbar/index.module.scss index 287be9e..abedf2b 100644 --- a/frontend/src/routes/user-management/components/DirectoryToolbar/index.module.scss +++ b/frontend/src/routes/user-management/components/DirectoryToolbar/index.module.scss @@ -1,3 +1,22 @@ -.root { - min-width: 0; +.root { min-width: 0; } +.searchForm { position: relative; display: flex; align-items: center; gap: 8px; padding: 14px 0 12px; border-bottom: 1px solid var(--page-rule); } +.searchIcon { flex: 0 0 auto; color: var(--page-muted); } +.searchForm input { width: min(280px, 100%); height: 36px; padding: 0; border: 0; outline: 0; background: transparent; color: var(--page-ink); font-size: 12px; } +.searchForm input::placeholder { color: var(--page-muted); } +.searchButton { min-height: 30px; padding: 0 10px; border: 1px solid var(--page-rule); background: transparent; color: var(--page-ink); font-size: 11px; font-weight: 700; } +.searchButton:hover { border-color: var(--page-accent); color: var(--page-accent); } +.filterGroup { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 0 7px; } +.filterGroup select { width: 150px; height: 34px; padding: 0 10px; border: 1px solid var(--page-rule); outline: 0; background: var(--page-surface); color: var(--page-ink); font-size: 11px; } +.filterGroup select:focus { border-color: var(--page-accent); box-shadow: 0 0 0 3px rgb(43 118 128 / 11%); } +.clearButton { padding: 0; border: 0; background: transparent; color: var(--page-accent); font-size: 11px; font-weight: 700; } +.clearButton:hover { text-decoration: underline; } +.statusTabs { display: flex; gap: 3px; border-bottom: 1px solid var(--page-rule); } +.statusTabs button { position: relative; min-height: 35px; padding: 0 12px; border: 0; background: transparent; color: var(--page-muted); font-size: 11px; font-weight: 700; } +.statusTabs button:hover, .statusTabs .active { color: var(--page-ink); } +.statusTabs .active::after { position: absolute; right: 9px; bottom: -1px; left: 9px; height: 2px; background: var(--page-accent); content: ""; } +.srOnly { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } + +@media (max-width: 760px) { + .searchForm input { flex: 1; width: auto; } + .filterGroup { padding-top: 10px; } } diff --git a/frontend/src/routes/user-management/components/DirectoryToolbar/index.tsx b/frontend/src/routes/user-management/components/DirectoryToolbar/index.tsx index 7350e77..2cbe408 100644 --- a/frontend/src/routes/user-management/components/DirectoryToolbar/index.tsx +++ b/frontend/src/routes/user-management/components/DirectoryToolbar/index.tsx @@ -20,25 +20,25 @@ interface DirectoryToolbarProps { export default function DirectoryToolbar(props: DirectoryToolbarProps) { return ( <> -
-
- -