fastadmin实现站内通知功能

实现效果如下
在这里插入图片描述
在这里插入图片描述
application/admin/view/common/header.html

<style>#notificationMenu {display: none;position: absolute;top: 40px;right: 0;background: #fff;border-radius: 6px;padding: 10px 0;width: 300px;box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);z-index: 1000;}#notificationItem {display: block !important;}#notificationIcon {display: inline-block;}#notificationMenu .menu li {padding: 12px 15px;border-bottom: 1px solid #f0f0f0;transition: background-color 0.3s ease;cursor: pointer;}#notificationMenu .menu li:hover {background-color: #f7f7f7;}#notificationMenu .menu {max-height: 250px;overflow-y: auto;}.badge {position: relative;top: -10px;right: -10px;}
</style>
<!-- Logo -->
<a href="javascript:;" class="logo"><!-- 迷你模式下Logo的大小为50X50 --><span class="logo-mini">{$site.name|mb_substr=0,4,'utf-8'|mb_strtoupper='utf-8'|htmlentities}</span><!-- 普通模式下Logo --><span class="logo-lg">{$site.name|htmlentities}</span>
</a><!-- 顶部通栏样式 -->
<nav class="navbar navbar-static-top"><!--第一级菜单--><div id="firstnav"><!-- 边栏切换按钮--><a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button"><span class="sr-only">{:__('Toggle navigation')}</span></a><!--如果不想在顶部显示角标,则给ul加上disable-top-badge类即可--><ul class="nav nav-tabs nav-addtabs disable-top-badge hidden-xs" role="tablist">{$navlist}</ul><div class="navbar-custom-menu"><ul class="nav navbar-nav"><!-- 通知图标 --><li id="notificationItem" class="dropdown notifications-menu" style="display: block;"><a href="#" id="notificationIcon"><i class="fa fa-bell"></i><span id="notificationBadge" class="badge badge-danger">{$notice|default=0}</span></a><ul id="notificationMenu" class="dropdown-menu"><li style="padding: 10px; display: flex; justify-content: space-between; align-items: center;"><a href="javascript:void(0);" id="doNotDisturbButton" style="display: flex; align-items: center;"><i class="fa fa-bell" id="doNotDisturbIcon" style="margin-right: 5px;"></i> 免打扰</a><a href="javascript:void(0);" id="markAllReadButton"><i class="fa fa-check-circle"></i> 一键已读</a></li><li><ul id="notificationList" class="menu"></ul></li></ul></li><!-- 多语言列表 -->{if $Think.config.lang_switch_on}<li class="hidden-xs"><a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-language"></i></a><ul class="dropdown-menu"><li class="{$config['language']=='zh-cn'?'active':''}"><a href="?ref=addtabs&lang=zh-cn">简体中文</a></li><li class="{$config['language']=='en'?'active':''}"><a href="?ref=addtabs&lang=en">English</a></li></ul></li>{/if}<!-- 账号信息下拉框 --><li class="dropdown user user-menu"><a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="{$admin.avatar|cdnurl|htmlentities}" class="user-image" alt=""><span class="hidden-xs">{$admin.nickname|htmlentities}</span></a><ul class="dropdown-menu"><!-- User image --><li class="user-header"><img src="{$admin.avatar|cdnurl|htmlentities}" class="img-circle" alt=""><p>{$admin.nickname|htmlentities}<small>{$admin.logintime|date="Y-m-d H:i:s",###}</small></p></li><li class="user-body"><div class="visible-xs"><div class="pull-left"><a href="__PUBLIC__" target="_blank"><i class="fa fa-home"style="font-size:14px;"></i>{:__('Home')}</a></div><div class="pull-right"><a href="javascript:;" data-type="all" class="wipecache"><iclass="fa fa-trash fa-fw"></i> {:__('Wipe all cache')}</a></div></div></li><!-- Menu Footer--><li class="user-footer"><div class="pull-left"><a href="general/profile" class="btn btn-primary addtabsit"><i class="fa fa-user"></i>{:__('Profile')}</a></div><div class="pull-right"><a href="{:url('index/logout')}" class="btn btn-danger"><i class="fa fa-sign-out"></i>{:__('Logout')}</a></div></li></ul></li><!-- 控制栏切换按钮 --><li class="hidden-xs"><a href="javascript:;" data-toggle="control-sidebar"><i class="fa fa-gears"></i></a></li></ul></div></div>{if $Think.config.fastadmin.multiplenav}<!--第二级菜单,只有在multiplenav开启时才显示--><div id="secondnav"><ul class="nav nav-tabs nav-addtabs disable-top-badge" role="tablist">{if $fixedmenu}<li role="presentation" id="tab_{$fixedmenu.id}" class="{:$referermenu?'':'active'}"><ahref="#con_{$fixedmenu.id}" node-id="{$fixedmenu.id}" aria-controls="{$fixedmenu.id}" role="tab"data-toggle="tab"><i class="fa fa-dashboard fa-fw"></i> <span>{$fixedmenu.title}</span> <spanclass="pull-right-container"> </span></a></li>{/if}{if $referermenu}<li role="presentation" id="tab_{$referermenu.id}" class="active"><a href="#con_{$referermenu.id}"node-id="{$referermenu.id}"aria-controls="{$referermenu.id}"role="tab" data-toggle="tab"><iclass="fa fa-list fa-fw"></i> <span>{$referermenu.title}</span> <spanclass="pull-right-container"> </span></a> <i class="close-tab fa fa-remove"></i></li>{/if}</ul></div>{/if}
</nav><script>function initNotifications() {const notificationIcon = document.getElementById("notificationIcon");const notificationMenu = document.getElementById("notificationMenu");const notificationList = document.getElementById("notificationList");const notificationBadge = document.getElementById("notificationBadge");const doNotDisturbButton = document.getElementById("doNotDisturbButton");const doNotDisturbIcon = document.getElementById("doNotDisturbIcon");let notifications = []; // 初始通知列表let doNotDisturb = localStorage.getItem("doNotDisturb") === "true";// 更新免打扰图标状态function updateDoNotDisturbIcon() {doNotDisturbIcon.className = doNotDisturb ? "fa fa-bell-slash" : "fa fa-bell";// 同步主通知图标样式const notificationIconBell = document.querySelector("#notificationIcon i");if (notificationIconBell) {notificationIconBell.className = doNotDisturb ? "fa fa-bell-slash" : "fa fa-bell";}}// 切换免打扰模式doNotDisturbButton.addEventListener("click", () => {doNotDisturb = !doNotDisturb;localStorage.setItem("doNotDisturb", doNotDisturb);updateDoNotDisturbIcon();// 同步主通知图标样式const notificationIconBell = document.querySelector("#notificationIcon i");if (notificationIconBell) {notificationIconBell.className = doNotDisturb ? "fa fa-bell-slash" : "fa fa-bell";}Toastr.info(doNotDisturb ? "已开启免打扰模式" : "已关闭免打扰模式");});// 初始化免打扰图标updateDoNotDisturbIcon();// 一键已读功能document.getElementById("markAllReadButton").addEventListener("click", markAllAsRead);//全部已读function markAllAsRead() {$.ajax({url: "notice/markAllAsRead",method: "POST",success: function (data) {if (data.code === 1) {notifications.forEach((notif) => (notif.read = 2)); // 更新本地状态updateNotifications();Toastr.success("全部已读");} else {Toastr.error(data.msg || "一键已读失败");}},error: function () {Toastr.error("网络错误,无法一键已读");},});}// 获取通知列表function fetchNotifications() {$.ajax({url: "notice/noticelist",method: "GET",success: function (response) {if (response.code === 1 && Array.isArray(response.data)) {notifications = response.data.map((item) => ({id: item.id,text: item.title,read: item.read,}));updateNotifications();} else {Toastr.error(response.msg || "获取通知失败");}},error: function () {Toastr.error("网络错误,无法获取通知");},});}// 更新通知列表和未读数function updateNotifications() {const fragment = document.createDocumentFragment();let unreadCount = 0;notificationList.innerHTML = "";notifications.forEach((notif) => {const li = document.createElement("li");li.textContent = notif.text;li.style.opacity = notif.read > 1 ? "0.5" : "1";if (notif.read == 1) unreadCount++;li.addEventListener("click", () => {notif.read = 2updateNotifications();markAsRead(notif.id); // 标记为已读});fragment.appendChild(li);});notificationList.appendChild(fragment);notificationBadge.textContent = unreadCount;}// 单个标记为已读function markAsRead(id) {$.ajax({url: "notice/markAsRead",method: "GET",data: { id: id },success: function (data) {if (data.code !== 1) {console.error(data.msg || "标记已读失败");}},error: function () {Toastr.error("网络错误,无法标记为已读");},});}// 点击通知图标时notificationIcon.addEventListener("click", () => {notificationMenu.classList.toggle("show");if (notificationMenu.classList.contains("show")) {fetchNotifications();}});// 点击外部关闭菜单document.addEventListener("click", (e) => {if (!notificationMenu.contains(e.target) && e.target !== notificationIcon) {notificationMenu.classList.remove("show");}});}// 等待 DOM 加载完成后运行document.addEventListener("DOMContentLoaded", initNotifications);
</script>

public/assets/js/backend/index.js

				var connectWebSocket = function () {var ws = new WebSocket(Config.socket_url);ws.onopen = function () {console.log("WebSocket连接已建立");};ws.onmessage = function (event) {var message = JSON.parse(event.data);//处理消息通知if (message && message.type === "ping") {// console.log(message)let doNotDisturb = localStorage.getItem("doNotDisturb");if (!doNotDisturb||doNotDisturb === 'false') {//判断是否设置免打扰模式Toastr.info(message.content || "您有一条新消息");}console.log(doNotDisturb)// 更新角标-未读数加 1const badgeElement = document.getElementById("notificationBadge");badgeElement.textContent = parseInt(badgeElement.textContent || 0) + 1;}// 处理 "init" 消息类型并发送 AJAX 请求else if (message && message.type === "init") {//Toastr.success(message.content || "Socket 链接成功");// 发送 AJAX 请求到 admin/index/bind_admin 接口,传递 client_id$.ajax({url: 'index/bind_admin',type: 'POST',data: {client_id: message.client_id},dataType: 'json',success: function (response) {if (response.code === 1) {Toastr.info(response.content || "Socket 绑定成功");} else {Toastr.error(response.content || "Socket 绑定失败");}},error: function () {Toastr.error("网络错误,绑定失败");}});}};ws.onclose = function () {console.log("WebSocket连接已关闭,正在重连...");setTimeout(connectWebSocket, 10000);  // 延迟10秒自动重连};ws.onerror = function () {Toastr.error("socket链接失败");};return ws;};// 初始化WebSocket连接var ws = connectWebSocket();

application/admin/controller/Notice.php

<?phpnamespace app\admin\controller;use app\common\controller\Backend;/*** 消息通知管理** @icon fa fa-circle-o*/
class Notice extends Backend
{/*** Notice模型对象** @var \app\admin\model\Notice*/protected $model = null;public function _initialize(){parent::_initialize();$this->model = new \app\admin\model\Notice;$this->view->assign("roleList", $this->model->getRoleList());$this->view->assign("readList", $this->model->getReadList());}//消息通知列表public function noticelist(){$notifications = $this->model->where(['role' => 1, 'uid' => $this->auth->id])->order('id desc')->select();$this->success('获取成功', '', $notifications);}//标记已读public function markAsRead(){$id = input('id');if (!$id) {$this->error('参数错误');}$info = $this->model->where(['id' => $id])->find();if (!$info) {$this->error('数据读取失败');}$info->save(['read' => 2]);$this->success('已标记为已读');}//全部已读public function markAllAsRead(){$uid = $this->auth->id;$sql = $this->model->where(['role' => 1, 'uid' => $uid])->update(['read' => 2]);if (!$sql){$this->error('无数据更新');}$this->success('已标记为已读');}
}

musql表结构如下

CREATE TABLE `fa_notice` (`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,`title` VARCHAR(255) NULL DEFAULT '0' COMMENT '消息标题' COLLATE 'utf8_general_ci',`content` TEXT NULL DEFAULT NULL COMMENT '消息内容' COLLATE 'utf8_general_ci',`uid` INT(11) NULL DEFAULT '0' COMMENT '收消息方用户id',`role` ENUM('1','2') NOT NULL COMMENT '推送:1=向后台推送,2=向用户推送' COLLATE 'utf8_general_ci',`read` ENUM('1','2') NULL DEFAULT '1' COMMENT '查看状态:1=未读,2=已读' COLLATE 'utf8_general_ci',`createtime` BIGINT(16) NULL DEFAULT NULL,`updatetime` BIGINT(16) NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
)
COMMENT='消息通知表'
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=5
;

在这里插入图片描述
index.php映射

//获取websocket链接
$this->assignconfig('socket_url', Config::get('websocket.url')?Config::get('websocket.url'):'ws://127.0.0.1:8282');//js中Config.socket_url就可以读取值

config.php

//配置websocket地址'websocket' => ['url' => 'ws://127.0.0.1:8282',//本地测试],

整体实现效果逻辑
映射页面时,先查询统计消息表未读消息,让角标加载后显示未读消息数量

index.js连接websocket,实现角标未读数值更新,如果有新推送消息,角标数字+1,并弹出新消息提示框(如果设置了免打扰,免打扰的值使用localStorage.setItem存储,判断有没有该值进行是否弹框提醒。免打扰下只更新角标)
在这里插入图片描述

在这里插入图片描述
点击icon访问列表接口,渲染出未读消息,点击消息实现消息更新已读状态。点击一键已读,把当前用户的消息update一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/476532.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大语言模型---LoRA中损失值的计算

文章目录 概要损失计算流程小结 概要 Llama-7B模型的LoRA微调训练中&#xff0c;通过使用Cross-Entropy Loss来度量模型输出的预测分布和真实标签分布之间的距离&#xff0c;来衡量模型的准确性。 本文主要介绍LoRA中损失值的计算流程。 Cross-Entropy Loss作用&#xff1a;是…

【Vue】指令扩充(指令修饰符、样式绑定)

目录 指令修饰符 按键修饰符 事件修饰符 双向绑定指令修饰符 输入框 表单域 下拉框 单选按钮 复选框 样式绑定 分类 绑定class 绑定style tab页切换示例 指令修饰符 作用 借助指令修饰符&#xff0c;可以让指令的功能更强大 分类 按键修饰符&#xff1a;用来…

集成金蝶云星空数据至MySQL的完整案例解析

金蝶云星空数据集成到MySQL的技术案例分享 在企业信息化系统中&#xff0c;数据的高效流动和准确同步是确保业务连续性和决策支持的重要环节。本文将聚焦于一个具体的系统对接集成案例——金蝶云星空的数据集成到MySQL&#xff0c;方案名称为“2金蝶物料同步到商城中间表”。 …

为什么transformer的时间复杂度是N的平方,具体是里面的哪一个计算流程最占用时间

Transformer的时间复杂度为 O(N2)&#xff0c;其中 NN 是输入序列的长度。这一复杂度主要来源于自注意力机制&#xff08;self-attention mechanism&#xff09;的计算过程。 在Transformer模型中&#xff0c;自注意力机制的核心步骤是计算查询&#xff08;Query&#xff09;、…

如何在Linux上安装Canal同步工具

1. 下载安装包 所用到的安装包 canal.admin-1.1.4.tar.gz 链接&#xff1a;https://pan.baidu.com/s/1B1LxZUZsKVaHvoSx6VV3sA 提取码&#xff1a;v7ta canal.deployer-1.1.4.tar.gz 链接&#xff1a;https://pan.baidu.com/s/13RSqPinzgaaYQUyo9D8ZCQ 提取码&#xff1a;…

操作系统大会2024 | 麒麟信安根植openEuler社区,持续技术创新 共拓新应用 探索新机遇

[中国&#xff0c;北京&#xff0c;2024年11月15日] 以“以智能&#xff0c;致世界”为主题的操作系统大会2024在北京中关村国际创新中心召开&#xff0c;本次大会由openEuler社区和全球计算联盟主办&#xff0c;旨在汇聚全球产业界力量&#xff0c;推动基础软件根技术持续创新…

Wallpaper壁纸制作学习记录03

添加用户属性 Wallpaper Engine 允许用户在用户属性的帮助下进一步自定义您的壁纸。用户属性允许您为用户提供进一步调整和自定义壁纸各个方面的选项&#xff0c;包括完全隐藏壁纸中的对象。 创建可见性属性 每个元素在右上角都有一个 visibility 属性&#xff08;由眼睛图标…

杰理-gpadc

gpadc API是系统提供的用于adc采集的接口 void adc_init(); //adc功能初始化&#xff0c;一般在板级配置.c文件已经默认调用&#xff0c;用户无需再重复调用。 示例&#xff1a; static void WANG_printf(void *_arg) {//adc_init(); //板级配置中默认会调用&#xff0c;实际…

如何使用Jmeter做性能测试?

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 今天我们来说说jmeter如何进行性能测试&#xff0c;我们都知道jmeter工具除了可以进行接口功能测试外&#xff0c;还可以进行性能测试。当项目趋于稳定&#xf…

【CSP CCF记录】201903-1第16次认证 小中大

题目 样例1输入 3 -1 2 4 样例1输出 4 2 -1 样例1解释 4 为最大值&#xff0c;2 为中位数&#xff0c;−1 为最小值。 样例2输入 4 -2 -1 3 4 样例2输出 4 1 -2 样例2解释 4 为最大值&#xff0c;(−13)21为中位数&#xff0c;−2为最小值。 思路 本题两个注意点&#xff0…

windows下,用CMake编译qt项目,出现错误By not providing “FindQt5.cmake“...

开发环境&#xff1a;windows10 qt5.14&#xff0c; 编译器msvc2017x64&#xff0c;CMake3.30&#xff1b; 现象&#xff1a; CMakeList文件里&#xff0c;如有find_package(Qt5 COMPONENTS Widgets REQUIRED) target_link_libraries(dis_lib PRIVATE Qt5::Widgets) 用CMak…

自由学习记录(23)

Lua的学习 table.concat(tb,";") 如果表里带表&#xff0c;则不能拼接&#xff0c;表里带nil也不能&#xff0c;都会报错 true和false也不可以&#xff0c;数字和字符串可以 if要和一个end配对&#xff0c;所以 if a>b then return true end end 两个end …

JavaWeb-表格标签-06

表格标签 table code: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>表格标签</title><…

【Stable Diffusion】 超大尺寸绘制、分区控制,详解Tiled Diffusion VAE插件功能

今天&#xff0c;我们将向您介绍一款令人兴奋的AI工具——Tiled Diffusion & VAE插件。这是一款基于Stable Diffusion技术的创新应用&#xff0c;旨在为您提供超大尺寸绘制和分区控制的便捷体验。无论您是AI绘画的新手还是专业人士&#xff0c;这个工具都能为您带来极大的便…

【大数据分析机器学习】分布式机器学习

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈智能大数据分析 ⌋ ⌋ ⌋ 智能大数据分析是指利用先进的技术和算法对大规模数据进行深入分析和挖掘&#xff0c;以提取有价值的信息和洞察。它结合了大数据技术、人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&a…

染色质重塑与心衰中的细胞间通讯机制:解读一篇Nature力作

染色质重塑与心衰中的细胞间通讯机制&#xff1a;解读一篇Nature力作 一、文献的主要行文思路与观点 在这篇发表在 Nature 的文章中&#xff0c;作者聚焦于心脏衰竭中的慢性炎症与纤维化问题&#xff0c;试图揭示免疫细胞与成纤维细胞之间的通讯机制。研究围绕以下几个核心问题…

WordPress添加类似说说、微博的时间轴微语页面

这个版本的WordPress可以直接使用&#xff0c;CSS样式可以完美兼容。效果如图 使用方法&#xff1a; 一、后台配置 新建微语功能 将下面的代码复制粘贴到主题的functions.php函数文件中&#xff0c;为WordPress添加微语功能。添加完成后&#xff0c;可以在WordPress后台菜单…

解决IDEA报包不存在,但实际存在的问题

前言 最近在把一个亿老项目交割给同事&#xff0c;同事在导入项目运行时遇到IDEA报包不存在&#xff0c;但实际存在的问题&#xff0c;最终通过以下方式解决 现象 在IDEA里启动运行项目&#xff0c;报某个类有问题&#xff0c;引入的包不存在。 点击这个引入的包&#xff0c;可…

云原生之k8s服务管理

文章目录 服务管理Service服务原理ClusterIP服务 对外发布应用服务类型NodePort服务Ingress安装配置Ingress规则 Dashboard概述 认证和授权ServiceAccount用户概述创建ServiceAccount 权限管理角色与授权 服务管理 Service 服务原理 容器化带来的问题 自动调度&#xff1a;…

RocketMQ: 集群部署注意事项

概述 RocketMQ 是一款分布式、队列模型的消息中间件&#xff0c;具有以下特点&#xff1a; 能够保证严格的消息顺序提供丰富的消息拉取模式高效的订阅者水平扩展能力实时的消息订阅机制亿级消息堆积能力 选用理由&#xff1a; 强调集群无单点&#xff0c;可扩展&#xff0c;任…