71 lines
5.4 KiB
Markdown
71 lines
5.4 KiB
Markdown
## Context
|
||
|
||
系统枚举的「值」目前是代码里的魔法字符串:后端收敛在 `src/common/enums.ts`(CommonStatus/UserStatus/ResourceType/DataScope/LoginStatus),前端则散布在 6+ 页面的 `STATUS_MAP`/`TYPE_MAP`/`DATA_SCOPE_MAP`。字典表(sys_dict_type / sys_dict_item)与字典管理页已存在但 seed 为空。见 proposal.md 动机。
|
||
|
||
关键约束:
|
||
- 枚举的 **value 被业务逻辑分支依赖**(登录校验 `status==='enabled'`、菜单树取 `type='menu'`、`resolveDataScope` 分支),因此 value 必须稳定,字典只承载「展示层」的 label/颜色/排序。
|
||
- 后端 `enums.ts` 仍是代码逻辑唯一枚举值源;dict 是展示层映射,两者靠 value 字符串对齐。改值要两边同步,但本次只新增不改值。
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
- 让前端枚举渲染完全字典驱动,消除本地硬编码中文/颜色映射。
|
||
- 内置字典值受保护(不可删、value 不可改),label/排序/颜色可定制。
|
||
- 组件库支持 `dictCode` 声明,降低页面配置成本。
|
||
|
||
**Non-Goals:**
|
||
- 不改任何枚举的现有 value(避免破坏既有登录/鉴权/菜单逻辑)。
|
||
- 不做「纯业务字典」(性别/学历等)——当前模型无对应字段,等业务来了再加。
|
||
- `http_method` 不进字典(固定标准、后端枚举校验)。
|
||
- 不做字典版本号/增量同步/多级缓存失效,够用即可。
|
||
|
||
## Decisions
|
||
|
||
### D1. 保护标记:类型级 + 项级 `built_in` 各加一列
|
||
`dict_type.built_in`(Boolean,默认 false,内置类型不可删)+ `dict_item.built_in`(Boolean,默认 false,内置项 value 不可改、项不可删)。
|
||
- 备选:仅项级(空的内置类型会被误删)、仅硬编码保护清单(散在代码、不透明)。选两列,粒度最清晰,语义直白。
|
||
|
||
### D2. 颜色存独立列 `color_type`
|
||
`dict_item.color_type`(String?,取值 primary/success/warning/danger/info,可空)。
|
||
- 备选:通用 `ext` JSON 列(弱类型、字典页要做自定义编辑)、按 sort 猜颜色(不可控)。选独立列,语义化、前后端都简单,且复用前端既有 `TagType` 联合类型。
|
||
|
||
### D3. 保护规则在后端 service 层强制,前端只做「降级展示 + 交互禁用」
|
||
`dicts.service` 的 update/delete 前置校验:内置类型删除、内置项删除、内置项改 value 一律拒绝(返回 4xx 明确错误);label/sort/color_type 放行。前端字典管理页展示「内置」徽标、禁用 value 输入与删除入口,但**不把前端当安全边界**,后端兜底。
|
||
|
||
### D4. 聚合查询走新端点 `GET /dicts`
|
||
一次返回当前租户全部启用字典:`{ list: [{ code, name, items: [{ label, value, colorType, sort }] }] }`,item 按 sort 升序、仅启用。登录后拉一次缓存。
|
||
- 备选:复用 `listDictTypes` + 逐类型 `listDictItems`(N+1 请求,5 个内置字典就 6 次)。选聚合端点,一次搞定。
|
||
|
||
### D5. 前端字典 store + `useDict`
|
||
- `dict.ts` Pinia store:`dicts: Record<code, DictItem[]>` + `loaded` + `fetch()`(调 GET /dicts)+ `reset()`(登出清空)。
|
||
- `useDict(code)`:computed 返回该 code 的响应式字典项数组,附带 `tagMap`(value→{label,type})与 `options`(value→label,供下拉)两个衍生视图。
|
||
- 加载时机:`layout/default.vue` 在菜单拉取后触发 `dictStore.fetch()`;`main.ts` 的 auth 失效回调加 `dictStore.reset()`。
|
||
|
||
### D6. 组件 `dictCode` 声明与优先级
|
||
- `JsonSchemaProperty` 增 `dictCode?: string`:ty-form / ty-search-form 的 select 字段若声明 `dictCode`,忽略手写 `enum`/`enumNames`,用 `useDict` 的 options 渲染下拉。
|
||
- `TableColumn` 增 `dictCode?: string`:ty-table 列若声明 `dictCode`,忽略 `tagMap`,用字典渲染标签(label + colorType)。
|
||
- 优先级:`dictCode` > 手写 `enum`/`tagMap`。已声明 `dictCode` 的字段不再需要 `enum`/`enumNames`。
|
||
|
||
### D7. seed 幂等
|
||
按 `(tenantCode, code)` upsert dict_type、按 `(tenantCode, dictTypeCode, value)` upsert dict_item;内置项置 `built_in=true`。5 个字典项的值与 `enums.ts` 完全一致。
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- **[内置项 value 被前端绕过]** → 后端 service 层强制校验兜底(D3),前端禁用只是体验优化。
|
||
- **[字典缓存滞后:改 label 后需刷新才生效]** → 提供 `useDict.refresh`;字典管理页保存后调用 `dictStore.fetch()` 重拉。不追求实时推送。
|
||
- **[停用某字典类型后 useDict 返回空]** → 组件降级:无映射时直接展示原始 value 字符串,避免空白。
|
||
- **[enums.ts 与 seed 取值漂移]** → 两者值均由 spec(shared/data-model)锚定,本次不改值;后续改动需同步更新 seed 与 enums.ts,并在 spec 落一条。
|
||
- **[加列迁移]** → 均为可空/带默认值的新增列,`prisma migrate` 增量即可,无回滚风险;向后兼容(旧数据 built_in 默认 false)。
|
||
|
||
## Migration Plan
|
||
|
||
1. Prisma schema 加列 → `prisma migrate dev` 生成增量 migration。
|
||
2. 更新 seed,跑 `pnpm seed` 预置 5 个内置字典。
|
||
3. 后端加保护校验 + `GET /dicts`。
|
||
4. 前端加 dict store/useDict + 组件 dictCode + 页面去硬编码。
|
||
5. 回滚:加列可 `prisma migrate resolve --rolled-back`(无破坏性变更);前端无外部依赖,随代码回滚。
|
||
|
||
## Open Questions
|
||
|
||
(无 —— 关键决策已在本 design 与用户确认中敲定)
|