Forráskód Böngészése

优化通知公告SignalR实时更新

zuohuaijun 3 éve
szülő
commit
f9bf4ddb29

+ 4 - 4
Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj

@@ -25,9 +25,9 @@
   <ItemGroup>
     <PackageReference Include="AngleSharp" Version="0.17.1" />
     <PackageReference Include="AspNetCoreRateLimit" Version="4.0.2" />
-    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.7.2" />
-    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.7.2" />
-    <PackageReference Include="Furion.Pure" Version="4.7.2" />
+    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.7.3" />
+    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.7.3" />
+    <PackageReference Include="Furion.Pure" Version="4.7.3" />
     <PackageReference Include="Lazy.Captcha.Core" Version="1.1.6" />
     <PackageReference Include="Magicodes.IE.Excel" Version="2.6.9" />
     <PackageReference Include="Magicodes.IE.Pdf" Version="2.6.9" />
@@ -42,7 +42,7 @@
     <PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.21" />
     <PackageReference Include="UAParser" Version="3.1.47" />
     <PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
-    <PackageReference Include="Masuit.Tools.Core" Version="2.5.6.3" />
+    <PackageReference Include="Masuit.Tools.Core" Version="2.5.7.1" />
   </ItemGroup>
 
   <ItemGroup>

+ 11 - 11
Admin.NET/Admin.NET.Core/Admin.NET.Core.xml

@@ -3302,32 +3302,32 @@
             结果筛选器
             </summary>
         </member>
-        <member name="M:Admin.NET.Core.IOnlineUserHub.ForceOffline(System.Object)">
+        <member name="M:Admin.NET.Core.IOnlineUserHub.OnlineUserList(Admin.NET.Core.OnlineUserList)">
             <summary>
-            强制下线
+            在线用户列表
             </summary>
             <param name="context"></param>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.IOnlineUserHub.ReceiveMessage(System.Object)">
+        <member name="M:Admin.NET.Core.IOnlineUserHub.ForceOffline(System.Object)">
             <summary>
-            接收消息
+            强制下线
             </summary>
             <param name="context"></param>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.IOnlineUserHub.OnlineUserChange(Admin.NET.Core.OnlineUserHubOutput)">
+        <member name="M:Admin.NET.Core.IOnlineUserHub.PublicNotice(Admin.NET.Core.SysNotice)">
             <summary>
-            在线用户变动
+            发布站内消息
             </summary>
             <param name="context"></param>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.IOnlineUserHub.AppendNotice(Admin.NET.Core.SysNotice)">
+        <member name="M:Admin.NET.Core.IOnlineUserHub.ReceiveMessage(System.Object)">
             <summary>
-            组合消息
+            接收消息
             </summary>
-            <param name="notice"></param>
+            <param name="context"></param>
             <returns></returns>
         </member>
         <member name="T:Admin.NET.Core.OnlineUserHub">
@@ -5557,9 +5557,9 @@
             <param name="user"></param>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.Service.SysOnlineUserService.AppendNotice(Admin.NET.Core.SysNotice,System.Collections.Generic.List{System.Int64})">
+        <member name="M:Admin.NET.Core.Service.SysOnlineUserService.PublicNotice(Admin.NET.Core.SysNotice,System.Collections.Generic.List{System.Int64})">
             <summary>
-            发消息
+            发布站内消息
             </summary>
             <param name="notice"></param>
             <param name="userIds"></param>

+ 1 - 1
Admin.NET/Admin.NET.Core/Hub/Dto/OnlineUserHubOutput.cs

@@ -1,6 +1,6 @@
 namespace Admin.NET.Core;
 
-public class OnlineUserHubOutput
+public class OnlineUserList
 {
     public string RealName { get; set; }
 

+ 9 - 9
Admin.NET/Admin.NET.Core/Hub/IOnlineUserHub.cs

@@ -3,30 +3,30 @@
 public interface IOnlineUserHub
 {
     /// <summary>
-    /// 强制下线
+    /// 在线用户列表
     /// </summary>
     /// <param name="context"></param>
     /// <returns></returns>
-    Task ForceOffline(object context);
+    Task OnlineUserList(OnlineUserList context);
 
     /// <summary>
-    /// 接收消息
+    /// 强制下线
     /// </summary>
     /// <param name="context"></param>
     /// <returns></returns>
-    Task ReceiveMessage(object context);
+    Task ForceOffline(object context);
 
     /// <summary>
-    /// 在线用户变动
+    /// 发布站内消息
     /// </summary>
     /// <param name="context"></param>
     /// <returns></returns>
-    Task OnlineUserChange(OnlineUserHubOutput context);
+    Task PublicNotice(SysNotice context);
 
     /// <summary>
-    /// 组合消息
+    /// 接收消息
     /// </summary>
-    /// <param name="notice"></param>
+    /// <param name="context"></param>
     /// <returns></returns>
-    Task AppendNotice(SysNotice notice);
+    Task ReceiveMessage(object context);
 }

+ 6 - 8
Admin.NET/Admin.NET.Core/Hub/OnlineUserHub.cs

@@ -9,20 +9,17 @@ namespace Admin.NET.Core;
 [MapHub("/hubs/onlineUser")]
 public class OnlineUserHub : Hub<IOnlineUserHub>
 {
+    private const string GROUP_ONLINE = "GROUP_ONLINE_"; // 租户分组前缀
+
     private readonly SqlSugarRepository<SysOnlineUser> _sysOnlineUerRep;
-    private readonly SysCacheService _sysCache;
     private readonly SysMessageService _sysMessageService;
     private readonly IHubContext<OnlineUserHub, IOnlineUserHub> _onlineUserHubContext;
 
-    private const string GROUP_ONLINE = "GROUP_ONLINE_"; // 租户分组前缀
-
     public OnlineUserHub(SqlSugarRepository<SysOnlineUser> sysOnlineUerRep,
-        SysCacheService sysCache,
         SysMessageService sysMessageService,
         IHubContext<OnlineUserHub, IOnlineUserHub> onlineUserHubContext)
     {
         _sysOnlineUerRep = sysOnlineUerRep;
-        _sysCache = sysCache;
         _sysMessageService = sysMessageService;
         _onlineUserHubContext = onlineUserHubContext;
     }
@@ -51,11 +48,12 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
         await _sysOnlineUerRep.InsertAsync(user);
 
         // 以租户Id分组方便区分
-        await _onlineUserHubContext.Groups.AddToGroupAsync(Context.ConnectionId, $"{GROUP_ONLINE}{user.TenantId}");
+        var groupName = $"{GROUP_ONLINE}{user.TenantId}";
+        await _onlineUserHubContext.Groups.AddToGroupAsync(Context.ConnectionId, groupName);
 
         var userList = await _sysOnlineUerRep.AsQueryable().Filter("", true)
             .Where(u => u.TenantId == user.TenantId).Take(10).ToListAsync();
-        await _onlineUserHubContext.Clients.Groups($"{GROUP_ONLINE}{user.TenantId}").OnlineUserChange(new OnlineUserHubOutput
+        await _onlineUserHubContext.Clients.Groups(groupName).OnlineUserList(new OnlineUserList
         {
             RealName = user.RealName,
             Online = true,
@@ -80,7 +78,7 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
         // 通知当前组用户变动
         var userList = await _sysOnlineUerRep.AsQueryable().Filter("", true)
             .Where(u => u.TenantId == user.TenantId).Take(10).ToListAsync();
-        await _onlineUserHubContext.Clients.Groups($"{GROUP_ONLINE}{user.TenantId}").OnlineUserChange(new OnlineUserHubOutput
+        await _onlineUserHubContext.Clients.Groups($"{GROUP_ONLINE}{user.TenantId}").OnlineUserList(new OnlineUserList
         {
             RealName = user.RealName,
             Online = false,

+ 1 - 1
Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs

@@ -15,7 +15,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
         {
             new SysMenu{ Id=252885263002100, Pid=0, Title="数据面板", Path="/dashboard", Name="dashboard", Component="Layout", Redirect="/dashboard/home", Icon="ele-HomeFilled", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Order=10 },
             new SysMenu{ Id=252885263002110, Pid=252885263002100, Title="工作台", Path="/dashboard/home", Name="home", Component="/home/index", IsAffix=true, Icon="ele-HomeFilled", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Order=100 },
-            new SysMenu{ Id=252885263002111, Pid=252885263002100, Title="站内信", Path="/dashboard/notice", Name="notice", Component="/home/notice/index", IsAffix=true, Icon="ele-Bell", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Order=101 },
+            new SysMenu{ Id=252885263002111, Pid=252885263002100, Title="站内信", Path="/dashboard/notice", Name="notice", Component="/home/notice/index", Icon="ele-Bell", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Order=101 },
 
             new SysMenu{ Id=252885263002200, Pid=0, Title="系统管理", Path="/system", Name="system", Component="Layout", Redirect="/system/user", Icon="ele-Setting", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Order=100 },
 

+ 3 - 3
Admin.NET/Admin.NET.Core/Service/Notice/SysNoticeService.cs

@@ -107,7 +107,7 @@ public class SysNoticeService : IDynamicApiController, ITransient
         await _sysNoticeUserRep.InsertRangeAsync(noticeUserList);
 
         // 广播所有在线账号
-        await _sysOnlineUserService.AppendNotice(notice, userIdList);
+        await _sysOnlineUserService.PublicNotice(notice, userIdList);
     }
 
     /// <summary>
@@ -146,12 +146,12 @@ public class SysNoticeService : IDynamicApiController, ITransient
     /// </summary>
     /// <returns></returns>
     [HttpGet("/sysNotice/unReadList")]
-    public async Task<List<SysNoticeUser>> GetUnReadNoticeList()
+    public async Task<List<SysNotice>> GetUnReadNoticeList()
     {
         return await _sysNoticeRep.AsSugarClient().Queryable<SysNoticeUser>().Includes(u => u.SysNotice)
             .Where(u => u.UserId == _userManager.UserId && u.ReadStatus == NoticeUserStatusEnum.UNREAD)
             .OrderBy(u => u.SysNotice.CreateTime, OrderByType.Desc)
-            .OrderBy(u => u.SysNotice.CreateTime, OrderByType.Desc).ToListAsync();
+            .ToListAsync(u => u.SysNotice);
     }
 
     /// <summary>

+ 3 - 3
Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs

@@ -48,20 +48,20 @@ public class SysOnlineUserService : IDynamicApiController, ITransient
     }
 
     /// <summary>
-    /// 发消息
+    /// 发布站内消息
     /// </summary>
     /// <param name="notice"></param>
     /// <param name="userIds"></param>
     /// <returns></returns>
     [NonAction]
-    public async Task AppendNotice(SysNotice notice, List<long> userIds)
+    public async Task PublicNotice(SysNotice notice, List<long> userIds)
     {
         var userList = await _sysOnlineUerRep.GetListAsync(m => userIds.Contains(m.UserId));
         if (!userList.Any()) return;
 
         foreach (var item in userList)
         {
-            await _onlineUserHubContext.Clients.Client(item.ConnectionId).AppendNotice(notice);
+            await _onlineUserHubContext.Clients.Client(item.ConnectionId).PublicNotice(notice);
         }
     }
 

+ 1 - 1
Admin.NET/Admin.NET.Web.Core/Admin.NET.Web.Core.csproj

@@ -11,7 +11,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="AspNet.Security.OAuth.Weixin" Version="6.0.13" />
+    <PackageReference Include="AspNet.Security.OAuth.Weixin" Version="6.0.14" />
     <PackageReference Include="IGeekFan.AspNetCore.Knife4jUI" Version="0.0.12" />
   </ItemGroup>
 

+ 4 - 4
Web/src/api-services/apis/sys-notice-api.ts

@@ -17,7 +17,7 @@ import { Configuration } from '../configuration';
 // @ts-ignore
 import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from '../base';
 import { AddNoticeInput } from '../models';
-import { AdminResultListSysNoticeUser } from '../models';
+import { AdminResultListSysNotice } from '../models';
 import { AdminResultSqlSugarPagedListSysNotice } from '../models';
 import { AdminResultSqlSugarPagedListSysNoticeUser } from '../models';
 import { DeleteNoticeInput } from '../models';
@@ -524,7 +524,7 @@ export const SysNoticeApiFp = function(configuration?: Configuration) {
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async sysNoticeUnReadListGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultListSysNoticeUser>>> {
+        async sysNoticeUnReadListGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultListSysNotice>>> {
             const localVarAxiosArgs = await SysNoticeApiAxiosParamCreator(configuration).sysNoticeUnReadListGet(options);
             return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
                 const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
@@ -632,7 +632,7 @@ export const SysNoticeApiFactory = function (configuration?: Configuration, base
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async sysNoticeUnReadListGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultListSysNoticeUser>> {
+        async sysNoticeUnReadListGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultListSysNotice>> {
             return SysNoticeApiFp(configuration).sysNoticeUnReadListGet(options).then((request) => request(axios, basePath));
         },
         /**
@@ -740,7 +740,7 @@ export class SysNoticeApi extends BaseAPI {
      * @throws {RequiredError}
      * @memberof SysNoticeApi
      */
-    public async sysNoticeUnReadListGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultListSysNoticeUser>> {
+    public async sysNoticeUnReadListGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultListSysNotice>> {
         return SysNoticeApiFp(this.configuration).sysNoticeUnReadListGet(options).then((request) => request(this.axios, this.basePath));
     }
     /**

+ 11 - 11
Web/src/api-services/models/admin-result-list-sys-notice-user.ts → Web/src/api-services/models/admin-result-list-sys-notice.ts

@@ -11,47 +11,47 @@
  * https://github.com/swagger-api/swagger-codegen.git
  * Do not edit the class manually.
  */
-import { SysNoticeUser } from './sys-notice-user';
+import { SysNotice } from './sys-notice';
 /**
  * 全局返回结果
  * @export
- * @interface AdminResultListSysNoticeUser
+ * @interface AdminResultListSysNotice
  */
-export interface AdminResultListSysNoticeUser {
+export interface AdminResultListSysNotice {
     /**
      * 状态码
      * @type {number}
-     * @memberof AdminResultListSysNoticeUser
+     * @memberof AdminResultListSysNotice
      */
     code?: number;
     /**
      * 类型success、warning、error
      * @type {string}
-     * @memberof AdminResultListSysNoticeUser
+     * @memberof AdminResultListSysNotice
      */
     type?: string | null;
     /**
      * 错误信息
      * @type {string}
-     * @memberof AdminResultListSysNoticeUser
+     * @memberof AdminResultListSysNotice
      */
     message?: string | null;
     /**
      * 数据
-     * @type {Array<SysNoticeUser>}
-     * @memberof AdminResultListSysNoticeUser
+     * @type {Array<SysNotice>}
+     * @memberof AdminResultListSysNotice
      */
-    result?: Array<SysNoticeUser> | null;
+    result?: Array<SysNotice> | null;
     /**
      * 附加数据
      * @type {any}
-     * @memberof AdminResultListSysNoticeUser
+     * @memberof AdminResultListSysNotice
      */
     extras?: any | null;
     /**
      * 时间
      * @type {Date}
-     * @memberof AdminResultListSysNoticeUser
+     * @memberof AdminResultListSysNotice
      */
     time?: Date;
 }

+ 1 - 1
Web/src/api-services/models/index.ts

@@ -32,7 +32,7 @@ export * from './admin-result-list-sys-config';
 export * from './admin-result-list-sys-dict-data';
 export * from './admin-result-list-sys-dict-type';
 export * from './admin-result-list-sys-menu';
-export * from './admin-result-list-sys-notice-user';
+export * from './admin-result-list-sys-notice';
 export * from './admin-result-list-sys-org';
 export * from './admin-result-list-sys-pos';
 export * from './admin-result-list-sys-region';

+ 14 - 6
Web/src/layout/navBars/breadcrumb/user.vue

@@ -33,7 +33,7 @@
 			<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
 		</div>
 		<div class="layout-navbars-breadcrumb-user-icon">
-			<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
+			<el-popover placement="bottom" trigger="hover" transition="el-zoom-in-top" :width="300" :persistent="false">
 				<template #reference>
 					<el-badge :is-dot="true">
 						<el-icon :title="$t('message.user.title4')">
@@ -41,9 +41,7 @@
 						</el-icon>
 					</el-badge>
 				</template>
-				<template #default>
-					<UserNews />
-				</template>
+				<UserNews :noticeList="noticeList" />
 			</el-popover>
 		</div>
 		<div class="layout-navbars-breadcrumb-user-icon" @click="onScreenfullClick">
@@ -91,7 +89,7 @@ import Search from '/@/layout/navBars/breadcrumb/search.vue';
 
 import OnlineUser from '/@/views/system/onlineUser/index.vue';
 import { getAPI } from '/@/utils/axios-utils';
-import { SysAuthApi } from '/@/api-services/api';
+import { SysAuthApi, SysNoticeApi } from '/@/api-services/api';
 
 export default defineComponent({
 	name: 'layoutBreadcrumbUser',
@@ -110,6 +108,7 @@ export default defineComponent({
 			isScreenfull: false,
 			disabledI18n: 'zh-cn',
 			disabledSize: 'large',
+			noticeList: [] as any, // 站内信列表
 		});
 		// 设置分割样式
 		const layoutUserFlexNum = computed(() => {
@@ -236,12 +235,21 @@ export default defineComponent({
 			}
 		};
 		// 页面加载时
-		onMounted(() => {
+		onMounted(async () => {
 			if (Local.get('themeConfig')) {
 				initI18n();
 				initComponentSize();
 			}
+			// 加载未读的站内信
+			var res = await getAPI(SysNoticeApi).sysNoticeUnReadListGet();
+			state.noticeList = res.data.result ?? [];
+
+			// 接收站内信
+			proxy.signalR.on('PublicNotice', reciveNotice);
 		});
+		const reciveNotice = (msg: any) => {
+			state.noticeList.push(msg);
+		};
 		return {
 			userInfos,
 			onLayoutSetingClick,

+ 85 - 85
Web/src/layout/navBars/breadcrumb/userNews.vue

@@ -1,115 +1,115 @@
 <template>
-	<div class="layout-navbars-breadcrumb-user-news">
-		<div class="head-box">
-			<div class="head-box-title">{{ $t('message.user.newTitle') }}</div>
-			<div class="head-box-btn" v-if="newsList.length > 0" @click="onAllReadClick">{{ $t('message.user.newBtn') }}</div>
-		</div>
-		<div class="content-box">
-			<template v-if="newsList.length > 0">
-				<div class="content-box-item" v-for="(v, k) in newsList" :key="k">
-					<div>{{ v.label }}</div>
-					<div class="content-box-msg">
-						{{ v.value }}
-					</div>
-					<div class="content-box-time">{{ v.time }}</div>
+	<div class="user-news-container">
+		<el-tabs stretch class="content-box">
+			<el-tab-pane label="站内信">
+				<template #label>
+					<el-icon><ele-Bell /></el-icon>
+					<span style="margin-left: 5px">站内信</span>
+				</template>
+				<div class="notice-box">
+					<template v-if="noticeList.length > 0">
+						<div class="notice-item" v-for="(v, k) in noticeList" :key="k" @click="viewNoticeDetail(v)">
+							<div class="notice-title">{{ v.type == 1 ? '【通知】' : '【公告】' }}{{ v.title }}</div>
+							<div class="notice-msg">{{ v.content }}</div>
+							<div class="notice-time">{{ v.publicTime }}</div>
+							<el-divider border-style="dashed" style="margin: 10px 0" />
+						</div>
+					</template>
+					<el-empty description="空" v-else></el-empty>
 				</div>
+				<div class="notice-foot" @click="goToNotice" v-if="noticeList.length > 0">前往通知中心</div>
+			</el-tab-pane>
+			<el-tab-pane label="我的">
+				<template #label>
+					<el-icon><ele-Position /></el-icon>
+					<span style="margin-left: 5px">我的</span>
+				</template>
+				<div style="height: 400px; overflow-y: auto; padding-right: 10px">
+					<el-empty description="空"></el-empty>
+				</div>
+			</el-tab-pane>
+		</el-tabs>
+		<el-dialog v-model="state.dialogVisible" title="消息详情" draggable width="769px">
+			<p v-html="state.content"></p>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button type="primary" @click="state.dialogVisible = false">确认</el-button>
+				</span>
 			</template>
-			<el-empty :description="$t('message.user.newDesc')" v-else></el-empty>
-		</div>
-		<div class="foot-box" @click="onGoToGiteeClick" v-if="newsList.length > 0">{{ $t('message.user.newGo') }}</div>
+		</el-dialog>
 	</div>
 </template>
 
-<script lang="ts">
-import { reactive, toRefs, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { reactive } from 'vue';
+import { SysNoticeApi } from '/@/api-services/api';
+import router from '/@/router';
+import { getAPI } from '/@/utils/axios-utils';
 
-export default defineComponent({
-	name: 'layoutBreadcrumbUserNews',
-	setup() {
-		const state = reactive({
-			newsList: [
-				{
-					label: '关于版本发布的通知',
-					value: 'vue-next-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus,正式发布时间:2021年02月28日!',
-					time: '2020-12-08',
-				},
-				{
-					label: '关于学习交流的通知',
-					value: 'QQ群号码 665452019,欢迎小伙伴入群学习交流探讨!',
-					time: '2020-12-08',
-				},
-			],
-		});
-		// 全部已读点击
-		const onAllReadClick = () => {
-			state.newsList = [];
-		};
-		// 前往通知中心点击
-		const onGoToGiteeClick = () => {
-			window.open('https://gitee.com/lyt-top/vue-next-admin');
-		};
-		return {
-			onAllReadClick,
-			onGoToGiteeClick,
-			...toRefs(state),
-		};
-	},
+defineProps({
+	noticeList: Array as any,
+});
+const state = reactive({
+	dialogVisible: false,
+	content: '',
 });
+// 前往通知中心点击
+const goToNotice = () => {
+	router.push('/dashboard/notice');
+};
+// 查看消息详情
+const viewNoticeDetail = async (notice: any) => {
+	state.content = notice.content;
+	state.dialogVisible = true;
+
+	// 设置已读
+	await getAPI(SysNoticeApi).sysNoticeSetReadPost({ id: notice.id });
+};
 </script>
 
 <style scoped lang="scss">
-.layout-navbars-breadcrumb-user-news {
-	.head-box {
-		display: flex;
-		border-bottom: 1px solid var(--el-border-color-lighter);
-		box-sizing: border-box;
-		color: var(--el-text-color-primary);
-		justify-content: space-between;
-		height: 35px;
-		align-items: center;
-		.head-box-btn {
-			color: var(--el-color-primary);
-			font-size: 13px;
-			cursor: pointer;
-			opacity: 0.8;
+.user-news-container {
+	.content-box {
+		font-size: 12px;
+		.notice-box {
+			height: 400px;
+			padding-right: 10px;
+
+			margin-bottom: 35px;
 			&:hover {
-				opacity: 1;
+				overflow-y: scroll;
 			}
 		}
-	}
-	.content-box {
-		font-size: 13px;
-		.content-box-item {
-			padding-top: 12px;
-			&:last-of-type {
-				padding-bottom: 12px;
+		.notice-item {
+			&:hover {
+				background-color: rgba(#b8b8b8, 0.1);
 			}
-			.content-box-msg {
+			// .notice-title {
+			// 	color: var(--el-color-primary);
+			// }
+			.notice-msg {
 				color: var(--el-text-color-secondary);
-				margin-top: 5px;
-				margin-bottom: 5px;
+				margin-top: 3px;
+				margin-bottom: 3px;
 			}
-			.content-box-time {
+			.notice-time {
 				color: var(--el-text-color-secondary);
+				text-align: right;
 			}
 		}
 	}
-	.foot-box {
+	.notice-foot {
 		height: 35px;
+		width: 100%;
 		color: var(--el-color-primary);
-		font-size: 13px;
+		font-size: 14px;
 		cursor: pointer;
-		opacity: 0.8;
+		position: absolute;
+		bottom: 0px;
+		background-color: #fff;
 		display: flex;
 		align-items: center;
 		justify-content: center;
-		border-top: 1px solid var(--el-border-color-lighter);
-		&:hover {
-			opacity: 1;
-		}
-	}
-	:deep(.el-empty__description p) {
-		font-size: 13px;
 	}
 }
 </style>

+ 2 - 0
Web/src/main.ts

@@ -5,6 +5,7 @@ import router from './router';
 import { directive } from '/@/utils/directive';
 import { i18n } from '/@/i18n/index';
 import other from '/@/utils/other';
+import { signalR } from '/@/views/system/onlineUser/signalR';
 
 import ElementPlus from 'element-plus';
 import 'element-plus/dist/index.css';
@@ -27,6 +28,7 @@ app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(VForm3)
 const globalProperties = {
 	mittBus: mitt(),
 	i18n,
+	signalR,
 };
 
 // 必须合并vue默认的变量,否则有问题

+ 6 - 5
Web/src/views/home/notice/index.vue

@@ -26,13 +26,13 @@
 				<el-table-column prop="sysNotice.type" label="类型" width="100" align="center" show-overflow-tooltip>
 					<template #default="scope">
 						<el-tag v-if="scope.row.sysNotice.type === 1"> 通知 </el-tag>
-						<el-tag type="danger" v-else> 公告 </el-tag>
+						<el-tag type="warning" v-else> 公告 </el-tag>
 					</template>
 				</el-table-column>
 				<el-table-column prop="sysNotice.createTime" label="创建时间" align="center" show-overflow-tooltip />
 				<el-table-column prop="readStatus" label="阅读状态" width="100" align="center" show-overflow-tooltip>
 					<template #default="scope">
-						<el-tag type="success" v-if="scope.row.readStatus === 1"> 已读 </el-tag>
+						<el-tag type="info" v-if="scope.row.readStatus === 1"> 已读 </el-tag>
 						<el-tag type="danger" v-else> 未读 </el-tag>
 					</template>
 				</el-table-column>
@@ -134,6 +134,7 @@ export default defineComponent({
 			state.content = row.sysNotice.content;
 			state.dialogVisible = true;
 
+			row.readStatus = 1;
 			await getAPI(SysNoticeApi).sysNoticeSetReadPost({ id: row.sysNotice.id });
 		};
 		// eslint-disable-next-line no-unused-vars
@@ -154,7 +155,7 @@ export default defineComponent({
 </script>
 
 <style lang="scss">
-.el-table .info-row {
-	--el-table-tr-bg-color: var(--el-color-info-light-9);
-}
+// .el-table .info-row {
+// 	--el-table-tr-bg-color: var(--el-color-info-light-9);
+// }
 </style>

+ 1 - 1
Web/src/views/system/notice/component/editNotice.vue

@@ -10,7 +10,7 @@
 					</el-col>
 					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
 						<el-form-item label="类型" prop="type" :rules="[{ required: true, message: '类型不能为空', trigger: 'blur' }]">
-							<el-select v-model="ruleForm.type" placeholder="类型" clearable filterable allow-create default-first-option style="width: 100%">
+							<el-select v-model="ruleForm.type" placeholder="类型" filterable allow-create default-first-option style="width: 100%">
 								<el-option label="通知" :value="1" />
 								<el-option label="公告" :value="2" />
 							</el-select>

+ 1 - 1
Web/src/views/system/notice/index.vue

@@ -27,7 +27,7 @@
 				<el-table-column prop="type" label="类型" width="100" align="center" show-overflow-tooltip>
 					<template #default="scope">
 						<el-tag v-if="scope.row.type === 1"> 通知 </el-tag>
-						<el-tag type="danger" v-else> 公告 </el-tag>
+						<el-tag type="warning" v-else> 公告 </el-tag>
 					</template>
 				</el-table-column>
 				<el-table-column prop="createTime" label="创建时间" align="center" show-overflow-tooltip />

+ 23 - 61
Web/src/views/system/onlineUser/index.vue

@@ -48,14 +48,14 @@
 </template>
 
 <script setup lang="ts">
-import { reactive } from 'vue';
+import { getCurrentInstance, onMounted, reactive } from 'vue';
 import { ElMessageBox, ElNotification } from 'element-plus';
-import * as SignalR from '@microsoft/signalr';
 
-import { getAPI, getToken, clearAccessTokens } from '/@/utils/axios-utils';
+import { getAPI, clearAccessTokens } from '/@/utils/axios-utils';
 import { SysOnlineUserApi, SysAuthApi } from '/@/api-services/api';
 import { SysOnlineUser } from '/@/api-services/models';
 
+const { proxy } = getCurrentInstance() as any;
 const state = reactive({
 	loading: false,
 	isVisible: false,
@@ -71,68 +71,30 @@ const state = reactive({
 	onlineUserList: [] as Array<SysOnlineUser>, // 在线用户列表
 });
 
-// 初始化SignalR对象
-const connection = new SignalR.HubConnectionBuilder()
-	//.configureLogging(SignalR.LogLevel.Information)
-	.withUrl(`${import.meta.env.VITE_API_URL}/hubs/onlineUser?access_token=${getToken()}`)
-	.withAutomaticReconnect({
-		nextRetryDelayInMilliseconds: (a) => {
-			console.log(a);
-			return 5000; // 每5秒重连一次
-		},
-	})
-	.build();
-
-connection.keepAliveIntervalInMilliseconds = 15 * 1000; // 心跳检测15s
-connection.serverTimeoutInMilliseconds = 30 * 60 * 1000; // 超时时间30m
+onMounted(async () => {
+	handleQuery();
 
-// 启动连接
-connection.start().then(() => {
-	console.log('启动连接');
-});
-// 断开连接
-connection.onclose(async () => {
-	console.log('断开连接');
-});
-// 重连中
-connection.onreconnecting(() => {
-	ElNotification({
-		title: '提示',
-		message: '服务器已断线...',
-		type: 'error',
-		position: 'bottom-right',
+	// 在线用户列表
+	proxy.signalR.off('OnlineUserList');
+	proxy.signalR.on('OnlineUserList', (data: any) => {
+		state.onlineUserList = data.userList;
+		ElNotification({
+			title: '提示',
+			message: `${data.online ? `【${data.realName}】上线了` : `【${data.realName}】离开了`}`,
+			type: `${data.online ? 'info' : 'error'}`,
+			position: 'bottom-right',
+		});
 	});
-});
-// 重连成功
-connection.onreconnected(() => {
-	console.log('重连成功');
-});
+	// 强制下线
+	proxy.signalR.off('ForceOffline');
+	proxy.signalR.on('ForceOffline', async (data: any) => {
+		console.log('强制下线', data);
+		await proxy.signalR.stop();
 
-const reciveMessage = (msg: any) => {
-	console.log('接收消息:', msg);
-};
-
-// 接收消息
-connection.on('ReceiveMessage', reciveMessage);
-// 强制下线
-connection.on('ForceOffline', async (data: any) => {
-	console.log('强制下线', data);
-	await connection.stop();
-
-	await getAPI(SysAuthApi).logoutPost();
-	clearAccessTokens();
-});
-// 在线用户改变
-connection.on('OnlineUserChange', (data: any) => {
-	state.onlineUserList = data.userList;
-	ElNotification({
-		title: '提示',
-		message: `${data.online ? `【${data.realName}】上线了` : `【${data.realName}】离开了`}`,
-		type: `${data.online ? 'info' : 'error'}`,
-		position: 'bottom-right',
+		await getAPI(SysAuthApi).logoutPost();
+		clearAccessTokens();
 	});
 });
-
 // 打开页面
 const openDrawer = () => {
 	state.isVisible = true;
@@ -159,7 +121,7 @@ const forceOffline = async (row: any) => {
 		type: 'warning',
 	})
 		.then(async () => {
-			await connection.send('ForceOffline', { connectionId: row.connectionId }).catch(function (err) {
+			await proxy.signalR.send('ForceOffline', { connectionId: row.connectionId }).catch(function (err: any) {
 				console.log(err);
 			});
 		})

+ 44 - 0
Web/src/views/system/onlineUser/signalR.ts

@@ -0,0 +1,44 @@
+import * as SignalR from '@microsoft/signalr';
+import { ElNotification } from 'element-plus';
+import { getToken } from '/@/utils/axios-utils';
+
+// 初始化SignalR对象
+const connection = new SignalR.HubConnectionBuilder()
+	.configureLogging(SignalR.LogLevel.Information)
+	.withUrl(`${import.meta.env.VITE_API_URL}/hubs/onlineUser?access_token=${getToken()}`)
+	.withAutomaticReconnect({
+		nextRetryDelayInMilliseconds: (a) => {
+			console.log(a);
+			return 5000; // 每5秒重连一次
+		},
+	})
+	.build();
+
+connection.keepAliveIntervalInMilliseconds = 15 * 1000; // 心跳检测15s
+connection.serverTimeoutInMilliseconds = 30 * 60 * 1000; // 超时时间30m
+
+// 启动连接
+connection.start().then(() => {
+	console.log('启动连接');
+});
+// 断开连接
+connection.onclose(async () => {
+	console.log('断开连接');
+});
+// 重连中
+connection.onreconnecting(() => {
+	ElNotification({
+		title: '提示',
+		message: '服务器已断线...',
+		type: 'error',
+		position: 'bottom-right',
+	});
+});
+// 重连成功
+connection.onreconnected(() => {
+	console.log('重连成功');
+});
+
+connection.on('OnlineUserList', () => {});
+
+export { connection as signalR };