基础管理合同
用户、部门与角色是 UG Admin 的正式模板能力,不是静态展示页。React/Vue 页面分别位于 apps/<framework>/src/modules/system/management/,通过标准 AdminDataProvider 操作;框架无关事实源在 packages/admin-core/src/management/index.ts。真实服务的连接方式见 后端 Adapter。
资源与字段
| 资源 | 合同 | 字段 |
|---|---|---|
users |
ManagementUser |
id、username、name、email、status、departmentId、roleIds;可选 phone、remark、只读 createdAt |
departments |
ManagementDepartment |
id、name、parentId、status、sort |
roles |
ManagementRole |
id、name、code、description、permissions、protected |
管理资源 ID 为稳定字符串;AdminUserId 是共享用户主体类型,session.user.id 与 ManagementUser.id 必须映射同一主体。新增业务通过该 ID 关联用户,账号与姓名允许修改,不能作为关系主键。departmentId 与 parentId 没有归属时为 null,多角色和权限使用字符串数组。状态为 active | disabled,不把展示用的“启用/停用”直接写入 API。后端字段不同就由 Adapter 映射,不在页面中维护第二套结构。
角色列表的“功能权限”只表示已授予的操作权限,不表示部门数据范围;当前模型没有 dataScope。protected 表示系统保护,列表与详情统一称“保护状态”,值为“系统保留 / 普通角色”;它不表示创建来源,普通角色仍需经过操作者权限与关系校验才能维护。保护状态列为紧凑辅助列,剩余宽度优先用于角色名称、编码和功能权限。
phone 为最多 32 字符的电话号码,允许国际区号、空格、括号及短横线;remark 最多 500 字符。createdAt 由后台生成并在更新时保留,不由编辑表单提交或补造。既有 DTO 未提供这些可选字段仍可使用,缺失手机和创建时间显示“—”。
用户模型不含密码。真实开户、邀请、密码重置、登录方式和凭据存储属于认证后端范围,不把明文密码、密码散列或服务端令牌放入前端资源记录。
权限与业务保护
packages/admin-core/src/management/permissions.ts 使用 getManagementPermissionCatalog() / getManagementPermissionCodes() 提供当前只读目录。内置核心为用户、部门、角色、字典类型、字典条目五类管理资源的 list/read/create/update/delete 与 settings:read。页面准入和行级操作使用对应资源动作,不能只检查菜单是否可见。字典资源的字段与业务消费见 字典合同。
业务扩展在模块 index.ts 的 permissions 中声明稳定的 code/label/group 和可选 description,由应用统一交给通用权限目录。重复码、无效 resource:action 或空名称/分组立即报配置错误。system 提供管理权限,examples 仅启用时提供学习权限;新业务不修改管理模块或核心。旧的 configureManagementPermissions(extensions) 保留给直接使用管理 helper 的消费者,会替换目录;模块化应用不再单独调用它。登记只使权限可解释、可选择,不会授予操作者任何权限。
角色权限编辑使用公开 UGSelectionList,支持名称/码搜索、分组以及可见可操作项的全选/半选。筛选外项和锁定项不会被批量动作丢失;超出自身可授予范围的项禁选。既有未登记码显示原码与“既有权限只读保留”,不能新增或删除;先在业务启动目录登记后才能调整。已持久化角色不会因目录配置变化被迁移清删,服务端仍应实施同等校验。
权限目录中的页面访问项必须与 packages/admin-core/src/management/pages.ts 的实际准入一致:自定义角色获得 settings:read 后即可访问系统设置,不附加固定角色名称要求。其他非管理示例页面仍以路由登记的角色或权限声明为准;新增可分配的页面访问权限时,必须同步菜单与路由合同,不能提供授予后仍无法进入对应页面的权限项。
页面访问与记录操作独立授权,不自动补选其他权限。面包屑和菜单使用当前身份快照;无权访问祖先页面时保留层级名称,不提供点击后才被拒绝的祖先链接。
默认访问控制按权限数组判断;resource:* 覆盖该资源动作,isSuperAdmin: true 或全局 * 表达功能全权。真实服务器必须自行产生可信权限快照,不能信任客户端传入的权限或角色。仅把角色字符串命名为 superadmin 不会自动使通用 HTTP 身份全权;演示提供者才会根据内置角色计算该标记。
以下保护在演示 Provider 中模拟,生产必须在服务端原子地实施:
- 不编辑或删除
protected内置角色;superadmin是演示保留角色编码,不能通过普通角色表单创建。 - 不删除、停用当前登录用户;始终保留至少一名启用的超级管理员。
- 不授予超出操作者自身范围的权限或角色,不修改权限高于操作者的用户或角色。
- 部门不能归入自身或后代;仍有下级或关联用户的部门不能删除。
- 停用部门不能新增用户或下级归属;已经绑定的停用部门可以显示并保留,避免修改无关字段时误移归属。
- 仍有用户使用的角色不能删除;删除前调整关联,不静默级联清除用户权限。
- 校验账号唯一、同级部门名称唯一、角色编码唯一以及关联 ID 的有效性。
全权是功能授权,不是跳过这些业务不变量。服务端还需按实际项目补充数据范围、并发控制、审计和会话撤销;模板不预建多租户或后端项目。
列表、表单与一致性
部门、角色、用户与字典列表采用同一层次:可见标签的搜索区、按需出现的独立条件摘要、32px 表格外工具栏、填满剩余高度的表格。查询区和工具栏统一使用 12px 纵向、16px 横向内部留白;窄屏使用 8px 留白,外层保持直接衔接页签的平面工作区。所有查询动作按“查询、重置”排列;标签靠右,字段内 8px、组间 16px,颜色仅表达主要动作或危险操作。部门与角色的生效关键词和待查询提示同样归入“当前筛选条件”区域,搜索隐藏后仍可见;清除最后关键词后聚焦持续存在的搜索显隐按钮,不落到页面背景。部门与角色关键词在点击“查询”或回车后生效,输入中显示“条件待查询”;搜索显隐保留草稿,隐藏时仍能识别并清除已生效关键词。重置与关键词标签只清查询,不重置部门展开状态或角色每页条数。已生效关键词用 q、角色分页用 page/size、部门折叠节点用重复 collapsed 保存在 URL,纯解析与序列化位于 packages/admin-core/src/management/management-list-state.ts,保留 detail 和接入项目的其他查询参数。角色默认每页 20 条,支持 10/20/50/100;返回和刷新恢复已提交上下文,不保存弹窗及未提交草稿。
部门关键词只匹配名称并保留祖先展示路径,查询中临时展开命中路径并说明“清空搜索后可折叠部门”,清空后恢复此前折叠状态。授权数据缺失上级或包含自引用、循环时,表格为这些部门建立有说明的临时展示根,保证授权记录不静默消失或重复;原始 parentId 不变,不将展示降级当作已修复关系。异常节点显示“层级异常”及原因,编辑仍由真实关系校验约束;depth/hasChildren/relationWarning 都不进入写入载荷。
三类正式管理编辑统一使用公开 UGForm 横排标签、默认控件尺寸与 UGModal width=600 / placement=top / topOffset=6vh;部门四个字段为双列,角色名称与编码为双列、说明和功能权限跨列,手机改为单列。字段通过公开 FormField/FormItem/FormLabel/FormControl/FormMessage 关联输入和错误,不用控件自身标签拼接第二套间距。新建统一为 solid/primary,刷新、列设置与搜索显隐使用 ghost/neutral 的 32px 纯图标按钮;行内操作使用 icon-sm 并有访问名称与悬停说明。角色行内的“查看角色成员”直接进入带 role 条件的用户列表,保留角色同样可查看成员,权限仍要求 users:list;受保护标记只在保护状态列呈现,不再重复占操作列。角色编码及权限摘要在实际列宽内省略,详情继续提供完整资料。
用户编辑按“基本资料”和“组织与权限”两段组织,姓名与账号先于联系方式;备注跨列。三类表单共用业务字段封装与公开 FormDescription,单选组直接交给 FormControl,通过可访问标签和描述关联真实组控件,不将外层布局容器误当作表单控件。字段错误保持至该字段相对提交快照发生修改;改回原提交值时仍显示该次错误。一次提交失败后滚动并聚焦首个错误字段,错误文本同字段进入可视范围;未知字段或全局错误则聚焦可访问的错误摘要。更正输入期间不重复抢焦点,加载失败重试保持自然宽度。
用户详情按资料、组织权限分组,部门详情展示完整路径与状态,角色详情按功能分组展示完整权限码;长值换行,关联名称继续受目标 list + read 权限限制。读取完成后,具备对应记录 update 权限且不是系统保留角色时,详情提供“编辑用户 / 编辑部门 / 编辑角色”。交接先关闭详情并恢复稳定记录入口,再打开原有最新资料编辑流程;编辑取消、保存或确认放弃后恢复该记录的当前入口,记录已退出查询结果时回到持续存在的搜索显隐按钮。交接只观察公开对话框语义与业务自有记录 ID,不依赖组件内部选择器、固定延时或第二份读取授权。
部门管理使用真实层级 UGTreeSelect 选择上级,自身与全部后代禁选;新增下级预填当前部门,仅启用部门开放此动作。部门名称可打开只读详情,显示组织路径、状态、排序和保护说明;“成员”按 department URL 查询进入用户页,Provider 在分页前包含该部门及后代用户,绝不只筛当前页。
角色列表显示权限数量与分组摘要,完整权限码与说明在重新读取后的只读详情中展示,系统保留角色同样可查看。角色详情“查看关联用户”通过 URL role 进入用户列表,映射为 filters.roleId 精确匹配 user.roleIds,可以与部门、关键词、状态、日期复合筛选。用户查询默认四组保持紧凑,额外角色条件通过“展开用户筛选”显示;已生效角色显示可关闭标签,清除角色仍提交其他草稿。用户详情里的组织/角色在具备目标 list + read 权限时跳转管理页面的 detail=<id> 只读详情,无权时保留名称,不额外请求完整关联资料。详情参数不扩大页面与记录读取授权。
部门、角色与用户编辑在具备对应 read 权限时先 getOne 求证最新资料,加载/错误期间不展示旧表单;无读取但有列表和更新权限时只编辑已授权列表字段。三个资源的更新统一只 PATCH 变化字段;树的 depth/hasChildren、用户只读 createdAt 和角色受保护标记不进入更新载荷。服务端必须以部分更新解释未传字段,不清空它们;冲突检测仍由服务端版本或 ETag 完成。只读详情每次重开都重新读取,读取中不显示旧记录,错误可重试。
部门、用户和角色管理同属“组织与权限”模块。组织栏只把真实部门渲染为树节点;独立的“全部用户”按钮表示全部部门范围,清除 departmentId 与部门树搜索,保留并提交其余查询草稿,不伪造一个部门节点。默认范围不重复说明;生效的具体部门用可关闭标签表达,关闭操作同样保留并提交其余查询草稿并清空部门树搜索,选中记录时仍显示该筛选标签。总数仍只由分页摘要显示,页面保留唯一的语义 h1。
正式列表工作区统一为 surface-1 平面,外层 main/content 四周内边距、路由区间距为零,直接衔接页签下沿;AdminListPage 和用户组织工作区不再使用整页 Card 外框,内部查询、操作和组织栏保留必要留白及分割线。用户页在查询之后、记录操作之前,通过独立 summary 入口呈现名为“当前筛选条件”的区域:包含已生效关键词、部门、角色、状态、手机和创建时间,以及待查询或校验反馈;没有这些内容时桌面不留空摘要行。搜索隐藏后摘要仍可识别、清除;“已选 N 项”和“清空选择”始终属于 CRUD 操作区。标签关闭继续提交其余草稿并经过日期校验,成功后聚焦更新后的摘要;最后条件清空时聚焦搜索显隐按钮,校验失败保留原关闭入口和错误。长标签在可用宽度内省略,完整值通过标题可达,关闭按钮不被长文案挤走。
组织栏的层级行使用 UG UI UGTree size="md" 的 32px 行高,通过公开 React renderNodeIcon 与 Vue node-icon 插槽显示 16px 部门图标,保留真实层级;搜索框、全部展开/折叠和筛选状态由 Admin 组合,树的键盘导航、展开热区、选中视觉和节点 Anatomy 不由页面重画。部门搜索非空时临时展示命中路径,禁用标题展开/折叠入口并说明“清空搜索后可折叠部门”,不修改隐藏的展开状态。组织标题行与首行查询同为 32px,左右内容保留 16px 留白,与查询和操作区使用相同的 12px 纵向留白,只保留真实分割线,不叠加部门栏右边框。部门搜索位于独立的“全部用户”按钮之前;该按钮使用公开 default 尺寸与 ghost 外观,以 aria-pressed 表达选中范围,在搜索无结果时仍可清除范围与树搜索。
1024px 起使用水平 UGSplitter 支持拖动、方向键及 Enter 调宽和折叠;不显示中段悬浮折叠把手,右侧工具栏的持续入口使用公开 32px icon 按钮,并以 aria-controls 关联稳定组织面板 ID、aria-expanded 表达状态。折叠/恢复保留同一按钮节点和键盘焦点。部门栏初始 224px,可在 180–320px 间调整,业务容器宽度换算为公开受控百分比尺寸;零宽折叠、窗口调整以及窄屏再回桌面仍保留最近展开宽度。更窄视口通过条件摘要区持续可见的“选择部门范围”按钮打开公开 UGPopover,即使查询隐藏且没有生效条件也能操作,选择后关闭;不在页面常驻展开整棵组织树。部门数据仍从 users/options 取得,不额外要求 departments:list。
用户提供查询、分页、CRUD、启停及部门/角色选择。组织快捷筛选点击即提交当前可见的全部查询草稿;上方字段随“查询”或回车一起提交,未提交草稿显示“条件待查询”。节点选择与展开/收起是两个独立动作;部门搜索临时显示匹配路径,清空后恢复先前展开状态。用户工作区沿连续主平面展开;搜索表单采用 UG 公共控件默认 32px 高度,真实文字保持 14px/20px。可见标签为“关键词”“手机号”“状态”“创建时间”,关键词涵盖账号、姓名和邮箱;双端通过公开 UGForm labelPosition="right" 与 labelWidth="max-content" 让标签只占自然文字宽度,保留真实字段关联和现有控件访问名称。标签文字到控件为 8px,同行控件到下一标签文字及日期到查询操作均为 16px,搜索与重置的内部间距为 8px。创建日期通过 UGDateRangePicker 呈现一体中文范围输入,普通控件与一体范围控件本体同宽:普通布局采用若依源码的 240px,空间不足时整组换行;实际列表区达到 84rem 时业务 Grid 使用四组自然标签和四条等宽控件列,控件最多 240px、空间不足时等量收缩,查询操作按实际内容占宽并保留在首行。更宽视口不继续放大控件,字段仍按内容排列。字段通过公开 FormField/FormItem 的 class/style 与 subgrid 组合,不测算或覆盖控件内部宽度,不把整组等宽误当控件等宽。展开额外角色条件不改变首行操作的位置。768px 以下改为堆叠字段,共用 56px 右对齐标签列和 8px 列内间距,四个控件填满剩余空间并保持相同左右边界;常驻关键词,“展开用户筛选”按需显示手机、状态与日期,收起保留草稿;已生效状态用可关闭标签显示,关闭只清状态并提交其余草稿。主操作在表格外左侧,搜索显隐、列设置与刷新在右侧;手机操作保留可访问名称。表格填满剩余高度,分页固定在底部。
部门查询只通过组织栏或窄屏的部门范围弹层,不在上方查询区重复部门控件。编辑的归属部门使用主入口 UGTreeSelect,通过共享 createManagementDepartmentOptionTree 将精简候选映射为真实 key/label/children 层级;搜索保留匹配节点的祖先路径及匹配部门的下级,不用缩进文本模拟树。编辑时停用部门不能新增分配,当前已绑定的停用部门可显示并保留;禁选父节点仍可展开和访问启用的子节点。编辑清空映射为 departmentId: null。旧 Adapter 不提供 parentId 时仍可平铺选择,缺失父节点、自引用及循环关系降级为根节点以避免候选失踪,最终部门关系由后端校验。
用户页按 docs/legal/THIRD_PARTY_NOTICES.md 的管理流程改编:常驻新增、修改和删除工具栏,整行操作统一为 32px,文字按钮使用公开 default 尺寸、纯图标按钮使用 icon;新增为 solid/primary、修改为 default/neutral、删除为 default/danger,保留独立禁用状态。组织折叠、更多筛选、搜索显隐、刷新与列设置采用 ghost/neutral;清空选择采用文字动作,移动端部门入口保持有边界的持续入口。整行操作使用相同尺寸,图标仍为 16px;禁用与加载不改变按钮几何。默认主题控件和编辑弹窗圆角为 4px、普通控件高 32px;用户表单通过公开 width/placement/topOffset 使用 600px 内容宽度和顶部 6vh 定位,默认皮肤无外框及头尾分隔线,使用统一 section 留白,修改仅单选时可用,删除需选择记录且不含当前用户。行内修改和删除直接可达;状态使用受控 UGSwitch,确认成功后再变更,取消或请求失败保持原值。账号详情通过独立 getOne("users", { id, signal }) 读取,并检查 users:read;支持加载、失败重试、关闭回焦。编辑表单在桌面使用双列分组、窄屏单列,状态用单选,角色与备注跨列;复用 UG 原生控件及已有字段错误、未保存与重复提交保护。
默认表格显示身份、部门、手机、状态和创建时间,角色、邮箱默认隐藏,可通过列设置显示,详情与编辑始终保留对应资料。默认列不缩字号或压小可读列宽;全部信息列显示后可以在表内横滚。URL 无 hide 使用默认隐藏列,非默认选择使用重复 hide,hide= 明确表示全部可选列显示;刷新、返回均保留显式全显选择。旧 URL 仅设 sort=email 时自动显示邮箱以保留排序意图;显式 hide 隐藏当前排序列时恢复账号升序与第一页。
用户编辑在获 users:read 时先读取最新单条记录,读取失败显示错误和重试,不静默使用旧列表。只有 users:list + users:update 的既有授权仍可编辑合法列表资料,不因此扩大详情读取权限;更新统一只 PATCH 实际修改的字段,避免旧列表中未改的手机、备注或角色覆盖后台新值。新建仍提交完整表单,凭据生命周期不进入此流程。
用户详情与编辑每次打开都求证最新单条记录,独立于全局查询缓存的默认新鲜期;重新读取期间显示加载,读取失败显示重试,不把缓存资料当作最新结果。管理资源列表、详情与选项的缓存键包含公开会话用户快照,当前用户身份或权限写后更新时重新绑定查询,避免停留在已移出缓存的旧结果;凭据不进入查询键。
上方常驻查询为用户关键词、手机号码、状态和一组创建时间起止日期,角色为可展开的附加条件。日期使用公开 UGDateRangePicker,白底边框内显示“开始日期 - 结束日期”,复用双月日历、范围高亮、手动草稿与清空;搜索字段可选 labelFor 透传公开 FormLabel 的 htmlFor/for,日期字段通过框架 useId 生成稳定开始输入 ID,同时传给 labelFor 与公开 startInputId,点击“创建时间”真实聚焦开始输入,不把范围根容器当作可关联的原生控件。原有搜索字段省略 labelFor 时保持 Form 自动关联;不猜测组件内部 ID 后缀。隐藏搜索或手机收起条件时关闭日期弹层并保留草稿。getUserListQuery 统一两端 Provider 参数:search 匹配账号、姓名或邮箱,filters.phone 为包含匹配,filters.createdFrom/createdTo 为有效 YYYY-MM-DD 日历日期。范围含首尾日,以后台 createdAt 的业务日历日期比较,不转换成客户端 UTC;支持单边范围,缺失创建时间的记录不匹配有效日期筛选。表单中反向范围提示并阻止提交,URL 的非法日期被丢弃、反向范围降级为空。整体搜索显隐及手机展开均保留草稿;已生效条件与待查询提示仍可识别。
用户、部门和角色查询区分本地提交与外部 URL 导航:本地提交已同步草稿,其延迟 URL 回执只消费一次,不覆盖提交之后的新输入;连续本地写入基于最近一次目标参数合并。浏览器返回、前进(即使筛选值相同)及外部条件变化重新同步草稿。私有导航来源仅存于当前 history state,保留已有状态及未知查询参数;不新增 URL 参数或持久化草稿。
已生效查询通过 URL 的 q/department/role/status/phone/from/to/page/size/sort/order 保存;组织搜索、折叠与隐藏列使用 tree/collapsed/hide,由双端共用的 packages/admin-core/src/management/user-list-state.ts 解析、校验和序列化,保留其他查询参数。用户页默认每页 20 条,缺失或非法 size 回到 20,序列化省略默认值;显式 size=10 仍可恢复,支持 10/20/50/100 条。离开再返回或刷新恢复这些上下文,不保存编辑、删除确认框及记录选择,不引入新的浏览器存储。首列为当前页多选,其后为跨页连续序号;查询、范围切换、重置、排序、翻页、每页条数和刷新时清空选择。批量删除逐条复核权限后调用 deleteMany,二次确认并阻止重复提交;包含当前登录用户时禁用,服务端继续校验业务约束。批量启用/停用通过工具栏“批量状态”进入,逐条复核 users:update 后顺序调用 update,确认框说明受影响人数与登录影响;包含当前登录用户时同样禁用,部分写入失败会明确已更新数量并刷新列表,不报告全部成功。批量分配部门通过“分配部门”进入,目标树只允许启用部门,留空表示清除归属;同样逐条复核 users:update 后顺序写入,部分失败保留弹窗并显示已更新数量。
姓名、账号、邮箱、手机号、状态和创建时间使用 Provider 的 sort/order 排序,部门和角色不提供假排序。身份与序号列固定显示,其他列可隐藏;隐藏当前排序列时恢复账号升序并回到第一页。单元格文字使用实际列内全部可用宽度,只有空间不足才省略;长字符串不撑大表格固有列宽,窄屏继续在表格区域横向滚动,隐藏列后释放的宽度可用于完整显示其余文字。复制账号/邮箱放入有名称的“更多”菜单,失败就地反馈,不报告虚假成功。当前用户的删除按钮与状态开关禁用并说明原因,编辑表单也禁用停用选项;新建用户默认填入当前仍启用的部门。状态切换只通过 PATCH 提交 status,避免覆盖其他资料。字段长度在两端一致,422 错误仅在对应字段被修改时消退,其他字段错误保留。编辑和危险操作保持加载、空状态、重试、权限复核、防重复提交和未保存离开确认。开户、邀请和密码重置仍属于认证后端,页面不模拟这些能力。
表格以内容确定列预算,不将短字段按整表百分比放大。用户姓名和账号只指定最小可读宽度,以自动列承接剩余空间;部门、角色、邮箱、手机号、创建时间和状态通过公开列 width/minWidth 指定内容预算。默认姓名、账号、邮箱、手机与创建时间的最小文字宽度分别保留 128/144/240/160/192px;隐藏可选列后释放的宽度交给身份列,复选框、序号和行内操作保持紧凑。部门名称承担余量,排序和状态分别使用 120px 和 140px;角色名称与功能权限承担余量,编码使用 240px、保护状态使用 120px;字典名称与说明承担余量,编码/条目值、状态、排序与计数保持固定内容预算。双端均使用公开列属性与自动表格布局,窄屏在有名称的表格区域横向滚动。布局、层级和颜色的原则见 UI 设计依据。
行内修改、删除和更多操作统一使用公开 icon-sm 按钮,保留完整操作名称、悬停说明和键盘访问;手机固定操作区不遮住姓名省略号。
表单字段名与 API 字段一致,例如服务端返回:
{
"message": "请检查表单中的错误项。",
"fieldErrors": { "username": ["用户账号已存在。"] }
}
配合 HTTP 422,数据层会转换为 AdminDataError.fieldErrors;不要只用 Toast 代替字段提示。管理表单继续使用原生 required/type/min/max/step/maxLength 约束,通过捕获 invalid 抑制浏览器临时提示,按 ValidityState 显示中文字段错误;不关闭原生约束、不另造邮箱或数字规则。同次提交的所有无效字段均保持错误,修正后仅清除对应的本地错误;错误复核在 input 冒泡阶段执行,让控件先保存业务值,避免捕获阶段的重渲染覆盖新输入。合法可选空邮箱及排序 0 可正常保存。服务端错误仍按提交快照独立保留,首错沿用滚动与聚焦行为。
所有新建和编辑入口记录本次打开来源;详情转编辑以记录 ID 保留来源。编辑结束时等待普通对话框、放弃确认框及背景隔离结束,并等本轮列表查询和该记录的 read 判定就绪,再聚焦当前记录入口。记录真正退出筛选或 read 被拒绝时回到稳定的搜索显隐入口;不在异步授权仍 pending 时误判记录不可见,不缓存授权,不以持续观察抢回用户已经移动的焦点。
全局通知通过公开 Toaster 的 compact 模式与 admin-notifications 类装配到顶部居中。默认收成 32px 单行,保留数量、最新摘要和展开入口,完整内容仍向辅助技术播报;主动展开后查看完整消息与逐条关闭,列表在公开组件内有界滚动,收起入口保持可达。短通知按内容宽度显示,两侧各预留 control-height-md + 4 × content-gap 的操作通道,保证窄屏详情和编辑弹窗的关闭按钮可点。用户、部门和角色详情使用同一应用级宽度(默认 28rem,窄屏不超可用宽度);详情打开时,通知改锚到面板标题右侧、关闭按钮左侧的自由区,按实际详情宽度保留两侧操作通道。关闭详情或详情转编辑后恢复普通顶部居中位置,只通过公开 Content 类和应用自有状态类编排,不依赖组件内部结构。应用不再给根节点叠加滚动限制,不遮挡底部保存、取消入口来换取顶部可用性;通知层级、手动关闭、悬停暂停与停留时长保持公开组件合同。
用户页通过 getManagementUserOptions(provider, signal) 读取 GET /users/options:部门仅含 id/name/status 与可选的 parentId,角色仅含 id/name/assignable。users 的 list/read/create/update 任一授权即可读取,不隐含 roles:list 或 departments:list。既有不可分配角色仍可显示,但不能新增分配;后端按操作者计算 assignable,不泄露完整角色权限表,并在提交时再次验证。响应与 Adapter 映射见 后端合同。
部门树及上级选择使用 getManagementOptions(provider, "departments", signal) 按分页读取完整部门集合,不截取固定前若干条;后端必须提供正确的 total、稳定分页及授权后的数据,取消信号原样传递。用户精简选项端点同样不能静默截断候选集合。
users/departments/roles 互相关联,写入成功后需同时刷新相关列表与选项。默认 HTTP 及注入的数据提供者在当前用户或角色的相关写入后重新求证会话,更新认证状态与查询缓存;具体触发范围、失败处理和外部撤权边界见 写后会话复核。演示模式通过身份回调同步变化,最终请求校验仍在服务端。
演示模式
packages/admin-core/src/testing/demo-management-data-provider.ts 导出 createDemoManagementDataProvider({ fallback, getIdentity, storage?, storageKey?, onIdentityChange? }),处理三个管理资源,其他资源交给 fallback;应用组合任务演示 Provider。管理数据以版本化结构持久化,应用传入品牌派生的 management-demo 存储键。损坏或不兼容的数据会报错,不静默覆盖。
resolveDemoManagementIdentity(identity, storage?, storageKey?) 根据已存用户及角色重新计算演示身份;种子账户停用或删除后返回 null。写入后的 onIdentityChange 通知当前会话更新。
演示登录凭据是固定的 admin/admin123、operator/operator123、viewer/viewer123。管理员默认全权,运营与访客默认无核心管理权限;显式启用 examples 时,新种子运营角色拥有任务只读权限。修改用户资料不创建新的登录凭据;新用户记录也不自动获得可登录的密码。真实账户流程必须连接认证服务。
演示存储可以被浏览器用户修改,因此只用于功能验证,不是安全边界。生产构建不得携带演示账户或种子数据,不得在真实接口失败时自动退回 Mock。
接入验收
三个管理页使用同一查询、操作、详情与编辑编排。编辑弹窗通过公共 Modal 的 initialFocusSelector 聚焦名称字段;异步加载后只聚焦一次,用户已转移焦点时不抢回。备注/说明显示字数,用户详情显示完整部门路径。
角色授权使用公共 SelectionList 的响应式分组网格、组折叠和搜索;折叠不移除既有权限,搜索临时展开匹配组,未登记及不可分配权限仍只读保留。部门表使用公共 TreeTable,展示森林与写入 DTO 分离;搜索保留祖先并禁用折叠,退出搜索恢复原折叠状态,异常层级只提示、不修改原始 parentId。用户创建日期快捷筛选在点击时按本地日历计算,选择只改查询草稿,仍需点击搜索才应用。
对三个真实资源分别验证分页、空列表、CRUD、422、401/403、关联选择与删除约束;验证刷新后数据仍来自服务端。至少用全权与受限账户各验证一次,直接访问 URL 和直接发起未授权写入都必须被阻止。权限变更后验证菜单、路由、按钮和服务端返回一致;静态测试或演示通过不代表真实后端已完成这些检查。
基底与扩展顺序
先保证稳定主体 ID、关联模型、权限目录和可替换 Adapter 一致,再验收用户、部门、角色的完整管理流程;最后才扩展复用能力。完成页数不是前置条件通过的证明:自删/自停用、最后超管、越权分配、部门循环、关联删除、写后撤权及查询刷新必须持续回归。公共控件缺陷先在 UG UI 修复,不通过业务 CSS 掩盖。
通用字典已作为独立核心模块提供类型、条目维护和现有任务优先级消费:稳定类型 code 与条目 value、标签、排序、启停、唯一性、引用删除保护以及精简选项读取共同构成合同。字典管理不强制开启任务等学习示例;新业务通过 Provider 接入同一套选项,并补充自己的引用与授权规则。完整接入与历史值行为见 字典合同。
字典选项读取不能强迫业务用户拥有字典管理权限。功能权限码、角色保护、超级管理员标识、路由和模块标识不属于可任意编辑的普通字典;安全状态的显示标签与 active/disabled 等值的业务语义须分开。数据范围、岗位、多租户和凭据生命周期分别有自己的模型与验收条件,不由部门树、功能权限或字典隐式代替。