ソースを参照

Add 增加通知推送模块,优化已有通知代码

Yue 3 ヶ月 前
コミット
11a45f2e2e
20 ファイル変更792 行追加78 行削除
  1. 22 0
      SERVER/ChickenFarmV3/vb-common/vb-common-core/src/main/java/cn/vber/common/core/service/NoticeService.java
  2. 36 0
      SERVER/ChickenFarmV3/vb-common/vb-common-notice/pom.xml
  3. 73 0
      SERVER/ChickenFarmV3/vb-common/vb-common-notice/src/main/java/cn/vber/common/notice/core/NoticePusher.java
  4. 37 0
      SERVER/ChickenFarmV3/vb-common/vb-common-notice/src/main/java/cn/vber/common/notice/dto/NoticeMessageDetailDto.java
  5. 42 0
      SERVER/ChickenFarmV3/vb-common/vb-common-notice/src/main/java/cn/vber/common/notice/dto/NoticeMessageDto.java
  6. 93 0
      SERVER/ChickenFarmV3/vb-common/vb-common-notice/src/main/java/cn/vber/common/notice/vo/NoticeMessageVo.java
  7. 4 0
      SERVER/ChickenFarmV3/vb-modules/vb-system/pom.xml
  8. 14 1
      SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/java/cn/vber/system/controller/system/SysNoticeController.java
  9. 4 42
      SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/java/cn/vber/system/domain/vo/SysMessageVo.java
  10. 4 0
      SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/java/cn/vber/system/domain/vo/SysNoticeVo.java
  11. 19 2
      SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/java/cn/vber/system/mapper/SysNoticeMapper.java
  12. 4 0
      SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/java/cn/vber/system/service/ISysNoticeService.java
  13. 141 14
      SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/java/cn/vber/system/service/impl/SysNoticeServiceImpl.java
  14. 31 1
      SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/resources/mapper/system/SysNoticeMapper.xml
  15. 13 0
      UI/VB.VUE/src/api/system/_notice.ts
  16. 14 5
      UI/VB.VUE/src/layouts/main/header/navbar/NotificationsMenu.vue
  17. 11 1
      UI/VB.VUE/src/layouts/main/header/navbar/UserAccountMenu.vue
  18. 58 7
      UI/VB.VUE/src/router/_staticRouter.ts
  19. 46 5
      UI/VB.VUE/src/stores/_notice.ts
  20. 126 0
      UI/VB.VUE/src/views/my/notice.vue

+ 22 - 0
SERVER/ChickenFarmV3/vb-common/vb-common-core/src/main/java/cn/vber/common/core/service/NoticeService.java

@@ -0,0 +1,22 @@
+package cn.vber.common.core.service;
+
+import java.util.List;
+
+/**
+ * 通用 消息通知服务
+ *
+ * @author Iwb
+ */
+public interface NoticeService {
+    /**
+     * 保存消息通知
+     *
+     * @param noticeTitle   通知标题
+     * @param noticeType    通知类型
+     * @param noticeContent 通知内容
+     * @param userIds       接收用户ID列表
+     * @return 是否保存成功
+     */
+    Long savePushNotice(String noticeTitle, String noticeType, String noticeContent, List<Long> userIds);
+
+}

+ 36 - 0
SERVER/ChickenFarmV3/vb-common/vb-common-notice/pom.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.vap</groupId>
+        <artifactId>vb-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>vb-common-notice</artifactId>
+    <name>${project.artifactId}</name>
+    <description>消息通知模块</description>
+    <dependencies>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-commons</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.vap</groupId>
+            <artifactId>vb-common-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.vap</groupId>
+            <artifactId>vb-common-sse</artifactId>
+        </dependency>
+    </dependencies>
+
+
+</project>

+ 73 - 0
SERVER/ChickenFarmV3/vb-common/vb-common-notice/src/main/java/cn/vber/common/notice/core/NoticePusher.java

@@ -0,0 +1,73 @@
+package cn.vber.common.notice.core;
+
+import cn.vber.common.core.service.NoticeService;
+import cn.vber.common.core.utils.ObjectUtils;
+import cn.vber.common.core.utils.SpringUtils;
+import cn.vber.common.notice.vo.NoticeMessageVo;
+import cn.vber.common.notice.dto.NoticeMessageDto;
+import cn.vber.common.sse.dto.SseMessageDto;
+import cn.vber.common.sse.utils.SseMessageUtils;
+
+import java.util.List;
+
+public class NoticePusher {
+
+   /**
+    * 推送消息
+    */
+   public static void push(String title,String  message , String type, List<Long> userIds) {
+       if (ObjectUtils.isEmpty(userIds)) {
+           return;
+       }
+       NoticeMessageDto noticeMessageDto = new NoticeMessageDto();
+       noticeMessageDto.setTitle(title);
+       noticeMessageDto.setType(type);
+       noticeMessageDto.setContent(message);
+       push(noticeMessageDto,userIds);
+   }
+
+    /**
+     * 推送消息
+     */
+    public static void push(NoticeMessageDto message, List<Long> userIds) {
+        if (ObjectUtils.isEmpty(userIds)) {
+            return;
+        }
+        NoticeService noticeService = SpringUtils.getBean(NoticeService.class);
+        // ScheduledExecutorService scheduledExecutorService=SpringUtils.getBean(ScheduledExecutorService.class);
+        Long id =  noticeService.savePushNotice(message.getTitle(), message.getType(), message.getContent(), userIds);
+        if (id>0) {
+           SseMessageDto dto = new SseMessageDto();
+           dto.setUserIds(userIds);
+           if(message.getDetail()!=null){
+               dto.setMessage(new NoticeMessageVo().buildMessage(id,message,message.getDetail()));
+           }else{
+               dto.setMessage(new NoticeMessageVo().buildMessage(id,message,message.getContent()));
+           }
+           SseMessageUtils.publishMessage(dto);
+        }
+    }
+
+
+    /**
+     * 推送所有用户
+     */
+    public static void pushAll(String title,String  message , String type) {
+        NoticeMessageDto noticeMessageDto = new NoticeMessageDto();
+        noticeMessageDto.setTitle(title);
+        noticeMessageDto.setType(type);
+        noticeMessageDto.setContent(message);
+        pushAll(noticeMessageDto);
+    }
+
+    /**
+     * 推送所有用户
+     */
+    public static void pushAll(NoticeMessageDto message) {
+        NoticeService noticeService = SpringUtils.getBean(NoticeService.class);
+        Long id =  noticeService.savePushNotice(message.getTitle(), message.getType(), message.getContent(), null);
+        if (id>0) {
+            SseMessageUtils.publishAll(new NoticeMessageVo().buildMessage(id,message,message.getContent()));
+        }
+    }
+}

+ 37 - 0
SERVER/ChickenFarmV3/vb-common/vb-common-notice/src/main/java/cn/vber/common/notice/dto/NoticeMessageDetailDto.java

@@ -0,0 +1,37 @@
+package cn.vber.common.notice.dto;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 消息通知详情
+ *
+ * @author Iwb
+ */
+@Data
+public class NoticeMessageDetailDto implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 弹出通知类型
+     */
+    private String alertType;
+
+    /**
+     * 业务类型
+     */
+    private String businessType;
+
+    /**
+     * 业务数据
+     */
+    private Object data;
+
+    /**
+     * 消息内容
+     */
+    private String content;
+}

+ 42 - 0
SERVER/ChickenFarmV3/vb-common/vb-common-notice/src/main/java/cn/vber/common/notice/dto/NoticeMessageDto.java

@@ -0,0 +1,42 @@
+package cn.vber.common.notice.dto;
+
+import cn.vber.common.json.utils.JsonUtils;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 消息通知实体类
+ *
+ * @author Iwb
+ */
+@Data
+public class NoticeMessageDto implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 推送消息标题
+     */
+    private String title;
+    /**
+     * 推送消息类型  1:通知 2:公告
+     */
+    private String type="1";
+    /**
+     * 推送消息内容
+     */
+    private String  content;
+
+     /**
+      * 推送消息详细内容
+      */
+    private NoticeMessageDetailDto detail;
+    public void setDetail(NoticeMessageDetailDto detail) {
+        this.detail = detail;
+        this.content = JsonUtils.toJsonString(detail);
+    }
+}
+

+ 93 - 0
SERVER/ChickenFarmV3/vb-common/vb-common-notice/src/main/java/cn/vber/common/notice/vo/NoticeMessageVo.java

@@ -0,0 +1,93 @@
+package cn.vber.common.notice.vo;
+
+import cn.vber.common.json.utils.JsonUtils;
+import cn.vber.common.notice.dto.NoticeMessageDetailDto;
+import cn.vber.common.notice.dto.NoticeMessageDto;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date; /**
+ * 消息
+ *
+ * @author Yue
+ */
+@Data
+public class NoticeMessageVo implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 消息ID
+     */
+    private String messageId;
+
+    /**
+     * 消息标题
+     */
+    private String messageTitle;
+
+    /**
+     * 消息内容
+     */
+    private String messageContent;
+
+    /**
+     * 消息类型
+     */
+    private String noticeType;
+
+    /**
+     * 弹出通知类型
+     */
+    private String alertType;
+
+    /**
+     * 业务类型
+     */
+    private String businessType;
+
+    /**
+     * 业务数据
+     */
+    private Object data;
+
+    /**
+     * 发布时间
+     */
+    private Date messageTime;
+    public String buildMessage() {
+        return JsonUtils.toJsonString(this);
+    }
+    public String buildMessage(Long id, NoticeMessageDto message, NoticeMessageDetailDto detail) {
+        this.messageId = String.valueOf(id);
+        this.messageTitle = message.getTitle();
+        this.messageContent = detail.getContent();
+        this.noticeType = message.getType().toString();
+        this.alertType = detail.getAlertType();
+        this.businessType = detail.getBusinessType();
+        this.data = detail.getData();
+        this.messageTime = new Date();
+        return buildMessage();
+    }
+
+    public String buildMessage(Long id,NoticeMessageDto message,String messageContent) {
+        this.messageId = String.valueOf(id);
+        this.messageTitle = message.getTitle();
+        this.messageContent = messageContent;
+        this.noticeType = message.getType().toString();
+        this.messageTime = new Date();
+        return buildMessage();
+    }
+
+    public String buildMessage(Long id,String title,String messageContent,String noticeType) {
+        this.messageId = String.valueOf(id);
+        this.messageTitle = title;
+        this.messageContent = messageContent;
+        this.noticeType = noticeType;
+        this.messageTime = new Date();
+        return buildMessage();
+    }
+
+
+}

+ 4 - 0
SERVER/ChickenFarmV3/vb-modules/vb-system/pom.xml

@@ -97,6 +97,10 @@
             <groupId>com.vap</groupId>
             <artifactId>vb-common-sse</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.vap</groupId>
+            <artifactId>vb-common-notice</artifactId>
+        </dependency>
 
     </dependencies>
 

+ 14 - 1
SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/java/cn/vber/system/controller/system/SysNoticeController.java

@@ -50,7 +50,6 @@ public class SysNoticeController extends BaseController {
      *
      * @param noticeId 公告ID
      */
-    @SaCheckPermission("system:notice:query")
     @GetMapping(value = "/{noticeId}")
     public R<SysNoticeVo> getInfo(@PathVariable Long noticeId) {
         return R.ok(noticeService.selectNoticeById(noticeId));
@@ -108,6 +107,20 @@ public class SysNoticeController extends BaseController {
         return toAjax(noticeService.read(LoginHelper.getUserId(), messageId, messageType));
     }
 
+    /**
+     * 阅读所有公告
+     */
+    @Log(title = "全部已读", businessType = BusinessType.OTHER)
+    @PostMapping("/readAll")
+    public R<Void> readAll() {
+        return toAjax(noticeService.readAll(LoginHelper.getUserId()));
+    }
+
+    @GetMapping("/listMy")
+    public TableDataInfo<SysNoticeVo> listMy(SysNoticeBo notice, PageQuery pageQuery) {
+        return noticeService.selectPageMyNoticeList(notice, pageQuery);
+    }
+
     /**
      * 获取前${limit}条未读消息
      *

+ 4 - 42
SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/java/cn/vber/system/domain/vo/SysMessageVo.java

@@ -1,6 +1,8 @@
 package cn.vber.system.domain.vo;
 
+import cn.vber.common.notice.vo.NoticeMessageVo;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 
 import java.io.Serial;
 import java.io.Serializable;
@@ -11,49 +13,9 @@ import java.util.Date;
  *
  * @author Yue
  */
+@EqualsAndHashCode(callSuper = true)
 @Data
-public class SysMessageVo implements Serializable {
-    @Serial
-    private static final long serialVersionUID = 1L;
+public class SysMessageVo extends NoticeMessageVo {
 
 
-    /**
-     * 消息ID
-     */
-    private String messageId;
-
-    /**
-     * 消息类型
-     */
-    private String messageType;
-
-    /**
-     * 弹出通知类型
-     */
-    private String alertType;
-
-    /**
-     * 消息标题
-     */
-    private String messageTitle;
-
-    /**
-     * 消息内容
-     */
-    private String messageContent;
-
-    /**
-     * 发布时间
-     */
-    private Date messageTime;
-
-    /**
-     * 消息跳转路由名称
-     */
-    private String routerPath;
-
-    /**
-     * 消息跳转参数
-     */
-    private Object params;
 }

+ 4 - 0
SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/java/cn/vber/system/domain/vo/SysNoticeVo.java

@@ -69,4 +69,8 @@ public class SysNoticeVo implements Serializable {
      */
     private Date createTime;
 
+    /**
+     * 阅读状态(0未读 1已读)
+     */
+    private String readStatus;
 }

+ 19 - 2
SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/java/cn/vber/system/mapper/SysNoticeMapper.java

@@ -2,7 +2,15 @@ package cn.vber.system.mapper;
 
 import cn.vber.common.mybatis.core.mapper.BaseMapperPlus;
 import cn.vber.system.domain.SysNotice;
+import cn.vber.system.domain.SysUser;
 import cn.vber.system.domain.vo.SysNoticeVo;
+import cn.vber.system.domain.vo.SysUserVo;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import jdk.jfr.Registered;
+import org.apache.ibatis.annotations.Param;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import org.springframework.stereotype.Repository;
 
 /**
@@ -12,5 +20,14 @@ import org.springframework.stereotype.Repository;
  */
 @Repository
 public interface SysNoticeMapper extends BaseMapperPlus<SysNotice, SysNoticeVo> {
-
-}
+    
+    /**
+     * 分页查询我的通知公告列表,包含阅读状态
+     * 
+     * @param page 分页参数
+     * @param wrapper 查询条件
+     * @param userId 用户ID
+     * @return 分页结果
+     */
+    Page<SysNoticeVo> selectMyNoticePage(@Param("page") Page<SysNoticeVo>  page, @Param(Constants.WRAPPER) Wrapper<SysNotice> wrapper, @Param("userId") Long userId);
+}

+ 4 - 0
SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/java/cn/vber/system/service/ISysNoticeService.java

@@ -89,4 +89,8 @@ public interface ISysNoticeService {
      * @return 结果
      */
     List<SysMessageVo> getUserUnreadMessage(Long userId);
+
+    TableDataInfo<SysNoticeVo> selectPageMyNoticeList(SysNoticeBo notice, PageQuery pageQuery);
+
+    int readAll(Long userId);
 }

+ 141 - 14
SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/java/cn/vber/system/service/impl/SysNoticeServiceImpl.java

@@ -1,5 +1,8 @@
 package cn.vber.system.service.impl;
 
+import cn.vber.common.core.service.NoticeService;
+import cn.vber.common.notice.dto.NoticeMessageDetailDto;
+import cn.vber.common.satoken.utils.LoginHelper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -26,6 +29,7 @@ import cn.vber.system.service.ISysNoticeService;
 import lombok.RequiredArgsConstructor;
 import org.jetbrains.annotations.NotNull;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.*;
 
@@ -36,9 +40,8 @@ import java.util.*;
  */
 @RequiredArgsConstructor
 @Service
-public class SysNoticeServiceImpl implements ISysNoticeService {
+public class SysNoticeServiceImpl implements ISysNoticeService, NoticeService {
 
-    private final static String MESSAGE_TYPE = "NOTICE";
     private final SysNoticeMapper baseMapper;
     private final SysNoticeStatusMapper noticeStatusMapper;
     private final SysUserMapper userMapper;
@@ -105,7 +108,7 @@ public class SysNoticeServiceImpl implements ISysNoticeService {
         int rows = baseMapper.insert(notice);
         if (rows > 0) {
             // 发送消息
-            SysMessageVo sysMessageVo = getSysMessageVo(notice);
+            SysMessageVo sysMessageVo = buildSysMessageVo(notice);
             String msg = JsonUtils.toJsonString(sysMessageVo);
             SseMessageUtils.publishAll(msg);
         }
@@ -114,6 +117,22 @@ public class SysNoticeServiceImpl implements ISysNoticeService {
 
     }
 
+    /**
+     * 构建系统消息
+     *
+     * @param notice 公告
+     * @return 系统消息
+     */
+    private SysMessageVo buildSysMessageVo(SysNotice notice) {
+        SysMessageVo sysMessageVo = new SysMessageVo();
+        sysMessageVo.setMessageId(UUID.randomUUID().toString());
+        sysMessageVo.setNoticeType(notice.getNoticeType());
+        sysMessageVo.setAlertType("success");
+        sysMessageVo.setMessageTitle(notice.getNoticeTitle());
+        sysMessageVo.setMessageContent(notice.getNoticeContent());
+        sysMessageVo.setMessageTime(notice.getCreateTime());
+        return sysMessageVo;
+    }
 
     /**
      * 修改公告
@@ -160,8 +179,7 @@ public class SysNoticeServiceImpl implements ISysNoticeService {
     public int read(Long userId, String messageId, String messageType) {
         SysNoticeStatus sysNoticeStatus = noticeStatusMapper.selectOne(new LambdaQueryWrapper<SysNoticeStatus>()
                 .eq(SysNoticeStatus::getUserId, userId)
-                .eq(SysNoticeStatus::getMessageId, messageId)
-                .eq(SysNoticeStatus::getMessageType, messageType));
+                .eq(SysNoticeStatus::getMessageId, messageId));
         if (sysNoticeStatus == null) {
             sysNoticeStatus = new SysNoticeStatus();
             sysNoticeStatus.setMessageId(messageId);
@@ -190,7 +208,7 @@ public class SysNoticeServiceImpl implements ISysNoticeService {
 
         List<SysNotice> list = baseMapper.selectList(new LambdaQueryWrapper<SysNotice>()
                 .eq(SysNotice::getStatus, "0")
-                .apply("notice_id NOT IN (SELECT message_id FROM sys_notice_status WHERE message_type='" + MESSAGE_TYPE + "' AND user_id=" + userId + " AND status='1')")
+                .apply("notice_id NOT IN (SELECT message_id FROM sys_notice_status WHERE user_id=" + userId + " AND status='1')")
                 .orderByDesc(SysNotice::getCreateTime));
         List<SysMessageVo> msgList = new ArrayList<>();
         for (SysNotice notice : list) {
@@ -200,19 +218,128 @@ public class SysNoticeServiceImpl implements ISysNoticeService {
         return msgList;
     }
 
+    /**
+     * 查询我的公告列表
+     *
+     * @param notice    查询条件
+     * @param pageQuery 分页参数
+     * @return 公告分页列表
+     */
+    @Override
+    public TableDataInfo<SysNoticeVo> selectPageMyNoticeList(SysNoticeBo notice, PageQuery pageQuery) {
+        Long userId = LoginHelper.getUserId();
+        LambdaQueryWrapper<SysNotice> lqw = new LambdaQueryWrapper<SysNotice>()
+                .eq(SysNotice::getStatus, "0")
+                .orderByDesc(SysNotice::getCreateTime);
+        Page<SysNoticeVo> page = baseMapper.selectMyNoticePage(pageQuery.build(), lqw, userId);
+        
+        // 检查类型为2的公告是否都有对应的sys_notice_status记录,没有则插入默认记录
+        List<SysNotice> type2Notices = baseMapper.selectList(new LambdaQueryWrapper<SysNotice>()
+                .eq(SysNotice::getNoticeType, "2")
+                .eq(SysNotice::getStatus, "0"));
+        
+        if (!type2Notices.isEmpty()) {
+            for (SysNotice sysNotice : type2Notices) {
+                SysNoticeStatus status = noticeStatusMapper.selectOne(new LambdaQueryWrapper<SysNoticeStatus>()
+                        .eq(SysNoticeStatus::getMessageId, sysNotice.getNoticeId().toString())
+                        .eq(SysNoticeStatus::getUserId, userId));
+                
+                if (status == null) {
+                    // 插入默认记录
+                    SysNoticeStatus newStatus = new SysNoticeStatus();
+                    newStatus.setUserId(userId);
+                    newStatus.setMessageId(sysNotice.getNoticeId().toString());
+                    newStatus.setMessageType("2");
+                    newStatus.setStatus("0");
+                    noticeStatusMapper.insert(newStatus);
+                }
+            }
+        }
+        
+        return TableDataInfo.build(page);
+    }
+
     private @NotNull SysMessageVo getSysMessageVo(SysNotice notice) {
         SysMessageVo msg = new SysMessageVo();
         msg.setMessageId(notice.getNoticeId().toString());
         msg.setMessageTitle(notice.getNoticeTitle());
-        msg.setMessageContent(notice.getNoticeContent());
-        msg.setAlertType("success");
-        msg.setMessageType(MESSAGE_TYPE);
-        msg.setRouterPath(sysMessageConfig.getNoticeRouterPath());
+        msg.setNoticeType(notice.getNoticeType());
         msg.setMessageTime(notice.getCreateTime());
-        Map<String, String> params = new HashMap<>();
-        params.put("id", notice.getNoticeId().toString());
-        params.put("title", notice.getNoticeTitle());
-        msg.setParams(params);
+        NoticeMessageDetailDto detail = JsonUtils.parseObject(notice.getNoticeContent(), NoticeMessageDetailDto.class);
+        if(detail != null){
+          msg.setBusinessType(detail.getBusinessType());
+          msg.setData(detail.getData());
+          msg.setMessageContent(detail.getContent());
+          msg.setAlertType(detail.getAlertType());
+        }
         return msg;
     }
+
+    /**
+     * 阅读所有公告
+     *
+     * @param userId 用户Id
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int readAll(Long userId) {
+        int count = 0;
+        count += noticeStatusMapper.update(new LambdaUpdateWrapper<SysNoticeStatus>()
+                .eq(SysNoticeStatus::getUserId, userId)
+                .eq(SysNoticeStatus::getMessageType, "1")
+                .set(SysNoticeStatus::getStatus, "1"));
+        
+        // 查询所有的公告(这些在sys_notice_status中可能没有记录)
+        List<SysNotice> type2Notices = baseMapper.selectList(new LambdaQueryWrapper<SysNotice>()
+                .eq(SysNotice::getNoticeType, "2")
+                .eq(SysNotice::getStatus, "0"));
+        
+        // 为公告创建阅读记录或更新现有记录
+        for (SysNotice notice : type2Notices) {
+            SysNoticeStatus status = noticeStatusMapper.selectOne(new LambdaQueryWrapper<SysNoticeStatus>()
+                    .eq(SysNoticeStatus::getUserId, userId)
+                    .eq(SysNoticeStatus::getMessageId, notice.getNoticeId().toString()));
+            
+            if (status == null) {
+                // 如果没有记录,则插入新记录并设置为已读
+                SysNoticeStatus newStatus = new SysNoticeStatus();
+                newStatus.setUserId(userId);
+                newStatus.setMessageId(notice.getNoticeId().toString());
+                newStatus.setMessageType("2");
+                newStatus.setStatus("1"); // 设置为已读
+                noticeStatusMapper.insert(newStatus);
+                count++;
+            } else if (!"1".equals(status.getStatus())) {
+                // 如果有记录但未读,则更新为已读
+                count += noticeStatusMapper.update(new LambdaUpdateWrapper<SysNoticeStatus>()
+                        .eq(SysNoticeStatus::getUserId, userId)
+                        .eq(SysNoticeStatus::getMessageId, notice.getNoticeId().toString())
+                        .set(SysNoticeStatus::getStatus, "1"));
+            }
+        }
+        
+        return count;
+    }
+
+    @Override
+    @Transactional
+    public Long savePushNotice(String noticeTitle, String noticeType, String noticeContent, List<Long> userIds) {
+        SysNotice notice = new SysNotice();
+        notice.setNoticeTitle(noticeTitle);
+        notice.setNoticeType(noticeType );
+        notice.setNoticeContent(noticeContent);
+        notice.setStatus("0");
+        if( baseMapper.insert(notice)>0){
+            for (Long userId : userIds) {
+                SysNoticeStatus sysNoticeStatus = new SysNoticeStatus();
+                sysNoticeStatus.setMessageId(notice.getNoticeId().toString());
+                sysNoticeStatus.setMessageType(noticeType);
+                sysNoticeStatus.setUserId(userId);
+                sysNoticeStatus.setStatus("0");
+                noticeStatusMapper.insert(sysNoticeStatus);
+            }
+        }
+        return notice.getNoticeId();
+    }
 }

+ 31 - 1
SERVER/ChickenFarmV3/vb-modules/vb-system/src/main/resources/mapper/system/SysNoticeMapper.xml

@@ -3,4 +3,34 @@
         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="cn.vber.system.mapper.SysNoticeMapper">
-</mapper>
+
+    <resultMap type="cn.vber.system.domain.vo.SysNoticeVo" id="SysNoticeResult">
+        <result property="noticeId" column="notice_id"/>
+        <result property="noticeTitle" column="notice_title"/>
+        <result property="noticeType" column="notice_type"/>
+        <result property="noticeContent" column="notice_content"/>
+        <result property="status" column="status"/>
+        <result property="remark" column="remark"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createByName" column="create_by_name"/>
+        <result property="createTime" column="create_time"/>
+        <result property="readStatus" column="read_status"/>
+    </resultMap>
+    
+    <select id="selectMyNoticePage" resultMap="SysNoticeResult">
+        SELECT 
+            n.notice_id,
+            n.notice_title,
+            n.notice_type,
+            n.notice_content,
+            n.status,
+            n.remark,
+            n.create_by,
+            n.create_time,
+            IFNULL(s.read_status, '0') AS read_status
+        FROM sys_notice n
+        LEFT JOIN (select message_id,status as read_status FROM sys_notice_status WHERE user_id = #{userId}) s ON n.notice_id = s.message_id
+        ${ew.customSqlSegment}
+    </select>
+
+</mapper>

+ 13 - 0
UI/VB.VUE/src/api/system/_notice.ts

@@ -11,6 +11,13 @@ class noticeApi {
 			loading: false
 		})
 	}
+	listMyNotice = (query: any) => {
+		return Rs.get({
+			url: "/system/notice/listMy",
+			params: query,
+			loading: false
+		})
+	}
 
 	// 查询公告详细
 	getNotice = (noticeId: string) => {
@@ -48,6 +55,12 @@ class noticeApi {
 		})
 	}
 
+	readAll = () => {
+		return Rs.post({
+			url: `/system/notice/readAll`
+		})
+	}
+
 	getAllMessage = (limit = 100) => {
 		return Rs.get({
 			url: `/system/notice/getAllMessage/${limit}`

+ 14 - 5
UI/VB.VUE/src/layouts/main/header/navbar/NotificationsMenu.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import { getAssetPath } from "@@/utils"
+import router from "@r"
 import appStore from "@s"
 const listBox = ref<HTMLElement>()
 const isShow = ref(true)
@@ -13,13 +14,18 @@ const messageCount = computed(() => {
 })
 
 const onNewsClick = (item: any) => {
+	console.log("item", item)
 	noticeStore.read(item)
 }
 
 function readAll() {
 	noticeStore.readAll()
 }
-
+function handleJumpNotice() {
+	router.push({
+		path: "/my/notice"
+	})
+}
 onMounted(() => {
 	noticeStore.loadNotice()
 })
@@ -44,7 +50,7 @@ onMounted(() => {
 				:style="`background-image: url('${getAssetPath('media/misc/menu-header-bg.jpg')}')`">
 				<h3
 					class="text-white fw-semobold px-9 mt-10 mb-6 d-flex align-items-center justify-content-between">
-					<span>
+					<span class="" @click="handleJumpNotice">
 						消息通知
 						<span class="fs-8 opacity-75 ps-3" v-if="messageCount > 0">{{ messageCount }} 条</span>
 					</span>
@@ -61,12 +67,15 @@ onMounted(() => {
 			<div ref="listBox" v-if="newsList.length" class="h-500px overflow-auto p-2">
 				<template v-for="(v, i) in newsList" :key="i">
 					<div class="d-flex px-3" style="justify-content: space-between">
-						<div class="d-flex align-items-center" @click="onNewsClick(v)" style="cursor: pointer">
+						<div
+							class="d-flex align-items-center w-100"
+							@click="onNewsClick(v)"
+							style="cursor: pointer">
 							<span class="svg-icon svg-icon-1 svg-icon-warning ps-2 pe-5">
-								<VbIcon icon-name="abstract-38" icon-type="duotone"></VbIcon>
+								<VbIcon icon-name="abstract-38" icon-type="duotone" class="fs-2"></VbIcon>
 							</span>
 							<div class="d-flex flex-column">
-								<span class="text-gray-700">{{ v.message }}</span>
+								<span class="text-gray-700 w-100">{{ v.title }}</span>
 								<span class="text-gray-600">{{ v.time }}</span>
 							</div>
 							<!-- 已读/未读 -->

+ 11 - 1
UI/VB.VUE/src/layouts/main/header/navbar/UserAccountMenu.vue

@@ -2,7 +2,7 @@
 import appStore from "@/stores"
 import apis from "@a"
 import router from "@r"
-
+import WorkOrderMenu from "@/layouts/main/header/navbar/WorkOrderMenu.vue"
 const user = appStore.authStore.user
 const dynamic = computed(() => {
 	return appStore.tenantStore.isDynamic()
@@ -71,6 +71,16 @@ function signOut() {
 				</router-link>
 			</div>
 			<!-- <VbWorkflow /> -->
+			<div class="menu-item">
+				<router-link to="/my/notice" class="menu-link">
+					<span class="menu-icon">
+						<VbIcon icon-name="messages" icon-type="duotone" class="me-2 fs-3"></VbIcon>
+					</span>
+					<span class="menu-title">我的通知</span>
+				</router-link>
+			</div>
+			<div class="separator my-2"></div>
+			<WorkOrderMenu />
 			<div class="separator my-2"></div>
 			<div class="menu-item">
 				<span @click="clearCache" class="menu-link px-5">

+ 58 - 7
UI/VB.VUE/src/router/_staticRouter.ts

@@ -2,19 +2,70 @@ import type { RouteRecordRaw } from "vue-router"
 export const staticRouter: RouteRecordRaw[] = [
 	{
 		path: "/",
+		name: "my",
+		component: () => import("@/layouts/dynamic/Layout.vue"),
 		children: [
 			{
-				path: "/icon",
-				name: "icon",
-				component: () => import("@/views/icon/index.vue"),
+				path: "/my/notice",
+				name: "notice",
+				component: () => import("@/views/my/notice.vue"),
 				meta: {
-					title: "ICON"
+					title: "我的通知",
+					noCache: false
+				}
+			}
+		]
+	},
+	{
+		path: "/",
+		name: "workOrder",
+		component: () => import("@/layouts/dynamic/Layout.vue"),
+		children: [
+			{
+				path: "/workOrder/needReceive",
+				name: "needReceive",
+				component: () => import("@/views/workOrder/needReceive.vue"),
+				meta: {
+					title: "待接收维修工单",
+					noCache: false
+				}
+			},
+			{
+				path: "/workOrder/myReport",
+				name: "myReport",
+				component: () => import("@/views/workOrder/myReport.vue"),
+				meta: {
+					title: "报修工单",
+					noCache: false
 				}
 			},
 			{
-				path: "/r",
-				name: "report",
-				component: () => import("@/views/report/chicken.vue"),
+				path: "/workOrder/myReceiveR",
+				name: "myReceiveR",
+				component: () => import("@/views/workOrder/myReceiveR.vue"),
+				meta: {
+					title: "维修工单",
+					noCache: false
+				}
+			},
+			{
+				path: "/workOrder/myReceiveM",
+				name: "myReceiveM",
+				component: () => import("@/views/workOrder/myReceiveM.vue"),
+				meta: {
+					title: "保养工单",
+					noCache: false
+				}
+			}
+		]
+	},
+	{
+		path: "/",
+		children: [
+			{
+				path: "/icon",
+				name: "icon",
+				component: () => import("@/views/icon/index.vue"),
 				meta: {
 					title: "ICON"
 				}

+ 46 - 5
UI/VB.VUE/src/stores/_notice.ts

@@ -8,6 +8,7 @@ export const useNoticeStore = defineStore("noticeStore", () => {
 
 	function loadNotice() {
 		apis.system.noticeApi.getAllMessage().then((res) => {
+			console.log("NOTICE", res)
 			notices.value = res.data.map((item: any) => {
 				return {
 					title: item.messageTitle,
@@ -49,20 +50,59 @@ export const useNoticeStore = defineStore("noticeStore", () => {
 			const old = notices.value[index]
 			if (old.data) {
 				const messageId = old.data.messageId
-				const messageType = old.data.messageType
-				if (old.data.routerPath) {
-					router.push({ path: "/redirect/" + old.data.routerPath, query: old.data.params })
+				const messageType = old.data.noticeType
+				// if (old.data.routerPath) {
+				// 	router.push({ path: "/redirect/" + old.data.routerPath, query: old.data.params })
+				// }
+				if (old.read) {
+					jump(old.data)
+					return
 				}
-				if (old.read) return
-				apis.system.noticeApi.read(messageId, messageType).then(() => {})
+
+				apis.system.noticeApi.read(messageId, messageType).then(() => {
+					message.msgSuccess("消息已阅读")
+					jump(old.data)
+				})
 			}
 			old.read = true
 		}
 	}
+
+	function readNotice(data) {
+		apis.system.noticeApi.read(data.noticeId, data.noticeType).then(() => {
+			message.msgSuccess("消息已阅读")
+			try {
+				const temp = JSON.parse(data.noticeContent)
+				jump(temp)
+			} catch (error) {
+				console.log("error", error)
+			}
+		})
+	}
+	function jump(data: any) {
+		switch (data.businessType) {
+			case "device_repair":
+				router.push({ path: "/workOrder/needReceive" })
+				break
+			case "device_receive":
+			case "device_suspend":
+			case "device_resume":
+			case "device_submit":
+				router.push({ path: "/workOrder/myReport" })
+				break
+			case "device_accept":
+			case "device_reject":
+				router.push({ path: "/workOrder/myReceiveR" })
+				break
+		}
+	}
 	function readAll() {
 		notices.value.forEach((item: any) => {
 			item.read = true
 		})
+		apis.system.noticeApi.readAll().then(() => {
+			message.msgSuccess("已全部标记为已读")
+		})
 	}
 	const clearNotice = () => {
 		notices.value = []
@@ -73,6 +113,7 @@ export const useNoticeStore = defineStore("noticeStore", () => {
 		addNotice,
 		removeNotice,
 		read,
+		readNotice,
 		readAll,
 		clearNotice
 	}

+ 126 - 0
UI/VB.VUE/src/views/my/notice.vue

@@ -0,0 +1,126 @@
+<script setup lang="ts" name="Notice">
+import apis from "@/api"
+import appStore from "@s"
+
+const tableRef = ref()
+const modalRef = ref()
+const opts = reactive({
+	columns: [
+		{ field: "noticeId", name: "公告公告ID", width: 100, isSort: true, visible: false },
+		{ field: "noticeTitle", name: "公告通知", visible: true },
+		{ field: "noticeType", name: "公告通知类型", visible: true, width: 130 },
+		{ field: "readStatus", name: "是否阅读", visible: true, width: 100 },
+		{ field: "createTime", name: "公告通知时间", visible: true, width: 190 },
+		{ field: "actions", name: `操作`, width: 60 }
+	],
+	queryParams: {
+		noticeTitle: "",
+		noticeType: undefined,
+		status: undefined
+	},
+	searchFormItems: [
+		{
+			field: "noticeTitle",
+			label: "通知公告标题",
+			class: "w-100",
+			required: false,
+			placeholder: "请输入通知公告标题",
+			listeners: {
+				keyup: (e: KeyboardEvent) => {
+					if (e.code == "Enter") {
+						handleQuery()
+					}
+				}
+			}
+		}
+		// {
+		// 	field: "noticeType",
+		// 	label: "公告类型",
+		// 	class: "w-100",
+		// 	required: false,
+		// 	component: "Dict",
+		// 	props: {
+		// 		placeholder: "请选择公告类型",
+		// 		dictType: "sys_notice_type"
+		// 	}
+		// },
+		// {
+		// 	field: "status",
+		// 	label: "通知状态",
+		// 	class: "w-100",
+		// 	required: false,
+		// 	component: "Dict",
+		// 	props: {
+		// 		placeholder: "请选择公告状态",
+		// 		dictType: "sys_notice_status"
+		// 	}
+		// }
+	] as any,
+	permission: "",
+	handleFuns: {},
+	customBtns: [],
+	tableListFun: apis.system.noticeApi.listMyNotice
+})
+const { queryParams } = toRefs(opts)
+/** 搜索按钮操作 */
+function handleQuery(query?: any) {
+	query = query || tableRef.value?.getQueryParams() || queryParams.value
+	tableRef.value?.query(query)
+}
+/** 重置按钮操作 */
+function resetQuery() {
+	//
+}
+function handleRead(row: any) {
+	appStore.noticeStore.readNotice(row)
+}
+
+function init() {}
+
+onMounted(init)
+</script>
+<template>
+	<div>
+		<VbDataTable
+			ref="tableRef"
+			:handle-perm="opts.permission"
+			:handle-funs="opts.handleFuns"
+			:search-form-items="opts.searchFormItems"
+			:columns="opts.columns as any"
+			:custom-btns="opts.customBtns"
+			:remote-fun="opts.tableListFun"
+			:modal="modalRef"
+			v-model:query-params="queryParams"
+			:url="apis.system.noticeApi.tableUrl"
+			:check-multiple="true"
+			:init-search="true"
+			:reset-search-form-fun="resetQuery"
+			:custom-search-fun="handleQuery">
+			<template #noticeTitle="{ row }">
+				<span v-if="row.noticeType == '1'" class="w-100 text-primary" @click="handleRead(row)">
+					{{ row.noticeTitle }}
+				</span>
+				<span v-else>{{ row.noticeTitle }}</span>
+			</template>
+			<template #noticeType="{ row }">
+				<DictTag type="sys_notice_type" :value="row.noticeType"></DictTag>
+			</template>
+			<template #status="{ row }">
+				<DictTag type="sys_notice_status" :value="row.status"></DictTag>
+			</template>
+			<template #readStatus="{ row }">
+				<DictTag type="sys_notice_read_status" :value="row.readStatus"></DictTag>
+			</template>
+			<template #actions="{ row }">
+				<vb-tooltip v-if="row.readStatus == '0'" content="阅读" placement="top">
+					<el-button link type="primary" @click="handleRead(row)">
+						<template #icon>
+							<VbIcon icon-name="book" icon-type="duotone" class="fs-3"></VbIcon>
+						</template>
+					</el-button>
+				</vb-tooltip>
+				<span v-else>-</span>
+			</template>
+		</VbDataTable>
+	</div>
+</template>