From 350aa66d4818f85f042cb2d399691209dc97b97e Mon Sep 17 00:00:00 2001
From: sunpengfei <i@angelzzz.com>
Date: 星期三, 06 八月 2025 14:46:54 +0800
Subject: [PATCH] feat:字典开发

---
 FlexJobApi.User.Application/Menus/Commands/SaveMenuButtonCommandHandler.cs           |   61 +--
 FlexJobApi.Core/Utils/DbUtils/DbUtils.cs                                             |  136 +++++++
 FlexJobApi.User.Application/Auths/Queries/GetCurrentLogierMenusQueryHandler.cs       |    2 
 FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuFieldCommand.cs                   |    7 
 FlexJobApi.Core/Entities/Common/DictionaryData.cs                                    |    2 
 FlexJobApi.Core/Utils/DbUtils/SelectQuery.cs                                         |    5 
 FlexJobApi.Core/Interfaces/ITreeData.cs                                              |   42 ++
 FlexJobApi.Core/FlexJobApi.Core.xml                                                  |  196 +++++++++-
 FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuButtonCommand.cs                  |    7 
 FlexJobApi.User.Application/Menus/Commands/SaveMenuFieldCommandHandler.cs            |   60 +--
 FlexJobApi.Application/Dictionaries/Commands/DictionaryDataCommandHandler.cs         |   23 +
 FlexJobApi.Application/Dictionaries/Queries/DictionaryCategoriesQueryHandler.cs      |   27 -
 FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryCategorySelectQuery.cs |   34 +
 FlexJobApi.User.Application/Roles/Commands/SetRoleIsDisabledCommandHandler.cs        |   17 
 FlexJobApi.User.Application/FlexJobApi.User.Application.xml                          |   43 --
 FlexJobApi.User.Application/Menus/Commands/SaveMenuCommandHandler.cs                 |  132 +------
 FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryDataSelectQuery.cs     |   81 ++++
 FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuCommand.cs                        |    7 
 FlexJobApi.Application/Dictionaries/Queries/DictionaryDatasQueryHandler.cs           |   49 ++
 FlexJobApi.Application/FlexJobApi.Application.xml                                    |    8 
 FlexJobApi.Core/Entities/Users/Menu.cs                                               |    2 
 FlexJobApi.User.Application/UserInfos/Queries/GetOperationUserInfosQueryHandler.cs   |    2 
 FlexJobApi.User.Application/Roles/Queries/GetRolesQueryHandler.cs                    |    2 
 FlexJobApi.Core/Models/User/Roles/Commands/SetRoleIsDisabledCommand.cs               |   16 
 FlexJobApi.Application/Dictionaries/Commands/DictionaryCategoryCommandHandler.cs     |    3 
 FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryDatasQuery.cs          |    7 
 26 files changed, 626 insertions(+), 345 deletions(-)

diff --git a/FlexJobApi.Application/Dictionaries/Commands/DictionaryCategoryCommandHandler.cs b/FlexJobApi.Application/Dictionaries/Commands/DictionaryCategoryCommandHandler.cs
index ec0ca8a..e2dc9f6 100644
--- a/FlexJobApi.Application/Dictionaries/Commands/DictionaryCategoryCommandHandler.cs
+++ b/FlexJobApi.Application/Dictionaries/Commands/DictionaryCategoryCommandHandler.cs
@@ -35,7 +35,8 @@
         public Task<Guid> Handle(SaveDictionaryCategoryCommand request, CancellationToken cancellationToken)
         {
             return request.SaveData<DictionaryCategory, SaveDictionaryCategoryCommand>(
-                 (q, e, r) => q.Any(it => it.Id != request.Id && it.Code == request.Code),
+                 it => it.Id != request.Id && it.Code == request.Code,
+                 null,
                  cancellationToken);
         }
     }
diff --git a/FlexJobApi.Application/Dictionaries/Commands/DictionaryDataCommandHandler.cs b/FlexJobApi.Application/Dictionaries/Commands/DictionaryDataCommandHandler.cs
index a480570..f9c7a0b 100644
--- a/FlexJobApi.Application/Dictionaries/Commands/DictionaryDataCommandHandler.cs
+++ b/FlexJobApi.Application/Dictionaries/Commands/DictionaryDataCommandHandler.cs
@@ -1,7 +1,10 @@
 锘縰sing FlexJobApi.Core;
+using Furion.DatabaseAccessor;
+using Mapster;
 using MediatR;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
@@ -25,11 +28,25 @@
         public Task<Guid> Handle(SaveDictionaryDataCommand request, CancellationToken cancellationToken)
         {
             return request.SaveData<DictionaryData, SaveDictionaryDataCommand>(
-                (q, e, r) => q.Any(it =>
+                it =>
                     it.CategoryId == request.CategoryId
                     && it.ParentId == request.ParentId
                     && it.Code == request.Code
-                    && it.Content == request.Content), cancellationToken);
+                    && it.Content == request.Content
+                    && it.Id != request.Id,
+                (entity) =>
+                {
+                    entity.Path = DbUtils.GetTreeDataPath<DictionaryData>(request.ParentId, cancellationToken).Result;
+                    if (request.Id.HasValue)
+                    {
+                        DbUtils.UpdateTreeDataChildrenPath<DictionaryData>(
+                           $"{entity.Path}/{entity.Code}/",
+                           $"{entity.Path}/{request.Code}/",
+                           cancellationToken).Wait();
+                    }
+                    request.Adapt(entity);
+                },
+                cancellationToken);
         }
 
         /// <summary>
@@ -40,7 +57,7 @@
         /// <returns></returns>
         public Task<int> Handle(SetDictionaryDataIsDisabledCommand request, CancellationToken cancellationToken)
         {
-            return request.SetIsDisable<DictionaryData>(cancellationToken: cancellationToken);
+            return request.SetIsDisabled<DictionaryData>(cancellationToken: cancellationToken);
         }
     }
 }
diff --git a/FlexJobApi.Application/Dictionaries/Queries/DictionaryCategoriesQueryHandler.cs b/FlexJobApi.Application/Dictionaries/Queries/DictionaryCategoriesQueryHandler.cs
index 79d4f86..3858f64 100644
--- a/FlexJobApi.Application/Dictionaries/Queries/DictionaryCategoriesQueryHandler.cs
+++ b/FlexJobApi.Application/Dictionaries/Queries/DictionaryCategoriesQueryHandler.cs
@@ -17,7 +17,7 @@
     public class DictionaryCategoriesQueryHandler(
             IRepository<DictionaryCategory> rep
         ) : IRequestHandler<GetDictionaryCategoriesQuery, PagedListQueryResult<GetDictionaryCategoriesQueryResultItem>>,
-            IRequestHandler<GetDictionaryCategorySelectQuery, List<SelectQueryResultItem<Guid>>>
+            IRequestHandler<GetDictionaryCategorySelectQuery, List<SelectQueryResultOption<Guid, GetDictionaryCategorySelectQueryOption>>>
     {
         private readonly IRepository<DictionaryCategory> rep = rep;
 
@@ -29,10 +29,9 @@
         /// <returns></returns>
         public Task<PagedListQueryResult<GetDictionaryCategoriesQueryResultItem>> Handle(GetDictionaryCategoriesQuery request, CancellationToken cancellationToken)
         {
-            return request.PageModel.ToPagedListAsync<DictionaryCategory, GetDictionaryCategoriesQueryResultItem>(
+            return request.PageModel.GetPagedListAsync<DictionaryCategory, GetDictionaryCategoriesQueryResultItem>(
                 q =>
                 {
-                    q = q.OrderBy(it => it.Sort).ThenBy(it => it.CreatedTime);
                     if (request.Keywords.IsNotNull())
                     {
                         q = q.Where(it =>
@@ -50,23 +49,13 @@
         /// <param name="request"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public Task<List<SelectQueryResultItem<Guid>>> Handle(GetDictionaryCategorySelectQuery request, CancellationToken cancellationToken)
+        public Task<List<SelectQueryResultOption<Guid, GetDictionaryCategorySelectQueryOption>>> Handle(GetDictionaryCategorySelectQuery request, CancellationToken cancellationToken)
         {
-            var items = rep.AsQueryable().AsNoTracking()
-                .Select(it => new SelectQueryResultItem<Guid>
-                {
-                    Value = it.Id,
-                    Label = it.Name,
-                    Data = new
-                    {
-                        it.Id,
-                        it.Name,
-                        it.Remark,
-                        it.FieldNames
-                    }
-                })
-                .ToListAsync(cancellationToken);
-            return items;
+            return request.GetSelect<DictionaryCategory, Guid, GetDictionaryCategorySelectQueryOption>(
+                it => it.Id,
+                it => it.Name,
+                null,
+                cancellationToken);
         }
     }
 }
diff --git a/FlexJobApi.Application/Dictionaries/Queries/DictionaryDatasQueryHandler.cs b/FlexJobApi.Application/Dictionaries/Queries/DictionaryDatasQueryHandler.cs
index aedfaf2..101d979 100644
--- a/FlexJobApi.Application/Dictionaries/Queries/DictionaryDatasQueryHandler.cs
+++ b/FlexJobApi.Application/Dictionaries/Queries/DictionaryDatasQueryHandler.cs
@@ -1,5 +1,6 @@
 锘縰sing FlexJobApi.Core;
 using Furion.DatabaseAccessor;
+using Furion.FriendlyException;
 using Mapster;
 using MediatR;
 using Microsoft.EntityFrameworkCore;
@@ -16,7 +17,8 @@
     /// </summary>
     public class DictionaryDatasQueryHandler(
             IRepository<DictionaryData> rep
-        ) : IRequestHandler<GetDictionaryDatasQuery, PagedListQueryResult<GetDictionaryDatasQueryResultItem>>
+        ) : IRequestHandler<GetDictionaryDatasQuery, PagedListQueryResult<GetDictionaryDatasQueryResultItem>>,
+            IRequestHandler<GetDictionaryDataSelectQuery, List<SelectQueryResultOption<Guid, GetDictionaryDataSelectQueryResultOption>>>
     {
         private readonly IRepository<DictionaryData> rep = rep;
 
@@ -28,13 +30,21 @@
         /// <returns></returns>
         public Task<PagedListQueryResult<GetDictionaryDatasQueryResultItem>> Handle(GetDictionaryDatasQuery request, CancellationToken cancellationToken)
         {
-            return request.PageModel.ToPagedListAsync<DictionaryData, GetDictionaryDatasQueryResultItem>(
+            return request.PageModel.GetPagedListAsync<DictionaryData, GetDictionaryDatasQueryResultItem>(
                 q =>
                 {
                     q = q.OrderBy(it => it.Sort).ThenBy(it => it.CreatedTime);
                     if (request.CategoryId.HasValue)
                     {
                         q = q.Where(it => it.CategoryId == request.CategoryId);
+                    }
+                    else if (request.CategoryCode.IsNotNull())
+                    {
+                        q = q.Where(it => it.Category.Code == request.CategoryCode);
+                    }
+                    else
+                    {
+                        throw Oops.Oh(EnumErrorCodeType.s400, "璇峰~鍐欑被鍒獻d鎴栫紪鍙�");
                     }
                     if (request.Keywords.IsNotNull())
                     {
@@ -50,5 +60,40 @@
                     return q;
                 }, cancellationToken: cancellationToken);
         }
+
+        /// <summary>
+        /// 鏌ヨ鏁版嵁瀛楀吀閫夋嫨鍣�
+        /// </summary>
+        /// <param name="request"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task<List<SelectQueryResultOption<Guid, GetDictionaryDataSelectQueryResultOption>>> Handle(GetDictionaryDataSelectQuery request, CancellationToken cancellationToken)
+        {
+            return request.GetSelect<DictionaryData, Guid, GetDictionaryDataSelectQueryResultOption>(
+                it => it.Id,
+                it => it.Content,
+                q =>
+                {
+                    q = q
+                        .OrderBy(it => it.Sort).ThenBy(it => it.CreatedTime)
+                        .Where(it =>
+                            it.ParentId == request.ParentId
+                            && !it.IsDisabled);
+                    if (request.CategoryId.HasValue)
+                    {
+                        q = q.Where(it => it.CategoryId == request.CategoryId);
+                    }
+                    else if (request.CategoryCode.IsNotNull())
+                    {
+                        q = q.Where(it => it.Category.Code == request.CategoryCode);
+                    }
+                    else
+                    {
+                        throw Oops.Oh(EnumErrorCodeType.s400, "璇峰~鍐欑被鍒獻d鎴栫紪鍙�");
+                    }
+                    return q;
+                },
+                cancellationToken);
+        }
     }
 }
diff --git a/FlexJobApi.Application/FlexJobApi.Application.xml b/FlexJobApi.Application/FlexJobApi.Application.xml
index 6e8995a..adb41b2 100644
--- a/FlexJobApi.Application/FlexJobApi.Application.xml
+++ b/FlexJobApi.Application/FlexJobApi.Application.xml
@@ -90,5 +90,13 @@
             <param name="cancellationToken"></param>
             <returns></returns>
         </member>
+        <member name="M:FlexJobApi.Application.DictionaryDatasQueryHandler.Handle(FlexJobApi.Core.GetDictionaryDataSelectQuery,System.Threading.CancellationToken)">
+            <summary>
+            鏌ヨ鏁版嵁瀛楀吀閫夋嫨鍣�
+            </summary>
+            <param name="request"></param>
+            <param name="cancellationToken"></param>
+            <returns></returns>
+        </member>
     </members>
 </doc>
diff --git a/FlexJobApi.Core/Entities/Common/DictionaryData.cs b/FlexJobApi.Core/Entities/Common/DictionaryData.cs
index a89e7d8..ca477ff 100644
--- a/FlexJobApi.Core/Entities/Common/DictionaryData.cs
+++ b/FlexJobApi.Core/Entities/Common/DictionaryData.cs
@@ -13,7 +13,7 @@
     /// <summary>
     /// 瀛楀吀鏁版嵁
     /// </summary>
-    public class DictionaryData : CommonEntity, IEntityTypeBuilder<DictionaryData>, IIsDisabled
+    public class DictionaryData : CommonEntity, IEntityTypeBuilder<DictionaryData>, ITreeData<DictionaryData>, IIsDisabled
     {
         public DictionaryData()
         {
diff --git a/FlexJobApi.Core/Entities/Users/Menu.cs b/FlexJobApi.Core/Entities/Users/Menu.cs
index fae1628..6a5b7d1 100644
--- a/FlexJobApi.Core/Entities/Users/Menu.cs
+++ b/FlexJobApi.Core/Entities/Users/Menu.cs
@@ -13,7 +13,7 @@
     /// <summary>
     /// 鑿滃崟
     /// </summary>
-    public class Menu : CommonEntity, IEntityTypeBuilder<Menu>, IIsDisabled
+    public class Menu : CommonEntity, IEntityTypeBuilder<Menu>, ITreeData<Menu>, IIsDisabled
     {
         public Menu()
         {
diff --git a/FlexJobApi.Core/FlexJobApi.Core.xml b/FlexJobApi.Core/FlexJobApi.Core.xml
index a319979..47bb11e 100644
--- a/FlexJobApi.Core/FlexJobApi.Core.xml
+++ b/FlexJobApi.Core/FlexJobApi.Core.xml
@@ -2129,6 +2129,37 @@
             鐗╃悊鍒犻櫎
             </summary>
         </member>
+        <member name="T:FlexJobApi.Core.ITreeData`1">
+            <summary>
+            鏍戝舰鏁版嵁
+            </summary>
+            <typeparam name="TEntity"></typeparam>
+        </member>
+        <member name="P:FlexJobApi.Core.ITreeData`1.ParentId">
+            <summary>
+            涓婄骇Id
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.ITreeData`1.Parent">
+            <summary>
+            涓婄骇
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.ITreeData`1.Children">
+            <summary>
+            涓嬬骇
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.ITreeData`1.Path">
+            <summary>
+            瀛楀吀璺緞
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.ITreeData`1.Code">
+            <summary>
+            缂栧彿
+            </summary>
+        </member>
         <member name="T:FlexJobApi.Core.DeleteDictionaryCategoryCommand">
             <summary>
             鍒犻櫎鏁版嵁瀛楀吀绫诲埆
@@ -2267,6 +2298,96 @@
         <member name="T:FlexJobApi.Core.GetDictionaryCategorySelectQuery">
             <summary>
             鏌ヨ鏁版嵁瀛楀吀绫诲埆閫夋嫨鍣ㄦ暟鎹�
+            </summary>
+        </member>
+        <member name="T:FlexJobApi.Core.GetDictionaryCategorySelectQueryOption">
+            <summary>
+            鏌ヨ鏁版嵁瀛楀吀绫诲埆閫夋嫨鍣ㄦ暟鎹�-缁撴灉-閫夐」
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryCategorySelectQueryOption.Id">
+            <summary>
+            Id
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryCategorySelectQueryOption.Code">
+            <summary>
+            缂栧彿
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryCategorySelectQueryOption.Name">
+            <summary>
+            鍚嶇О
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryCategorySelectQueryOption.FieldNames">
+            <summary>
+            瀛楁鍚嶏紙閫楀彿闅斿紑锛�
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryCategorySelectQueryOption.Remark">
+            <summary>
+            澶囨敞
+            </summary>
+        </member>
+        <member name="T:FlexJobApi.Core.GetDictionaryDataSelectQuery">
+            <summary>
+            鏌ヨ鏁版嵁瀛楀吀閫夋嫨鍣�
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryDataSelectQuery.CategoryId">
+            <summary>
+            绫诲埆Id
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryDataSelectQuery.ParentId">
+            <summary>
+            涓婄骇Id
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryDataSelectQueryResultOption.Id">
+            <summary>
+            Id
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryDataSelectQueryResultOption.Path">
+            <summary>
+            瀛楀吀璺緞
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryDataSelectQueryResultOption.Code">
+            <summary>
+            缂栧彿
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryDataSelectQueryResultOption.Content">
+            <summary>
+            鏄剧ず鍐呭
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryDataSelectQueryResultOption.Field1">
+            <summary>
+            瀛楁1
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryDataSelectQueryResultOption.Field2">
+            <summary>
+            瀛楁2
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryDataSelectQueryResultOption.Field3">
+            <summary>
+            瀛楁3
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryDataSelectQueryResultOption.Field4">
+            <summary>
+            瀛楁4
+            </summary>
+        </member>
+        <member name="P:FlexJobApi.Core.GetDictionaryDataSelectQueryResultOption.Field5">
+            <summary>
+            瀛楁5
             </summary>
         </member>
         <member name="T:FlexJobApi.Core.GetDictionaryDatasQuery">
@@ -2474,11 +2595,6 @@
             淇濆瓨鑿滃崟鎸夐挳
             </summary>
         </member>
-        <member name="P:FlexJobApi.Core.SaveMenuButtonCommand.Id">
-            <summary>
-            Id
-            </summary>
-        </member>
         <member name="P:FlexJobApi.Core.SaveMenuButtonCommand.ParentId">
             <summary>
             鑿滃崟Id
@@ -2527,11 +2643,6 @@
         <member name="T:FlexJobApi.Core.SaveMenuCommand">
             <summary>
             淇濆瓨鑿滃崟
-            </summary>
-        </member>
-        <member name="P:FlexJobApi.Core.SaveMenuCommand.Id">
-            <summary>
-            Id
             </summary>
         </member>
         <member name="P:FlexJobApi.Core.SaveMenuCommand.UserType">
@@ -2717,11 +2828,6 @@
         <member name="T:FlexJobApi.Core.SaveMenuFieldCommand">
             <summary>
             淇濆瓨鑿滃崟瀛楁
-            </summary>
-        </member>
-        <member name="P:FlexJobApi.Core.SaveMenuFieldCommand.Id">
-            <summary>
-            Id
             </summary>
         </member>
         <member name="P:FlexJobApi.Core.SaveMenuFieldCommand.ParentId">
@@ -3349,16 +3455,6 @@
             璁剧疆瑙掕壊鏄惁绂佺敤
             </summary>
         </member>
-        <member name="P:FlexJobApi.Core.SetRoleIsDisabledCommand.Ids">
-            <summary>
-            Id
-            </summary>
-        </member>
-        <member name="P:FlexJobApi.Core.SetRoleIsDisabledCommand.IsDisabled">
-            <summary>
-            鏄惁绂佺敤
-            </summary>
-        </member>
         <member name="T:FlexJobApi.Core.SetRoleUserInfosCommand">
             <summary>
             璁剧疆瑙掕壊鐢ㄦ埛
@@ -3810,7 +3906,39 @@
             鏁版嵁搴撳伐鍏�
             </summary>
         </member>
-        <member name="M:FlexJobApi.Core.DbUtils.ToPagedListAsync``2(FlexJobApi.Core.PagedListQueryPageModel,System.Func{System.Linq.IQueryable{``0},System.Linq.IQueryable{``0}},System.Linq.Expressions.Expression{System.Func{``0,``1}},System.Threading.CancellationToken)">
+        <member name="M:FlexJobApi.Core.DbUtils.GetTreeDataPath``1(System.Nullable{System.Guid},System.Threading.CancellationToken)">
+            <summary>
+            鑾峰彇鏍戝舰鏁版嵁璺緞
+            </summary>
+            <typeparam name="TEntity"></typeparam>
+            <param name="parentId"></param>
+            <param name="cancellationToken"></param>
+            <returns></returns>
+        </member>
+        <member name="M:FlexJobApi.Core.DbUtils.UpdateTreeDataChildrenPath``1(System.String,System.String,System.Threading.CancellationToken)">
+            <summary>
+            鏇存柊鏍戝舰鏁版嵁涓嬬骇璺緞
+            </summary>
+            <param name="oldPath"></param>
+            <param name="newPath"></param>
+            <param name="cancellationToken"></param>
+            <returns></returns>
+        </member>
+        <member name="M:FlexJobApi.Core.DbUtils.GetSelect``3(FlexJobApi.Core.SelectQuery{``1,``2},System.Func{``2,``1},System.Func{``2,System.String},System.Func{System.Linq.IQueryable{``0},System.Linq.IQueryable{``0}},System.Threading.CancellationToken)">
+            <summary>
+            鏌ヨ閫夋嫨鍣ㄦ暟鎹�
+            </summary>
+            <typeparam name="TEntity"></typeparam>
+            <typeparam name="TValue"></typeparam>
+            <typeparam name="TData"></typeparam>
+            <param name="request"></param>
+            <param name="getValue"></param>
+            <param name="getLabel"></param>
+            <param name="query"></param>
+            <param name="cancellationToken"></param>
+            <returns></returns>
+        </member>
+        <member name="M:FlexJobApi.Core.DbUtils.GetPagedListAsync``2(FlexJobApi.Core.PagedListQueryPageModel,System.Func{System.Linq.IQueryable{``0},System.Linq.IQueryable{``0}},System.Linq.Expressions.Expression{System.Func{``0,``1}},System.Threading.CancellationToken)">
             <summary>
             鏌ヨ鍒嗛〉鍒楄〃鏁版嵁
             </summary>
@@ -3831,7 +3959,7 @@
             <param name="orders"></param>
             <returns></returns>
         </member>
-        <member name="M:FlexJobApi.Core.DbUtils.SetIsDisable``1(FlexJobApi.Core.SetIsDisabledCommand,System.Func{System.Linq.IQueryable{``0},System.Linq.IQueryable{``0}},System.Threading.CancellationToken)">
+        <member name="M:FlexJobApi.Core.DbUtils.SetIsDisabled``1(FlexJobApi.Core.SetIsDisabledCommand,System.Func{System.Linq.IQueryable{``0},System.Linq.IQueryable{``0}},System.Threading.CancellationToken)">
             <summary>
             璁剧疆鏄惁绂佺敤
             </summary>
@@ -3851,7 +3979,7 @@
             <param name="cancellationToken"></param>
             <returns></returns>
         </member>
-        <member name="M:FlexJobApi.Core.DbUtils.SaveData``2(``1,System.Func{System.Linq.IQueryable{``0},``0,``1,System.Boolean},System.Threading.CancellationToken)">
+        <member name="M:FlexJobApi.Core.DbUtils.SaveData``2(``1,System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}},System.Action{``0},System.Threading.CancellationToken)">
             <summary>
             淇濆瓨鏁版嵁
             </summary>
@@ -3859,6 +3987,7 @@
             <typeparam name="TRequest"></typeparam>
             <param name="request"></param>
             <param name="checkExist"></param>
+            <param name="update"></param>
             <param name="cancellationToken"></param>
             <returns></returns>
         </member>
@@ -3970,28 +4099,29 @@
             Id
             </summary>
         </member>
-        <member name="T:FlexJobApi.Core.SelectQuery`1">
+        <member name="T:FlexJobApi.Core.SelectQuery`2">
             <summary>
             閫夋嫨鍣ㄦ煡璇�
             </summary>
             <typeparam name="TValue"></typeparam>
+            <typeparam name="TData"></typeparam>
         </member>
-        <member name="T:FlexJobApi.Core.SelectQueryResultItem`1">
+        <member name="T:FlexJobApi.Core.SelectQueryResultOption`2">
             <summary>
             閫夋嫨鍣ㄦ煡璇�-缁撴灉-椤�
             </summary>
         </member>
-        <member name="P:FlexJobApi.Core.SelectQueryResultItem`1.Value">
+        <member name="P:FlexJobApi.Core.SelectQueryResultOption`2.Value">
             <summary>
             鍊�
             </summary>
         </member>
-        <member name="P:FlexJobApi.Core.SelectQueryResultItem`1.Label">
+        <member name="P:FlexJobApi.Core.SelectQueryResultOption`2.Label">
             <summary>
             鏍囩
             </summary>
         </member>
-        <member name="P:FlexJobApi.Core.SelectQueryResultItem`1.Data">
+        <member name="P:FlexJobApi.Core.SelectQueryResultOption`2.Data">
             <summary>
             鏁版嵁
             </summary>
diff --git a/FlexJobApi.Core/Interfaces/ITreeData.cs b/FlexJobApi.Core/Interfaces/ITreeData.cs
new file mode 100644
index 0000000..b88ca4a
--- /dev/null
+++ b/FlexJobApi.Core/Interfaces/ITreeData.cs
@@ -0,0 +1,42 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FlexJobApi.Core
+{
+    /// <summary>
+    /// 鏍戝舰鏁版嵁
+    /// </summary>
+    /// <typeparam name="TEntity"></typeparam>
+    public interface ITreeData<TEntity>
+        where TEntity : CommonEntity, ITreeData<TEntity>, new()
+    {
+        /// <summary>
+        /// 涓婄骇Id
+        /// </summary>
+        Guid? ParentId { get; set; }
+
+        /// <summary>
+        /// 涓婄骇
+        /// </summary>
+        TEntity Parent { get; set; }
+
+        /// <summary>
+        /// 涓嬬骇
+        /// </summary>
+        List<TEntity> Children { get; set; }
+
+        /// <summary>
+        /// 瀛楀吀璺緞
+        /// </summary>
+        string Path { get; set; }
+
+        /// <summary>
+        /// 缂栧彿
+        /// </summary>
+        string Code { get; set; }
+    }
+}
diff --git a/FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryCategorySelectQuery.cs b/FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryCategorySelectQuery.cs
index e04aa32..6b90dcf 100644
--- a/FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryCategorySelectQuery.cs
+++ b/FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryCategorySelectQuery.cs
@@ -2,6 +2,7 @@
 using Newtonsoft.Json.Linq;
 using System;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
@@ -12,7 +13,38 @@
     /// 鏌ヨ鏁版嵁瀛楀吀绫诲埆閫夋嫨鍣ㄦ暟鎹�
     /// </summary>
     [Resource([EnumResourceController.Dictionary])]
-    public class GetDictionaryCategorySelectQuery : SelectQuery<Guid>
+    public class GetDictionaryCategorySelectQuery : SelectQuery<Guid, GetDictionaryCategorySelectQueryOption>
     {
     }
+
+    /// <summary>
+    /// 鏌ヨ鏁版嵁瀛楀吀绫诲埆閫夋嫨鍣ㄦ暟鎹�-缁撴灉-閫夐」
+    /// </summary>
+    public class GetDictionaryCategorySelectQueryOption
+    {
+        /// <summary>
+        /// Id
+        /// </summary>
+        public Guid Id { get; set; }
+
+        /// <summary>
+        /// 缂栧彿
+        /// </summary>
+        public string Code { get; set; }
+
+        /// <summary>
+        /// 鍚嶇О
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 瀛楁鍚嶏紙閫楀彿闅斿紑锛�
+        /// </summary>
+        public string FieldNames { get; set; }
+
+        /// <summary>
+        /// 澶囨敞
+        /// </summary>
+        public string Remark { get; set; }
+    }
 }
diff --git a/FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryDataSelectQuery.cs b/FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryDataSelectQuery.cs
new file mode 100644
index 0000000..5dc6774
--- /dev/null
+++ b/FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryDataSelectQuery.cs
@@ -0,0 +1,81 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FlexJobApi.Core
+{
+    /// <summary>
+    /// 鏌ヨ鏁版嵁瀛楀吀閫夋嫨鍣�
+    /// </summary>
+    [Resource([EnumResourceController.Dictionary])]
+    public class GetDictionaryDataSelectQuery : SelectQuery<Guid, GetDictionaryDataSelectQueryResultOption>
+    {
+        /// <summary>
+        /// 绫诲埆Id锛圛d/缂栧彿浜岄�変竴锛�
+        /// </summary>
+        public Guid? CategoryId { get; set; }
+
+        /// <summary>
+        /// 绫诲埆缂栧彿锛圛d/缂栧彿浜岄�変竴锛�
+        /// </summary>
+        public string CategoryCode { get; set; }
+
+        /// <summary>
+        /// 涓婄骇Id
+        /// </summary>
+        public Guid? ParentId { get; set; }
+    }
+
+    public class GetDictionaryDataSelectQueryResultOption
+    {
+        /// <summary>
+        /// Id
+        /// </summary>
+        public Guid Id { get; set; }
+
+        /// <summary>
+        /// 瀛楀吀璺緞
+        /// </summary>
+        public string Path { get; set; }
+
+        /// <summary>
+        /// 缂栧彿
+        /// </summary>
+        [MaxLength(128)]
+        public string Code { get; set; }
+
+        /// <summary>
+        /// 鏄剧ず鍐呭
+        /// </summary>
+        [Required]
+        public string Content { get; set; }
+
+        /// <summary>
+        /// 瀛楁1
+        /// </summary>
+        public string Field1 { get; set; }
+
+        /// <summary>
+        /// 瀛楁2
+        /// </summary>
+        public string Field2 { get; set; }
+
+        /// <summary>
+        /// 瀛楁3
+        /// </summary>
+        public string Field3 { get; set; }
+
+        /// <summary>
+        /// 瀛楁4
+        /// </summary>
+        public string Field4 { get; set; }
+
+        /// <summary>
+        /// 瀛楁5
+        /// </summary>
+        public string Field5 { get; set; }
+    }
+}
diff --git a/FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryDatasQuery.cs b/FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryDatasQuery.cs
index 4006dc4..2967ec9 100644
--- a/FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryDatasQuery.cs
+++ b/FlexJobApi.Core/Models/Main/Dictionaries/Queries/GetDictionaryDatasQuery.cs
@@ -15,11 +15,16 @@
     public class GetDictionaryDatasQuery : PagedListQuery<PagedListQueryResult<GetDictionaryDatasQueryResultItem>, GetDictionaryDatasQueryResultItem>
     {
         /// <summary>
-        /// 绫诲埆Id
+        /// 绫诲埆Id锛圛d/缂栧彿浜岄�変竴锛�
         /// </summary>
         public Guid? CategoryId { get; set; }
 
         /// <summary>
+        /// 绫诲埆缂栧彿锛圛d/缂栧彿浜岄�変竴锛�
+        /// </summary>
+        public string CategoryCode { get; set; }
+
+        /// <summary>
         /// 鍏抽敭瀛�
         /// </summary>
         public string Keywords { get; set; }
diff --git a/FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuButtonCommand.cs b/FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuButtonCommand.cs
index 77bc812..1d9b5d6 100644
--- a/FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuButtonCommand.cs
+++ b/FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuButtonCommand.cs
@@ -11,13 +11,8 @@
     /// 淇濆瓨鑿滃崟鎸夐挳
     /// </summary>
     [Resource([EnumResourceController.Menu])]
-    public class SaveMenuButtonCommand : IRequest<Guid>
+    public class SaveMenuButtonCommand : SaveDataCommand
     {
-        /// <summary>
-        /// Id
-        /// </summary>
-        public Guid? Id { get; set; }
-
         /// <summary>
         /// 鑿滃崟Id
         /// </summary>
diff --git a/FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuCommand.cs b/FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuCommand.cs
index 200d8da..8322094 100644
--- a/FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuCommand.cs
+++ b/FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuCommand.cs
@@ -12,17 +12,12 @@
     /// 淇濆瓨鑿滃崟
     /// </summary>
     [Resource([EnumResourceController.Menu])]
-    public class SaveMenuCommand : IRequest<Guid>
+    public class SaveMenuCommand : SaveDataCommand
     {
         public SaveMenuCommand()
         {
             Groups = [];
         }
-
-        /// <summary>
-        /// Id
-        /// </summary>
-        public Guid? Id { get; set; }
 
         /// <summary>
         /// 鐢ㄦ埛绫诲瀷
diff --git a/FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuFieldCommand.cs b/FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuFieldCommand.cs
index a5d35cd..a937918 100644
--- a/FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuFieldCommand.cs
+++ b/FlexJobApi.Core/Models/User/Menus/Commands/SaveMenuFieldCommand.cs
@@ -11,13 +11,8 @@
     /// 淇濆瓨鑿滃崟瀛楁
     /// </summary>
     [Resource([EnumResourceController.Menu])]
-    public class SaveMenuFieldCommand : IRequest<Guid>
+    public class SaveMenuFieldCommand : SaveDataCommand
     {
-        /// <summary>
-        /// Id
-        /// </summary>
-        public Guid? Id { get; set; }
-
         /// <summary>
         /// 鑿滃崟Id
         /// </summary>
diff --git a/FlexJobApi.Core/Models/User/Roles/Commands/SetRoleIsDisabledCommand.cs b/FlexJobApi.Core/Models/User/Roles/Commands/SetRoleIsDisabledCommand.cs
index b84990b..4516bdd 100644
--- a/FlexJobApi.Core/Models/User/Roles/Commands/SetRoleIsDisabledCommand.cs
+++ b/FlexJobApi.Core/Models/User/Roles/Commands/SetRoleIsDisabledCommand.cs
@@ -12,22 +12,8 @@
     /// 璁剧疆瑙掕壊鏄惁绂佺敤
     /// </summary>
     [Resource([EnumResourceController.Role])]
-    public class SetRoleIsDisabledCommand : IRequest<int>
+    public class SetRoleIsDisabledCommand : SetIsDisabledCommand
     {
-        public SetRoleIsDisabledCommand()
-        {
-            Ids = [];
-        }
 
-        /// <summary>
-        /// Id
-        /// </summary>
-        [Required]
-        public List<Guid> Ids { get; set; }
-
-        /// <summary>
-        /// 鏄惁绂佺敤
-        /// </summary>
-        public bool IsDisabled { get; set; }
     }
 }
diff --git a/FlexJobApi.Core/Utils/DbUtils/DbUtils.cs b/FlexJobApi.Core/Utils/DbUtils/DbUtils.cs
index e6a1389..59d0c77 100644
--- a/FlexJobApi.Core/Utils/DbUtils/DbUtils.cs
+++ b/FlexJobApi.Core/Utils/DbUtils/DbUtils.cs
@@ -16,6 +16,7 @@
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using System.Timers;
 
 namespace FlexJobApi.Core
 {
@@ -24,6 +25,105 @@
     /// </summary>
     public static class DbUtils
     {
+        /// <summary>
+        /// 鑾峰彇鏍戝舰鏁版嵁璺緞
+        /// </summary>
+        /// <typeparam name="TEntity"></typeparam>
+        /// <param name="parentId"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public static async Task<string> GetTreeDataPath<TEntity>(
+             Guid? parentId,
+            CancellationToken cancellationToken = default)
+            where TEntity : CommonEntity, ITreeData<TEntity>, new()
+        {
+            var rep = Db.GetRepository<TEntity>();
+            if (parentId.HasValue)
+            {
+                var parent = await rep.AsQueryable().AsNoTracking()
+                    .Where(it => it.Id == parentId)
+                    .Select(it => new
+                    {
+                        it.Path,
+                        it.Code
+                    })
+                    .FirstOrDefaultAsync(cancellationToken);
+                var summary = typeof(TEntity).GetSummary();
+                if (parent == null) throw Oops.Oh(EnumErrorCodeType.s404, $"涓婄骇{summary}");
+                return $"{parent.Path}{parent.Code}/";
+            }
+            else
+            {
+                return "/";
+            }
+        }
+
+        /// <summary>
+        /// 鏇存柊鏍戝舰鏁版嵁涓嬬骇璺緞
+        /// </summary>
+        /// <param name="oldPath"></param>
+        /// <param name="newPath"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public static async Task UpdateTreeDataChildrenPath<TEntity>(
+            string oldPath,
+            string newPath,
+            CancellationToken cancellationToken)
+            where TEntity : CommonEntity, ITreeData<TEntity>, new()
+        {
+            var rep = Db.GetRepository<TEntity>();
+            var models = await rep.AsQueryable()
+                .Where(it => it.Path.StartsWith(oldPath))
+                .ToListAsync(cancellationToken);
+            if (models.IsNotNull())
+            {
+                foreach (var model in models)
+                {
+                    model.Path = model.Path.Replace(oldPath, newPath);
+                }
+                await rep.UpdateAsync(models);
+            }
+        }
+
+        /// <summary>
+        /// 鏌ヨ閫夋嫨鍣ㄦ暟鎹�
+        /// </summary>
+        /// <typeparam name="TEntity"></typeparam>
+        /// <typeparam name="TValue"></typeparam>
+        /// <typeparam name="TData"></typeparam>
+        /// <param name="request"></param>
+        /// <param name="getValue"></param>
+        /// <param name="getLabel"></param>
+        /// <param name="query"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public static async Task<List<SelectQueryResultOption<TValue, TData>>> GetSelect<TEntity, TValue, TData>(
+            this SelectQuery<TValue, TData> request,
+            Func<TData, TValue> getValue,
+            Func<TData, string> getLabel,
+            Func<IQueryable<TEntity>, IQueryable<TEntity>> query = null,
+            CancellationToken cancellationToken = default)
+            where TEntity : CommonEntity, new()
+        {
+            var rep = Db.GetRepository<TEntity>();
+            var q = rep.AsQueryable().AsNoTracking();
+            if (query != null) q = query(q);
+            else q = q.OrderBy(it => it.Sort).ThenBy(it => it.CreatedTime);
+            var models = await q
+                .ProjectToType<TData>()
+                .ToListAsync(cancellationToken);
+            var options = new List<SelectQueryResultOption<TValue, TData>>();
+            foreach (var model in models)
+            {
+                var option = new SelectQueryResultOption<TValue, TData>();
+                option.Data = model;
+                option.Value = getValue(model);
+                option.Label = getLabel(model);
+                options.Add(option);
+            }
+            return options;
+        }
+
         /// <summary>
         /// 鏌ヨ鍒嗛〉鍒楄〃鏁版嵁
         /// </summary>
@@ -34,8 +134,8 @@
         /// <param name="selector"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public static async Task<PagedListQueryResult<TItem>> ToPagedListAsync<TEntity, TItem>(
-            this PagedListQueryPageModel page, 
+        public static async Task<PagedListQueryResult<TItem>> GetPagedListAsync<TEntity, TItem>(
+            this PagedListQueryPageModel page,
             Func<IQueryable<TEntity>, IQueryable<TEntity>> query = null,
             Expression<Func<TEntity, TItem>> selector = null,
             CancellationToken cancellationToken = default)
@@ -45,6 +145,7 @@
             var rep = Db.GetRepository<TEntity>();
             var q = rep.AsQueryable().AsNoTracking();
             if (query != null) q = query(q);
+            else q = q.OrderBy(it => it.Sort).ThenBy(it => it.CreatedTime);
             q = q.OrderBy(page.OrderInput);
             var s = selector == null
                 ? q.ProjectToType<TItem>()
@@ -65,7 +166,9 @@
         /// <param name="q"></param>
         /// <param name="orders"></param>
         /// <returns></returns>
-        public static IQueryable<T> OrderBy<T>(this IQueryable<T> q, List<PagedListQueryPageModelOrderInput> orders)
+        public static IQueryable<T> OrderBy<T>(
+            this IQueryable<T> q,
+            List<PagedListQueryPageModelOrderInput> orders)
         {
             if (orders.IsNull()) return q;
 
@@ -126,7 +229,10 @@
         /// <param name="query"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public static async Task<int> SetIsDisable<TEntity>(this SetIsDisabledCommand request, Func<IQueryable<TEntity>, IQueryable<TEntity>> query = null, CancellationToken cancellationToken = default)
+        public static async Task<int> SetIsDisabled<TEntity>(
+            this SetIsDisabledCommand request,
+            Func<IQueryable<TEntity>, IQueryable<TEntity>> query = null,
+            CancellationToken cancellationToken = default)
             where TEntity : CommonEntity, IIsDisabled, new()
         {
             var rep = Db.GetRepository<TEntity>();
@@ -150,7 +256,10 @@
         /// <param name="query"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public static async Task<int> DeleteData<TEntity>(this DeleteDataCommand request, Func<IQueryable<TEntity>, IQueryable<TEntity>> query = null, CancellationToken cancellationToken = default)
+        public static async Task<int> DeleteData<TEntity>(
+            this DeleteDataCommand request, Func<IQueryable<TEntity>,
+            IQueryable<TEntity>> query = null,
+            CancellationToken cancellationToken = default)
             where TEntity : CommonEntity, new()
         {
             var rep = Db.GetRepository<TEntity>();
@@ -171,29 +280,36 @@
         /// <typeparam name="TRequest"></typeparam>
         /// <param name="request"></param>
         /// <param name="checkExist"></param>
+        /// <param name="update"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public static async Task<Guid> SaveData<TEntity, TRequest>(this TRequest request, Func<IQueryable<TEntity>, TEntity, TRequest, bool> checkExist = null, CancellationToken cancellationToken = default)
+        public static async Task<Guid> SaveData<TEntity, TRequest>(
+            this TRequest request,
+            Expression<Func<TEntity, bool>> checkExist = null,
+            Action<TEntity> update = null,
+            CancellationToken cancellationToken = default)
             where TEntity : CommonEntity, new()
             where TRequest : SaveDataCommand, new()
         {
             var xmlDoc = await XmlDocUtils.GetXmlDocAsync();
             var summary = await typeof(TEntity).GetSummary(xmlDoc);
             var rep = Db.GetRepository<TEntity>();
+            if (checkExist != null && await rep.AsQueryable().AsNoTracking().AnyAsync(checkExist))
+                throw Oops.Oh(EnumErrorCodeType.s405, $"璇summary ?? "淇℃伅"}");
             if (request.Id.HasValue)
             {
                 var entity = await rep.AsQueryable().FirstOrDefaultAsync(it => it.Id == request.Id, cancellationToken);
                 if (entity == null) throw Oops.Oh(EnumErrorCodeType.s404, $"璇summary ?? "淇℃伅"}");
-                if (checkExist != null && checkExist(rep.AsQueryable().AsNoTracking(), entity, request)) throw Oops.Oh(EnumErrorCodeType.s405, $"璇summary ?? "淇℃伅"}");
-                request.Adapt(entity);
+                if (update != null) update(entity);
+                else request.Adapt(entity);
                 await rep.UpdateAsync(entity);
                 return entity.Id;
             }
             else
             {
                 var entity = new TEntity();
-                if (checkExist != null && checkExist(rep.AsQueryable().AsNoTracking(), entity, request)) throw Oops.Oh(EnumErrorCodeType.s405, $"璇summary ?? "淇℃伅"}");
-                request.Adapt(entity);
+                if (update != null) update(entity);
+                else request.Adapt(entity);
                 await rep.InsertAsync(entity);
                 return entity.Id;
             }
diff --git a/FlexJobApi.Core/Utils/DbUtils/SelectQuery.cs b/FlexJobApi.Core/Utils/DbUtils/SelectQuery.cs
index 6fe76c1..3328e9a 100644
--- a/FlexJobApi.Core/Utils/DbUtils/SelectQuery.cs
+++ b/FlexJobApi.Core/Utils/DbUtils/SelectQuery.cs
@@ -11,7 +11,8 @@
     /// 閫夋嫨鍣ㄦ煡璇�
     /// </summary>
     /// <typeparam name="TValue"></typeparam>
-    public abstract class SelectQuery<TValue> : IRequest<List<SelectQueryResultItem<TValue>>>
+    /// <typeparam name="TData"></typeparam>
+    public abstract class SelectQuery<TValue, TData> : IRequest<List<SelectQueryResultOption<TValue, TData>>>
     {
 
     }
@@ -19,7 +20,7 @@
     /// <summary>
     /// 閫夋嫨鍣ㄦ煡璇�-缁撴灉-椤�
     /// </summary>
-    public class SelectQueryResultItem<TValue>
+    public class SelectQueryResultOption<TValue, TData>
     {
         /// <summary>
         /// 鍊�
diff --git a/FlexJobApi.User.Application/Auths/Queries/GetCurrentLogierMenusQueryHandler.cs b/FlexJobApi.User.Application/Auths/Queries/GetCurrentLogierMenusQueryHandler.cs
index f531070..3d89064 100644
--- a/FlexJobApi.User.Application/Auths/Queries/GetCurrentLogierMenusQueryHandler.cs
+++ b/FlexJobApi.User.Application/Auths/Queries/GetCurrentLogierMenusQueryHandler.cs
@@ -34,7 +34,7 @@
             var all = await (from m in repMenu.AsQueryable().AsNoTracking()
                              join rm in repRoleMenu.AsQueryable().AsNoTracking() on m.Id equals rm.MenuId
                              join ur in repUserInfoRole.AsQueryable().AsNoTracking() on rm.RoleId equals ur.RoleId
-                             where ur.UserInfoId == logier.UserInfoId
+                             where ur.UserInfoId == logier.UserInfoId && !m.IsDisabled
                              && (m.Type == EnumMenuType.Menu || m.Type == EnumMenuType.Page || m.Type == EnumMenuType.Modal)
                              select m).ProjectToType<GetMenusQueryResultItem>().ToListAsync();
             var models = all.Where(it => it.ParentId == null).ToList();
diff --git a/FlexJobApi.User.Application/FlexJobApi.User.Application.xml b/FlexJobApi.User.Application/FlexJobApi.User.Application.xml
index 577984f..b0058c5 100644
--- a/FlexJobApi.User.Application/FlexJobApi.User.Application.xml
+++ b/FlexJobApi.User.Application/FlexJobApi.User.Application.xml
@@ -77,13 +77,6 @@
         <member name="M:FlexJobApi.User.Application.SaveMenuButtonCommandHandler.Handle(FlexJobApi.Core.SaveMenuButtonCommand,System.Threading.CancellationToken)">
             <inheritdoc/>
         </member>
-        <member name="M:FlexJobApi.User.Application.SaveMenuButtonCommandHandler.CheckExist(FlexJobApi.Core.Menu)">
-            <summary>
-            鏍¢獙鑿滃崟鏄惁閲嶅
-            </summary>
-            <param name="entity"></param>
-            <returns></returns>
-        </member>
         <member name="T:FlexJobApi.User.Application.SaveMenuCommandHandler">
             <summary>
             淇濆瓨鑿滃崟
@@ -118,33 +111,6 @@
             <param name="request"></param>
             <returns></returns>
         </member>
-        <member name="M:FlexJobApi.User.Application.SaveMenuCommandHandler.CheckExist(FlexJobApi.Core.Menu,System.Nullable{System.Guid},System.String)">
-            <summary>
-            鏍¢獙鑿滃崟鏄惁閲嶅
-            </summary>
-            <param name="entity"></param>
-            <param name="parentId"></param>
-            <param name="code"></param>
-            <returns></returns>
-        </member>
-        <member name="M:FlexJobApi.User.Application.SaveMenuCommandHandler.GetPathAsync(System.Nullable{System.Guid},System.String,System.Threading.CancellationToken)">
-            <summary>
-            鑾峰彇鍦板潃
-            </summary>
-            <param name="parentId"></param>
-            <param name="code"></param>
-            <param name="cancellationToken"></param>
-            <returns></returns>
-        </member>
-        <member name="M:FlexJobApi.User.Application.SaveMenuCommandHandler.UpdateChildrensPath(System.String,System.String,System.Threading.CancellationToken)">
-            <summary>
-            鏇存柊涓嬬骇鑿滃崟璺緞
-            </summary>
-            <param name="oldPath"></param>
-            <param name="newPath"></param>
-            <param name="cancellationToken"></param>
-            <returns></returns>
-        </member>
         <member name="T:FlexJobApi.User.Application.SaveMenuFieldCommandHandler">
             <summary>
             
@@ -157,13 +123,6 @@
         </member>
         <member name="M:FlexJobApi.User.Application.SaveMenuFieldCommandHandler.Handle(FlexJobApi.Core.SaveMenuFieldCommand,System.Threading.CancellationToken)">
             <inheritdoc/>
-        </member>
-        <member name="M:FlexJobApi.User.Application.SaveMenuFieldCommandHandler.CheckExist(FlexJobApi.Core.Menu)">
-            <summary>
-            鏍¢獙鑿滃崟鏄惁閲嶅
-            </summary>
-            <param name="entity"></param>
-            <returns></returns>
         </member>
         <member name="T:FlexJobApi.User.Application.SetMenuSwitchCommandHandler">
             <summary>
@@ -271,7 +230,7 @@
             璁剧疆瑙掕壊鏄惁绂佺敤
             </summary>
         </member>
-        <member name="M:FlexJobApi.User.Application.SetRoleIsDisabledCommandHandler.#ctor(Furion.DatabaseAccessor.IRepository{FlexJobApi.Core.Role})">
+        <member name="M:FlexJobApi.User.Application.SetRoleIsDisabledCommandHandler.#ctor">
             <summary>
             璁剧疆瑙掕壊鏄惁绂佺敤
             </summary>
diff --git a/FlexJobApi.User.Application/Menus/Commands/SaveMenuButtonCommandHandler.cs b/FlexJobApi.User.Application/Menus/Commands/SaveMenuButtonCommandHandler.cs
index 342236e..8f77325 100644
--- a/FlexJobApi.User.Application/Menus/Commands/SaveMenuButtonCommandHandler.cs
+++ b/FlexJobApi.User.Application/Menus/Commands/SaveMenuButtonCommandHandler.cs
@@ -26,44 +26,29 @@
         {
             var parent = await rep.FirstOrDefaultAsync(it => it.Id == request.ParentId);
             if (parent == null) throw Oops.Oh(EnumErrorCodeType.s404, "涓婄骇鑿滃崟");
-            if (request.Id.HasValue)
-            {
-                var entity = await rep.FirstOrDefaultAsync(it => it.Id == request.Id);
-                if (entity == null) throw Oops.Oh(EnumErrorCodeType.s404, "璇ヨ彍鍗�");
-                if (entity.ParentId != request.ParentId) throw Oops.Oh(EnumErrorCodeType.s410, "涓婄骇Id");
-                request.Adapt(entity);
-                if (await CheckExist(entity)) throw Oops.Oh(EnumErrorCodeType.s406, "鑿滃崟缂栧彿");
-                await rep.UpdateAsync(entity);
-                return entity.Id;
-            }
-            else
-            {
-                var entity = new Menu();
-                entity.Path = $"{parent.Path}{parent.Code}/";
-                entity.Type = EnumMenuType.Button;
-                entity.VisitLevel = parent.VisitLevel;
-                request.Adapt(entity);
-                if (await CheckExist(entity)) throw Oops.Oh(EnumErrorCodeType.s406, "鑿滃崟缂栧彿");
-                await rep.InsertAsync(entity);
-                return entity.Id;
-            }
-        }
-
-        /// <summary>
-        /// 鏍¢獙鑿滃崟鏄惁閲嶅
-        /// </summary>
-        /// <param name="entity"></param>
-        /// <returns></returns>
-        private async Task<bool> CheckExist(Menu entity)
-        {
-            return await rep.AsQueryable().AsNoTracking()
-                .AnyAsync(it =>
-                    it.ParentId == entity.ParentId
-                    && it.Type == entity.Type
-                    && it.Group == entity.Group
-                    && it.Location == entity.Location
-                    && it.Code == entity.Code
-                    && it.Id != entity.Id);
+            return await request.SaveData<Menu, SaveMenuButtonCommand>(
+                it =>
+                    it.ParentId == request.ParentId
+                    && it.Type == EnumMenuType.Button
+                    && it.Group == request.Group
+                    && it.Location == request.Location
+                    && it.Code == request.Code
+                    && it.Id != request.Id,
+                (entity) =>
+                {
+                    if (request.Id.HasValue)
+                    {
+                        if (entity.ParentId != request.ParentId) throw Oops.Oh(EnumErrorCodeType.s410, "涓婄骇Id");
+                    }
+                    else
+                    {
+                        entity.Path = $"{parent.Path}{parent.Code}/";
+                        entity.Type = EnumMenuType.Button;
+                        entity.VisitLevel = parent.VisitLevel;
+                    }
+                    request.Adapt(entity);
+                },
+                cancellationToken);
         }
     }
 }
diff --git a/FlexJobApi.User.Application/Menus/Commands/SaveMenuCommandHandler.cs b/FlexJobApi.User.Application/Menus/Commands/SaveMenuCommandHandler.cs
index 59550fc..925775d 100644
--- a/FlexJobApi.User.Application/Menus/Commands/SaveMenuCommandHandler.cs
+++ b/FlexJobApi.User.Application/Menus/Commands/SaveMenuCommandHandler.cs
@@ -1,4 +1,5 @@
-锘縰sing FlexJobApi.Core;
+锘縰sing Consul;
+using FlexJobApi.Core;
 using Furion.DatabaseAccessor;
 using Furion.DataValidation;
 using Furion.FriendlyException;
@@ -29,35 +30,29 @@
         /// <inheritdoc/>
         public async Task<Guid> Handle(SaveMenuCommand request, CancellationToken cancellationToken)
         {
-            if (request.Id.HasValue)
-            {
-                var entity = await rep.AsQueryable()
-                    .Include(it => it.Children)
-                    .FirstOrDefaultAsync(it => it.Id == request.Id, cancellationToken);
-                if (entity == null) throw Oops.Oh(EnumErrorCodeType.s404, "鑿滃崟");
-                if (entity.UserType != request.UserType) throw Oops.Oh(EnumErrorCodeType.s410, "鐢ㄦ埛绫诲瀷");
-                if (entity.ClientType != request.ClientType) throw Oops.Oh(EnumErrorCodeType.s410, "瀹㈡埛绔被鍨�");
-
-                var path = await GetPathAsync(request.ParentId, request.Code, cancellationToken);
-                if (await CheckExist(entity, request.ParentId, request.Code)) throw Oops.Oh(EnumErrorCodeType.s405, "鑿滃崟缂栧彿");
-                await UpdateChildrensPath($"{entity.Path}/{entity.Code}", $"{path}/{request.Code}", cancellationToken);
-                request.Adapt(entity);
-                SaveChildrens(entity, request);
-                await rep.UpdateAsync(entity);
-
-                return entity.Id;
-            }
-            else
-            {
-                var entity = new Menu();
-                request.Adapt(entity);
-                entity.Path = await GetPathAsync(entity.ParentId, entity.Code, cancellationToken);
-                if (await CheckExist(entity, request.ParentId, request.Code)) throw Oops.Oh(EnumErrorCodeType.s405, "鑿滃崟缂栧彿");
-                SaveChildrens(entity, request);
-                await rep.InsertAsync(entity);
-
-                return entity.Id;
-            }
+            return await request.SaveData<Menu, SaveMenuCommand>(
+                it =>
+                    it.UserType == request.UserType
+                    && it.ClientType == request.ClientType
+                    && it.ParentId == request.ParentId
+                    && it.Code == request.Code
+                    && it.Id != request.Id,
+                (entity) =>
+                {
+                    entity.Path = DbUtils.GetTreeDataPath<DictionaryData>(request.ParentId, cancellationToken).Result;
+                    if (request.Id.HasValue)
+                    {
+                        if (entity.UserType != request.UserType) throw Oops.Oh(EnumErrorCodeType.s410, "鐢ㄦ埛绫诲瀷");
+                        if (entity.ClientType != request.ClientType) throw Oops.Oh(EnumErrorCodeType.s410, "瀹㈡埛绔被鍨�");
+                        DbUtils.UpdateTreeDataChildrenPath<DictionaryData>(
+                           $"{entity.Path}/{entity.Code}/",
+                           $"{entity.Path}/{request.Code}/",
+                           cancellationToken).Wait();
+                    }
+                    request.Adapt(entity);
+                    SaveChildrens(entity, request);
+                },
+                cancellationToken);
         }
 
         /// <summary>
@@ -72,10 +67,10 @@
             var childrenIds = GetRequestChildrenIds(request);
             // 鍒犻櫎瀛愮骇
             entity.Children = entity.Children
-                .Where(it => 
-                    it.Type == EnumMenuType.Menu 
-                    || it.Type == EnumMenuType.Page 
-                    || it.Type == EnumMenuType.Modal 
+                .Where(it =>
+                    it.Type == EnumMenuType.Menu
+                    || it.Type == EnumMenuType.Page
+                    || it.Type == EnumMenuType.Modal
                     || childrenIds.Contains(it.Id))
                 .ToList();
 
@@ -172,75 +167,6 @@
                     g.Fields.Where(f => f.Id.HasValue).Select(f => f.Id!.Value)))
                 .SelectMany(it => it)
                 .ToList();
-        }
-
-        /// <summary>
-        /// 鏍¢獙鑿滃崟鏄惁閲嶅
-        /// </summary>
-        /// <param name="entity"></param>
-        /// <param name="parentId"></param>
-        /// <param name="code"></param>
-        /// <returns></returns>
-        private async Task<bool> CheckExist(Menu entity, Guid? parentId, string code)
-        {
-            return await rep.AsQueryable().AsNoTracking()
-                .AnyAsync(it =>
-                    it.UserType == entity.UserType
-                    && it.ClientType == entity.ClientType
-                    && it.ParentId == parentId
-                    && it.Code == code
-                    && it.Id != entity.Id);
-        }
-
-        /// <summary>
-        /// 鑾峰彇鍦板潃
-        /// </summary>
-        /// <param name="parentId"></param>
-        /// <param name="code"></param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
-        private async Task<string> GetPathAsync(Guid? parentId, string code, CancellationToken cancellationToken)
-        {
-            if (parentId.HasValue)
-            {
-                var parent = await rep.AsQueryable().AsNoTracking()
-                    .Where(it => it.Id == parentId)
-                    .Select(it => new
-                    {
-                        it.Path,
-                        it.Code
-                    })
-                    .FirstOrDefaultAsync(cancellationToken);
-                if (parent == null) throw Oops.Oh(EnumErrorCodeType.s404, "涓婄骇鑿滃崟");
-                return $"{parent.Path}{parent.Code}/";
-            }
-            else
-            {
-                return "/";
-            }
-        }
-
-        /// <summary>
-        /// 鏇存柊涓嬬骇鑿滃崟璺緞
-        /// </summary>
-        /// <param name="oldPath"></param>
-        /// <param name="newPath"></param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
-        private async Task UpdateChildrensPath(string oldPath, string newPath, CancellationToken cancellationToken)
-        {
-            var models = await rep.AsQueryable()
-                .Where(it => it.Path.StartsWith(oldPath))
-                .Where(it => it.Type == EnumMenuType.Menu || it.Type == EnumMenuType.Page || it.Type == EnumMenuType.Modal)
-                .ToListAsync(cancellationToken);
-            if (models.IsNotNull())
-            {
-                foreach (var model in models)
-                {
-                    model.Path = model.Path.Replace(oldPath, newPath);
-                }
-                await rep.UpdateAsync(models);
-            }
         }
     }
 }
diff --git a/FlexJobApi.User.Application/Menus/Commands/SaveMenuFieldCommandHandler.cs b/FlexJobApi.User.Application/Menus/Commands/SaveMenuFieldCommandHandler.cs
index 2bda2f5..a4889e4 100644
--- a/FlexJobApi.User.Application/Menus/Commands/SaveMenuFieldCommandHandler.cs
+++ b/FlexJobApi.User.Application/Menus/Commands/SaveMenuFieldCommandHandler.cs
@@ -26,44 +26,28 @@
         {
             var parent = await rep.FirstOrDefaultAsync(it => it.Id == request.ParentId);
             if (parent == null) throw Oops.Oh(EnumErrorCodeType.s404, "涓婄骇鑿滃崟");
-            if (request.Id.HasValue)
-            {
-                var entity = await rep.FirstOrDefaultAsync(it => it.Id == request.Id);
-                if (entity == null) throw Oops.Oh(EnumErrorCodeType.s404, "璇ヨ彍鍗�");
-                if (entity.ParentId != request.ParentId) throw Oops.Oh(EnumErrorCodeType.s410, "涓婄骇Id");
-                request.Adapt(entity);
-                if (await CheckExist(entity)) throw Oops.Oh(EnumErrorCodeType.s406, "鑿滃崟缂栧彿");
-                await rep.UpdateAsync(entity);
-                return entity.Id;
-            }
-            else
-            {
-                var entity = new Menu();
-                entity.Path = $"{parent.Path}{parent.Code}/";
-                entity.Type = EnumMenuType.Field;
-                entity.VisitLevel = parent.VisitLevel;
-                request.Adapt(entity);
-                if (await CheckExist(entity)) throw Oops.Oh(EnumErrorCodeType.s406, "鑿滃崟缂栧彿");
-                await rep.InsertAsync(entity);
-                return entity.Id;
-            }
+            return await request.SaveData<Menu, SaveMenuFieldCommand>(
+                it =>
+                    it.ParentId == request.ParentId
+                    && it.Type == EnumMenuType.Field
+                    && it.Group == request.Group
+                    && it.Code == request.Code
+                    && it.Id != request.Id,
+                (entity) =>
+                {
+                    if (request.Id.HasValue)
+                    {
+                        if (entity.ParentId != request.ParentId) throw Oops.Oh(EnumErrorCodeType.s410, "涓婄骇Id");
+                    }
+                    else
+                    {
+                        entity.Path = $"{parent.Path}{parent.Code}/";
+                        entity.Type = EnumMenuType.Field;
+                        entity.VisitLevel = parent.VisitLevel;
+                    }
+                    request.Adapt(entity);
+                },
+                cancellationToken);
         }
-
-        /// <summary>
-        /// 鏍¢獙鑿滃崟鏄惁閲嶅
-        /// </summary>
-        /// <param name="entity"></param>
-        /// <returns></returns>
-        private async Task<bool> CheckExist(Menu entity)
-        {
-            return await rep.AsQueryable().AsNoTracking()
-                .AnyAsync(it =>
-                    it.ParentId == entity.ParentId
-                    && it.Type == entity.Type
-                    && it.Group == entity.Group
-                    && it.Code == entity.Code
-                    && it.Id != entity.Id);
-        }
-
     }
 }
diff --git a/FlexJobApi.User.Application/Roles/Commands/SetRoleIsDisabledCommandHandler.cs b/FlexJobApi.User.Application/Roles/Commands/SetRoleIsDisabledCommandHandler.cs
index eb437e5..84b92f9 100644
--- a/FlexJobApi.User.Application/Roles/Commands/SetRoleIsDisabledCommandHandler.cs
+++ b/FlexJobApi.User.Application/Roles/Commands/SetRoleIsDisabledCommandHandler.cs
@@ -14,23 +14,12 @@
     /// 璁剧疆瑙掕壊鏄惁绂佺敤
     /// </summary>
     [Resource([EnumResourceController.Role])]
-    public class SetRoleIsDisabledCommandHandler(
-            IRepository<Role> rep
-        ) : IRequestHandler<SetRoleIsDisabledCommand, int>
+    public class SetRoleIsDisabledCommandHandler() : IRequestHandler<SetRoleIsDisabledCommand, int>
     {
-        private readonly IRepository<Role> rep = rep;
-
         /// <inheritdoc/>
-        public async Task<int> Handle(SetRoleIsDisabledCommand request, CancellationToken cancellationToken)
+        public Task<int> Handle(SetRoleIsDisabledCommand request, CancellationToken cancellationToken)
         {
-            var entities = await rep.AsQueryable()
-                .Where(it => request.Ids.Contains(it.Id) && it.IsDisabled != request.IsDisabled)
-                .ToListAsync();
-            foreach (var entity in entities)
-            {
-                entity.IsDisabled = request.IsDisabled;
-            }
-            return entities.Count;
+            return request.SetIsDisabled<Role>(cancellationToken: cancellationToken);
         }
     }
 }
diff --git a/FlexJobApi.User.Application/Roles/Queries/GetRolesQueryHandler.cs b/FlexJobApi.User.Application/Roles/Queries/GetRolesQueryHandler.cs
index 84cbf3b..30e1a86 100644
--- a/FlexJobApi.User.Application/Roles/Queries/GetRolesQueryHandler.cs
+++ b/FlexJobApi.User.Application/Roles/Queries/GetRolesQueryHandler.cs
@@ -27,7 +27,7 @@
         /// <inheritdoc/>
         public async Task<PagedListQueryResult<GetRolesQueryResultItem>> Handle(GetRolesQuery request, CancellationToken cancellationToken)
         {
-            var result = await request.PageModel.ToPagedListAsync<Role, GetRolesQueryResultItem>(
+            var result = await request.PageModel.GetPagedListAsync<Role, GetRolesQueryResultItem>(
                 q =>
                 {
                     q = q.OrderBy(it => it.Sort).ThenBy(it => it.CreatedTime);
diff --git a/FlexJobApi.User.Application/UserInfos/Queries/GetOperationUserInfosQueryHandler.cs b/FlexJobApi.User.Application/UserInfos/Queries/GetOperationUserInfosQueryHandler.cs
index 44c2d5e..a776dae 100644
--- a/FlexJobApi.User.Application/UserInfos/Queries/GetOperationUserInfosQueryHandler.cs
+++ b/FlexJobApi.User.Application/UserInfos/Queries/GetOperationUserInfosQueryHandler.cs
@@ -24,7 +24,7 @@
         /// <inheritdoc/>
         public async Task<PagedListQueryResult<GetOperationUserInfosQueryResultItem>> Handle(GetOperationUserInfosQuery request, CancellationToken cancellationToken)
         {
-            var result = await request.PageModel.ToPagedListAsync<UserInfo, GetOperationUserInfosQueryResultItem>(
+            var result = await request.PageModel.GetPagedListAsync<UserInfo, GetOperationUserInfosQueryResultItem>(
                 q =>
                 {
                     q = q.OrderByDescending(it => it.Level).ThenByDescending(it => it.CreatedTime)

--
Gitblit v1.9.1