字典管理与业务消费

字典是“系统管理”下的正式核心能力,与系统设置同组;“组织与权限”仅承载部门、用户和角色。React 与 Vue 均通过 UG UI 公开组件实现。类型列表位于 /system/dictionaries,条目列表位于 /system/dictionaries/:typeId/entries。类型与条目分别授权,业务选项读取不要求使用者拥有字典管理权限。

旧 /organization/dictionaries 及其条目地址通过共享 aliases 登记兼容,双端以替换历史记录的方式跳到正式地址,保留查询参数和锚点。持久化页签迁移后按页面身份、模块和权限重新校验并去重。/settings 保持稳定;系统分组按可访问子项显示,不要求字典使用者拥有 settings:read。

中性资源

事实源在 packages/admin-core/src/dictionaries/types.ts,业务页面不依赖 Java、Go、Python 或 TypeScript 后端的专属 DTO。

资源 字段 约束
dictionary-types id/name/code/description/status,只读可选 entryCount id 稳定字符串;名称与编码最多 80 字符,说明最多 500 字符
dictionary-entries id/typeId/label/value/sort/status/tone,只读可选 usageCount 标签与值最多 80 字符,排序为 0–9999 整数

code 全局唯一,以小写字母开头,只含小写字母、数字、下划线;创建后不可修改。条目 value 在同一类型内唯一,创建后不可修改,typeId 同样不可移入其他类型。标签和说明可以修改,所有关系保存 ID 或稳定值,不能保存显示标签作为主键。输入去除首尾空格后校验。

状态为 active | disabled;显示颜色为 neutral | primary | success | warning | danger,仅控制展示,不表达权限或业务流程。统计由服务端计算,不接受客户端覆盖。条目默认按 sort 升序、稳定 ID 次序排列。

权限与维护流程

两个资源各有 list/read/create/update/delete 五种独立权限,登记在核心功能权限目录。list 不隐含 read;详情要有 read,有 read 的编辑每次打开读取最新记录;只有 list + update 时可维护已获授权的列表字段,只提交差量 PATCH。保存、启停和删除在真正请求前重新等待操作访问控制判定,服务端仍负责最终校验。

类型列表进入条目须具备 dictionary-entries:list。条目页没有类型 read 时不得为标题偷读类型详情,可以显示类型 ID 并维护已授权的条目。返回类型列表保留原查询上下文。搜索草稿点击查询或回车后生效,生效条件、分页、排序保存在 URL;清除或重置条件不丢失其他路由参数。

新增、编辑与详情使用同一管理页面编排和 UG UI 公开表单、Modal、Drawer、Table。不可变字段明确只读;字段错误对应服务端 422 的 fieldErrors,修改其他字段不能清除尚未修正的错误。保存和删除防止重复提交,脏表单关闭需确认;加载、失败重试和空结果都必须可辨识。

停用类型后不能新增条目,也不能在业务中新增选择该类型下的任何值;仍可修改既有条目的标签、排序和状态,为恢复或历史资料维护保留入口。停用条目不删除记录、不改写业务数据。删除类型须先处理全部条目;删除条目须确认所有已登记业务引用为空,不能只相信客户端 usageCount。

业务精简选项

packages/admin-core/src/dictionaries/options.ts 调用标准 Provider:

provider.custom({
  path: "/dictionaries/options",
  method: "GET",
  query: { code: "task_priority" },
  signal,
});

默认 HTTP 请求为 GET /dictionaries/options?code=task_priority,响应如下:

{
  "data": {
    "code": "task_priority",
    "name": "任务优先级",
    "status": "active",
    "entries": [
      {
        "id": "priority-high",
        "label": "高",
        "value": "high",
        "sort": 30,
        "status": "active",
        "tone": "warning"
      }
    ]
  }
}

返回当前业务范围内的完整精简集合,包括用于历史显示的停用条目,不静默截取前若干项。只含显示和选择必要字段,不返回完整管理记录、权限、成员或引用明细。读取 helper 校验类型编码、状态、颜色、稳定 ID、排序以及重复值,响应错误不会伪装成空列表。

选项端点必须校验登录身份及具体业务授权:演示中,task_priority 允许 tasks:list/read/create/update 任一动作读取;只有 workspace:read/write、只有 tasks:delete 或匿名都不允许。具有任一字典资源的 list/read/create/update 可为管理用途读取选项,不能据此操作任务。新增其他业务字典时,接入方 Adapter/服务端显式登记该类型允许的业务动作,不能把所有字典一律公开给全部登录用户。

packages/admin-core/src/dictionaries/options.ts 仅给出启用类型中的启用条目;传入既有 currentValue 时补回该值作为禁选历史项。未知旧值按原值显示并禁选,不因修改无关字段而强迫迁移。只有已有记录且提交值没有改变时才允许保留停用或未知值;新建和改选必须重新校验当前启用集合。

getDictionaryEntryLabel 和 getDictionaryEntryTone 为列表、详情、看板及标签提供统一显示;未知值回退原值和中性色。读取失败时选择控件给出重试并阻止无效新建,不能偷偷切回硬编码选项或默认值。

查询条件使用 getDictionaryFilterOptions,允许用停用条目筛选历史记录并标记“已停用”;筛选不建立新业务绑定,不能直接复用新建表单的候选限制。当前未知筛选值按原值保留,清除后回到全部优先级。

已接入的实际业务

现有任务示例使用 task_priority:列表筛选、任务新建/编辑、详情、工作台和看板的优先级展示读取同一套选项。演示种子 low/medium/high 只是初始条目,DemoTaskPriority 为稳定字符串,新建条目可直接用于任务。

任务属于显式启用的 examples 模块,字典管理属于核心。默认核心应用不因此开启全部示例;验证实际任务消费时使用 VITE_ADMIN_EXAMPLES=true。消费系统接入自己的业务时复用同一 helper 与 Provider,而不是复制任务或强制启用示例模块。

任务列表、看板、日历使用 tasks:list,详情使用 tasks:read,新建和编辑分别使用 tasks:create/update,删除使用 tasks:delete;工作台页面本身仍由 workspace:read 准入。workspace:read/write 不隐含任务资源动作。自定义角色从固定权限目录显式选择所需动作,选项读取只为业务选择提供数据,不反向授予任务操作。

任务的生命周期状态继续使用固定领域合同;功能权限码、角色保护、超级管理员标记、路由/模块 ID、用户状态不属于任意编辑字典。

缓存、演示与后端

字典写入成功后失效两个管理资源及 dictionaryQueryKeys.all,活跃类型详情、条目列表和业务选项重新读取。选项键含类型 code,身份变化沿用应用数据层清理/重置机制,不让另一个账户沿用旧授权选项。

演示提供者只在 testing/,通过 createDemoDictionaryDataProvider({ fallback, getIdentity, storage, storageKey, managementStorageKey }) 包装现有管理与任务提供者。字典使用品牌命名空间 dictionary-demo,版本化持久化;损坏、存储不足和取消都返回错误,不覆盖坏数据或报告虚假保存成功。每次授权根据已存用户和角色求证,拒绝旧会话的失效权限。

同一演示 Provider 实例将字典和任务写入串行化,引用检查实时分页读取任务,不依赖启动时快照或固定条数上限。批量删除先完成全部校验,拒绝时不部分删除。跨浏览器客户端并发不由本地队列保证;真实后端须在事务中完成唯一性、稳定值、启停选择和引用删除校验。

生产只走注入的 AdminDataProvider 或默认 HTTP Adapter,不包含演示种子,不因接口失败切回 Mock。其他 URL、分页格式和字段名由 Adapter 映射,详见 后端接入合同。

类型列表到条目列表、标签/值/排序/启停的组织方式参考 RuoYi-Vue3 字典类型与字典条目源码。控件仍为 UG UI 原生实现,不引入 Element 运行时;接口、权限和历史值保护遵循上述中性合同。