Pārlūkot izejas kodu

Merge commit 'refs/pull/334/head' of https://gitee.com/zuohuaijun/Admin.NET into next

zuohuaijun 4 gadi atpakaļ
vecāks
revīzija
c99ca35f1a

+ 7 - 7
Admin.NET/Admin.NET.Core/Const/CacheConst.cs

@@ -8,27 +8,27 @@ public class CacheConst
     /// <summary>
     /// 用户缓存
     /// </summary>
-    public const string KeyUser = "user_";
+    public const string KeyUser = "user:";
 
     /// <summary>
     /// 菜单缓存
     /// </summary>
-    public const string KeyMenu = "menu_";
+    public const string KeyMenu = "menu:";
 
     /// <summary>
     /// 权限缓存
     /// </summary>
-    public const string KeyPermission = "permission_";
+    public const string KeyPermission = "permission:";
 
     /// <summary>
     /// 机构Id集合缓存
     /// </summary>
-    public const string KeyOrgIdList = "org_";
+    public const string KeyOrgIdList = "org:";
 
     /// <summary>
     /// 验证码缓存
     /// </summary>
-    public const string KeyVerCode = "verCode_";
+    public const string KeyVerCode = "verCode:";
 
     /// <summary>
     /// 所有缓存关键字集合
@@ -38,12 +38,12 @@ public class CacheConst
     /// <summary>
     /// 定时任务缓存
     /// </summary>
-    public const string KeyTimer = "timer";
+    public const string KeyTimer = "timer:";
 
     /// <summary>
     /// 在线用户缓存
     /// </summary>
-    public const string KeyOnlineUser = "onlineuser";
+    public const string KeyOnlineUser = "onlineuser:";
 
     /// <summary>
     /// 常量下拉框

+ 32 - 11
Admin.NET/Admin.NET.Core/Service/ConstSelector/ConstSelectorService.cs

@@ -6,22 +6,25 @@
 [ApiDescriptionSettings(Name = "常量下拉框", Order = 189)]
 public class ConstSelectorService : IDynamicApiController, ITransient
 {
-    private readonly IDistributedCache _cache;
-
-    public ConstSelectorService(IDistributedCache cache)
+    //private readonly IDistributedCache _cache;
+    private readonly ISysCacheService _sysCacheService;
+    public ConstSelectorService(IDistributedCache cache,
+        ISysCacheService sysCacheService)
     {
-        _cache = cache;
+        //_cache = cache;
+        _sysCacheService = sysCacheService;
     }
 
     /// <summary>
     /// 获取所有常量下拉框列表
     /// </summary>
     /// <returns></returns>
+    [AllowAnonymous]
     [HttpGet("/constSelector/allConstSelector")]
-    public async Task<dynamic> GetAllConstSelector()
+    public async Task<List<SelectorDto>> GetAllConstSelector()
     {
         var key = $"{CacheConst.KeyConstSelector}AllSelector";
-        var json = await _cache.GetStringAsync(key);
+        var json = await _sysCacheService.GetStringAsync(key);
         if (!string.IsNullOrWhiteSpace(json))
         {
             return json.ToObject<List<SelectorDto>>();
@@ -32,7 +35,7 @@ public class ConstSelectorService : IDynamicApiController, ITransient
             Name = x.CustomAttributes.ToList().FirstOrDefault()?.ConstructorArguments.ToList().FirstOrDefault().Value?.ToString() ?? x.Name,
             Code = x.Name
         }).ToList();
-        await _cache.SetStringAsync(key, selectData.ToJson());
+        await _sysCacheService.SetStringAsync(key, selectData.ToJson());
         return selectData;
     }
 
@@ -41,11 +44,12 @@ public class ConstSelectorService : IDynamicApiController, ITransient
     /// </summary>
     /// <param name="typeName"></param>
     /// <returns></returns>
+    [AllowAnonymous]
     [HttpGet("/constSelector/constSelector")]
-    public async Task<dynamic> GetConstSelector(string typeName)
+    public async Task<List<SelectorDto>> GetConstSelector(string typeName)
     {
         var key = $"{CacheConst.KeyConstSelector}{typeName.ToUpper()}";
-        var json = await _cache.GetStringAsync(key);
+        var json = await _sysCacheService.GetStringAsync(key);
         if (!string.IsNullOrWhiteSpace(json))
         {
             return json.ToObject<List<SelectorDto>>();
@@ -61,10 +65,26 @@ public class ConstSelectorService : IDynamicApiController, ITransient
                 Name = x.Name,
                 Code = isEnum ? (int)x.GetValue(BindingFlags.Instance) : x.GetValue(BindingFlags.Instance)
             }).ToList();
-        await _cache.SetStringAsync(key, selectData.ToJson());
+        await _sysCacheService.SetStringAsync(key, selectData.ToJson());
         return selectData;
     }
 
+    /// <summary>
+    /// 获取所有下拉框及选项  用于前端缓存
+    /// </summary>
+    /// <returns></returns>
+    [AllowAnonymous]
+    [HttpGet("/constSelector/allConstSelectorWithOptions")]
+    public async Task<List<SelectorDto>> GetAllConstSelectorWithOptions()
+    {
+        var selectors = await GetAllConstSelector();
+        foreach (var p in selectors)
+        {
+            p.Data = await GetConstSelector(Convert.ToString(p.Code));
+        }
+        return selectors;
+    }
+
     /// <summary>
     /// 获取所有常量
     /// </summary>
@@ -80,4 +100,5 @@ public class ConstSelectorService : IDynamicApiController, ITransient
             return typeList;
         });
     }
-}
+}
+

+ 9 - 7
Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/data.data.ts.vm

@@ -10,7 +10,7 @@ if(@column.EffectType == "Upload"){
 }else if(@column.EffectType == "ApiTreeSelect"){
 @:import { get@(@column.FkEntityName)Tree } from '/@@/api/main/@(@Model.ClassName)';
 }else if(@column.EffectType == "ConstSelector"){
-@:import { getConstSelector } from '/@@/api/sys/admin';
+@:import { codeToName, getSelector } from '/@@/utils/helper/constSelectorHelper';
 }else if(@column.EffectType == "Switch"){
 @:import { Switch } from 'ant-design-vue';
 }
@@ -32,6 +32,10 @@ if(@column.EffectType == "Upload"){
     @:customRender: ({ record }) => {
       @:return h(@(@column.EffectType), { checked: record.@(@column.LowerColumnName) });
     @:},
+}else if(@column.EffectType == "ConstSelector"){
+    @:customRender: ({ record }) => {
+      @:return codeToName(record.@(@column.LowerColumnName), '@(@column.DictTypeCode)');
+    @:},
 }
   @:},
     }
@@ -63,10 +67,9 @@ if(@column.EffectType == "fk"){
       @:},
     @:},
 }else if(@column.EffectType == "ConstSelector"){
-    @:component: 'ApiSelect',
+    @:component: 'Select',
     @:componentProps: {
-      @:api: getConstSelector,
-      @:params: '@(@column.DictTypeCode)',
+      @:options: getSelector('@(@column.DictTypeCode)'),
       @:fieldNames: {
         @:label: 'name',
         @:value: 'code',
@@ -115,10 +118,9 @@ if(@column.EffectType == "fk"){
       @:},
     @:},
 }else if(@column.EffectType == "ConstSelector"){
-    @:component: 'ApiSelect',
+    @:component: 'Select',
     @:componentProps: {
-      @:api: getConstSelector,
-      @:params: '@(@column.DictTypeCode)',
+      @:options: getSelector('@(@column.DictTypeCode)'),
       @:fieldNames: {
         @:label: 'name',
         @:value: 'code',

+ 19 - 0
Vben2/src/api/sys/admin.ts

@@ -136,6 +136,12 @@ enum Api {
   // 常量下拉框接口
   AllConstSelector = '/constSelector/allConstSelector',
   ConstSelector = '/constSelector/constSelector',
+  AllConstSelectorWithOptions = '/constSelector/allConstSelectorWithOptions',
+
+  // 缓存接口
+  GetAllCacheKeys = '/sysCache/keyList',
+  GetStringAsync = '/sysCache/detail',
+  RemoveAsync = '/sysCache/remove',
 }
 
 ////////// 账号管理接口 //////////
@@ -433,3 +439,16 @@ export const getAllConstSelector = () => defHttp.get<any>({ url: Api.AllConstSel
 // 根据类名获取下拉框数据
 export const getConstSelector = (typeName?: string) =>
   defHttp.get<any>({ url: Api.ConstSelector, params: { typeName } });
+// 获取所有下拉框及选项
+export const getAllConstSelectorWithOptions = () =>
+  defHttp.get<any>({ url: Api.AllConstSelectorWithOptions });
+
+////////// 缓存管理接口 //////////
+// 获取所有缓存列表
+export const getAllCacheKeys = () => defHttp.get<any>({ url: Api.GetAllCacheKeys });
+// 根据类名获取下拉框数据
+export const getCacheStringAsync = (cacheKey?: string) =>
+  defHttp.get<any>({ url: Api.GetStringAsync, params: { cacheKey } });
+// 获取所有下拉框及选项
+export const removeCacheAsync = (key?: string) =>
+  defHttp.get<any>({ url: Api.RemoveAsync, params: { key } });

+ 3 - 0
Vben2/src/main.ts

@@ -14,6 +14,7 @@ import { setupStore } from '/@/store';
 import { setupGlobDirectives } from '/@/directives';
 import { setupI18n } from '/@/locales/setupI18n';
 import { registerGlobComp } from '/@/components/registerGlobComp';
+import { setupConstSelectorFilter } from './utils/helper/constSelectorHelper';
 
 async function bootstrap() {
   const app = createApp(App);
@@ -52,6 +53,8 @@ async function bootstrap() {
   // 配置全局错误处理
   setupErrorHandle(app);
 
+  setupConstSelectorFilter(app);
+
   // https://next.router.vuejs.org/api/#isready
   // await router.isReady();
 

+ 4 - 0
Vben2/src/router/guard/permissionGuard.ts

@@ -29,6 +29,10 @@ export function createPermissionGuard(router: Router) {
       return;
     }
 
+    //缓存所有常量数据
+    if (userStore.constSelectorWithOptions.length === 0) {
+      await userStore.getAllConstSelectorWithOptionsAction();
+    }
     const token = userStore.getToken;
 
     // Whitelist can be directly entered

+ 12 - 0
Vben2/src/store/modules/user.ts

@@ -8,6 +8,7 @@ import { ROLES_KEY, TOKEN_KEY, USER_INFO_KEY } from '/@/enums/cacheEnum';
 import { getAuthCache, setAuthCache } from '/@/utils/auth';
 import { GetUserInfoModel, LoginParams } from '/@/api/sys/model/userModel';
 import { doLogout, getUserInfo, loginApi } from '/@/api/sys/user';
+import { getAllConstSelectorWithOptions } from '/@/api/sys/admin';
 import { useI18n } from '/@/hooks/web/useI18n';
 import { useMessage } from '/@/hooks/web/useMessage';
 import { router } from '/@/router';
@@ -23,6 +24,7 @@ interface UserState {
   roleList: RoleEnum[];
   sessionTimeout?: boolean;
   lastUpdateTime: number;
+  constSelectorWithOptions: [];
 }
 
 export const useUserStore = defineStore({
@@ -38,6 +40,8 @@ export const useUserStore = defineStore({
     sessionTimeout: false,
     // Last fetch time
     lastUpdateTime: 0,
+
+    constSelectorWithOptions: [],
   }),
   getters: {
     getUserInfo(): UserInfo {
@@ -55,6 +59,9 @@ export const useUserStore = defineStore({
     getLastUpdateTime(): number {
       return this.lastUpdateTime;
     },
+    getAllConstSelectorWithOptions(): any[] {
+      return this.constSelectorWithOptions || getAllConstSelectorWithOptions();
+    },
   },
   actions: {
     setToken(info: string | undefined) {
@@ -168,6 +175,11 @@ export const useUserStore = defineStore({
         },
       });
     },
+    async getAllConstSelectorWithOptionsAction() {
+      const data = await getAllConstSelectorWithOptions();
+      this.constSelectorWithOptions = data;
+      // setAuthCache(USER_INFO_KEY, data);
+    },
   },
 });
 

+ 32 - 0
Vben2/src/utils/helper/constSelectorHelper.ts

@@ -0,0 +1,32 @@
+import type { App } from 'vue';
+import { useUserStoreWithOut } from '/@/store/modules/user';
+
+export function setupConstSelectorFilter(app: App) {
+  // 全局过滤器  在vue文件中调用  $filters.codeToName(code,type)
+  app.config.globalProperties.$filters = {
+    codeToName(code, type) {
+      return codeToName(code, type);
+    },
+  };
+}
+
+//常量值转换
+export function codeToName(code, type) {
+  const userStore = useUserStoreWithOut();
+  try {
+    const name = userStore.constSelectorWithOptions
+      .filter((x) => x.code === type)
+      .map((x) => x.data)
+      .map((x) => x.filter((y) => y.code === code))
+      .map((x) => x[0].name);
+    return name[0];
+  } catch (error) {
+    return code;
+  }
+}
+
+export function getSelector(type) {
+  const userStore = useUserStoreWithOut();
+  const selector = userStore.constSelectorWithOptions.filter((x) => x.code === type)[0].data;
+  return selector;
+}

+ 172 - 0
Vben2/src/views/sys/admin/cache/index.vue

@@ -0,0 +1,172 @@
+<template>
+  <PageWrapper dense contentFullHeight fixedHeight contentClass="flex">
+    <div class="m-4 mr-0 overflow-hidden bg-white w-1/4 xl:w-1/5" style="overflow: auto">
+      <BasicTree
+        title="缓存键列表"
+        toolbar
+        search
+        :clickRowToExpand="true"
+        :treeData="treeData"
+        :fieldNames="{ key: 'key', title: 'name' }"
+        @select="handleSelect"
+        ref="treeAction"
+      />
+    </div>
+    <div class="w-3/4 xl:w-4/5 m-4">
+      <div class="bg-white h-full">
+        <div class="p-4 text-right flex">
+          <div class="flex-1 text-left leading-8 font-300 text-lg">
+            当前键:{{ currentNode.key }}
+          </div>
+          <PopConfirmButton
+            title="确定要删除此缓存吗?"
+            ok-text="删除"
+            cancel-text="取消"
+            @confirm="onDeleteCache"
+            v-if="currentNode && currentNode.pid"
+            color="warning"
+          >
+            删除此缓存
+          </PopConfirmButton>
+          <PopConfirmButton
+            title="确定要清空此缓存吗?"
+            ok-text="删除"
+            cancel-text="取消"
+            @confirm="onEmptyCache"
+            v-if="currentNode && currentNode.pid == 0"
+            color="error"
+          >
+            清空此缓存
+          </PopConfirmButton>
+        </div>
+        <ScrollContainer ref="scrollRef" style="height: calc(100% - 64px)">
+          <div class="p-4 pt-0">
+            <JsonPreview v-if="isJson" :data="jsonData" />
+            <div v-else>{{ jsonData }}</div>
+          </div>
+        </ScrollContainer>
+      </div>
+    </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { usePermission } from '/@/hooks/web/usePermission';
+  import { PageWrapper } from '/@/components/Page';
+  import { PopConfirmButton } from '/@/components/Button';
+
+  import { getAllCacheKeys, getCacheStringAsync, removeCacheAsync } from '/@/api/sys/admin';
+  import { BasicTree, TreeActionType, TreeItem } from '/@/components/Tree/index';
+  import { JsonPreview } from '/@/components/CodeEditor';
+  import { ScrollContainer } from '/@/components/Container/index';
+  import { useLoading } from '/@/components/Loading';
+
+  export default defineComponent({
+    name: 'OrgManagement',
+    components: {
+      PageWrapper,
+      BasicTree,
+      JsonPreview,
+      ScrollContainer,
+      PopConfirmButton,
+    },
+    setup() {
+      const { hasPermission } = usePermission();
+
+      const treeData = ref<TreeItem[]>([]);
+      const treeAction = ref<Nullable<TreeActionType>>(null);
+      const jsonData = ref<any>('');
+      const isJson = ref(true);
+      const currentNode = ref<any>({});
+      const [openFullLoading, closeFullLoading] = useLoading({
+        tip: '请稍后...',
+      });
+
+      async function fetch() {
+        let keys = await getAllCacheKeys();
+        let cacheData: any[] = [];
+        for (let i = 0; i < keys.length; i++) {
+          let keyArr = keys[i].split(':');
+          let p = keyArr[0];
+          let parentKey = keyArr.length > 0 ? `${p}:` : p;
+          if (cacheData.filter((x) => x.key == parentKey).length === 0) {
+            cacheData.push({
+              pid: 0,
+              children: [],
+              name: p,
+              key: parentKey,
+            });
+          }
+          if (keyArr.length > 1) {
+            let parent = cacheData.filter((x) => x.key == parentKey)[0] || {};
+            parent.children.push({
+              pid: p,
+              name: keyArr[1],
+              key: keys[i],
+            });
+          }
+        }
+        treeData.value = cacheData;
+        console.log(treeData);
+      }
+
+      async function handleSelect(keys, obj) {
+        if (obj == undefined || keys.length == 0) return;
+        console.log('select', keys[0], obj.selectedNodes[0]);
+        currentNode.value = obj.selectedNodes[0];
+
+        if ((currentNode.value?.pid || 0) == 0) {
+          jsonData.value = '';
+          isJson.value = false;
+          return;
+        }
+
+        var res = await getCacheStringAsync(keys[0]);
+        try {
+          jsonData.value = JSON.parse(res);
+          isJson.value = true;
+        } catch (error) {
+          isJson.value = false;
+          jsonData.value = res;
+        }
+      }
+
+      async function onDeleteCache() {
+        openFullLoading();
+        await removeCacheAsync(currentNode.value.key);
+        closeFullLoading();
+        currentNode.value = {};
+        isJson.value = false;
+        jsonData.value = '';
+        fetch();
+      }
+
+      async function onEmptyCache() {
+        openFullLoading();
+        for (let i = 0; i < currentNode.value.children.length; i++) {
+          const e = currentNode.value.children[i];
+          await removeCacheAsync(e.key);
+        }
+        closeFullLoading();
+        currentNode.value = {};
+        isJson.value = false;
+        jsonData.value = '';
+        fetch();
+      }
+
+      fetch();
+
+      return {
+        treeData,
+        treeAction,
+        jsonData,
+        hasPermission,
+        handleSelect,
+        isJson,
+        currentNode,
+        onDeleteCache,
+        onEmptyCache,
+      };
+    },
+  });
+</script>