feat: 框架代码同步

This commit is contained in:
tk 2024-12-10 14:49:58 +08:00 committed by nsnail
parent 9504c96f40
commit 725662693b
68 changed files with 694 additions and 399 deletions

View File

@ -117,4 +117,4 @@ biz-infra-->infra
| JavaScript | JavaScript解析器 | [Terser](https://github.com/terser/terser) | | JavaScript | JavaScript解析器 | [Terser](https://github.com/terser/terser) |
| JavaScript | 代码质量检查 | [ESLint](https://github.com/eslint/eslint) | | JavaScript | 代码质量检查 | [ESLint](https://github.com/eslint/eslint) |
| JavaScript | 代码格式化工具 | [Prettier](https://github.com/prettier/prettier) | | JavaScript | 代码格式化工具 | [Prettier](https://github.com/prettier/prettier) |
| JavaScript | 标准加密库 | [crypto-js](https://github.com/brix/crypto-js) | | JavaScript | 标准加密库 | [crypto-js](https://github.com/brix/crypto-js) |

View File

@ -3,7 +3,7 @@
"devDependencies": { "devDependencies": {
"cz-git": "^1.11.0", "cz-git": "^1.11.0",
"commitizen": "^4.3.1", "commitizen": "^4.3.1",
"prettier": "^3.4.1", "prettier": "^3.4.2",
"standard-version": "^9.5.0" "standard-version": "^9.5.0"
}, },
"config": { "config": {
@ -11,4 +11,4 @@
"path": "node_modules/cz-git" "path": "node_modules/cz-git"
} }
} }
} }

View File

@ -1,4 +1,4 @@
using NetAdmin.Domain.DbMaps.Sys; using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.Api; namespace NetAdmin.Domain.Dto.Sys.Api;

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Domain.Dto.Sys.JobRecord; namespace NetAdmin.Domain.Dto.Sys.JobRecord;
/// <summary> /// <summary>
/// 请求:编辑计划作业执行记录 /// 请求:编辑计划作业执行记录

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Domain.Dto.Sys.LoginLog; namespace NetAdmin.Domain.Dto.Sys.LoginLog;
/// <summary> /// <summary>
/// 请求:编辑登录日志 /// 请求:编辑登录日志

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Domain.Dto.Sys.RequestLog; namespace NetAdmin.Domain.Dto.Sys.RequestLog;
/// <summary> /// <summary>
/// 请求:编辑请求日志 /// 请求:编辑请求日志

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Domain.Dto.Sys.RequestLogDetail; namespace NetAdmin.Domain.Dto.Sys.RequestLogDetail;
/// <summary> /// <summary>
/// 请求:编辑请求日志明细 /// 请求:编辑请求日志明细

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Domain.Dto.Sys.SiteMsgDept; namespace NetAdmin.Domain.Dto.Sys.SiteMsgDept;
/// <summary> /// <summary>
/// 请求:编辑站内信-部门映射 /// 请求:编辑站内信-部门映射

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Domain.Dto.Sys.SiteMsgFlag; namespace NetAdmin.Domain.Dto.Sys.SiteMsgFlag;
/// <summary> /// <summary>
/// 请求:编辑站内信标记 /// 请求:编辑站内信标记

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Domain.Dto.Sys.SiteMsgRole; namespace NetAdmin.Domain.Dto.Sys.SiteMsgRole;
/// <summary> /// <summary>
/// 请求:编辑站内信-角色映射 /// 请求:编辑站内信-角色映射

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Domain.Dto.Sys.SiteMsgUser; namespace NetAdmin.Domain.Dto.Sys.SiteMsgUser;
/// <summary> /// <summary>
/// 请求:编辑站内信-用户映射 /// 请求:编辑站内信-用户映射

View File

@ -0,0 +1,8 @@
using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.UserRole;
/// <summary>
/// 请求:创建用户-角色映射
/// </summary>
public record CreateUserRoleReq : Sys_UserRole;

View File

@ -0,0 +1,6 @@
namespace NetAdmin.Domain.Dto.Sys.UserRole;
/// <summary>
/// 请求:编辑用户-角色映射
/// </summary>
public sealed record EditUserRoleReq : CreateUserRoleReq;

View File

@ -0,0 +1,8 @@
using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.UserRole;
/// <summary>
/// 请求:查询用户-角色映射
/// </summary>
public sealed record QueryUserRoleReq : Sys_UserRole;

View File

@ -0,0 +1,13 @@
using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.UserRole;
/// <summary>
/// 响应:查询用户-角色映射
/// </summary>
public sealed record QueryUserRoleRsp : Sys_UserRole
{
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
}

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Domain.Dto.Sys.VerifyCode; namespace NetAdmin.Domain.Dto.Sys.VerifyCode;
/// <summary> /// <summary>
/// 请求:编辑验证码 /// 请求:编辑验证码

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Domain.Dto.Tpl.Example; namespace NetAdmin.Domain.Dto.Tpl.Example;
/// <summary> /// <summary>
/// 请求:编辑示例 /// 请求:编辑示例

View File

@ -5,7 +5,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CronExpressionDescriptor" Version="2.36.0"/> <PackageReference Include="CronExpressionDescriptor" Version="2.36.0"/>
<PackageReference Include="Cronos" Version="0.8.4"/> <PackageReference Include="Cronos" Version="0.9.0"/>
<PackageReference Include="NetAdmin.CsvHelper" Version="1.0.0"/> <PackageReference Include="NetAdmin.CsvHelper" Version="1.0.0"/>
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14"/> <PackageReference Include="Yitter.IdGenerator" Version="1.0.14"/>
</ItemGroup> </ItemGroup>

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Infrastructure.Enums; namespace NetAdmin.Infrastructure.Enums;
/// <summary> /// <summary>
/// 枚举扩展方法 /// 枚举扩展方法

View File

@ -1,4 +1,4 @@
namespace NetAdmin.Infrastructure.Extensions; namespace NetAdmin.Infrastructure.Extensions;
/// <summary> /// <summary>
/// CountryCodes 扩展方法 /// CountryCodes 扩展方法

View File

@ -1,4 +1,5 @@
using NetAdmin.Domain.Dto.Sys.Role; using NetAdmin.Domain.Dto.Sys.Role;
using NetAdmin.Domain.Dto.Sys.UserRole;
namespace NetAdmin.SysComponent.Application.Modules.Sys; namespace NetAdmin.SysComponent.Application.Modules.Sys;
@ -25,4 +26,9 @@ public interface IRoleModule : ICrudModule<CreateRoleReq, QueryRoleRsp // 创建
/// 设置是否忽略权限控制 /// 设置是否忽略权限控制
/// </summary> /// </summary>
Task<int> SetIgnorePermissionControlAsync(SetIgnorePermissionControlReq req); Task<int> SetIgnorePermissionControlAsync(SetIgnorePermissionControlReq req);
/// <summary>
/// 角色用户映射分组计数
/// </summary>
Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> UserCountByAsync(QueryReq<QueryUserRoleReq> req);
} }

View File

@ -0,0 +1,12 @@
using NetAdmin.Domain.Dto.Sys.UserRole;
namespace NetAdmin.SysComponent.Application.Modules.Sys;
/// <summary>
/// 用户-角色映射模块
/// </summary>
public interface IUserRoleModule : ICrudModule<CreateUserRoleReq, QueryUserRoleRsp // 创建类型
, EditUserRoleReq // 编辑类型
, QueryUserRoleReq, QueryUserRoleRsp // 查询类型
, DelReq // 删除类型
>;

View File

@ -1,3 +1,3 @@
<wpf:ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xml:space="preserve"> <wpf:ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xml:space="preserve">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Csys_005Csession/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Csys_005Csession/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary> </wpf:ResourceDictionary>

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
/// <summary>
/// 用户角-色映射服务
/// </summary>
public interface IUserRoleService : IService, IUserRoleModule;

View File

@ -1,10 +1,11 @@
using NetAdmin.Domain.DbMaps.Sys; using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys.Role; using NetAdmin.Domain.Dto.Sys.Role;
using NetAdmin.Domain.Dto.Sys.UserRole;
namespace NetAdmin.SysComponent.Application.Services.Sys; namespace NetAdmin.SysComponent.Application.Services.Sys;
/// <inheritdoc cref="IRoleService" /> /// <inheritdoc cref="IRoleService" />
public sealed class RoleService(BasicRepository<Sys_Role, long> rpo) // public sealed class RoleService(BasicRepository<Sys_Role, long> rpo, IUserRoleService userRoleService) //
: RepositoryService<Sys_Role, long, IRoleService>(rpo), IRoleService : RepositoryService<Sys_Role, long, IRoleService>(rpo), IRoleService
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -134,6 +135,13 @@ public sealed class RoleService(BasicRepository<Sys_Role, long> rpo) //
return UpdateAsync(req, [nameof(req.IgnorePermissionControl)]); return UpdateAsync(req, [nameof(req.IgnorePermissionControl)]);
} }
/// <inheritdoc />
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> UserCountByAsync(QueryReq<QueryUserRoleReq> req)
{
req.ThrowIfInvalid();
return userRoleService.CountByAsync(req);
}
private ISelect<Sys_Role> QueryInternal(QueryReq<QueryRoleReq> req) private ISelect<Sys_Role> QueryInternal(QueryReq<QueryRoleReq> req)
{ {
#pragma warning disable RCS1196 #pragma warning disable RCS1196

View File

@ -0,0 +1,119 @@
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys.UserRole;
namespace NetAdmin.SysComponent.Application.Services.Sys;
/// <inheritdoc cref="IUserRoleService" />
public sealed class UserRoleService(BasicRepository<Sys_UserRole, long> rpo) //
: RepositoryService<Sys_UserRole, long, IUserRoleService>(rpo), IUserRoleService
{
/// <inheritdoc />
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
req.ThrowIfInvalid();
var ret = 0;
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var item in req.Items) {
ret += await DeleteAsync(item).ConfigureAwait(false);
}
return ret;
}
/// <inheritdoc />
public Task<long> CountAsync(QueryReq<QueryUserRoleReq> req)
{
req.ThrowIfInvalid();
return QueryInternal(req).WithNoLockNoWait().CountAsync();
}
/// <inheritdoc />
public async Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryUserRoleReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req with { Order = Orders.None })
.WithNoLockNoWait()
.GroupBy(req.GetToListExp<Sys_UserRole>())
.ToDictionaryAsync(a => a.Count())
.ConfigureAwait(false);
return ret.Select(x => new KeyValuePair<IImmutableDictionary<string, string>, int>(
req.RequiredFields.ToImmutableDictionary(y => y, y => typeof(Sys_UserRole).GetProperty(y)!.GetValue(x.Key)!.ToString())
, x.Value))
.OrderByDescending(x => x.Value);
}
/// <inheritdoc />
public async Task<QueryUserRoleRsp> CreateAsync(CreateUserRoleReq req)
{
req.ThrowIfInvalid();
var ret = await Rpo.InsertAsync(req).ConfigureAwait(false);
return ret.Adapt<QueryUserRoleRsp>();
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
req.ThrowIfInvalid();
return Rpo.DeleteAsync(a => a.Id == req.Id);
}
/// <inheritdoc />
public Task<QueryUserRoleRsp> EditAsync(EditUserRoleReq req)
{
req.ThrowIfInvalid();
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryUserRoleReq> req)
{
req.ThrowIfInvalid();
throw new NotImplementedException();
}
/// <inheritdoc />
public async Task<QueryUserRoleRsp> GetAsync(QueryUserRoleReq req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(new QueryReq<QueryUserRoleReq> { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false);
return ret.Adapt<QueryUserRoleRsp>();
}
/// <inheritdoc />
public async Task<PagedQueryRsp<QueryUserRoleRsp>> PagedQueryAsync(PagedQueryReq<QueryUserRoleReq> req)
{
req.ThrowIfInvalid();
var list = await QueryInternal(req).Page(req.Page, req.PageSize).WithNoLockNoWait().Count(out var total).ToListAsync().ConfigureAwait(false);
return new PagedQueryRsp<QueryUserRoleRsp>(req.Page, req.PageSize, total, list.Adapt<IEnumerable<QueryUserRoleRsp>>());
}
/// <inheritdoc />
public async Task<IEnumerable<QueryUserRoleRsp>> QueryAsync(QueryReq<QueryUserRoleReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req).WithNoLockNoWait().Take(req.Count).ToListAsync().ConfigureAwait(false);
return ret.Adapt<IEnumerable<QueryUserRoleRsp>>();
}
private ISelect<Sys_UserRole> QueryInternal(QueryReq<QueryUserRoleReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
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);
if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) {
ret = ret.OrderByDescending(a => a.Id);
}
return ret;
}
}

View File

@ -1,4 +1,5 @@
using NetAdmin.Domain.Dto.Sys.Role; using NetAdmin.Domain.Dto.Sys.Role;
using NetAdmin.Domain.Dto.Sys.UserRole;
namespace NetAdmin.SysComponent.Cache.Sys; namespace NetAdmin.SysComponent.Cache.Sys;
@ -83,4 +84,10 @@ public sealed class RoleCache(IDistributedCache cache, IRoleService service) //
{ {
return Service.SetIgnorePermissionControlAsync(req); return Service.SetIgnorePermissionControlAsync(req);
} }
/// <inheritdoc />
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> UserCountByAsync(QueryReq<QueryUserRoleReq> req)
{
return Service.UserCountByAsync(req);
}
} }

View File

@ -1,4 +1,5 @@
using NetAdmin.Domain.Dto.Sys.Role; using NetAdmin.Domain.Dto.Sys.Role;
using NetAdmin.Domain.Dto.Sys.UserRole;
namespace NetAdmin.SysComponent.Host.Controllers.Sys; namespace NetAdmin.SysComponent.Host.Controllers.Sys;
@ -116,4 +117,12 @@ public sealed class RoleController(IRoleCache cache) : ControllerBase<IRoleCache
{ {
return Cache.SetIgnorePermissionControlAsync(req); return Cache.SetIgnorePermissionControlAsync(req);
} }
/// <summary>
/// 角色用户映射分组计数
/// </summary>
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> UserCountByAsync(QueryReq<QueryUserRoleReq> req)
{
return Cache.UserCountByAsync(req);
}
} }

View File

@ -1,4 +1,5 @@
using NetAdmin.Domain.Dto.Sys.Role; using NetAdmin.Domain.Dto.Sys.Role;
using NetAdmin.Domain.Dto.Sys.UserRole;
namespace UnitTests.Sys; namespace UnitTests.Sys;
@ -139,4 +140,14 @@ public class RoleTests(WebTestApplicationFactory<Startup> factory, ITestOutputHe
Assert.True(rsp.IsSuccessStatusCode); Assert.True(rsp.IsSuccessStatusCode);
return default; return default;
} }
/// <inheritdoc />
[InlineData(default)]
[Theory]
public async Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> UserCountByAsync(QueryReq<QueryUserRoleReq> req)
{
var rsp = await PostJsonAsync(typeof(RoleController), req);
Assert.True(rsp.IsSuccessStatusCode);
return default;
}
} }

View File

@ -11,19 +11,19 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.3.1", "@element-plus/icons-vue": "2.3.1",
"ace-builds": "1.36.5", "ace-builds": "1.36.5",
"aieditor": "1.2.7", "aieditor": "1.2.8",
"axios": "1.7.8", "axios": "1.7.9",
"crypto-js": "4.2.0", "crypto-js": "4.2.0",
"echarts": "5.5.1", "echarts": "5.5.1",
"element-plus": "2.8.8", "element-plus": "2.9.0",
"json-bigint": "1.0.0", "json-bigint": "1.0.0",
"markdown-it": "14.1.0", "markdown-it": "14.1.0",
"markdown-it-emoji": "3.0.0", "markdown-it-emoji": "3.0.0",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"sortablejs": "1.15.4", "sortablejs": "1.15.6",
"vkbeautify": "0.99.3", "vkbeautify": "0.99.3",
"vue": "3.5.13", "vue": "3.5.13",
"vue-i18n": "10.0.4", "vue-i18n": "10.0.5",
"vue-router": "4.5.0", "vue-router": "4.5.0",
"vue3-ace-editor": "2.2.4", "vue3-ace-editor": "2.2.4",
"vue3-json-viewer": "2.2.2", "vue3-json-viewer": "2.2.2",
@ -32,11 +32,11 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "5.2.1", "@vitejs/plugin-vue": "5.2.1",
"prettier": "3.4.1", "prettier": "3.4.2",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"sass": "1.81.0", "sass": "1.82.0",
"terser": "5.36.0", "terser": "5.37.0",
"vite": "6.0.1" "vite": "6.0.3"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",

View File

@ -147,4 +147,15 @@ export default {
return await http.post(this.url, data, config) return await http.post(this.url, data, config)
}, },
}, },
/**
* 角色用户映射分组计数
*/
userCountBy: {
url: `${config.API_URL}/api/sys/role/user.count.by`,
name: `角色用户映射分组计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
} }

View File

@ -1,13 +1,3 @@
<!--
* @Descripttion: scContextmenu组件
* @version: 1.1
* @Author: sakuya
* @Date: 2021年7月23日09:25:57
* @LastEditors: sakuya
* @LastEditTime: 2022年5月30日20:17:42
* @other: 代码完全开源欢迎参考也欢迎PR
-->
<template> <template>
<transition name="el-zoom-in-top"> <transition name="el-zoom-in-top">
<div v-if="visible" :style="{ left: left + 'px', top: top + 'px' }" @contextmenu.prevent="fun" class="sc-contextmenu" ref="contextmenu"> <div v-if="visible" :style="{ left: left + 'px', top: top + 'px' }" @contextmenu.prevent="fun" class="sc-contextmenu" ref="contextmenu">

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: scContextmenuItem组件
* @version: 1.3
* @Author: sakuya
* @Date: 2021年7月23日16:29:36
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-18 12:59:06
-->
<template> <template>
<hr v-if="divided" /> <hr v-if="divided" />
<li :class="disabled ? 'disabled' : ''" @click.stop="liClick" @mouseenter="openSubmenu($event)" @mouseleave="closeSubmenu($event)"> <li :class="disabled ? 'disabled' : ''" @click.stop="liClick" @mouseenter="openSubmenu($event)" @mouseleave="closeSubmenu($event)">

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: cron规则生成器
* @version: 1.0
* @Author: sakuya
* @Date: 2021年12月29日15:23:54
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-18 13:04:08
-->
<template> <template>
<el-input v-bind="$attrs" v-model="defaultValue"> <el-input v-bind="$attrs" v-model="defaultValue">
<template #append> <template #append>

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 弹窗扩展组件
* @version: 2.0
* @Author: sakuya
* @Date: 2021年8月27日08:51:52
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-18 13:04:33
-->
<template> <template>
<div class="sc-dialog" ref="scDialog"> <div class="sc-dialog" ref="scDialog">
<el-dialog v-bind="$attrs" v-model="dialogVisible" :fullscreen="isFullscreen" :show-close="false" draggable ref="dialog"> <el-dialog v-bind="$attrs" v-model="dialogVisible" :fullscreen="isFullscreen" :show-close="false" draggable ref="dialog">

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 文件导出
* @version: 1.1
* @Author: sakuya
* @Date: 2022年5月24日16:20:12
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-19 11:59:39
-->
<template> <template>
<slot :open="open"> <slot :open="open">
<el-button @click="open" plain type="primary">{{ $t('导出') }}</el-button> <el-button @click="open" plain type="primary">{{ $t('导出') }}</el-button>

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 文件导入
* @version: 1.0
* @Author: sakuya
* @Date: 2022年5月24日11:30:03
* @LastEditors:
* @LastEditTime:
-->
<template> <template>
<slot :open="open"> <slot :open="open">
<el-button @click="open" plain type="primary">{{ $t('导入') }}</el-button> <el-button @click="open" plain type="primary">{{ $t('导入') }}</el-button>

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 资源文件选择器
* @version: 1.0
* @Author: sakuya
* @Date: 2021年10月11日16:01:40
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-19 11:44:42
-->
<template> <template>
<div class="sc-file-select"> <div class="sc-file-select">
<div v-loading="menuLoading" class="sc-file-select__side"> <div v-loading="menuLoading" class="sc-file-select__side">

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 表单表格
* @version: 1.3
* @Author: sakuya
* @Date: 2023年2月9日12:32:26
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-18 13:08:27
-->
<template> <template>
<div class="sc-form-table" ref="scFormTable"> <div class="sc-form-table" ref="scFormTable">
<el-table :data="data" border ref="table" stripe> <el-table :data="data" border ref="table" stripe>

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 图标选择器组件
* @version: 2.0
* @Author: sakuya
* @Date: 2021年7月27日10:02:46
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-18 13:08:59
-->
<template> <template>
<div class="sc-icon-select"> <div class="sc-icon-select">
<div :class="{ hasValue: value }" @click="open" class="sc-icon-select__wrapper"> <div :class="{ hasValue: value }" @click="open" class="sc-icon-select__wrapper">

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 状态指示器
* @version: 1.0
* @Author: sakuya
* @Date: 2021年11月11日09:30:12
* @LastEditors:
* @LastEditTime:
-->
<template> <template>
<span :class="[{ 'sc-status-processing': pulse }, 'sc-state-bg--' + type]" class="sc-state"></span> <span :class="[{ 'sc-status-processing': pulse }, 'sc-state-bg--' + type]" class="sc-state"></span>
</template> </template>

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 趋势标记
* @version: 1.0
* @Author: sakuya
* @Date: 2021年11月11日11:07:10
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-19 11:46:37
-->
<template> <template>
<span :class="'sc-trend--' + type" class="sc-trend"> <span :class="'sc-trend--' + type" class="sc-trend">
<el-icon v-if="iconType === 'P'" class="sc-trend-icon"><el-icon-top /></el-icon> <el-icon v-if="iconType === 'P'" class="sc-trend-icon"><el-icon-top /></el-icon>

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 页面头部样式组件
* @version: 1.0
* @Author: sakuya
* @Date: 2021年7月20日08:49:07
* @LastEditors:
* @LastEditTime:
-->
<template> <template>
<div class="sc-page-header"> <div class="sc-page-header">
<div v-if="icon" class="sc-page-header__icon"> <div v-if="icon" class="sc-page-header__icon">

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 密码强度检测
* @version: 1.0
* @Author: sakuya
* @Date: 2022年6月2日15:36:01
* @LastEditors:
* @LastEditTime:
-->
<template> <template>
<div class="sc-password-strength"> <div class="sc-password-strength">
<div :class="`sc-password-strength-level-${level}`" class="sc-password-strength-bar"></div> <div :class="`sc-password-strength-level-${level}`" class="sc-password-strength-bar"></div>

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 异步选择器
* @version: 1.1
* @Author: sakuya
* @Date: 2021年8月3日15:53:37
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-18 13:09:37
-->
<template> <template>
<div class="sc-select"> <div class="sc-select">
<div v-if="initLoading" class="sc-select-loading"> <div v-if="initLoading" class="sc-select-loading">

View File

@ -1,18 +1,14 @@
<!--
* @Descripttion: 分类筛选器
* @version: 1.0
* @Author: sakuya
* @Date: 2022年5月26日15:59:52
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-18 13:09:49
-->
<template> <template>
<div class="sc-select-filter"> <div class="sc-select-filter">
<div v-if="data.length <= 0" class="sc-select-filter__no-data">{{ $t('暂无数据') }}</div> <div v-if="data.length <= 0" class="sc-select-filter__no-data">{{ $t('暂无数据') }}</div>
<div v-for="item in data" :class="`sc-select-filter__item${item.w100p ? ' sc-select-filter__item-w100p' : ''}`" :key="item.key"> <div v-for="item in data" :class="`sc-select-filter__item${item.w100p ? ' sc-select-filter__item-w100p' : ''}`" :key="item.key">
<div :style="{ width: labelWidth + 'rem' }" class="sc-select-filter__item-title"> <div :style="{ width: labelWidth + 'rem' }" @click="autoHeight" class="sc-select-filter__item-title">
<label>{{ item.title }}</label> <label>
<span>{{ item.title }}</span>
<el-icon style="display: none">
<el-icon-arrow-up></el-icon-arrow-up>
</el-icon>
</label>
</div> </div>
<div class="sc-select-filter__item-options"> <div class="sc-select-filter__item-options">
<ul> <ul>
@ -20,15 +16,12 @@
v-for="option in item.options" v-for="option in item.options"
:class="{ active: selected[item.key] && selected[item.key].includes(option.value) }" :class="{ active: selected[item.key] && selected[item.key].includes(option.value) }"
:key="option.value" :key="option.value"
:title="option.title"
@click="select(option, item)"> @click="select(option, item)">
<el-icon v-if="option.icon"> <el-icon v-if="option.icon">
<component :is="option.icon" /> <component :is="option.icon" />
</el-icon> </el-icon>
<el-badge <el-badge :max="item.badgeMax ?? 999999999" :show-zero="false" :value="option.badge" badge-class="badge-small">
:max="item.badgeMax ?? 999999999"
:show-zero="false"
:type="option.badgeType ? option.badgeType : option.badge > 1 ? 'danger' : 'info'"
:value="option.badge">
<span>{{ option.label }}</span> <span>{{ option.label }}</span>
</el-badge> </el-badge>
</li> </li>
@ -54,6 +47,10 @@ export default {
data() { data() {
return { return {
selected: {}, selected: {},
svgIconUp:
'<svg data-v-a30f2a47="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="m488.832 344.32-339.84 356.672a32 32 0 0 0 0 44.16l.384.384a29.44 29.44 0 0 0 42.688 0l320-335.872 319.872 335.872a29.44 29.44 0 0 0 42.688 0l.384-.384a32 32 0 0 0 0-44.16L535.168 344.32a32 32 0 0 0-46.336 0"></path></svg>',
svgIconDown:
'<svg data-v-a30f2a47="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="M831.872 340.864 512 652.672 192.128 340.864a30.592 30.592 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728 30.592 30.592 0 0 0-42.752 0z"></path></svg>',
} }
}, },
watch: { watch: {
@ -63,6 +60,25 @@ export default {
// this.selectedValues[item.key] || (Array.isArray(item.options) && item.options.length) ? [item.options[0].value] : [] // this.selectedValues[item.key] || (Array.isArray(item.options) && item.options.length) ? [item.options[0].value] : []
// }) // })
// }, // },
data: {
immediate: true,
handler() {
this.$nextTick(() => {
for (const el of document.getElementsByClassName('sc-select-filter__item-options')) {
if (el.children[0].clientHeight / el.clientHeight < 1.5) {
el.previousSibling.children[0].children[1].style.display = 'none'
el.previousSibling.children[0].style.cursor = 'unset'
el.style.height = '3.5rem'
} else {
el.previousSibling.children[0].style.cursor = 'pointer'
el.previousSibling.children[0].children[1].style.display = 'unset'
el.previousSibling.children[0].children[1].innerHTML = this.svgIconUp
}
}
})
},
},
}, },
computed: { computed: {
selectedString() { selectedString() {
@ -81,6 +97,16 @@ export default {
}) })
}, },
methods: { methods: {
autoHeight(e) {
if (e.currentTarget.nextSibling.style.height === 'auto') {
e.currentTarget.nextSibling.style.height = '3.5rem'
e.currentTarget.children[0].children[1].innerHTML = this.svgIconUp
} else {
if (e.currentTarget.nextSibling.children[0].clientHeight / e.currentTarget.nextSibling.clientHeight < 1.5) return
e.currentTarget.nextSibling.style.height = 'auto'
e.currentTarget.children[0].children[1].innerHTML = this.svgIconDown
}
},
select(option, item) { select(option, item) {
// //
if (item.multiple) { if (item.multiple) {
@ -153,13 +179,18 @@ export default {
.sc-select-filter__item-title label { .sc-select-filter__item-title label {
font-size: 1.1rem; font-size: 1.1rem;
padding-top: 1rem; padding-top: 1rem;
display: inline-block; display: flex;
align-items: center;
justify-content: space-between;
padding-right: 1rem;
color: #999; color: #999;
} }
.sc-select-filter__item-options { .sc-select-filter__item-options {
flex: 1; flex: 1;
border-bottom: 1px dashed var(--el-border-color-light); border-bottom: 1px dashed var(--el-border-color-light);
height: 3.5rem;
overflow: hidden;
} }
.sc-select-filter__item-options ul { .sc-select-filter__item-options ul {
@ -202,7 +233,14 @@ export default {
.sc-select-filter__no-data { .sc-select-filter__no-data {
color: #999; color: #999;
} }
.sc-select-filter__item-w100p { .sc-select-filter__item-w100p {
width: 100%; width: 100%;
} }
</style>
<style>
.badge-small {
font-size: 0.8rem;
height: 1.3rem;
}
</style> </style>

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 统计数值组件
* @version: 1.1
* @Author: sakuya
* @Date: 2021年6月23日13:11:32
* @LastEditors: sakuya
* @LastEditTime: 2022年5月14日19:55:09
-->
<template> <template>
<div class="sc-statistic"> <div class="sc-statistic">
<div class="sc-statistic-title"> <div class="sc-statistic-title">

View File

@ -1,11 +1,3 @@
<!--
* @Description: 数据表格组件
* @version: 1.11
* @Author: sakuya
* @Date: 2021年11月29日21:51:15
* @LastEditors: sakuya
* @LastEditTime: 2023年3月2日10:43:35
-->
<template> <template>
<div v-loading="loading" :style="{ height: _height }" class="scTable" ref="scTableMain"> <div v-loading="loading" :style="{ height: _height }" class="scTable" ref="scTableMain">
<div :style="{ height: _table_height }" class="scTable-table"> <div :style="{ height: _table_height }" class="scTable-table">

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 表格选择器组件
* @version: 1.3
* @Author: sakuya
* @Date: 2021年6月10日10:04:07
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-18 13:12:15
-->
<template> <template>
<el-select <el-select
v-model="defaultValue" v-model="defaultValue"

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 局部水印组件
* @version: 1.1
* @Author: sakuya
* @Date: 2021年12月18日12:16:16
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-18 13:14:19
-->
<template> <template>
<div class="sc-water-mark" ref="scWaterMark"> <div class="sc-water-mark" ref="scWaterMark">
<slot></slot> <slot></slot>

View File

@ -1,12 +1,3 @@
<!--
* @Descripttion: 处理iframe持久化涉及store(VUEX)
* @version: 1.0
* @Author: sakuya
* @Date: 2021年6月30日13:20:41
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-19 11:52:34
-->
<template> <template>
<div v-show="$route.meta.type === 'iframe'" class="iframe-pages"> <div v-show="$route.meta.type === 'iframe'" class="iframe-pages">
<iframe <iframe

View File

@ -1,154 +0,0 @@
<template>
<el-container v-loading="loading">
<el-main>
<el-empty v-if="jobs.length === 0" :image-size="120">
<template #description>
<h2>{{ $t('没有正在执行的作业') }}</h2>
</template>
<p style="color: #999; line-height: 1.5; margin: 0 3rem">
在处理耗时过久的作业时为了不阻碍正在处理的工作可在作业中心进行异步执行
</p>
</el-empty>
<el-card v-for="job in jobs" :class="`user-bar-jobs-item ${job.lastStatusCode === 'oK' ? '' : 'alert'}`" :key="job.id" shadow="hover">
<div class="user-bar-jobs-item-body">
<div class="jobIcon">
{{ job.lastStatusCode?.toUpperCase() }}
</div>
<div class="jobMain">
<div class="title">
<h2>{{ job.jobName }}</h2>
<p>{{ $t('上次执行:') }}<span v-time.tip="job.lastExecTime" :title="job.lastExecTime"></span></p>
<p>
下次执行<span>{{ job.nextExecTime }}</span>
</p>
</div>
<div class="bottom">
<div class="status">
<el-tag v-if="job.status === 'running'" type="warning">{{ $t('执行中') }}</el-tag>
<el-tag v-if="job.status === 'idle'" :type="job.lastStatusCode === 'oK' ? 'primary' : 'danger'">{{
$t('空闲')
}}</el-tag>
</div>
<div class="handler">
<el-button
:type="job.lastStatusCode === 'oK' ? 'primary' : 'danger'"
@click="view(job)"
circle
icon="el-icon-view"></el-button>
</div>
</div>
</div>
</div>
</el-card>
</el-main>
<el-footer class="flex" style="justify-content: space-evenly; height: unset">
<div>
<el-badge :hidden="fail === 0" :value="fail">
<el-button @click="gotoJob">{{ $t('异常作业') }}</el-button>
</el-badge>
</div>
<div style="text-align: right; flex-grow: 1">
<el-button @click="refresh" circle icon="el-icon-refresh"></el-button>
</div>
</el-footer>
</el-container>
<save-dialog v-if="dialog.save" @closed="dialog.save = null" @mounted="$refs.saveDialog.open(dialog.save)" ref="saveDialog"></save-dialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
const saveDialog = defineAsyncComponent(() => import('@/views/sys/job/all/save.vue'))
export default {
components: {
saveDialog,
},
data() {
return {
dialog: {},
loading: false,
jobs: [],
}
},
emits: ['closed'],
inject: ['reload'],
methods: {
gotoJob() {
this.$router.push({ path: '/sys/job', query: { view: 'fail' } })
this.$emit('closed')
},
async getData() {
this.loading = true
const res = await this.$API.sys_job.query.post({ prop: 'lastStatusCode', order: 'descending' })
this.jobs = res.data
this.loading = false
},
refresh() {
this.getData()
},
async view(job) {
this.dialog.save = { mode: 'view', row: { id: job.id }, tabId: 'record' }
},
},
mounted() {
this.getData()
},
props: {
fail: { type: Number, default: 0 },
},
watch: {},
}
</script>
<style scoped>
.user-bar-jobs-item {
margin-bottom: 0.5rem;
}
.user-bar-jobs-item:hover {
border-color: var(--na-color-primary);
}
.user-bar-jobs-item.alert:hover {
border-color: var(--el-color-danger);
}
.user-bar-jobs-item-body {
display: flex;
}
.user-bar-jobs-item-body .jobIcon {
width: 3rem;
height: 3rem;
background: var(--el-color-primary-light-9);
margin-right: 2rem;
display: flex;
justify-content: center;
align-items: center;
color: var(--na-color-primary);
border-radius: 1.5rem;
}
.user-bar-jobs-item-body .jobMain {
flex: 1;
}
.user-bar-jobs-item-body .title h2 {
font-size: 1rem;
}
.user-bar-jobs-item-body .title p {
font-size: 1rem;
color: #999;
margin-top: 0.5rem;
}
.user-bar-jobs-item-body .bottom {
display: flex;
justify-content: space-between;
align-items: center;
}
.user-bar-jobs-item.alert .jobIcon {
background: var(--el-color-danger-light-9);
color: var(--el-color-danger);
}
</style>

View File

@ -0,0 +1,261 @@
<template>
<el-container v-loading="loading">
<el-main>
<el-empty v-if="jobs.length === 0" :image-size="120">
<template #description>
<h2>{{ $t('没有正在执行的作业') }}</h2>
</template>
<p style="color: #999; line-height: 1.5; margin: 0 3rem">
在处理耗时过久的作业时为了不阻碍正在处理的工作可在作业中心进行异步执行
</p>
</el-empty>
<el-row :gutter="10">
<el-col :lg="12">
<el-card
v-for="job in jobs"
:class="`user-bar-jobs-item ${job.lastStatusCode === 'oK' ? '' : 'alert'}`"
:key="job.id"
shadow="hover">
<div class="user-bar-jobs-item-body">
<div class="jobIcon">
{{ job.lastStatusCode?.toUpperCase().slice(0, 2) }}
</div>
<div class="jobMain">
<div class="title">
<h2>{{ job.jobName }}</h2>
<p>{{ $t('上次执行:') }}<span v-time.tip="job.lastExecTime" :title="job.lastExecTime"></span></p>
<p>
下次执行<span>{{ job.nextExecTime }}</span>
</p>
</div>
<div class="bottom">
<div class="status">
<el-tag v-if="job.status === 'running'" type="warning">{{ $t('执行中') }}</el-tag>
<el-tag v-if="job.status === 'idle'" :type="job.lastStatusCode === 'oK' ? 'primary' : 'danger'"
>{{ $t('空闲') }}
</el-tag>
</div>
<div class="handler">
<el-button
:type="job.lastStatusCode === 'oK' ? 'primary' : 'danger'"
@click="dialog.jobSave = { mode: 'view', row: { id: job.id }, tabId: 'record' }"
circle
icon="el-icon-view"></el-button>
</div>
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :lg="12">
<el-empty v-if="!failJobs" description="未发现新的异常作业"></el-empty>
<el-card v-else v-for="job in failJobs" :class="`user-bar-jobs-item alert`" :key="job.job.id" shadow="hover">
<div class="user-bar-jobs-item-body">
<div class="jobIcon">
{{ job.httpStatusCode.toUpperCase().slice(0, 2) }}
</div>
<div class="jobMain">
<div class="title">
<h2>{{ job.job.jobName }}</h2>
<p>{{ $t('出错时间:') }}<span v-time.tip="job.createdTime" :title="job.createdTime"></span></p>
<p>
执行耗时<span>{{ $TOOL.groupSeparator(job.duration) }} ms</span>
</p>
</div>
<div class="bottom">
<div class="status failJobs">
{{ job.responseBody }}
</div>
<div class="handler">
<el-button
@click="dialog.jobRecordSave = { mode: 'view', row: { id: job.id } }"
circle
icon="el-icon-view"
type="danger"></el-button>
</div>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</el-main>
<el-footer class="flex" style="justify-content: space-evenly; height: unset">
<div>
<el-badge :hidden="jobsCnt === 0" :value="jobsCnt">
<el-button @click="dialog.save = { tabId: 'all' }">{{ $t('作业管理') }}</el-button>
</el-badge>
</div>
<el-button @click="refresh" circle icon="el-icon-refresh"></el-button>
<div v-if="failJobs">
<el-badge :hidden="fail === 0" :value="fail">
<el-button @click="dialog.save = { tabId: 'fail' }" plain type="danger">{{ $t('异常日志') }}</el-button>
</el-badge>
</div>
</el-footer>
</el-container>
<jobSaveDialog
v-if="dialog.jobSave"
@closed="dialog.jobSave = null"
@mounted="$refs.jobSaveDialog.open(dialog.jobSave)"
ref="jobSaveDialog"></jobSaveDialog>
<jobRecordSaveDialog
v-if="dialog.jobRecordSave"
@closed="dialog.jobRecordSave = null"
@mounted="$refs.jobRecordSaveDialog.open(dialog.jobRecordSave)"
ref="jobRecordSaveDialog"></jobRecordSaveDialog>
<saveDialog v-if="dialog.save" @closed="dialog.save = null" @mounted="$refs.saveDialog.open(dialog.save)" ref="saveDialog"></saveDialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
const jobSaveDialog = defineAsyncComponent(() => import('@/views/sys/job/all/save.vue'))
const jobRecordSaveDialog = defineAsyncComponent(() => import('@/views/sys/job/record/save.vue'))
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
components: {
jobSaveDialog,
jobRecordSaveDialog,
saveDialog,
},
data() {
return {
dialog: {},
loading: false,
jobs: [],
jobsCnt: 0,
failJobs: [],
}
},
emits: ['closed'],
inject: ['reload'],
methods: {
async getData() {
this.loading = true
const res = await Promise.all([
this.$API.sys_job.pagedQuery.post({
prop: 'nextExecTime',
order: 'ascending',
dynamicFilter: {
field: 'enabled',
value: true,
operator: 'eq',
},
}),
this.$API.sys_job.pagedQueryRecord.post({
dynamicFilter: {
filters: [
{
logic: 'or',
filters: [
{
field: 'httpStatusCode',
operator: 'range',
value: '300,399',
},
{
field: 'httpStatusCode',
operator: 'range',
value: '400,499',
},
{
field: 'httpStatusCode',
operator: 'range',
value: '500,599',
},
{
field: 'httpStatusCode',
operator: 'range',
value: '900,999',
},
],
},
],
field: 'createdTime',
operator: 'greaterThan',
value: this.$TOOL.data.get('APP_SET_FAIL_JOB_VIEW_TIME') ?? this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd'),
},
}),
])
this.jobs = res[0].data.rows
this.jobsCnt = res[0].data.total
this.failJobs = res[1].data.rows
this.loading = false
},
refresh() {
this.getData()
},
},
mounted() {
this.getData()
},
props: {
fail: { type: Number, default: 0 },
},
watch: {},
}
</script>
<style scoped>
.user-bar-jobs-item {
margin-bottom: 0.5rem;
}
.user-bar-jobs-item:hover {
border-color: var(--na-color-primary);
}
.user-bar-jobs-item.alert:hover {
border-color: var(--el-color-danger);
}
.user-bar-jobs-item-body {
display: flex;
}
.user-bar-jobs-item-body .jobIcon {
width: 3rem;
height: 3rem;
background: var(--el-color-primary-light-9);
margin-right: 2rem;
display: flex;
justify-content: center;
align-items: center;
color: var(--na-color-primary);
border-radius: 1.5rem;
}
.user-bar-jobs-item-body .jobMain {
flex: 1;
}
.user-bar-jobs-item-body .title h2 {
font-size: 1rem;
}
.user-bar-jobs-item-body .title p {
font-size: 1rem;
color: #999;
margin-top: 0.5rem;
}
.user-bar-jobs-item-body .bottom {
display: flex;
justify-content: space-between;
align-items: center;
}
.user-bar-jobs-item.alert .jobIcon {
background: var(--el-color-danger-light-9);
color: var(--el-color-danger);
}
.status.failJobs {
color: var(--el-color-danger);
width: 18rem;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,44 @@
<template>
<sc-dialog v-model="visible" :title="$t('作业中心')" @closed="$emit('closed')" append-to-body destroy-on-close full-screen>
<job v-if="tabId" :statusCodes="statusCodes" :tab="tabId" />
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
</template>
</sc-dialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
const job = defineAsyncComponent(() => import('@/views/sys/job/index.vue'))
export default {
components: { job },
data() {
return {
statusCodes: null,
tabId: null,
loading: true,
visible: false,
}
},
emits: ['success', 'closed', 'mounted'],
methods: {
//
async open(data) {
this.visible = true
if (data.tabId === 'fail') {
data.tabId = 'log'
this.statusCodes = ['300,399', '400,499', '500,599', '900,999']
await this.$TOOL.data.set('APP_SET_FAIL_JOB_VIEW_TIME', this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd hh:mm:ss'))
}
this.tabId = data.tabId
return this
},
//
async submit() {},
},
mounted() {
this.$emit('mounted')
},
}
</script>
<style scoped></style>

View File

@ -74,7 +74,7 @@
<search @success="searchVisible = false"></search> <search @success="searchVisible = false"></search>
</el-dialog> </el-dialog>
<el-drawer v-model="tasksVisible" :size="450" :title="$t('作业中心')" destroy-on-close> <el-drawer v-model="tasksVisible" :size="800" :title="$t('作业中心')" destroy-on-close>
<tasks :fail="failJobCnt" @closed="tasksVisible = false"></tasks> <tasks :fail="failJobCnt" @closed="tasksVisible = false"></tasks>
</el-drawer> </el-drawer>
</template> </template>
@ -83,7 +83,7 @@
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import avatar from '../../utils/avatar' import avatar from '../../utils/avatar'
const search = defineAsyncComponent(() => import('./search.vue')) const search = defineAsyncComponent(() => import('./search.vue'))
const tasks = defineAsyncComponent(() => import('./tasks.vue')) const tasks = defineAsyncComponent(() => import('./tasks/index.vue'))
const message = defineAsyncComponent(() => import('@/views/profile/message/components/list.vue')) const message = defineAsyncComponent(() => import('@/views/profile/message/components/list.vue'))
export default { export default {
components: { components: {

View File

@ -461,7 +461,7 @@ export default {
作业信息: 'Job information', 作业信息: 'Job information',
查看作业记录: 'View job records', 查看作业记录: 'View job records',
异常作业: 'Abnormal jobs', 异常作业: 'Abnormal jobs',
所有作业: 'All jobs', 作业管理: 'Job management',
用户列表: 'User list', 用户列表: 'User list',
: 'Yes', : 'Yes',
: 'No', : 'No',

View File

@ -459,7 +459,7 @@ export default {
作业信息: '作业信息', 作业信息: '作业信息',
查看作业记录: '查看作业记录', 查看作业记录: '查看作业记录',
异常作业: '异常作业', 异常作业: '异常作业',
所有作业: '所有作业', 作业管理: '作业管理',
用户列表: '用户列表', 用户列表: '用户列表',
: '是', : '是',
: '否', : '否',

View File

@ -625,5 +625,5 @@ textarea {
.el-header.el-header-select-filter { .el-header.el-header-select-filter {
height: auto; height: auto;
padding: 0 1rem padding: 0 1rem;
} }

View File

@ -1,10 +1,3 @@
/*
* @Descripttion: 工具集
* @version: 1.2
* @LastEditors: Xujianchen
* @LastEditTime: 2023-03-19 11:17:54
*/
import CryptoJS from 'crypto-js' import CryptoJS from 'crypto-js'
import sysConfig from '@/config' import sysConfig from '@/config'

View File

@ -88,6 +88,13 @@
<el-table-column type="selection" width="50" /> <el-table-column type="selection" width="50" />
<na-col-id :label="$t('部门编号')" prop="id" sortable="custom" width="170" /> <na-col-id :label="$t('部门编号')" prop="id" sortable="custom" width="170" />
<el-table-column :label="$t('部门名称')" min-width="150" prop="name" sortable="custom" /> <el-table-column :label="$t('部门名称')" min-width="150" prop="name" sortable="custom" />
<el-table-column :label="$t('用户数量')" align="right" width="100">
<template #default="{ row }">
<el-link @click.native="dialog.save = { mode: 'view', row, tabId: 'user' }"
>{{ statistics.deptId?.find((x) => x.key.deptId === row.id.toString())?.value ?? '...' }}
</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('排序')" align="right" prop="sort" sortable="custom" width="100" /> <el-table-column :label="$t('排序')" align="right" prop="sort" sortable="custom" width="100" />
<el-table-column label="备注" min-width="100" prop="summary" sortable="custom" /> <el-table-column label="备注" min-width="100" prop="summary" sortable="custom" />
<el-table-column :label="$t('启用')" align="center" prop="enabled" sortable="custom" width="100"> <el-table-column :label="$t('启用')" align="center" prop="enabled" sortable="custom" width="100">
@ -172,9 +179,13 @@ export default {
}, },
requiredFields: ['Enabled'], requiredFields: ['Enabled'],
}), }),
this.$API.sys_user.countBy.post({
requiredFields: ['DeptId'],
}),
]) ])
this.statistics.enabled = res[0].data this.statistics.enabled = res[0].data
this.statistics.deptId = res[1].data
}, },
async setEnabled(enabled) { async setEnabled(enabled) {
let loading let loading

View File

@ -100,6 +100,9 @@ export default {
Object.assign(this.form, res.data) Object.assign(this.form, res.data)
} }
this.loading = false this.loading = false
if (data.tabId) {
this.tabId = data.tabId
}
return this return this
}, },
// //

View File

@ -2,30 +2,25 @@
<el-container> <el-container>
<el-header style="border: none"> <el-header style="border: none">
<el-tabs v-model="tabId" class="w100p"> <el-tabs v-model="tabId" class="w100p">
<el-tab-pane :label="$t('所有作业')" name="all"></el-tab-pane> <el-tab-pane :label="$t('作业管理')" name="all"></el-tab-pane>
<el-tab-pane :label="$t('异常作业')" name="fail"></el-tab-pane> <el-tab-pane :label="$t('作业日志')" name="log"></el-tab-pane>
</el-tabs> </el-tabs>
</el-header> </el-header>
<el-main class="nopadding"> <el-main class="nopadding">
<component :is="tabId" :status-codes="['300,399', '400,499', '500,599', '900,999']" /> <component :is="tabId" :status-codes="statusCodes" />
</el-main> </el-main>
</el-container> </el-container>
</template> </template>
<script> <script>
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
const fail = defineAsyncComponent(() => import('@/views/sys/job/record/index.vue')) const log = defineAsyncComponent(() => import('@/views/sys/job/record/index.vue'))
const all = defineAsyncComponent(() => import('@/views/sys/job/all/index.vue')) const all = defineAsyncComponent(() => import('@/views/sys/job/all/index.vue'))
export default { export default {
components: { all, fail }, components: { all, log },
computed: {}, computed: {},
created() { created() {},
if (this.$route.query.view === 'fail') {
this.tabId = 'fail'
this.$TOOL.data.set('APP_SET_FAIL_JOB_VIEW_TIME', this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd hh:mm:ss'))
}
},
data() { data() {
return { return {
tabId: 'all', tabId: 'all',
@ -34,7 +29,25 @@ export default {
inject: ['reload'], inject: ['reload'],
methods: {}, methods: {},
mounted() {}, mounted() {},
watch: {}, watch: {
tab: {
immediate: true,
deep: true,
handler(n) {
this.tabId = n
},
},
},
props: {
tab: {
type: String,
default: 'all',
},
statusCodes: {
type: Array,
default: null,
},
},
} }
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -33,7 +33,7 @@
w100p: true, w100p: true,
}, },
]" ]"
:label-width="6" :label-width="8"
@on-change="filterChange" @on-change="filterChange"
ref="selectFilter"></sc-select-filter> ref="selectFilter"></sc-select-filter>
</el-header> </el-header>

View File

@ -210,8 +210,8 @@ export default {
}), }),
]) ])
this.statistics.errorCode = res[0].data this.statistics.errorCode = res[0].data
this.statistics.loginUserName = res[1].data.slice(0, 20) this.statistics.loginUserName = res[1].data
this.statistics.createdClientIp = res[2].data.slice(0, 20) this.statistics.createdClientIp = res[2].data
}, },
async dataChange(data) { async dataChange(data) {
this.apis = [] this.apis = []

View File

@ -45,7 +45,8 @@
let api = this.apis?.find((y) => y.pathCrc32.toString() === x.key.apiPathCrc32) let api = this.apis?.find((y) => y.pathCrc32.toString() === x.key.apiPathCrc32)
return { return {
value: x.key.apiPathCrc32, value: x.key.apiPathCrc32,
label: `${api?.summary} : ${api?.id}`, label: api?.summary,
title: api?.id,
badge: x.value, badge: x.value,
} }
}) ?? []), }) ?? []),
@ -318,7 +319,7 @@ export default {
}), }),
]) ])
this.statistics.httpStatusCode = res[0].data this.statistics.httpStatusCode = res[0].data
this.statistics.apiPathCrc32 = res[1].data.slice(0, 20) this.statistics.apiPathCrc32 = res[1].data
}, },
filterChange(data) { filterChange(data) {
Object.entries(data).forEach(([key, value]) => { Object.entries(data).forEach(([key, value]) => {

View File

@ -135,6 +135,13 @@
<el-table-column type="selection" width="50" /> <el-table-column type="selection" width="50" />
<na-col-id :label="$t('角色编号')" prop="id" sortable="custom" width="170" /> <na-col-id :label="$t('角色编号')" prop="id" sortable="custom" width="170" />
<el-table-column :label="$t('角色名称')" min-width="150" prop="name" sortable="custom" /> <el-table-column :label="$t('角色名称')" min-width="150" prop="name" sortable="custom" />
<el-table-column :label="$t('用户数量')" align="right" width="100">
<template #default="{ row }">
<el-link @click.native="dialog.save = { mode: 'view', row, tabId: 'user' }"
>{{ statistics.roleId?.find((x) => x.key.roleId === row.id.toString())?.value ?? '...' }}
</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('排序')" align="right" prop="sort" sortable="custom" width="100" /> <el-table-column :label="$t('排序')" align="right" prop="sort" sortable="custom" width="100" />
<el-table-column :label="$t('无限权限')" align="center" prop="ignorePermissionControl" sortable="custom" width="100"> <el-table-column :label="$t('无限权限')" align="center" prop="ignorePermissionControl" sortable="custom" width="100">
<template #default="{ row }"> <template #default="{ row }">
@ -267,12 +274,16 @@ export default {
}, },
requiredFields: ['DataScope'], requiredFields: ['DataScope'],
}), }),
this.$API.sys_role.userCountBy.post({
requiredFields: ['RoleId'],
}),
]) ])
this.statistics.enabled = res[0].data this.statistics.enabled = res[0].data
this.statistics.displayDashboard = res[1].data this.statistics.displayDashboard = res[1].data
this.statistics.ignorePermissionControl = res[2].data this.statistics.ignorePermissionControl = res[2].data
this.statistics.dataScope = res[3].data this.statistics.dataScope = res[3].data
this.statistics.roleId = res[4].data
}, },
async copyRole(row) { async copyRole(row) {
const loading = this.$loading() const loading = this.$loading()

View File

@ -179,6 +179,9 @@ export default {
await this.getTrees('api') await this.getTrees('api')
await this.getTrees('dept') await this.getTrees('dept')
this.loading = false this.loading = false
if (data.tabId) {
this.tabId = data.tabId
}
return this return this
}, },

View File

@ -204,6 +204,7 @@ export default {
this.statistics.total = this.$refs.table?.total this.statistics.total = this.$refs.table?.total
const res = await Promise.all([ const res = await Promise.all([
this.$API.sys_user.countBy.post({ this.$API.sys_user.countBy.post({
filter: this.query.filter,
dynamicFilter: { dynamicFilter: {
filters: this.query.dynamicFilter.filters, filters: this.query.dynamicFilter.filters,
}, },
@ -291,6 +292,23 @@ export default {
}) })
} }
if (this.roleId) {
this.$refs.search.form.filter.roleId = this.roleId
this.$refs.search.keeps.push({
field: 'roleId',
value: this.roleId,
type: 'filter',
})
}
if (this.deptId) {
this.$refs.search.form.filter.deptId = this.deptId
this.$refs.search.keeps.push({
field: 'deptId',
value: this.deptId,
type: 'filter',
})
}
this.$refs.search.form.dy.enabled = true this.$refs.search.form.dy.enabled = true
this.$refs.search.keeps.push({ this.$refs.search.keeps.push({
field: 'enabled', field: 'enabled',