首页新增通知公告消息提醒
This commit is contained in:
parent
589e290e6c
commit
3975b2af17
|
|
@ -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 jakarta.validation.constraints.NotBlank;
|
|||
import jakarta.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>
|
||||
|
|
@ -665,7 +665,21 @@ insert into sys_notice values('3', '若依开源框架介绍', '1', '<p><span st
|
|||
|
||||
|
||||
-- ----------------------------
|
||||
-- 19、代码生成业务表
|
||||
-- 19、公告已读记录表
|
||||
-- ----------------------------
|
||||
drop table if exists sys_notice_read;
|
||||
create table sys_notice_read (
|
||||
read_id bigint(20) not null auto_increment comment '已读主键',
|
||||
notice_id int(4) not null comment '公告id',
|
||||
user_id bigint(20) not null comment '用户id',
|
||||
read_time datetime not null comment '阅读时间',
|
||||
primary key (read_id),
|
||||
unique key uk_user_notice (user_id, notice_id) comment '同一用户同一公告只记录一次'
|
||||
) engine=innodb auto_increment=1 comment='公告已读记录表';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 20、代码生成业务表
|
||||
-- ----------------------------
|
||||
drop table if exists gen_table;
|
||||
create table gen_table (
|
||||
|
|
@ -695,7 +709,7 @@ create table gen_table (
|
|||
|
||||
|
||||
-- ----------------------------
|
||||
-- 20、代码生成业务表字段
|
||||
-- 21、代码生成业务表字段
|
||||
-- ----------------------------
|
||||
drop table if exists gen_table_column;
|
||||
create table gen_table_column (
|
||||
Loading…
Reference in New Issue
Block a user