feat: 字典(DictTag)支持fallback属性(未匹配到字典项时的回显)

This commit is contained in:
dap
2025-07-18 11:40:19 +08:00
parent 253c9b5248
commit 808406ebce
4 changed files with 138 additions and 48 deletions

View File

@@ -5,10 +5,12 @@
- 流程表达式 follow后端更新 - 流程表达式 follow后端更新
- websocket功能(默认关闭) - websocket功能(默认关闭)
- useVbenForm 增加 TimeRangePicker(时间区间选择) 组件 - useVbenForm 增加 TimeRangePicker(时间区间选择) 组件
- 字典(DictTag)支持fallback属性(未匹配到字典项时的回显)
**REFACTOR** **REFACTOR**
- Modal/Drawer中使用VxeTable tooltip需要设置更高的z-index 防止被遮挡 - Modal/Drawer中使用VxeTable tooltip需要设置更高的z-index 防止被遮挡
- 字典(DictTag)使用tsx写法重构
**OTHERS** **OTHERS**

View File

@@ -1,62 +1,134 @@
<!-- eslint-disable eqeqeq --> <!-- eslint-disable eqeqeq -->
<script setup lang="ts"> <script lang="tsx">
import type { PropType } from 'vue';
import type { DictFallback } from './type';
import type { DictData } from '#/api/system/dict/dict-data-model'; import type { DictData } from '#/api/system/dict/dict-data-model';
import { computed } from 'vue'; import { computed, defineComponent, h } from 'vue';
import { Spin, Tag } from 'ant-design-vue'; import { Spin, Tag } from 'ant-design-vue';
import { isFunction, isString } from 'lodash-es';
import { tagTypes } from './data'; import { tagTypes } from './data';
interface Props { /**
dicts: DictData[]; // dict数组 * 使用tsx重构原来的template写法
value: number | string; // value * 在大量if的情况 tsx比template的v-if好用得多
} */
export default defineComponent({
name: 'DictTag',
props: {
/**
* 字典项options
*/
dicts: {
required: false,
type: Array as PropType<DictData[]>,
default: () => [],
},
/**
* 当前值
*/
value: {
required: true,
type: [Number, String],
},
/**
* 未匹配到字典项的fallback
*/
fallback: {
required: false,
type: [String, Function] as PropType<DictFallback>,
default: 'unknown',
},
},
setup(props) {
const color = computed<string>(() => {
const current = props.dicts.find((item) => item.dictValue == props.value);
const listClass = current?.listClass ?? '';
// 是否为默认的颜色
const isDefault = Reflect.has(tagTypes, listClass);
// 判断是默认还是自定义颜色
if (isDefault) {
// 这里做了antd - element-plus的兼容
return tagTypes[listClass]!.color;
}
return listClass;
});
const props = withDefaults(defineProps<Props>(), { const cssClass = computed<string>(() => {
dicts: undefined, const current = props.dicts.find((item) => item.dictValue == props.value);
}); return current?.cssClass ?? '';
});
const color = computed<string>(() => { /**
const current = props.dicts.find((item) => item.dictValue == props.value); * 返回null 走 fallback逻辑
const listClass = current?.listClass ?? ''; */
// 是否为默认的颜色 const label = computed<null | string>(() => {
const isDefault = Reflect.has(tagTypes, listClass); const current = props.dicts.find((item) => item.dictValue == props.value);
// 判断是默认还是自定义颜色 return current?.dictLabel ?? null;
if (isDefault) { });
// 这里做了antd - element-plus的兼容
return tagTypes[listClass]!.color;
}
return listClass;
});
const cssClass = computed<string>(() => { const loading = computed(() => {
const current = props.dicts.find((item) => item.dictValue == props.value); return props.dicts?.length === 0;
return current?.cssClass ?? ''; });
});
const label = computed<number | string>(() => { return {
const current = props.dicts.find((item) => item.dictValue == props.value); color,
return current?.dictLabel ?? 'unknown'; cssClass,
}); label,
loading,
};
},
render() {
const { color, cssClass, label, loading, fallback, value } = this;
const tagComponent = computed(() => (color.value ? Tag : 'div')); /**
* 字典list为0 加载中
*/
if (loading) {
return (
<div>
<Spin size="small" spinning />
</div>
);
}
const loading = computed(() => { /**
return props.dicts?.length === 0; * 没有匹配到字典label === null的fallback
* 可为string/Vnode
*/
if (label === null) {
// VNode
if (isFunction(fallback)) {
return h(fallback(value));
}
// 默认显示 unknown 文案
if (isString(fallback)) {
return <div>{fallback}</div>;
}
}
/**
* 有color 属性 渲染Tag
*/
if (color) {
return (
<div>
<Tag class={cssClass} color={color}>
{label}
</Tag>
</div>
);
}
return (
<div>
<div class={cssClass}>{label}</div>
</div>
);
},
}); });
</script> </script>
<template>
<div>
<component
v-if="!loading"
:is="tagComponent"
:class="cssClass"
:color="color"
>
{{ label }}
</component>
<Spin v-else :spinning="true" size="small" />
</div>
</template>

View File

@@ -0,0 +1,5 @@
/**
* fallback的渲染
* 可返回 字符串/Vnode
*/
export type DictFallback = ((current: number | string) => VNode) | string;

View File

@@ -1,6 +1,7 @@
import type { Component as ComponentType } from 'vue'; import type { Component as ComponentType } from 'vue';
import type { DictData } from '#/api/system/dict/dict-data-model'; import type { DictData } from '#/api/system/dict/dict-data-model';
import type { DictFallback } from '#/components/dict/src/type';
import { h } from 'vue'; import { h } from 'vue';
@@ -149,16 +150,26 @@ export function renderDictTags(
); );
} }
export interface RenderDictOptions {
fallback?: DictFallback;
}
/** /**
* 显示字典标签 一般是table使用 * 显示字典标签 一般是table使用
* @param value 值 * @param value 值
* @param dictName dictName * @param dictName dictName
* @returns tag * @returns tag
*/ */
export function renderDict(value: number | string, dictName: string) { export function renderDict(
value: number | string,
dictName: string,
options?: RenderDictOptions,
) {
const { fallback } = options ?? {};
const dictInfo = getDictOptions(dictName); const dictInfo = getDictOptions(dictName);
return renderDictTag(value, dictInfo); return <DictTag dicts={dictInfo} fallback={fallback} value={value}></DictTag>;
} }
export function renderIconSpan( export function renderIconSpan(
icon: ComponentType, icon: ComponentType,
value: string, value: string,