首页新增通知公告消息提醒

springboot2
RuoYi 3 weeks ago
parent 784615563a
commit c28be5db3b

@ -15,8 +15,11 @@ import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.system.domain.SysNotice;
import com.ruoyi.system.service.ISysNoticeReadService;
import com.ruoyi.system.service.ISysNoticeService;
/**
@ -33,6 +36,9 @@ public class SysNoticeController extends BaseController
@Autowired
private ISysNoticeService noticeService;
@Autowired
private ISysNoticeReadService noticeReadService;
@RequiresPermissions("system:notice:view")
@GetMapping()
public String notice()
@ -111,6 +117,46 @@ public class SysNoticeController extends BaseController
return prefix + "/view";
}
/**
* 5
*/
@GetMapping("/listTop")
@ResponseBody
public AjaxResult listTop()
{
Long userId = ShiroUtils.getSysUser().getUserId();
List<SysNotice> list = noticeReadService.selectNoticeListWithReadStatus(userId, 5);
long unreadCount = list.stream().filter(n -> !n.getIsRead()).count();
AjaxResult result = AjaxResult.success(list);
result.put("unreadCount", unreadCount);
return result;
}
/**
*
*/
@PostMapping("/markRead")
@ResponseBody
public AjaxResult markRead(Long noticeId)
{
Long userId = ShiroUtils.getSysUser().getUserId();
noticeReadService.markRead(noticeId, userId);
return success();
}
/**
*
*/
@PostMapping("/markReadAll")
@ResponseBody
public AjaxResult markReadAll(String ids)
{
Long userId = ShiroUtils.getSysUser().getUserId();
Long[] noticeIds = Convert.toLongArray(ids);
noticeReadService.markReadBatch(userId, noticeIds);
return success();
}
/**
*
*/
@ -120,6 +166,7 @@ public class SysNoticeController extends BaseController
@ResponseBody
public AjaxResult remove(String ids)
{
noticeReadService.deleteByNoticeIds(ids);
return toAjax(noticeService.deleteNoticeByIds(ids));
}
}

@ -15,8 +15,8 @@
<link th:href="@{/css/style.min.css}" rel="stylesheet"/>
<link th:href="@{/css/skins.css?v=20200902}" rel="stylesheet"/>
<link th:href="@{/ruoyi/css/ry-ui.css?v=4.8.2}" rel="stylesheet"/>
<style type="text/css">.fixed-sidebar .nav:not(.navbar-toolbar)>li.active{border-left:0px!important;}</style>
</head>
<style type="text/css">.fixed-sidebar .nav:not(.navbar-toolbar)>li.active{border-left:0px!important;}#noticeItems li a{transition:background .15s;border-radius:2px}#noticeItems li a:hover{background:#f7f9fb;text-decoration:none}#noticeItems li a:active{background:#eef2f7}#noticeItems li a:focus{background:transparent;outline:0}#noticeList .mark-all-read{font-size:12px;color:#5b9bd5;text-decoration:none;padding:2px 8px;border-radius:3px;transition:background 0.15s}#noticeList .mark-all-read:hover{background:#eef5fc;color:#2b7cc1}#noticeList .mark-all-read:active{background:#eef5fc;color:#2b7cc1}#noticeList .mark-all-read:focus{outline:none;background:transparent}
</style>
<body class="fixed-sidebar full-height-layout gray-bg" th:classappend="${isMobile} ? 'canvas-menu'" style="overflow: hidden">
<div id="wrapper">
@ -252,7 +252,19 @@
<li><a data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="开发文档" href="http://doc.ruoyi.vip/ruoyi" target="_blank"><i class="fa fa-question-circle"></i> 文档</a></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" id="noticeDropdown">
<a href="javascript:void(0)" class="dropdown-toggle" style="position:relative;" data-hover="dropdown">
<i class="fa fa-bell-o"></i> 消息
<span id="noticeBadge" style="display:none;position:absolute;top:8px;right:-4px;background:#e74c3c;color:#fff;border-radius:50%;width:16px;height:16px;font-size:10px;line-height:16px;text-align:center;font-style:normal;"></span>
</a>
<ul class="dropdown-menu" id="noticeList" style="width:320px;padding:0;left:auto;right:0;">
<li style="padding:10px 16px;background:#f7f9fb;border-bottom:1px solid #eee;display:flex;align-items:center;justify-content:space-between;">
<span style="font-size:13px;font-weight:600;color:#333;">通知公告</span>
<a href="javascript:void(0)" onclick="markAllRead()" class="mark-all-read">全部已读</a>
</li>
<li id="noticeItems"><div style="padding:20px;text-align:center;color:#aaa;font-size:12px;"><i class="fa fa-spinner fa-spin"></i> 加载中...</div></li>
</ul>
</li>
<li class="dropdown user-menu">
<a href="javascript:void(0)" class="dropdown-toggle" data-hover="dropdown">
<img th:src="(${#strings.isEmpty(user.avatar)}) ? @{/img/profile.jpg} : @{${user.avatar}}" th:onerror="this.src='img/profile.jpg'" class="user-image">
@ -457,7 +469,123 @@ $(function() {
}
$("[data-toggle='tooltip']").tooltip();
/* 页面加载时拉取公告 */
loadNoticeTop();
});
/* 加载顶部公告列表 */
function loadNoticeTop() {
$.ajax({
url: ctx + "system/notice/listTop",
type: "get",
dataType: "json",
success: function(res) {
if (res.code !== 0 || !res.data || res.data.length === 0) {
$("#noticeItems").html('<div style="padding:24px;text-align:center;color:#bbb;font-size:12px;"><i class=\"fa fa-inbox\"></i><br>暂无公告</div>');
return;
}
var list = res.data;
var unread = (res.unreadCount !== undefined && res.unreadCount !== null) ? res.unreadCount : list.length;
if (unread > 0) {
$("#noticeBadge").text(unread).show();
} else {
$("#noticeBadge").hide();
}
var html = '';
for (var i = 0; i < list.length; i++) {
var n = list[i];
var typeClass = n.noticeType === '1' ? 'warning' : 'success';
var typeLabel = n.noticeType === '1' ? '通知' : '公告';
var isRead = n.isRead === true || n.isRead === 'true' || n.read === true || n.read === 'true';
var aColor = isRead ? '#999' : '#333';
var titleColor = isRead ? 'color:#999;' : '';
var readAttr = isRead ? ' data-read="1"' : '';
var badgeHtml = isRead
? '<span style="flex-shrink:0;display:inline-block;padding:2px 6px;border-radius:3px;font-size:11px;font-weight:600;background:#e9ecef;color:#aaa;">' + typeLabel + '</span>'
: '<span class="badge badge-' + typeClass + '" style="flex-shrink:0;">' + typeLabel + '</span>';
html += '<li id="notice-item-' + n.noticeId + '"' + readAttr + ' style="border-bottom:1px solid #f5f5f5;">';
html += '<a href="javascript:void(0)" onclick="previewNotice(' + n.noticeId + ')" style="display:flex;align-items:center;padding:10px 16px;gap:10px;text-decoration:none;color:' + aColor + ';">';
html += badgeHtml;
html += '<span style="flex:1;font-size:12px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;' + titleColor + '" title="' + (n.noticeTitle||'') + '">' + (n.noticeTitle||'') + '</span>';
html += '<span style="font-size:11px;color:#bbb;flex-shrink:0;">' + (n.createTime ? n.createTime.substring(0,10) : '') + '</span>';
html += '</a></li>';
}
$("#noticeItems").html(html);
},
error: function() {
$("#noticeItems").html('<div style="padding:20px;text-align:center;color:#e74c3c;font-size:12px;">加载失败</div>');
}
});
}
/* 预览公告 */
function previewNotice(noticeId) {
var url = ctx + "system/notice/view/" + noticeId;
// 先立即更新样式并标记已读
var $item = $("#notice-item-" + noticeId);
$item.attr("data-read", "1");
$item.find("a").css("color", "#999");
$item.find("span").css("color", "#999");
var $badge = $item.find(".badge");
if ($badge.length) {
$badge.replaceWith('<span style="flex-shrink:0;display:inline-block;padding:2px 6px;border-radius:3px;font-size:11px;font-weight:600;background:#e9ecef;color:#aaa;">' + $badge.text() + '</span>');
}
// 用 data-read 属性计数未读
var unread = $("#noticeItems li:not([data-read])").length;
if (unread > 0) {
$("#noticeBadge").text(unread).show();
} else {
$("#noticeBadge").hide();
}
// 异步通知后端记录已读fire and forget
$.post(ctx + "system/notice/markRead", { noticeId: noticeId });
var _layerIdx;
_layerIdx = top.layer.open({
type: 2,
offset: 'r',
anim: 'slideLeft',
move: false,
title: false,
closeBtn: 0,
shade: [0.25, '#1a202c'],
shadeClose: true,
area: ['600px', '100%'],
content: url,
success: function(layero) {
// 去掉 layer 默认 padding让内容完全填满
layero.css({ 'border-radius': '0', 'border': 'none' });
layero.find('.layui-layer-content').css({ 'border-radius': '0' });
// 悬浮关闭按钮,紧贴面板左边缘外侧
var $btn = $('<button id="noticeCloseBtn" style="position:fixed;top:20px;right:608px;width:34px;height:34px;border-radius:50%;background:#fff;border:1px solid #e2e8f0;box-shadow:0 2px 10px rgba(0,0,0,0.12);cursor:pointer;font-size:13px;color:#718096;line-height:1;z-index:19891017;transition:all 0.18s;"><i class="fa fa-times"></i></button>');
$btn.hover(
function(){ $(this).css({'background':'#f7fafc','color':'#2d3748','border-color':'#cbd5e0'}); },
function(){ $(this).css({'background':'#fff','color':'#718096','border-color':'#e2e8f0'}); }
);
$btn.on('click', function(){ top.layer.close(_layerIdx); });
$('body', top.document).append($btn);
},
end: function() {
$('body', top.document).find('#noticeCloseBtn').remove();
}
});
}
function markAllRead() {
var ids = [];
$("#noticeItems li").each(function() {
var id = $(this).attr("id").replace("notice-item-", "");
if (id) ids.push(id);
$(this).attr("data-read", "1");
$(this).find("a").css("color", "#999");
$(this).find("span").css("color", "#999");
var $b = $(this).find(".badge");
$b.replaceWith('<span style="flex-shrink:0;display:inline-block;padding:2px 6px;border-radius:3px;font-size:11px;font-weight:600;background:#e9ecef;color:#aaa;">' + $b.text() + '</span>');
});
$("#noticeBadge").hide();
if (ids.length > 0) {
$.post(ctx + "system/notice/markReadAll", { ids: ids.join(",") });
}
}
</script>
</body>
</html>

@ -15,6 +15,8 @@
<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.8.2}" rel="stylesheet"/>
<style type="text/css">#noticeItems li a{transition:background .15s;border-radius:2px}#noticeItems li a:hover{background:#f7f9fb;text-decoration:none}#noticeItems li a:active{background:#eef2f7}#noticeItems li a:focus{background:transparent;outline:0}#noticeList .mark-all-read{font-size:12px;color:#5b9bd5;text-decoration:none;padding:2px 8px;border-radius:3px;transition:background 0.15s}#noticeList .mark-all-read:hover{background:#eef5fc;color:#2b7cc1}#noticeList .mark-all-read:active{background:#eef5fc;color:#2b7cc1}#noticeList .mark-all-read:focus{outline:none;background:transparent}
</style>
</head>
<body class="fixed-sidebar full-height-layout gray-bg" th:classappend="${isMobile} ? 'canvas-menu'" style="overflow: hidden">
<div id="wrapper">
@ -199,6 +201,19 @@
<li><a data-toggle="tooltip" data-trigger="hover" data-placement="bottom" title="开发文档" href="http://doc.ruoyi.vip/ruoyi" target="_blank"><i class="fa fa-question-circle"></i> 文档</a></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" id="noticeDropdown">
<a href="javascript:void(0)" class="dropdown-toggle" style="position:relative;" data-hover="dropdown">
<i class="fa fa-bell-o"></i> 消息
<span id="noticeBadge" style="display:none;position:absolute;top:8px;right:-4px;background:#e74c3c;color:#fff;border-radius:50%;width:16px;height:16px;font-size:10px;line-height:16px;text-align:center;font-style:normal;"></span>
</a>
<ul class="dropdown-menu" id="noticeList" style="width:320px;padding:0;left:auto;right:0;">
<li style="padding:10px 16px;background:#f7f9fb;border-bottom:1px solid #eee;display:flex;align-items:center;justify-content:space-between;">
<span style="font-size:13px;font-weight:600;color:#333;">通知公告</span>
<a href="javascript:void(0)" onclick="markAllRead()" class="mark-all-read">全部已读</a>
</li>
<li id="noticeItems"><div style="padding:20px;text-align:center;color:#aaa;font-size:12px;"><i class="fa fa-spinner fa-spin"></i> 加载中...</div></li>
</ul>
</li>
<li class="dropdown user-menu">
<a href="javascript:void(0)" class="dropdown-toggle" data-hover="dropdown">
<img th:src="(${#strings.isEmpty(user.avatar)}) ? @{/img/profile.jpg} : @{${user.avatar}}" th:onerror="this.src='img/profile.jpg'" class="user-image">
@ -392,7 +407,123 @@ $(function() {
});
}
$("[data-toggle='tooltip']").tooltip();
/* 页面加载时拉取公告 */
loadNoticeTop();
});
/* 加载顶部公告列表 */
function loadNoticeTop() {
$.ajax({
url: ctx + "system/notice/listTop",
type: "get",
dataType: "json",
success: function(res) {
if (res.code !== 0 || !res.data || res.data.length === 0) {
$("#noticeItems").html('<div style="padding:24px;text-align:center;color:#bbb;font-size:12px;"><i class=\"fa fa-inbox\"></i><br>暂无公告</div>');
return;
}
var list = res.data;
var unread = (res.unreadCount !== undefined && res.unreadCount !== null) ? res.unreadCount : list.length;
if (unread > 0) {
$("#noticeBadge").text(unread).show();
} else {
$("#noticeBadge").hide();
}
var html = '';
for (var i = 0; i < list.length; i++) {
var n = list[i];
var typeClass = n.noticeType === '1' ? 'warning' : 'success';
var typeLabel = n.noticeType === '1' ? '通知' : '公告';
var isRead = n.isRead === true || n.isRead === 'true' || n.read === true || n.read === 'true';
var aColor = isRead ? '#999' : '#333';
var titleColor = isRead ? 'color:#999;' : '';
var readAttr = isRead ? ' data-read="1"' : '';
var badgeHtml = isRead
? '<span style="flex-shrink:0;display:inline-block;padding:2px 6px;border-radius:3px;font-size:11px;font-weight:600;background:#e9ecef;color:#aaa;">' + typeLabel + '</span>'
: '<span class="badge badge-' + typeClass + '" style="flex-shrink:0;">' + typeLabel + '</span>';
html += '<li id="notice-item-' + n.noticeId + '"' + readAttr + ' style="border-bottom:1px solid #f5f5f5;">';
html += '<a href="javascript:void(0)" onclick="previewNotice(' + n.noticeId + ')" style="display:flex;align-items:center;padding:10px 16px;gap:10px;text-decoration:none;color:' + aColor + ';">';
html += badgeHtml;
html += '<span style="flex:1;font-size:12px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;' + titleColor + '" title="' + (n.noticeTitle||'') + '">' + (n.noticeTitle||'') + '</span>';
html += '<span style="font-size:11px;color:#bbb;flex-shrink:0;">' + (n.createTime ? n.createTime.substring(0,10) : '') + '</span>';
html += '</a></li>';
}
$("#noticeItems").html(html);
},
error: function() {
$("#noticeItems").html('<div style="padding:20px;text-align:center;color:#e74c3c;font-size:12px;">加载失败</div>');
}
});
}
/* 预览公告 */
function previewNotice(noticeId) {
var url = ctx + "system/notice/view/" + noticeId;
// 先立即更新样式并标记已读
var $item = $("#notice-item-" + noticeId);
$item.attr("data-read", "1");
$item.find("a").css("color", "#999");
$item.find("span").css("color", "#999");
var $badge = $item.find(".badge");
if ($badge.length) {
$badge.replaceWith('<span style="flex-shrink:0;display:inline-block;padding:2px 6px;border-radius:3px;font-size:11px;font-weight:600;background:#e9ecef;color:#aaa;">' + $badge.text() + '</span>');
}
// 用 data-read 属性计数未读
var unread = $("#noticeItems li:not([data-read])").length;
if (unread > 0) {
$("#noticeBadge").text(unread).show();
} else {
$("#noticeBadge").hide();
}
// 异步通知后端记录已读fire and forget
$.post(ctx + "system/notice/markRead", { noticeId: noticeId });
var _layerIdx;
_layerIdx = top.layer.open({
type: 2,
offset: 'r',
anim: 'slideLeft',
move: false,
title: false,
closeBtn: 0,
shade: [0.25, '#1a202c'],
shadeClose: true,
area: ['600px', '100%'],
content: url,
success: function(layero) {
// 去掉 layer 默认 padding让内容完全填满
layero.css({ 'border-radius': '0', 'border': 'none' });
layero.find('.layui-layer-content').css({ 'border-radius': '0' });
// 悬浮关闭按钮,紧贴面板左边缘外侧
var $btn = $('<button id="noticeCloseBtn" style="position:fixed;top:20px;right:608px;width:34px;height:34px;border-radius:50%;background:#fff;border:1px solid #e2e8f0;box-shadow:0 2px 10px rgba(0,0,0,0.12);cursor:pointer;font-size:13px;color:#718096;line-height:1;z-index:19891017;transition:all 0.18s;"><i class="fa fa-times"></i></button>');
$btn.hover(
function(){ $(this).css({'background':'#f7fafc','color':'#2d3748','border-color':'#cbd5e0'}); },
function(){ $(this).css({'background':'#fff','color':'#718096','border-color':'#e2e8f0'}); }
);
$btn.on('click', function(){ top.layer.close(_layerIdx); });
$('body', top.document).append($btn);
},
end: function() {
$('body', top.document).find('#noticeCloseBtn').remove();
}
});
}
function markAllRead() {
var ids = [];
$("#noticeItems li").each(function() {
var id = $(this).attr("id").replace("notice-item-", "");
if (id) ids.push(id);
$(this).attr("data-read", "1");
$(this).find("a").css("color", "#999");
$(this).find("span").css("color", "#999");
var $b = $(this).find(".badge");
$b.replaceWith('<span style="flex-shrink:0;display:inline-block;padding:2px 6px;border-radius:3px;font-size:11px;font-weight:600;background:#e9ecef;color:#aaa;">' + $b.text() + '</span>');
});
$("#noticeBadge").hide();
if (ids.length > 0) {
$.post(ctx + "system/notice/markReadAll", { ids: ids.join(",") });
}
}
</script>
</body>
</html>

@ -1,27 +1,58 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('公告详细')" />
<th:block th:include="include :: header('公告详情')" />
<style>body.white-bg{background:#f5f6f8;font-family:'PingFang SC','Microsoft YaHei','Helvetica Neue',sans-serif}.notice-page{max-width:760px;margin:0 auto;padding:32px 24px 60px;animation:fadeUp .35s ease both}@keyframes fadeUp{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:translateY(0)}}.notice-type-tag{display:inline-flex;align-items:center;gap:5px;padding:3px 12px;border-radius:2px;font-size:11px;font-weight:700;letter-spacing:1px;text-transform:uppercase;margin-bottom:14px}.type-notify{background:#fff8e6;color:#b7791f;border-left:3px solid #d97706}.type-announce{background:#e8f5e9;color:#276749;border-left:3px solid #38a169}.notice-title{font-size:22px;font-weight:700;color:#1a202c;line-height:1.45;margin:0 0 16px;letter-spacing:-0.2px}.notice-meta{display:flex;align-items:center;flex-wrap:wrap;gap:16px;padding:12px 0;border-top:1px solid #e9ecef;border-bottom:1px solid #e9ecef;margin-bottom:28px}.meta-item{display:flex;align-items:center;gap:5px;font-size:12px;color:#718096}.meta-item i{font-size:12px;color:#a0aec0}.status-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:4px}.status-ok{background:#38a169}.status-off{background:#e53e3e}.notice-divider{display:flex;align-items:center;gap:12px;margin-bottom:24px}.notice-divider::before,.notice-divider::after{content:'';flex:1;height:1px;background:linear-gradient(to right,transparent,#dee2e6,transparent)}.notice-divider-dot{width:6px;height:6px;border-radius:50%;background:#cbd5e0}.notice-body{background:#fff;border-radius:6px;padding:28px 32px;box-shadow:0 1px 4px rgba(0,0,0,0.06),0 0 0 1px rgba(0,0,0,0.04);min-height:160px}.notice-content{font-size:14px;line-height:1.85;color:#2d3748;word-break:break-word}.notice-content p{margin:0 0 1em}.notice-content h1,.notice-content h2,.notice-content h3{font-weight:700;color:#1a202c;margin:1.4em 0 .6em}.notice-content h1{font-size:18px}.notice-content h2{font-size:16px}.notice-content h3{font-size:14px}.notice-content a{color:#3182ce;text-decoration:underline}.notice-content a:hover{color:#2b6cb0}.notice-content img{max-width:100%;border-radius:4px;margin:8px 0}.notice-content ul,.notice-content ol{padding-left:20px;margin:0 0 1em}.notice-content li{margin-bottom:4px}.notice-content blockquote{border-left:3px solid #cbd5e0;margin:1em 0;padding:6px 16px;color:#718096;background:#f7fafc}.notice-content table{border-collapse:collapse;width:100%;margin:1em 0;font-size:13px}.notice-content table th,.notice-content table td{border:1px solid #e2e8f0;padding:7px 12px}.notice-content table th{background:#f7fafc;font-weight:600}.notice-empty{text-align:center;padding:40px 0;color:#a0aec0;font-size:13px}.notice-empty i{font-size:28px;display:block;margin-bottom:10px}</style>
</head>
<body>
<div class="main-content">
<div class="row">
<div class="col-sm-12">
<div class="mail-box-header">
<div class="mail-tools tooltip-demo m-t-md">
<h3 style="text-align: center">[[${notice.noticeTitle}]]</h3>
<h5>
<span class="pull-right font-noraml">发送时间:[[${#dates.format(notice.createTime, 'yyyy-MM-dd HH:mm:ss')}]] </span>
<span class="font-noraml">发件人: </span>[[${notice.createBy}]]
</h5>
</div>
</div>
<div class="mail-box">
<div class="mail-body" th:utext="${notice.noticeContent}"></div>
</div>
</div>
<body class="white-bg">
<div class="notice-page">
<div th:switch="${notice.noticeType}">
<span th:case="'1'" class="notice-type-tag type-notify">
<i class="fa fa-bell-o"></i> 通知
</span>
<span th:case="'2'" class="notice-type-tag type-announce">
<i class="fa fa-bullhorn"></i> 公告
</span>
<span th:case="*" class="notice-type-tag type-notify">
<i class="fa fa-file-text-o"></i> 消息
</span>
</div>
<h1 class="notice-title" th:text="${notice.noticeTitle}">公告标题</h1>
<div class="notice-meta">
<span class="meta-item">
<i class="fa fa-user-o"></i>
<span th:text="${notice.createBy}">发布人</span>
</span>
<span class="meta-item">
<i class="fa fa-clock-o"></i>
<span th:text="${#dates.format(notice.createTime, 'yyyy-MM-dd HH:mm:ss')}">发布时间</span>
</span>
<span class="meta-item">
<span th:class="${notice.status == '0'} ? 'status-dot status-ok' : 'status-dot status-off'"></span>
<span th:text="${notice.status == '0'} ? '正常' : '已关闭'">状态</span>
</span>
</div>
<div class="notice-divider">
<span class="notice-divider-dot"></span>
<span class="notice-divider-dot"></span>
<span class="notice-divider-dot"></span>
</div>
<div class="notice-body">
<div class="notice-content"
th:if="${not #strings.isEmpty(notice.noticeContent)}"
th:utext="${notice.noticeContent}">
</div>
<div class="notice-empty" th:if="${#strings.isEmpty(notice.noticeContent)}">
<i class="fa fa-file-o"></i>暂无内容
</div>
</div>
<th:block th:include="include :: footer" />
</div>
<th:block th:include="include :: footer" />
</body>
</html>

@ -4,6 +4,7 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.xss.Xss;
@ -31,6 +32,10 @@ public class SysNotice extends BaseEntity
/** 公告状态0正常 1关闭 */
private String status;
/** 是否已读 */
@JsonProperty("isRead")
private boolean isRead;
public Long getNoticeId()
{
return noticeId;
@ -84,6 +89,16 @@ public class SysNotice extends BaseEntity
return status;
}
public boolean getIsRead()
{
return isRead;
}
public void setIsRead(boolean isRead)
{
this.isRead = isRead;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)

@ -0,0 +1,76 @@
package com.ruoyi.system.domain;
import java.util.Date;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* sys_notice_read
*
* @author ruoyi
*/
public class SysNoticeRead
{
/** 主键 */
private Long readId;
/** 公告ID */
private Long noticeId;
/** 用户ID */
private Long userId;
/** 阅读时间 */
private Date readTime;
public Long getReadId()
{
return readId;
}
public void setReadId(Long readId)
{
this.readId = readId;
}
public Long getNoticeId()
{
return noticeId;
}
public void setNoticeId(Long noticeId)
{
this.noticeId = noticeId;
}
public Long getUserId()
{
return userId;
}
public void setUserId(Long userId)
{
this.userId = userId;
}
public Date getReadTime()
{
return readTime;
}
public void setReadTime(Date readTime)
{
this.readTime = readTime;
}
@Override
public String toString()
{
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("readId", getReadId())
.append("noticeId", getNoticeId())
.append("userId", getUserId())
.append("readTime", getReadTime())
.toString();
}
}

@ -0,0 +1,65 @@
package com.ruoyi.system.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.system.domain.SysNoticeRead;
import com.ruoyi.system.domain.SysNotice;
/**
*
*
* @author ruoyi
*/
public interface SysNoticeReadMapper
{
/**
*
*
* @param noticeRead
* @return
*/
public int insertNoticeRead(SysNoticeRead noticeRead);
/**
*
*
* @param userId ID
* @return
*/
public int selectUnreadCount(@Param("userId") Long userId);
/**
*
*
* @param noticeId ID
* @param userId ID
* @return 0 1
*/
public int selectIsRead(@Param("noticeId") Long noticeId, @Param("userId") Long userId);
/**
*
*
* @param userId ID
* @param noticeIds ID
* @return
*/
public int insertNoticeReadBatch(@Param("userId") Long userId, @Param("noticeIds") Long[] noticeIds);
/**
* SQL
*
* @param userId ID
* @param limit
* @return isRead
*/
public List<SysNotice> selectNoticeListWithReadStatus(@Param("userId") Long userId, @Param("limit") int limit);
/**
*
*
* @param noticeIds ID
* @return
*/
public int deleteByNoticeIds(@Param("noticeIds") String[] noticeIds);
}

@ -0,0 +1,52 @@
package com.ruoyi.system.service;
import java.util.List;
import com.ruoyi.system.domain.SysNotice;
/**
*
*
* @author ruoyi
*/
public interface ISysNoticeReadService
{
/**
*
*
* @param noticeId ID
* @param userId ID
*/
public void markRead(Long noticeId, Long userId);
/**
*
*
* @param userId ID
* @return
*/
public int selectUnreadCount(Long userId);
/**
*
*
* @param userId ID
* @param limit
* @return isRead
*/
public List<SysNotice> selectNoticeListWithReadStatus(Long userId, int limit);
/**
*
*
* @param userId ID
* @param noticeIds ID
*/
public void markReadBatch(Long userId, Long[] noticeIds);
/**
*
*
* @param ids ID
*/
public void deleteByNoticeIds(String ids);
}

@ -0,0 +1,74 @@
package com.ruoyi.system.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.system.domain.SysNoticeRead;
import com.ruoyi.system.domain.SysNotice;
import com.ruoyi.system.mapper.SysNoticeReadMapper;
import com.ruoyi.system.service.ISysNoticeReadService;
/**
*
*
* @author ruoyi
*/
@Service
public class SysNoticeReadServiceImpl implements ISysNoticeReadService
{
@Autowired
private SysNoticeReadMapper noticeReadMapper;
/**
*
*/
@Override
public void markRead(Long noticeId, Long userId)
{
SysNoticeRead record = new SysNoticeRead();
record.setNoticeId(noticeId);
record.setUserId(userId);
noticeReadMapper.insertNoticeRead(record);
}
/**
*
*/
@Override
public int selectUnreadCount(Long userId)
{
return noticeReadMapper.selectUnreadCount(userId);
}
/**
*
*/
@Override
public List<SysNotice> selectNoticeListWithReadStatus(Long userId, int limit)
{
return noticeReadMapper.selectNoticeListWithReadStatus(userId, limit);
}
/**
*
*/
@Override
public void markReadBatch(Long userId, Long[] noticeIds)
{
if (noticeIds == null || noticeIds.length == 0)
{
return;
}
noticeReadMapper.insertNoticeReadBatch(userId, noticeIds);
}
/**
*
*/
@Override
public void deleteByNoticeIds(String ids)
{
noticeReadMapper.deleteByNoticeIds(Convert.toStrArray(ids));
}
}

@ -40,6 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND create_by like concat('%', #{createBy}, '%')
</if>
</where>
order by notice_id desc
</select>
<insert id="insertNotice" parameterType="SysNotice">

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.SysNoticeReadMapper">
<resultMap type="SysNoticeRead" id="SysNoticeReadResult">
<id property="readId" column="read_id" />
<result property="noticeId" column="notice_id" />
<result property="userId" column="user_id" />
<result property="readTime" column="read_time" />
</resultMap>
<!-- 新增已读记录 -->
<insert id="insertNoticeRead" parameterType="SysNoticeRead">
insert ignore into sys_notice_read (notice_id, user_id, read_time)
values (#{noticeId}, #{userId}, sysdate())
</insert>
<!-- 查询未读数量:正常状态公告 减去 当前用户已读数 -->
<select id="selectUnreadCount" resultType="int">
select count(*) from sys_notice n
where n.status = '0' and not exists (select 1 from sys_notice_read r where r.notice_id = n.notice_id and r.user_id = #{userId})
</select>
<!-- 查询是否已读 -->
<select id="selectIsRead" resultType="int">
select count(*) from sys_notice_read where notice_id = #{noticeId} and user_id = #{userId}
</select>
<!-- 查询带已读状态的公告列表直接在SQL中限制条数 -->
<select id="selectNoticeListWithReadStatus" resultType="SysNotice">
select
n.notice_id as noticeId,
n.notice_title as noticeTitle,
n.notice_type as noticeType,
n.status,
n.create_by as createBy,
n.create_time as createTime,
case when r.notice_id is not null then true else false end as isRead
from sys_notice n
left join sys_notice_read r
on r.notice_id = n.notice_id and r.user_id = #{userId}
where n.status = '0'
order by n.notice_id desc
limit #{limit}
</select>
<!-- 批量标记已读 -->
<insert id="insertNoticeReadBatch">
insert ignore into sys_notice_read (notice_id, user_id, read_time)
values
<foreach collection="noticeIds" item="noticeId" separator=",">
(#{noticeId}, #{userId}, sysdate())
</foreach>
</insert>
<!-- 删除公告时清理已读记录 -->
<delete id="deleteByNoticeIds">
delete from sys_notice_read where notice_id in
<foreach collection="noticeIds" item="noticeId" open="(" separator="," close=")">
#{noticeId}
</foreach>
</delete>
</mapper>
Loading…
Cancel
Save