feat: 文档管理 (#221)

Co-authored-by: tk <fiyne1a@dingtalk.com>
This commit is contained in:
nsnail 2024-11-27 15:52:23 +08:00 committed by GitHub
parent 71bfdaafa8
commit 7ed30406c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
89 changed files with 2748 additions and 141 deletions

View File

@ -18,7 +18,6 @@
作业名称
作业状态
保密
保密
信息
倒序排序
全部数据
@ -43,7 +42,6 @@
大于
大于等于
字典内容导出
宕机
客户端IP
@ -74,6 +72,9 @@
插入种子数据
操作系统
数据范围
文档内容
文档内容导出
文档标题
无效操作
无效输入
无限权限
@ -100,7 +101,6 @@
用户导出
电子邮箱
登录
登录名
登录日志导出
@ -110,8 +110,6 @@
空闲
站内信导出
等于
等于
等于
等待发送
管理模块
系统模块

View File

@ -19,15 +19,10 @@ XML注释文件不存在
字典名称不能为空
字典目录不存在
字典目录编号不能为空
字典目录编号不能为空
字典编码不能为空
学历不正确
完全公开
密码不能为空
密码不能为空
密码不能为空
密码不能为空
已处理完毕
已处理完毕
已处理完毕
并发冲突_请稍后重试
开始事务
@ -41,6 +36,12 @@ XML注释文件不存在
数据库服务器时钟偏移
数据库结构同步完成
文件不能为空
文档内容不能为空
文档分类不存在
文档分类名称不能为空
文档分类编号不能为空
文档分类编码不能为空
文档标题不能为空
新密码不能为空
新手机号码验证码不正确
无效端口号
@ -53,28 +54,24 @@ XML注释文件不存在
时间计划不能为空
未指定部门
未获取到待执行任务
档案可见性不正确
模块名称不能为空
模块类型不能为空
模块说明不能为空
此节点已下线
此节点已下线
民族不正确
消息主题不能为空
消息内容不能为空
父节点不存在
用户不存在
用户名不能为空
用户名不能为空
用户名不能为空
用户名不能是手机号码
用户名或密码错误
用户名长度4位以上
用户头像不能为空
用户编号不存在
登录用户
目标设备不能为空
目标设备不能为空
短信验证请求不能为空
短信验证请求不能为空
短信验证请求不能为空
种子数据插入完成
站内信不存在
@ -82,8 +79,7 @@ XML注释文件不存在
站内信类型不正确
缓存键不能为空
网络地址不正确
网络地址不正确
网络地址不正确
自己可见
菜单名称不能为空
菜单标题不能为空
菜单类型不正确
@ -109,11 +105,11 @@ XML注释文件不存在
邀请码不正确
邮箱验证码不正确
部门不存在
部门可见
部门名称不能为空
配置文件初始化完毕
键值不能为空
键名称不能为空
键名称不能为空
随机延时结束时间不正确
随机延时起始时间不正确
非JSON字符串

View File

@ -140,13 +140,34 @@
"Title": "缓存管理",
"Type": 1
},
// ------------------------------ ------------------------------
{
"Icon": "sc-icon-App",
"Id": 616214756757512,
"Name": "archive",
"Path": "/archive",
"Sort": 98,
"Title": "档案管理",
"Type": 1
},
{
"Component": "sys/doc",
"Icon": "el-icon-set-up",
"Id": 616214756757516,
"Name": "archive/doc",
"ParentId": 616214756757512,
"Path": "/archive/doc",
"Sort": 100,
"Title": "文档管理",
"Type": 1
},
// ------------------------------ ------------------------------
{
"Icon": "el-icon-tickets",
"Id": 374792687640581,
"Name": "log",
"Path": "/log",
"Sort": 98,
"Sort": 97,
"Title": "日志管理",
"Type": 1
},
@ -178,7 +199,7 @@
"Id": 373838105399301,
"Name": "dev",
"Path": "/dev",
"Sort": 97,
"Sort": 96,
"Title": "开发管理",
"Type": 1
},

View File

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

View File

@ -8,7 +8,7 @@ public static class UnitOfWorkManagerExtensions
/// <summary>
/// 事务操作
/// </summary>
public static async Task AtomicOperateAsync(this UnitOfWorkManager me, Func<Task> handle)
public static async Task AtomicOperateAsync(this UnitOfWorkManager me, Func<Task> handle, Func<Task> onErrorHandle = null)
{
var logger = LogHelper.Get<UnitOfWorkManager>();
using var unitOfWork = me.Begin();
@ -25,6 +25,11 @@ public static class UnitOfWorkManagerExtensions
logger?.Warn(ex);
unitOfWork.Rollback();
logger?.Warn($"{Ln.事务已回滚}: {hashCode}");
if (onErrorHandle != null) {
await onErrorHandle().ConfigureAwait(false);
}
throw;
}
}

View File

@ -16,7 +16,7 @@ public record Sys_DicCatalog : VersionEntity
public IEnumerable<Sys_DicCatalog> Children { get; init; }
/// <summary>
/// 字典编码
/// 字典目录编码
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)]
[CsvIgnore]
@ -32,7 +32,7 @@ public record Sys_DicCatalog : VersionEntity
public ICollection<Sys_DicContent> Contents { get; init; }
/// <summary>
/// 字典名称
/// 字典目录名称
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)]
[CsvIgnore]

View File

@ -0,0 +1,73 @@
namespace NetAdmin.Domain.DbMaps.Sys;
/// <summary>
/// 文档分类表
/// </summary>
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(Code), nameof(Code), true)]
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_DocCatalog))]
public record Sys_DocCatalog : VersionEntity, IFieldOwner
{
/// <summary>
/// 子节点
/// </summary>
[CsvIgnore]
[JsonIgnore]
[Navigate(nameof(ParentId))]
public IEnumerable<Sys_DocCatalog> Children { get; init; }
/// <summary>
/// 文档分类编码
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)]
[CsvIgnore]
[JsonIgnore]
public virtual string Code { get; init; }
/// <summary>
/// 文档内容集合
/// </summary>
[CsvIgnore]
[JsonIgnore]
[Navigate(nameof(Sys_DocContent.CatalogId))]
public ICollection<Sys_DocContent> Contents { get; init; }
/// <summary>
/// 文档分类名称
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)]
[CsvIgnore]
[JsonIgnore]
public virtual string Name { get; init; }
/// <summary>
/// 拥有者
/// </summary>
[CsvIgnore]
[JsonIgnore]
[Navigate(nameof(OwnerId))]
public Sys_User Owner { get; init; }
/// <summary>
/// 拥有者部门编号
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerDeptId { get; init; }
/// <summary>
/// 拥有者用户编号
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerId { get; init; }
/// <summary>
/// 父编号
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long ParentId { get; init; }
}

View File

@ -0,0 +1,86 @@
using NetAdmin.Domain.Enums.Sys;
namespace NetAdmin.Domain.DbMaps.Sys;
/// <summary>
/// 文档内容表
/// </summary>
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_DocContent))]
public record Sys_DocContent : VersionEntity, IFieldEnabled, IFieldOwner
{
/// <summary>
/// 文档正文
/// </summary>
#if DBTYPE_SQLSERVER
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#endif
[CsvIgnore]
[JsonIgnore]
public virtual string Body { get; init; }
/// <summary>
/// 文档分类
/// </summary>
[CsvIgnore]
[JsonIgnore]
[Navigate(nameof(CatalogId))]
public Sys_DocCatalog Catalog { get; init; }
/// <summary>
/// 文档分类编号
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long CatalogId { get; init; }
/// <summary>
/// 是否启用
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual bool Enabled { get; init; }
/// <summary>
/// 拥有者
/// </summary>
[CsvIgnore]
[JsonIgnore]
[Navigate(nameof(OwnerId))]
public Sys_User Owner { get; init; }
/// <summary>
/// 拥有者部门编号
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerDeptId { get; init; }
/// <summary>
/// 拥有者用户编号
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerId { get; init; }
/// <summary>
/// 文档标题
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
[CsvIgnore]
[JsonIgnore]
public virtual string Title { get; init; }
/// <summary>
/// 可见性
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual ArchiveVisibilities Visibility { get; init; }
}

View File

@ -0,0 +1,23 @@
using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.Doc.Catalog;
/// <summary>
/// 请求:创建文档分类
/// </summary>
public record CreateDocCatalogReq : Sys_DocCatalog
{
/// <inheritdoc cref="Sys_DocCatalog.Code" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.文档分类编码不能为空))]
public override string Code { get; init; }
/// <inheritdoc cref="Sys_DocCatalog.Name" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.文档分类名称不能为空))]
public override string Name { get; init; }
/// <inheritdoc cref="Sys_DocCatalog.ParentId" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long ParentId { get; init; }
}

View File

@ -0,0 +1,11 @@
namespace NetAdmin.Domain.Dto.Sys.Doc.Catalog;
/// <summary>
/// 请求:编辑文档分类
/// </summary>
public sealed record EditDocCatalogReq : CreateDocCatalogReq
{
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -0,0 +1,8 @@
using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.Doc.Catalog;
/// <summary>
/// 请求:查询文档分类
/// </summary>
public sealed record QueryDocCatalogReq : Sys_DocCatalog;

View File

@ -0,0 +1,32 @@
using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.Doc.Catalog;
/// <summary>
/// 响应:查询文档分类
/// </summary>
public sealed record QueryDocCatalogRsp : Sys_DocCatalog
{
/// <inheritdoc cref="Sys_DocCatalog.Children" />
public new IEnumerable<QueryDocCatalogRsp> Children { get; init; }
/// <inheritdoc cref="Sys_DocCatalog.Code" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Code { get; init; }
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="Sys_DocCatalog.Name" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Name { get; init; }
/// <inheritdoc cref="Sys_DocCatalog.ParentId" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long ParentId { get; init; }
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -0,0 +1,34 @@
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Enums.Sys;
namespace NetAdmin.Domain.Dto.Sys.Doc.Content;
/// <summary>
/// 请求:创建文档内容
/// </summary>
public record CreateDocContentReq : Sys_DocContent
{
/// <inheritdoc cref="Sys_DocContent.Body" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.文档内容不能为空))]
public override string Body { get; init; }
/// <inheritdoc cref="Sys_DocContent.CatalogId" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.文档分类编号不能为空))]
public override long CatalogId { get; init; }
/// <inheritdoc cref="IFieldEnabled.Enabled" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override bool Enabled { get; init; } = true;
/// <inheritdoc cref="Sys_DocContent.Title" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.文档标题不能为空))]
public override string Title { get; init; }
/// <inheritdoc cref="Sys_DocContent.Visibility" />
[EnumDataType(typeof(ArchiveVisibilities), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.档案可见性不正确))]
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override ArchiveVisibilities Visibility { get; init; }
}

View File

@ -0,0 +1,11 @@
namespace NetAdmin.Domain.Dto.Sys.Doc.Content;
/// <summary>
/// 请求:编辑文档内容
/// </summary>
public sealed record EditDocContentReq : CreateDocContentReq
{
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -0,0 +1,31 @@
namespace NetAdmin.Domain.Dto.Sys.Doc.Content;
/// <summary>
/// 响应:导出文档内容
/// </summary>
public sealed record ExportDocContentRsp : QueryDocContentRsp
{
/// <inheritdoc />
[CsvIndex(1)]
[CsvIgnore(false)]
[CsvName(nameof(Ln.文档内容))]
public override string Body { get; init; }
/// <inheritdoc />
[CsvIndex(2)]
[CsvIgnore(false)]
[CsvName(nameof(Ln.创建时间))]
public override DateTime CreatedTime { get; init; }
/// <inheritdoc />
[CsvIndex(3)]
[CsvIgnore(false)]
[CsvName(nameof(Ln.是否启用))]
public override bool Enabled { get; init; }
/// <inheritdoc />
[CsvIndex(0)]
[CsvIgnore(false)]
[CsvName(nameof(Ln.文档标题))]
public override string Title { get; init; }
}

View File

@ -0,0 +1,8 @@
using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.Doc.Content;
/// <summary>
/// 请求:查询文档内容
/// </summary>
public sealed record QueryDocContentReq : Sys_DocContent;

View File

@ -0,0 +1,38 @@
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Enums.Sys;
namespace NetAdmin.Domain.Dto.Sys.Doc.Content;
/// <summary>
/// 响应:查询文档内容
/// </summary>
public record QueryDocContentRsp : Sys_DocContent
{
/// <inheritdoc cref="Sys_DocContent.Body" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Body { get; init; }
/// <inheritdoc cref="Sys_DocContent.CatalogId" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long CatalogId { get; init; }
/// <inheritdoc cref="IFieldCreatedTime.CreatedTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override DateTime CreatedTime { get; init; }
/// <inheritdoc cref="IFieldEnabled.Enabled" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override bool Enabled { get; init; }
/// <inheritdoc cref="Sys_DocContent.Title" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Title { get; init; }
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
/// <inheritdoc cref="Sys_DocContent.Visibility" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override ArchiveVisibilities Visibility { get; init; }
}

View File

@ -0,0 +1,17 @@
using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.Doc.Content;
/// <summary>
/// 请求:设置文档内容启用状态
/// </summary>
public sealed record SetDocContentEnabledReq : Sys_DocContent
{
/// <inheritdoc cref="IFieldEnabled.Enabled" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override bool Enabled { get; init; }
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -0,0 +1,38 @@
namespace NetAdmin.Domain.Enums.Sys;
/// <summary>
/// 档案可见性
/// </summary>
[Export]
public enum ArchiveVisibilities
{
/// <summary>
/// 完全公开
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.完全公开))]
Public = 1
,
/// <summary>
/// 登录用户
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.登录用户))]
LogonUser = 2
,
/// <summary>
/// 部门可见
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.部门可见))]
DeptUser = 3
,
/// <summary>
/// 自己可见
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.自己可见))]
Self = 4
}

View File

@ -25,7 +25,8 @@ public abstract class ApiResultHandler<T>
{
var naException = context.Exception switch {
NetAdminException ex => ex
, _ => context.Exception.Message.Contains(Chars.FLG_DB_EXCEPTION_PRIMARY_KEY_CONFLICT)
, _ => context.Exception.Message.Contains(Chars.FLG_DB_EXCEPTION_PRIMARY_KEY_CONFLICT) ||
context.Exception.Message.Contains(Chars.FLG_DB_EXCEPTION_UNIQUE_CONSTRAINT_CONFLICT)
? new NetAdminInvalidOperationException(Ln.)
: null
};

View File

@ -16,6 +16,7 @@ public static class Chars
public const string FLG_CONTEXT_USER_INFO = nameof(FLG_CONTEXT_USER_INFO);
public const string FLG_CRON_PER_SECS = "* * * * * *";
public const string FLG_DB_EXCEPTION_PRIMARY_KEY_CONFLICT = "PRIMARY KEY";
public const string FLG_DB_EXCEPTION_UNIQUE_CONSTRAINT_CONFLICT = "UNIQUE constraint";
public const string FLG_DB_FIELD_TYPE_NVARCHAR = "nvarchar";
public const string FLG_DB_FIELD_TYPE_NVARCHAR_1022 = "nvarchar(1022)";
public const string FLG_DB_FIELD_TYPE_NVARCHAR_127 = "nvarchar(127)";

View File

@ -0,0 +1,11 @@
using NetAdmin.Domain.Dto.Sys.Doc.Catalog;
namespace NetAdmin.SysComponent.Application.Modules.Sys;
/// <summary>
/// 文档分类模块
/// </summary>
public interface IDocCatalogModule : ICrudModule<CreateDocCatalogReq, QueryDocCatalogRsp // 创建类型
, QueryDocCatalogReq, QueryDocCatalogRsp // 查询类型
, DelReq // 删除类型
>;

View File

@ -0,0 +1,17 @@
using NetAdmin.Domain.Dto.Sys.Doc.Content;
namespace NetAdmin.SysComponent.Application.Modules.Sys;
/// <summary>
/// 文档内容模块
/// </summary>
public interface IDocContentModule : ICrudModule<CreateDocContentReq, QueryDocContentRsp // 创建类型
, QueryDocContentReq, QueryDocContentRsp // 查询类型
, DelReq // 删除类型
>
{
/// <summary>
/// 启用/禁用文档内容
/// </summary>
Task<int> SetEnabledAsync(SetDocContentEnabledReq req);
}

View File

@ -0,0 +1,95 @@
using NetAdmin.Domain.Dto.Sys.Doc.Catalog;
using NetAdmin.Domain.Dto.Sys.Doc.Content;
namespace NetAdmin.SysComponent.Application.Modules.Sys;
/// <summary>
/// 文档模块
/// </summary>
public interface IDocModule
{
/// <summary>
/// 批量删除文档分类
/// </summary>
Task<int> BulkDeleteCatalogAsync(BulkReq<DelReq> req);
/// <summary>
/// 批量删除文档内容
/// </summary>
Task<int> BulkDeleteContentAsync(BulkReq<DelReq> req);
/// <summary>
/// 创建文档分类
/// </summary>
Task<QueryDocCatalogRsp> CreateCatalogAsync(CreateDocCatalogReq req);
/// <summary>
/// 创建文档内容
/// </summary>
Task<QueryDocContentRsp> CreateContentAsync(CreateDocContentReq req);
/// <summary>
/// 删除文档分类
/// </summary>
Task<int> DeleteCatalogAsync(DelReq req);
/// <summary>
/// 删除文档内容
/// </summary>
Task<int> DeleteContentAsync(DelReq req);
/// <summary>
/// 编辑文档分类
/// </summary>
Task<int> EditCatalogAsync(EditDocCatalogReq req);
/// <summary>
/// 编辑文档内容
/// </summary>
Task<QueryDocContentRsp> EditContentAsync(EditDocContentReq req);
/// <summary>
/// 导出文档内容
/// </summary>
Task<IActionResult> ExportContentAsync(QueryReq<QueryDocContentReq> req);
/// <summary>
/// 获取单个文档分类
/// </summary>
Task<QueryDocCatalogRsp> GetCatalogAsync(QueryDocCatalogReq req);
/// <summary>
/// 获取单个文档内容
/// </summary>
Task<QueryDocContentRsp> GetContentAsync(QueryDocContentReq req);
/// <summary>
/// 分页查询文档分类
/// </summary>
Task<PagedQueryRsp<QueryDocCatalogRsp>> PagedQueryCatalogAsync(PagedQueryReq<QueryDocCatalogReq> req);
/// <summary>
/// 分页查询文档内容
/// </summary>
Task<PagedQueryRsp<QueryDocContentRsp>> PagedQueryContentAsync(PagedQueryReq<QueryDocContentReq> req);
/// <summary>
/// 查询文档分类
/// </summary>
Task<IEnumerable<QueryDocCatalogRsp>> QueryCatalogAsync(QueryReq<QueryDocCatalogReq> req);
/// <summary>
/// 查询文档内容
/// </summary>
Task<IEnumerable<QueryDocContentRsp>> QueryContentAsync(QueryReq<QueryDocContentReq> req);
/// <summary>
/// 启用/禁用文档内容
/// </summary>
Task<int> SetEnabledAsync(SetDocContentEnabledReq req);
/// <summary>
/// 浏览文档内容
/// </summary>
Task<QueryDocContentRsp> ViewContentAsync(QueryDocContentReq req);
}

View File

@ -0,0 +1,14 @@
using NetAdmin.Domain.Dto.Sys.Doc.Catalog;
namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
/// <summary>
/// 文档分类服务
/// </summary>
public interface IDocCatalogService : IService, IDocCatalogModule
{
/// <summary>
/// 编辑文档分类
/// </summary>
Task<int> EditAsync(EditDocCatalogReq req);
}

View File

@ -0,0 +1,19 @@
using NetAdmin.Domain.Dto.Sys.Doc.Content;
namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
/// <summary>
/// 文档内容服务
/// </summary>
public interface IDocContentService : IService, IDocContentModule
{
/// <summary>
/// 编辑文档内容
/// </summary>
Task<QueryDocContentRsp> EditAsync(EditDocContentReq req);
/// <summary>
/// 浏览文档内容
/// </summary>
Task<QueryDocContentRsp> ViewAsync(QueryDocContentReq req);
}

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
/// <summary>
/// 文档服务
/// </summary>
public interface IDocService : IService, IDocModule;

View File

@ -46,7 +46,14 @@ public sealed class DevService(IApiService apiService) : ServiceBase<DevService>
var appServicesDependencyDir = Path.Combine(appServicesDir, "Dependency");
// 数据契约层目录
var dataDir = GetDir($"{req.Type}.Domain");
string dataDir;
try {
dataDir = GetDir($"{req.Type}.Domain");
}
catch (InvalidOperationException) {
dataDir = tplDataDir;
}
var dtoDir = Path.Combine(dataDir, "Dto", typeAbbr, req.ModuleName);
var entityDir = Path.Combine(dataDir, "DbMaps", typeAbbr);

View File

@ -26,10 +26,10 @@ public sealed class DicContentService(BasicRepository<Sys_DicContent, long> rpo)
{
req.ThrowIfInvalid();
return QueryInternal(req)
#if DBTYPE_SQLSERVER
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.CountAsync();
#endif
.CountAsync();
}
/// <inheritdoc />
@ -87,10 +87,10 @@ public sealed class DicContentService(BasicRepository<Sys_DicContent, long> rpo)
{
req.ThrowIfInvalid();
return QueryInternal(req)
#if DBTYPE_SQLSERVER
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.AnyAsync();
#endif
.AnyAsync();
}
/// <inheritdoc />
@ -162,7 +162,11 @@ public sealed class DicContentService(BasicRepository<Sys_DicContent, long> rpo)
private ISelect<Sys_DicContent> QueryInternal(QueryReq<QueryDicContentReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter)
.WhereDynamic(req.Filter)
.WhereIf( //
req.Keywords?.Length > 0
, a => a.Key.Contains(req.Keywords) || a.Value.Contains(req.Keywords) || a.Summary.Contains(req.Keywords));
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
switch (req.Order) {

View File

@ -0,0 +1,150 @@
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys.Doc.Catalog;
namespace NetAdmin.SysComponent.Application.Services.Sys;
/// <inheritdoc cref="IDocCatalogService" />
public sealed class DocCatalogService(BasicRepository<Sys_DocCatalog, long> rpo) //
: RepositoryService<Sys_DocCatalog, long, IDocCatalogService>(rpo), IDocCatalogService
{
/// <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<QueryDocCatalogReq> req)
{
req.ThrowIfInvalid();
return QueryInternal(req)
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.CountAsync();
}
/// <inheritdoc />
/// <exception cref="NetAdminInvalidOperationException">The_parent_node_does_not_exist</exception>
public async Task<QueryDocCatalogRsp> CreateAsync(CreateDocCatalogReq req)
{
req.ThrowIfInvalid();
if (req.ParentId != 0 && !await Rpo.Where(a => a.Id == req.ParentId)
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.AnyAsync()
.ConfigureAwait(false)) {
throw new NetAdminInvalidOperationException(Ln.);
}
var ret = await Rpo.InsertAsync(req).ConfigureAwait(false);
return ret.Adapt<QueryDocCatalogRsp>();
}
/// <inheritdoc />
public async Task<int> DeleteAsync(DelReq req)
{
req.ThrowIfInvalid();
var ret = await Rpo.DeleteCascadeByDatabaseAsync(a => a.Id == req.Id).ConfigureAwait(false);
return ret.Count;
}
/// <inheritdoc />
/// <exception cref="NetAdminInvalidOperationException">The_parent_node_does_not_exist</exception>
public async Task<int> EditAsync(EditDocCatalogReq req)
{
req.ThrowIfInvalid();
return req.ParentId == 0 || await Rpo.Where(a => a.Id == req.ParentId)
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.AnyAsync()
.ConfigureAwait(false)
? await UpdateAsync(req, null).ConfigureAwait(false)
: throw new NetAdminInvalidOperationException(Ln.);
}
/// <inheritdoc />
public Task<bool> ExistAsync(QueryReq<QueryDocCatalogReq> req)
{
req.ThrowIfInvalid();
return QueryInternal(req)
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.AnyAsync();
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryDocCatalogReq> req)
{
req.ThrowIfInvalid();
throw new NotImplementedException();
}
/// <inheritdoc />
public async Task<QueryDocCatalogRsp> GetAsync(QueryDocCatalogReq req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(new QueryReq<QueryDocCatalogReq> { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false);
return ret.Adapt<QueryDocCatalogRsp>();
}
/// <inheritdoc />
public async Task<PagedQueryRsp<QueryDocCatalogRsp>> PagedQueryAsync(PagedQueryReq<QueryDocCatalogReq> req)
{
req.ThrowIfInvalid();
var list = await QueryInternal(req)
.Page(req.Page, req.PageSize)
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.Count(out var total)
.ToListAsync()
.ConfigureAwait(false);
return new PagedQueryRsp<QueryDocCatalogRsp>(req.Page, req.PageSize, total, list.Adapt<IEnumerable<QueryDocCatalogRsp>>());
}
/// <inheritdoc />
public async Task<IEnumerable<QueryDocCatalogRsp>> QueryAsync(QueryReq<QueryDocCatalogReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req)
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.ToTreeListAsync()
.ConfigureAwait(false);
return ret.Adapt<IEnumerable<QueryDocCatalogRsp>>();
}
private ISelect<Sys_DocCatalog> QueryInternal(QueryReq<QueryDocCatalogReq> 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

@ -0,0 +1,202 @@
using NetAdmin.Domain.DbMaps.Dependency.Fields;
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys.Doc.Content;
using NetAdmin.Domain.Enums.Sys;
namespace NetAdmin.SysComponent.Application.Services.Sys;
/// <inheritdoc cref="IDocContentService" />
public sealed class DocContentService(BasicRepository<Sys_DocContent, long> rpo) //
: RepositoryService<Sys_DocContent, long, IDocContentService>(rpo), IDocContentService
{
/// <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<QueryDocContentReq> req)
{
req.ThrowIfInvalid();
return QueryInternal(req)
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.CountAsync();
}
/// <inheritdoc />
/// <exception cref="NetAdminInvalidOperationException">Doctionary_directory_does_not_exist</exception>
public async Task<QueryDocContentRsp> CreateAsync(CreateDocContentReq req)
{
req.ThrowIfInvalid();
if (!await Rpo.Orm.Select<Sys_DocCatalog>()
.Where(a => a.Id == req.CatalogId)
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.AnyAsync()
.ConfigureAwait(false)) {
throw new NetAdminInvalidOperationException(Ln.);
}
var ret = await Rpo.InsertAsync(req).ConfigureAwait(false);
return ret.Adapt<QueryDocContentRsp>();
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
req.ThrowIfInvalid();
return Rpo.DeleteAsync(a => a.Id == req.Id);
}
/// <inheritdoc />
/// <exception cref="NetAdminInvalidOperationException">Doctionary_directory_does_not_exist</exception>
public async Task<QueryDocContentRsp> EditAsync(EditDocContentReq req)
{
req.ThrowIfInvalid();
if (!await Rpo.Orm.Select<Sys_DocCatalog>()
.Where(a => a.Id == req.CatalogId)
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.AnyAsync()
.ConfigureAwait(false)) {
throw new NetAdminInvalidOperationException(Ln.);
}
#if DBTYPE_SQLSERVER
return (await UpdateReturnListAsync(req, null).ConfigureAwait(false)).FirstOrDefault()?.Adapt<QueryDocContentRsp>();
#else
return await UpdateAsync(req, null, [nameof(IFieldOwner.OwnerId), nameof(IFieldOwner.OwnerDeptId)]).ConfigureAwait(false) > 0
? await GetAsync(new QueryDocContentReq { Id = req.Id }).ConfigureAwait(false)
: null;
#endif
}
/// <inheritdoc />
public Task<bool> ExistAsync(QueryReq<QueryDocContentReq> req)
{
req.ThrowIfInvalid();
return QueryInternal(req)
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.AnyAsync();
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryDocContentReq> req)
{
req.ThrowIfInvalid();
return ExportAsync<QueryDocContentReq, ExportDocContentRsp>(QueryInternal, req, Ln.);
}
/// <inheritdoc />
public async Task<QueryDocContentRsp> GetAsync(QueryDocContentReq req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(new QueryReq<QueryDocContentReq> { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false);
return ret.Adapt<QueryDocContentRsp>();
}
/// <inheritdoc />
public async Task<PagedQueryRsp<QueryDocContentRsp>> PagedQueryAsync(PagedQueryReq<QueryDocContentReq> req)
{
req.ThrowIfInvalid();
var list = await QueryInternal(req)
.Page(req.Page, req.PageSize)
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.Count(out var total)
.ToListAsync()
.ConfigureAwait(false);
return new PagedQueryRsp<QueryDocContentRsp>(req.Page, req.PageSize, total, list.Adapt<IEnumerable<QueryDocContentRsp>>());
}
/// <inheritdoc />
public async Task<IEnumerable<QueryDocContentRsp>> QueryAsync(QueryReq<QueryDocContentReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req)
#if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif
.Take(req.Count)
.ToListAsync()
.ConfigureAwait(false);
return ret.Adapt<IEnumerable<QueryDocContentRsp>>();
}
/// <inheritdoc />
public Task<int> SetEnabledAsync(SetDocContentEnabledReq req)
{
req.ThrowIfInvalid();
return UpdateAsync(req, [nameof(Sys_DocContent.Enabled)]);
}
/// <inheritdoc />
public async Task<QueryDocContentRsp> ViewAsync(QueryDocContentReq req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(new QueryReq<QueryDocContentReq> { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false);
switch (ret?.Visibility) {
case ArchiveVisibilities.LogonUser:
if (UserToken == null) {
return null;
}
break;
case ArchiveVisibilities.DeptUser:
if (UserToken == null || UserToken.DeptId != ret.OwnerDeptId) {
return null;
}
break;
case ArchiveVisibilities.Self:
if (UserToken == null || UserToken.Id != ret.OwnerId) {
return null;
}
break;
}
return ret?.Enabled == false ? null : ret?.Adapt<QueryDocContentRsp>();
}
private ISelect<Sys_DocContent> QueryInternal(QueryReq<QueryDocContentReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter)
.WhereDynamic(req.Filter)
.WhereIf( //
req.Keywords?.Length > 0, a => a.Title.Contains(req.Keywords));
// 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

@ -0,0 +1,128 @@
using NetAdmin.Domain.Dto.Sys.Doc.Catalog;
using NetAdmin.Domain.Dto.Sys.Doc.Content;
namespace NetAdmin.SysComponent.Application.Services.Sys;
/// <inheritdoc cref="IDocService" />
public sealed class DocService(IDocCatalogService catalogService, IDocContentService contentService) //
: ServiceBase<IDocService>, IDocService
{
/// <inheritdoc />
public Task<int> BulkDeleteCatalogAsync(BulkReq<DelReq> req)
{
req.ThrowIfInvalid();
return catalogService.BulkDeleteAsync(req);
}
/// <inheritdoc />
public Task<int> BulkDeleteContentAsync(BulkReq<DelReq> req)
{
req.ThrowIfInvalid();
return contentService.BulkDeleteAsync(req);
}
/// <inheritdoc />
public Task<QueryDocCatalogRsp> CreateCatalogAsync(CreateDocCatalogReq req)
{
req.ThrowIfInvalid();
return catalogService.CreateAsync(req);
}
/// <inheritdoc />
public Task<QueryDocContentRsp> CreateContentAsync(CreateDocContentReq req)
{
req.ThrowIfInvalid();
return contentService.CreateAsync(req);
}
/// <inheritdoc />
public Task<int> DeleteCatalogAsync(DelReq req)
{
req.ThrowIfInvalid();
return catalogService.DeleteAsync(req);
}
/// <inheritdoc />
public Task<int> DeleteContentAsync(DelReq req)
{
req.ThrowIfInvalid();
return contentService.DeleteAsync(req);
}
/// <inheritdoc />
public Task<int> EditCatalogAsync(EditDocCatalogReq req)
{
req.ThrowIfInvalid();
return catalogService.EditAsync(req);
}
/// <inheritdoc />
public Task<QueryDocContentRsp> EditContentAsync(EditDocContentReq req)
{
req.ThrowIfInvalid();
return contentService.EditAsync(req);
}
/// <inheritdoc />
public Task<IActionResult> ExportContentAsync(QueryReq<QueryDocContentReq> req)
{
req.ThrowIfInvalid();
return contentService.ExportAsync(req);
}
/// <inheritdoc />
public Task<QueryDocCatalogRsp> GetCatalogAsync(QueryDocCatalogReq req)
{
req.ThrowIfInvalid();
return catalogService.GetAsync(req);
}
/// <inheritdoc />
public Task<QueryDocContentRsp> GetContentAsync(QueryDocContentReq req)
{
req.ThrowIfInvalid();
return contentService.GetAsync(req);
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryDocCatalogRsp>> PagedQueryCatalogAsync(PagedQueryReq<QueryDocCatalogReq> req)
{
req.ThrowIfInvalid();
return catalogService.PagedQueryAsync(req);
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryDocContentRsp>> PagedQueryContentAsync(PagedQueryReq<QueryDocContentReq> req)
{
req.ThrowIfInvalid();
return contentService.PagedQueryAsync(req);
}
/// <inheritdoc />
public Task<IEnumerable<QueryDocCatalogRsp>> QueryCatalogAsync(QueryReq<QueryDocCatalogReq> req)
{
req.ThrowIfInvalid();
return catalogService.QueryAsync(req);
}
/// <inheritdoc />
public Task<IEnumerable<QueryDocContentRsp>> QueryContentAsync(QueryReq<QueryDocContentReq> req)
{
req.ThrowIfInvalid();
return contentService.QueryAsync(req);
}
/// <inheritdoc />
public Task<int> SetEnabledAsync(SetDocContentEnabledReq req)
{
req.ThrowIfInvalid();
return contentService.SetEnabledAsync(req);
}
/// <inheritdoc />
public Task<QueryDocContentRsp> ViewContentAsync(QueryDocContentReq req)
{
req.ThrowIfInvalid();
return contentService.ViewAsync(req);
}
}

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Cache.Sys.Dependency;
/// <summary>
/// 文档缓存
/// </summary>
public interface IDocCache : ICache<IDistributedCache, IDocService>, IDocModule;

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Cache.Sys.Dependency;
/// <summary>
/// 文档分类缓存
/// </summary>
public interface IDocCatalogCache : ICache<IDistributedCache, IDocCatalogService>, IDocCatalogModule;

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Cache.Sys.Dependency;
/// <summary>
/// 文档内容缓存
/// </summary>
public interface IDocContentCache : ICache<IDistributedCache, IDocContentService>, IDocContentModule;

View File

@ -0,0 +1,111 @@
using NetAdmin.Domain.Dto.Sys.Doc.Catalog;
using NetAdmin.Domain.Dto.Sys.Doc.Content;
namespace NetAdmin.SysComponent.Cache.Sys;
/// <inheritdoc cref="IDocCache" />
public sealed class DocCache(IDistributedCache cache, IDocService service) //
: DistributedCache<IDocService>(cache, service), IScoped, IDocCache
{
/// <inheritdoc />
public Task<int> BulkDeleteCatalogAsync(BulkReq<DelReq> req)
{
return Service.BulkDeleteCatalogAsync(req);
}
/// <inheritdoc />
public Task<int> BulkDeleteContentAsync(BulkReq<DelReq> req)
{
return Service.BulkDeleteContentAsync(req);
}
/// <inheritdoc />
public Task<QueryDocCatalogRsp> CreateCatalogAsync(CreateDocCatalogReq req)
{
return Service.CreateCatalogAsync(req);
}
/// <inheritdoc />
public Task<QueryDocContentRsp> CreateContentAsync(CreateDocContentReq req)
{
return Service.CreateContentAsync(req);
}
/// <inheritdoc />
public Task<int> DeleteCatalogAsync(DelReq req)
{
return Service.DeleteCatalogAsync(req);
}
/// <inheritdoc />
public Task<int> DeleteContentAsync(DelReq req)
{
return Service.DeleteContentAsync(req);
}
/// <inheritdoc />
public Task<int> EditCatalogAsync(EditDocCatalogReq req)
{
return Service.EditCatalogAsync(req);
}
/// <inheritdoc />
public Task<QueryDocContentRsp> EditContentAsync(EditDocContentReq req)
{
return Service.EditContentAsync(req);
}
/// <inheritdoc />
public Task<IActionResult> ExportContentAsync(QueryReq<QueryDocContentReq> req)
{
return Service.ExportContentAsync(req);
}
/// <inheritdoc />
public Task<QueryDocCatalogRsp> GetCatalogAsync(QueryDocCatalogReq req)
{
return Service.GetCatalogAsync(req);
}
/// <inheritdoc />
public Task<QueryDocContentRsp> GetContentAsync(QueryDocContentReq req)
{
return Service.GetContentAsync(req);
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryDocCatalogRsp>> PagedQueryCatalogAsync(PagedQueryReq<QueryDocCatalogReq> req)
{
return Service.PagedQueryCatalogAsync(req);
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryDocContentRsp>> PagedQueryContentAsync(PagedQueryReq<QueryDocContentReq> req)
{
return Service.PagedQueryContentAsync(req);
}
/// <inheritdoc />
public Task<IEnumerable<QueryDocCatalogRsp>> QueryCatalogAsync(QueryReq<QueryDocCatalogReq> req)
{
return Service.QueryCatalogAsync(req);
}
/// <inheritdoc />
public Task<IEnumerable<QueryDocContentRsp>> QueryContentAsync(QueryReq<QueryDocContentReq> req)
{
return Service.QueryContentAsync(req);
}
/// <inheritdoc />
public Task<int> SetEnabledAsync(SetDocContentEnabledReq req)
{
return Service.SetEnabledAsync(req);
}
/// <inheritdoc />
public Task<QueryDocContentRsp> ViewContentAsync(QueryDocContentReq req)
{
return Service.ViewContentAsync(req);
}
}

View File

@ -0,0 +1,62 @@
using NetAdmin.Domain.Dto.Sys.Doc.Catalog;
namespace NetAdmin.SysComponent.Cache.Sys;
/// <inheritdoc cref="IDocCatalogCache" />
public sealed class DocCatalogCache(IDistributedCache cache, IDocCatalogService service)
: DistributedCache<IDocCatalogService>(cache, service), IScoped, IDocCatalogCache
{
/// <inheritdoc />
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Service.BulkDeleteAsync(req);
}
/// <inheritdoc />
public Task<long> CountAsync(QueryReq<QueryDocCatalogReq> req)
{
return Service.CountAsync(req);
}
/// <inheritdoc />
public Task<QueryDocCatalogRsp> CreateAsync(CreateDocCatalogReq req)
{
return Service.CreateAsync(req);
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
return Service.DeleteAsync(req);
}
/// <inheritdoc />
public Task<bool> ExistAsync(QueryReq<QueryDocCatalogReq> req)
{
return Service.ExistAsync(req);
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryDocCatalogReq> req)
{
return Service.ExportAsync(req);
}
/// <inheritdoc />
public Task<QueryDocCatalogRsp> GetAsync(QueryDocCatalogReq req)
{
return Service.GetAsync(req);
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryDocCatalogRsp>> PagedQueryAsync(PagedQueryReq<QueryDocCatalogReq> req)
{
return Service.PagedQueryAsync(req);
}
/// <inheritdoc />
public Task<IEnumerable<QueryDocCatalogRsp>> QueryAsync(QueryReq<QueryDocCatalogReq> req)
{
return Service.QueryAsync(req);
}
}

View File

@ -0,0 +1,68 @@
using NetAdmin.Domain.Dto.Sys.Doc.Content;
namespace NetAdmin.SysComponent.Cache.Sys;
/// <inheritdoc cref="IDocContentCache" />
public sealed class DocContentCache(IDistributedCache cache, IDocContentService service)
: DistributedCache<IDocContentService>(cache, service), IScoped, IDocContentCache
{
/// <inheritdoc />
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Service.BulkDeleteAsync(req);
}
/// <inheritdoc />
public Task<long> CountAsync(QueryReq<QueryDocContentReq> req)
{
return Service.CountAsync(req);
}
/// <inheritdoc />
public Task<QueryDocContentRsp> CreateAsync(CreateDocContentReq req)
{
return Service.CreateAsync(req);
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
return Service.DeleteAsync(req);
}
/// <inheritdoc />
public Task<bool> ExistAsync(QueryReq<QueryDocContentReq> req)
{
return Service.ExistAsync(req);
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryDocContentReq> req)
{
return Service.ExportAsync(req);
}
/// <inheritdoc />
public Task<QueryDocContentRsp> GetAsync(QueryDocContentReq req)
{
return Service.GetAsync(req);
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryDocContentRsp>> PagedQueryAsync(PagedQueryReq<QueryDocContentReq> req)
{
return Service.PagedQueryAsync(req);
}
/// <inheritdoc />
public Task<IEnumerable<QueryDocContentRsp>> QueryAsync(QueryReq<QueryDocContentReq> req)
{
return Service.QueryAsync(req);
}
/// <inheritdoc />
public Task<int> SetEnabledAsync(SetDocContentEnabledReq req)
{
return Service.SetEnabledAsync(req);
}
}

View File

@ -0,0 +1,157 @@
using NetAdmin.Domain.Dto.Sys.Doc.Catalog;
using NetAdmin.Domain.Dto.Sys.Doc.Content;
namespace NetAdmin.SysComponent.Host.Controllers.Sys;
/// <summary>
/// 文档服务
/// </summary>
[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))]
[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)]
public sealed class DocController(IDocCache cache) : ControllerBase<IDocCache, IDocService>(cache), IDocModule
{
/// <summary>
/// 批量删除文档分类
/// </summary>
[Transaction]
public Task<int> BulkDeleteCatalogAsync(BulkReq<DelReq> req)
{
return Cache.BulkDeleteCatalogAsync(req);
}
/// <summary>
/// 批量删除文档内容
/// </summary>
[Transaction]
public Task<int> BulkDeleteContentAsync(BulkReq<DelReq> req)
{
return Cache.BulkDeleteContentAsync(req);
}
/// <summary>
/// 创建文档分类
/// </summary>
[Transaction]
public Task<QueryDocCatalogRsp> CreateCatalogAsync(CreateDocCatalogReq req)
{
return Cache.CreateCatalogAsync(req);
}
/// <summary>
/// 创建文档内容
/// </summary>
[Transaction]
public Task<QueryDocContentRsp> CreateContentAsync(CreateDocContentReq req)
{
return Cache.CreateContentAsync(req);
}
/// <summary>
/// 删除文档分类
/// </summary>
[Transaction]
public Task<int> DeleteCatalogAsync(DelReq req)
{
return Cache.DeleteCatalogAsync(req);
}
/// <summary>
/// 删除文档内容
/// </summary>
[Transaction]
public Task<int> DeleteContentAsync(DelReq req)
{
return Cache.DeleteContentAsync(req);
}
/// <summary>
/// 编辑文档分类
/// </summary>
[Transaction]
public Task<int> EditCatalogAsync(EditDocCatalogReq req)
{
return Cache.EditCatalogAsync(req);
}
/// <summary>
/// 编辑文档内容
/// </summary>
[Transaction]
public Task<QueryDocContentRsp> EditContentAsync(EditDocContentReq req)
{
return Cache.EditContentAsync(req);
}
/// <summary>
/// 导出文档内容
/// </summary>
public Task<IActionResult> ExportContentAsync(QueryReq<QueryDocContentReq> req)
{
return Cache.ExportContentAsync(req);
}
/// <summary>
/// 获取单个文档分类
/// </summary>
public Task<QueryDocCatalogRsp> GetCatalogAsync(QueryDocCatalogReq req)
{
return Cache.GetCatalogAsync(req);
}
/// <summary>
/// 获取单个文档内容
/// </summary>
public Task<QueryDocContentRsp> GetContentAsync(QueryDocContentReq req)
{
return Cache.GetContentAsync(req);
}
/// <summary>
/// 分页查询文档分类
/// </summary>
public Task<PagedQueryRsp<QueryDocCatalogRsp>> PagedQueryCatalogAsync(PagedQueryReq<QueryDocCatalogReq> req)
{
return Cache.PagedQueryCatalogAsync(req);
}
/// <summary>
/// 分页查询文档内容
/// </summary>
public Task<PagedQueryRsp<QueryDocContentRsp>> PagedQueryContentAsync(PagedQueryReq<QueryDocContentReq> req)
{
return Cache.PagedQueryContentAsync(req);
}
/// <summary>
/// 查询文档分类
/// </summary>
public Task<IEnumerable<QueryDocCatalogRsp>> QueryCatalogAsync(QueryReq<QueryDocCatalogReq> req)
{
return Cache.QueryCatalogAsync(req);
}
/// <summary>
/// 查询文档内容
/// </summary>
public Task<IEnumerable<QueryDocContentRsp>> QueryContentAsync(QueryReq<QueryDocContentReq> req)
{
return Cache.QueryContentAsync(req);
}
/// <summary>
/// 启用/禁用文档内容
/// </summary>
public Task<int> SetEnabledAsync(SetDocContentEnabledReq req)
{
return Cache.SetEnabledAsync(req);
}
/// <summary>
/// 浏览文档内容
/// </summary>
[AllowAnonymous]
public Task<QueryDocContentRsp> ViewContentAsync(QueryDocContentReq req)
{
return Cache.ViewContentAsync(req);
}
}

View File

@ -12,7 +12,7 @@
"@element-plus/icons-vue": "2.3.1",
"ace-builds": "1.36.5",
"aieditor": "1.2.7",
"axios": "1.7.7",
"axios": "1.7.8",
"crypto-js": "4.2.0",
"echarts": "5.5.1",
"element-plus": "2.8.8",
@ -20,11 +20,11 @@
"markdown-it": "14.1.0",
"markdown-it-emoji": "3.0.0",
"nprogress": "0.2.0",
"sortablejs": "1.15.3",
"sortablejs": "1.15.4",
"vkbeautify": "0.99.3",
"vue": "3.5.13",
"vue-i18n": "10.0.4",
"vue-router": "4.4.5",
"vue-router": "4.5.0",
"vue3-ace-editor": "2.2.4",
"vue3-json-viewer": "2.2.2",
"vuedraggable": "4.0.3",
@ -32,7 +32,7 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "5.2.0",
"prettier": "3.3.3",
"prettier": "3.4.0",
"prettier-plugin-organize-attributes": "1.0.0",
"sass": "1.81.0",
"terser": "5.36.0",

View File

@ -0,0 +1,194 @@
/**
* 文档服务
* @module @/api/sys/doc
*/
import config from '@/config'
import http from '@/utils/request'
export default {
/**
* 批量删除文档分类
*/
bulkDeleteCatalog: {
url: `${config.API_URL}/api/sys/doc/bulk.delete.catalog`,
name: `批量删除文档分类`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 批量删除文档内容
*/
bulkDeleteContent: {
url: `${config.API_URL}/api/sys/doc/bulk.delete.content`,
name: `批量删除文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 创建文档分类
*/
createCatalog: {
url: `${config.API_URL}/api/sys/doc/create.catalog`,
name: `创建文档分类`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 创建文档内容
*/
createContent: {
url: `${config.API_URL}/api/sys/doc/create.content`,
name: `创建文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 删除文档分类
*/
deleteCatalog: {
url: `${config.API_URL}/api/sys/doc/delete.catalog`,
name: `删除文档分类`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 删除文档内容
*/
deleteContent: {
url: `${config.API_URL}/api/sys/doc/delete.content`,
name: `删除文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 编辑文档分类
*/
editCatalog: {
url: `${config.API_URL}/api/sys/doc/edit.catalog`,
name: `编辑文档分类`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 编辑文档内容
*/
editContent: {
url: `${config.API_URL}/api/sys/doc/edit.content`,
name: `编辑文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 导出文档内容
*/
exportContent: {
url: `${config.API_URL}/api/sys/doc/export.content`,
name: `导出文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取单个文档分类
*/
getCatalog: {
url: `${config.API_URL}/api/sys/doc/get.catalog`,
name: `获取单个文档分类`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取单个文档内容
*/
getContent: {
url: `${config.API_URL}/api/sys/doc/get.content`,
name: `获取单个文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询文档分类
*/
pagedQueryCatalog: {
url: `${config.API_URL}/api/sys/doc/paged.query.catalog`,
name: `分页查询文档分类`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询文档内容
*/
pagedQueryContent: {
url: `${config.API_URL}/api/sys/doc/paged.query.content`,
name: `分页查询文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 查询文档分类
*/
queryCatalog: {
url: `${config.API_URL}/api/sys/doc/query.catalog`,
name: `查询文档分类`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 查询文档内容
*/
queryContent: {
url: `${config.API_URL}/api/sys/doc/query.content`,
name: `查询文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 启用/禁用文档内容
*/
setEnabled: {
url: `${config.API_URL}/api/sys/doc/set.enabled`,
name: `启用/禁用文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 浏览文档内容
*/
viewContent: {
url: `${config.API_URL}/api/sys/doc/view.content`,
name: `浏览文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -0,0 +1,73 @@
/**
* 文档分类服务
* @module @/api/sys/doc.catalog
*/
import config from '@/config'
import http from '@/utils/request'
export default {
/**
* 批量删除文档分类
*/
bulkDelete: {
url: `${config.API_URL}/api/sys/doc.catalog/bulk.delete`,
name: `批量删除文档分类`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 文档分类计数
*/
count: {
url: `${config.API_URL}/api/sys/doc.catalog/count`,
name: `文档分类计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 创建文档分类
*/
create: {
url: `${config.API_URL}/api/sys/doc.catalog/create`,
name: `创建文档分类`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 删除文档分类
*/
delete: {
url: `${config.API_URL}/api/sys/doc.catalog/delete`,
name: `删除文档分类`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取单个文档分类
*/
get: {
url: `${config.API_URL}/api/sys/doc.catalog/get`,
name: `获取单个文档分类`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询文档分类
*/
pagedQuery: {
url: `${config.API_URL}/api/sys/doc.catalog/paged.query`,
name: `分页查询文档分类`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -0,0 +1,84 @@
/**
* 文档内容服务
* @module @/api/sys/doc.content
*/
import config from '@/config'
import http from '@/utils/request'
export default {
/**
* 批量删除文档内容
*/
bulkDelete: {
url: `${config.API_URL}/api/sys/doc.content/bulk.delete`,
name: `批量删除文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 文档内容计数
*/
count: {
url: `${config.API_URL}/api/sys/doc.content/count`,
name: `文档内容计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 创建文档内容
*/
create: {
url: `${config.API_URL}/api/sys/doc.content/create`,
name: `创建文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 删除文档内容
*/
delete: {
url: `${config.API_URL}/api/sys/doc.content/delete`,
name: `删除文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取单个文档内容
*/
get: {
url: `${config.API_URL}/api/sys/doc.content/get`,
name: `获取单个文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询文档内容
*/
pagedQuery: {
url: `${config.API_URL}/api/sys/doc.content/paged.query`,
name: `分页查询文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 启用/禁用文档内容
*/
setEnabled: {
url: `${config.API_URL}/api/sys/doc.content/set.enabled`,
name: `启用/禁用文档内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -11,7 +11,7 @@
<p v-if="$attrs.nestProp2">{{ $TOOL.getNestedProperty(row, $attrs.nestProp2) }}</p>
</div>
</div>
<save-dialog v-if="dialog.save" @closed="dialog.save = false" ref="saveDialog"></save-dialog>
<save-dialog v-if="dialog.save" @closed="(dialog.save = false)" ref="saveDialog"></save-dialog>
</template>
</el-table-column>
</template>

View File

@ -293,7 +293,7 @@
</div>
<template #footer>
<el-button @click="dialogVisible = false">{{ $t('取消') }}</el-button>
<el-button @click="(dialogVisible = false)">{{ $t('取消') }}</el-button>
<el-button @click="submit()" type="primary">{{ $t('确认') }}</el-button>
</template>
</el-dialog>

View File

@ -49,7 +49,7 @@
</div>
<template #footer>
<el-button @click="clear" text>{{ $t('清除') }}</el-button>
<el-button @click="dialogVisible = false">{{ $t('取消') }}</el-button>
<el-button @click="(dialogVisible = false)">{{ $t('取消') }}</el-button>
</template>
</el-dialog>
</div>

View File

@ -13,7 +13,7 @@
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button @click="submit" type="primary">{{ $t('确定') }}</el-button>
</template>
</sc-dialog>

View File

@ -90,8 +90,8 @@
:hide-after="0"
:title="$t('列设置')"
:width="500"
@after-leave="customColumnShow = false"
@show="customColumnShow = true"
@after-leave="(customColumnShow = false)"
@show="(customColumnShow = true)"
placement="top"
trigger="click">
<template #reference>

View File

@ -51,7 +51,7 @@
<el-dialog v-model="cropperDialogVisible" :title="$t('剪裁')" :width="580" @closed="cropperClosed" destroy-on-close draggable>
<sc-cropper :aspectRatio="aspectRatio" :compress="compress" :src="cropperFile.tempCropperFile" ref="cropper"></sc-cropper>
<template #footer>
<el-button @click="cropperDialogVisible = false">{{ $t('取消') }}</el-button>
<el-button @click="(cropperDialogVisible = false)">{{ $t('取消') }}</el-button>
<el-button @click="cropperSave" type="primary">{{ $t('确定') }}</el-button>
</template>
</el-dialog>

View File

@ -55,7 +55,6 @@ import naColTags from '@/components/naColTags/index.vue'
import naColTime from '@/components/naColTime/index.vue'
import naColUser from '@/components/naColUser/index.vue'
import naDept from '@/components/naDept/index.vue'
import naDicCatalog from '@/components/naDicCatalog/index.vue'
import naFormEmail from '@/components/naFormEmail/index.vue'
import naSearch from '@/components/naSearch'
import naUserSelect from '@/components/naUserSelect/index.vue'
@ -98,7 +97,6 @@ export default {
app.component('naColTime', naColTime)
app.component('naColUser', naColUser)
app.component('naDept', naDept)
app.component('naDicCatalog', naDicCatalog)
app.component('naFormEmail', naFormEmail)
app.component('naSearch', naSearch)
app.component('naUserSelect', naUserSelect)

View File

@ -53,7 +53,7 @@
</el-footer>
</el-container>
<save-dialog v-if="dialog.save" @closed="dialog.save = null" @mounted="$refs.saveDialog.open(dialog.save)" ref="saveDialog"></save-dialog>
<save-dialog v-if="dialog.save" @closed="(dialog.save = null)" @mounted="$refs.saveDialog.open(dialog.save)" ref="saveDialog"></save-dialog>
</template>
<script>

View File

@ -71,11 +71,11 @@
</div>
<el-dialog v-model="searchVisible" :title="$t('搜索')" :width="700" center destroy-on-close>
<search @success="searchVisible = false"></search>
<search @success="(searchVisible = false)"></search>
</el-dialog>
<el-drawer v-model="tasksVisible" :size="450" :title="$t('作业中心')" destroy-on-close>
<tasks :fail="failJobCnt" @closed="tasksVisible = false"></tasks>
<tasks :fail="failJobCnt" @closed="(tasksVisible = false)"></tasks>
</el-drawer>
</template>

View File

@ -84,7 +84,7 @@ export default {
失败: 'Failed',
头像: 'Avatar',
婚姻状况: 'Marital status',
字典分类: 'Dictionary Classification',
字典目录: 'Dictionary catalog',
客户端IP: 'Client IP',
密码: 'Password',
: 'Small',
@ -380,7 +380,7 @@ export default {
响应体: 'Response body',
响应头: 'Response header',
执行时间编号: 'Execution time ID',
新增字典: 'Add dictionary',
新增字典目录: 'Add dictionary catalog',
字典名称: 'Dictionary name',
字典编码: 'Dictionary code',
父路径: 'Parent path',

View File

@ -84,7 +84,7 @@ export default {
失败: '失败',
头像: '头像',
婚姻状况: '婚姻状况',
字典分类: '字典分类',
字典目录: '字典目录',
客户端IP: '客户端IP',
密码: '密码',
: '小',
@ -379,7 +379,7 @@ export default {
响应体: '响应体',
响应头: '响应头',
执行时间编号: '执行时间编号',
新增字典: '新增字典',
新增字典目录: '新增字典目录',
字典名称: '字典名称',
字典编码: '字典编码',
父路径: '父路径',

View File

@ -68,6 +68,13 @@ const routes = [
title: '重置密码',
},
},
{
path: '/guest/view-doc/:id',
component: () => import(/* webpackChunkName: "view-doc" */ '@/views/guest/view-doc'),
meta: {
title: '查看文档',
},
},
]
export default routes

View File

@ -31,7 +31,7 @@
</el-form-item>
<el-form-item label="" prop="agree">
<el-checkbox v-model="form.agree" label="">{{ $t('我已阅读并同意') }}</el-checkbox>
<span @click="showAgree = true" class="link">{{ $t('平台服务协议') }}</span>
<span @click="(showAgree = true)" class="link">{{ $t('平台服务协议') }}</span>
</el-form-item>
</el-form>
<el-form v-if="stepActive === 1" :model="form" :rules="rules" ref="stepForm_1" size="large">
@ -57,7 +57,7 @@
</el-form>
<el-dialog v-model="showAgree" :title="$t('平台服务协议')" destroy-on-close>
<template #footer>
<el-button @click="showAgree = false">{{ $t('取消') }}</el-button>
<el-button @click="(showAgree = false)">{{ $t('取消') }}</el-button>
<el-button
@click="
() => {

View File

@ -0,0 +1,61 @@
<template>
<article v-if="doc" v-loading="loading" class="article">
<h1>{{ doc.title }}</h1>
<section
:class="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'aie-theme-dark' : 'aie-theme-light'"
ref="editor"></section>
</article>
<not-found v-else></not-found>
</template>
<script>
import { AiEditor } from 'aieditor'
import 'aieditor/dist/style.css'
import { defineAsyncComponent } from 'vue'
const notFound = defineAsyncComponent(() => import('@/layout/other/404.vue'))
import sysConfig from '@/config'
import tool from '@/utils/tool'
export default {
components: { notFound },
data() {
return {
loading: true,
doc: { title: '' },
}
},
async created() {
const res = await this.$API.sys_doc.viewContent.post({ id: this.$route.params.id })
this.doc = res.data
this.loading = false
if (this.doc) {
document.title = this.doc.title
const aiEditor = new AiEditor({
element: this.$refs.editor,
content: this.doc.body,
onChange: (body) => {
this.doc.body = body.getHtml()
},
})
aiEditor.changeLang(this.$TOOL.data.get('APP_SET_LANG') || sysConfig.APP_SET_LANG)
} else {
await tool.data.set('LOGIN_REDIRECT', window.location.href)
}
},
methods: {},
}
</script>
<style scoped>
.article {
display: flex;
height: 100%;
flex-direction: column;
> h1 {
padding: 1rem;
}
> section {
flex: 1;
}
}
</style>

View File

@ -77,7 +77,7 @@
</div>
</el-header>
<el-header style="height: auto">
<el-button @click="this.dialog.customLayout = { title: '添加自定义布局' }" style="margin: 0 auto">添加自定义布局</el-button>
<el-button @click="(this.dialog.customLayout = { title: '添加自定义布局' })" style="margin: 0 auto">添加自定义布局</el-button>
</el-header>
<el-main class="nopadding">
<div class="widgets-list">
@ -115,7 +115,7 @@
<custom-layout-dialog
v-if="dialog.customLayout"
@closed="dialog.customLayout = null"
@closed="(dialog.customLayout = null)"
@mounted="$refs.customLayoutDialog.open(dialog.customLayout)"
@onCustomLayout="(l) => (customLayouts = [l])"
ref="customLayoutDialog"></custom-layout-dialog>

View File

@ -52,7 +52,7 @@
</draggable>
</div>
<template #footer>
<el-button @click="modsDrawer = false">{{ $t('取消') }}</el-button>
<el-button @click="(modsDrawer = false)">{{ $t('取消') }}</el-button>
<el-button @click="saveMods" type="primary">{{ $t('保存') }}</el-button>
</template>
</el-drawer>

View File

@ -36,18 +36,18 @@
<set-mobile-dialog
v-if="dialog.setMobile"
@closed="dialog.setMobile = null"
@closed="(dialog.setMobile = null)"
@mounted="$refs.setMobileDialog.open(dialog.setMobile)"
@success="setSuccess"
ref="setMobileDialog"></set-mobile-dialog>
<set-password-dialog
v-if="dialog.setPassword"
@closed="dialog.setPassword = null"
@closed="(dialog.setPassword = null)"
@mounted="$refs.setPasswordDialog.open(dialog.setPassword)"
ref="setPasswordDialog"></set-password-dialog>
<set-email-dialog
v-if="dialog.setEmail"
@closed="dialog.setEmail = null"
@closed="(dialog.setEmail = null)"
@mounted="$refs.setEmailDialog.open(dialog.setEmail)"
@success="setSuccess"
ref="setEmailDialog"></set-email-dialog>

View File

@ -28,7 +28,7 @@
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</el-dialog>

View File

@ -33,7 +33,7 @@
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</el-dialog>

View File

@ -34,7 +34,7 @@
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</el-dialog>

View File

@ -39,7 +39,7 @@
ref="search" />
</div>
<div class="right-panel">
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<el-button @click="(this.dialog.save = { mode: 'add' })" icon="el-icon-plus" type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_config.bulkDelete" :vue="this" />
<el-dropdown v-show="this.selection.length > 0">
<el-button type="primary">
@ -106,7 +106,7 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@closed="(dialog.save = null)"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => $refs.table.upData()"
ref="saveDialog"></save-dialog>

View File

@ -44,7 +44,7 @@
</el-tabs>
</div>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>

View File

@ -46,7 +46,7 @@
ref="search" />
</div>
<div class="right-panel">
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<el-button @click="(this.dialog.save = { mode: 'add' })" icon="el-icon-plus" type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_dept.bulkDelete" :vue="this" />
<el-dropdown v-show="this.selection.length > 0">
<el-button type="primary">
@ -113,7 +113,7 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@closed="(dialog.save = null)"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>

View File

@ -42,7 +42,7 @@
</el-tabs>
</div>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>

View File

@ -38,7 +38,7 @@
</el-main>
<el-footer>
<el-button @click="add(form.catalogId)" icon="el-icon-plus" size="small" style="width: 100%" type="primary">{{
$t('字典分类')
$t('字典目录')
}}</el-button>
</el-footer>
</el-container>
@ -48,7 +48,7 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@closed="(dialog.save = null)"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => getData()"
ref="saveDialog"></save-dialog>
@ -106,7 +106,7 @@ export default {
},
//
async add(id, code) {
this.dialog.save = { mode: 'add', data: { catalogId: id, code: code + '>' } }
this.dialog.save = { mode: 'add', data: { catalogId: id, code: code ? code + '>' : '' } }
},
//
async edit(data) {

View File

@ -23,8 +23,8 @@
:controls="[
{
type: 'input',
field: ['dy', 'keywords'],
placeholder: $t('项名 / 项值'),
field: ['root', 'keywords'],
placeholder: $t('项名 / 项值 / 备注'),
style: 'width:20rem',
},
]"
@ -38,7 +38,7 @@
</div>
<div class="right-panel">
<el-button
@click="this.dialog.save = { mode: 'add', data: { catalogId: this.catalogId } }"
@click="(this.dialog.save = { mode: 'add', data: { catalogId: this.catalogId } })"
icon="el-icon-plus"
type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_dic.bulkDeleteContent" :vue="this" />
@ -53,7 +53,7 @@
<el-dropdown-menu>
<el-dropdown-item @click="setEnabled(true)">{{ $t('启用') }}</el-dropdown-item>
<el-dropdown-item @click="setEnabled(false)">{{ $t('禁用') }}</el-dropdown-item>
<el-dropdown-item @click="this.dialog.savebatch = { mode: 'batchedit', data: { catalogId: this.catalogId } }">{{
<el-dropdown-item @click="(this.dialog.savebatch = { mode: 'batchedit', data: { catalogId: this.catalogId } })">{{
$t('设置项值')
}}</el-dropdown-item>
</el-dropdown-menu>
@ -64,7 +64,7 @@
<el-main class="nopadding">
<sc-table
:before-post="(data) => data.dynamicFilter.filters.length > 0"
:context-menus="['key', 'value', 'enabled', 'createdTime']"
:context-menus="['key', 'value', 'enabled', 'createdTime', 'id', 'summary']"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:export-api="$API.sys_dic.exportContent"
:params="query"
@ -80,6 +80,7 @@
row-key="id"
stripe>
<el-table-column type="selection" width="50" />
<na-col-id :label="$t('唯一编码')" prop="id" sortable="custom" width="170" />
<el-table-column :label="$t('项名')" prop="key" sortable="custom" />
<el-table-column :label="$t('项值')" prop="value" sortable="custom" />
<el-table-column :label="$t('备注')" prop="summary" sortable="custom" />
@ -88,7 +89,6 @@
<el-switch v-model="row.enabled" @change="changeSwitch($event, row)"></el-switch>
</template>
</el-table-column>
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" width="170" />
<na-col-operation
:buttons="
naColOperation.buttons.concat({
@ -107,14 +107,14 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@closed="(dialog.save = null)"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
<savebatch-dialog
v-if="dialog.savebatch"
@closed="dialog.savebatch = null"
@closed="(dialog.savebatch = null)"
@mounted="$refs.savebatchDialog.open(dialog.savebatch)"
@success="(data, mode) => batchsuccess(data, mode)"
ref="savebatchDialog"></savebatch-dialog>
@ -222,25 +222,7 @@ export default {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
operator: 'dateRange',
value: form.dy.createdTime,
})
}
if (form.dy.keywords) {
this.query.dynamicFilter.filters.push({
logic: 'or',
filters: [
{
field: 'key',
operator: 'contains',
value: form.dy.keywords,
},
{
field: 'value',
operator: 'contains',
value: form.dy.keywords,
},
],
value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')),
})
}

View File

@ -4,8 +4,8 @@
<el-tabs tab-position="top">
<el-tab-pane :label="$t('基本信息')">
<el-form :disabled="mode === 'view'" :model="form" :rules="rules" label-width="10rem" ref="dialogForm">
<el-form-item :label="$t('所属字典')" prop="catalogId">
<na-dic-catalog v-model="form.catalogId" class="w100p" />
<el-form-item :label="$t('所属字典目录')" prop="catalogId">
<catalog-select v-model="form.catalogId" class="w100p" />
</el-form-item>
<el-form-item :label="$t('项名')" prop="key">
<el-input v-model="form.key" clearable></el-input>
@ -30,15 +30,17 @@
</el-tabs>
</div>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
const catalogSelect = defineAsyncComponent(() => import('../components/catalog-select.vue'))
export default {
components: {},
components: { catalogSelect },
data() {
return {
//
@ -47,7 +49,7 @@ export default {
mode: 'add',
//
rules: {
catalogId: [{ required: true, message: '请选择所属字典' }],
catalogId: [{ required: true, message: '请选择所属字典目录' }],
key: [{ required: true, message: '请输入项名' }],
value: [{ required: true, message: '请输入项值' }],
},

View File

@ -21,7 +21,7 @@
</el-tabs>
</div>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>

View File

@ -1,25 +1,28 @@
<template>
<sc-dialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" @closed="$emit('closed')" destroy-on-close>
<el-form v-loading="loading" :model="form" :rules="rules" label-width="10rem" ref="dialogForm">
<el-form-item :label="$t('字典名称')" prop="name">
<el-input v-model="form.name" :placeholder="$t('字典名称')" clearable></el-input>
<el-form-item :label="$t('字典目录名称')" prop="name">
<el-input v-model="form.name" :placeholder="$t('字典目录名称')" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('字典编码')" prop="code">
<el-input v-model="form.code" :placeholder="$t('字典编码')" clearable></el-input>
<el-form-item :label="$t('字典目录编码')" prop="code">
<el-input v-model="form.code" :placeholder="$t('字典目录编码')" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('父路径')" prop="parentId">
<na-dic-catalog v-model="form.parentId" class="w100p" />
<catalog-select v-model="form.parentId" class="w100p" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
const catalogSelect = defineAsyncComponent(() => import('./components/catalog-select.vue'))
export default {
components: { catalogSelect },
data() {
return {
//
@ -28,12 +31,12 @@ export default {
mode: 'add',
//
rules: {
code: [{ required: true, message: '请输入编码' }],
name: [{ required: true, message: '请输入字典名称' }],
code: [{ required: true, message: '请输入字典目录编码' }],
name: [{ required: true, message: '请输入字典目录名称' }],
},
titleMap: {
add: this.$t('新增字典'),
edit: this.$t('编辑字典'),
add: this.$t('新增字典目录'),
edit: this.$t('编辑字典目录'),
},
visible: false,
}

View File

@ -0,0 +1,52 @@
<template>
<el-cascader
v-bind="$attrs"
v-model="data"
:options="options"
:placeholder="placeholder"
:props="{
value: 'id',
label: 'name',
emitPath: false,
checkStrictly: true,
expandTrigger: 'hover',
}"
clearable></el-cascader>
</template>
<style scoped></style>
<script>
export default {
props: {
modelValue: { type: Object },
placeholder: { type: String },
},
data() {
return {
loaded: false,
data: null,
options: [],
}
},
watch: {
data(n) {
this.$emit('update:modelValue', n)
},
modelValue: {
immediate: true,
deep: true,
handler(n) {
this.data = n ?? null
},
},
},
mounted() {},
async created() {
const res = await this.$API.sys_doc.queryCatalog.post()
this.options = res.data
},
components: {},
computed: {},
methods: {},
}
</script>

View File

@ -0,0 +1,167 @@
<template>
<el-container>
<el-aside v-loading="loading" width="40rem">
<el-container>
<el-header>
<el-input v-model="filterText" :placeholder="$t('输入关键字进行过滤')" clearable></el-input>
</el-header>
<el-main class="nopadding">
<el-tree
:data="data"
:expand-on-click-node="false"
:filter-node-method="filterNode"
:highlight-current="true"
:props="{
label: 'name',
}"
@node-click="click"
default-expand-all
node-key="id"
ref="doc">
<template #default="{ _, data }">
<div class="custom-tree-node">
<span>{{ data.name }} {{ data.code }}</span>
<span class="btn">
<el-button-group size="small">
<el-button @click.stop="add(data.id, data.code)" icon="el-icon-plus"></el-button>
<el-button @click.stop="edit(data)" icon="el-icon-edit"></el-button>
<el-popconfirm :title="$t('确定删除 {item} 吗?', { item: data.name })" @confirm="del(data)" width="20rem">
<template #reference>
<el-button @click.stop="() => {}" icon="el-icon-delete"></el-button>
</template>
</el-popconfirm>
</el-button-group>
</span>
</div>
</template>
</el-tree>
</el-main>
<el-footer>
<el-button @click="add(form.catalogId)" icon="el-icon-plus" size="small" style="width: 100%" type="primary">{{
$t('文档分类')
}}</el-button>
</el-footer>
</el-container>
</el-aside>
<list :catalogId="form.catalogId" />
</el-container>
<save-dialog
v-if="dialog.save"
@closed="(dialog.save = null)"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => getData()"
ref="saveDialog"></save-dialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
import list from './list'
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
components: {
list,
saveDialog,
},
computed: {},
created() {},
data() {
return {
data: [],
dialog: {},
filterText: '',
form: {},
loading: false,
query: {
dynamicFilter: {
filters: [],
},
filter: {},
keywords: this.keywords,
},
}
},
inject: ['reload'],
methods: {
//
async getData() {
this.loading = true
const res = await this.$API.sys_doc.queryCatalog.post({ prop: 'name', order: 'ascending' })
this.loading = false
this.data = res.data
//, &
if (this.data.length > 0) {
await this.$nextTick()
this.$refs.doc.setCurrentKey(this.data[0].id)
this.form.catalogId = this.data[0].id
}
},
//
filterNode(value, data) {
if (!value) return true
const targetText = data.name + data.code
return targetText.indexOf(value) !== -1
},
//
async add(id, code) {
this.dialog.save = { mode: 'add', data: { catalogId: id, code: code ? code + '>' : '' } }
},
//
async edit(data) {
this.dialog.save = { mode: 'edit', row: data }
},
//
click(data) {
this.form.catalogId = data.id
},
//
async del(data) {
this.loading = true
try {
await this.$API.sys_doc.deleteCatalog.post({
id: data.id,
})
this.$message.success(this.$t('操作成功'))
} catch {
//
}
this.loading = false
await this.getData()
},
},
mounted() {
this.getData()
},
props: ['keywords'],
watch: {
filterText(val) {
this.$refs.doc.filter(val)
},
},
}
</script>
<style lang="scss" scoped>
.custom-tree-node {
display: flex;
flex-grow: 1;
align-items: center;
justify-content: space-between;
margin-right: 2rem;
.btn {
display: none;
gap: 1rem;
}
&:hover .btn {
display: flex;
}
}
::v-deep .el-tree-node__content {
height: 3rem;
}
</style>

View File

@ -0,0 +1,266 @@
<template>
<el-container>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
{
title: $t('启用状态'),
key: 'enabled',
options: [
{ label: $t('全部'), value: '' },
{ label: $t('启用'), value: true },
{ label: $t('禁用'), value: false },
],
},
{
title: $t('档案可见性'),
key: 'visibility',
options: [
{ label: $t('全部'), value: '' },
...Object.entries(this.$GLOBAL.enums.archiveVisibilities).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
:controls="[
{
type: 'input',
field: ['root', 'keywords'],
placeholder: $t('文档标题'),
style: 'width:20rem',
},
]"
:vue="this"
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
<div class="right-panel">
<el-button
@click="(this.dialog.save = { mode: 'add', data: { catalogId: this.catalogId } })"
icon="el-icon-plus"
type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_doc.bulkDeleteContent" :vue="this" />
<el-dropdown v-show="this.selection.length > 0">
<el-button type="primary">
{{ $t('批量操作') }}
<el-icon>
<el-icon-arrow-down />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="setEnabled(true)">{{ $t('启用') }}</el-dropdown-item>
<el-dropdown-item @click="setEnabled(false)">{{ $t('禁用') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-header>
<el-main class="nopadding">
<sc-table
:before-post="(data) => data.dynamicFilter.filters.length > 0"
:context-menus="['title', 'enabled', 'createdTime', 'id', 'visibility']"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:export-api="$API.sys_doc.exportContent"
:params="query"
:query-api="$API.sys_doc.pagedQueryContent"
:vue="this"
@selection-change="
(items) => {
selection = items
}
"
ref="table"
remote-sort
row-key="id"
stripe>
<el-table-column type="selection" width="50" />
<na-col-id :label="$t('唯一编码')" prop="id" sortable="custom" width="170" />
<el-table-column :label="$t('文档标题')" prop="title" sortable="custom" />
<na-col-indicator
:label="$t('档案可见性')"
:options="
Object.entries(this.$GLOBAL.enums.archiveVisibilities).map((x) => {
return { value: x[0], text: x[1][1], type: x[1][2] }
})
"
align="center"
prop="visibility"
sortable="custom"
width="150" />
<el-table-column :label="$t('启用')" align="center" prop="enabled" sortable="custom" width="100">
<template #default="{ row }">
<el-switch v-model="row.enabled" @change="changeSwitch($event, row)"></el-switch>
</template>
</el-table-column>
<na-col-operation
:buttons="
naColOperation.buttons.concat({
icon: 'el-icon-delete',
confirm: true,
title: '删除文档',
click: rowDel,
type: 'danger',
})
"
:vue="this"
width="150" />
</sc-table>
</el-main>
</el-container>
<save-dialog
v-if="dialog.save"
@closed="(dialog.save = null)"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
components: {
saveDialog,
},
computed: {
naColOperation() {
return naColOperation
},
table() {
return table
},
},
created() {},
data() {
return {
dialog: {},
loading: false,
query: {
dynamicFilter: {
filters: [],
},
filter: {},
keywords: this.keywords,
},
selection: [],
}
},
inject: ['reload'],
methods: {
filterChange(data) {
Object.entries(data).forEach(([key, value]) => {
this.$refs.search.form.dy[key] = value === 'true' ? true : value === 'false' ? false : value
})
this.$refs.search.search()
},
//
async changeSwitch(event, row) {
try {
await this.$API.sys_doc.setEnabled.post(row)
this.$message.success(`操作成功`)
} catch {
//
}
this.$refs.table.refresh()
},
async setEnabled(enabled) {
let loading
try {
await this.$confirm(`确定${enabled ? '启用' : '禁用'}选中的 ${this.selection.length} 项吗?`, '提示', {
type: 'warning',
})
loading = this.$loading()
const res = await Promise.all(this.selection.map((x) => this.$API.sys_doc.setEnabled.post(Object.assign(x, { enabled: enabled }))))
this.$message.success(`操作成功 ${res.map((x) => x.data ?? 0).reduce((a, b) => a + b, 0)}/${this.selection.length}`)
} catch {
//
}
this.$refs.table.refresh()
loading?.close()
},
onReset() {
Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))
},
//
onSearch(form) {
this.query.dynamicFilter.filters.push({
field: 'catalogId',
value: this.catalogId,
operator: 'eq',
})
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
operator: 'dateRange',
value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')),
})
}
if (typeof form.dy.enabled === 'boolean') {
this.query.dynamicFilter.filters.push({
field: 'enabled',
operator: 'eq',
value: form.dy.enabled,
})
}
if (typeof form.dy.visibility === 'string' && form.dy.visibility.trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'visibility',
operator: 'eq',
value: form.dy.visibility,
})
}
this.$refs.table.upData()
},
//
async rowDel(row) {
try {
const res = await this.$API.sys_doc.deleteContent.post({ id: row.id })
this.$message.success(this.$t('删除 {count} 项', { count: res.data }))
} catch {
//
}
this.$refs.table.refresh()
},
},
mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keeps.push({
field: 'keywords',
value: this.keywords,
type: 'root',
})
}
},
props: { catalogId: Number, keywords: String },
watch: {
catalogId() {
this.$refs.search.reset()
},
},
}
</script>
<style scoped></style>

View File

@ -0,0 +1,133 @@
<template>
<sc-dialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" @closed="$emit('closed')" destroy-on-close full-screen>
<div v-loading="loading">
<el-tabs tab-position="top">
<el-tab-pane :label="$t('基本信息')">
<el-form :disabled="mode === 'view'" :model="form" :rules="rules" label-width="10rem" ref="dialogForm">
<el-form-item :label="$t('所属文档分类')" prop="catalogId">
<catalog-select v-model="form.catalogId" class="w100p" />
</el-form-item>
<el-form-item :label="$t('文档标题')" prop="title">
<el-input v-model="form.title" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('档案可见性')" prop="visibility">
<el-select v-model="form.visibility" clearable filterable>
<el-option v-for="(item, i) in $GLOBAL.enums.archiveVisibilities" :key="i" :label="item[1]" :value="i" />
</el-select>
</el-form-item>
<el-form-item :label="$t('文档内容')" prop="body">
<div
:class="
this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK
? 'aie-theme-dark editor'
: 'aie-theme-light editor'
"
ref="editor"></div>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
<json-viewer
:expand-depth="5"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'dark' : 'light'"
:value="form"
copyable
expanded
sort></json-viewer>
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
import { AiEditor } from 'aieditor'
import 'aieditor/dist/style.css'
import sysConfig from '@/config'
const catalogSelect = defineAsyncComponent(() => import('../components/catalog-select.vue'))
export default {
components: { catalogSelect },
data() {
return {
//
form: {},
loading: true,
mode: 'add',
//
rules: {
catalogId: [{ required: true, message: '请选择所属文档分类' }],
title: [{ required: true, message: '请输入文档标题' }],
body: [{ required: true, message: '请输入文档内容' }],
visibility: [{ required: true, message: '请选择档案可见性' }],
},
titleMap: {
add: this.$t('新增文档'),
edit: this.$t('编辑文档'),
view: this.$t('查看文档'),
},
visible: false,
}
},
emits: ['success', 'closed', 'mounted'],
methods: {
//
async open(data) {
this.visible = true
this.loading = true
this.mode = data.mode
this.form.catalogId = data.data?.catalogId
if (data.row?.id) {
const res = await this.$API.sys_doc.getContent.post({ id: data.row.id })
Object.assign(this.form, res.data)
}
this.loading = false
await this.$nextTick()
const aiEditor = new AiEditor({
element: this.$refs.editor,
placeholder: this.$t('请输入消息内容...'),
content: this.form.body,
onChange: (body) => {
this.form.body = body.getHtml()
},
})
aiEditor.changeLang(this.$TOOL.data.get('APP_SET_LANG') || sysConfig.APP_SET_LANG)
return this
},
//
async submit() {
const valid = await this.$refs.dialogForm.validate().catch(() => {})
if (!valid) {
return false
}
this.loading = true
const method = this.mode === 'add' ? this.$API.sys_doc.createContent : this.$API.sys_doc.editContent
try {
const res = await method.post(this.form)
this.$emit('success', res.data, this.mode)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {}
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>
<style scoped>
.editor {
width: 100%;
height: 50rem;
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<sc-dialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" @closed="$emit('closed')" destroy-on-close>
<el-form v-loading="loading" :model="form" :rules="rules" label-width="10rem" ref="dialogForm">
<el-form-item :label="$t('文档分类名称')" prop="name">
<el-input v-model="form.name" :placeholder="$t('文档分类名称')" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('文档分类编码')" prop="code">
<el-input v-model="form.code" :placeholder="$t('文档分类编码')" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('父路径')" prop="parentId">
<catalog-select v-model="form.parentId" class="w100p" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
const catalogSelect = defineAsyncComponent(() => import('./components/catalog-select.vue'))
export default {
components: { catalogSelect },
data() {
return {
//
form: {},
loading: true,
mode: 'add',
//
rules: {
code: [{ required: true, message: '请输入文档分类编码' }],
name: [{ required: true, message: '请输入文档分类名称' }],
},
titleMap: {
add: this.$t('新增文档分类'),
edit: this.$t('编辑文档分类'),
},
visible: false,
}
},
emits: ['success', 'closed', 'mounted'],
methods: {
//
async open(data) {
this.visible = true
this.loading = true
this.mode = data.mode
this.form.parentId = data.data?.catalogId
this.form.code = data.data?.code
if (data.row?.id) {
const res = await this.$API.sys_doc.getCatalog.post({ id: data.row.id })
Object.assign(this.form, res.data)
}
this.loading = false
return this
},
//
async submit() {
const valid = await this.$refs.dialogForm.validate().catch(() => {})
if (!valid) {
return false
}
this.loading = true
const method = this.mode === 'add' ? this.$API.sys_doc.createCatalog : this.$API.sys_doc.editCatalog
try {
const res = await method.post(this.form)
this.$emit('success', res.data, this.mode)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {}
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>
<style scoped></style>

View File

@ -70,7 +70,7 @@
ref="search" />
</div>
<div class="right-panel">
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<el-button @click="(this.dialog.save = { mode: 'add' })" icon="el-icon-plus" type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_job.bulkDelete" :vue="this" />
<el-dropdown v-show="this.selection.length > 0">
<el-button type="primary">
@ -223,7 +223,7 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@closed="(dialog.save = null)"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>

View File

@ -45,7 +45,7 @@
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'github_dark' : 'github'"
lang="json"
style="height: 10rem; width: 100%" />
<el-button @click="form.requestHeader = jsonFormat(form.requestHeader)" type="text">{{ $t('JSON 格式化') }}</el-button>
<el-button @click="(form.requestHeader = jsonFormat(form.requestHeader))" type="text">{{ $t('JSON 格式化') }}</el-button>
</el-form-item>
<el-form-item :label="$t('请求体')" prop="requestBody">
<v-ace-editor
@ -53,7 +53,7 @@
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'github_dark' : 'github'"
lang="json"
style="height: 15rem; width: 100%" />
<el-button @click="form.requestBody = jsonFormat(form.requestBody)" type="text">{{ $t('JSON 格式化') }}</el-button>
<el-button @click="(form.requestBody = jsonFormat(form.requestBody))" type="text">{{ $t('JSON 格式化') }}</el-button>
</el-form-item>
<el-form-item :label="$t('请求的网络地址')" prop="requestUrl">
<el-input v-model="form.requestUrl" clearable />
@ -121,7 +121,7 @@
</el-tabs>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>

View File

@ -135,14 +135,14 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@closed="(dialog.save = null)"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
<job-dialog
v-if="dialog.job"
@closed="dialog.job = null"
@closed="(dialog.job = null)"
@mounted="$refs.jobDialog.open(dialog.job)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="jobDialog"></job-dialog>

View File

@ -39,7 +39,7 @@
</el-tabs>
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
</template>
</sc-dialog>
</template>

View File

@ -182,7 +182,7 @@
<na-info v-if="dialog.info" ref="info"></na-info>
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@closed="(dialog.save = null)"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>

View File

@ -47,7 +47,7 @@
ref="search" />
</div>
<div class="right-panel">
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<el-button @click="(this.dialog.save = { mode: 'add' })" icon="el-icon-plus" type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_sitemsg.bulkDelete" :vue="this" />
</div>
</el-header>
@ -105,7 +105,7 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@closed="(dialog.save = null)"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>

View File

@ -65,7 +65,7 @@
</el-tabs>
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>

View File

@ -64,7 +64,7 @@
ref="search" />
</div>
<div class="right-panel">
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<el-button @click="(this.dialog.save = { mode: 'add' })" icon="el-icon-plus" type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_role.bulkDelete" :vue="this" />
<el-dropdown v-show="this.selection.length > 0">
<el-button type="primary">
@ -159,7 +159,7 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@closed="(dialog.save = null)"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>

View File

@ -80,7 +80,7 @@
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'github_dark' : 'github'"
lang="json"
style="height: 30rem; width: 100%" />
<el-button @click="form.dashboardLayout = jsonFormat(form.dashboardLayout)" type="text">{{
<el-button @click="(form.dashboardLayout = jsonFormat(form.dashboardLayout))" type="text">{{
$t('JSON 格式化')
}}</el-button>
</el-form-item>
@ -101,7 +101,7 @@
</el-tabs>
</div>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>

View File

@ -62,7 +62,7 @@
ref="search" />
</div>
<div class="right-panel">
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<el-button @click="(this.dialog.save = { mode: 'add' })" icon="el-icon-plus" type="primary"></el-button>
<el-dropdown v-show="this.selection.length > 0">
<el-button type="primary">
{{ $t('批量操作') }}
@ -132,18 +132,18 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@closed="(dialog.save = null)"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
<role-save-dialog
v-if="dialog.roleSave"
@closed="dialog.roleSave = null"
@closed="(dialog.roleSave = null)"
@mounted="$refs.roleSaveDialog.open(dialog.roleSave)"
ref="roleSaveDialog"></role-save-dialog>
<dept-save-dialog
v-if="dialog.deptSave"
@closed="dialog.deptSave = null"
@closed="(dialog.deptSave = null)"
@mounted="$refs.deptSaveDialog.open(dialog.deptSave)"
ref="deptSaveDialog"></dept-save-dialog>
</template>

View File

@ -46,7 +46,7 @@
maxlength="16"
oninput="value=value.replace(/[^\w]/g,'')"
placeholder="8位以上数字字母组合"></el-input>
<el-button @click="form.passwordText = '1234qwer'">{{ $t('初始密码') }}</el-button>
<el-button @click="(form.passwordText = '1234qwer')">{{ $t('初始密码') }}</el-button>
</div>
</el-form-item>
@ -249,7 +249,7 @@
</el-tabs>
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button @click="(visible = false)">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>