feat: 首页仪表面板 (#103)

[skip ci]
This commit is contained in:
nsnail 2024-04-25 18:26:57 +08:00 committed by GitHub
parent 8f69c2907b
commit 149e1afa53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 1139 additions and 505 deletions

View File

@ -2,6 +2,7 @@
不以什么开始
不以什么结束
不包含
不排序
不等于
业务模块
丧偶
@ -81,6 +82,7 @@
跟踪
身份证
运行
通知
重设密码
链接
错误

View File

@ -15,15 +15,15 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.9.28">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.12-preview">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.Analyzers" Version="4.12.1">
<PackageReference Include="Roslynator.Analyzers" Version="4.12.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.23.2.88755">
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.24.0.89429">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -11,11 +11,11 @@ public sealed class CommandLineArgs : CommandSettings
/// 插入种子数据
/// </summary>
[CommandOption("-i|--insert-seed-data")]
public bool InsertSeedData { get; set; }
public bool InsertSeedData { get; init; }
/// <summary>
/// 同步数据库结构
/// </summary>
[CommandOption("-s|--sync-structure")]
public bool SyncStructure { get; set; }
public bool SyncStructure { get; init; }
}

View File

@ -14,13 +14,11 @@
},
{
"Group": "Tpl",
"Title": "示例服务",
"Description": "NetAdmin - 示例服务",
"Visible": false
"Visible": false,
},
{
"Group": "Health",
"Visible": false
"Group": "Probe",
"Visible": false,
}
],
"SecurityDefinitions": [

View File

@ -31,6 +31,13 @@ public record Sys_JobRecord : LiteImmutableEntity
[JsonIgnore]
public virtual int HttpStatusCode { get; init; }
/// <summary>
/// 拥有者信息
/// </summary>
[Navigate(nameof(JobId))]
[JsonIgnore]
public Sys_Job Job { get; init; }
/// <summary>
/// 作业编号
/// </summary>

View File

@ -0,0 +1,17 @@
namespace NetAdmin.Domain.Dto.Sys;
/// <summary>
/// 响应:获取条形图数据
/// </summary>
public sealed record GetBarChartRsp : DataAbstraction
{
/// <summary>
/// 时间戳
/// </summary>
public DateTime Timestamp { get; init; }
/// <summary>
/// 值
/// </summary>
public int Value { get; init; }
}

View File

@ -0,0 +1,17 @@
namespace NetAdmin.Domain.Dto.Sys;
/// <summary>
/// 响应:获取饼图数据
/// </summary>
public sealed record GetPieChartRsp : DataAbstraction
{
/// <summary>
/// 键名
/// </summary>
public string Key { get; init; }
/// <summary>
/// 键值
/// </summary>
public int Value { get; init; }
}

View File

@ -7,9 +7,9 @@ namespace NetAdmin.Domain.Enums.Sys;
public enum SiteMsgTypes
{
/// <summary>
/// 私信
/// 通知
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.私信))]
[ResourceDescription<Ln>(nameof(Ln.通知))]
Private = 1
,

View File

@ -23,6 +23,7 @@ public static class Numbers
public const int MAX_LIMIT_QUERY_PAGE_NO = 10000; // 最大限制:分页查询页码
public const int MAX_LIMIT_QUERY_PAGE_SIZE = 100; // 最大限制:分页查询页容量
public const int SECS_CACHE_CHART = 300; // 秒:缓存时间-仪表
public const int SECS_CACHE_DEFAULT = 60; // 秒:缓存时间-默认
public const int SECS_RED_LOCK_EXPIRY = 30; // 秒RedLock-锁过期时间,锁区域内的逻辑执行如果超过过期时间,锁将被释放
public const int SECS_RED_LOCK_RETRY = 1; // 秒RedLock-锁等待时间内,多久尝试获取一次

View File

@ -27,4 +27,12 @@ public enum Orders
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.随机排序))]
Random = 3
,
/// <summary>
/// 不排序
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.不排序))]
None = 4
}

View File

@ -1,5 +1,6 @@
using NetAdmin.Application.Modules;
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.Job;
using NetAdmin.Domain.Dto.Sys.JobRecord;
@ -19,6 +20,21 @@ public interface IJobModule : ICrudModule<CreateJobReq, QueryJobRsp // 创建类
/// </summary>
Task<QueryJobRsp> EditAsync(UpdateJobReq req);
/// <summary>
/// 获取作业记录条形图数据
/// </summary>
Task<IOrderedEnumerable<GetBarChartRsp>> GetRecordBarChartAsync(QueryReq<QueryJobRecordReq> req);
/// <summary>
/// 状态码分组作业记录饼图数据
/// </summary>
Task<IOrderedEnumerable<GetPieChartRsp>> GetRecordPieChartByHttpStatusCodeAsync(QueryReq<QueryJobRecordReq> req);
/// <summary>
/// 名称分组作业记录饼图数据
/// </summary>
Task<IOrderedEnumerable<GetPieChartRsp>> GetRecordPieChartByNameAsync(QueryReq<QueryJobRecordReq> req);
/// <summary>
/// 获取单个作业记录
/// </summary>

View File

@ -1,5 +1,6 @@
using NetAdmin.Application.Modules;
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.RequestLog;
namespace NetAdmin.SysComponent.Application.Modules.Sys;
@ -11,4 +12,20 @@ public interface IRequestLogModule : ICrudModule<CreateRequestLogReq, QueryReque
, QueryRequestLogReq, QueryRequestLogRsp // 查询类型
, NopReq, NopReq // 修改类型
, DelReq // 删除类型
>;
>
{
/// <summary>
/// 获取条形图数据
/// </summary>
Task<IOrderedEnumerable<GetBarChartRsp>> GetBarChartAsync(QueryReq<QueryRequestLogReq> req);
/// <summary>
/// 描述分组饼图数据
/// </summary>
Task<IOrderedEnumerable<GetPieChartRsp>> GetPieChartByApiSummaryAsync(QueryReq<QueryRequestLogReq> req);
/// <summary>
/// 状态码分组饼图数据
/// </summary>
Task<IOrderedEnumerable<GetPieChartRsp>> GetPieChartByHttpStatusCodeAsync(QueryReq<QueryRequestLogReq> req);
}

View File

@ -148,8 +148,11 @@ public sealed class ApiService(
private ISelect<Sys_Api> QueryInternal(QueryReq<QueryApiReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -121,8 +121,11 @@ public sealed class ConfigService(DefaultRepository<Sys_Config> rpo) //
.WhereDynamicFilter(req.DynamicFilter)
.WhereIf( //
req.Filter?.Enabled.HasValue ?? false, a => a.Enabled == req.Filter.Enabled.Value);
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -1,4 +1,7 @@
using NetAdmin.Application.Services;
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.JobRecord;
using NetAdmin.SysComponent.Application.Modules.Sys;
namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
@ -6,4 +9,20 @@ namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
/// <summary>
/// 计划作业执行记录服务
/// </summary>
public interface IJobRecordService : IService, IJobRecordModule;
public interface IJobRecordService : IService, IJobRecordModule
{
/// <summary>
/// 获取条形图数据
/// </summary>
Task<IOrderedEnumerable<GetBarChartRsp>> GetBarChartAsync(QueryReq<QueryJobRecordReq> req);
/// <summary>
/// 状态码分组饼图数据
/// </summary>
Task<IOrderedEnumerable<GetPieChartRsp>> GetPieChartByHttpStatusCodeAsync(QueryReq<QueryJobRecordReq> req);
/// <summary>
/// 名称分组饼图数据
/// </summary>
Task<IOrderedEnumerable<GetPieChartRsp>> GetPieChartByNameAsync(QueryReq<QueryJobRecordReq> req);
}

View File

@ -124,8 +124,11 @@ public sealed class DeptService(DefaultRepository<Sys_Dept> rpo) //
ret = ret.AsTreeCte();
}
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -120,8 +120,11 @@ public sealed class DicCatalogService(DefaultRepository<Sys_DicCatalog> rpo) //
private ISelect<Sys_DicCatalog> QueryInternal(QueryReq<QueryDicCatalogReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -125,8 +125,11 @@ public sealed class DicContentService(DefaultRepository<Sys_DicContent> rpo) //
private ISelect<Sys_DicContent> QueryInternal(QueryReq<QueryDicContentReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -2,6 +2,7 @@ using NetAdmin.Application.Repositories;
using NetAdmin.Application.Services;
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.JobRecord;
using NetAdmin.SysComponent.Application.Services.Sys.Dependency;
using DataType = FreeSql.DataType;
@ -65,6 +66,56 @@ public sealed class JobRecordService(DefaultRepository<Sys_JobRecord> rpo) //
return ret.Adapt<QueryJobRecordRsp>();
}
/// <inheritdoc />
public async Task<IOrderedEnumerable<GetBarChartRsp>> GetBarChartAsync(QueryReq<QueryJobRecordReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req with { Order = Orders.None })
.GroupBy(a => new {
a.CreatedTime.Year
, a.CreatedTime.Month
, a.CreatedTime.Day
, a.CreatedTime.Hour
})
.ToListAsync(a => new GetBarChartRsp {
Timestamp = new DateTime(
a.Key.Year, a.Key.Month, a.Key.Day, a.Key.Hour, 0
, 0, DateTimeKind.Unspecified)
, Value = a.Count()
})
.ConfigureAwait(false);
return ret.OrderBy(x => x.Timestamp);
}
/// <inheritdoc />
public async Task<IOrderedEnumerable<GetPieChartRsp>> GetPieChartByHttpStatusCodeAsync(
QueryReq<QueryJobRecordReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req with { Order = Orders.None })
.Include(a => a.Job)
.GroupBy(a => a.HttpStatusCode)
#pragma warning disable CA1305
.ToListAsync(a => new GetPieChartRsp { Value = a.Count(), Key = a.Key.ToString() })
#pragma warning restore CA1305
.ConfigureAwait(false);
return ret.Select(x => x with { Key = Enum.Parse<HttpStatusCode>(x.Key).ToString() })
.OrderByDescending(x => x.Value);
}
/// <inheritdoc />
public async Task<IOrderedEnumerable<GetPieChartRsp>> GetPieChartByNameAsync(QueryReq<QueryJobRecordReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req with { Order = Orders.None })
.Include(a => a.Job)
.GroupBy(a => a.Job.JobName)
.ToListAsync(a => new GetPieChartRsp { Value = a.Count(), Key = a.Key })
.ConfigureAwait(false);
return ret.OrderByDescending(x => x.Value);
}
/// <inheritdoc />
public async Task<PagedQueryRsp<QueryJobRecordRsp>> PagedQueryAsync(PagedQueryReq<QueryJobRecordReq> req)
{
@ -114,8 +165,11 @@ public sealed class JobRecordService(DefaultRepository<Sys_JobRecord> rpo) //
.WhereIf( //
req.Keywords?.Length > 0
, a => a.JobId == req.Keywords.Int64Try(0) || a.Id == req.Keywords.Int64Try(0));
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -3,6 +3,7 @@ using NetAdmin.Application.Repositories;
using NetAdmin.Application.Services;
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.Job;
using NetAdmin.Domain.Dto.Sys.JobRecord;
using NetAdmin.Domain.Enums.Sys;
@ -143,6 +144,28 @@ public sealed class JobService(DefaultRepository<Sys_Job> rpo, IJobRecordService
.ConfigureAwait(false);
}
/// <inheritdoc />
public Task<IOrderedEnumerable<GetBarChartRsp>> GetRecordBarChartAsync(QueryReq<QueryJobRecordReq> req)
{
req.ThrowIfInvalid();
return jobRecordService.GetBarChartAsync(req);
}
/// <inheritdoc />
public Task<IOrderedEnumerable<GetPieChartRsp>> GetRecordPieChartByHttpStatusCodeAsync(
QueryReq<QueryJobRecordReq> req)
{
req.ThrowIfInvalid();
return jobRecordService.GetPieChartByHttpStatusCodeAsync(req);
}
/// <inheritdoc />
public Task<IOrderedEnumerable<GetPieChartRsp>> GetRecordPieChartByNameAsync(QueryReq<QueryJobRecordReq> req)
{
req.ThrowIfInvalid();
return jobRecordService.GetPieChartByNameAsync(req);
}
/// <inheritdoc />
public async Task<PagedQueryRsp<QueryJobRsp>> PagedQueryAsync(PagedQueryReq<QueryJobReq> req)
{
@ -227,8 +250,11 @@ public sealed class JobService(DefaultRepository<Sys_Job> rpo, IJobRecordService
.WhereIf( //
req.Keywords?.Length > 0
, a => a.Id == req.Keywords.Int64Try(0) || a.JobName.Contains(req.Keywords));
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -2,6 +2,7 @@ using NetAdmin.Application.Repositories;
using NetAdmin.Application.Services;
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.RequestLog;
using NetAdmin.SysComponent.Application.Services.Sys.Dependency;
@ -64,6 +65,54 @@ public sealed class RequestLogService(DefaultRepository<Sys_RequestLog> rpo) //
return ret.Adapt<QueryRequestLogRsp>();
}
/// <inheritdoc />
public async Task<IOrderedEnumerable<GetBarChartRsp>> GetBarChartAsync(QueryReq<QueryRequestLogReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req with { Order = Orders.None })
.GroupBy(a => new {
a.CreatedTime.Year
, a.CreatedTime.Month
, a.CreatedTime.Day
, a.CreatedTime.Hour
})
.ToListAsync(a => new GetBarChartRsp {
Timestamp = new DateTime(
a.Key.Year, a.Key.Month, a.Key.Day, a.Key.Hour, 0
, 0, DateTimeKind.Unspecified)
, Value = a.Count()
})
.ConfigureAwait(false);
return ret.OrderBy(x => x.Timestamp);
}
/// <inheritdoc />
public async Task<IOrderedEnumerable<GetPieChartRsp>> GetPieChartByApiSummaryAsync(QueryReq<QueryRequestLogReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req with { Order = Orders.None })
.GroupBy(a => a.Api.Summary)
.ToListAsync(a => new GetPieChartRsp { Value = a.Count(), Key = a.Key })
.ConfigureAwait(false);
return ret.OrderByDescending(x => x.Value);
}
/// <inheritdoc />
public async Task<IOrderedEnumerable<GetPieChartRsp>> GetPieChartByHttpStatusCodeAsync(
QueryReq<QueryRequestLogReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req with { Order = Orders.None })
.GroupBy(a => a.HttpStatusCode)
#pragma warning disable CA1305
.ToListAsync(a => new GetPieChartRsp { Value = a.Count(), Key = a.Key.ToString() })
#pragma warning restore CA1305
.ConfigureAwait(false);
return ret.Select(x => x with { Key = Enum.Parse<HttpStatusCode>(x.Key).ToString() })
.OrderByDescending(x => x.Value);
}
/// <inheritdoc />
public async Task<PagedQueryRsp<QueryRequestLogRsp>> PagedQueryAsync(PagedQueryReq<QueryRequestLogReq> req)
{
@ -114,8 +163,11 @@ public sealed class RequestLogService(DefaultRepository<Sys_RequestLog> rpo) //
private ISelect<Sys_RequestLog> QueryInternal(QueryReq<QueryRequestLogReq> req)
{
var ret = Rpo.Select.Include(a => a.Api).WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -110,8 +110,11 @@ public sealed class SiteMsgDeptService(DefaultRepository<Sys_SiteMsgDept> rpo) /
private ISelect<Sys_SiteMsgDept> QueryInternal(QueryReq<QuerySiteMsgDeptReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -116,8 +116,11 @@ public sealed class SiteMsgFlagService(DefaultRepository<Sys_SiteMsgFlag> rpo) /
private ISelect<Sys_SiteMsgFlag> QueryInternal(QueryReq<QuerySiteMsgFlagReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -110,8 +110,11 @@ public sealed class SiteMsgRoleService(DefaultRepository<Sys_SiteMsgRole> rpo) /
private ISelect<Sys_SiteMsgRole> QueryInternal(QueryReq<QuerySiteMsgRoleReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -285,8 +285,11 @@ public sealed class SiteMsgService(
req.Keywords?.Length > 0
, a => a.Id == req.Keywords.Int64Try(0) || a.Title.Contains(req.Keywords) ||
a.Summary.Contains(req.Keywords));
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -110,8 +110,11 @@ public sealed class SiteMsgUserService(DefaultRepository<Sys_SiteMsgUser> rpo) /
private ISelect<Sys_SiteMsgUser> QueryInternal(QueryReq<QuerySiteMsgUserReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -486,8 +486,11 @@ public sealed class UserService(
private ISelect<Sys_User> QueryInternal(QueryReq<QueryUserReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);
@ -522,8 +525,11 @@ public sealed class UserService(
, a => a.Id == req.Keywords.Int64Try(0) || a.UserName.Contains(req.Keywords) ||
a.Mobile.Contains(req.Keywords) || a.Email.Contains(req.Keywords) ||
a.Summary.Contains(req.Keywords));
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -110,8 +110,11 @@ public sealed class ExampleService(DefaultRepository<Tpl_Example> rpo) //
private ISelect<Tpl_Example> QueryInternal(QueryReq<QueryExampleReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -1,5 +1,6 @@
using NetAdmin.Cache;
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.Job;
using NetAdmin.Domain.Dto.Sys.JobRecord;
using NetAdmin.SysComponent.Application.Services.Sys.Dependency;
@ -53,6 +54,44 @@ public sealed class JobCache(IDistributedCache cache, IJobService service)
return Service.GetAsync(req);
}
/// <inheritdoc />
public Task<IOrderedEnumerable<GetBarChartRsp>> GetRecordBarChartAsync(QueryReq<QueryJobRecordReq> req)
{
#if !DEBUG
return GetOrCreateAsync( //
GetCacheKey(req.GetHashCode().ToString(CultureInfo.InvariantCulture)) //
, () => Service.GetRecordBarChartAsync(req), TimeSpan.FromSeconds(Numbers.SECS_CACHE_CHART));
#else
return Service.GetRecordBarChartAsync(req);
#endif
}
/// <inheritdoc />
public Task<IOrderedEnumerable<GetPieChartRsp>> GetRecordPieChartByHttpStatusCodeAsync(
QueryReq<QueryJobRecordReq> req)
{
#if !DEBUG
return GetOrCreateAsync( //
GetCacheKey(req.GetHashCode().ToString(CultureInfo.InvariantCulture)) //
, () => Service.GetRecordPieChartByHttpStatusCodeAsync(req)
, TimeSpan.FromSeconds(Numbers.SECS_CACHE_DEFAULT));
#else
return Service.GetRecordPieChartByHttpStatusCodeAsync(req);
#endif
}
/// <inheritdoc />
public Task<IOrderedEnumerable<GetPieChartRsp>> GetRecordPieChartByNameAsync(QueryReq<QueryJobRecordReq> req)
{
#if !DEBUG
return GetOrCreateAsync( //
GetCacheKey(req.GetHashCode().ToString(CultureInfo.InvariantCulture)) //
, () => Service.GetRecordPieChartByNameAsync(req), TimeSpan.FromSeconds(Numbers.SECS_CACHE_CHART));
#else
return Service.GetRecordPieChartByNameAsync(req);
#endif
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryJobRsp>> PagedQueryAsync(PagedQueryReq<QueryJobReq> req)
{

View File

@ -1,5 +1,6 @@
using NetAdmin.Cache;
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.RequestLog;
using NetAdmin.SysComponent.Application.Services.Sys.Dependency;
using NetAdmin.SysComponent.Cache.Sys.Dependency;
@ -46,6 +47,42 @@ public sealed class RequestLogCache(IDistributedCache cache, IRequestLogService
return Service.GetAsync(req);
}
/// <inheritdoc />
public Task<IOrderedEnumerable<GetBarChartRsp>> GetBarChartAsync(QueryReq<QueryRequestLogReq> req)
{
#if !DEBUG
return GetOrCreateAsync( //
GetCacheKey(req.GetHashCode().ToString(CultureInfo.InvariantCulture)) //
, () => Service.GetBarChartAsync(req), TimeSpan.FromSeconds(Numbers.SECS_CACHE_CHART));
#else
return Service.GetBarChartAsync(req);
#endif
}
/// <inheritdoc />
public Task<IOrderedEnumerable<GetPieChartRsp>> GetPieChartByApiSummaryAsync(QueryReq<QueryRequestLogReq> req)
{
#if !DEBUG
return GetOrCreateAsync( //
GetCacheKey(req.GetHashCode().ToString(CultureInfo.InvariantCulture)) //
, () => Service.GetPieChartByApiSummaryAsync(req), TimeSpan.FromSeconds(Numbers.SECS_CACHE_CHART));
#else
return Service.GetPieChartByApiSummaryAsync(req);
#endif
}
/// <inheritdoc />
public Task<IOrderedEnumerable<GetPieChartRsp>> GetPieChartByHttpStatusCodeAsync(QueryReq<QueryRequestLogReq> req)
{
#if !DEBUG
return GetOrCreateAsync( //
GetCacheKey(req.GetHashCode().ToString(CultureInfo.InvariantCulture)) //
, () => Service.GetPieChartByHttpStatusCodeAsync(req), TimeSpan.FromSeconds(Numbers.SECS_CACHE_CHART));
#else
return Service.GetPieChartByHttpStatusCodeAsync(req);
#endif
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryRequestLogRsp>> PagedQueryAsync(PagedQueryReq<QueryRequestLogReq> req)
{

View File

@ -1,4 +1,5 @@
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.Job;
using NetAdmin.Domain.Dto.Sys.JobRecord;
using NetAdmin.Host.Attributes;
@ -75,6 +76,31 @@ public sealed class JobController(IJobCache cache) : ControllerBase<IJobCache, I
return Cache.GetAsync(req);
}
/// <summary>
/// 获取作业记录条形图数据
/// </summary>
public Task<IOrderedEnumerable<GetBarChartRsp>> GetRecordBarChartAsync(QueryReq<QueryJobRecordReq> req)
{
return Cache.GetRecordBarChartAsync(req);
}
/// <summary>
/// 状态码分组作业记录饼图数据
/// </summary>
public Task<IOrderedEnumerable<GetPieChartRsp>> GetRecordPieChartByHttpStatusCodeAsync(
QueryReq<QueryJobRecordReq> req)
{
return Cache.GetRecordPieChartByHttpStatusCodeAsync(req);
}
/// <summary>
/// 名称分组作业记录饼图数据
/// </summary>
public Task<IOrderedEnumerable<GetPieChartRsp>> GetRecordPieChartByNameAsync(QueryReq<QueryJobRecordReq> req)
{
return Cache.GetRecordPieChartByNameAsync(req);
}
/// <summary>
/// 分页查询计划作业
/// </summary>

View File

@ -1,4 +1,5 @@
using NetAdmin.Domain.Dto.Dependency;
using NetAdmin.Domain.Dto.Sys;
using NetAdmin.Domain.Dto.Sys.RequestLog;
using NetAdmin.Host.Attributes;
using NetAdmin.Host.Controllers;
@ -70,6 +71,30 @@ public sealed class LogController(IRequestLogCache cache) : ControllerBase<IRequ
return Cache.GetAsync(req);
}
/// <summary>
/// 获取条形图数据
/// </summary>
public Task<IOrderedEnumerable<GetBarChartRsp>> GetBarChartAsync(QueryReq<QueryRequestLogReq> req)
{
return Cache.GetBarChartAsync(req);
}
/// <summary>
/// 描述分组饼图数据
/// </summary>
public Task<IOrderedEnumerable<GetPieChartRsp>> GetPieChartByApiSummaryAsync(QueryReq<QueryRequestLogReq> req)
{
return Cache.GetPieChartByApiSummaryAsync(req);
}
/// <summary>
/// 状态码分组饼图数据
/// </summary>
public Task<IOrderedEnumerable<GetPieChartRsp>> GetPieChartByHttpStatusCodeAsync(QueryReq<QueryRequestLogReq> req)
{
return Cache.GetPieChartByHttpStatusCodeAsync(req);
}
/// <summary>
/// 分页查询请求日志
/// </summary>

View File

@ -11,14 +11,14 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@tinymce/tinymce-vue": "^5.1.1",
"ace-builds": "^1.33.0",
"ace-builds": "^1.33.1",
"axios": "^1.6.8",
"clipboard": "^2.0.11",
"core-js": "^3.37.0",
"cropperjs": "^1.6.1",
"cropperjs": "^1.6.2",
"crypto-js": "^4.2.0",
"echarts": "^5.5.0",
"element-plus": "^2.7.0",
"element-plus": "^2.7.1",
"json-bigint": "^1.0.0",
"json5-to-table": "^0.1.8",
"markdown-it": "^14.1.0",
@ -29,9 +29,9 @@
"sortablejs": "^1.15.2",
"tinymce": "^6.8.3",
"vkbeautify": "^0.99.3",
"vue": "^3.4.23",
"vue-i18n": "^9.12.1",
"vue-router": "^4.3.1",
"vue": "^3.4.25",
"vue-i18n": "^9.13.1",
"vue-router": "^4.3.2",
"vue3-ace-editor": "^2.2.4",
"vue3-json-viewer": "^2.2.2",
"vuedraggable": "^4.0.3",
@ -44,8 +44,8 @@
"prettier": "^3.2.5",
"prettier-plugin-organize-attributes": "^1.0.0",
"sass": "^1.75.0",
"terser": "^5.30.3",
"vite": "^5.2.9"
"terser": "^5.30.4",
"vite": "^5.2.10"
},
"browserslist": [
"> 1%",

View File

@ -82,6 +82,39 @@ export default {
},
},
/**
* 获取作业记录条形图数据
*/
getRecordBarChart: {
url: `${config.API_URL}/api/sys/job/get.record.bar.chart`,
name: `获取作业记录条形图数据`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 状态码分组作业记录饼图数据
*/
getRecordPieChartByHttpStatusCode: {
url: `${config.API_URL}/api/sys/job/get.record.pie.chart.by.http.status.code`,
name: `状态码分组作业记录饼图数据`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 名称分组作业记录饼图数据
*/
getRecordPieChartByName: {
url: `${config.API_URL}/api/sys/job/get.record.pie.chart.by.name`,
name: `名称分组作业记录饼图数据`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询计划作业
*/

View File

@ -27,6 +27,39 @@ export default {
},
},
/**
* 获取条形图数据
*/
getBarChart: {
url: `${config.API_URL}/api/sys/log/get.bar.chart`,
name: `获取条形图数据`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 描述分组饼图数据
*/
getPieChartByApiSummary: {
url: `${config.API_URL}/api/sys/log/get.pie.chart.by.api.summary`,
name: `描述分组饼图数据`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 状态码分组饼图数据
*/
getPieChartByHttpStatusCode: {
url: `${config.API_URL}/api/sys/log/get.pie.chart.by.http.status.code`,
name: `状态码分组饼图数据`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询请求日志
*/

View File

@ -0,0 +1,7 @@
<template>
<svg class="icon" height="128" p-id="5271" t="1713938893902" version="1.1" viewBox="0 0 1024 1024" width="128" xmlns="http://www.w3.org/2000/svg">
<path
d="M802.752 806.58A413.836 413.836 0 1 1 924 513.904a414.072 414.072 0 0 1-121.248 292.676z m-300.868-278.768a13.408 13.408 0 0 1-2.8-1.748l-0.236-0.212a8.652 8.652 0 0 1-0.72-0.764 12.5 12.5 0 0 1-1.432-1.6 4.736 4.736 0 0 1-0.328-0.4c-0.172-0.264-0.264-0.552-0.42-0.828a12.224 12.224 0 0 1-0.864-1.892c-0.092-0.24-0.212-0.476-0.316-0.684a3.248 3.248 0 0 1-0.104-0.672 3.44 3.44 0 0 1-0.252-0.776 11.492 11.492 0 0 1-0.076-1.484c-0.028-0.304 1.672-0.4 1.672-0.716V148c-197.6 7.104-355.944 169.648-355.972 367.796a367.156 367.156 0 0 0 696.02 164.244z m34.164-379.844v300l216-212.132a334.984 334.984 0 0 0-216-87.868z m247.4 116.076L552 498.672 850 636a353.712 353.712 0 0 0-66.552-371.956zM716 488h100v28h-100v-28z m0-56h100v28h-100v-28z m-52-180h-100v-28h100v28z m-56 56h-44v-28h44v28z m-304 396h164v28H304v-28z m0-56h164v28H304v-28z m0-56h164v28H304v-28z"
p-id="5272"></path>
</svg>
</template>

View File

@ -66,4 +66,5 @@ export { default as Exception } from './Exception.vue'
export { default as Collect } from './Collect.vue'
export { default as FreeSql } from './FreeSql.vue'
export { default as Performance } from './Performance.vue'
export { default as Proxy } from './Proxy.vue'
export { default as Proxy } from './Proxy.vue'
export { default as ECharts } from './ECharts.vue'

View File

@ -81,7 +81,7 @@ import tool from '@/utils/tool'
import vkbeautify from 'vkbeautify/index'
export default {
emits: ['search'],
emits: ['search', 'reset'],
props: {
hasDate: { type: Boolean, default: true },
dateType: { type: String, default: 'daterange' },
@ -252,10 +252,10 @@ export default {
extend: {
remote: true,
request: async (query) => {
var data = {
const data = {
keyword: query,
}
var list = await this.$API.system.dic.get.get(data)
const list = await this.$API.system.dic.get.get(data)
return list.data.map((item) => {
return {
label: item.label,
@ -322,6 +322,7 @@ export default {
],
casLoaded: false,
keepKeywords: null,
keepCreatedTime: null,
form: {
root: {},
filter: {},
@ -400,6 +401,10 @@ export default {
if (this.keepKeywords) {
this.form.root.keywords = this.keepKeywords
}
if (this.keepCreatedTime) {
this.form.dy.createdTime = this.keepCreatedTime
}
this.$emit('reset')
this.search()
},
},

View File

@ -12,7 +12,7 @@
<div v-if="data.length <= 0" class="sc-select-filter__no-data">暂无数据</div>
<div v-for="item in data" :key="item.key" class="sc-select-filter__item">
<div :style="{ width: labelWidth + 'rem' }" class="sc-select-filter__item-title">
<label>{{ item.title }}</label>
<label>{{ item.title }}</label>
</div>
<div class="sc-select-filter__item-options">
<ul>
@ -134,6 +134,7 @@ export default {
.sc-select-filter__item {
display: flex;
align-items: baseline;
}
.sc-select-filter__item-title {

View File

@ -111,9 +111,8 @@
</div>
<sc-contextmenu @command="contextMenuCommand" @visible-change="contextMenuVisibleChange" ref="contextmenu">
<sc-contextmenu-item
v-for="(menu, index) in contextMenus"
v-for="(menu, index) in contextMenus.filter((x) => x === current.column.property)"
:command="menu"
:disabled="menu !== current.column.property"
:key="index"
:title="`${menu}`">
<sc-contextmenu-item :command="`${menu}^|^Equal^|^${current.row[menu]}`" title="="></sc-contextmenu-item>

File diff suppressed because one or more lines are too long

View File

@ -60,9 +60,17 @@ const DEFAULT_CONFIG = {
//控制台首页默认布局
DEFAULT_GRID: {
//默认分栏数量和宽度 例如 [24] [18,6] [8,8,8] [6,12,6]
layout: [24, 12, 12],
layout: [8, 8, 8, 12, 12, 12, 12],
//小组件分布com取值:views/home/components 文件名
compsList: [['ver'], ['modules'], ['change-log']],
compsList: [
['chart-bar-request'],
['ver'],
['chart-bar-jobrecord'],
['chart-pie-request'],
['chart-pie-jobrecord'],
['modules'],
['change-log'],
],
},
//默认头像

View File

@ -40,6 +40,7 @@ import scUpload from '@/components/scUpload'
import scUploadFile from '@/components/scUpload/file'
import scUploadMultiple from '@/components/scUpload/multiple'
import scWaterMark from '@/components/scWaterMark'
import scSelectFilter from '@/components/scSelectFilter'
// net-admin组件
import naArea from '@/components/naArea/index.vue'
@ -113,6 +114,7 @@ export default {
app.component('scUploadFile', scUploadFile)
app.component('scUploadMultiple', scUploadMultiple)
app.component('scWaterMark', scWaterMark)
app.component('scSelectFilter', scSelectFilter)
//注册全局指令
app.directive('auth', auth)

View File

@ -1,10 +1,6 @@
<template>
<el-card header="changelog" shadow="never">
<el-skeleton :loading="loading" :rows="10" animated>
<template #default>
<div v-html="changeLog" class="change-log"></div>
</template>
</el-skeleton>
<el-card v-loading="loading" header="更新日志" shadow="never">
<div v-html="changeLog" class="change-log"></div>
</el-card>
</template>
@ -39,6 +35,7 @@ export default {
<style scoped></style>
<style lang="scss">
.change-log {
min-height: 30rem;
line-height: 3rem;
li {
margin: 0 0 0 2rem;

View File

@ -0,0 +1,44 @@
<template>
<el-card :header="$t('作业趋势')" shadow="never" style="height: 25rem">
<chart-bar
:api="[
{
file: 'sys_job',
name: 'getRecordBarChart',
value: [tool.dateFormat(new Date(), 'yyyy-MM-dd'), tool.dateFormat(new Date(), 'yyyy-MM-dd')],
},
{
file: 'sys_job',
name: 'getRecordBarChart',
value: [
tool.dateFormat(new Date().setDate(new Date().getDate() - 1), 'yyyy-MM-dd'),
tool.dateFormat(new Date().setDate(new Date().getDate() - 1), 'yyyy-MM-dd'),
],
},
]"></chart-bar>
</el-card>
</template>
<script>
import ChartBar from '@/views/home/widgets/components/components/chart-bar.vue'
import tool from '@/utils/tool'
export default {
computed: {
tool() {
return tool
},
},
title: '作业趋势',
icon: 'el-icon-data-line',
description: '作业趋势',
components: {
ChartBar,
},
data() {
return {}
},
async created() {},
mounted() {},
}
</script>

View File

@ -0,0 +1,44 @@
<template>
<el-card :header="$t('访问趋势')" shadow="never" style="height: 25rem">
<chart-bar
:api="[
{
file: 'sys_log',
name: 'getBarChart',
value: [tool.dateFormat(new Date(), 'yyyy-MM-dd'), tool.dateFormat(new Date(), 'yyyy-MM-dd')],
},
{
file: 'sys_log',
name: 'getBarChart',
value: [
tool.dateFormat(new Date().setDate(new Date().getDate() - 1), 'yyyy-MM-dd'),
tool.dateFormat(new Date().setDate(new Date().getDate() - 1), 'yyyy-MM-dd'),
],
},
]"></chart-bar>
</el-card>
</template>
<script>
import ChartBar from '@/views/home/widgets/components/components/chart-bar.vue'
import tool from '@/utils/tool'
export default {
computed: {
tool() {
return tool
},
},
title: '访问趋势',
icon: 'el-icon-data-line',
description: '访问趋势',
components: {
ChartBar,
},
data() {
return {}
},
async created() {},
mounted() {},
}
</script>

View File

@ -0,0 +1,43 @@
<template>
<el-card :header="$t('作业分布')" shadow="never" style="height: 25rem">
<chart-pie
:api="[
{
file: 'sys_job',
name: 'getRecordPieChartByName',
value: [tool.dateFormat(new Date(), 'yyyy-MM-dd'), tool.dateFormat(new Date(), 'yyyy-MM-dd')],
radius: ['70%', '100%'],
},
{
file: 'sys_job',
name: 'getRecordPieChartByHttpStatusCode',
value: [tool.dateFormat(new Date(), 'yyyy-MM-dd'), tool.dateFormat(new Date(), 'yyyy-MM-dd')],
radius: [0, '30%'],
},
]"></chart-pie>
</el-card>
</template>
<script>
import ChartPie from '@/views/home/widgets/components/components/chart-pie.vue'
import tool from '@/utils/tool'
export default {
computed: {
tool() {
return tool
},
},
title: '作业分布',
icon: 'el-icon-data-line',
description: '作业分布',
components: {
ChartPie,
},
data() {
return {}
},
async created() {},
mounted() {},
}
</script>

View File

@ -0,0 +1,43 @@
<template>
<el-card :header="$t('访问分布')" shadow="never" style="height: 25rem">
<chart-pie
:api="[
{
file: 'sys_log',
name: 'getPieChartByApiSummary',
value: [tool.dateFormat(new Date(), 'yyyy-MM-dd'), tool.dateFormat(new Date(), 'yyyy-MM-dd')],
radius: ['70%', '100%'],
},
{
file: 'sys_log',
name: 'getPieChartByHttpStatusCode',
value: [tool.dateFormat(new Date(), 'yyyy-MM-dd'), tool.dateFormat(new Date(), 'yyyy-MM-dd')],
radius: [0, '30%'],
},
]"></chart-pie>
</el-card>
</template>
<script>
import ChartPie from '@/views/home/widgets/components/components/chart-pie.vue'
import tool from '@/utils/tool'
export default {
computed: {
tool() {
return tool
},
},
title: '访问分布',
icon: 'el-icon-data-line',
description: '访问分布',
components: {
ChartPie,
},
data() {
return {}
},
async created() {},
mounted() {},
}
</script>

View File

@ -0,0 +1,55 @@
<template>
<div v-loading="loading">
<scEcharts :option="option" height="20rem"></scEcharts>
</div>
</template>
<script>
import scEcharts from '@/components/scEcharts'
export default {
components: {
scEcharts,
},
props: ['api'],
data() {
return {
loading: true,
option: {
tooltip: {
trigger: 'axis',
},
xAxis: {
type: 'category',
data: [],
},
yAxis: {
type: 'value',
},
series: [],
},
}
},
async created() {
const res = await Promise.all(
this.api.map((x) => {
return this.$API[x.file][x.name].post({
dynamicFilter: {
field: 'createdTime',
operator: 'dateRange',
value: x.value,
},
})
}),
)
this.loading = false
this.option.xAxis.data = res[0].data.map((x) => {
return x.timestamp
})
this.option.series = res.map((x) => {
return { type: 'line', data: x.data.map((y) => y.value), symbol: 'none', areaStyle: {} }
})
},
mounted() {},
}
</script>

View File

@ -0,0 +1,61 @@
<template>
<div v-loading="loading">
<scEcharts :option="option" height="20rem"></scEcharts>
</div>
</template>
<script>
import scEcharts from '@/components/scEcharts'
export default {
components: {
scEcharts,
},
props: ['api'],
data() {
return {
loading: true,
option: {
tooltip: {
trigger: 'item',
formatter: '{b}<br />{c}{d}%',
},
series: [],
},
}
},
async created() {
const res = await Promise.all(
this.api.map((x) => {
return this.$API[x.file][x.name].post({
dynamicFilter: {
field: 'createdTime',
operator: 'dateRange',
value: x.value,
},
})
}),
)
this.loading = false
let i = 0
this.option.series = res.map((x) => {
return {
label: {
show: true,
alignTo: 'labelLine',
formatter: '{b} {d}%',
textStyle: {
textBorderColor: 'transparent',
},
},
radius: this.api[i++].radius,
type: 'pie',
data: x.data.map((y) => {
return { name: y.key, value: y.value }
}),
}
})
},
mounted() {},
}
</script>

View File

@ -1,96 +0,0 @@
<template>
<el-card v-loading="loading" :header="$t('实时收入')" shadow="hover">
<scEcharts :option="option" height="300px" ref="c1"></scEcharts>
</el-card>
</template>
<script>
import scEcharts from '@/components/scEcharts'
export default {
title: '实时收入',
icon: 'el-icon-data-line',
description: 'Echarts组件演示',
components: {
scEcharts,
},
data() {
return {
loading: true,
option: {},
}
},
created() {
const _this = this
setTimeout(function () {
_this.loading = false
}, 500)
this.option = {
tooltip: {
trigger: 'axis',
},
xAxis: {
boundaryGap: false,
type: 'category',
data: (function () {
let now = new Date()
const res = []
let len = 30
while (len--) {
res.unshift(now.toLocaleTimeString().replace(/^\D*/, ''))
now = new Date(now - 2000)
}
return res
})(),
},
yAxis: [
{
type: 'value',
name: '价格',
splitLine: {
show: false,
},
},
],
series: [
{
name: '收入',
type: 'line',
symbol: 'none',
lineStyle: {
width: 1,
color: '#409EFF',
},
areaStyle: {
opacity: 0.1,
color: '#79bbff',
},
data: (function () {
const res = []
let len = 30
while (len--) {
res.push(Math.round(0))
}
return res
})(),
},
],
}
},
mounted() {
const _this = this
setInterval(function () {
const o = _this.option
o.series[0].data.shift()
o.series[0].data.push(Math.round(Math.random() * 100))
o.xAxis.data.shift()
o.xAxis.data.push(new Date().toLocaleTimeString().replace(/^\D*/, ''))
//_this.$refs.c1.myChart.setOption(o)
}, 2100)
},
}
</script>

View File

@ -1,33 +1,19 @@
<template>
<el-card header="dependencies" shadow="never">
<el-skeleton :loading="loading" :rows="5" animated>
<template #default>
<el-descriptions :column="2" border>
<el-descriptions-item v-for="(value, key) in packageJson.dependencies" :key="key" :label="key">{{ value }}</el-descriptions-item>
</el-descriptions>
</template>
</el-skeleton>
</el-card>
<el-card header="devDependencies" shadow="never">
<el-skeleton :loading="loading" :rows="5" animated>
<template #default>
<el-descriptions :column="2" border>
<el-descriptions-item v-for="(value, key) in packageJson.devDependencies" :key="key" :label="key">{{
value
}}</el-descriptions-item>
</el-descriptions>
</template>
</el-skeleton>
</el-card>
<el-card header="assemblies" shadow="never">
<el-skeleton :loading="loading" :rows="5" animated>
<template #default>
<el-descriptions :column="1" border>
<el-descriptions-item v-for="(value, key) in modules" :key="key" :label="value.name">{{ value.version }}</el-descriptions-item>
</el-descriptions>
</template>
</el-skeleton>
</el-card>
<div v-loading="loading">
<el-card header="开发依赖" shadow="never">
<el-descriptions :column="2" border>
<el-descriptions-item v-for="(value, key) in packageJson.devDependencies" :key="key" :label="key">{{ value }}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card header="运行依赖" shadow="never">
<el-descriptions :column="2" border>
<el-descriptions-item v-for="(value, key) in packageJson.dependencies" :key="key" :label="key">{{ value }}</el-descriptions-item>
</el-descriptions>
<el-descriptions :column="1" border style="margin-top: 1rem">
<el-descriptions-item v-for="(value, key) in modules" :key="key" :label="value.name">{{ value.version }}</el-descriptions-item>
</el-descriptions>
</el-card>
</div>
</template>
<script>

View File

@ -1,51 +0,0 @@
<template>
<el-card :header="$t('时钟')" class="item-background" shadow="hover">
<div class="time">
<h2>{{ time }}</h2>
<p>{{ day }}</p>
</div>
</el-card>
</template>
<script>
export default {
title: '时钟',
icon: 'el-icon-clock',
description: '演示部件效果',
data() {
return {
time: '',
day: '',
}
},
mounted() {
this.showTime()
setInterval(() => {
this.showTime()
}, 1000)
},
methods: {
showTime() {
this.time = this.$TOOL.dateFormat(new Date(), 'hh:mm:ss')
this.day = this.$TOOL.dateFormat(new Date(), 'yyyy年MM月dd日')
},
},
}
</script>
<style scoped>
.item-background {
background: linear-gradient(to right, rgb(66, 76, 80), #ccc);
color: #fff;
}
.time h2 {
font-size: 3rem;
}
.time p {
font-size: 1.1rem;
margin-top: 1rem;
opacity: 0.7;
}
</style>

View File

@ -1,14 +1,10 @@
<template>
<el-card :class="loading ? '' : 'aboutTop'" shadow="never">
<el-skeleton :loading="loading" :rows="5" animated>
<template #default>
<div class="aboutTop-info">
<img alt="" src="@/assets/img/logo.png" />
<h2>{{ packageJson.name }}</h2>
<p>{{ ver }}</p>
</div>
</template>
</el-skeleton>
<el-card v-loading="loading" class="main" shadow="never">
<div class="wrap">
<img alt="" src="@/assets/img/logo.png" />
<h2>{{ packageJson.name }}</h2>
<p>{{ ver }}</p>
</div>
</el-card>
</template>
@ -40,22 +36,18 @@ export default {
</script>
<style scoped>
.aboutTop {
border: 0;
background: linear-gradient(to right, rgb(66, 76, 80), #ccc);
color: #fff;
}
.aboutTop-info {
.main,
.wrap {
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.aboutTop-info img {
width: 10rem;
.main {
height: 25rem;
}
.aboutTop-info h2 {
font-size: 2rem;
margin-top: 1rem;
}
.aboutTop-info p {
margin-top: 1rem;
.wrap {
gap: 1rem;
}
</style>

View File

@ -1,5 +1,23 @@
<template>
<el-container>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
{
title: $t('作业状态'),
key: 'status',
options: [
{ label: $t('全部'), value: '' },
...Object.entries(this.$GLOBAL.enums.jobStatues).map((x) => {
return { value: x[0], label: x[1][1] }
}),
],
},
]"
:label-width="6"
@on-change="filterChange"
ref="selectFilter"></sc-select-filter>
</el-header>
<el-header>
<div class="left-panel">
<na-search
@ -10,15 +28,6 @@
placeholder: $t('作业编号 / 作业名称'),
style: 'width:20rem',
},
{
type: 'select',
field: ['dy', 'status'],
options: Object.entries(this.$GLOBAL.enums.jobStatues).map((x) => {
return { value: x[0], label: x[1][1] }
}),
placeholder: $t('作业状态'),
style: 'width:10rem',
},
{
type: 'select',
field: ['dy', 'httpMethod'],
@ -40,6 +49,7 @@
},
]"
:vue="this"
@reset="Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))"
@search="onSearch"
ref="search" />
</div>
@ -151,9 +161,11 @@
import saveDialog from './save'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
import ScSelectFilter from '@/components/scSelectFilter/index.vue'
export default {
components: {
ScSelectFilter,
saveDialog,
},
inject: ['reload'],
@ -192,6 +204,12 @@ export default {
},
created() {},
methods: {
filterChange(data) {
Object.entries(data).forEach(([key, value]) => {
this.$refs.search.form.dy[key] = value
})
this.$refs.search.search()
},
//
async changeSwitch(event, row) {
try {

View File

@ -1,73 +1,69 @@
<template>
<el-container>
<el-header>
<div class="left-panel">
<na-search
:controls="[
{
type: 'select',
field: ['dy', 'httpStatusCode'],
options: [
{ label: '成功', value: '200,299' },
{ label: '失败', value: '300,999' },
],
placeholder: '登录结果',
style: 'width:15rem',
},
{
type: 'input',
field: ['dy', 'extraData'],
placeholder: '登录名',
style: 'width:15rem',
},
{
type: 'input',
field: ['dy', 'createdClientIp'],
placeholder: '客户端IP',
style: 'width:15rem',
},
]"
:vue="this"
@search="onSearch"
ref="search" />
</div>
<div class="right-panel"></div>
</el-header>
<el-main class="nopadding">
<el-container>
<el-header class="headerPublic">
<div class="left-panel">
<na-search
:controls="[
{
type: 'select',
field: ['dy', 'httpStatusCode'],
options: [
{ label: '成功', value: '200,299' },
{ label: '失败', value: '300,999' },
],
placeholder: '登录结果',
style: 'width:15rem',
},
{
type: 'input',
field: ['dy', 'extraData'],
placeholder: '登录名',
style: 'width:15rem',
},
{
type: 'input',
field: ['dy', 'createdClientIp'],
placeholder: '客户端IP',
style: 'width:15rem',
},
]"
:vue="this"
@search="onSearch"
ref="search" />
</div>
<div class="right-panel"></div>
</el-header>
<el-main class="nopadding">
<sc-table
:apiObj="$API.sys_log.pagedQuery"
:context-menus="['id', 'httpStatusCode', 'extraData', 'createdClientIp', 'os', 'createdUserAgent', 'createdTime']"
:context-opers="['view']"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:params="query"
:vue="this"
@row-click="rowClick"
ref="table"
remoteFilter
remoteSort
row-key="id"
stripe>
<el-table-column :label="$t('日志编号')" prop="id" sortable="custom" width="150"></el-table-column>
<el-table-column :label="$t('结果')" align="center" prop="httpStatusCode" sortable="custom" width="80">
<template #default="scope">
<sc-status-indicator :type="scope.row.httpStatusCode === 200 ? 'success' : 'danger'" />
{{ scope.row.httpStatusCode === 200 ? '成功' : '失败' }}
</template>
</el-table-column>
<el-table-column :label="$t('登录名')" prop="extraData" sortable="custom" width="200"></el-table-column>
<el-table-column :label="$t('客户端IP')" prop="createdClientIp" sortable="custom" width="200">
<template #default="scope">
<na-ip :ip="scope.row.createdClientIp"></na-ip>
</template>
</el-table-column>
<el-table-column :label="$t('操作系统')" prop="os" width="150"></el-table-column>
<el-table-column :label="$t('用户代理')" prop="createdUserAgent" show-overflow-tooltip sortable="custom"></el-table-column>
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" width="170"></el-table-column>
</sc-table>
</el-main>
</el-container>
<sc-table
:apiObj="$API.sys_log.pagedQuery"
:context-menus="['id', 'httpStatusCode', 'extraData', 'createdClientIp', 'os', 'createdUserAgent', 'createdTime']"
:context-opers="['view']"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:params="query"
:vue="this"
@row-click="rowClick"
ref="table"
remoteFilter
remoteSort
row-key="id"
stripe>
<el-table-column :label="$t('日志编号')" prop="id" sortable="custom" width="150"></el-table-column>
<el-table-column :label="$t('结果')" align="center" prop="httpStatusCode" sortable="custom" width="80">
<template #default="scope">
<sc-status-indicator :type="scope.row.httpStatusCode === 200 ? 'success' : 'danger'" />
{{ scope.row.httpStatusCode === 200 ? '成功' : '失败' }}
</template>
</el-table-column>
<el-table-column :label="$t('登录名')" prop="extraData" sortable="custom" width="200"></el-table-column>
<el-table-column :label="$t('客户端IP')" prop="createdClientIp" sortable="custom" width="200">
<template #default="scope">
<na-ip :ip="scope.row.createdClientIp"></na-ip>
</template>
</el-table-column>
<el-table-column :label="$t('操作系统')" prop="os" width="150"></el-table-column>
<el-table-column :label="$t('用户代理')" prop="createdUserAgent" show-overflow-tooltip sortable="custom"></el-table-column>
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" width="170"></el-table-column>
</sc-table>
</el-main>
</el-container>

View File

@ -1,131 +1,121 @@
<template>
<el-container>
<el-header>
<div class="left-panel">
<na-search
:controls="[
{
type: 'input',
field: ['filter', 'id'],
placeholder: '日志编号',
style: 'width:12rem',
},
{
multiple: true,
type: 'select',
field: ['dy', 'httpStatusCode'],
options: [
{ label: '20x', value: '200,299' },
{ label: '30x', value: '300,399' },
{ label: '40x', value: '400,499' },
{ label: '50x', value: '500,599' },
{ label: '90x', value: '900,999' },
],
placeholder: '状态码',
style: 'width:15rem',
},
{
type: 'cascader',
field: ['dy', 'apiId'],
api: $API.sys_api.query,
props: { label: 'summary', value: 'id', checkStrictly: true, expandTrigger: 'hover', emitPath: false },
placeholder: '请求服务',
style: 'width:20rem',
},
{
type: 'input',
field: ['dy', 'createdUserName'],
placeholder: '用户名',
style: 'width:12rem',
},
{
type: 'input',
field: ['dy', 'createdClientIp'],
placeholder: '客户端IP',
style: 'width:12rem',
},
]"
:vue="this"
@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"></div>
</el-header>
<el-main class="nopadding">
<el-container>
<el-header class="headerPublic">
<div class="left-panel">
<na-search
:controls="[
{
type: 'input',
field: ['filter', 'id'],
placeholder: '日志编号',
style: 'width:12rem',
},
{
multiple: true,
type: 'select',
field: ['dy', 'httpStatusCode'],
options: [
{ label: '20x', value: '200,299' },
{ label: '30x', value: '300,399' },
{ label: '40x', value: '400,499' },
{ label: '50x', value: '500,599' },
{ label: '90x', value: '900,999' },
],
placeholder: '状态码',
style: 'width:15rem',
},
{
type: 'cascader',
field: ['dy', 'apiId'],
api: $API.sys_api.query,
props: { label: 'summary', value: 'id', checkStrictly: true, expandTrigger: 'hover', emitPath: false },
placeholder: '请求服务',
style: 'width:20rem',
},
{
type: 'input',
field: ['dy', 'createdUserName'],
placeholder: '用户名',
style: 'width:12rem',
},
{
type: 'input',
field: ['dy', 'createdClientIp'],
placeholder: '客户端IP',
style: 'width:12rem',
},
]"
:vue="this"
@search="onSearch" />
</div>
<div class="right-panel"></div>
</el-header>
<el-main class="nopadding">
<sc-table
:apiObj="$API.sys_log.pagedQuery"
:context-menus="[
'id',
'httpStatusCode',
'apiId',
'createdUserName',
'method',
'duration',
'createdClientIp',
'os',
'createdTime',
]"
:context-opers="[]"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:params="query"
:vue="this"
@row-click="rowClick"
ref="table"
remoteFilter
remoteSort
row-key="id"
stripe>
<el-table-column :label="$t('日志编号')" align="center" prop="id" sortable="custom" width="150"></el-table-column>
<el-table-column :label="$t('响应码')" align="center" prop="httpStatusCode" sortable="custom" width="100">
<template #default="scope">
<div class="indicator">
<sc-status-indicator
:style="`background: #${Math.abs(this.$TOOL.crypto.hashCode(scope.row.httpStatusCode)).toString(16).substring(0, 6)}`" />
<span> {{ scope.row.httpStatusCode }}</span>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('请求服务')" align="center">
<el-table-column :label="$t('路径')" prop="apiId" show-overflow-tooltip sortable="custom">
<template #default="scope">
<p>{{ scope.row.apiId }}</p>
<p>{{ scope.row.apiSummary }}</p>
</template>
</el-table-column>
<el-table-column :label="$t('方法')" align="center" prop="method" sortable="custom" width="100">
<template #default="scope">
<div class="indicator">
<sc-status-indicator
:style="`background: #${Math.abs(this.$TOOL.crypto.hashCode(scope.row.method)).toString(16).substring(0, 6)}`" />
<span> {{ scope.row.method }}</span>
</div>
</template>
</el-table-column>
<el-table-column
:formatter="(row) => `${tool.groupSeparator((row.duration / 1000).toFixed(0))} ms`"
:label="$t('耗时')"
align="right"
prop="duration"
sortable="custom"
width="120">
</el-table-column>
</el-table-column>
<el-table-column :label="$t('用户名')" prop="createdUserName" sortable="custom" width="150">
<template #default="scope">
{{ scope.row.apiId === 'api/sys/user/pwd.login' ? scope.row.extraData : scope.row.createdUserName }}
</template>
</el-table-column>
<el-table-column :label="$t('客户端IP')" prop="createdClientIp" show-overflow-tooltip sortable="custom" width="200">
<template #default="scope">
<na-ip :ip="scope.row.createdClientIp"></na-ip>
</template>
</el-table-column>
<el-table-column :label="$t('操作系统')" align="center" prop="os" width="150"></el-table-column>
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" width="170"></el-table-column>
</sc-table>
</el-main>
</el-container>
<sc-table
:apiObj="$API.sys_log.pagedQuery"
:context-menus="['id', 'httpStatusCode', 'apiId', 'createdUserName', 'method', 'duration', 'createdClientIp', 'os', 'createdTime']"
:context-opers="[]"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:params="query"
:vue="this"
@row-click="rowClick"
ref="table"
remoteFilter
remoteSort
row-key="id"
stripe>
<el-table-column :label="$t('日志编号')" align="center" prop="id" sortable="custom" width="150"></el-table-column>
<el-table-column :label="$t('响应码')" align="center" prop="httpStatusCode" sortable="custom" width="100">
<template #default="scope">
<div class="indicator">
<sc-status-indicator
:style="`background: #${Math.abs(this.$TOOL.crypto.hashCode(scope.row.httpStatusCode)).toString(16).substring(0, 6)}`" />
<span> {{ scope.row.httpStatusCode }}</span>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('请求服务')" align="center">
<el-table-column :label="$t('路径')" prop="apiId" show-overflow-tooltip sortable="custom">
<template #default="scope">
<p>{{ scope.row.apiId }}</p>
<p>{{ scope.row.apiSummary }}</p>
</template>
</el-table-column>
<el-table-column :label="$t('方法')" align="center" prop="method" sortable="custom" width="100">
<template #default="scope">
<div class="indicator">
<sc-status-indicator
:style="`background: #${Math.abs(this.$TOOL.crypto.hashCode(scope.row.method)).toString(16).substring(0, 6)}`" />
<span> {{ scope.row.method }}</span>
</div>
</template>
</el-table-column>
<el-table-column
:formatter="(row) => `${tool.groupSeparator((row.duration / 1000).toFixed(0))} ms`"
:label="$t('耗时')"
align="right"
prop="duration"
sortable="custom"
width="120">
</el-table-column>
</el-table-column>
<el-table-column :label="$t('用户名')" prop="createdUserName" sortable="custom" width="150">
<template #default="scope">
{{ scope.row.apiId === 'api/sys/user/pwd.login' ? scope.row.extraData : scope.row.createdUserName }}
</template>
</el-table-column>
<el-table-column :label="$t('客户端IP')" prop="createdClientIp" show-overflow-tooltip sortable="custom" width="200">
<template #default="scope">
<na-ip :ip="scope.row.createdClientIp"></na-ip>
</template>
</el-table-column>
<el-table-column :label="$t('操作系统')" align="center" prop="os" width="150"></el-table-column>
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" width="170"></el-table-column>
</sc-table>
</el-main>
</el-container>
@ -153,7 +143,13 @@ export default {
return {
query: {
dynamicFilter: {
filters: [],
filters: [
{
field: 'createdTime',
operator: 'dateRange',
value: [tool.dateFormat(new Date(), 'yyyy-MM-dd'), tool.dateFormat(new Date(), 'yyyy-MM-dd')],
},
],
},
filter: {},
},
@ -162,7 +158,12 @@ export default {
},
}
},
mounted() {},
mounted() {
this.$refs.search.form.dy.createdTime = this.$refs.search.keepCreatedTime = [
`${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
`${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
]
},
methods: {
//
onSearch(form) {
@ -170,41 +171,35 @@ export default {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
operator: 'dateRange',
value: form.dy.createdTime,
value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')),
})
}
if (Array.isArray(form.dy.httpStatusCode) && form.dy.httpStatusCode.length !== 0) {
const filters = []
for (const code of form.dy.httpStatusCode) {
filters.push({
field: 'httpStatusCode',
operator: 'range',
value: code,
})
}
this.query.dynamicFilter.filters.push({
logic: 'or',
filters: filters,
filters: form.dy.httpStatusCode.map((x) => {
return { field: 'httpStatusCode', operator: 'range', value: x }
}),
})
}
if (form.dy.apiId) {
if (typeof form.dy.apiId === 'string' && form.dy.apiId.trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'apiId',
operator: 'contains',
operator: 'eq',
value: form.dy.apiId,
})
}
if (form.dy.createdUserName) {
if (typeof form.dy.createdUserName === 'string' && form.dy.createdUserName.trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'createdUserName',
operator: 'contains',
operator: 'eq',
value: form.dy.createdUserName,
})
}
if (form.dy.createdClientIp) {
if (typeof form.dy.createdClientIp === 'string' && form.dy.createdClientIp.trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'createdClientIp',
operator: 'contains',
operator: 'eq',
value: form.dy.createdClientIp,
})
}

View File

@ -1,5 +1,23 @@
<template>
<el-container>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
{
title: $t('消息类型'),
key: 'msgType',
options: [
{ label: $t('全部'), value: '' },
...Object.entries(this.$GLOBAL.enums.siteMsgTypes).map((x) => {
return { value: x[0], label: x[1][1] }
}),
],
},
]"
:label-width="6"
@on-change="filterChange"
ref="selectFilter"></sc-select-filter>
</el-header>
<el-header>
<div class="left-panel">
<na-search
@ -10,18 +28,11 @@
placeholder: $t('消息编号 / 消息主题 / 消息内容'),
style: 'width:20rem',
},
{
type: 'select',
field: ['dy', 'msgType'],
options: Object.entries(this.$GLOBAL.enums.siteMsgTypes).map((x) => {
return { value: x[0], label: x[1][1] }
}),
placeholder: $t('消息类型'),
style: 'width:15rem',
},
]"
:vue="this"
@search="onSearch" />
@reset="Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))"
@search="onSearch"
ref="search" />
</div>
<div class="right-panel">
<na-button-add :vue="this" />
@ -51,13 +62,14 @@
<na-col-avatar :label="$t('用户名')" prop="createdUserName" />
<na-col-indicator
:label="$t('消息类型')"
:options="[
{ text: '私信', type: 'success', value: 'private' },
{ text: '公告', type: 'warning', value: 'public' },
]"
:options="
Object.entries(this.$GLOBAL.enums.siteMsgTypes).map((x) => {
return { value: x[0], text: x[1][1] }
})
"
prop="msgType"
width="100"></na-col-indicator>
width="100">
</na-col-indicator>
<el-table-column :label="$t('消息主题')" prop="title" show-overflow-tooltip sortable="custom" />
<el-table-column :label="$t('消息摘要')" prop="summary" show-overflow-tooltip sortable="custom" />
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" width="170" />
@ -120,6 +132,12 @@ export default {
mounted() {},
created() {},
methods: {
filterChange(data) {
Object.entries(data).forEach(([key, value]) => {
this.$refs.search.form.dy[key] = value
})
this.$refs.search.search()
},
//
async rowDel(row) {
try {