From 0017cc88620501d55cd8bf11c802ad435525e138 Mon Sep 17 00:00:00 2001 From: RuoYi Date: Sun, 22 Mar 2026 00:15:34 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E9=A1=B5=E6=96=B0=E5=A2=9E=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E5=85=AC=E5=91=8A=E6=B6=88=E6=81=AF=E6=8F=90=E9=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SysNoticeController.java | 47 ++++ .../com/ruoyi/system/domain/SysNotice.java | 15 ++ .../ruoyi/system/domain/SysNoticeRead.java | 76 ++++++ .../system/mapper/SysNoticeReadMapper.java | 65 +++++ .../system/service/ISysNoticeReadService.java | 52 ++++ .../impl/SysNoticeReadServiceImpl.java | 73 ++++++ .../mapper/system/SysNoticeMapper.xml | 1 + .../mapper/system/SysNoticeReadMapper.xml | 66 +++++ ruoyi-ui/src/api/system/notice.js | 28 ++- ruoyi-ui/src/assets/icons/svg/bell.svg | 1 + .../layout/components/HeaderNotice/index.vue | 229 ++++++++++++++++++ ruoyi-ui/src/layout/components/Navbar.vue | 9 +- sql/ry_20260321.sql | 19 +- 13 files changed, 676 insertions(+), 5 deletions(-) create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNoticeRead.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeReadMapper.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeReadService.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeReadServiceImpl.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeReadMapper.xml create mode 100644 ruoyi-ui/src/assets/icons/svg/bell.svg create mode 100644 ruoyi-ui/src/layout/components/HeaderNotice/index.vue diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysNoticeController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysNoticeController.java index eb9061129..c0f6635e8 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysNoticeController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysNoticeController.java @@ -10,7 +10,9 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.web.controller.BaseController; import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.page.TableDataInfo; @@ -19,6 +21,7 @@ import com.ruoyi.common.log.enums.BusinessType; import com.ruoyi.common.security.annotation.RequiresPermissions; import com.ruoyi.common.security.utils.SecurityUtils; 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; + /** * 获取通知公告列表 */ @@ -79,6 +85,46 @@ public class SysNoticeController extends BaseController return toAjax(noticeService.updateNotice(notice)); } + /** + * 首页顶部公告列表(返回全部正常公告,带当前用户已读标记,最多5条) + */ + @GetMapping("/listTop") + @ResponseBody + public AjaxResult listTop() + { + Long userId = SecurityUtils.getUserId(); + List 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 = SecurityUtils.getUserId(); + noticeReadService.markRead(noticeId, userId); + return success(); + } + + /** + * 批量标记已读 + */ + @PostMapping("/markReadAll") + @ResponseBody + public AjaxResult markReadAll(String ids) + { + Long userId = SecurityUtils.getUserId(); + Long[] noticeIds = Convert.toLongArray(ids); + noticeReadService.markReadBatch(userId, noticeIds); + return success(); + } + /** * 删除通知公告 */ @@ -87,6 +133,7 @@ public class SysNoticeController extends BaseController @DeleteMapping("/{noticeIds}") public AjaxResult remove(@PathVariable Long[] noticeIds) { + noticeReadService.deleteByNoticeIds(noticeIds); return toAjax(noticeService.deleteNoticeByIds(noticeIds)); } } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java index cb916201c..5b5ee4ea9 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java @@ -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.web.domain.BaseEntity; import com.ruoyi.common.core.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) diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNoticeRead.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNoticeRead.java new file mode 100644 index 000000000..6ebf88cd3 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNoticeRead.java @@ -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(); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeReadMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeReadMapper.java new file mode 100644 index 000000000..173e6cf30 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeReadMapper.java @@ -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 selectNoticeListWithReadStatus(@Param("userId") Long userId, @Param("limit") int limit); + + /** + * 公告删除时清理对应已读记录 + * + * @param noticeIds 公告ID数组 + * @return 结果 + */ + public int deleteByNoticeIds(@Param("noticeIds") Long[] noticeIds); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeReadService.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeReadService.java new file mode 100644 index 000000000..8b6902768 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeReadService.java @@ -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 selectNoticeListWithReadStatus(Long userId, int limit); + + /** + * 批量标记已读 + * + * @param userId 用户ID + * @param noticeIds 公告ID数组 + */ + public void markReadBatch(Long userId, Long[] noticeIds); + + /** + * 删除公告时清理对应已读记录 + * + * @param noticeIds 公告ID数组 + */ + public void deleteByNoticeIds(Long[] noticeIds); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeReadServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeReadServiceImpl.java new file mode 100644 index 000000000..7c6c00acd --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeReadServiceImpl.java @@ -0,0 +1,73 @@ +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.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 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(Long[] noticeIds) + { + noticeReadMapper.deleteByNoticeIds(noticeIds); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml index 6915a1482..24ef6a4fc 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml @@ -40,6 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND create_by like concat('%', #{createBy}, '%') + order by notice_id desc diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeReadMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeReadMapper.xml new file mode 100644 index 000000000..3af8092e9 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeReadMapper.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + insert ignore into sys_notice_read (notice_id, user_id, read_time) + values (#{noticeId}, #{userId}, sysdate()) + + + + + + + + + + + + + + insert ignore into sys_notice_read (notice_id, user_id, read_time) + values + + (#{noticeId}, #{userId}, sysdate()) + + + + + + delete from sys_notice_read where notice_id in + + #{noticeId} + + + + diff --git a/ruoyi-ui/src/api/system/notice.js b/ruoyi-ui/src/api/system/notice.js index 737fc169d..c620e4753 100644 --- a/ruoyi-ui/src/api/system/notice.js +++ b/ruoyi-ui/src/api/system/notice.js @@ -41,4 +41,30 @@ export function delNotice(noticeId) { url: '/system/notice/' + noticeId, method: 'delete' }) -} \ No newline at end of file +} + +// 首页顶部公告列表(带已读状态) +export function listNoticeTop() { + return request({ + url: '/system/notice/listTop', + method: 'get' + }) +} + +// 标记公告已读 +export function markNoticeRead(noticeId) { + return request({ + url: '/system/notice/markRead', + method: 'post', + params: { noticeId } + }) +} + +// 批量标记已读 +export function markNoticeReadAll(ids) { + return request({ + url: '/system/notice/markReadAll', + method: 'post', + params: { ids } + }) +} diff --git a/ruoyi-ui/src/assets/icons/svg/bell.svg b/ruoyi-ui/src/assets/icons/svg/bell.svg new file mode 100644 index 000000000..a7320a05b --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/bell.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-ui/src/layout/components/HeaderNotice/index.vue b/ruoyi-ui/src/layout/components/HeaderNotice/index.vue new file mode 100644 index 000000000..6ee927375 --- /dev/null +++ b/ruoyi-ui/src/layout/components/HeaderNotice/index.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/ruoyi-ui/src/layout/components/Navbar.vue b/ruoyi-ui/src/layout/components/Navbar.vue index 35cf72802..d0a78ea38 100644 --- a/ruoyi-ui/src/layout/components/Navbar.vue +++ b/ruoyi-ui/src/layout/components/Navbar.vue @@ -26,6 +26,10 @@ + + + + @@ -64,9 +68,9 @@ import SizeSelect from '@/components/SizeSelect' import Search from '@/components/HeaderSearch' import RuoYiGit from '@/components/RuoYi/Git' import RuoYiDoc from '@/components/RuoYi/Doc' +import HeaderNotice from './HeaderNotice' export default { - emits: ['setLayout'], components: { Breadcrumb, Logo, @@ -77,7 +81,8 @@ export default { SizeSelect, Search, RuoYiGit, - RuoYiDoc + RuoYiDoc, + HeaderNotice }, computed: { ...mapGetters([ diff --git a/sql/ry_20260321.sql b/sql/ry_20260321.sql index d8e952cbf..95469d81e 100644 --- a/sql/ry_20260321.sql +++ b/sql/ry_20260321.sql @@ -639,10 +639,25 @@ create table sys_notice ( -- ---------------------------- insert into sys_notice values('1', '温馨提醒:2018-07-01 若依新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate(), '', null, '管理员'); insert into sys_notice values('2', '维护通知:2018-07-01 若依系统凌晨维护', '1', '维护内容', '0', 'admin', sysdate(), '', null, '管理员'); +insert into sys_notice values('3', '若依开源框架介绍', '1', '

项目介绍

RuoYi开源项目是为企业用户定制的后台脚手架框架,为企业打造的一站式解决方案,降低企业开发成本,提升开发效率。主要包括用户管理、角色管理、部门管理、菜单管理、参数管理、字典管理、岗位管理、定时任务服务监控、登录日志、操作日志、代码生成等功能。其中,还支持多数据源、数据权限、国际化、Redis缓存、Docker部署、滑动验证码、第三方认证登录、分布式事务、分布式文件存储、分库分表处理等技术特点。


官网及演示

若依官网地址: http://ruoyi.vip

若依文档地址: http://doc.ruoyi.vip

演示地址【不分离版】: http://demo.ruoyi.vip

演示地址【分离版本】: http://vue.ruoyi.vip

演示地址【微服务版】: http://cloud.ruoyi.vip

演示地址【移动端版】: http://h5.ruoyi.vip


', '0', 'admin', sysdate(), '', null, '管理员'); -- ---------------------------- --- 18、代码生成业务表 +-- 18、公告已读记录表 +-- ---------------------------- +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='公告已读记录表'; + + +-- ---------------------------- +-- 19、代码生成业务表 -- ---------------------------- drop table if exists gen_table; create table gen_table ( @@ -672,7 +687,7 @@ create table gen_table ( -- ---------------------------- --- 19、代码生成业务表字段 +-- 20、代码生成业务表字段 -- ---------------------------- drop table if exists gen_table_column; create table gen_table_column (