Merge pull request 'feet: [Dict]新增多选校验,规范校验特性设计' (#401) from jasondom/Admin.NET.Pro:v2-1 into v2
Reviewed-on: https://code.adminnet.top/Admin.NET/Admin.NET.Pro/pulls/401
This commit is contained in:
commit
e9b051848d
@ -10,128 +10,136 @@ namespace Admin.NET.Core;
|
|||||||
/// 字典值合规性校验特性
|
/// 字典值合规性校验特性
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SuppressSniffer]
|
[SuppressSniffer]
|
||||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||||
public class DictAttribute : ValidationAttribute, ITransient
|
public class DictAttribute : ValidationAttribute, ITransient
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 字典编码
|
/// 字典编码
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string DictTypeCode { get; }
|
public readonly string DictTypeCode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否多选
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool Multiple;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否允许空字符串
|
/// 是否允许空字符串
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("参数已废弃,逻辑已删除,请使用[Required]特性校验必填")]
|
||||||
public bool AllowEmptyStrings { get; set; } = false;
|
public bool AllowEmptyStrings { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 允许空值,有值才验证,默认 false
|
/// 允许空值,有值才验证,默认 false
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("参数已废弃,逻辑已删除,请使用[Required]特性校验必填")]
|
||||||
public bool AllowNullValue { get; set; } = false;
|
public bool AllowNullValue { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 字典值合规性校验特性
|
/// 构造函数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dictTypeCode"></param>
|
/// <param name="dictTypeCode">字典类型编码</param>
|
||||||
/// <param name="errorMessage"></param>
|
/// <param name="multiple">是否允许多选</param>
|
||||||
public DictAttribute(string dictTypeCode = "", string errorMessage = "字典值不合法!")
|
/// <param name="errorMessage">自定义错误消息</param>
|
||||||
|
public DictAttribute(string dictTypeCode = "", bool multiple = false, string errorMessage = "字典值不合法!")
|
||||||
{
|
{
|
||||||
DictTypeCode = dictTypeCode;
|
DictTypeCode = dictTypeCode;
|
||||||
|
Multiple = multiple;
|
||||||
ErrorMessage = errorMessage;
|
ErrorMessage = errorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 字典值合规性校验
|
/// 验证方法(异步安全)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value"></param>
|
private async Task<ValidationResult> IsValidAsync(object? value, ValidationContext validationContext, CancellationToken cancellation)
|
||||||
/// <param name="validationContext"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected override ValidationResult IsValid(object? value, ValidationContext validationContext)
|
|
||||||
{
|
{
|
||||||
// 判断是否允许空值
|
// 1. 空值检查
|
||||||
if (AllowNullValue && value == null) return ValidationResult.Success;
|
if (IsEmpty(value))
|
||||||
|
return ValidationResult.Success;
|
||||||
|
|
||||||
// 获取属性的类型
|
// 2. 获取属性类型
|
||||||
var property = validationContext.ObjectType.GetProperty(validationContext.MemberName!);
|
var property = validationContext.ObjectType.GetProperty(validationContext.MemberName!);
|
||||||
if (property == null) return new ValidationResult($"未知属性: {validationContext.MemberName}");
|
if (property == null)
|
||||||
|
return new ValidationResult($"未知属性: {validationContext.MemberName}");
|
||||||
string importHeaderName = GetImporterHeaderName(property, validationContext.MemberName);
|
|
||||||
|
|
||||||
var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
|
var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
|
||||||
|
|
||||||
// 先尝试从 ValidationContext 的依赖注入容器中拿服务,拿不到或类型不匹配时,再从全局的 App 容器中获取
|
// 3. 枚举类型校验
|
||||||
if (validationContext.GetService(typeof(SysDictDataService)) is not SysDictDataService sysDictDataService)
|
|
||||||
sysDictDataService = App.GetRequiredService<SysDictDataService>();
|
|
||||||
|
|
||||||
// 获取字典值列表
|
|
||||||
var dictDataList = sysDictDataService.GetDataList(DictTypeCode).GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
// 使用 HashSet 来提高查找效率
|
|
||||||
var dictHash = new HashSet<string>(dictDataList.Select(u => u.Value));
|
|
||||||
|
|
||||||
// 判断是否为集合类型
|
|
||||||
if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>))
|
|
||||||
{
|
|
||||||
// 如果是空集合并且允许空值,则直接返回成功
|
|
||||||
if (value == null && AllowNullValue) return ValidationResult.Success;
|
|
||||||
|
|
||||||
// 处理集合为空的情况
|
|
||||||
if (value is not IEnumerable collection) return ValidationResult.Success;
|
|
||||||
|
|
||||||
// 获取集合的元素类型
|
|
||||||
var elementType = propertyType.GetGenericArguments()[0];
|
|
||||||
var underlyingElementType = Nullable.GetUnderlyingType(elementType) ?? elementType;
|
|
||||||
|
|
||||||
// 如果元素类型是枚举,则逐个验证
|
|
||||||
if (underlyingElementType.IsEnum)
|
|
||||||
{
|
|
||||||
foreach (var item in collection)
|
|
||||||
{
|
|
||||||
if (item == null && AllowNullValue) continue;
|
|
||||||
|
|
||||||
if (!Enum.IsDefined(underlyingElementType, item!))
|
|
||||||
return new ValidationResult($"提示:{ErrorMessage}|枚举值【{item}】不是有效的【{underlyingElementType.Name}】枚举类型值!", [importHeaderName]);
|
|
||||||
}
|
|
||||||
return ValidationResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in collection)
|
|
||||||
{
|
|
||||||
if (item == null && AllowNullValue) continue;
|
|
||||||
|
|
||||||
var itemString = item?.ToString();
|
|
||||||
if (!dictHash.Contains(itemString))
|
|
||||||
return new ValidationResult($"提示:{ErrorMessage}|字典【{DictTypeCode}】不包含【{itemString}】!", [importHeaderName]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ValidationResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
var valueAsString = value?.ToString();
|
|
||||||
|
|
||||||
// 是否忽略空字符串
|
|
||||||
if (AllowEmptyStrings && string.IsNullOrEmpty(valueAsString)) return ValidationResult.Success;
|
|
||||||
|
|
||||||
// 枚举类型验证
|
|
||||||
if (propertyType.IsEnum)
|
if (propertyType.IsEnum)
|
||||||
{
|
{
|
||||||
if (!Enum.IsDefined(propertyType, value!)) return new ValidationResult($"提示:{ErrorMessage}|枚举值【{value}】不是有效的【{propertyType.Name}】枚举类型值!", [importHeaderName]);
|
return !Enum.IsDefined(propertyType, value!) ? new ValidationResult($"提示:{ErrorMessage}|枚举值【{value}】不是有效的【{propertyType.Name}】枚举类型值!") : ValidationResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 获取字典服务
|
||||||
|
var sysDictDataService = validationContext.GetService(typeof(SysDictDataService)) as SysDictDataService
|
||||||
|
?? App.GetRequiredService<SysDictDataService>();
|
||||||
|
|
||||||
|
// 5. 异步获取字典数据
|
||||||
|
var dictDataList = await sysDictDataService.GetDataList(DictTypeCode);
|
||||||
|
var dictHashSet = new HashSet<string>(dictDataList.Select(d => d.Code), StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// 6. 单选校验
|
||||||
|
if (!Multiple)
|
||||||
|
{
|
||||||
|
var valStr = value?.ToString()?.Trim();
|
||||||
|
if (string.IsNullOrEmpty(valStr) || !dictHashSet.Contains(valStr))
|
||||||
|
{
|
||||||
|
return new ValidationResult($"提示:{ErrorMessage}|字典【{DictTypeCode}】不包含【{value}】!");
|
||||||
|
}
|
||||||
return ValidationResult.Success;
|
return ValidationResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dictHash.Contains(valueAsString))
|
// 7. 多选校验
|
||||||
return new ValidationResult($"提示:{ErrorMessage}|字典【{DictTypeCode}】不包含【{valueAsString}】!", [importHeaderName]);
|
if (value is string stringValue)
|
||||||
|
{
|
||||||
|
var codes = stringValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
foreach (var code in codes)
|
||||||
|
{
|
||||||
|
if (!dictHashSet.Contains(code))
|
||||||
|
{
|
||||||
|
return new ValidationResult($"提示:{ErrorMessage}|字典【{DictTypeCode}】不包含【{code}】!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ValidationResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
return ValidationResult.Success;
|
// 集合类数据校验
|
||||||
|
if (value is IList listValue)
|
||||||
|
{
|
||||||
|
foreach (var item in listValue)
|
||||||
|
{
|
||||||
|
var itemStr = item?.ToString()?.Trim();
|
||||||
|
if (string.IsNullOrEmpty(itemStr) || !dictHashSet.Contains(itemStr))
|
||||||
|
{
|
||||||
|
return new ValidationResult($"提示:{ErrorMessage}|字典【{DictTypeCode}】不包含【{item}】!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ValidationResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 类型不匹配
|
||||||
|
return new ValidationResult($"提示:{ErrorMessage}|不支持的值类型:{value?.GetType().Name},多选时应为逗号分隔字符串或集合类型。");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取本字段上 [ImporterHeader(Name = "xxx")] 里的Name,如果没有则使用defaultName.
|
/// 同步入口(兼容旧版调用)
|
||||||
/// 用于在从excel导入数据时,能让调用者知道是哪个字段验证失败,而不是抛异常
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static string GetImporterHeaderName(PropertyInfo property, string defaultName)
|
protected override ValidationResult IsValid(object? value, ValidationContext validationContext)
|
||||||
{
|
{
|
||||||
var importerHeader = property.GetCustomAttribute<ImporterHeaderAttribute>();
|
return IsValidAsync(value, validationContext, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
string importerHeaderName = importerHeader?.Name ?? defaultName;
|
}
|
||||||
return importerHeaderName;
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断值是否为空(null、空字符串、空集合)
|
||||||
|
/// </summary>
|
||||||
|
private static bool IsEmpty(object? value)
|
||||||
|
{
|
||||||
|
return value switch
|
||||||
|
{
|
||||||
|
null => true,
|
||||||
|
string s => string.IsNullOrWhiteSpace(s),
|
||||||
|
IList list => list.Count == 0,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -10,7 +10,7 @@ namespace Admin.NET.Core;
|
|||||||
/// 枚举值合规性校验特性
|
/// 枚举值合规性校验特性
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SuppressSniffer]
|
[SuppressSniffer]
|
||||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Enum | AttributeTargets.Field, AllowMultiple = true)]
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Enum | AttributeTargets.Field)]
|
||||||
public class EnumAttribute : ValidationAttribute, ITransient
|
public class EnumAttribute : ValidationAttribute, ITransient
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -31,22 +31,22 @@ public class EnumAttribute : ValidationAttribute, ITransient
|
|||||||
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
||||||
{
|
{
|
||||||
// 获取属性的类型
|
// 获取属性的类型
|
||||||
var property = validationContext.ObjectType.GetProperty(validationContext.MemberName);
|
var property = validationContext.ObjectType.GetProperty(validationContext.MemberName!);
|
||||||
if (property == null)
|
if (property == null) return new ValidationResult($"未知属性: {validationContext.MemberName}");
|
||||||
return new ValidationResult($"未知属性: {validationContext.MemberName}");
|
|
||||||
|
|
||||||
var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
|
// 如果是可空类型,则允许为null
|
||||||
|
var propertyType = Nullable.GetUnderlyingType(property.PropertyType);
|
||||||
|
if (value == null && propertyType == null) return ValidationResult.Success;
|
||||||
|
|
||||||
// 检查属性类型是否为枚举或可空枚举类型
|
// 获取实际属性类型
|
||||||
|
propertyType ??= property.PropertyType;
|
||||||
|
|
||||||
|
// 检查属性类型是否为枚举
|
||||||
if (!propertyType.IsEnum)
|
if (!propertyType.IsEnum)
|
||||||
return new ValidationResult($"属性类型'{validationContext.MemberName}'不是有效的枚举类型!");
|
return new ValidationResult($"'{validationContext.MemberName}'不是有效的枚举类型!");
|
||||||
|
|
||||||
// 检查枚举值是否有效
|
|
||||||
if (value == null && Nullable.GetUnderlyingType(property.PropertyType) == null)
|
|
||||||
return new ValidationResult($"提示:{ErrorMessage}|枚举值不能为 null!");
|
|
||||||
|
|
||||||
if (value != null && !Enum.IsDefined(propertyType, value))
|
if (value != null && !Enum.IsDefined(propertyType, value))
|
||||||
return new ValidationResult($"提示:{ErrorMessage}|枚举值【{value}】不是有效的【{propertyType.Name}】枚举类型值!");
|
return new ValidationResult(ErrorMessage ?? $"'{value}'不是有效的'{validationContext.MemberName}'枚举值");
|
||||||
|
|
||||||
return ValidationResult.Success;
|
return ValidationResult.Success;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,31 +10,44 @@ namespace Admin.NET.Core;
|
|||||||
/// 身份证号码合规性校验特性
|
/// 身份证号码合规性校验特性
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SuppressSniffer]
|
[SuppressSniffer]
|
||||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||||
public class IdCardNoAttribute : ValidationAttribute
|
public class IdCardNoAttribute : ValidationAttribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否允许空字符串
|
/// 是否允许空字符串
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool AllowEmptyStrings { get; set; } = false;
|
[Obsolete("参数已废弃,逻辑已删除,请使用[Required]特性校验必填")]
|
||||||
|
public bool AllowEmptyStrings { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 允许空值,有值才验证,默认 false
|
/// 允许空值,有值才验证,默认 false
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool AllowNullValue { get; set; } = false;
|
[Obsolete("参数已废弃,逻辑已删除,请使用[Required]特性校验必填")]
|
||||||
|
public bool AllowNullValue { get; set; } = false;
|
||||||
|
|
||||||
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
||||||
{
|
{
|
||||||
// 判断是否允许空值
|
// 判断是否允许空值
|
||||||
if (AllowNullValue && value == null) return ValidationResult.Success;
|
if (IsEmpty(value)) return ValidationResult.Success;
|
||||||
|
|
||||||
string idCardNo = value?.ToString();
|
string idCardNo = value?.ToString();
|
||||||
|
|
||||||
// 是否允许空字符串
|
if (!IdCardHelper.CheckIdCard(idCardNo))
|
||||||
if (AllowEmptyStrings && string.IsNullOrEmpty(idCardNo)) return ValidationResult.Success;
|
return new ValidationResult($"身份证号格式不正确");
|
||||||
|
|
||||||
if (!IdCardHelper.CheckIdCard(idCardNo)) return new ValidationResult($"身份证号码({idCardNo})校验未通过, 请检查证件号的有效性!");
|
|
||||||
|
|
||||||
return ValidationResult.Success;
|
return ValidationResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断值是否为空(null、空字符串、空集合)
|
||||||
|
/// </summary>
|
||||||
|
private static bool IsEmpty(object? value)
|
||||||
|
{
|
||||||
|
return value switch
|
||||||
|
{
|
||||||
|
null => true,
|
||||||
|
string s => string.IsNullOrWhiteSpace(s),
|
||||||
|
IList list => list.Count == 0,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -35,7 +35,7 @@ public class AddOrgInput : SysOrg
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 机构类型
|
/// 机构类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Dict("org_type", ErrorMessage = "机构类型不能合法", AllowNullValue = true, AllowEmptyStrings = true)]
|
[Dict("org_type", ErrorMessage = "机构类型不能合法")]
|
||||||
public override string? Type { get; set; }
|
public override string? Type { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -80,13 +80,13 @@ public class PageSerialInput : BasePageInput
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 使用分类
|
/// 使用分类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Dict(nameof(SerialTypeEnum), AllowNullValue = true)]
|
[Dict(nameof(SerialTypeEnum))]
|
||||||
public SerialTypeEnum? Type { get; set; }
|
public SerialTypeEnum? Type { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 状态
|
/// 状态
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Dict(nameof(StatusEnum), AllowNullValue = true)]
|
[Dict(nameof(StatusEnum))]
|
||||||
public StatusEnum? Status { get; set; }
|
public StatusEnum? Status { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,263 +1,464 @@
|
|||||||
<!-- 组件使用文档: https://gitee.com/zuohuaijun/Admin.NET/pulls/1559 -->
|
<!--
|
||||||
|
字典组件 - 根据字典编码渲染不同类型的数据展示控件
|
||||||
|
组件文档:https://gitee.com/zuohuaijun/Admin.NET/pulls/1559
|
||||||
|
|
||||||
|
功能特性:
|
||||||
|
- 支持多种渲染方式:标签、下拉框、单选框、复选框、单选按钮组
|
||||||
|
- 支持常量和普通字典
|
||||||
|
- 支持多选和单选模式
|
||||||
|
- 支持数组多选模式和逗号多选模式
|
||||||
|
- 支持自定义显示格式和过滤
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
const renderTypeArray = ['tag', 'select', 'radio', 'checkbox', 'radio-button'] as const;
|
/**
|
||||||
|
* 支持的渲染类型常量
|
||||||
|
* @type {Array}
|
||||||
|
* @constant
|
||||||
|
*/
|
||||||
|
const RENDER_TYPES = ['tag', 'select', 'radio', 'checkbox', 'radio-button'] as const;
|
||||||
|
type RenderType = typeof RENDER_TYPES[number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支持的标签类型常量
|
||||||
|
* @type {Array}
|
||||||
|
* @constant
|
||||||
|
*/
|
||||||
|
const TAG_TYPES = ['success', 'warning', 'info', 'primary', 'danger'] as const;
|
||||||
|
type TagType = typeof TAG_TYPES[number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典项数据结构
|
||||||
|
* @interface
|
||||||
|
* @property {string} [tagType] - 标签类型(当renderAs='tag'时生效)
|
||||||
|
* @property {string} [styleSetting] - 自定义样式
|
||||||
|
* @property {string} [classSetting] - 自定义类名
|
||||||
|
* @property {string} [label] - 显示文本
|
||||||
|
* @property {string|number} [value] - 值
|
||||||
|
*/
|
||||||
|
interface DictItem {
|
||||||
|
[key: string]: any;
|
||||||
|
tagType?: TagType;
|
||||||
|
styleSetting?: string;
|
||||||
|
classSetting?: string;
|
||||||
|
label?: string;
|
||||||
|
value?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多选值模式枚举
|
||||||
|
* @enum {string}
|
||||||
|
* @property {string} Array - 数组模式,如['1','2','3']
|
||||||
|
* @property {string} Comma - 逗号分隔模式,如'1,2,3'
|
||||||
|
*/
|
||||||
|
enum MultipleModel {
|
||||||
|
Array = 'array',
|
||||||
|
Comma = 'comma',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否为合法的渲染类型
|
||||||
|
* @function
|
||||||
|
* @param {any} value - 待检查的值
|
||||||
|
* @returns {value is RenderType} - 是否为合法的渲染类型
|
||||||
|
*/
|
||||||
|
function isRenderType(value: any): value is RenderType {
|
||||||
|
return RENDER_TYPES.includes(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否为合法的多选模式
|
||||||
|
* @function
|
||||||
|
* @param {any} value - 待检查的值
|
||||||
|
* @returns {value is MultipleModel} - 是否为合法的多选模式
|
||||||
|
*/
|
||||||
|
function isMultipleModel(value: any): value is MultipleModel {
|
||||||
|
return Object.values(MultipleModel).includes(value);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, watch, PropType } from 'vue';
|
import { reactive, watch, computed, PropType } from 'vue';
|
||||||
import { useUserInfo } from '/@/stores/userInfo';
|
import { useUserInfo } from '/@/stores/userInfo';
|
||||||
|
|
||||||
type RenderType = (typeof renderTypeArray)[number];
|
|
||||||
type DictItem = {
|
|
||||||
[key: string]: any;
|
|
||||||
tagType?: string;
|
|
||||||
styleSetting?: string;
|
|
||||||
classSetting?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const userStore = useUserInfo();
|
const userStore = useUserInfo();
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件属性定义
|
||||||
|
*/
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
/**
|
/**
|
||||||
* 绑定的值,支持多种类型
|
* 绑定值,支持多种类型
|
||||||
* @example
|
* @type {string|number|boolean|Array|null}
|
||||||
* <g-sys-dict v-model="selectedValue" code="xxxx" />
|
* @required
|
||||||
*/
|
* @example
|
||||||
modelValue: {
|
* // 单选选择器
|
||||||
type: [String, Number, Boolean, Array, null] as PropType<string | number | boolean | any[] | null>,
|
* <g-sys-dict v-model="selectedValue" code="gender" renderAs="select" />
|
||||||
default: null,
|
*
|
||||||
required: true,
|
* // 多选选择器(数组模式)
|
||||||
},
|
* <g-sys-dict v-model="selectedValues" code="roles" renderAs="select" multiple />
|
||||||
/**
|
*
|
||||||
* 字典编码,用于获取字典项
|
* // 多选选择器(逗号模式)
|
||||||
* @example 'gender'
|
* <g-sys-dict v-model="selectedValues" code="roles" renderAs="select" multiple multiple-model="comma" />
|
||||||
*/
|
*/
|
||||||
code: {
|
modelValue: {
|
||||||
type: String,
|
type: [String, Number, Boolean, Array, null] as PropType<string | number | boolean | any[] | null>,
|
||||||
required: true,
|
default: null,
|
||||||
},
|
required: true,
|
||||||
/**
|
},
|
||||||
* 是否是常量
|
/**
|
||||||
* @default false
|
* 字典编码,用于从字典中获取数据
|
||||||
*/
|
* @type {string}
|
||||||
isConst: {
|
* @required
|
||||||
type: Boolean,
|
* @example 'gender'
|
||||||
default: false,
|
*/
|
||||||
},
|
code: {
|
||||||
/**
|
type: String,
|
||||||
* 字典项中用于显示的字段名
|
required: true,
|
||||||
* @default 'label'
|
},
|
||||||
*/
|
/**
|
||||||
propLabel: {
|
* 是否为常量字典(true从常量列表获取,false从字典列表获取)
|
||||||
type: String,
|
* @type {boolean}
|
||||||
default: 'label',
|
* @default false
|
||||||
},
|
* @example true
|
||||||
/**
|
*/
|
||||||
* 字典项中用于取值的字段名
|
isConst: {
|
||||||
* @default 'value'
|
type: Boolean,
|
||||||
*/
|
default: false,
|
||||||
propValue: {
|
},
|
||||||
type: String,
|
/**
|
||||||
default: 'value',
|
* 字典项中用于显示的字段名
|
||||||
},
|
* @type {string}
|
||||||
/**
|
* @default 'label'
|
||||||
* 字典项过滤函数
|
* @example 'name'
|
||||||
* @param dict - 字典项
|
*/
|
||||||
* @returns 是否保留该项
|
propLabel: {
|
||||||
* @default (dict) => true
|
type: String,
|
||||||
*/
|
default: 'label',
|
||||||
onItemFilter: {
|
},
|
||||||
type: Function as PropType<(dict: DictItem) => boolean>,
|
/**
|
||||||
default: (dict: DictItem) => true,
|
* 字典项中用于取值的字段名
|
||||||
},
|
* @type {string}
|
||||||
/**
|
* @default 'value'
|
||||||
* 字典项显示内容格式化函数
|
* @example 'id'
|
||||||
* @param dict - 字典项
|
*/
|
||||||
* @returns 格式化后的显示内容
|
propValue: {
|
||||||
* @default () => undefined
|
type: String,
|
||||||
*/
|
default: 'value',
|
||||||
onItemFormatter: {
|
},
|
||||||
type: Function as PropType<(dict: DictItem) => string | undefined | null>,
|
/**
|
||||||
default: () => undefined,
|
* 字典项过滤函数
|
||||||
},
|
* @type {Function}
|
||||||
/**
|
* @param {DictItem} dict - 当前字典项
|
||||||
* 组件渲染方式
|
* @returns {boolean} - 是否保留该项
|
||||||
* @values 'tag', 'select', 'radio', 'checkbox', 'radio-button'
|
* @default (dict) => true
|
||||||
* @default 'tag'
|
* @example
|
||||||
*/
|
* // 只显示启用的字典项
|
||||||
renderAs: {
|
* :onItemFilter="(dict) => dict.status === 1"
|
||||||
type: String as PropType<RenderType>,
|
*/
|
||||||
default: 'tag',
|
onItemFilter: {
|
||||||
validator(value: any) {
|
type: Function as PropType<(dict: DictItem) => boolean>,
|
||||||
return renderTypeArray.includes(value);
|
default: () => true,
|
||||||
},
|
},
|
||||||
},
|
/**
|
||||||
/**
|
* 字典项显示内容格式化函数
|
||||||
* 是否多选
|
* @type {Function}
|
||||||
* @default false
|
* @param {DictItem} dict - 当前字典项
|
||||||
*/
|
* @returns {string|undefined|null} - 格式化后的显示内容
|
||||||
multiple: {
|
* @default () => undefined
|
||||||
type: Boolean,
|
* @example
|
||||||
default: false,
|
* // 在标签前添加图标
|
||||||
},
|
* :onItemFormatter="(dict) => `${dict.label} <icon-user />`"
|
||||||
|
*/
|
||||||
|
onItemFormatter: {
|
||||||
|
type: Function as PropType<(dict: DictItem) => string | undefined | null>,
|
||||||
|
default: () => undefined,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件渲染方式
|
||||||
|
* @type {'tag'|'select'|'radio'|'checkbox'|'radio-button'}
|
||||||
|
* @default 'tag'
|
||||||
|
* @example 'select'
|
||||||
|
*/
|
||||||
|
renderAs: {
|
||||||
|
type: String as PropType<RenderType>,
|
||||||
|
default: 'tag',
|
||||||
|
validator: isRenderType,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 是否多选(仅在renderAs为select/checkbox时有效)
|
||||||
|
* @type {boolean}
|
||||||
|
* @default false
|
||||||
|
* @example true
|
||||||
|
*/
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 多选值模式(仅在multiple为true时有效)
|
||||||
|
* @type {'array'|'comma'}
|
||||||
|
* @default 'array'
|
||||||
|
* @example 'comma'
|
||||||
|
*/
|
||||||
|
multipleModel: {
|
||||||
|
type: String as PropType<MultipleModel>,
|
||||||
|
default: MultipleModel.Array,
|
||||||
|
validator: isMultipleModel,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const state = reactive({
|
/**
|
||||||
dict: undefined as DictItem | DictItem[] | undefined,
|
* 格式化后的字典数据(计算属性)
|
||||||
dictData: [] as DictItem[],
|
* @computed
|
||||||
value: undefined as any,
|
* @returns {DictItem[]} - 过滤并格式化后的字典数据
|
||||||
|
*/
|
||||||
|
const formattedDictData = computed(() => {
|
||||||
|
return state.dictData
|
||||||
|
.filter(props.onItemFilter)
|
||||||
|
.map(item => ({
|
||||||
|
...item,
|
||||||
|
label: item[props.propLabel],
|
||||||
|
value: item[props.propValue],
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取数据集
|
/**
|
||||||
const getDataList = () => {
|
* 当前选中的字典项(计算属性)
|
||||||
if (props.isConst) {
|
* @computed
|
||||||
const data = userStore.constList?.find((x: any) => x.code === props.code)?.data?.result ?? [];
|
* @returns {DictItem|DictItem[]|null} - 当前选中的字典项或字典项数组
|
||||||
// 与字典的显示文本、值保持一致,方便渲染
|
*/
|
||||||
data?.forEach((item: any) => {
|
const currentDictItems = computed(() => {
|
||||||
item.label = item.name;
|
if (!state.value) return null;
|
||||||
item.value = item.code;
|
|
||||||
delete item.name;
|
if (Array.isArray(state.value)) {
|
||||||
});
|
// 确保回显时也去重
|
||||||
return data;
|
const uniqueValues = [...new Set(state.value)];
|
||||||
} else {
|
return formattedDictData.value.filter(item =>
|
||||||
return userStore.dictList[props.code];
|
uniqueValues.includes(item.value)
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedDictData.value.find(item => item.value == state.value) || null;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典数据列表
|
||||||
|
* @function
|
||||||
|
* @returns {DictItem[]} - 字典数据列表
|
||||||
|
* @throws {Error} - 获取数据失败时抛出错误
|
||||||
|
*/
|
||||||
|
const getDataList = (): DictItem[] => {
|
||||||
|
try {
|
||||||
|
if (!props.code) {
|
||||||
|
console.error('字典编码不能为空');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = props.isConst ? userStore.constList : userStore.dictList;
|
||||||
|
const data = props.isConst
|
||||||
|
? source?.find((x: any) => x.code === props.code)?.data?.result ?? []
|
||||||
|
: source[props.code] ?? [];
|
||||||
|
|
||||||
|
return data.map((item: any) => ({
|
||||||
|
...item,
|
||||||
|
label: item[props.propLabel] ?? item.name,
|
||||||
|
value: item[props.propValue] ?? item.code,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`获取字典[${props.code}]数据失败:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置字典数据
|
/**
|
||||||
const setDictData = () => {
|
* 处理数字类型的值
|
||||||
state.dictData = getDataList()?.filter(props.onItemFilter) ?? [];
|
* @function
|
||||||
processNumericValues(props.modelValue);
|
* @param {any} value - 待处理的值
|
||||||
};
|
*/
|
||||||
|
|
||||||
// 处理数字类型的值
|
|
||||||
const processNumericValues = (value: any) => {
|
const processNumericValues = (value: any) => {
|
||||||
if (typeof value === 'number' || (Array.isArray(value) && typeof value[0] === 'number')) {
|
if (typeof value === 'number' || (Array.isArray(value) && typeof value[0] === 'number')) {
|
||||||
state.dictData.forEach((item) => {
|
state.dictData.forEach(item => {
|
||||||
item[props.propValue] = Number(item[props.propValue]);
|
if (item.value) {
|
||||||
});
|
item.value = Number(item.value);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置多选值
|
/**
|
||||||
const trySetMultipleValue = (value: any) => {
|
* 解析多选值(修复逗号模式问题)
|
||||||
let newValue = value;
|
* @function
|
||||||
if (typeof value === 'string') {
|
* @param {any} value - 待解析的值
|
||||||
const trimmedValue = value.trim();
|
* @returns {any} - 解析后的值
|
||||||
if (trimmedValue.startsWith('[') && trimmedValue.endsWith(']')) {
|
*/
|
||||||
try {
|
const parseMultipleValue = (value: any): any => {
|
||||||
newValue = JSON.parse(trimmedValue);
|
// 处理空值情况
|
||||||
} catch (error) {
|
if (value === null || value === undefined || value === '') {
|
||||||
console.warn('[g-sys-dict]解析多选值失败, 异常信息:', error);
|
return props.multiple ? [] : value;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if ((props.renderAs === 'checkbox' || props.multiple) && !value) {
|
|
||||||
newValue = [];
|
|
||||||
}
|
|
||||||
if (newValue != value) updateValue(newValue);
|
|
||||||
|
|
||||||
setDictData();
|
// 处理字符串值
|
||||||
return newValue;
|
if (typeof value === 'string') {
|
||||||
|
const trimmedValue = value.trim();
|
||||||
|
|
||||||
|
// 处理JSON数组格式
|
||||||
|
if (trimmedValue.startsWith('[') && trimmedValue.endsWith(']')) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(trimmedValue);
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
// 去重处理
|
||||||
|
return [...new Set(parsed.filter(Boolean))];
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[g-sys-dict] 解析多选值失败:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理逗号分隔格式
|
||||||
|
if (props.multipleModel === MultipleModel.Comma && trimmedValue.includes(',')) {
|
||||||
|
// 去重处理
|
||||||
|
return [...new Set(
|
||||||
|
trimmedValue.split(',')
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理单个值情况
|
||||||
|
return props.multiple ? [trimmedValue] : trimmedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理数组值 - 添加去重
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return [...new Set(value.filter(Boolean))];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他情况直接返回
|
||||||
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置字典值
|
/**
|
||||||
const setDictValue = (value: any) => {
|
* 更新绑定值(修复逗号模式问题)
|
||||||
value = trySetMultipleValue(value);
|
* @function
|
||||||
if (Array.isArray(value)) {
|
* @param {any} newValue - 新值
|
||||||
state.dict = state.dictData?.filter((x) => value.find((y) => y == x[props.propValue]));
|
*/
|
||||||
state.dict?.forEach(ensureTagType);
|
|
||||||
} else {
|
|
||||||
state.dict = state.dictData?.find((x) => x[props.propValue] == value);
|
|
||||||
if (state.dict) ensureTagType(state.dict);
|
|
||||||
}
|
|
||||||
state.value = value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 确保标签类型存在
|
|
||||||
const ensureTagType = (item: DictItem) => {
|
|
||||||
if (!['success', 'warning', 'info', 'primary', 'danger'].includes(item.tagType ?? '')) {
|
|
||||||
item.tagType = 'primary';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 更新绑定值
|
|
||||||
const updateValue = (newValue: any) => {
|
const updateValue = (newValue: any) => {
|
||||||
emit('update:modelValue', newValue);
|
// 先对值进行去重处理
|
||||||
|
let processedValue = newValue;
|
||||||
|
if (Array.isArray(newValue)) {
|
||||||
|
processedValue = [...new Set(newValue.filter(v => v !== null && v !== undefined && v !== ''))];
|
||||||
|
}
|
||||||
|
|
||||||
|
let emitValue = processedValue;
|
||||||
|
|
||||||
|
if (props.multipleModel === MultipleModel.Comma) {
|
||||||
|
if (Array.isArray(processedValue)) {
|
||||||
|
emitValue = processedValue.length > 0 ? processedValue.join(',') : '';
|
||||||
|
} else if (processedValue === null || processedValue === undefined) {
|
||||||
|
emitValue = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.value = processedValue;
|
||||||
|
emit('update:modelValue', emitValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 计算显示的文本
|
/**
|
||||||
const getDisplayText = (dict: DictItem | undefined = undefined) => {
|
* 确保标签类型存在
|
||||||
if (dict) return props.onItemFormatter?.(dict) ?? dict[props.propLabel];
|
* @function
|
||||||
return state.value;
|
* @param {DictItem} item - 字典项
|
||||||
|
* @returns {TagType} - 合法的标签类型
|
||||||
|
*/
|
||||||
|
const ensureTagType = (item: DictItem): TagType => {
|
||||||
|
return TAG_TYPES.includes(item.tagType as TagType) ? item.tagType as TagType : 'primary';
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
/**
|
||||||
() => props.modelValue,
|
* 计算显示的文本
|
||||||
(newValue) => setDictValue(newValue),
|
* @function
|
||||||
{ immediate: true }
|
* @param {DictItem} [dict] - 字典项
|
||||||
);
|
* @returns {string} - 显示文本
|
||||||
|
*/
|
||||||
|
const getDisplayText = (dict?: DictItem): string => {
|
||||||
|
if (!dict) return String(state.value || '');
|
||||||
|
const formattedText = props.onItemFormatter?.(dict);
|
||||||
|
return formattedText ?? dict[props.propLabel] ?? '';
|
||||||
|
};
|
||||||
|
|
||||||
watch(
|
/**
|
||||||
() => userStore.dictList,
|
* 初始化数据
|
||||||
() => setDictValue(state.value),
|
* @function
|
||||||
{ immediate: true }
|
*/
|
||||||
);
|
const initData = () => {
|
||||||
|
state.dictData = getDataList();
|
||||||
|
processNumericValues(props.modelValue);
|
||||||
|
const initialValue = parseMultipleValue(props.modelValue);
|
||||||
|
if (initialValue !== state.value) {
|
||||||
|
state.value = initialValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
watch(
|
/**
|
||||||
() => userStore.constList,
|
* 组件状态
|
||||||
() => setDictValue(state.value),
|
* @property {DictItem[]} dictData - 原始字典数据
|
||||||
{ immediate: true }
|
* @property {any} value - 当前值
|
||||||
);
|
*/
|
||||||
|
const state = reactive({
|
||||||
|
dictData: [] as DictItem[],
|
||||||
|
value: parseMultipleValue(props.modelValue),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听数据变化
|
||||||
|
watch(() => props.modelValue, (newValue) => {
|
||||||
|
state.value = parseMultipleValue(newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => [userStore.dictList, userStore.constList], initData, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 渲染标签 -->
|
<!-- 渲染标签 -->
|
||||||
<template v-if="props.renderAs === 'tag'">
|
<template v-if="props.renderAs === 'tag'">
|
||||||
<template v-if="Array.isArray(state.dict)">
|
<template v-if="Array.isArray(currentDictItems)">
|
||||||
<el-tag v-for="(item, index) in state.dict" :key="index" v-bind="$attrs" :type="item.tagType" :style="item.styleSetting" :class="item.classSetting" class="mr2">
|
<el-tag v-for="(item, index) in currentDictItems" :key="index" v-bind="$attrs" :type="ensureTagType(item)" :style="item.styleSetting" :class="item.classSetting" class="mr2">
|
||||||
{{ getDisplayText(item) }}
|
{{ getDisplayText(item) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<el-tag v-if="state.dict" v-bind="$attrs" :type="state.dict.tagType" :style="state.dict.styleSetting" :class="state.dict.classSetting">
|
<el-tag v-if="currentDictItems" v-bind="$attrs" :type="ensureTagType(currentDictItems)" :style="currentDictItems.styleSetting" :class="currentDictItems.classSetting">
|
||||||
{{ getDisplayText(state.dict) }}
|
{{ getDisplayText(currentDictItems) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<span v-else>{{ getDisplayText() }}</span>
|
<span v-else>{{ getDisplayText() }}</span>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 渲染选择器 -->
|
<!-- 渲染选择器 -->
|
||||||
<template v-if="props.renderAs === 'select'">
|
<el-select v-else-if="props.renderAs === 'select'" v-model="state.value" v-bind="$attrs" :multiple="props.multiple" @change="updateValue" clearable>
|
||||||
<el-select v-model="state.value" v-bind="$attrs" :multiple="props.multiple" @change="updateValue" clearable>
|
<el-option v-for="(item, index) in formattedDictData" :key="index" :label="getDisplayText(item)" :value="item.value" />
|
||||||
<el-option v-for="(item, index) in state.dictData" :key="index" :label="getDisplayText(item)" :value="item[propValue]" />
|
</el-select>
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 渲染复选框(多选) -->
|
<!-- 渲染复选框(多选) -->
|
||||||
<template v-if="props.renderAs === 'checkbox'">
|
<el-checkbox-group v-else-if="props.renderAs === 'checkbox'" v-model="state.value" v-bind="$attrs" @change="updateValue">
|
||||||
<el-checkbox-group v-model="state.value" v-bind="$attrs" @change="updateValue">
|
<el-checkbox-button v-for="(item, index) in formattedDictData" :key="index" :value="item.value">
|
||||||
<el-checkbox-button v-for="(item, index) in state.dictData" :key="index" :value="item[propValue]">
|
{{ getDisplayText(item) }}
|
||||||
{{ getDisplayText(item) }}
|
</el-checkbox-button>
|
||||||
</el-checkbox-button>
|
</el-checkbox-group>
|
||||||
</el-checkbox-group>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 渲染单选框 -->
|
<!-- 渲染单选框 -->
|
||||||
<template v-if="props.renderAs === 'radio'">
|
<el-radio-group v-else-if="props.renderAs === 'radio'" v-model="state.value" v-bind="$attrs" @change="updateValue">
|
||||||
<el-radio-group v-model="state.value" v-bind="$attrs" @change="updateValue">
|
<el-radio v-for="(item, index) in formattedDictData" :key="index" :value="item.value">
|
||||||
<el-radio v-for="(item, index) in state.dictData" :key="index" :value="item[propValue]">
|
{{ getDisplayText(item) }}
|
||||||
{{ getDisplayText(item) }}
|
</el-radio>
|
||||||
</el-radio>
|
</el-radio-group>
|
||||||
</el-radio-group>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 渲染单选框按钮 -->
|
<!-- 渲染单选框按钮 -->
|
||||||
<template v-if="props.renderAs === 'radio-button'">
|
<el-radio-group v-else-if="props.renderAs === 'radio-button'" v-model="state.value" v-bind="$attrs" @change="updateValue">
|
||||||
<el-radio-group v-model="state.value" v-bind="$attrs" @change="updateValue">
|
<el-radio-button v-for="(item, index) in formattedDictData" :key="index" :value="item.value">
|
||||||
<el-radio-button v-for="(item, index) in state.dictData" :key="index" :value="item[propValue]">
|
{{ getDisplayText(item) }}
|
||||||
{{ getDisplayText(item) }}
|
</el-radio-button>
|
||||||
</el-radio-button>
|
</el-radio-group>
|
||||||
</el-radio-group>
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
Loading…
Reference in New Issue
Block a user