From 23e228783cff43f59157e3b8975a57d11cda315d Mon Sep 17 00:00:00 2001 From: liuyun Date: Sun, 11 Oct 2026 14:29:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20dict-driven=20rendering=20=E2=80=94=20u?= =?UTF-8?q?seDict=20+=20component=20dictCode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 dict store 与 useDict Hook(登录后缓存、登出清空) - ty-form/ty-table 支持 dictCode 自动解析下拉与标签 - 用户/角色/资源/组织/租户/审计/登录日志页去硬编码映射 - 字典管理页内置标记、value 保护、标签色编辑 Co-Authored-By: Claude Opus 4.8 --- src/api/dict.ts | 26 +++++++++- src/components/ty-crud.vue | 4 +- src/components/ty-form.vue | 24 +++++++-- src/components/ty-table.vue | 20 ++++++-- src/composables/useDict.ts | 27 ++++++++++ src/layouts/default.vue | 6 ++- src/main.ts | 2 + src/pages/monitor/audit/index.vue | 10 +--- src/pages/monitor/login-log/index.vue | 10 +--- src/pages/system/dict/index.vue | 73 +++++++++++++++++++++------ src/pages/system/org/index.vue | 13 ++--- src/pages/system/resource/index.vue | 27 +++------- src/pages/system/role/index.vue | 26 ++-------- src/pages/system/tenant/index.vue | 13 ++--- src/pages/system/user/index.vue | 14 ++--- src/stores/modules/dict.ts | 31 ++++++++++++ src/types/crud.ts | 4 ++ 17 files changed, 214 insertions(+), 116 deletions(-) create mode 100644 src/composables/useDict.ts create mode 100644 src/stores/modules/dict.ts diff --git a/src/api/dict.ts b/src/api/dict.ts index 6d392cf..fd3ea8a 100644 --- a/src/api/dict.ts +++ b/src/api/dict.ts @@ -6,6 +6,7 @@ export interface DictType { code: string name: string status: 'enabled' | 'disabled' + builtIn: boolean remark: string | null createdAt: string } @@ -18,6 +19,8 @@ export interface DictItem { value: string sort: number status: 'enabled' | 'disabled' + builtIn: boolean + colorType: string | null createdAt: string } @@ -28,6 +31,26 @@ export interface PageResult { pageSize: number } +// ---- 聚合查询(GET /dicts) ---- + +export interface DictOption { + label: string + value: string + colorType: string | null + sort: number +} + +export interface DictEntry { + code: string + name: string + items: DictOption[] +} + +/** 一次拉取全部启用字典(含字典项),供前端登录后缓存。 */ +export function listDicts(signal?: AbortSignal): Promise<{ list: DictEntry[] }> { + return http.get<{ list: DictEntry[] }>('/dicts', { signal }) +} + // ---- 字典类型 ---- export function listDictTypes(page = 1, pageSize = 1000, signal?: AbortSignal): Promise> { @@ -58,13 +81,14 @@ export function createDictItem(data: { label: string value: string sort?: number + colorType?: string }): Promise { return http.post('/dict-items', data) } export function updateDictItem( id: string, - data: { label?: string; value?: string; sort?: number; status?: string }, + data: { label?: string; value?: string; sort?: number; status?: string; colorType?: string }, ): Promise { return http.patch(`/dict-items/${id}`, data) } diff --git a/src/components/ty-crud.vue b/src/components/ty-crud.vue index f028802..c8db0d0 100644 --- a/src/components/ty-crud.vue +++ b/src/components/ty-crud.vue @@ -107,6 +107,8 @@ const props = withDefaults( nameKey?: string /** 编辑模式下强制只读的字段。 */ editReadonlyKeys?: string[] + /** 编辑模式下按行计算只读字段(优先于 editReadonlyKeys)。 */ + getEditReadonlyKeys?: (row: Record) => string[] /** 操作列动作(内置 'edit'/'delete' 或自定义动作)。 */ actions?: CrudAction[] /** 操作列按钮超过该数量时折叠为「更多」。 */ @@ -197,7 +199,7 @@ async function openEdit(row: Record): Promise { schema: props.formSchema, modelValue: model, mode: 'edit', - readonlyKeys: props.editReadonlyKeys, + readonlyKeys: props.getEditReadonlyKeys ? props.getEditReadonlyKeys(row) : props.editReadonlyKeys, }, title: `编辑${props.entityName}`, }) diff --git a/src/components/ty-form.vue b/src/components/ty-form.vue index 3afb23a..b480ec2 100644 --- a/src/components/ty-form.vue +++ b/src/components/ty-form.vue @@ -7,7 +7,7 @@ :prop="key" > @@ -86,9 +86,12 @@ import { computed, ref } from 'vue' import type { FormInstance, FormRules } from 'element-plus' import type { JsonSchema, JsonSchemaProperty } from '@/types/crud' +import { useDictStore } from '@/stores/modules/dict' defineOptions({ name: 'TyForm' }) +const dictStore = useDictStore() + const props = withDefaults( defineProps<{ /** 表单 JSON Schema。 */ @@ -167,6 +170,17 @@ function isReadonly(key: string, prop: { readOnly?: boolean }): boolean { return prop.readOnly === true || props.readonlyKeys.includes(key) } +/** 下拉选项:dictCode 优先,其次 enum/enumNames。 */ +function selectOptions(prop: JsonSchemaProperty): { value: string | number | boolean; label: string }[] { + if (prop.dictCode) { + return (dictStore.dicts[prop.dictCode] ?? []).map((i) => ({ value: i.value, label: i.label })) + } + if (prop.enum) { + return prop.enum.map((v, i) => ({ value: v, label: prop.enumNames?.[i] ?? String(v) })) + } + return [] +} + function widgetStyle(prop: JsonSchemaProperty): Record { return { width: prop.width ?? (props.inline ? '200px' : '100%') } } diff --git a/src/components/ty-table.vue b/src/components/ty-table.vue index 9bf2851..b344119 100644 --- a/src/components/ty-table.vue +++ b/src/components/ty-table.vue @@ -23,10 +23,10 @@