Selaa lähdekoodia

Merge branch 'next' of https://gitee.com/sanmen359/Admin.NET into next

闫腾 1 vuosi sitten
vanhempi
commit
8f759f35c9

+ 35 - 0
Admin.NET/Admin.NET.Core/Entity/SysUserFavorites.cs

@@ -0,0 +1,35 @@
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 用户菜单快捷导航表
+/// </summary>
+[SugarTable("Sys_User_Favorites", "用户菜单快捷导航表")]
+[SysTable]
+public partial class SysUserFavorites : EntityBaseId
+{
+    /// <summary>
+    /// 用户Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "用户Id")]
+    public long UserId { get; set; }
+
+    /// <summary>
+    /// 用户
+    /// </summary>
+    [Newtonsoft.Json.JsonIgnore]
+    [System.Text.Json.Serialization.JsonIgnore]
+    [Navigate(NavigateType.OneToOne, nameof(UserId))]
+    public SysUser SysUser { get; set; }
+
+    /// <summary>
+    /// 菜单Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "菜单Id")]
+    public long MenuId { get; set; }
+
+    /// <summary>
+    /// 菜单
+    /// </summary>
+    [Navigate(NavigateType.OneToOne, nameof(MenuId))]
+    public SysMenu SysMenu { get; set; }
+}

+ 24 - 4
Admin.NET/Admin.NET.Core/Hub/OnlineUserHub.cs

@@ -1,4 +1,4 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
 //
 // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
 //
@@ -21,16 +21,19 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
     private readonly SysMessageService _sysMessageService;
     private readonly IHubContext<OnlineUserHub, IOnlineUserHub> _onlineUserHubContext;
     private readonly SysCacheService _sysCacheService;
+    private readonly SysConfigService _sysConfigService;
 
     public OnlineUserHub(SqlSugarRepository<SysOnlineUser> sysOnlineUerRep,
         SysMessageService sysMessageService,
         IHubContext<OnlineUserHub, IOnlineUserHub> onlineUserHubContext,
-        SysCacheService sysCacheService)
+        SysCacheService sysCacheService,
+        SysConfigService sysConfigService)
     {
         _sysOnlineUerRep = sysOnlineUerRep;
         _sysMessageService = sysMessageService;
         _onlineUserHubContext = onlineUserHubContext;
         _sysCacheService = sysCacheService;
+        _sysConfigService = sysConfigService;
     }
 
     /// <summary>
@@ -59,7 +62,14 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
             TenantId = string.IsNullOrWhiteSpace(tenantId) ? 0 : Convert.ToInt64(tenantId),
         };
         await _sysOnlineUerRep.InsertAsync(user);
-        _sysCacheService.Set(CacheConst.KeyUserOnline + user.UserId, user);
+
+        // 是否开启单用户登录
+        if (await _sysConfigService.GetConfigValue<bool>(CommonConst.SysSingleLogin)) {
+            _sysCacheService.Set(CacheConst.KeyUserOnline + user.UserId, user);
+        } else {
+            var device = (client.UA.Family + client.UA.Major + client.OS.Family + client.OS.Major).Trim();
+            _sysCacheService.Set(CacheConst.KeyUserOnline + user.UserId + device, user);
+        }
 
         // 以租户Id进行分组
         var groupName = $"{GROUP_ONLINE}{user.TenantId}";
@@ -83,12 +93,22 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
     public override async Task OnDisconnectedAsync(Exception exception)
     {
         if (string.IsNullOrEmpty(Context.ConnectionId)) return;
+        
+        var httpContext = Context.GetHttpContext();
+        var client = Parser.GetDefault().Parse(httpContext.Request.Headers["User-Agent"]);
 
         var user = await _sysOnlineUerRep.AsQueryable().Filter("", true).FirstAsync(u => u.ConnectionId == Context.ConnectionId);
         if (user == null) return;
 
         await _sysOnlineUerRep.DeleteAsync(u => u.Id == user.Id);
-        _sysCacheService.Remove(CacheConst.KeyUserOnline + user.UserId);
+
+        // 是否开启单用户登录
+        if (await _sysConfigService.GetConfigValue<bool>(CommonConst.SysSingleLogin)) {
+            _sysCacheService.Remove(CacheConst.KeyUserOnline + user.UserId);
+        } else {
+            var device = (client.UA.Family + client.UA.Major + client.OS.Family + client.OS.Major).Trim();
+            _sysCacheService.Remove(CacheConst.KeyUserOnline + user.UserId + device);
+        }
 
         // 通知当前组用户变动
         var userList = await _sysOnlineUerRep.AsQueryable().Filter("", true)

+ 1 - 7
Admin.NET/Admin.NET.Core/Service/Const/SysConstService.cs

@@ -77,12 +77,6 @@ public class SysConstService : IDynamicApiController, ITransient
     /// <returns></returns>
     private List<Type> GetConstAttributeList()
     {
-#if NET6_0
-        return AppDomain.CurrentDomain.GetAssemblies().SelectMany(u => u.GetTypes())
-            .Where(u => u.CustomAttributes.Any(c => c.AttributeType == typeof(ConstAttribute))).ToList();
-#else
-        return AppDomain.CurrentDomain.GetAssemblies().SelectMany(u => u.GetExportedTypes())
-            .Where(u => u.CustomAttributes.Any(c => c.AttributeType == typeof(ConstAttribute))).ToList();
-#endif
+        return App.EffectiveTypes.Where(u => u.CustomAttributes.Any(c => c.AttributeType == typeof(ConstAttribute))).ToList();
     }
 }

+ 17 - 0
Admin.NET/Admin.NET.Core/Service/User/Dto/UserFavoritesInput.cs

@@ -0,0 +1,17 @@
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 用户菜单快捷导航收藏
+/// </summary>
+public class UserFavoritesInput
+{
+    /// <summary>
+    /// 用户Id
+    /// </summary>
+    public long UserId { get; set; }
+
+    /// <summary>
+    /// 收藏菜单Id集合
+    /// </summary>
+    public List<long> MenuIdList { get; set; }
+}

+ 81 - 0
Admin.NET/Admin.NET.Core/Service/User/SysUserFavoritesService.cs

@@ -0,0 +1,81 @@
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 用户菜单快捷导航服务
+/// </summary>
+[ApiDescriptionSettings(Order = 500)]
+public class SysUserFavoritesService : IDynamicApiController, ITransient
+{
+    private readonly SqlSugarRepository<SysUserFavorites> _sysUserFavoritesRep;
+    private readonly SysCacheService _sysCacheService;
+
+    public SysUserFavoritesService(SqlSugarRepository<SysUserFavorites> sysUserFavoritesRep,
+        SysCacheService sysCacheService)
+    {
+        _sysUserFavoritesRep = sysUserFavoritesRep;
+        _sysCacheService = sysCacheService;
+    }
+
+    /// <summary>
+    /// 收藏菜单
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [UnitOfWork]
+    public async Task GrantUserFavorites(UserFavoritesInput input)
+    {
+        await _sysUserFavoritesRep.DeleteAsync(u => u.UserId == input.UserId);
+
+        if (input.MenuIdList == null || input.MenuIdList.Count < 1) return;
+        var menus = input.MenuIdList.Select(u => new SysUserFavorites
+        {
+            UserId = input.UserId,
+            MenuId = u
+        }).ToList();
+        await _sysUserFavoritesRep.InsertRangeAsync(menus);
+    }
+
+    /// <summary>
+    /// 取消收藏菜单
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    public async Task DeleteUserFavorites(UserFavoritesInput input)
+    {
+        await _sysUserFavoritesRep.DeleteAsync(u => u.UserId == input.UserId && input.MenuIdList.Contains(u.MenuId));
+    }
+
+    /// <summary>
+    /// 根据用户Id删除收藏菜单
+    /// </summary>
+    /// <param name="userId"></param>
+    /// <returns></returns>
+    public async Task DeleteUserRoleByUserId(long userId)
+    {
+        await _sysUserFavoritesRep.DeleteAsync(u => u.UserId == userId);
+    }
+
+    /// <summary>
+    /// 根据用户Id获取收藏菜单集合
+    /// </summary>
+    /// <param name="userId"></param>
+    /// <returns></returns>
+    public async Task<List<MenuOutput>> GetUserRoleList(long userId)
+    {
+        var sysUserRoleList = await _sysUserFavoritesRep.AsQueryable()
+            .Includes(u => u.SysMenu)
+            .Where(u => u.UserId == userId).ToListAsync();
+        return sysUserRoleList.Where(u => u.SysMenu != null).Select(u => u.SysMenu).ToList().Adapt<List<MenuOutput>>();
+    }
+
+    /// <summary>
+    /// 根据用户Id获取收藏菜单Id集合
+    /// </summary>
+    /// <param name="userId"></param>
+    /// <returns></returns>
+    public async Task<List<long>> GetUserRoleIdList(long userId)
+    {
+        return await _sysUserFavoritesRep.AsQueryable()
+            .Where(u => u.UserId == userId).Select(u => u.MenuId).ToListAsync();
+    }
+}

+ 1 - 1
Web/src/stores/userInfo.ts

@@ -162,7 +162,7 @@ export const useUserInfo = defineStore('userInfo', {
 			for (let index = 0; index < ds.length; index++) {
 				const element = ds[index];
 				if (element.value == label) {
-					return element.code;
+					return element;
 				}
 			}
 		},

+ 87 - 62
Web/src/views/home/widgets/components/myapp.vue

@@ -1,50 +1,50 @@
 <template>
-	<el-card shadow="hover" header="快捷入口">
-		<ul class="myMods">
-			<li v-for="mod in myMods" :key="mod.path!">
-				<router-link :to="{ path: mod.path! }">
-					<SvgIcon :name="mod.meta?.icon" style="font-size: 18px" />
-					<p>{{ mod.meta?.title }}</p>
-				</router-link>
-			</li>
-			<li class="modItem-add" @click="addMods">
-				<a>
-					<el-icon><ele-Plus :style="{ color: '#fff' }" /></el-icon>
-				</a>
-			</li>
-		</ul>
-
-		<el-drawer title="添加应用" v-model="modsDrawer" :size="520" destroy-on-close :before-close="beforeClose">
-			<div class="setMods mt15">
-				<h4>我的常用 ( {{ myMods.length }} )</h4>
-				<draggable tag="ul" v-model="myMods" animation="200" item-key="id" group="app" class="draggable-box" force-fallback fallback-on-body>
-					<template #item="{ element }">
-						<li>
-							<SvgIcon :name="element.meta.icon" style="font-size: 18px" />
-							<p>{{ element.meta.title }}</p>
-						</li>
-					</template>
-				</draggable>
-			</div>
-			<div class="setMods">
-				<h4>全部应用 ( {{ filterMods.length }} )</h4>
-				<draggable tag="ul" v-model="filterMods" animation="200" item-key="id" group="app" class="draggable-box-all" force-fallback fallback-on-body>
-					<template #item="{ element }">
-						<li :style="{ background: element.meta.color || '#909399' }">
-							<SvgIcon :name="element.meta.icon" style="font-size: 18px" />
-							<p>{{ element.meta.title }}</p>
-						</li>
-					</template>
-				</draggable>
-			</div>
-			<template #footer>
-				<div style="margin: 0 20px 20px 0">
-					<el-button @click="beforeClose">取消</el-button>
-					<el-button type="primary" @click="saveMods">保存</el-button>
-				</div>
-			</template>
-		</el-drawer>
-	</el-card>
+  <el-card shadow="hover" header="快捷入口">
+    <ul class="myMods">
+      <li v-for="mod in myMods" :key="mod.path!">
+        <router-link :to="{ path: mod.path! }">
+          <SvgIcon :name="mod.meta?.icon" style="font-size: 18px" />
+          <p>{{ mod.meta?.title }}</p>
+        </router-link>
+      </li>
+      <li class="modItem-add" @click="addMods">
+        <a>
+          <el-icon><ele-Plus :style="{ color: '#fff' }" /></el-icon>
+        </a>
+      </li>
+    </ul>
+
+    <el-drawer title="添加应用" v-model="modsDrawer" :size="520" destroy-on-close :before-close="beforeClose">
+      <div class="setMods mt15">
+        <h4>我的常用 ( {{ myMods.length }} )</h4>
+        <draggable tag="ul" v-model="myMods" animation="200" item-key="id" group="app" class="draggable-box" force-fallback fallback-on-body>
+          <template #item="{ element }">
+            <li>
+              <SvgIcon :name="element.meta.icon" style="font-size: 18px" />
+              <p>{{ element.meta.title }}</p>
+            </li>
+          </template>
+        </draggable>
+      </div>
+      <div class="setMods">
+        <h4>全部应用 ( {{ filterMods.length }} )</h4>
+        <draggable tag="ul" v-model="filterMods" animation="200" item-key="id" group="app" class="draggable-box-all" force-fallback fallback-on-body>
+          <template #item="{ element }">
+            <li :style="{ background: element.meta.color || '#909399' }">
+              <SvgIcon :name="element.meta.icon" style="font-size: 18px" />
+              <p>{{ element.meta.title }}</p>
+            </li>
+          </template>
+        </draggable>
+      </div>
+      <template #footer>
+        <div style="margin: 0 20px 20px 0">
+          <el-button @click="beforeClose">取消</el-button>
+          <el-button type="primary" @click="saveMods">保存</el-button>
+        </div>
+      </template>
+    </el-drawer>
+  </el-card>
 </template>
 
 <script lang="ts">
@@ -57,34 +57,59 @@ export default {
 
 <script setup lang="ts" name="myapp">
 import draggable from 'vuedraggable';
-import { onMounted, ref } from 'vue';
+import { reactive, onMounted, ref } from 'vue';
 import { Local } from '/@/utils/storage';
 import { useRequestOldRoutes } from '/@/stores/requestOldRoutes';
 import { MenuOutput } from '/@/api-services/models';
 import { ElMessage } from 'element-plus';
+import { storeToRefs } from 'pinia';
+import { useUserInfo } from '/@/stores/userInfo';
+import { getAPI } from '/@/utils/axios-utils';
+import { SysUserFavoritesApi } from '/@/api-services/api';
+import { useRouter } from 'vue-router';
 
 const mods = ref<MenuOutput[]>([]); // 所有应用
 const myMods = ref<MenuOutput[]>([]); // 我的常用
 const myModsName = ref<Array<string | null | undefined>>([]); // 我的常用
 const filterMods = ref<MenuOutput[]>([]); // 过滤我的常用后的应用
 const modsDrawer = ref<boolean>(false);
+const myFavoriteMods = ref<Array<number | null | undefined>>([]); // 我的常用
+
+const navScrollbar = ref();
+
+const router = useRouter();
+const { userInfos } = storeToRefs(useUserInfo());
+
+const state = reactive({
+	navError: '',
+	navData: [],
+});
 
 onMounted(() => {
 	getMods();
 });
 
+// 请求已收藏菜单列表
+const getFavoriteMenuList = async () => {
+	try {
+		const res = await getAPI(SysUserFavoritesApi).apiSysUserFavoritesUserRoleListUserIdGet(userInfos.value.id);
+		return res.data.result || [];
+	} catch (error) {
+		return [];
+	}
+};
+
 const addMods = () => {
 	modsDrawer.value = true;
 };
 
-const getMods = () => {
-	myModsName.value = Local.get('my-mods') || [];
+const getMods = async () => {
 	var menuTree = (useRequestOldRoutes().requestOldRoutes as MenuOutput[]) || [];
 	filterMenu(menuTree);
-	myMods.value = mods.value.filter((item: MenuOutput) => {
-		return myModsName.value.includes(item.name);
-	});
 
+	myMods.value = await getFavoriteMenuList();
+
+	myModsName.value = myMods.value.map((v: MenuOutput) => v.name);
 	filterMods.value = mods.value.filter((item: MenuOutput) => {
 		return !myModsName.value.includes(item.name);
 	});
@@ -108,19 +133,19 @@ const filterMenu = (map: MenuOutput[]) => {
 };
 
 // 保存我的常用
-const saveMods = () => {
-	const myModsName = myMods.value.map((v: MenuOutput) => v.name);
-	Local.set('my-mods', myModsName);
+const saveMods = async () => {
+	const myFavoriteMods = myMods.value.map((v: MenuOutput) => v.id);
+
+	const param = { userId: userInfos.value.id, menuIdList: myFavoriteMods };
+	await getAPI(SysUserFavoritesApi).apiSysUserFavoritesGrantUserFavoritesPost(param);
 	ElMessage.success('设置常用成功');
 	modsDrawer.value = false;
 };
 
 // 取消
-const beforeClose = () => {
-	myModsName.value = Local.get('my-mods') || [];
-	myMods.value = mods.value.filter((item: MenuOutput) => {
-		return myModsName.value.includes(item.name);
-	});
+const beforeClose = async () => {
+	myMods.value = await getFavoriteMenuList();
+	myModsName.value = myMods.value.map((v: MenuOutput) => v.name);
 	filterMods.value = mods.value.filter((item: MenuOutput) => {
 		return !myModsName.value.includes(item.name);
 	});
@@ -194,7 +219,7 @@ const beforeClose = () => {
 	border: 1px dashed var(--el-color-primary);
 	padding: 15px;
 	height: calc(100vh - 330px);
-	overflow-y:scroll;
+	overflow-y: scroll;
 }
 
 .draggable-box-all::-webkit-scrollbar {
@@ -237,6 +262,6 @@ const beforeClose = () => {
 	opacity: 0.3;
 }
 a {
-  text-decoration: none;
+	text-decoration: none;
 }
 </style>