530 lines
22 KiB
C#
530 lines
22 KiB
C#
using MiaoYu.Repository.Admin.Entities.LowCode;
|
||
using MiaoYu.Shared.Admin.Models.LowCodes;
|
||
using CoreIDatabaseTableService = MiaoYu.Core.CodeGenerator.Services.IDatabaseTableService;
|
||
using CoreLowCodeTableInfo = MiaoYu.Core.CodeGenerator.Models.LowCodeTableInfo;
|
||
using CoreITableMetaConfigService = MiaoYu.Core.CodeGenerator.Services.ITableMetaConfigService;
|
||
using CoreGenDbTableDto = MiaoYu.Core.CodeGenerator.Models.GenDbTableDto;
|
||
using CoreTableMetaConfig = MiaoYu.Core.CodeGenerator.Models.TableMetaConfig;
|
||
using CoreColumnMetaConfig = MiaoYu.Core.CodeGenerator.Models.ColumnMetaConfig;
|
||
|
||
namespace MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl;
|
||
|
||
/// <summary>
|
||
/// 服务 Low_Code_Table_InfoService
|
||
/// </summary>
|
||
public class LowCodeTableInfoService : ApplicationService<IRepository<LowCodeTableInfo>>
|
||
{
|
||
private readonly CoreIDatabaseTableService _databaseTableService;
|
||
private readonly IRepository<LowCodeTable> _lowCodeTableRepository;
|
||
private readonly CoreITableMetaConfigService _tableMetaConfigService;
|
||
private readonly ILogger<LowCodeTableInfoService>? _logger;
|
||
|
||
public LowCodeTableInfoService(
|
||
CoreIDatabaseTableService databaseTableService,
|
||
IRepository<LowCodeTableInfo> defaultRepository,
|
||
IRepository<LowCodeTable> lowCodeTableRepository,
|
||
CoreITableMetaConfigService tableMetaConfigService,
|
||
ILogger<LowCodeTableInfoService>? logger = null) : base(defaultRepository)
|
||
{
|
||
_databaseTableService = databaseTableService;
|
||
_lowCodeTableRepository = lowCodeTableRepository;
|
||
_tableMetaConfigService = tableMetaConfigService;
|
||
_logger = logger;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据表名和数据库标识获取列列表(推荐使用)
|
||
/// </summary>
|
||
/// <param name="input">查询参数,包含表名和数据库标识</param>
|
||
/// <returns></returns>
|
||
public Task<PagingView> FindListByTableAsync(TableColumnQueryInput input)
|
||
{
|
||
// 参数验证
|
||
if (string.IsNullOrWhiteSpace(input.TableName) || string.IsNullOrWhiteSpace(input.DataBase))
|
||
{
|
||
return Task.FromResult(new PagingView(input.Page, input.Size));
|
||
}
|
||
|
||
// 从 Core 层获取所有表信息(已包含配置合并)
|
||
var allTables = _databaseTableService.GetAllTables();
|
||
|
||
// 直接使用表名和数据库标识查找目标表
|
||
var targetTable = allTables.FirstOrDefault(t =>
|
||
t.TableName == input.TableName && t.DataBase == input.DataBase);
|
||
|
||
if (targetTable == null || targetTable.TableInfos == null)
|
||
{
|
||
return Task.FromResult(new PagingView(input.Page, input.Size));
|
||
}
|
||
|
||
// 应用筛选条件
|
||
var columns = targetTable.TableInfos.AsEnumerable();
|
||
|
||
if (!string.IsNullOrWhiteSpace(input.Search?.ColumnName))
|
||
{
|
||
columns = columns.Where(w => w.ColumnName != null && w.ColumnName.Contains(input.Search.ColumnName));
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(input.Search?.Describe))
|
||
{
|
||
columns = columns.Where(w => w.Describe != null && w.Describe.Contains(input.Search.Describe));
|
||
}
|
||
|
||
// 排序和分页
|
||
var orderedColumns = columns
|
||
.OrderBy(w => w.Position)
|
||
.Skip((input.Page - 1) * input.Size)
|
||
.Take(input.Size)
|
||
.Select(w => new Dictionary<string, object?>
|
||
{
|
||
["id"] = Guid.NewGuid(), // 临时 ID,因为是从内存查询
|
||
["isPrimary"] = w.IsPrimary,
|
||
["isIdentity"] = w.IsIdentity,
|
||
["isNullable"] = w.IsNullable,
|
||
["position"] = w.Position,
|
||
["columnName"] = w.ColumnName,
|
||
["describe"] = w.Describe,
|
||
["databaseColumnType"] = w.DatabaseColumnType,
|
||
["csType"] = w.CsType,
|
||
["csField"] = w.CsField,
|
||
["displayName"] = w.DisplayName,
|
||
["tableName"] = input.TableName,
|
||
["dataBase"] = input.DataBase,
|
||
["lastModificationTime"] = DateTime.Now.ToString("yyyy-MM-dd"),
|
||
["creationTime"] = DateTime.Now.ToString("yyyy-MM-dd"),
|
||
["isTableColumnShow"] = w.IsTableColumnShow ?? true,
|
||
["isTableSelect"] = w.IsTableSelect,
|
||
["isImageId"] = w.IsImageId,
|
||
["orderById"] = w.OrderById ?? 10,
|
||
["width"] = w.Width,
|
||
["maxLength"] = w.MaxLength
|
||
})
|
||
.ToList();
|
||
|
||
return Task.FromResult(new PagingView(input.Page, input.Size)
|
||
{
|
||
Total = columns.Count(),
|
||
DataSource = orderedColumns,
|
||
PageCount = (columns.Count() + input.Size - 1) / input.Size
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取列表数据(从 Core 层查询表结构并合并配置)
|
||
/// </summary>
|
||
/// <param name="pagingSearchInput"></param>
|
||
/// <returns></returns>
|
||
[Obsolete("请使用 FindListByTableAsync 方法,该方法使用 TableName + DataBase 作为查询参数")]
|
||
public async Task<PagingView> FindListAsync(PagingSearchInput<LowCodeTableInfo> pagingSearchInput)
|
||
{
|
||
// ✅ 从 Core 层获取所有表信息(已包含配置合并)
|
||
var allTables = _databaseTableService.GetAllTables();
|
||
|
||
// 根据 Low_Code_TableId 查找对应的表名和数据库
|
||
string? targetTableName = null;
|
||
string? targetDatabase = null;
|
||
|
||
if (pagingSearchInput.Search.Low_Code_TableId != Guid.Empty)
|
||
{
|
||
var lowCodeTable = await _lowCodeTableRepository.FindAsync(w => w.Id == pagingSearchInput.Search.Low_Code_TableId);
|
||
if (lowCodeTable != null)
|
||
{
|
||
targetTableName = lowCodeTable.TableName;
|
||
targetDatabase = lowCodeTable.DataBase;
|
||
}
|
||
}
|
||
|
||
// 筛选目标表
|
||
var targetTable = allTables.FirstOrDefault(t =>
|
||
t.TableName == targetTableName && t.DataBase == targetDatabase);
|
||
|
||
if (targetTable == null || targetTable.TableInfos == null)
|
||
{
|
||
return new PagingView(pagingSearchInput.Page, pagingSearchInput.Size);
|
||
}
|
||
|
||
// 应用筛选条件
|
||
var columns = targetTable.TableInfos.AsEnumerable();
|
||
|
||
if (!string.IsNullOrWhiteSpace(pagingSearchInput.Search.ColumnName))
|
||
{
|
||
columns = columns.Where(w => w.ColumnName != null && w.ColumnName.Contains(pagingSearchInput.Search.ColumnName));
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(pagingSearchInput.Search.Describe))
|
||
{
|
||
columns = columns.Where(w => w.Describe != null && w.Describe.Contains(pagingSearchInput.Search.Describe));
|
||
}
|
||
|
||
// 排序和分页
|
||
var orderedColumns = columns
|
||
.OrderBy(w => w.Position)
|
||
.Skip((pagingSearchInput.Page - 1) * pagingSearchInput.Size)
|
||
.Take(pagingSearchInput.Size)
|
||
.Select(w => new Dictionary<string, object?>
|
||
{
|
||
["id"] = Guid.NewGuid(), // 临时 ID,因为是从内存查询
|
||
["isPrimary"] = w.IsPrimary,
|
||
["isIdentity"] = w.IsIdentity,
|
||
["isNullable"] = w.IsNullable,
|
||
["position"] = w.Position,
|
||
["columnName"] = w.ColumnName,
|
||
["describe"] = w.Describe,
|
||
["databaseColumnType"] = w.DatabaseColumnType,
|
||
["csType"] = w.CsType,
|
||
["csField"] = w.CsField,
|
||
["displayName"] = w.DisplayName,
|
||
["low_Code_TableId"] = pagingSearchInput.Search.Low_Code_TableId,
|
||
["lastModificationTime"] = DateTime.Now.ToString("yyyy-MM-dd"),
|
||
["creationTime"] = DateTime.Now.ToString("yyyy-MM-dd"),
|
||
["isTableColumnShow"] = w.IsTableColumnShow ?? true,
|
||
["isTableSelect"] = w.IsTableSelect,
|
||
["isImageId"] = w.IsImageId,
|
||
["orderById"] = w.OrderById ?? 10,
|
||
["width"] = w.Width,
|
||
["maxLength"] = w.MaxLength
|
||
})
|
||
.ToList();
|
||
|
||
return new PagingView(pagingSearchInput.Page, pagingSearchInput.Size)
|
||
{
|
||
Total = columns.Count(),
|
||
DataSource = orderedColumns,
|
||
PageCount = (columns.Count() + pagingSearchInput.Size - 1) / pagingSearchInput.Size
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据id数组删除
|
||
/// </summary>
|
||
/// <param name="ids"></param>
|
||
/// <returns></returns>
|
||
public Task DeleteListAsync(List<Guid> ids)
|
||
{
|
||
_databaseTableService.ClearAllTablesByCache();
|
||
return _defaultRepository.DeleteByIdsAsync(ids);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据表名和数据库标识同步字段(推荐使用)
|
||
/// </summary>
|
||
/// <param name="input">同步参数,包含表名和数据库标识</param>
|
||
/// <returns></returns>
|
||
/// <exception cref="ArgumentException">参数验证失败或表不存在时抛出</exception>
|
||
public Task<bool> SynchronizationByTableAsync(TableSyncInput input)
|
||
{
|
||
// 参数验证
|
||
if (input == null)
|
||
throw new ArgumentException("参数不能为空", nameof(input));
|
||
|
||
if (string.IsNullOrWhiteSpace(input.TableName))
|
||
throw new ArgumentException("表名不能为空", nameof(input));
|
||
|
||
if (string.IsNullOrWhiteSpace(input.DataBase))
|
||
throw new ArgumentException("数据库标识不能为空", nameof(input));
|
||
|
||
// 清除缓存并重新加载
|
||
_databaseTableService.ClearAllTablesByCache();
|
||
_databaseTableService.RefreshCache();
|
||
|
||
// 验证表是否存在
|
||
var allTables = _databaseTableService.GetAllTables();
|
||
var targetTable = allTables.FirstOrDefault(t =>
|
||
t.TableName == input.TableName && t.DataBase == input.DataBase);
|
||
|
||
if (targetTable == null)
|
||
throw new ArgumentException($"指定的表不存在:表名={input.TableName}, 数据库={input.DataBase}", nameof(input));
|
||
|
||
return Task.FromResult(true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步表
|
||
/// </summary>
|
||
/// <param name="tableId"></param>
|
||
/// <param name="isTableSync"></param>
|
||
/// <returns></returns>
|
||
[Obsolete("请使用 SynchronizationByTableAsync 方法,该方法使用 TableName + DataBase 作为查询参数")]
|
||
public async Task SynchronizationColumnByTableIdAsync(Guid tableId, bool isTableSync = false)
|
||
{
|
||
var allTables = _databaseTableService.GetAllTableInfos();
|
||
var table = await _lowCodeTableRepository.FindAsync(w => w.Id == tableId);
|
||
|
||
// 修复:同时匹配表名和数据库标识
|
||
var tableInfo = allTables.Find(w =>
|
||
w.Name == table.TableName && w.DataBase == table.DataBase);
|
||
|
||
//查询出当前表所有的字段
|
||
var tableColumns = await _defaultRepository.ToListAsync(w => w.Low_Code_TableId == table.Id);
|
||
|
||
//操作集合
|
||
var list = new List<LowCodeTableInfo>();
|
||
|
||
if (isTableSync)
|
||
{
|
||
if (tableColumns != null && tableColumns.Count == 0)
|
||
{
|
||
foreach (var item in tableInfo.Columns)
|
||
{
|
||
// if (tableColumns.Any(w => w.ColumnName == item.Name)) continue;
|
||
|
||
var model = new LowCodeTableInfo();
|
||
model.IsPrimary = item.IsPrimary;
|
||
model.IsIdentity = item.IsIdentity;
|
||
model.IsNullable = item.IsNullable;
|
||
model.Position = item.Position;
|
||
model.Low_Code_TableId = table.Id;
|
||
model.ColumnName = item.Name;
|
||
model.DatabaseColumnType = item.DbType;
|
||
model.CsType = item.CsType;
|
||
model.CsField = item.Name;
|
||
model.MaxLength = item.MaxLength;
|
||
//model.IsImageId = item.IsImageId;
|
||
//model.IsImageId = item.IsImageId;
|
||
//model.IsImageId = item.IsImageId;
|
||
if (!string.IsNullOrWhiteSpace(item.Comment))
|
||
{
|
||
model.Describe = item.Comment;
|
||
model.DisplayName = item.Comment;
|
||
}
|
||
list.Add(model);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
|
||
|
||
foreach (var item in tableInfo.Columns)
|
||
{
|
||
// if (tableColumns.Any(w => w.ColumnName == item.Name)) continue;
|
||
|
||
var model = new LowCodeTableInfo();
|
||
model.IsPrimary = item.IsPrimary;
|
||
model.IsIdentity = item.IsIdentity;
|
||
model.IsNullable = item.IsNullable;
|
||
model.Position = item.Position;
|
||
model.Low_Code_TableId = table.Id;
|
||
model.ColumnName = item.Name;
|
||
model.DatabaseColumnType = item.DbType;
|
||
model.CsType = item.CsType;
|
||
model.CsField = item.Name;
|
||
model.MaxLength = item.MaxLength;
|
||
if (!string.IsNullOrWhiteSpace(item.Comment))
|
||
{
|
||
model.Describe = item.Comment;
|
||
model.DisplayName = item.Comment;
|
||
}
|
||
var oldColumns = tableColumns.FirstOrDefault(w => w.ColumnName == item.Name);
|
||
if (oldColumns!=null)
|
||
{
|
||
model.OrderById = oldColumns.OrderById;
|
||
model.Describe = oldColumns.Describe;
|
||
model.IsImageId = oldColumns.IsImageId;
|
||
model.IsTableColumnShow = oldColumns.IsTableColumnShow;
|
||
model.IsTableSelect = oldColumns.IsTableSelect;
|
||
model.Width = oldColumns.Width;
|
||
};
|
||
list.Add(model);
|
||
}
|
||
}
|
||
await _defaultRepository.DeleteAsync(w => w.Low_Code_TableId == table.Id);
|
||
await _defaultRepository.InsertRangeAsync(list);
|
||
|
||
_databaseTableService.ClearAllTablesByCache();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据表名和数据库标识变更列配置(推荐使用)
|
||
/// </summary>
|
||
/// <param name="input">修改参数,包含表名、数据库标识和列配置列表</param>
|
||
/// <returns>保存成功返回 true</returns>
|
||
/// <exception cref="ArgumentException">参数验证失败或表不存在时抛出</exception>
|
||
public async Task<bool> ChangeByTableAsync(TableColumnChangeInput input)
|
||
{
|
||
// 参数验证
|
||
if (input == null)
|
||
throw new ArgumentException("参数不能为空", nameof(input));
|
||
|
||
if (string.IsNullOrWhiteSpace(input.TableName))
|
||
throw new ArgumentException("表名不能为空", nameof(input));
|
||
|
||
if (string.IsNullOrWhiteSpace(input.DataBase))
|
||
throw new ArgumentException("数据库标识不能为空", nameof(input));
|
||
|
||
if (input.Columns == null || input.Columns.Count == 0)
|
||
throw new ArgumentException("列配置列表不能为空", nameof(input));
|
||
|
||
// 验证所有列的 ColumnName 不为空
|
||
var invalidColumns = input.Columns.Where(c => string.IsNullOrWhiteSpace(c.ColumnName)).ToList();
|
||
if (invalidColumns.Count > 0)
|
||
throw new ArgumentException("列配置中存在空的列名", nameof(input));
|
||
|
||
// 清除缓存
|
||
_databaseTableService.ClearAllTablesByCache();
|
||
|
||
// 从内存缓存中查找表
|
||
var allTables = _databaseTableService.GetAllTables();
|
||
var targetTable = allTables.FirstOrDefault(t =>
|
||
t.TableName == input.TableName && t.DataBase == input.DataBase);
|
||
|
||
if (targetTable == null)
|
||
throw new ArgumentException($"指定的表不存在:表名={input.TableName}, 数据库={input.DataBase}", nameof(input));
|
||
|
||
if (targetTable.TableInfos == null)
|
||
throw new ArgumentException("表列信息为空", nameof(input));
|
||
|
||
// 更新用户修改的列配置
|
||
var updatedCount = 0;
|
||
foreach (var changedColumn in input.Columns)
|
||
{
|
||
var existingColumn = targetTable.TableInfos.FirstOrDefault(c => c.ColumnName == changedColumn.ColumnName);
|
||
if (existingColumn == null)
|
||
{
|
||
// 列不存在,记录日志但继续处理其他列
|
||
_logger?.LogWarning($"列不存在,跳过更新:表名={input.TableName}, 列名={changedColumn.ColumnName}");
|
||
continue;
|
||
}
|
||
|
||
// 更新可配置字段
|
||
// 注意:由于 JsonExtensionData 只能捕获未定义的属性,而我们的字段都已定义,
|
||
// 所以无法通过 WasFieldProvided 检测。我们采用以下策略:
|
||
// 1. 前端总是传递所有字段(包括 null)
|
||
// 2. 我们直接更新所有字段(字符串 null = 清空,可空值类型 null = 设置为 null)
|
||
|
||
// 字符串字段:直接更新(null = 清空,有值 = 更新)
|
||
existingColumn.DisplayName = changedColumn.DisplayName;
|
||
existingColumn.Describe = changedColumn.Describe;
|
||
existingColumn.CsField = changedColumn.CsField;
|
||
existingColumn.Width = changedColumn.Width;
|
||
|
||
// 可空值类型:直接更新(null = 设置为 null,有值 = 设置为该值)
|
||
existingColumn.IsTableColumnShow = changedColumn.IsTableColumnShow;
|
||
existingColumn.IsTableSelect = changedColumn.IsTableSelect;
|
||
existingColumn.IsImageId = changedColumn.IsImageId;
|
||
existingColumn.OrderById = changedColumn.OrderById;
|
||
|
||
updatedCount++;
|
||
}
|
||
|
||
if (updatedCount == 0)
|
||
{
|
||
throw new ArgumentException("没有成功更新任何列配置,请检查列名是否正确", nameof(input));
|
||
}
|
||
|
||
// 保存到配置文件
|
||
await SaveTableMetaConfigAsync(targetTable);
|
||
|
||
// 清除缓存,确保下次查询时重新加载并合并最新的配置文件
|
||
_databaseTableService.ClearAllTablesByCache();
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 变更数据(保存到配置文件)
|
||
/// </summary>
|
||
/// <param name="lowCodeTableInfos"></param>
|
||
/// <returns></returns>
|
||
[Obsolete("请使用 ChangeByTableAsync 方法,该方法使用 TableName + DataBase 作为查询参数")]
|
||
public async Task ChangeAsync(List<LowCodeTableInfo> lowCodeTableInfos)
|
||
{
|
||
// 清除缓存
|
||
_databaseTableService.ClearAllTablesByCache();
|
||
|
||
// 按表分组保存
|
||
var groupedByTable = lowCodeTableInfos.GroupBy(c => c.Low_Code_TableId);
|
||
|
||
foreach (var group in groupedByTable)
|
||
{
|
||
var tableId = group.Key;
|
||
var lowCodeTable = await _lowCodeTableRepository.FindAsync(w => w.Id == tableId);
|
||
|
||
if (lowCodeTable == null || string.IsNullOrEmpty(lowCodeTable.DataBase) || string.IsNullOrEmpty(lowCodeTable.TableName))
|
||
continue;
|
||
|
||
// 获取完整的表信息(包含所有列)
|
||
var allTables = _databaseTableService.GetAllTables();
|
||
var targetTable = allTables.FirstOrDefault(t =>
|
||
t.TableName == lowCodeTable.TableName &&
|
||
t.DataBase == lowCodeTable.DataBase);
|
||
|
||
if (targetTable == null)
|
||
continue;
|
||
|
||
// 更新用户修改的列配置
|
||
foreach (var changedColumn in group)
|
||
{
|
||
var existingColumn = targetTable.TableInfos?.FirstOrDefault(c => c.ColumnName == changedColumn.ColumnName);
|
||
if (existingColumn != null)
|
||
{
|
||
existingColumn.DisplayName = changedColumn.DisplayName;
|
||
existingColumn.Describe = changedColumn.Describe;
|
||
existingColumn.CsField = changedColumn.CsField;
|
||
existingColumn.IsTableSelect = changedColumn.IsTableSelect;
|
||
existingColumn.IsImageId = changedColumn.IsImageId;
|
||
existingColumn.IsTableColumnShow = changedColumn.IsTableColumnShow;
|
||
existingColumn.OrderById = changedColumn.OrderById;
|
||
existingColumn.Width = changedColumn.Width;
|
||
}
|
||
}
|
||
|
||
// 保存到配置文件
|
||
await SaveTableMetaConfigAsync(targetTable);
|
||
}
|
||
|
||
// 注意:不再更新数据库表,改为更新配置文件
|
||
// await _defaultRepository.UpdateRangeAsync(lowCodeTableInfos);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存表元信息到配置文件
|
||
/// </summary>
|
||
private async Task SaveTableMetaConfigAsync(CoreGenDbTableDto tableDto)
|
||
{
|
||
if (string.IsNullOrEmpty(tableDto.DataBase) || string.IsNullOrEmpty(tableDto.TableName))
|
||
return;
|
||
|
||
var config = new CoreTableMetaConfig
|
||
{
|
||
DisplayName = tableDto.DisplayName,
|
||
EntityName = tableDto.EntityName,
|
||
Remark = tableDto.Remark,
|
||
ModelPath = tableDto.ModelPath,
|
||
ServicePath = tableDto.ServicePath,
|
||
ControllerPath = tableDto.ControllerPath,
|
||
ClientIndexPath = tableDto.ClientIndexPath,
|
||
ClientInfoPath = tableDto.ClientInfoPath,
|
||
ClientServicePath = tableDto.ClientServicePath,
|
||
IsCover = tableDto.IsCover ?? false,
|
||
Columns = new Dictionary<string, CoreColumnMetaConfig>()
|
||
};
|
||
|
||
// 保存列配置
|
||
if (tableDto.TableInfos != null)
|
||
{
|
||
foreach (var column in tableDto.TableInfos)
|
||
{
|
||
if (!string.IsNullOrEmpty(column.ColumnName))
|
||
{
|
||
config.Columns[column.ColumnName] = new CoreColumnMetaConfig
|
||
{
|
||
DisplayName = column.DisplayName,
|
||
Describe = column.Describe,
|
||
CsField = column.CsField,
|
||
IsTableSelect = column.IsTableSelect,
|
||
IsImageId = column.IsImageId,
|
||
IsTableColumnShow = column.IsTableColumnShow,
|
||
Width = column.Width,
|
||
OrderById = column.OrderById
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
await _tableMetaConfigService.SaveConfigAsync(tableDto.DataBase!, tableDto.TableName!, config);
|
||
}
|
||
|
||
|
||
|
||
} |