Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f6ccda6e3 | ||
|
|
f849e443d7 |
No files matched your search
@@ -11,7 +11,7 @@ import { PageBreadcrumbs } from './PageBreadcrumbs';
|
||||
import { SideNav } from './SideNav';
|
||||
import { TopBar } from './TopBar';
|
||||
|
||||
/** 用户与组织、部门管理为「满高主从布局」,由页面自己控制内部滚动 */
|
||||
/** 用户管理、部门管理为「满高主从布局」,由页面自己控制内部滚动 */
|
||||
function isFlushRoute(pathname: string): boolean {
|
||||
return (
|
||||
pathname === ROUTES.system.users ||
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('面包屑子路由补全', () => {
|
||||
expect(getUserSectionTrail('/system/users/new')?.map((entry) => entry.label)).toEqual([
|
||||
'工作台',
|
||||
'管理中心',
|
||||
'用户与组织',
|
||||
'用户管理',
|
||||
'新建用户',
|
||||
]);
|
||||
expect(getUserSectionTrail('/system/users/01HZZZZZZZZZZZZZZZZZZZZZZZ')).not.toBeNull();
|
||||
|
||||
@@ -25,14 +25,14 @@ export function getRoleSectionTrail(pathname: string): BreadcrumbEntry[] | null
|
||||
|
||||
/**
|
||||
* 用户模块下的子路由(新建 / 详情)不在 nav-config 的菜单项里,
|
||||
* 这里补齐面包屑:工作台 / 管理中心 / 用户与组织 / 当前页。
|
||||
* 这里补齐面包屑:工作台 / 管理中心 / 用户管理 / 当前页。
|
||||
*/
|
||||
export function getUserSectionTrail(pathname: string): BreadcrumbEntry[] | null {
|
||||
if (pathname === ROUTES.system.userNew) {
|
||||
return [
|
||||
{ key: 'home', label: '工作台', path: ROUTES.home },
|
||||
{ key: 'system', label: '管理中心' },
|
||||
{ key: 'users', label: '用户与组织', path: ROUTES.system.users },
|
||||
{ key: 'users', label: '用户管理', path: ROUTES.system.users },
|
||||
{ key: 'user-new', label: '新建用户' },
|
||||
];
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export function getUserSectionTrail(pathname: string): BreadcrumbEntry[] | null
|
||||
return [
|
||||
{ key: 'home', label: '工作台', path: ROUTES.home },
|
||||
{ key: 'system', label: '管理中心' },
|
||||
{ key: 'users', label: '用户与组织', path: ROUTES.system.users },
|
||||
{ key: 'users', label: '用户管理', path: ROUTES.system.users },
|
||||
{ key: 'user-detail', label: '成员详情' },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ describe('导航配置', () => {
|
||||
expect(modules.map((module) => module.key)).toEqual(['dashboard']);
|
||||
});
|
||||
|
||||
it('子路由(/system/users/new、/system/users/:id)也归属用户与组织', () => {
|
||||
expect(findActiveNav(NAV_MODULES, '/system/users/new')?.item?.label).toBe('用户与组织');
|
||||
it('子路由(/system/users/new、/system/users/:id)也归属用户管理', () => {
|
||||
expect(findActiveNav(NAV_MODULES, '/system/users/new')?.item?.label).toBe('用户管理');
|
||||
expect(findActiveNav(NAV_MODULES, '/system/users/01HZZZZZZZZZZZZZZZZZZZZZZZ')?.module.key).toBe(
|
||||
'system',
|
||||
);
|
||||
@@ -63,7 +63,7 @@ describe('导航配置', () => {
|
||||
it('面包屑从导航配置派生', () => {
|
||||
expect(getBreadcrumbs(NAV_MODULES, '/')).toEqual([{ key: 'home', label: '工作台', path: '/' }]);
|
||||
const breadcrumbs = getBreadcrumbs(NAV_MODULES, '/system/users');
|
||||
expect(breadcrumbs.map((entry) => entry.label)).toEqual(['工作台', '管理中心', '用户与组织']);
|
||||
expect(breadcrumbs.map((entry) => entry.label)).toEqual(['工作台', '管理中心', '用户管理']);
|
||||
const deptBreadcrumbs = getBreadcrumbs(NAV_MODULES, '/system/departments');
|
||||
expect(deptBreadcrumbs.map((entry) => entry.label)).toEqual(['工作台', '管理中心', '部门管理']);
|
||||
});
|
||||
|
||||
@@ -249,11 +249,11 @@ export const NAV_MODULES: readonly NavModule[] = [
|
||||
items: [
|
||||
{
|
||||
key: 'users',
|
||||
label: '用户与组织',
|
||||
label: '用户管理',
|
||||
path: '/system/users',
|
||||
icon: IconUser,
|
||||
permission: Permission.USER_VIEW,
|
||||
description: '账号、组织与登录状态',
|
||||
description: '成员账号、部门归属与登录状态',
|
||||
},
|
||||
{
|
||||
key: 'departments',
|
||||
|
||||
@@ -83,7 +83,7 @@ function TemplateList({
|
||||
onRetry={onRetry}
|
||||
/>
|
||||
) : (
|
||||
<div role="listbox" aria-label="选择模板">
|
||||
<div className="lx-role-template-list" role="listbox" aria-label="选择模板">
|
||||
<button
|
||||
type="button"
|
||||
role="option"
|
||||
|
||||
@@ -31,7 +31,7 @@ function UsersHeader(): ReactNode {
|
||||
return (
|
||||
<header className="lx-users__header">
|
||||
<div>
|
||||
<h1>用户与组织</h1>
|
||||
<h1>用户管理</h1>
|
||||
<p>管理成员账号、角色与客户归属</p>
|
||||
</div>
|
||||
<Can permission={Permission.USER_MANAGE}>
|
||||
|
||||
@@ -96,10 +96,17 @@
|
||||
}
|
||||
|
||||
.lx-dept-tree__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.lx-dept-tree__children {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
margin-top: 2px;
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
@@ -110,15 +117,16 @@
|
||||
gap: 6px;
|
||||
padding: 4px 6px;
|
||||
border-radius: var(--lx-radius-md);
|
||||
transition: background var(--lx-motion-fast) var(--lx-ease-out);
|
||||
}
|
||||
|
||||
.lx-dept-row:hover {
|
||||
background: var(--lx-color-bg-hover);
|
||||
}
|
||||
|
||||
/* 选中态:品牌浅底 + 品牌色标题(与侧栏导航同一套语言,不再画左侧竖条) */
|
||||
.lx-dept-row.is-selected {
|
||||
background: var(--lx-color-brand-soft);
|
||||
box-shadow: inset 2px 0 0 var(--lx-color-brand);
|
||||
}
|
||||
|
||||
.lx-dept-row.is-disabled .lx-dept-row__name b,
|
||||
@@ -171,6 +179,7 @@
|
||||
|
||||
.lx-dept-row.is-selected .lx-dept-row__name b {
|
||||
color: var(--lx-color-brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lx-dept-row__meta {
|
||||
|
||||
@@ -220,8 +220,9 @@
|
||||
.lx-role-list__items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding: 6px 8px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@@ -229,20 +230,16 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 64px;
|
||||
min-height: 60px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px 8px 14px;
|
||||
border-bottom: 1px solid var(--lx-color-border);
|
||||
padding: 8px 10px 8px 12px;
|
||||
border-radius: var(--lx-radius-md);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: background var(--lx-motion-fast) var(--lx-ease-out);
|
||||
}
|
||||
|
||||
.lx-role-list__items li:last-child .lx-role-row {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.lx-role-row:hover {
|
||||
background: var(--lx-color-bg-hover);
|
||||
}
|
||||
@@ -252,9 +249,9 @@
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* 选中态:品牌浅底 + 品牌色标题(与侧栏导航同一套语言,不再画左侧竖条) */
|
||||
.lx-role-row.is-selected {
|
||||
background: var(--lx-color-brand-soft);
|
||||
box-shadow: inset 2px 0 0 var(--lx-color-brand);
|
||||
}
|
||||
|
||||
.lx-role-row__icon {
|
||||
@@ -302,17 +299,16 @@
|
||||
|
||||
.lx-role-row.is-selected .lx-role-row__name b {
|
||||
color: var(--lx-color-brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lx-role-row.is-disabled .lx-role-row__name b {
|
||||
color: var(--lx-color-text-secondary);
|
||||
}
|
||||
|
||||
/* 块级 + ellipsis:角色编码与数据范围可能超宽,避免被侧栏裁掉半个字 */
|
||||
.lx-role-row__meta {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: var(--lx-font-size-xs);
|
||||
line-height: 16px;
|
||||
color: var(--lx-color-text-secondary);
|
||||
@@ -322,13 +318,14 @@
|
||||
}
|
||||
|
||||
.lx-role-row__dot {
|
||||
margin: 0 4px;
|
||||
color: var(--lx-color-border-strong);
|
||||
}
|
||||
|
||||
.lx-role-row__side {
|
||||
display: flex;
|
||||
width: 132px;
|
||||
flex: 0 0 132px;
|
||||
width: 112px;
|
||||
flex: 0 0 112px;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
@@ -756,13 +753,20 @@
|
||||
padding: var(--lx-space-4);
|
||||
}
|
||||
|
||||
.lx-role-template-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.lx-role-template {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid var(--lx-color-border);
|
||||
padding: 10px 12px;
|
||||
border-radius: var(--lx-radius-md);
|
||||
text-align: left;
|
||||
transition: background var(--lx-motion-fast) var(--lx-ease-out);
|
||||
}
|
||||
@@ -776,9 +780,9 @@
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* 选中态:品牌浅底 + 品牌色标题(与侧栏导航同一套语言,不再画左侧竖条) */
|
||||
.lx-role-template.is-active {
|
||||
background: var(--lx-color-brand-soft);
|
||||
box-shadow: inset 2px 0 0 var(--lx-color-brand);
|
||||
}
|
||||
|
||||
.lx-role-template__icon {
|
||||
@@ -817,6 +821,7 @@
|
||||
|
||||
.lx-role-template.is-active .lx-role-template__name {
|
||||
color: var(--lx-color-brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lx-role-template__desc {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* A1 · 用户与组织(方案 B:主从分栏 + 分屏登录) */
|
||||
/* A1 · 用户管理(方案 B:主从分栏 + 分屏登录) */
|
||||
|
||||
/* ---------- 页面骨架 ---------- */
|
||||
|
||||
@@ -159,31 +159,29 @@
|
||||
.lx-user-list__items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.lx-user-row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 64px;
|
||||
min-height: 60px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px var(--lx-space-4);
|
||||
border-bottom: 1px solid var(--lx-color-border);
|
||||
padding: 8px 12px;
|
||||
border-radius: var(--lx-radius-md);
|
||||
text-align: left;
|
||||
transition: background var(--lx-motion-fast) var(--lx-ease-out);
|
||||
}
|
||||
|
||||
.lx-user-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.lx-user-row:hover {
|
||||
background: var(--lx-color-bg-hover);
|
||||
}
|
||||
|
||||
/* 选中态:品牌浅底 + 品牌色标题(与侧栏导航同一套语言,不再画左侧竖条) */
|
||||
.lx-user-row.is-selected {
|
||||
background: var(--lx-color-brand-soft);
|
||||
box-shadow: inset 2px 0 0 var(--lx-color-brand);
|
||||
}
|
||||
|
||||
.lx-user-row__avatar {
|
||||
@@ -224,6 +222,7 @@
|
||||
|
||||
.lx-user-row.is-selected .lx-user-row__name {
|
||||
color: var(--lx-color-brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lx-user-row__phone {
|
||||
|
||||
@@ -63,7 +63,7 @@ apps/web/src/pages/
|
||||
├─ assets/ # 资产(对应 /assets)
|
||||
├─ delivery/ # 交付(对应 /delivery)
|
||||
├─ system/ # 管理中心(对应 /system)
|
||||
│ └─ users/ # 用户与组织(对应 /system/users)
|
||||
│ └─ users/ # 用户管理(对应 /system/users)
|
||||
│ ├─ UsersPage.tsx
|
||||
│ ├─ UserNewPage.tsx
|
||||
│ ├─ UserListPanel.tsx
|
||||
|
||||
@@ -408,7 +408,7 @@ async function submitLogin(page: Page): Promise<void> {
|
||||
|
||||
async function expectUsersPage(page: Page): Promise<void> {
|
||||
await expect(page.getByTestId('app-shell')).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: '用户与组织' })).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: '用户管理' })).toBeVisible();
|
||||
}
|
||||
|
||||
async function readSession(page: Page): Promise<SessionSnapshot> {
|
||||
@@ -641,7 +641,7 @@ test('④ 停用后旧会话被拒(40301),再登录提示「账号已停
|
||||
await page.goto('/system/users');
|
||||
await expect(page).toHaveURL(/\/login/);
|
||||
await expect(page.locator('.lx-login__notice')).toContainText('账号已被停用');
|
||||
await expect(page.getByRole('heading', { name: '用户与组织' })).toHaveCount(0);
|
||||
await expect(page.getByRole('heading', { name: '用户管理' })).toHaveCount(0);
|
||||
await expect
|
||||
.poll(
|
||||
async () =>
|
||||
|
||||
Reference in new issue
Block a user