右上角铃铛消息

This commit is contained in:
19173159168
2025-09-08 22:07:00 +08:00
parent 742f93d08f
commit b8e62f94cd
6 changed files with 294 additions and 66 deletions

View File

@ -3,6 +3,8 @@ package com.ruoyi.web.controller.system;
import java.util.Date;
import java.util.List;
import com.mysql.cj.protocol.x.Notice;
import com.ruoyi.common.constant.UserConstants;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@ -41,7 +43,12 @@ public class SysNoticeController extends BaseController
{
return prefix + "/notice";
}
@RequiresPermissions("system:notice:view")
@GetMapping("/notice2")
public String notice2()
{
return prefix + "/notice2";
}
/**
* 查询公告列表
*/
@ -65,6 +72,9 @@ public class SysNoticeController extends BaseController
public String detail(@PathVariable Long noticeId, ModelMap mmap)
{
SysNotice notice = noticeService.selectNoticeById(noticeId);
// 阅读状态
notice.setReadStatus("1");
noticeService.updateNotice(notice);
mmap.put("notice", notice);
return prefix + "/detail";
}
@ -127,4 +137,15 @@ public class SysNoticeController extends BaseController
{
return toAjax(noticeService.deleteNoticeByIds(ids));
}
@GetMapping("/unreadCount")
@ResponseBody
public int getUnreadNoticeCount() {
SysNotice notice = new SysNotice();
notice.setUserId(getSysUser().getUserId());
notice.setReadStatus("0");
notice.setNoticeType("1");
List<SysNotice> list = noticeService.selectNoticeList(notice);
return list.size();
}
}

View File

@ -15,6 +15,49 @@
<link th:href="@{/css/style.min.css}" rel="stylesheet"/>
<link th:href="@{/css/skins.css}" rel="stylesheet"/>
<link th:href="@{/ruoyi/css/ry-ui.css?v=4.7.4}" rel="stylesheet"/>
<style>
.notice-tip {
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 10px;
z-index: 1000;
width: 180px;
text-align: center;
margin-top: 5px;
}
.notice-tip:before {
content: '';
position: absolute;
top: -8px;
left: 16%;
margin-left: -8px;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 8px solid #ddd;
}
.notice-tip:after {
content: '';
position: absolute;
top: -7px;
left: 16%;
margin-left: -7px;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #fff;
}
.label-danger {
background-color: #d9534f;
color: #fff;
padding: 2px 6px;
border-radius: 10px;
font-size: 12px;
}
</style>
</head>
<body class="fixed-sidebar full-height-layout gray-bg" th:classappend="${isMobile} ? 'canvas-menu'" style="overflow: hidden">
<div id="wrapper">
@ -193,6 +236,15 @@
</a>
</div>
<ul class="nav navbar-top-links navbar-right welcome-message">
<li id="notice">
<a data-toggle="tooltip" data-trigger="hover" data-placement="bottom" href="javascript:void(0);">
<i class="fa fa-bell-o"></i> 消息
<span class="label label-danger" id="noticeCount" style="position: absolute; top: 10px; right: 5px; display: none;">0</span>
</a>
<div id="noticeTip" class="notice-tip" style="display: none; position: absolute; background: #fff; border: 1px solid #ccc; padding: 10px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); z-index: 1000; width: 200px; text-align: center; margin-top: 5px;">
您有 <span id="unreadCount">0</span> 条未读消息
</div>
</li>
<li><a data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="锁定屏幕" href="javascript:;" id="lockScreen"><i class="fa fa-lock"></i> 锁屏</a></li>
<li><a data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="全屏显示" href="javascript:;" id="fullScreen"><i class="fa fa-arrows-alt"></i> 全屏</a></li>
<li class="dropdown user-menu">
@ -382,7 +434,88 @@ $(function() {
});
}
$("[data-toggle='tooltip']").tooltip();
initNoticeFunction();
});
// 初始化消息提醒功能
function initNoticeFunction() {
var hideTimeout; // 用于存储延迟隐藏的定时器ID
// 绑定鼠标悬停事件
$('#notice').hover(
function() {
// 鼠标移入时清除可能存在的隐藏定时器
clearTimeout(hideTimeout);
// 显示未读消息数量和提示
loadUnreadNoticeCount();
$('#noticeTip').show();
},
function() {
// 鼠标移出时1秒后隐藏提示
hideTimeout = setTimeout(function() {
$('#noticeTip').hide();
}, 1000); // 延迟1秒隐藏
}
);
// 鼠标悬停在提示框上时也清除隐藏定时器
$('#noticeTip').hover(
function() {
clearTimeout(hideTimeout);
},
function() {
hideTimeout = setTimeout(function() {
$('#noticeTip').hide();
}, 1000); // 延迟1秒隐藏
}
);
// 绑定点击事件,跳转到消息列表页面
$('#notice').on('click', function(e) {
// 阻止默认行为
e.preventDefault();
// 打开消息列表页面
noticeList();
});
// 初始化加载一次未读消息数量
loadUnreadNoticeCount();
// 设置定时刷新未读消息数量
setInterval(loadUnreadNoticeCount, 60000); // 每分钟刷新一次
}
// 加载未读消息数量
function loadUnreadNoticeCount() {
$.get(ctx + "/system/notice/unreadCount", function(data) {
if (data > 0) {
$("#noticeCount").text(data).show();
$("#unreadCount").text(data);
} else {
$("#noticeCount").hide();
$("#unreadCount").text(0);
}
}).fail(function() {
// 请求失败时隐藏提示
$("#noticeCount").hide();
$("#unreadCount").text(0);
});
}
// 点击消息提醒区域时跳转到消息列表页面
function noticeList(){
var url = ctx + "/system/notice/notice2";
// $.modal.open("消息", url, 1200, 800);
layer.open({
type : 2,
shadeClose : true,
title : "消息",
area : ["1200px", "800px"],
content : [ctx + "/system/notice/notice2", 'no']
})
}
</script>
</body>
</html>

View File

@ -9,35 +9,23 @@
<form class="form-horizontal m" id="form-notice-edit" th:object="${notice}">
<input id="noticeId" name="noticeId" th:field="*{noticeId}" type="hidden">
<div class="form-group">
<label class="col-sm-2 control-label is-required">公告标题:</label>
<label class="col-sm-2 control-label ">标题:</label>
<div class="col-sm-10">
<input id="noticeTitle" name="noticeTitle" th:field="*{noticeTitle}" class="form-control" type="text" required readonly="true">
<div class="form-control-static" th:text="*{noticeTitle}"></div>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">公告类型:</label>
<label class="col-sm-2 control-label">类型:</label>
<div class="col-sm-10">
<select name="noticeType" class="form-control m-b" th:with="type=${@dict.getType('sys_notice_type')}" readonly="true">
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}" th:field="*{noticeType}"></option>
</select>
<div class="form-control-static" th:text="${@dict.getLabel('sys_notice_type', notice.noticeType)}"></div>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">公告内容:</label>
<label class="col-sm-2 control-label">内容:</label>
<div class="col-sm-10">
<input id="noticeContent" name="noticeContent" th:field="*{noticeContent}" type="hidden" readonly="true">
<div id="editor" ></div>
<div class="form-control-static" th:text="*{noticeContent}"></div>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">公告状态:</label>
<div class="col-sm-10">
<div class="radio-box" th:each="dict : ${@dict.getType('sys_notice_status')}">
<input type="radio" th:id="${dict.dictCode}" name="status" th:value="${dict.dictValue}" th:field="*{status}" readonly="true">
<label th:for="${dict.dictCode}" th:text="${dict.dictLabel}"></label>
</div>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
@ -46,58 +34,14 @@
var prefix = ctx + "system/notice";
$(function() {
$('.summernote').summernote({
placeholder: '请输入公告内容',
height : 192,
lang : 'zh-CN',
followingToolbar: false,
dialogsInBody: true,
callbacks: {
onImageUpload: function (files) {
sendFile(files[0], this);
}
}
});
var content = $("#noticeContent").val();
$('#editor').summernote('code', content);
});
// 上传文件
function sendFile(file, obj) {
var data = new FormData();
data.append("file", file);
$.ajax({
type: "POST",
url: ctx + "common/upload",
data: data,
cache: false,
contentType: false,
processData: false,
dataType: 'json',
success: function(result) {
if (result.code == web_status.SUCCESS) {
$(obj).summernote('editor.insertImage', result.url, result.fileName);
} else {
$.modal.alertError(result.msg);
}
},
error: function(error) {
$.modal.alertWarning("图片上传失败。");
}
});
}
$("#form-notice-edit").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
var sHTML = $('.summernote').summernote('code');
$("#noticeContent").val(sHTML);
$.operate.save(prefix + "/edit", $('#form-notice-edit').serialize());
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,99 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('通知公告列表')" />
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="notice-form">
<div class="select-list">
<input type="hidden" class="form-control" name="status" value="0" >
<ul>
<li>
类型:<select name="noticeType" th:with="type=${@dict.getType('sys_notice_type')}">
<option value="">所有</option>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</li>
<li>
状态:<select name="readStatus" th:with="type=${@dict.getType('sys_read_status')}">
<option value="">所有</option>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</li>
<li>
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
</li>
</ul>
</div>
</form>
</div>
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var editFlag = [[${@permission.hasPermi('system:notice:edit')}]];
var removeFlag = [[${@permission.hasPermi('system:notice:remove')}]];
var types = [[${@dict.getType('sys_notice_type')}]];
var datas = [[${@dict.getType('sys_read_status')}]];
var prefix = ctx + "system/notice";
$(function() {
var options = {
url: prefix + "/list",
updateUrl: prefix + "/edit/{id}",
detailUrl: prefix + "/detail/{id}",
modalName: "消息",
columns: [{
checkbox: true
},
{
field : 'noticeId',
title : '序号'
},
{
field : 'noticeTitle',
title : '消息标题',
width: 200,
formatter: function(value, row, index) {
return '<a href="javascript:void(0)" onclick="$.operate.detail(\'' + row.noticeId + '\', 900, 700)">' + value + '</a>';
}
},
{
field : 'noticeContent',
title : '消息内容',
width : 600
},
{
field: 'readStatus',
title: '状态',
align: 'center',
width: 100,
formatter: function(value, row, index) {
return $.table.selectDictLabel(datas, value);
}
},
{
field: 'createTime',
title: '消息时间',
width: 150,
sortable: true
}]
};
$.table.init(options);
});
function detail(noticeId){
var url = prefix + "/detail/"+noticeId;
$.modal.openOptions("通知公告",url);
}
</script>
</body>
</html>

View File

@ -31,6 +31,10 @@ public class SysNotice extends BaseEntity
/** 公告状态0正常 1关闭 */
private String status;
private String readStatus;
private Long userId;
public Long getNoticeId()
{
return noticeId;
@ -84,6 +88,22 @@ public class SysNotice extends BaseEntity
return status;
}
public String getReadStatus() {
return readStatus;
}
public void setReadStatus(String readStatus) {
this.readStatus = readStatus;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)

View File

@ -10,6 +10,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="noticeType" column="notice_type" />
<result property="noticeContent" column="notice_content" />
<result property="status" column="status" />
<result property="readStatus" column="read_status" />
<result property="userId" column="user_id" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
@ -18,7 +20,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectNoticeVo">
select notice_id, notice_title, notice_type, notice_content, status, create_by, create_time, update_by, update_time, remark
select notice_id, notice_title, notice_type, notice_content, status, read_status,user_id, create_by, create_time, update_by, update_time, remark
from sys_notice
</sql>
@ -36,6 +38,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="noticeType != null and noticeType != ''">
AND notice_type = #{noticeType}
</if>
<if test="readStatus != null and readStatus != ''">
AND read_status = #{readStatus}
</if>
<if test="userId != null">
AND user_id = #{userId}
</if>
<if test="createBy != null and createBy != ''">
AND create_by like concat('%', #{createBy}, '%')
</if>
@ -74,6 +82,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="noticeType != null and noticeType != '' ">notice_type, </if>
<if test="noticeContent != null and noticeContent != '' ">notice_content, </if>
<if test="status != null and status != '' ">status, </if>
<if test="userId != null">user_id, </if>
<if test="remark != null and remark != ''">remark,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
create_time,
@ -83,6 +92,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="noticeType != null and noticeType != ''">#{noticeType}, </if>
<if test="noticeContent != null and noticeContent != ''">#{noticeContent}, </if>
<if test="status != null and status != ''">#{status}, </if>
<if test="userId != null ">#{userId}, </if>
<if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
sysdate(),
@ -97,6 +107,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="noticeType != null and noticeType != ''">notice_type = #{noticeType}, </if>
<if test="noticeContent != null">notice_content = #{noticeContent}, </if>
<if test="status != null and status != ''">status = #{status}, </if>
<if test="readStatus != null and readStatus != ''">read_status = #{readStatus}, </if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = sysdate()
</set>