feat: 代码模板 (#335)

Co-authored-by: tk <fiyne1a@dingtalk.com>
This commit is contained in:
2025-07-08 16:04:15 +08:00
committed by GitHub
parent dc3249428a
commit d01ead8283
71 changed files with 2588 additions and 600 deletions

View File

@ -116,7 +116,7 @@ USDT
登录名
登录日志导出
硕士
示例导出
代码模板导出
离异
空闲
站内信导出

View File

@ -0,0 +1 @@
示例导出

View File

@ -0,0 +1,16 @@
[
{
"Enabled": true,
"Gender": 1,
"Id": 694360665923594,
"Name": "老王",
"Sort": 100,
},
{
"Enabled": true,
"Gender": 2,
"Id": 694360665923595,
"Name": "媳妇儿",
"Sort": 100,
}
]

View File

@ -278,13 +278,24 @@
"Title": "代码生成",
"Type": 1
},
{
"Component": "sys/template",
"Icon": "sc-icon-template",
"Id": 694076641718288,
"Name": "dev/template",
"ParentId": 373838105399301,
"Path": "/dev/template",
"Sort": 99,
"Title": "页面模板",
"Type": 1
},
{
"Id": 482777529417739,
"ParentId": 373838105399301,
"Icon": "el-icon-eleme-filled",
"Name": "dev/element",
"Path": "https://element-plus.org/zh-CN/component/overview.html",
"Sort": 99,
"Sort": 98,
"Title": "Element",
"Type": 3,
},
@ -294,7 +305,7 @@
"Icon": "sc-icon-free-sql",
"Name": "dev/freesql",
"Path": "https://freesql.net/guide",
"Sort": 99,
"Sort": 97,
"Title": "FreeSql",
"Type": 3,
}

View File

@ -1,13 +0,0 @@
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Tpl.Example;
namespace NetAdmin.Application.Modules.Tpl;
/// <summary>
/// 示例模块
/// </summary>
public interface IExampleModule : ICrudModule<CreateExampleReq, QueryExampleRsp // 创建类型
, EditExampleReq // 编辑类型
, QueryExampleReq, QueryExampleRsp // 查询类型
, DelReq // 删除类型
>;

View File

@ -1,8 +0,0 @@
using NetAdmin.Application.Modules.Tpl;
namespace NetAdmin.Application.Services.Tpl.Dependency;
/// <summary>
/// 示例服务
/// </summary>
public interface IExampleService : IService, IExampleModule;

View File

@ -1,9 +0,0 @@
using NetAdmin.Application.Modules.Tpl;
using NetAdmin.Application.Services.Tpl.Dependency;
namespace NetAdmin.Cache.Tpl.Dependency;
/// <summary>
/// 示例缓存
/// </summary>
public interface IExampleCache : ICache<IDistributedCache, IExampleService>, IExampleModule;

View File

@ -1,71 +0,0 @@
using NetAdmin.Application.Services.Tpl.Dependency;
using NetAdmin.Cache.Tpl.Dependency;
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Tpl.Example;
namespace NetAdmin.Cache.Tpl;
/// <inheritdoc cref="IExampleCache" />
public sealed class ExampleCache(IDistributedCache cache, IExampleService service)
: DistributedCache<IExampleService>(cache, service), IScoped, IExampleCache
{
/// <inheritdoc />
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Service.BulkDeleteAsync(req);
}
/// <inheritdoc />
public Task<long> CountAsync(QueryReq<QueryExampleReq> req)
{
return Service.CountAsync(req);
}
/// <inheritdoc />
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryExampleReq> req)
{
return Service.CountByAsync(req);
}
/// <inheritdoc />
public Task<QueryExampleRsp> CreateAsync(CreateExampleReq req)
{
return Service.CreateAsync(req);
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
return Service.DeleteAsync(req);
}
/// <inheritdoc />
public Task<QueryExampleRsp> EditAsync(EditExampleReq req)
{
return Service.EditAsync(req);
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryExampleReq> req)
{
return Service.ExportAsync(req);
}
/// <inheritdoc />
public Task<QueryExampleRsp> GetAsync(QueryExampleReq req)
{
return Service.GetAsync(req);
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryExampleRsp>> PagedQueryAsync(PagedQueryReq<QueryExampleReq> req)
{
return Service.PagedQueryAsync(req);
}
/// <inheritdoc />
public Task<IEnumerable<QueryExampleRsp>> QueryAsync(QueryReq<QueryExampleReq> req)
{
return Service.QueryAsync(req);
}
}

View File

@ -0,0 +1,89 @@
namespace NetAdmin.Domain.DbMaps.Sys;
/// <summary>
/// 代码模板表
/// </summary>
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_CodeTemplate))]
public record Sys_CodeTemplate : VersionEntity, IFieldSort, IFieldSummary, IFieldEnabled, IFieldOwner
{
/// <summary>
/// 是否启用
/// </summary>
/// <example>true</example>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual bool Enabled { get; init; }
/// <summary>
/// 性别
/// </summary>
/// <example>Male</example>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual Genders? Gender { get; init; }
/// <summary>
/// 唯一编码
/// </summary>
/// <example>123456</example>
[Column(IsIdentity = false, IsPrimary = true, Position = 1)]
[CsvIgnore]
[JsonIgnore]
[Snowflake]
public override long Id { get; init; }
/// <summary>
/// 名称
/// </summary>
/// <example>老王</example>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)]
[CsvIgnore]
[JsonIgnore]
public virtual string Name { get; init; }
/// <summary>
/// 归属用户
/// </summary>
[CsvIgnore]
[JsonIgnore]
[Navigate(nameof(OwnerId))]
public Sys_User Owner { get; init; }
/// <summary>
/// 归属部门编号
/// </summary>
/// <example>370942943322181</example>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerDeptId { get; init; }
/// <summary>
/// 归属用户编号
/// </summary>
/// <example>370942943322181</example>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerId { get; init; }
/// <summary>
/// 排序值,越大越前
/// </summary>
/// <example>100</example>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long Sort { get; init; }
/// <summary>
/// 备注
/// </summary>
/// <example>备注文字</example>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
[CsvIgnore]
[JsonIgnore]
public virtual string Summary { get; init; }
}

View File

@ -1,7 +0,0 @@
namespace NetAdmin.Domain.DbMaps.Tpl;
/// <summary>
/// 示例表
/// </summary>
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Tpl_Example))]
public record Tpl_Example : VersionEntity;

View File

@ -0,0 +1,30 @@
namespace NetAdmin.Domain.Dto.Sys.CodeTemplate;
/// <summary>
/// 请求:创建代码模板
/// </summary>
public record CreateCodeTemplateReq : Sys_CodeTemplate
{
/// <inheritdoc cref="IFieldEnabled.Enabled" />
public override bool Enabled { get; init; } = true;
/// <inheritdoc cref="Sys_CodeTemplate.Gender" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override Genders? Gender { get; init; }
/// <inheritdoc cref="Sys_CodeTemplate.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="Sys_CodeTemplate.Name" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Name { get; init; }
/// <inheritdoc cref="IFieldSort.Sort" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Sort { get; init; }
/// <inheritdoc cref="IFieldSummary.Summary" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Summary { get; init; }
}

View File

@ -0,0 +1,11 @@
namespace NetAdmin.Domain.Dto.Sys.CodeTemplate;
/// <summary>
/// 请求:编辑代码模板
/// </summary>
public sealed record EditCodeTemplateReq : CreateCodeTemplateReq
{
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -0,0 +1,11 @@
namespace NetAdmin.Domain.Dto.Sys.CodeTemplate;
/// <summary>
/// 请求:查询代码模板
/// </summary>
public sealed record QueryCodeTemplateReq : Sys_CodeTemplate
{
/// <inheritdoc cref="Sys_CodeTemplate.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
}

View File

@ -0,0 +1,72 @@
using NetAdmin.Domain.Dto.Sys.User;
namespace NetAdmin.Domain.Dto.Sys.CodeTemplate;
/// <summary>
/// 响应:查询代码模板
/// </summary>
public record QueryCodeTemplateRsp : Sys_CodeTemplate
{
/// <inheritdoc cref="IFieldCreatedTime.CreatedTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override DateTime CreatedTime { get; init; }
/// <inheritdoc cref="IFieldCreatedUser.CreatedUserId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? CreatedUserId { get; init; }
/// <inheritdoc cref="IFieldCreatedUser.CreatedUserName" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string CreatedUserName { get; init; }
/// <inheritdoc cref="IFieldEnabled.Enabled" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override bool Enabled { get; init; }
/// <inheritdoc cref="Sys_CodeTemplate.Gender" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override Genders? Gender { get; init; }
/// <inheritdoc cref="Sys_CodeTemplate.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="IFieldModifiedTime.ModifiedTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override DateTime? ModifiedTime { get; init; }
/// <inheritdoc cref="IFieldModifiedUser.ModifiedUserId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? ModifiedUserId { get; init; }
/// <inheritdoc cref="IFieldModifiedUser.ModifiedUserName" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string ModifiedUserName { get; init; }
/// <inheritdoc cref="Sys_CodeTemplate.Name" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Name { get; init; }
/// <inheritdoc cref="Sys_CodeTemplate.Owner" />
public new virtual QueryUserRsp Owner { get; init; }
/// <inheritdoc cref="IFieldOwner.OwnerDeptId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? OwnerDeptId { get; init; }
/// <inheritdoc cref="IFieldOwner.OwnerId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? OwnerId { get; init; }
/// <inheritdoc cref="IFieldSort.Sort" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Sort { get; init; }
/// <inheritdoc cref="IFieldSummary.Summary" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Summary { get; init; }
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -0,0 +1,47 @@
namespace NetAdmin.Domain.Dto.Sys.Dev;
/// <summary>
/// 信息:字段项信息
/// </summary>
public sealed record FieldItemInfo : DataAbstraction
{
/// <summary>
/// 数据库字段类型
/// </summary>
public string DbType { get; init; }
/// <summary>
/// 代码模板
/// </summary>
public string Example { get; init; }
/// <summary>
/// 可空
/// </summary>
public bool IsNullable { get; init; }
/// <summary>
/// 是否主键
/// </summary>
public bool IsPrimary { get; set; }
/// <summary>
/// 值类型
/// </summary>
public bool IsStruct { get; init; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; init; }
/// <summary>
/// 备注
/// </summary>
public string Summary { get; init; }
/// <summary>
/// 类型
/// </summary>
public string Type { get; init; }
}

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Domain.Dto.Sys.Dev;
namespace NetAdmin.Domain.Dto.Sys.Dev;
/// <summary>
/// 请求:生成后端代码
@ -6,20 +6,32 @@ namespace NetAdmin.Domain.Dto.Sys.Dev;
public sealed record GenerateCsCodeReq : DataAbstraction
{
/// <summary>
/// 模块名称
/// 基类
/// </summary>
[Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.模块名称不能为空))]
public string ModuleName { get; init; }
public string BaseClass { get; init; }
/// <summary>
/// 模块说明
/// 实体名称
/// </summary>
[Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.模块说明不能为空))]
public string ModuleRemark { get; init; }
public string EntityName { get; init; }
/// <summary>
/// 模块类型
/// 字段列表
/// </summary>
[Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.模块类型不能为空))]
public string Type { get; init; }
public IReadOnlyCollection<FieldItemInfo> FieldList { get; init; }
/// <summary>
/// 接口列表
/// </summary>
public string[] Interfaces { get; init; }
/// <summary>
/// 项目
/// </summary>
public string Project { get; init; }
/// <summary>
/// 描述
/// </summary>
public string Summary { get; init; }
}

View File

@ -0,0 +1,13 @@
namespace NetAdmin.Domain.Dto.Sys.Dev;
/// <summary>
/// 请求:获取所有数据类型
/// </summary>
public sealed record GetDotnetDataTypesReq : DataAbstraction
{
/// <summary>
/// 开始匹配
/// </summary>
[Required]
public string StartWith { get; init; }
}

View File

@ -1,8 +0,0 @@
using NetAdmin.Domain.DbMaps.Tpl;
namespace NetAdmin.Domain.Dto.Tpl.Example;
/// <summary>
/// 请求:创建示例
/// </summary>
public record CreateExampleReq : Tpl_Example;

View File

@ -1,15 +0,0 @@
namespace NetAdmin.Domain.Dto.Tpl.Example;
/// <summary>
/// 请求:编辑示例
/// </summary>
public record EditExampleReq : CreateExampleReq
{
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -1,13 +0,0 @@
using NetAdmin.Domain.DbMaps.Tpl;
namespace NetAdmin.Domain.Dto.Tpl.Example;
/// <summary>
/// 请求:查询示例
/// </summary>
public sealed record QueryExampleReq : Tpl_Example
{
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
}

View File

@ -1,17 +0,0 @@
using NetAdmin.Domain.DbMaps.Tpl;
namespace NetAdmin.Domain.Dto.Tpl.Example;
/// <summary>
/// 响应:查询示例
/// </summary>
public sealed record QueryExampleRsp : Tpl_Example
{
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -4,7 +4,7 @@
<ProjectReference Include="../NetAdmin.Infrastructure/NetAdmin.Infrastructure.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CronExpressionDescriptor" Version="2.41.0"/>
<PackageReference Include="CronExpressionDescriptor" Version="2.44.0"/>
<PackageReference Include="Cronos" Version="0.11.0"/>
<PackageReference Include="NetAdmin.CsvHelper" Version="1.0.0"/>
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14"/>

View File

@ -1,102 +0,0 @@
using NetAdmin.Application.Modules.Tpl;
using NetAdmin.Application.Services.Tpl.Dependency;
using NetAdmin.Cache.Tpl.Dependency;
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Tpl.Example;
using NetAdmin.Host.Attributes;
namespace NetAdmin.Host.Controllers.Tpl;
/// <summary>
/// 示例服务
/// </summary>
[ApiDescriptionSettings(nameof(Tpl), Module = nameof(Tpl))]
[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)]
public sealed class ExampleController(IExampleCache cache) : ControllerBase<IExampleCache, IExampleService>(cache), IExampleModule
{
/// <summary>
/// 批量删除示例
/// </summary>
[Transaction]
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Cache.BulkDeleteAsync(req);
}
/// <summary>
/// 示例计数
/// </summary>
public Task<long> CountAsync(QueryReq<QueryExampleReq> req)
{
return Cache.CountAsync(req);
}
/// <summary>
/// 示例分组计数
/// </summary>
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryExampleReq> req)
{
return Cache.CountByAsync(req);
}
/// <summary>
/// 创建示例
/// </summary>
[Transaction]
public Task<QueryExampleRsp> CreateAsync(CreateExampleReq req)
{
return Cache.CreateAsync(req);
}
/// <summary>
/// 删除示例
/// </summary>
[Transaction]
public Task<int> DeleteAsync(DelReq req)
{
return Cache.DeleteAsync(req);
}
/// <summary>
/// 编辑示例
/// </summary>
[Transaction]
public Task<QueryExampleRsp> EditAsync(EditExampleReq req)
{
return Cache.EditAsync(req);
}
/// <summary>
/// 导出示例
/// </summary>
[NonAction]
public Task<IActionResult> ExportAsync(QueryReq<QueryExampleReq> req)
{
return Cache.ExportAsync(req);
}
/// <summary>
/// 获取单个示例
/// </summary>
public Task<QueryExampleRsp> GetAsync(QueryExampleReq req)
{
return Cache.GetAsync(req);
}
/// <summary>
/// 分页查询示例
/// </summary>
public Task<PagedQueryRsp<QueryExampleRsp>> PagedQueryAsync(PagedQueryReq<QueryExampleReq> req)
{
return Cache.PagedQueryAsync(req);
}
/// <summary>
/// 查询示例
/// </summary>
[NonAction]
public Task<IEnumerable<QueryExampleRsp>> QueryAsync(QueryReq<QueryExampleReq> req)
{
return Cache.QueryAsync(req);
}
}

View File

@ -45,8 +45,7 @@ public abstract class ApiResultHandler<T>
/// <summary>
/// HTTP状态码处理
/// </summary>
#pragma warning disable ASA001, VSTHRD200
public Task OnResponseStatusCodes( //
public Task OnResponseStatusCodesAsync( //
HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings = null)
{
// 设置响应状态码
@ -54,8 +53,6 @@ public abstract class ApiResultHandler<T>
return Task.CompletedTask;
}
#pragma warning restore ASA001, VSTHRD200
/// <summary>
/// 请求成功
/// </summary>

View File

@ -5,7 +5,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="NetAdmin.ApiSkin" Condition="'$(Configuration)' == 'Debug'" Version="1.0.1"/>
<PackageReference Include="NetAdmin.Spectre.Console.Cli" Version="1.0.1"/>
<PackageReference Include="NetAdmin.Spectre.Console.Cli" Version="1.0.3"/>
<PackageReference Include="prometheus-net.AspNetCore" Condition="'$(Configuration)' != 'Debug'" Version="8.2.1"/>
</ItemGroup>
</Project>

View File

@ -5,7 +5,7 @@
<ItemGroup>
<PackageReference Include="NetAdmin.FreeSql.DbContext" Version="1.1.7" Label="refs"/>
<PackageReference Include="NetAdmin.FreeSql.Provider.Sqlite" Version="1.1.7" Label="refs"/>
<PackageReference Include="Gurion" Version="1.2.15" Label="refs"/>
<PackageReference Include="Gurion" Version="1.2.16" Label="refs"/>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.6"/>
<PackageReference Include="Minio" Version="6.0.5"/>
<PackageReference Include="NSExt" Version="2.3.7"/>

View File

@ -0,0 +1,12 @@
using NetAdmin.Domain.Dto.Sys.CodeTemplate;
namespace NetAdmin.SysComponent.Application.Modules.Sys;
/// <summary>
/// 代码模板模块
/// </summary>
public interface ICodeTemplateModule : ICrudModule<CreateCodeTemplateReq, QueryCodeTemplateRsp // 创建类型
, EditCodeTemplateReq // 编辑类型
, QueryCodeTemplateReq, QueryCodeTemplateRsp // 查询类型
, DelReq // 删除类型
>;

View File

@ -21,4 +21,24 @@ public interface IDevModule
/// 生成接口代码
/// </summary>
Task GenerateJsCodeAsync();
/// <summary>
/// 获取实体项目列表
/// </summary>
Task<IEnumerable<Tuple<string, string>>> GetDomainProjectsAsync();
/// <summary>
/// 获取所有数据类型
/// </summary>
IEnumerable<string> GetDotnetDataTypes(GetDotnetDataTypesReq req);
/// <summary>
/// 获取实体基类列表
/// </summary>
IEnumerable<Tuple<string, string>> GetEntityBaseClasses();
/// <summary>
/// 获取字段接口列表
/// </summary>
IEnumerable<Tuple<string, string>> GetFieldInterfaces();
}

View File

@ -1,15 +1,12 @@
using NetAdmin.Application.Repositories;
using NetAdmin.Application.Services.Tpl.Dependency;
using NetAdmin.Domain.DbMaps.Tpl;
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Tpl.Example;
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys.CodeTemplate;
using NetAdmin.Domain.Extensions;
namespace NetAdmin.Application.Services.Tpl;
namespace NetAdmin.SysComponent.Application.Services.Sys;
/// <inheritdoc cref="IExampleService" />
public sealed class ExampleService(BasicRepository<Tpl_Example, long> rpo) //
: RepositoryService<Tpl_Example, long, IExampleService>(rpo), IExampleService
/// <inheritdoc cref="ICodeTemplateService" />
public sealed class CodeTemplateService(BasicRepository<Sys_CodeTemplate, long> rpo) //
: RepositoryService<Sys_CodeTemplate, long, ICodeTemplateService>(rpo), ICodeTemplateService
{
/// <inheritdoc />
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
@ -26,34 +23,34 @@ public sealed class ExampleService(BasicRepository<Tpl_Example, long> rpo) //
}
/// <inheritdoc />
public Task<long> CountAsync(QueryReq<QueryExampleReq> req)
public Task<long> CountAsync(QueryReq<QueryCodeTemplateReq> req)
{
req.ThrowIfInvalid();
return QueryInternal(req).WithNoLockNoWait().CountAsync();
}
/// <inheritdoc />
public async Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryExampleReq> req)
public async Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryCodeTemplateReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req with { Order = Orders.None })
.WithNoLockNoWait()
.GroupBy(req.GetToListExp<Tpl_Example>())
.GroupBy(req.GetToListExp<Sys_CodeTemplate>())
.ToDictionaryAsync(a => a.Count())
.ConfigureAwait(false);
return ret.Select(x => new KeyValuePair<IImmutableDictionary<string, string>, int>(
req.RequiredFields.ToImmutableDictionary(y => y, y => typeof(Tpl_Example).GetProperty(y)!.GetValue(x.Key)?.ToString())
, x.Value))
req.RequiredFields.ToImmutableDictionary(
y => y, y => typeof(Sys_CodeTemplate).GetProperty(y)!.GetValue(x.Key)?.ToString()), x.Value))
.Where(x => x.Key.Any(y => !y.Value.NullOrEmpty()))
.OrderByDescending(x => x.Value);
}
/// <inheritdoc />
public async Task<QueryExampleRsp> CreateAsync(CreateExampleReq req)
public async Task<QueryCodeTemplateRsp> CreateAsync(CreateCodeTemplateReq req)
{
req.ThrowIfInvalid();
var ret = await Rpo.InsertAsync(req).ConfigureAwait(false);
return ret.Adapt<QueryExampleRsp>();
return ret.Adapt<QueryCodeTemplateRsp>();
}
/// <inheritdoc />
@ -64,33 +61,35 @@ public sealed class ExampleService(BasicRepository<Tpl_Example, long> rpo) //
}
/// <inheritdoc />
public async Task<QueryExampleRsp> EditAsync(EditExampleReq req)
public async Task<QueryCodeTemplateRsp> EditAsync(EditCodeTemplateReq req)
{
req.ThrowIfInvalid();
#if DBTYPE_SQLSERVER
return (await UpdateReturnListAsync(req).ConfigureAwait(false)).FirstOrDefault()?.Adapt<QueryExampleRsp>();
#else
return await UpdateAsync(req).ConfigureAwait(false) > 0 ? await GetAsync(new QueryExampleReq { Id = req.Id }).ConfigureAwait(false) : null;
return await UpdateAsync(req).ConfigureAwait(false) > 0
? await GetAsync(new QueryCodeTemplateReq { Id = req.Id }).ConfigureAwait(false)
: null;
#endif
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryExampleReq> req)
public Task<IActionResult> ExportAsync(QueryReq<QueryCodeTemplateReq> req)
{
req.ThrowIfInvalid();
return ExportAsync<QueryExampleReq, QueryExampleRsp>(QueryInternal, req, Ln.);
return ExportAsync<QueryCodeTemplateReq, QueryCodeTemplateRsp>(QueryInternal, req, Ln.);
}
/// <inheritdoc />
public async Task<QueryExampleRsp> GetAsync(QueryExampleReq req)
public async Task<QueryCodeTemplateRsp> GetAsync(QueryCodeTemplateReq req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(new QueryReq<QueryExampleReq> { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false);
return ret.Adapt<QueryExampleRsp>();
var ret = await QueryInternal(new QueryReq<QueryCodeTemplateReq> { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false);
return ret.Adapt<QueryCodeTemplateRsp>();
}
/// <inheritdoc />
public async Task<PagedQueryRsp<QueryExampleRsp>> PagedQueryAsync(PagedQueryReq<QueryExampleReq> req)
public async Task<PagedQueryRsp<QueryCodeTemplateRsp>> PagedQueryAsync(PagedQueryReq<QueryCodeTemplateReq> req)
{
req.ThrowIfInvalid();
var list = await QueryInternal(req)
@ -100,18 +99,18 @@ public sealed class ExampleService(BasicRepository<Tpl_Example, long> rpo) //
.ToListAsync(req)
.ConfigureAwait(false);
return new PagedQueryRsp<QueryExampleRsp>(req.Page, req.PageSize, total, list.Adapt<IEnumerable<QueryExampleRsp>>());
return new PagedQueryRsp<QueryCodeTemplateRsp>(req.Page, req.PageSize, total, list.Adapt<IEnumerable<QueryCodeTemplateRsp>>());
}
/// <inheritdoc />
public async Task<IEnumerable<QueryExampleRsp>> QueryAsync(QueryReq<QueryExampleReq> req)
public async Task<IEnumerable<QueryCodeTemplateRsp>> QueryAsync(QueryReq<QueryCodeTemplateReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req).WithNoLockNoWait().Take(req.Count).ToListAsync(req).ConfigureAwait(false);
return ret.Adapt<IEnumerable<QueryExampleRsp>>();
return ret.Adapt<IEnumerable<QueryCodeTemplateRsp>>();
}
private ISelect<Tpl_Example> QueryInternal(QueryReq<QueryExampleReq> req)
private ISelect<Sys_CodeTemplate> QueryInternal(QueryReq<QueryCodeTemplateReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
/// <summary>
/// 代码模板服务
/// </summary>
public interface ICodeTemplateService : IService, ICodeTemplateModule;

View File

@ -0,0 +1,68 @@
using NetAdmin.Domain.Dto.Sys.CodeTemplate;
namespace NetAdmin.SysComponent.Cache.Sys;
/// <inheritdoc cref="ICodeTemplateCache" />
public sealed class CodeTemplateCache(IDistributedCache cache, ICodeTemplateService service)
: DistributedCache<ICodeTemplateService>(cache, service), IScoped, ICodeTemplateCache
{
/// <inheritdoc />
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Service.BulkDeleteAsync(req);
}
/// <inheritdoc />
public Task<long> CountAsync(QueryReq<QueryCodeTemplateReq> req)
{
return Service.CountAsync(req);
}
/// <inheritdoc />
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryCodeTemplateReq> req)
{
return Service.CountByAsync(req);
}
/// <inheritdoc />
public Task<QueryCodeTemplateRsp> CreateAsync(CreateCodeTemplateReq req)
{
return Service.CreateAsync(req);
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
return Service.DeleteAsync(req);
}
/// <inheritdoc />
public Task<QueryCodeTemplateRsp> EditAsync(EditCodeTemplateReq req)
{
return Service.EditAsync(req);
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryCodeTemplateReq> req)
{
return Service.ExportAsync(req);
}
/// <inheritdoc />
public Task<QueryCodeTemplateRsp> GetAsync(QueryCodeTemplateReq req)
{
return Service.GetAsync(req);
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryCodeTemplateRsp>> PagedQueryAsync(PagedQueryReq<QueryCodeTemplateReq> req)
{
return Service.PagedQueryAsync(req);
}
/// <inheritdoc />
public Task<IEnumerable<QueryCodeTemplateRsp>> QueryAsync(QueryReq<QueryCodeTemplateReq> req)
{
return Service.QueryAsync(req);
}
}

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Cache.Sys.Dependency;
/// <summary>
/// 代码模板缓存
/// </summary>
public interface ICodeTemplateCache : ICache<IDistributedCache, ICodeTemplateService>, ICodeTemplateModule;

View File

@ -23,4 +23,28 @@ public sealed class DevCache(IDistributedCache cache, IDevService service) //
{
return Service.GenerateJsCodeAsync();
}
/// <inheritdoc />
public Task<IEnumerable<Tuple<string, string>>> GetDomainProjectsAsync()
{
return Service.GetDomainProjectsAsync();
}
/// <inheritdoc />
public IEnumerable<string> GetDotnetDataTypes(GetDotnetDataTypesReq req)
{
return Service.GetDotnetDataTypes(req);
}
/// <inheritdoc />
public IEnumerable<Tuple<string, string>> GetEntityBaseClasses()
{
return Service.GetEntityBaseClasses();
}
/// <inheritdoc />
public IEnumerable<Tuple<string, string>> GetFieldInterfaces()
{
return Service.GetFieldInterfaces();
}
}

View File

@ -0,0 +1,98 @@
using NetAdmin.Domain.Dto.Sys.CodeTemplate;
namespace NetAdmin.SysComponent.Host.Controllers.Sys;
/// <summary>
/// 代码模板服务
/// </summary>
[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))]
[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)]
public sealed class CodeTemplateController(ICodeTemplateCache cache)
: ControllerBase<ICodeTemplateCache, ICodeTemplateService>(cache), ICodeTemplateModule
{
/// <summary>
/// 批量删除代码模板
/// </summary>
[Transaction]
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Cache.BulkDeleteAsync(req);
}
/// <summary>
/// 代码模板计数
/// </summary>
public Task<long> CountAsync(QueryReq<QueryCodeTemplateReq> req)
{
return Cache.CountAsync(req);
}
/// <summary>
/// 代码模板分组计数
/// </summary>
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryCodeTemplateReq> req)
{
return Cache.CountByAsync(req);
}
/// <summary>
/// 创建代码模板
/// </summary>
[Transaction]
public Task<QueryCodeTemplateRsp> CreateAsync(CreateCodeTemplateReq req)
{
return Cache.CreateAsync(req);
}
/// <summary>
/// 删除代码模板
/// </summary>
[Transaction]
public Task<int> DeleteAsync(DelReq req)
{
return Cache.DeleteAsync(req);
}
/// <summary>
/// 编辑代码模板
/// </summary>
[Transaction]
public Task<QueryCodeTemplateRsp> EditAsync(EditCodeTemplateReq req)
{
return Cache.EditAsync(req);
}
/// <summary>
/// 导出代码模板
/// </summary>
[NonAction]
public Task<IActionResult> ExportAsync(QueryReq<QueryCodeTemplateReq> req)
{
return Cache.ExportAsync(req);
}
/// <summary>
/// 获取单个代码模板
/// </summary>
public Task<QueryCodeTemplateRsp> GetAsync(QueryCodeTemplateReq req)
{
return Cache.GetAsync(req);
}
/// <summary>
/// 分页查询代码模板
/// </summary>
public Task<PagedQueryRsp<QueryCodeTemplateRsp>> PagedQueryAsync(PagedQueryReq<QueryCodeTemplateReq> req)
{
return Cache.PagedQueryAsync(req);
}
/// <summary>
/// 查询代码模板
/// </summary>
[NonAction]
public Task<IEnumerable<QueryCodeTemplateRsp>> QueryAsync(QueryReq<QueryCodeTemplateReq> req)
{
return Cache.QueryAsync(req);
}
}

View File

@ -32,4 +32,36 @@ public sealed class DevController(IDevCache cache) : ControllerBase<IDevCache, I
{
return Cache.GenerateJsCodeAsync();
}
/// <summary>
/// 获取实体项目列表
/// </summary>
public Task<IEnumerable<Tuple<string, string>>> GetDomainProjectsAsync()
{
return Cache.GetDomainProjectsAsync();
}
/// <summary>
/// 获取所有数据类型
/// </summary>
public IEnumerable<string> GetDotnetDataTypes(GetDotnetDataTypesReq req)
{
return Cache.GetDotnetDataTypes(req);
}
/// <summary>
/// 获取实体基类列表
/// </summary>
public IEnumerable<Tuple<string, string>> GetEntityBaseClasses()
{
return Cache.GetEntityBaseClasses();
}
/// <summary>
/// 获取字段接口列表
/// </summary>
public IEnumerable<Tuple<string, string>> GetFieldInterfaces()
{
return Cache.GetFieldInterfaces();
}
}

View File

@ -28,6 +28,7 @@ public sealed class RequestAuditMiddleware(
) {
#pragma warning restore SA1009
await next(context).ConfigureAwait(false);
return;
}

View File

@ -35,4 +35,45 @@ public class DevTests(WebTestApplicationFactory<Startup> factory, ITestOutputHel
var rsp = await PostJsonAsync(typeof(DevController));
Assert.True(rsp.IsSuccessStatusCode);
}
/// <inheritdoc />
[Fact]
public async Task<IEnumerable<Tuple<string, string>>> GetDomainProjectsAsync()
{
var rsp = await PostJsonAsync(typeof(DevController));
Assert.True(rsp.IsSuccessStatusCode);
return null;
}
/// <inheritdoc />
public IEnumerable<string> GetDotnetDataTypes(GetDotnetDataTypesReq req)
{
#pragma warning disable xUnit1031
var rsp = PostJsonAsync(typeof(DevController)).GetAwaiter().GetResult();
#pragma warning restore xUnit1031
Assert.True(rsp.IsSuccessStatusCode);
return null;
}
/// <inheritdoc />
[Fact]
public IEnumerable<Tuple<string, string>> GetEntityBaseClasses()
{
#pragma warning disable xUnit1031
var rsp = PostJsonAsync(typeof(DevController)).GetAwaiter().GetResult();
#pragma warning restore xUnit1031
Assert.True(rsp.IsSuccessStatusCode);
return null;
}
/// <inheritdoc />
[Fact]
public IEnumerable<Tuple<string, string>> GetFieldInterfaces()
{
#pragma warning disable xUnit1031
var rsp = PostJsonAsync(typeof(DevController)).GetAwaiter().GetResult();
#pragma warning restore xUnit1031
Assert.True(rsp.IsSuccessStatusCode);
return null;
}
}

View File

@ -0,0 +1 @@
global using CsvIgnore = CsvHelper.Configuration.Attributes.IgnoreAttribute;

View File

@ -105,9 +105,13 @@ namespace YourSolution.AdmServer.Host
}
/// <inheritdoc />
#pragma warning disable ASA001
public async Task<int> Execute(CommandContext context, CommandLineArgs settings)
#pragma warning restore ASA001
public Task<int> ExecuteAsync(CommandContext context, CommandSettings settings)
{
return ExecuteAsync(context, (settings as CommandLineArgs)!);
}
/// <inheritdoc />
public async Task<int> ExecuteAsync(CommandContext context, CommandLineArgs settings)
{
Args = settings;
var webOpt = new WebApplicationOptions //
@ -120,14 +124,6 @@ namespace YourSolution.AdmServer.Host
return 0;
}
/// <inheritdoc />
#pragma warning disable ASA001
public Task<int> Execute(CommandContext context, CommandSettings settings)
#pragma warning restore ASA001
{
return Execute(context, (settings as CommandLineArgs)!);
}
/// <inheritdoc />
public ValidationResult Validate(CommandContext context, CommandSettings settings)
{

View File

@ -13,10 +13,6 @@
"Group": "Adm",
"Title": "管理服务",
},
{
"Group": "Tpl",
"Visible": false
},
{
"Group": "Probe",
"Visible": false

View File

@ -1,93 +1,93 @@
/**
* 示例服务
* @module @/api/tpl/example
* 代码模板服务
* @module @/api/sys/code.template
*/
import config from '@/config'
import http from '@/utils/request'
export default {
/**
* 批量删除示例
* 批量删除代码模板
*/
bulkDelete: {
url: `${config.API_URL}/api/tpl/example/bulk.delete`,
name: `批量删除示例`,
url: `${config.API_URL}/api/sys/code.template/bulk.delete`,
name: `批量删除代码模板`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 示例计数
* 代码模板计数
*/
count: {
url: `${config.API_URL}/api/tpl/example/count`,
name: `示例计数`,
url: `${config.API_URL}/api/sys/code.template/count`,
name: `代码模板计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 示例分组计数
* 代码模板分组计数
*/
countBy: {
url: `${config.API_URL}/api/tpl/example/count.by`,
name: `示例分组计数`,
url: `${config.API_URL}/api/sys/code.template/count.by`,
name: `代码模板分组计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 创建示例
* 创建代码模板
*/
create: {
url: `${config.API_URL}/api/tpl/example/create`,
name: `创建示例`,
url: `${config.API_URL}/api/sys/code.template/create`,
name: `创建代码模板`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 删除示例
* 删除代码模板
*/
delete: {
url: `${config.API_URL}/api/tpl/example/delete`,
name: `删除示例`,
url: `${config.API_URL}/api/sys/code.template/delete`,
name: `删除代码模板`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 编辑示例
* 编辑代码模板
*/
edit: {
url: `${config.API_URL}/api/tpl/example/edit`,
name: `编辑示例`,
url: `${config.API_URL}/api/sys/code.template/edit`,
name: `编辑代码模板`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取单个示例
* 获取单个代码模板
*/
get: {
url: `${config.API_URL}/api/tpl/example/get`,
name: `获取单个示例`,
url: `${config.API_URL}/api/sys/code.template/get`,
name: `获取单个代码模板`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询示例
* 分页查询代码模板
*/
pagedQuery: {
url: `${config.API_URL}/api/tpl/example/paged.query`,
name: `分页查询示例`,
url: `${config.API_URL}/api/sys/code.template/paged.query`,
name: `分页查询代码模板`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},

View File

@ -37,4 +37,48 @@ export default {
return await http.post(this.url, data, config)
},
},
/**
* 获取实体项目列表
*/
getDomainProjects: {
url: `${config.API_URL}/api/sys/dev/get.domain.projects`,
name: `获取实体项目列表`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取所有数据类型
*/
getDotnetDataTypes: {
url: `${config.API_URL}/api/sys/dev/get.dotnet.data.types`,
name: `获取所有数据类型`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取实体基类列表
*/
getEntityBaseClasses: {
url: `${config.API_URL}/api/sys/dev/get.entity.base.classes`,
name: `获取实体基类列表`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取字段接口列表
*/
getFieldInterfaces: {
url: `${config.API_URL}/api/sys/dev/get.field.interfaces`,
name: `获取字段接口列表`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -75,4 +75,5 @@ export { default as archive } from './archive'
export { default as 'device-log' } from './device-log'
export { default as 'nick-name' } from './nick-name'
export { default as telegram } from './telegram'
export { default as country } from './country'
export { default as country } from './country'
export {default as template} from './template.vue'

View File

@ -0,0 +1 @@
<template><svg t="1751618283110" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5188" width="256" height="256"><path d="M533.333333 597.333333H106.666667c-12.8 0-21.333333-8.533333-21.333334-21.333333V106.666667c0-12.8 8.533333-21.333333 21.333334-21.333334h426.666666c12.8 0 21.333333 8.533333 21.333334 21.333334v469.333333c0 12.8-8.533333 21.333333-21.333334 21.333333zM128 554.666667h384V128H128v426.666667zM917.333333 938.666667H106.666667c-12.8 0-21.333333-8.533333-21.333334-21.333334V704c0-12.8 8.533333-21.333333 21.333334-21.333333h810.666666c12.8 0 21.333333 8.533333 21.333334 21.333333v213.333333c0 12.8-8.533333 21.333333-21.333334 21.333334zM128 896h768v-170.666667H128v170.666667zM917.333333 298.666667H661.333333c-12.8 0-21.333333-8.533333-21.333333-21.333334V106.666667c0-12.8 8.533333-21.333333 21.333333-21.333334h256c12.8 0 21.333333 8.533333 21.333334 21.333334v170.666666c0 12.8-8.533333 21.333333-21.333334 21.333334z m-234.666666-42.666667h213.333333V128H682.666667v128zM917.333333 597.333333H661.333333c-12.8 0-21.333333-8.533333-21.333333-21.333333v-170.666667c0-12.8 8.533333-21.333333 21.333333-21.333333h256c12.8 0 21.333333 8.533333 21.333334 21.333333v170.666667c0 12.8-8.533333 21.333333-21.333334 21.333333z m-234.666666-42.666666h213.333333v-128H682.666667v128z" fill="#333333" p-id="5189"></path></svg></template>

View File

@ -443,7 +443,7 @@ export default {
}
this.aceEditorValue = this.vkbeautify.json(this.vue.query, 2)
this.selectInputKey = this.controls.find((x) => x.type === 'select-input')?.field[1][0].key
if (this.dateType === 'datetime-range') {
if (this.dateType === 'datetimerange') {
this.dateShortCuts.unshift(
{
text: this.$t('最近一时'),

View File

@ -0,0 +1,125 @@
<template>
<sc-dialog v-model="visible" :full-screen="dialogFullScreen" :title="titleMap[mode]" @closed="$emit(`closed`)" destroy-on-close>
<div v-loading="loading">
<el-form
:disabled="![`edit`, `add`].includes(mode)"
:model="form"
:rules="rules"
label-position="right"
label-width="12rem"
ref="dialogForm">
<template v-for="(item, i) in columns" :key="i">
<el-form-item v-if="item.show?.includes(mode)" :label="item.label" :prop="i">
<el-date-picker v-if="i.endsWith(`Time`)" v-model="form[i]" :disabled="item.disabled?.includes(mode)" type="datetime" />
<el-select v-else-if="item.enum" v-model="form[i]" :disabled="item.disabled?.includes(mode)">
<el-option
v-for="e in Object.entries(this.$GLOBAL.enums[item.enum]).map((x) => {
return { value: x[0], text: x[1][1] }
})"
:key="e.value"
:label="e.text"
:value="e.value" />
</el-select>
<el-switch
v-else-if="typeof form[i] === `boolean` || item.isBoolean"
v-model="form[i]"
:disabled="item.disabled?.includes(mode)" />
<component
v-bind="item.detail?.props"
v-else
v-model="form[i]"
:disabled="item.disabled?.includes(mode)"
:is="item.detail?.is ?? `el-input`" />
</el-form-item>
</template>
</el-form>
</div>
<template #footer>
<el-button @click="visible = false">{{ $t(`取消`) }}</el-button>
<el-button v-if="mode !== `view`" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t(`保存`) }}</el-button>
</template>
</sc-dialog>
</template>
<script>
export default {
components: {},
data() {
return {
rules: {},
visible: false,
mode: `add`,
loading: false,
form: {},
titleMap: {
add: this.$t(`新建{summary}`, { summary: this.summary }),
edit: this.$t(`编辑{summary}: {id}`, { summary: this.summary, id: `...` }),
view: this.$t(`查看{summary}: {id}`, { summary: this.summary, id: `...` }),
},
}
},
created() {},
emits: [`success`, `closed`, `mounted`],
methods: {
//显示
async open(data) {
this.visible = true
this.loading = true
this.mode = data.mode
this.rules = Object.fromEntries(
Object.entries(this.columns)
.map((x) => {
if (x[1].rule?.required) {
return [
x[0],
[
{ required: true, message: this.$t(`{field} 不能为空`, { field: x[1].label }) },
{ validator: x[1].rule.validator, message: this.$t(`{field} 不正确`, { field: x[1].label }) },
],
]
}
return null
})
.filter((x) => x),
)
if (data.row?.id) {
const res = await this.$API[this.entityName].get.post({ id: data.row.id })
Object.assign(this.form, res.data)
this.titleMap.edit = this.$t(`编辑{summary}: {id}`, { summary: this.summary, id: this.form.id })
this.titleMap.view = this.$t(`查看{summary}: {id}`, { summary: this.summary, id: this.form.id })
}
this.loading = false
return this
},
//表单提交方法
async submit() {
const valid = await this.$refs.dialogForm.validate().catch(() => {})
if (!valid) {
return false
}
this.loading = true
const method = this.mode === `add` ? this.$API[this.entityName].create : this.$API[this.entityName].edit
try {
const res = await method.post(this.form)
this.$emit(`success`, res.data, this.mode)
this.visible = false
this.$message.success(this.$t(`操作成功`))
} catch {}
this.loading = false
},
},
mounted() {
this.$emit(`mounted`)
},
props: {
entityName: { type: String },
summary: { type: String },
columns: { type: Array },
dialogFullScreen: { type: Boolean },
},
}
</script>
<style scoped />

View File

@ -0,0 +1,344 @@
<template>
<el-container>
<!-- 仪表板-->
<el-header v-loading="statistics.total === `...`" class="el-header-statistics">
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never">
<sc-statistic :title="$t(`总数`)" :value="statistics.total" group-separator />
</el-card>
</el-col>
</el-row>
</el-header>
<!-- 过滤器-->
<el-header v-if="selectFilterData.length > 0" class="el-header-select-filter">
<sc-select-filter :data="selectFilterData" :label-width="10" @on-change="onFilterChange" ref="selectFilter" />
</el-header>
<!-- 搜索栏-->
<el-header>
<div class="left-panel">
<na-search
:controls="searchControls"
:vue="this"
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
<div class="right-panel">
<el-button v-if="operations.includes('add')" @click="onAddClick" icon="el-icon-plus" type="primary" />
<el-button
:disabled="this.table.selection.length === 0 || this.loading"
@click="onBulkDeleteClick"
icon="el-icon-delete"
plain
type="danger" />
</div>
</el-header>
<!-- 表格主体-->
<el-main class="nopadding">
<sc-table
:context-extra="this.table.menu.extra"
:context-menus="Object.keys(this.columns)"
:context-opers="this.operations"
:default-sort="{ prop: `id`, order: `descending` }"
:export-api="$API[entityName].export"
:params="query"
:query-api="$API[entityName].pagedQuery"
:vue="this"
@data-change="onDataChange"
@selection-change="onSelectionChange"
ref="table"
remote-filter
remote-sort
row-key="id"
stripe>
<el-table-column type="selection" width="50" />
<template v-for="(item, i) in columns" :key="i">
<component
v-bind="item"
v-if="item.show.includes('list')"
:is="item.is ?? `el-table-column`"
:options="
item.options ??
(item.enum
? Object.entries(this.$GLOBAL.enums[item.enum]).map((x) => {
return { value: x[0], text: x[1][1], type: x[1][2], pulse: x[1][3] === 'true' }
})
: null)
"
:prop="i"
:sortable="item.sortable ?? `custom`">
<template v-if="item.isBoolean" #default="{ row }">
<el-switch v-model="row[i]" @change="onSwitchChange(row, i === `enabled` ? `setEnabled` : item.onChange)"></el-switch>
</template>
</component>
</template>
<el-table-column :label="$t(`操作`)" align="right" fixed="right" width="150">
<template #default="{ row }">
<el-button-group size="small">
<el-button @click="onViewClick(row)" icon="el-icon-view" />
<el-button v-if="operations.includes(`edit`)" @click="onEditClick(row)" icon="el-icon-edit" />
<el-button v-if="operations.includes(`del`)" @click="onDeleteClick(row)" icon="el-icon-delete" type="danger" />
</el-button-group>
</template>
</el-table-column>
</sc-table>
</el-main>
</el-container>
<detail-dialog
v-bind="$props"
v-if="dialog.detail"
@closed="dialog.detail = null"
@mounted="$refs.detailDialog.open(dialog.detail)"
@success="(data, mode) => tableConfig.handleUpdate($refs.table, data, mode)"
ref="detailDialog" />
</template>
<script>
import { h } from 'vue'
import { defineAsyncComponent } from 'vue'
import tableConfig from '@/config/table'
import naColOperation from '@/config/na-col-operation'
const naColAvatar = defineAsyncComponent(() => import('@/components/na-col-avatar'))
const detailDialog = defineAsyncComponent(() => import('./detail'))
export default {
components: {
detailDialog,
naColAvatar,
},
computed: {
naColOperation() {
return naColOperation
},
tableConfig() {
return tableConfig
},
},
created() {
const searchFields = []
for (const item in this.columns) {
this.table.menu.extra[item] = this.columns[item].extra
if (this.columns[item].searchable) {
searchFields.push({ label: this.columns[item].label, key: item })
}
}
if (searchFields.length > 0) {
this.searchControls.push({
type: 'select-input',
field: ['dy', searchFields],
placeholder: this.$t('匹配内容'),
style: 'width:25rem',
selectStyle: 'width:8rem',
})
}
for (const filter of this.selectFilters) {
this.selectFilterData.push({
title: filter.title,
key: filter.key,
options: [{ label: this.$t(`全部`), value: `` }].concat(
filter.isBoolean
? filter.isBoolean
: Object.entries(this.$GLOBAL.enums[filter.enumName]).map((x) => {
return {
value: x[0],
label: x[1][1],
}
}),
),
})
}
},
data() {
return {
// 过滤器数据
selectFilterData: [],
// 搜索栏数据
searchControls: [],
// 正在加载标记
loading: false,
// 仪表盘统计数据
statistics: {
total: `...`,
},
// 对话框
dialog: {},
// 表格查询参数
query: {
dynamicFilter: {
filters: [],
},
filter: {},
keywords: this.keywords,
},
// 表格配置
table: {
// 选中项
selection: [],
// 右键菜单
menu: {
extra: {},
opers: [],
},
},
}
},
inject: [`reload`],
methods: {
// ---------------------------- ↓ 过滤器事件 ----------------------------
onFilterChange(data) {
Object.entries(data).forEach(([key, value]) => {
this.$refs.search.form.dy[key] = value === `true` ? true : value === `false` ? false : value
})
this.$refs.search.search()
},
// ---------------------------- 过滤器事件 ↑ ----------------------------
// ---------------------------- ↓ 搜索栏事件 ----------------------------
async onReset() {
Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = [``]))
},
async onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: `createdTime`,
operator: `dateRange`,
value: form.dy.createdTime,
})
}
for (const item in this.columns) {
const field = form.dy[item] || form.dy[item[0].toUpperCase() + item.substring(1)]
if (field === undefined || field === '' || typeof field === 'object') continue
this.query.dynamicFilter.filters.push({
field: item,
operator: this.columns[item].searchable,
value: field,
})
}
await this.$refs.table.upData()
},
// ---------------------------- 搜索栏事件 ↑ ----------------------------
// ---------------------------- ↓ 表格事件 ----------------------------
async onViewClick(row) {
this.dialog.detail = { mode: 'view', row }
},
async onEditClick(row) {
this.dialog.detail = { mode: 'edit', row }
},
async onBulkDeleteClick() {
let loading
try {
await this.$confirm(this.$t('确定删除选中的 {count} 项吗?', { count: this.table.selection.length }), this.$t('提示'), {
type: 'warning',
})
loading = this.$loading()
const res = await this.$API[this.entityName].bulkDelete.post({
items: this.table.selection,
})
this.$refs.table.refresh()
this.$message.success(this.$t('删除 {count} 项', { count: res.data }))
} catch {
//
}
loading?.close()
},
async onDeleteClick(row) {
try {
await this.$confirm(h('div', [h('p', this.$t('是否确认删除?')), h('p', row.id)]), this.$t('提示'), {
type: 'warning',
})
} catch {
return
}
let loading = this.$loading()
try {
const res = await this.$API[this.entityName].delete.post({
id: row.id,
})
this.$message.success(this.$t(`删除 {count} 项`, { count: res.data }))
this.$refs.table.refresh()
} catch {}
loading?.close()
},
async onAddClick() {
this.dialog.detail = { mode: 'add' }
},
async onSwitchChange(row, method) {
try {
await this.$API[this.entityName][method].post(row)
this.$message.success(this.$t('操作成功'))
} catch {
//
}
this.$refs.table.refresh()
},
async onDataChange(data) {
this.statistics.total = this.$refs.table?.total
const calls = []
for (const col in this.columns) {
if (this.columns[col].countBy)
calls.push(
this.$API[this.entityName].countBy.post({
dynamicFilter: { filters: this.query.dynamicFilter.filters },
requiredFields: [col[0].toUpperCase() + col.substring(1)],
}),
)
}
const res = await Promise.all(calls)
Object.assign(this.statistics, { res })
for (const item of res) {
for (const item2 of item.data) {
const key = Object.entries(item2.key)[0]
const filter = this.selectFilterData
.find((x) => x.key.toString().toLowerCase() === key[0].toLowerCase())
?.options.find((x) => x.value.toString().toLowerCase() === key[1].toLowerCase())
if (filter) filter.badge = item2.value
}
}
},
onSelectionChange(data) {
this.table.selection = data
},
// ---------------------------- 表格事件 ↑ ----------------------------
},
async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keeps.push({
field: `keywords`,
value: this.keywords,
type: `root`,
})
}
},
props: {
keywords: { type: String },
entityName: { type: String },
summary: { type: String },
selectFilters: { type: Array },
columns: { type: Object },
operations: { type: Array, default: ['add', 'edit', 'del'] },
dialogFullScreen: { type: Boolean },
},
watch: {},
}
</script>
<style scoped />

View File

@ -117,9 +117,12 @@
<sc-contextmenu-item
v-for="(menu, index) in contextMenus.filter((x) => {
if (current.column?.property === x) {
this.showCopy = true
return true
}
return this.contextMulti && !!this.contextMulti[current.column?.property]?.find((y) => y === x)
const ret = this.contextExtra && !!this.contextExtra[current.column?.property]?.find((y) => y === x)
if (ret) this.showCopy = true
return ret
})"
:command="menu"
:key="index"
@ -151,9 +154,15 @@
:command="`${menu}^|^order-descending^|^${tool.getNestedProperty(current.row, menu) ?? ''}`"
:title="$t('倒序排序')" />
</sc-contextmenu-item>
<sc-contextmenu-item :title="$t('复制')" command="copy" divided icon="el-icon-copy-document" suffix="C" />
<sc-contextmenu-item v-if="contextOpers.includes('add')" :title="$t('新建')" command="add" divided icon="el-icon-plus" suffix="A" />
<sc-contextmenu-item v-if="contextOpers.includes('view')" :title="$t('查看')" command="view" icon="el-icon-view" suffix="V" />
<sc-contextmenu-item v-if="showCopy" :title="$t('复制')" command="copy" divided icon="el-icon-copy-document" suffix="C" />
<sc-contextmenu-item
v-if="contextOpers.includes('add')"
:divided="showCopy"
:title="$t('新建')"
command="add"
icon="el-icon-plus"
suffix="A" />
<sc-contextmenu-item :title="$t('查看')" command="view" icon="el-icon-view" suffix="V" />
<sc-contextmenu-item v-if="contextOpers.includes('edit')" :title="$t('编辑')" command="edit" icon="el-icon-edit" suffix="E" />
<sc-contextmenu-item v-if="contextOpers.includes('del')" :title="$t('删除')" command="del" icon="el-icon-delete" suffix="D" />
<sc-contextmenu-item
@ -179,6 +188,7 @@ const fieldFilter = defineAsyncComponent(() => import('./field-filter'))
import { h } from 'vue'
import tool from '@/utils/tool'
import iframe from '@/store/modules/iframe'
export default {
name: 'scTable',
@ -192,9 +202,9 @@ export default {
dblClickDisable: { type: Boolean, default: false },
vue: { type: Object },
contextMenus: { type: Array },
contextOpers: { type: Array, default: ['copy', 'add', 'view', 'edit', 'del'] },
contextOpers: { type: Array, default: [] },
contextAdvs: { type: Array, default: [] },
contextMulti: { type: Object },
contextExtra: { type: Object },
tableName: { type: String, default: '' },
beforePost: {
type: Function,
@ -256,6 +266,9 @@ export default {
},
},
computed: {
iframe() {
return iframe
},
tool() {
return tool
},
@ -268,6 +281,7 @@ export default {
},
data() {
return {
showCopy: false,
pagerCount: 11,
current: {
row: null,
@ -326,7 +340,7 @@ export default {
return
}
if (this.vue.dialog) {
this.vue.dialog.save = { mode: 'view', row: { id: row.id } }
this.vue.dialog.detail = { mode: 'view', row: { id: row.id } }
}
},
async contextMenuCommand(command) {
@ -357,7 +371,7 @@ export default {
return
}
if (command === 'view') {
this.vue.dialog.save = { mode: 'view', row: { id: this.current.row.id } }
await this.vue.onViewClick(this.current.row)
return
}
if (command === 'export') {
@ -365,22 +379,15 @@ export default {
return
}
if (command === 'add') {
this.vue.dialog.save = { mode: 'add' }
await this.vue.onAddClick()
return
}
if (command === 'edit') {
this.vue.dialog.save = { mode: 'edit', row: { id: this.current.row.id } }
await this.vue.onEditClick(this.current.row)
return
}
if (command === 'del') {
try {
await this.$confirm(h('div', [h('p', this.$t('是否确认删除?')), h('p', this.current.row.id)]), this.$t('提示'), {
type: 'warning',
})
} catch {
return
}
await this.vue.rowDel(this.current.row)
await this.vue.onDeleteClick(this.current.row)
return
}
const kv = command.split('^|^')
@ -404,6 +411,7 @@ export default {
}
},
contextMenuVisibleChange(visible) {
this.showCopy = false
if (!visible) {
this.setCurrentRow()
}

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
// 书写格式与动态路由格式一致,全部经由框架统一转换
// 比较动态路由在meta中多加入了role角色权限为数组类型。一个菜单是否有权限显示取决于它以及后代菜单是否有权限。
// routes 显示在左侧菜单中的路由(显示顺序在动态路由之前)
// 示例如下
// 代码模板如下
// const routes = [
// {

View File

@ -31,6 +31,7 @@ import scSelectFilter from '@/components/sc-select-filter'
import scStatistic from '@/components/sc-statistic'
import scStatusIndicator from '@/components/sc-mini/sc-status-indicator'
import scTable from '@/components/sc-table'
import scSelect from '@/components/sc-select'
// net-admin组件
import naButtonBulkDel from '@/components/na-button-bulk-del'
@ -39,8 +40,9 @@ import naColIndicator from '@/components/na-col-indicator'
import naColOperation from '@/components/na-col-operation'
import naColUser from '@/components/na-col-user'
import naIndicator from '@/components/na-indicator'
import naSearch from '@/components/na-search'
import naInfo from '@/components/na-info'
import naSearch from '@/components/na-search'
import naTablePage from '@/components/na-table-page'
export default {
install(app) {
@ -80,6 +82,7 @@ export default {
app.component('naIndicator', naIndicator)
app.component('naInfo', naInfo)
app.component('naSearch', naSearch)
app.component('naTablePage', naTablePage)
// sc组件
app.component('scDialog', scDialog)
@ -87,6 +90,7 @@ export default {
app.component('scStatistic', scStatistic)
app.component('scStatusIndicator', scStatusIndicator)
app.component('scTable', scTable)
app.component('scSelect', scSelect)
//注册全局指令
app.directive('auth', auth)

View File

@ -625,4 +625,9 @@ export default {
按钮: 'Button',
倒序排序: 'Sort-Descending',
顺序排序: 'Sort-Ascending',
'查看{summary}: {id}': 'View {summary}: {id}',
'新建{summary}': 'Add {summary}',
'编辑{summary}: {id}': 'Edit {summary}: {id}',
'{field} 不能为空': '{field} Must not be empty',
'{field} Incorrect': '{field} Incorrect',
}

View File

@ -623,4 +623,9 @@ export default {
按钮: '按钮',
倒序排序: '倒序排序',
顺序排序: '顺序排序',
'查看{summary}: {id}': '查看{summary}: {id}',
'新建{summary}': '新建{summary}',
'编辑{summary}: {id}': '编辑{summary}: {id}',
'{field} 不能为空': '{field} 不能为空',
'{field} 不正确': '{field} 不正确',
}

View File

@ -1,70 +1,148 @@
<template>
<router-view />
<el-main>
<el-row :gutter="15">
<el-col :lg="6" :md="8" :sm="12" :xl="6" :xs="24">
<el-card :body-style="{ padding: '0' }" shadow="hover">
<el-col>
<el-card :body-style="{ padding: `0` }" shadow="hover">
<div class="code-item">
<div :style="{ background: 'blue' }" class="img">
<el-icon>
<component :is="`sc-icon-js`" />
</el-icon>
</div>
<div class="title">
<h2>{{ $t('生成前端代码') }}</h2>
<p>
<el-button @click="generateJsCode()">{{ $t('生成') }}</el-button>
</p>
</div>
</div>
</el-card>
</el-col>
<el-col :lg="6" :md="8" :sm="12" :xl="6" :xs="24">
<el-card :body-style="{ padding: '0' }" shadow="hover">
<div class="code-item">
<div :style="{ background: 'orange' }" class="img">
<div :style="{ background: `orange` }" class="img">
<el-icon>
<component :is="`sc-icon-csharp`" />
</el-icon>
</div>
<div class="title">
<h2>{{ $t('生成后端代码') }}</h2>
<el-form :model="form" :rules="rules" label-position="right" label-width="10rem" ref="form">
<el-form-item :label="$t(`选择项目`)" prop="project">
<sc-select
v-model="form.project"
:config="{ props: { label: `item1`, value: `item2` } }"
:query-api="$API.sys_dev.getDomainProjects"
class="w100p"
clearable
filterable />
</el-form-item>
<el-form-item :label="$t(`实体名称`)" prop="entityName">
<el-input v-model="form.entityName" placeholder="Example"></el-input>
</el-form-item>
<el-form-item :label="$t(`基类`)" prop="baseClass">
<sc-select
v-model="form.baseClass"
:config="{ props: { label: `item1`, value: `item1` } }"
:query-api="$API.sys_dev.getEntityBaseClasses"
class="w100p"
clearable
filterable />
</el-form-item>
<el-form-item :label="$t(`实现接口`)" prop="interfaces">
<sc-select
v-model="form.interfaces"
:config="{ props: { label: `item1`, value: `item1` } }"
:query-api="$API.sys_dev.getFieldInterfaces"
class="w100p"
clearable
filterable
multiple />
</el-form-item>
<el-form-item :label="$t(`描述`)" prop="summary">
<el-input v-model="form.summary" placeholder="example" />
</el-form-item>
<el-form-item :label="$t(`字段列表`)" prop="fieldList">
<sc-form-table v-model="form.fieldList" :addTemplate="{}" :placeholder="$t(`暂无数据`)" drag-sort ref="formTable">
<el-table-column :label="$t(`字段名称`)" prop="name" width="150">
<template #default="{ row }">
<el-input v-model="row.name" placeholder="Username" />
</template>
</el-table-column>
<el-table-column :label="$t(`字段说明`)" prop="summary" width="150">
<template #default="{ row }">
<el-input v-model="row.summary" :placeholder="$t(`用户名`)" />
</template>
</el-table-column>
<el-table-column :label="$t(`示例值`)" prop="example" width="150">
<template #default="{ row }">
<el-input v-model="row.example" :placeholder="$t(`root`)" />
</template>
</el-table-column>
<el-table-column :label="$t(`字段类型`)" prop="type" width="150">
<template #default="{ row }">
<el-select
v-loading="loading"
v-model="row.type"
:remote-method="remoteMethod"
filterable
remote
remote-show-suffix
reserve-keyword>
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
</el-table-column>
<el-table-column :label="$t(`数据库字段类型`)" prop="dbType">
<template #default="{ row }">
<el-select v-model="row.dbType" clearable filterable>
<el-option v-for="(item, i) in dbTypes" :key="i" :label="item[0]" :value="item[0]" />
</el-select>
</template>
</el-table-column>
<el-table-column :label="$t(`可空`)" align="center" prop="isNullable" width="100">
<template #default="{ row }">
<el-switch v-model="row.isNullable" />
</template>
</el-table-column>
<el-table-column :label="$t(`主键`)" align="center" prop="isPrimary" width="100">
<template #default="{ row }">
<el-switch v-model="row.isPrimary" />
</template>
</el-table-column>
<el-table-column :label="$t(`值类型`)" align="center" prop="isStruct" width="100">
<template #default="{ row }">
<el-switch v-model="row.isStruct" />
</template>
</el-table-column>
</sc-form-table>
</el-form-item>
<el-form-item>
<el-button @click="submit" type="primary">{{ $t(`生成`) }}</el-button>
</el-form-item>
</el-form>
</div>
</div>
</el-card>
</el-col>
<el-col>
<el-card :body-style="{ padding: `0` }" shadow="hover">
<div class="code-item">
<div :style="{ background: `blue` }" class="img">
<el-icon>
<component :is="`sc-icon-js`" />
</el-icon>
</div>
<div class="title">
<p>
<el-input v-model="formCs.type" :placeholder="$t('模块类型')" />
</p>
<p>
<el-input v-model="formCs.moduleName" :placeholder="$t('模块名称')" />
</p>
<p>
<el-input v-model="formCs.moduleRemark" :placeholder="$t('模块说明')" />
</p>
<p>
<el-button @click="generateCsCode()">{{ $t('生成') }}</el-button>
<el-button @click="generateJsCode()">{{ $t(`生成`) }}</el-button>
</p>
</div>
</div>
</el-card>
</el-col>
<el-col :lg="6" :md="8" :sm="12" :xl="6" :xs="24">
<el-card :body-style="{ padding: '0' }" shadow="hover">
<el-col>
<el-card :body-style="{ padding: `0` }" shadow="hover">
<div class="code-item">
<div :style="{ background: 'green' }" class="img">
<div :style="{ background: `green` }" class="img">
<el-icon>
<component :is="`el-icon-picture`" />
</el-icon>
</div>
<div class="title">
<h2>{{ $t('生成图标代码') }}</h2>
<p>
<el-input v-model="form.iconName" :placeholder="$t('图标名称')" />
<el-input v-model="iconForm.iconName" :placeholder="$t(`图标名称`)" />
</p>
<p>
<el-input v-model="form.svgCode" :placeholder="$t('粘贴SVG代码')" />
<el-input v-model="iconForm.svgCode" :placeholder="$t(`粘贴SVG代码`)" />
</p>
<p>
<el-row align="middle">
<el-col :span="12">
<el-button @click="generateIconCode()">{{ $t('生成') }}</el-button>
<el-button @click="generateIconCode()">{{ $t(`生成`) }}</el-button>
</el-col>
<el-col :span="12">
<el-link href="https://www.iconfont.cn/" target="_blank">Iconfont</el-link>
@ -75,84 +153,138 @@
</div>
</el-card>
</el-col>
<el-col :lg="6" :md="8" :sm="12" :xl="6" :xs="24">
<el-card :body-style="{ padding: '0' }" shadow="hover">
<div class="code-item">
<div :style="{ background: 'gray' }" class="img">
<el-icon>
<component :is="`el-icon-picture`" />
</el-icon>
</div>
<div class="title">
<h2>{{ $t('生成表格代码') }}</h2>
<p>
<el-input v-model="form.summaryInfo" :placeholder="$t('注释信息')" type="textarea" />
</p>
<p>
<el-input v-model="form.tableCode" :placeholder="$t('表格代码')" type="textarea" />
</p>
<p>
<el-input v-model="form.formCode" :placeholder="$t('表单代码')" type="textarea" />
</p>
<p>
<el-button @click="generateTableCode()">{{ $t('生成') }}</el-button>
</p>
</div>
</div>
</el-card>
</el-col>
</el-row>
</el-main>
</template>
<script>
import { defineAsyncComponent } from 'vue'
const scFormTable = defineAsyncComponent(() => import('@/components/sc-form-table'))
export default {
components: {
scFormTable,
},
created() {
this.dbTypes = Object.entries(this.$GLOBAL.chars).filter((x) => x[0].indexOf(`FLG_DB_FIELD_TYPE`) === 0)
},
data() {
return {
form: {
svgCode: '',
iconName: '',
loading: false,
options: [],
dbTypes: [],
iconForm: {
svgCode: ``,
iconName: ``,
},
formCs: {
moduleName: '',
/// 模块说明
moduleRemark: '',
/// 模块类型
type: 'YourSolution.AdmServer',
form: {
entityName: `Example`,
interfaces: [`IFieldSummary`, `IFieldOwner`, `IFieldSort`, `IFieldEnabled`],
summary: this.$t(`示例`),
baseClass: `VersionEntity`,
fieldList: [
{
name: `Id`,
summary: this.$t(`示例编号`),
example: `123456`,
type: `long`,
isPrimary: true,
isStruct: true,
},
{
name: `Name`,
summary: this.$t(`名称`),
example: this.$t(`老王`),
type: `string`,
dbType: `FLG_DB_FIELD_TYPE_VARCHAR_31`,
},
{
name: `Gender`,
summary: this.$t(`性别`),
example: this.$t(`Male`),
type: `Genders`,
isNullable: true,
isStruct: true,
},
],
},
rules: {
project: [{ required: true, message: this.$t(`请选择项目`) }],
entityName: [{ required: true, message: this.$t(`请输入实体名称`) }],
summary: [{ required: true, message: this.$t(`请输入描述`) }],
baseClass: [{ required: true, message: this.$t(`请选择基类`) }],
},
}
},
methods: {
async generateIconCode() {
try {
await this.$API.sys_dev.generateIconCode.post(this.form)
this.$message.success('生成完毕')
await this.$API.sys_dev.generateIconCode.post(this.iconForm)
this.$message.success($t(`生成完毕`))
} catch {}
},
async generateTableCode() {
for (const line of this.form.summaryInfo.split('\n')) {
if (!line) continue
let lineSplit = line.split(',')
this.form.tableCode += `<el-table-column prop="${lineSplit[0].slice(0, 1).toLowerCase()}${lineSplit[0].slice(1)}" label="${
lineSplit[1]
}" />`
this.form.formCode += `<el-form-item prop="${lineSplit[0].slice(0, 1).toLowerCase()}${lineSplit[0].slice(1)}" label="${
lineSplit[1]
}"><el-input v-model="form.${lineSplit[0].slice(0, 1).toLowerCase()}${lineSplit[0].slice(1)}" clearable /></el-form-item>`
}
},
async generateJsCode() {
try {
await this.$API.sys_dev.generateJsCode.post()
this.$message.success('生成完毕')
this.$message.success($t(`生成完毕`))
} catch {}
},
async generateCsCode() {
async remoteMethod(query) {
if (query) {
await new Promise((x) => setTimeout(x, 100))
this.loading = true
const res = await this.$API.sys_dev.getDotnetDataTypes.post({ startWith: query })
this.options = res.data.map((x) => {
return {
label: x,
value: x,
}
})
this.loading = false
} else {
this.options = []
}
},
async submit() {
let primaryCount = 0
for (const field of this.form.fieldList ?? []) {
if (field.isPrimary) {
primaryCount++
}
if (!field.name) {
this.$message.error(this.$t(`缺少字段名`))
return
}
if (!field.summary) {
this.$message.error(this.$t(`缺少字段说明`))
return
}
if (!field.example) {
this.$message.error(this.$t(`缺少字段示例值`))
return
}
if (!field.type) {
this.$message.error(this.$t(`缺少字段类型`))
return
}
}
if (primaryCount !== 1) {
this.$message.error(this.$t(`主键字段数量不为1`))
return
}
const valid = await this.$refs.form.validate().catch(() => {})
if (!valid) {
return false
}
this.loading = true
try {
await this.$API.sys_dev.generateCsCode.post(this.formCs)
this.$message.success('生成完毕')
const res = await this.$API.sys_dev.generateCsCode.post(this.form)
this.$emit(`success`, res.data, this.mode)
this.visible = false
this.$message.success(this.$t(`操作成功`))
} catch {}
this.loading = false
},
},
}

View File

@ -34,7 +34,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -59,8 +59,8 @@
</el-header>
<el-main class="nopadding">
<sc-table
:context-extra="{ id: ['createdTime'] }"
:context-menus="['id', 'userRegisterConfirm', 'userRegisterDept.name', 'userRegisterRole.name', 'enabled', 'createdTime']"
:context-multi="{ id: ['createdTime'] }"
:export-api="$API.sys_config.export"
:params="query"
:query-api="$API.sys_config.pagedQuery"

View File

@ -41,7 +41,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -66,8 +66,8 @@
</el-header>
<el-main class="nopadding">
<sc-table
:context-extra="{ id: ['createdTime'] }"
:context-menus="['id', 'name', 'sort', 'enabled', 'createdTime', 'summary']"
:context-multi="{ id: ['createdTime'] }"
:default-sort="{ prop: 'sort', order: 'descending' }"
:export-api="$API.sys_dept.export"
:params="query"

View File

@ -41,7 +41,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -70,8 +70,8 @@
<el-main class="nopadding">
<sc-table
:before-post="(data) => data.dynamicFilter.filters.length > 0"
:context-extra="{ id: ['createdTime'] }"
:context-menus="['key', 'value', 'enabled', 'createdTime', 'id', 'summary']"
:context-multi="{ id: ['createdTime'] }"
:default-sort="{ prop: 'id', order: 'descending' }"
:export-api="$API.sys_dic.exportContent"
:params="query"

View File

@ -55,7 +55,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -81,8 +81,8 @@
<el-main class="nopadding">
<sc-table
:before-post="(data) => data.dynamicFilter.filters.length > 0"
:context-extra="{ id: ['createdTime'] }"
:context-menus="['title', 'enabled', 'createdTime', 'id', 'visibility']"
:context-multi="{ id: ['createdTime'] }"
:default-sort="{ prop: 'id', order: 'descending' }"
:export-api="$API.sys_doc.exportContent"
:params="query"

View File

@ -31,7 +31,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>

View File

@ -78,7 +78,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -112,6 +112,7 @@
}
}
"
:context-extra="{ id: ['createdTime'] }"
:context-menus="[
'id',
'jobName',
@ -125,7 +126,6 @@
'createdTime',
'lastDuration',
]"
:context-multi="{ id: ['createdTime'] }"
:default-sort="{ prop: 'lastExecTime', order: 'descending' }"
:export-api="$API.sys_job.export"
:page-size="100"

View File

@ -81,7 +81,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -98,8 +98,8 @@
}
}
"
:context-extra="{ id: ['createdTime'] }"
:context-menus="['id', 'duration', 'httpMethod', 'requestUrl', 'httpStatusCode', 'createdTime', 'jobId', 'responseBody']"
:context-multi="{ id: ['createdTime'] }"
:default-sort="{ prop: 'id', order: 'descending' }"
:export-api="$API.sys_job.exportRecord"
:params="query"

View File

@ -93,7 +93,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -101,8 +101,8 @@
</el-header>
<el-main class="nopadding">
<sc-table
:context-extra="{ id: ['createdTime'] }"
:context-menus="['id', 'httpStatusCode', 'loginUserName', 'createdClientIp', 'createdUserAgent', 'createdTime']"
:context-multi="{ id: ['createdTime'] }"
:context-opers="['view']"
:default-sort="{ prop: 'id', order: 'descending' }"
:export-api="$API.sys_loginlog.export"

View File

@ -110,7 +110,7 @@
@reset="Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -118,8 +118,8 @@
</el-header>
<el-main class="nopadding">
<sc-table
:context-extra="{ id: ['createdTime'] }"
:context-menus="['id', 'httpStatusCode', 'apiPathCrc32', 'ownerId', 'httpMethod', 'duration', 'createdClientIp', 'createdTime']"
:context-multi="{ id: ['createdTime'] }"
:context-opers="[]"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:export-api="$API.sys_requestlog.export"

View File

@ -46,7 +46,7 @@
@reset="Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -57,8 +57,8 @@
</el-header>
<el-main class="nopadding">
<sc-table
:context-extra="{ id: ['createdTime'] }"
:context-menus="['id', 'createdUserName', 'msgType', 'title', 'summary', 'createdTime']"
:context-multi="{ id: ['createdTime'] }"
:default-sort="{ prop: 'id', order: 'descending' }"
:export-api="$API.sys_sitemsg.export"
:params="query"

View File

@ -71,7 +71,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -81,6 +81,7 @@
</el-header>
<el-main class="nopadding">
<sc-table
:context-extra="{ id: ['createdTime'], ownerId: ['owner.userName'] }"
:context-menus="[
'id',
'ownerId',
@ -93,7 +94,6 @@
'depositOrderStatus',
'actualPayAmount',
]"
:context-multi="{ id: ['createdTime'], ownerId: ['owner.userName'] }"
:context-opers="['view']"
:default-sort="{ prop: 'id', order: 'descending' }"
:export-api="$API.sys_depositorder.export"

View File

@ -89,7 +89,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -114,8 +114,8 @@
</el-header>
<el-main class="nopadding">
<sc-table
:context-extra="{ id: ['createdTime'] }"
:context-menus="['id', 'name', 'sort', 'enabled', 'ignorePermissionControl', 'dataScope', 'displayDashboard', 'createdTime']"
:context-multi="{ id: ['createdTime'] }"
:default-sort="{ prop: 'sort', order: 'descending' }"
:export-api="$API.sys_role.export"
:params="query"

View File

@ -0,0 +1,108 @@
<template>
<na-table-page
:columns="{
id: {
label: $t(`唯一编码`),
is: `na-col-id`,
extra: [`createdTime`],
width: 170,
show: [`list`, `view`],
searchable: `eq`,
},
name: {
label: $t(`名字`),
width: 150,
show: [`list`, `view`, `add`, `edit`],
rule: {
required: true,
},
searchable: `eq`,
},
gender: {
label: $t(`性别`),
is: `na-col-indicator`,
enum: `genders`,
width: 100,
align: `center`,
countBy: true,
show: [`list`, `view`, `add`, `edit`],
},
sort: {
label: $t(`排序`),
align: `right`,
width: 100,
show: [`list`, `view`, `add`, `edit`],
rule: {
required: true,
validator: (rule, value, callback) => {
if (/^-?\d+$/.test(value)) callback()
else callback(new Error())
},
},
},
summary: {
label: $t(`备注`),
show: [`list`, `view`, `add`, `edit`],
searchable: `contains`,
},
enabled: {
label: $t(`启用`),
width: 100,
align: `center`,
countBy: true,
show: [`list`, `view`],
isBoolean: true,
},
createdTime: {
label: $t(`创建时间`),
show: [`view`],
},
version: {
label: $t(`数据版本`),
show: [`view`],
},
}"
:operations="[`add`, `del`, `edit`]"
:search-controls="[
{
type: `input`,
field: [`root`, `keywords`],
placeholder: $t(`消息编号 / 消息主题 / 消息内容`),
style: `width:25rem`,
},
]"
:select-filters="[
{
title: $t(`是否启用`),
key: `Enabled`,
enumName: `Enabled`,
isBoolean: [
{ label: $t(`启用`), value: true },
{ label: $t(`禁用`), value: false },
],
},
{
title: $t(`性别`),
key: `Gender`,
enumName: `genders`,
},
]"
:summary="$t(`代码模板`)"
entity-name="sys_codetemplate" />
</template>
<script>
export default {
components: {},
computed: {},
created() {},
data() {},
inject: [`reload`],
methods: {},
mounted() {},
props: [`keywords`],
watch: {},
}
</script>
<style scoped />

View File

@ -69,7 +69,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -77,6 +77,7 @@
</el-header>
<el-main class="nopadding">
<sc-table
:context-extra="{ id: ['createdTime'], ownerId: ['owner.userName'] }"
:context-menus="[
'id',
'ownerId',
@ -88,7 +89,6 @@
'owner.userName',
'tradeDirection',
]"
:context-multi="{ id: ['createdTime'], ownerId: ['owner.userName'] }"
:context-opers="['view']"
:default-sort="{ prop: 'id', order: 'descending' }"
:export-api="$API.sys_wallettrade.export"

View File

@ -49,7 +49,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -92,8 +92,8 @@
</el-col>
<el-col :lg="20">
<sc-table
:context-extra="{ id: ['createdTime'] }"
:context-menus="['id', 'userName', 'mobile', 'email', 'enabled', 'createdTime', 'lastLoginTime']"
:context-multi="{ id: ['createdTime'] }"
:context-opers="['view', 'edit']"
:default-sort="{ prop: 'id', order: 'descending' }"
:export-api="$API.sys_user.export"

View File

@ -51,7 +51,7 @@
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetime-range"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
@ -59,6 +59,7 @@
</el-header>
<el-main class="nopadding">
<sc-table
:context-extra="{ id: ['createdTime'], ownerId: ['owner.userName'] }"
:context-menus="[
'id',
'ownerId',
@ -71,7 +72,6 @@
'totalExpenditure',
'modifiedTime',
]"
:context-multi="{ id: ['createdTime'], ownerId: ['owner.userName'] }"
:context-opers="['view']"
:default-sort="{ prop: 'id', order: 'descending' }"
:export-api="$API.sys_userwallet.export"