From 037d4b7cdfad5523f9f2e995c2ce00d5bfd312ba Mon Sep 17 00:00:00 2001 From: wuweihua <1583622118@qq.com> Date: Fri, 9 Oct 2026 17:06:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=A2=E6=88=B7=E8=B6=85=E9=A2=9D=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/SysNotifyController.java | 142 +++++++ .../com/bms/notify/domain/NotifyMessage.java | 59 +++ .../notify/domain/NotifyOverdueCustomer.java | 38 ++ .../com/bms/notify/domain/NotifyReceive.java | 64 +++ .../com/bms/notify/domain/NotifyRule.java | 48 +++ .../bms/notify/domain/NotifyRuleTarget.java | 34 ++ .../com/bms/notify/mapper/NotifyMapper.java | 107 +++++ .../bms/notify/service/INotifyService.java | 58 +++ .../service/impl/NotifyServiceImpl.java | 342 +++++++++++++++ .../bms/notify/task/NotifyCustomerTask.java | 36 ++ .../resources/mapper/notify/NotifyMapper.xml | 323 ++++++++++++++ bms-back/sql/20261009_add_sys_notify.sql | 98 +++++ bms-front/src/api/system/notify.js | 110 +++++ .../src/components/NotifyPopup/index.vue | 172 ++++++++ .../src/components/NotifyPopup/notifyState.js | 11 + bms-front/src/layout/components/Navbar.vue | 9 + bms-front/src/layout/index.vue | 3 + bms-front/src/store/modules/user.js | 17 +- .../customerinfo/index.vue | 2 +- bms-front/src/views/system/notify/index.vue | 399 ++++++++++++++++++ 20 files changed, 2065 insertions(+), 7 deletions(-) create mode 100644 bms-back/bms-admin/src/main/java/com/bms/web/controller/system/SysNotifyController.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyMessage.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyOverdueCustomer.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyReceive.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyRule.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyRuleTarget.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/notify/mapper/NotifyMapper.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/notify/service/INotifyService.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/notify/service/impl/NotifyServiceImpl.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/notify/task/NotifyCustomerTask.java create mode 100644 bms-back/bms-system/src/main/resources/mapper/notify/NotifyMapper.xml create mode 100644 bms-back/sql/20261009_add_sys_notify.sql create mode 100644 bms-front/src/api/system/notify.js create mode 100644 bms-front/src/components/NotifyPopup/index.vue create mode 100644 bms-front/src/components/NotifyPopup/notifyState.js create mode 100644 bms-front/src/views/system/notify/index.vue diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/system/SysNotifyController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/system/SysNotifyController.java new file mode 100644 index 00000000..04f55393 --- /dev/null +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/system/SysNotifyController.java @@ -0,0 +1,142 @@ +package com.bms.web.controller.system; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import com.bms.common.annotation.Log; +import com.bms.common.core.controller.BaseController; +import com.bms.common.core.domain.AjaxResult; +import com.bms.common.core.page.TableDataInfo; +import com.bms.common.enums.BusinessType; +import com.bms.common.utils.SecurityUtils; +import com.bms.notify.domain.NotifyMessage; +import com.bms.notify.domain.NotifyRule; +import com.bms.notify.service.INotifyService; + +/** + * 消息通知管理(系统管理) + * + *

规则/消息管理需 system:notify:* 权限;“我的消息”系列接口所有登录用户可用。

+ * + * @author bms + * @date 2026-10-09 + */ +@RestController +@RequestMapping("/system/notify") +public class SysNotifyController extends BaseController +{ + @Autowired + private INotifyService notifyService; + + /* ================= 通知规则 ================= */ + + @PreAuthorize("@ss.hasPermi('system:notify:list')") + @GetMapping("/rule/list") + public TableDataInfo ruleList(NotifyRule query) + { + startPage(); + return getDataTable(notifyService.selectRuleList(query)); + } + + @PreAuthorize("@ss.hasPermi('system:notify:query')") + @GetMapping("/rule/{ruleId}") + public AjaxResult getRule(@PathVariable Long ruleId) + { + return success(notifyService.selectRuleById(ruleId)); + } + + @PreAuthorize("@ss.hasPermi('system:notify:add')") + @Log(title = "消息通知规则", businessType = BusinessType.INSERT) + @PostMapping("/rule") + public AjaxResult addRule(@RequestBody NotifyRule rule) + { + return toAjax(notifyService.insertRule(rule)); + } + + @PreAuthorize("@ss.hasPermi('system:notify:edit')") + @Log(title = "消息通知规则", businessType = BusinessType.UPDATE) + @PutMapping("/rule") + public AjaxResult editRule(@RequestBody NotifyRule rule) + { + return toAjax(notifyService.updateRule(rule)); + } + + @PreAuthorize("@ss.hasPermi('system:notify:remove')") + @Log(title = "消息通知规则", businessType = BusinessType.DELETE) + @DeleteMapping("/rule/{ruleIds}") + public AjaxResult removeRule(@PathVariable Long[] ruleIds) + { + return toAjax(notifyService.deleteRuleByIds(ruleIds)); + } + + /** 立即执行一次超额扫描(测试/补发用) */ + @PreAuthorize("@ss.hasPermi('system:notify:edit')") + @Log(title = "客户超额通知扫描", businessType = BusinessType.OTHER) + @PostMapping("/scan") + public AjaxResult scanNow() + { + int count = notifyService.scanCustomerOverdue(); + return AjaxResult.success("扫描完成,新生成通知 " + count + " 条", count); + } + + /* ================= 通知记录 ================= */ + + @PreAuthorize("@ss.hasPermi('system:notify:list')") + @GetMapping("/message/list") + public TableDataInfo messageList(NotifyMessage query) + { + startPage(); + return getDataTable(notifyService.selectMessageList(query)); + } + + @PreAuthorize("@ss.hasPermi('system:notify:query')") + @GetMapping("/message/{messageId}") + public AjaxResult getMessage(@PathVariable Long messageId) + { + return success(notifyService.selectMessageById(messageId)); + } + + /* ================= 我的消息(所有登录用户) ================= */ + + /** 登录后待弹屏消息;拉取即置弹屏已展示,避免刷新/每次进首页重复打扰 */ + @GetMapping("/mine/popup") + public AjaxResult minePopup() + { + return success(notifyService.popupAndMarkShown(SecurityUtils.getUserId())); + } + + @GetMapping("/mine/list") + public AjaxResult mineList(@RequestParam(required = false) String readFlag) + { + return success(notifyService.selectMineList(SecurityUtils.getUserId(), readFlag)); + } + + @GetMapping("/mine/count") + public AjaxResult mineCount() + { + return success(notifyService.countUnread(SecurityUtils.getUserId())); + } + + @Log(title = "我的通知-已读", businessType = BusinessType.OTHER) + @PutMapping("/mine/read/{receiveId}") + public AjaxResult mineRead(@PathVariable Long receiveId) + { + return toAjax(notifyService.markRead(receiveId, SecurityUtils.getUserId())); + } + + @Log(title = "我的通知-全部已读", businessType = BusinessType.OTHER) + @PutMapping("/mine/readAll") + public AjaxResult mineReadAll() + { + return toAjax(notifyService.markAllRead(SecurityUtils.getUserId())); + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyMessage.java b/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyMessage.java new file mode 100644 index 00000000..84a8f79b --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyMessage.java @@ -0,0 +1,59 @@ +package com.bms.notify.domain; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +/** + * 通知消息 sys_notify_message + * + * @author bms + * @date 2026-10-09 + */ +@Data +public class NotifyMessage +{ + /** 消息ID */ + private Long messageId; + + /** 事件类型 */ + private String eventType; + + /** 标题 */ + private String title; + + /** 内容 */ + private String content; + + /** 关联客户ID */ + private Long customerId; + + /** 客户编号快照 */ + private String customerCode; + + /** 客户名称快照 */ + private String customerName; + + /** 超额金额(CNY,正数) */ + private BigDecimal overdueAmount; + + /** 创建者(系统任务记为 system) */ + private String createBy; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /* ===== 管理页统计/明细字段 ===== */ + + /** 接收人数 */ + private Integer receiveCount; + + /** 已读人数 */ + private Integer readCount; + + /** 接收明细 */ + private List receives; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyOverdueCustomer.java b/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyOverdueCustomer.java new file mode 100644 index 00000000..968e4084 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyOverdueCustomer.java @@ -0,0 +1,38 @@ +package com.bms.notify.domain; + +import java.math.BigDecimal; +import lombok.Data; + +/** + * 超额客户扫描结果(只读DTO,不落表)。 + * + *

可用额度口径与客户资金状态保持一致: + * 余额 + 当日有效的信用额度 + 当日有效的临时额度,< 0 即超额(欠费)。

+ * + * @author bms + * @date 2026-10-09 + */ +@Data +public class NotifyOverdueCustomer +{ + /** 客户ID */ + private Long customerId; + + /** 客户编号 */ + private String customerCode; + + /** 客户名称 */ + private String customerName; + + /** 停用标志(0正常 1停用) */ + private String isEnable; + + /** 账户余额 */ + private BigDecimal balance; + + /** 当日有效额度合计后可用额度(负=超额) */ + private BigDecimal available; + + /** 超额金额(= -available,正数) */ + private BigDecimal overdueAmount; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyReceive.java b/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyReceive.java new file mode 100644 index 00000000..f7a8c41a --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyReceive.java @@ -0,0 +1,64 @@ +package com.bms.notify.domain; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +/** + * 通知消息接收人 sys_notify_receive + * + * @author bms + * @date 2026-10-09 + */ +@Data +public class NotifyReceive +{ + /** 接收ID */ + private Long receiveId; + + /** 消息ID */ + private Long messageId; + + /** 接收用户ID */ + private Long userId; + + /** 已读标志(0未读 1已读) */ + private String readFlag; + + /** 阅读时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date readTime; + + /** 登录弹屏是否已展示(N否 Y是) */ + private String popupFlag; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /* ===== 我的消息列表展示字段(来自 sys_notify_message / sys_user) ===== */ + + /** 标题 */ + private String title; + + /** 内容 */ + private String content; + + /** 事件类型 */ + private String eventType; + + /** 客户名称 */ + private String customerName; + + /** 客户编号 */ + private String customerCode; + + /** 超额金额(CNY) */ + private java.math.BigDecimal overdueAmount; + + /** 接收人昵称 */ + private String nickName; + + /** 接收人账号 */ + private String userName; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyRule.java b/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyRule.java new file mode 100644 index 00000000..cd72498e --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyRule.java @@ -0,0 +1,48 @@ +package com.bms.notify.domain; + +import java.util.List; +import com.bms.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 消息通知规则 sys_notify_rule + * + *

当前仅一种事件:{@link #EVENT_CUSTOMER_OVERDUE}(客户已超额/欠费)。 + * 每条规则可配置多个通知对象(用户/角色),见 {@link NotifyRuleTarget}。

+ * + * @author bms + * @date 2026-10-09 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotifyRule extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 事件类型:客户已超额(欠费) */ + public static final String EVENT_CUSTOMER_OVERDUE = "CUSTOMER_OVERDUE"; + + /** 对象类型:用户 */ + public static final String TARGET_USER = "USER"; + /** 对象类型:角色 */ + public static final String TARGET_ROLE = "ROLE"; + + /** 规则ID */ + private Long ruleId; + + /** 规则名称 */ + private String ruleName; + + /** 事件类型 */ + private String eventType; + + /** 状态(0启用 1停用) */ + private String status; + + /** 删除标志(0存在 2删除) */ + private String delFlag; + + /** 通知对象列表(保存/回显用) */ + private List targets; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyRuleTarget.java b/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyRuleTarget.java new file mode 100644 index 00000000..c601474b --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/notify/domain/NotifyRuleTarget.java @@ -0,0 +1,34 @@ +package com.bms.notify.domain; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +/** + * 通知规则对象 sys_notify_rule_target(USER 指定用户 / ROLE 指定角色) + * + * @author bms + * @date 2026-10-09 + */ +@Data +public class NotifyRuleTarget +{ + /** 对象ID */ + private Long targetId; + + /** 规则ID */ + private Long ruleId; + + /** 对象类型:USER/ROLE */ + private String targetType; + + /** 用户ID或角色ID */ + private Long refId; + + /** 用户名/角色名快照 */ + private String refName; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/notify/mapper/NotifyMapper.java b/bms-back/bms-system/src/main/java/com/bms/notify/mapper/NotifyMapper.java new file mode 100644 index 00000000..934ad391 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/notify/mapper/NotifyMapper.java @@ -0,0 +1,107 @@ +package com.bms.notify.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.bms.notify.domain.NotifyMessage; +import com.bms.notify.domain.NotifyOverdueCustomer; +import com.bms.notify.domain.NotifyReceive; +import com.bms.notify.domain.NotifyRule; +import com.bms.notify.domain.NotifyRuleTarget; + +/** + * 消息通知 Mapper(规则/对象/消息/接收/扫描日志) + * + * @author bms + * @date 2026-10-09 + */ +public interface NotifyMapper +{ + /* ================= 规则 ================= */ + + List selectRuleList(NotifyRule query); + + NotifyRule selectRuleById(@Param("ruleId") Long ruleId); + + int insertRule(NotifyRule rule); + + int updateRule(NotifyRule rule); + + /** 软删除规则 */ + int deleteRuleByIds(@Param("ids") Long[] ids, @Param("updateBy") String updateBy); + + int countEnabledRules(@Param("eventType") String eventType); + + /* ================= 规则对象 ================= */ + + List selectTargetsByRuleId(@Param("ruleId") Long ruleId); + + List selectTargetsByRuleIds(@Param("ruleIds") List ruleIds); + + int insertTarget(NotifyRuleTarget target); + + int deleteTargetsByRuleId(@Param("ruleId") Long ruleId); + + /** 校验用户存在且为启用状态 */ + int countEnabledUser(@Param("userId") Long userId); + + /** 校验角色存在且为启用状态 */ + int countEnabledRole(@Param("roleId") Long roleId); + + /** 汇总所有启用规则的去重接收用户(直选用户 + 选中角色下用户) */ + List selectRecipientUserIds(@Param("eventType") String eventType); + + /* ================= 消息与接收 ================= */ + + List selectMessageList(NotifyMessage query); + + NotifyMessage selectMessageById(@Param("messageId") Long messageId); + + int insertMessage(NotifyMessage message); + + int insertReceive(NotifyReceive receive); + + List selectReceivesByMessageId(@Param("messageId") Long messageId); + + /* ================= 扫描日志 ================= */ + + String selectScanLogStatus(@Param("eventType") String eventType, + @Param("customerId") Long customerId); + + java.util.Date selectScanLogNotifyDate(@Param("eventType") String eventType, + @Param("customerId") Long customerId); + + int insertScanLog(@Param("eventType") String eventType, + @Param("customerId") Long customerId, + @Param("status") String status, + @Param("notify") boolean notify); + + int updateScanLog(@Param("eventType") String eventType, + @Param("customerId") Long customerId, + @Param("status") String status, + @Param("notify") boolean notify); + + /** 当前不超额但日志仍为OVERDUE的客户 -> 置NORMAL */ + int resetNormalForCustomers(@Param("eventType") String eventType, + @Param("customerIds") List customerIds); + + /** 查询所有当前超额客户(CNY账户,有效额度口径) */ + List selectOverdueCustomers(); + + /* ================= 我的消息 ================= */ + + /** 登录待弹屏消息(popup_flag=N) */ + List selectPopupReceives(@Param("userId") Long userId); + + /** 取弹屏列表后置 popup_flag=Y(服务器时间,一次登录只弹一次) */ + int markPopupShown(@Param("userId") Long userId); + + /** 我的消息列表(readFlag 为空=全部) */ + List selectMineList(@Param("userId") Long userId, + @Param("readFlag") String readFlag); + + int countUnread(@Param("userId") Long userId); + + int markRead(@Param("receiveId") Long receiveId, @Param("userId") Long userId); + + int markAllRead(@Param("userId") Long userId); +} diff --git a/bms-back/bms-system/src/main/java/com/bms/notify/service/INotifyService.java b/bms-back/bms-system/src/main/java/com/bms/notify/service/INotifyService.java new file mode 100644 index 00000000..36440366 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/notify/service/INotifyService.java @@ -0,0 +1,58 @@ +package com.bms.notify.service; + +import java.util.List; +import com.bms.notify.domain.NotifyMessage; +import com.bms.notify.domain.NotifyReceive; +import com.bms.notify.domain.NotifyRule; + +/** + * 消息通知 Service + * + * @author bms + * @date 2026-10-09 + */ +public interface INotifyService +{ + /** 规则分页列表 */ + List selectRuleList(NotifyRule query); + + /** 规则详情(含通知对象) */ + NotifyRule selectRuleById(Long ruleId); + + /** 新增规则(含通知对象) */ + int insertRule(NotifyRule rule); + + /** 修改规则(全量重建通知对象) */ + int updateRule(NotifyRule rule); + + /** 删除规则 */ + int deleteRuleByIds(Long[] ruleIds); + + /** 通知消息分页列表 */ + List selectMessageList(NotifyMessage query); + + /** 消息详情(含接收人已读情况) */ + NotifyMessage selectMessageById(Long messageId); + + /** + * 扫描超额客户并按启用规则投递消息(定时任务/手工触发)。 + * 同一客户当天只通知一次;客户恢复正常后再次超额可立即再通知。 + * @return 本次新生成的消息条数 + */ + int scanCustomerOverdue(); + + /** 登录后取待弹屏消息,并置 popup_flag=Y(一次登录只弹一次) */ + List popupAndMarkShown(Long userId); + + /** 我的消息列表(readFlag 为空=全部) */ + List selectMineList(Long userId, String readFlag); + + /** 我的未读数 */ + int countUnread(Long userId); + + /** 标记单条已读 */ + int markRead(Long receiveId, Long userId); + + /** 全部已读 */ + int markAllRead(Long userId); +} diff --git a/bms-back/bms-system/src/main/java/com/bms/notify/service/impl/NotifyServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/notify/service/impl/NotifyServiceImpl.java new file mode 100644 index 00000000..4faf2bd2 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/notify/service/impl/NotifyServiceImpl.java @@ -0,0 +1,342 @@ +package com.bms.notify.service.impl; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import com.bms.common.exception.ServiceException; +import com.bms.common.utils.SecurityUtils; +import com.bms.notify.domain.NotifyMessage; +import com.bms.notify.domain.NotifyOverdueCustomer; +import com.bms.notify.domain.NotifyReceive; +import com.bms.notify.domain.NotifyRule; +import com.bms.notify.domain.NotifyRuleTarget; +import com.bms.notify.mapper.NotifyMapper; +import com.bms.notify.service.INotifyService; + +/** + * 消息通知 Service实现 + * + * @author bms + * @date 2026-10-09 + */ +@Service +public class NotifyServiceImpl implements INotifyService +{ + private static final Logger log = LoggerFactory.getLogger(NotifyServiceImpl.class); + + @Autowired + private NotifyMapper notifyMapper; + + /* ================= 规则 ================= */ + + @Override + public List selectRuleList(NotifyRule query) + { + List rules = notifyMapper.selectRuleList(query); + if (rules.isEmpty()) + { + return rules; + } + List ids = rules.stream().map(NotifyRule::getRuleId).toList(); + Map> targetMap = loadTargets(ids); + rules.forEach(r -> r.setTargets(targetMap.getOrDefault(r.getRuleId(), new ArrayList<>()))); + return rules; + } + + @Override + public NotifyRule selectRuleById(Long ruleId) + { + NotifyRule rule = notifyMapper.selectRuleById(ruleId); + if (rule != null) + { + rule.setTargets(notifyMapper.selectTargetsByRuleId(ruleId)); + } + return rule; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int insertRule(NotifyRule rule) + { + prepareRule(rule); + String username = SecurityUtils.getUsername(); + rule.setCreateBy(username); + notifyMapper.insertRule(rule); + saveTargets(rule); + return 1; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int updateRule(NotifyRule rule) + { + if (rule.getRuleId() == null || notifyMapper.selectRuleById(rule.getRuleId()) == null) + { + throw new ServiceException("通知规则不存在"); + } + prepareRule(rule); + rule.setUpdateBy(SecurityUtils.getUsername()); + int rows = notifyMapper.updateRule(rule); + notifyMapper.deleteTargetsByRuleId(rule.getRuleId()); + saveTargets(rule); + return rows; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteRuleByIds(Long[] ruleIds) + { + if (ruleIds == null || ruleIds.length == 0) + { + return 0; + } + return notifyMapper.deleteRuleByIds(ruleIds, SecurityUtils.getUsername()); + } + + private void prepareRule(NotifyRule rule) + { + if (!StringUtils.hasText(rule.getRuleName())) + { + throw new ServiceException("规则名称不能为空"); + } + if (!StringUtils.hasText(rule.getEventType())) + { + rule.setEventType(NotifyRule.EVENT_CUSTOMER_OVERDUE); + } + if (!NotifyRule.EVENT_CUSTOMER_OVERDUE.equals(rule.getEventType())) + { + throw new ServiceException("暂不支持的通知事件类型"); + } + if (!StringUtils.hasText(rule.getStatus())) + { + rule.setStatus("0"); + } + List raw = rule.getTargets(); + if (raw == null || raw.isEmpty()) + { + throw new ServiceException("请至少选择一个通知用户或角色"); + } + // 去重 + 校验对象存在且启用 + Map uniq = new LinkedHashMap<>(); + for (NotifyRuleTarget t : raw) + { + if (t == null || t.getRefId() == null || !StringUtils.hasText(t.getTargetType())) + { + continue; + } + String type = t.getTargetType().trim().toUpperCase(); + if (!NotifyRule.TARGET_USER.equals(type) && !NotifyRule.TARGET_ROLE.equals(type)) + { + throw new ServiceException("通知对象类型只能是用户或角色"); + } + String key = type + "#" + t.getRefId(); + if (uniq.containsKey(key)) + { + continue; + } + int exists = NotifyRule.TARGET_USER.equals(type) + ? notifyMapper.countEnabledUser(t.getRefId()) + : notifyMapper.countEnabledRole(t.getRefId()); + if (exists == 0) + { + throw new ServiceException((NotifyRule.TARGET_USER.equals(type) ? "用户" : "角色") + + "不存在或已停用:" + (StringUtils.hasText(t.getRefName()) ? t.getRefName() : t.getRefId())); + } + t.setTargetType(type); + uniq.put(key, t); + } + if (uniq.isEmpty()) + { + throw new ServiceException("请至少选择一个有效的通知用户或角色"); + } + rule.setTargets(new ArrayList<>(uniq.values())); + } + + private void saveTargets(NotifyRule rule) + { + for (NotifyRuleTarget t : rule.getTargets()) + { + t.setTargetId(null); + t.setRuleId(rule.getRuleId()); + notifyMapper.insertTarget(t); + } + } + + private Map> loadTargets(List ruleIds) + { + Map> map = new LinkedHashMap<>(); + for (NotifyRuleTarget t : notifyMapper.selectTargetsByRuleIds(ruleIds)) + { + map.computeIfAbsent(t.getRuleId(), k -> new ArrayList<>()).add(t); + } + return map; + } + + /* ================= 消息 ================= */ + + @Override + public List selectMessageList(NotifyMessage query) + { + return notifyMapper.selectMessageList(query); + } + + @Override + public NotifyMessage selectMessageById(Long messageId) + { + NotifyMessage message = notifyMapper.selectMessageById(messageId); + if (message != null) + { + message.setReceives(notifyMapper.selectReceivesByMessageId(messageId)); + } + return message; + } + + /* ================= 超额扫描 ================= */ + + @Override + @Transactional(rollbackFor = Exception.class) + public int scanCustomerOverdue() + { + String eventType = NotifyRule.EVENT_CUSTOMER_OVERDUE; + if (notifyMapper.countEnabledRules(eventType) == 0) + { + log.info("客户超额通知扫描:无启用规则,跳过"); + return 0; + } + List recipients = notifyMapper.selectRecipientUserIds(eventType); + if (recipients.isEmpty()) + { + log.info("客户超额通知扫描:启用规则未匹配到有效接收人,跳过"); + return 0; + } + + List overdueList = notifyMapper.selectOverdueCustomers(); + List overdueIds = overdueList.stream().map(NotifyOverdueCustomer::getCustomerId).toList(); + // 曾超额但当前已恢复的客户:状态置 NORMAL,下次再超额可立即通知 + notifyMapper.resetNormalForCustomers(eventType, overdueIds); + + LocalDate today = LocalDate.now(); + int generated = 0; + for (NotifyOverdueCustomer c : overdueList) + { + String lastStatus = notifyMapper.selectScanLogStatus(eventType, c.getCustomerId()); + // 当日去重:今天已通知过且仍超额,跳过 + if ("OVERDUE".equals(lastStatus) && today.equals(lastNotifyDate(eventType, c.getCustomerId()))) + { + continue; + } + + NotifyMessage message = new NotifyMessage(); + message.setEventType(eventType); + boolean disabled = "1".equals(c.getIsEnable()); + message.setTitle((disabled ? "【停用客户已超额】" : "【客户已超额】") + c.getCustomerName()); + message.setContent(buildContent(c, disabled)); + message.setCustomerId(c.getCustomerId()); + message.setCustomerCode(c.getCustomerCode()); + message.setCustomerName(c.getCustomerName()); + message.setOverdueAmount(c.getOverdueAmount()); + message.setCreateBy("system"); + notifyMapper.insertMessage(message); + + for (Long userId : recipients) + { + NotifyReceive rv = new NotifyReceive(); + rv.setMessageId(message.getMessageId()); + rv.setUserId(userId); + notifyMapper.insertReceive(rv); + } + + if (lastStatus == null) + { + notifyMapper.insertScanLog(eventType, c.getCustomerId(), "OVERDUE", true); + } + else + { + notifyMapper.updateScanLog(eventType, c.getCustomerId(), "OVERDUE", true); + } + generated++; + } + log.info("客户超额通知扫描完成:超额客户 {} 户,新生成通知 {} 条,接收 {} 人", + overdueList.size(), generated, recipients.size()); + return generated; + } + + private LocalDate lastNotifyDate(String eventType, Long customerId) + { + Date d = notifyMapper.selectScanLogNotifyDate(eventType, customerId); + if (d == null) + { + return null; + } + return d.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } + + private String buildContent(NotifyOverdueCustomer c, boolean disabled) + { + StringBuilder sb = new StringBuilder(); + sb.append("客户「").append(c.getCustomerName()).append("」(").append(c.getCustomerCode()).append(")"); + if (disabled) + { + sb.append("已停用且"); + } + sb.append("已超额(欠费)。"); + sb.append("当前可用额度 ").append(format(c.getAvailable())).append(" CNY,"); + sb.append("超额金额 ").append(format(c.getOverdueAmount())).append(" CNY。"); + sb.append("请及时催收回款,可在【客户财务管理-收款管理】登记收款并审核。"); + return sb.toString(); + } + + private String format(java.math.BigDecimal v) + { + return v == null ? "0.00" : v.stripTrailingZeros().toPlainString(); + } + + /* ================= 我的消息 ================= */ + + @Override + @Transactional(rollbackFor = Exception.class) + public List popupAndMarkShown(Long userId) + { + List list = notifyMapper.selectPopupReceives(userId); + if (!list.isEmpty()) + { + // 服务器端置位:一次登录只弹一次,刷新页面不再弹,未读消息可从“我的通知”再次查看 + notifyMapper.markPopupShown(userId); + } + return list; + } + + @Override + public List selectMineList(Long userId, String readFlag) + { + return notifyMapper.selectMineList(userId, readFlag); + } + + @Override + public int countUnread(Long userId) + { + return notifyMapper.countUnread(userId); + } + + @Override + public int markRead(Long receiveId, Long userId) + { + return notifyMapper.markRead(receiveId, userId); + } + + @Override + public int markAllRead(Long userId) + { + return notifyMapper.markAllRead(userId); + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/notify/task/NotifyCustomerTask.java b/bms-back/bms-system/src/main/java/com/bms/notify/task/NotifyCustomerTask.java new file mode 100644 index 00000000..79039d98 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/notify/task/NotifyCustomerTask.java @@ -0,0 +1,36 @@ +package com.bms.notify.task; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.bms.notify.service.INotifyService; + +/** + * 客户超额通知定时任务。 + * + *

纳入 Quartz(sys_job)统一管理,调用目标: + * {@code notifyCustomerTask.scanOverdue()},cron/启停在「系统监控-定时任务」配置。

+ * + *

本任务只做消息投递,不改动任何客户余额/额度数据。

+ * + * @author bms + * @date 2026-10-09 + */ +@Component("notifyCustomerTask") +public class NotifyCustomerTask +{ + private static final Logger log = LoggerFactory.getLogger(NotifyCustomerTask.class); + + @Autowired + private INotifyService notifyService; + + /** + * 扫描已超额(欠费)客户并按通知规则投递消息(同一客户当天最多一条)。 + */ + public void scanOverdue() + { + int count = notifyService.scanCustomerOverdue(); + log.info("客户超额通知任务执行完成,新生成通知 {} 条", count); + } +} diff --git a/bms-back/bms-system/src/main/resources/mapper/notify/NotifyMapper.xml b/bms-back/bms-system/src/main/resources/mapper/notify/NotifyMapper.xml new file mode 100644 index 00000000..6e325beb --- /dev/null +++ b/bms-back/bms-system/src/main/resources/mapper/notify/NotifyMapper.xml @@ -0,0 +1,323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into sys_notify_rule (rule_name, event_type, status, remark, create_by, create_time) + values (#{ruleName}, #{eventType}, #{status}, #{remark}, #{createBy}, sysdate()) + + + + update sys_notify_rule + + rule_name = #{ruleName}, + event_type = #{eventType}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where rule_id = #{ruleId} and del_flag = '0' + + + + update sys_notify_rule + set del_flag = '2', update_by = #{updateBy}, update_time = sysdate() + where rule_id in + #{id} + + + + + + + + + + + insert into sys_notify_rule_target (rule_id, target_type, ref_id, ref_name, create_time) + values (#{ruleId}, #{targetType}, #{refId}, #{refName}, sysdate()) + + + + delete from sys_notify_rule_target where rule_id = #{ruleId} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into sys_notify_message (event_type, title, content, customer_id, customer_code, + customer_name, overdue_amount, create_by, create_time) + values (#{eventType}, #{title}, #{content}, #{customerId}, #{customerCode}, + #{customerName}, #{overdueAmount}, #{createBy}, sysdate()) + + + + insert ignore into sys_notify_receive (message_id, user_id, read_flag, popup_flag, create_time) + values (#{messageId}, #{userId}, '0', 'N', sysdate()) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into sys_notify_scan_log (event_type, customer_id, last_status, last_notify_date, update_time) + values (#{eventType}, #{customerId}, #{status}, + curdate()null, + sysdate()) + + + + update sys_notify_scan_log + set last_status = #{status}, + last_notify_date = curdate()last_notify_date, + update_time = sysdate() + where event_type = #{eventType} and customer_id = #{customerId} + + + + update sys_notify_scan_log + set last_status = 'NORMAL', update_time = sysdate() + where event_type = #{eventType} and last_status = 'OVERDUE' + + and customer_id not in + #{id} + + + + + + + + + + + + + + + + + + + + + update sys_notify_receive set popup_flag = 'Y' + where user_id = #{userId} and popup_flag = 'N' + + + + + + + + update sys_notify_receive + set read_flag = '1', read_time = sysdate() + where receive_id = #{receiveId} and user_id = #{userId} and read_flag = '0' + + + + update sys_notify_receive + set read_flag = '1', read_time = sysdate() + where user_id = #{userId} and read_flag = '0' + + diff --git a/bms-back/sql/20261009_add_sys_notify.sql b/bms-back/sql/20261009_add_sys_notify.sql new file mode 100644 index 00000000..e57acd99 --- /dev/null +++ b/bms-back/sql/20261009_add_sys_notify.sql @@ -0,0 +1,98 @@ +-- ============================================================ +-- 功能:系统管理 - 消息通知管理(客户超额通知) +-- 日期:2026-10-09 +-- 说明: +-- 1) sys_notify_rule 通知规则(当前事件类型:CUSTOMER_OVERDUE 客户已超额) +-- 2) sys_notify_rule_target 规则通知对象(USER 指定用户 / ROLE 指定角色,可多个) +-- 3) sys_notify_message 通知消息(一次超额扫描/客户/天最多一条,防重复打扰) +-- 4) sys_notify_receive 接收人与已读/弹屏记录 +-- 5) sys_notify_scan_log 扫描状态日志(用于超额->恢复状态机与当日去重) +-- 6) 菜单:系统管理 -> 消息通知管理(system/notify/index) +-- 7) 定时任务:notifyCustomerTask.scanOverdue() 每日09:00 +-- 口径:通知与核销无关,不涉及任何余额/额度变动。 +-- ============================================================ + +-- 1. 通知规则 +CREATE TABLE IF NOT EXISTS `sys_notify_rule` ( + `rule_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '规则ID', + `rule_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规则名称', + `event_type` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'CUSTOMER_OVERDUE' COMMENT '事件类型:CUSTOMER_OVERDUE客户已超额', + `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '状态(0启用 1停用)', + `remark` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `del_flag` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '删除标志(0存在 2删除)', + PRIMARY KEY (`rule_id`), + KEY `idx_snr_event_status` (`event_type`,`status`,`del_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消息通知规则'; + +-- 2. 规则通知对象 +CREATE TABLE IF NOT EXISTS `sys_notify_rule_target` ( + `target_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '对象ID', + `rule_id` bigint(20) NOT NULL COMMENT '规则ID', + `target_type` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '对象类型:USER用户/ROLE角色', + `ref_id` bigint(20) NOT NULL COMMENT '用户ID或角色ID', + `ref_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户名/角色名快照', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`target_id`), + UNIQUE KEY `uk_snrt_rule_type_ref` (`rule_id`,`target_type`,`ref_id`), + KEY `idx_snrt_type_ref` (`target_type`,`ref_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知规则对象(用户/角色)'; + +-- 3. 通知消息 +CREATE TABLE IF NOT EXISTS `sys_notify_message` ( + `message_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '消息ID', + `event_type` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'CUSTOMER_OVERDUE' COMMENT '事件类型', + `title` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '标题', + `content` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '内容', + `customer_id` bigint(20) DEFAULT NULL COMMENT '关联客户ID', + `customer_code` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '客户编号快照', + `customer_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '客户名称快照', + `overdue_amount` decimal(18,2) DEFAULT NULL COMMENT '超额金额(CNY,正数)', + `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建者(系统任务记为system)', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`message_id`), + KEY `idx_snm_event_customer` (`event_type`,`customer_id`), + KEY `idx_snm_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知消息'; + +-- 4. 接收人 +CREATE TABLE IF NOT EXISTS `sys_notify_receive` ( + `receive_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '接收ID', + `message_id` bigint(20) NOT NULL COMMENT '消息ID', + `user_id` bigint(20) NOT NULL COMMENT '接收用户ID', + `read_flag` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '已读标志(0未读 1已读)', + `read_time` datetime DEFAULT NULL COMMENT '阅读时间', + `popup_flag` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'N' COMMENT '登录弹屏是否已展示(N否 Y是)', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`receive_id`), + UNIQUE KEY `uk_snr_message_user` (`message_id`,`user_id`), + KEY `idx_snr_user_popup` (`user_id`,`popup_flag`,`read_flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知消息接收人'; + +-- 5. 扫描状态日志(每客户每事件一行) +CREATE TABLE IF NOT EXISTS `sys_notify_scan_log` ( + `log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID', + `event_type` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '事件类型', + `customer_id` bigint(20) NOT NULL COMMENT '客户ID', + `last_status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '最近状态:OVERDUE超额/NORMAL正常', + `last_notify_date` date DEFAULT NULL COMMENT '最近通知日期(当日去重)', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`log_id`), + UNIQUE KEY `uk_snsl_event_customer` (`event_type`,`customer_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知扫描状态日志'; + +-- 6. 菜单:系统管理 -> 消息通知管理 +INSERT INTO `sys_menu`(`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`) +VALUES (2450, '消息通知管理', 1, 9, 'notify', 'system/notify/index', NULL, 1, 0, 'C', '0', '0', 'system:notify:list', 'message', 'admin', sysdate(), '消息通知配置与记录'); +INSERT INTO `sys_menu`(`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`) VALUES +(2451, '通知规则查询', 2450, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:notify:query', '#', 'admin', sysdate(), ''), +(2452, '通知规则新增', 2450, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:notify:add', '#', 'admin', sysdate(), ''), +(2453, '通知规则修改', 2450, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:notify:edit', '#', 'admin', sysdate(), ''), +(2454, '通知规则删除', 2450, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:notify:remove', '#', 'admin', sysdate(), ''); + +-- 7. 定时任务:客户超额通知扫描(每日09:00,misfire不补偿、禁止并发) +INSERT INTO `sys_job`(`job_id`, `job_name`, `job_group`, `invoke_target`, `cron_expression`, `misfire_policy`, `concurrent`, `status`, `create_by`, `create_time`, `remark`) +VALUES (8, '客户超额通知扫描', 'DEFAULT', 'notifyCustomerTask.scanOverdue()', '0 0 9 * * ?', '3', '1', '0', 'admin', sysdate(), '扫描已超额(欠费)客户并按通知规则投递消息'); diff --git a/bms-front/src/api/system/notify.js b/bms-front/src/api/system/notify.js new file mode 100644 index 00000000..2b403b33 --- /dev/null +++ b/bms-front/src/api/system/notify.js @@ -0,0 +1,110 @@ +import request from '@/utils/request' + +// 查询通知规则列表 +export function listNotifyRule(query) { + return request({ + url: '/system/notify/rule/list', + method: 'get', + params: query + }) +} + +// 查询通知规则详情 +export function getNotifyRule(ruleId) { + return request({ + url: '/system/notify/rule/' + ruleId, + method: 'get' + }) +} + +// 新增通知规则 +export function addNotifyRule(data) { + return request({ + url: '/system/notify/rule', + method: 'post', + data + }) +} + +// 修改通知规则 +export function updateNotifyRule(data) { + return request({ + url: '/system/notify/rule', + method: 'put', + data + }) +} + +// 删除通知规则 +export function delNotifyRule(ruleIds) { + return request({ + url: '/system/notify/rule/' + ruleIds, + method: 'delete' + }) +} + +// 立即执行一次客户超额扫描 +export function scanOverdueNow() { + return request({ + url: '/system/notify/scan', + method: 'post' + }) +} + +// 查询通知消息列表 +export function listNotifyMessage(query) { + return request({ + url: '/system/notify/message/list', + method: 'get', + params: query + }) +} + +// 查询通知消息详情(含接收人已读情况) +export function getNotifyMessage(messageId) { + return request({ + url: '/system/notify/message/' + messageId, + method: 'get' + }) +} + +// 登录后待弹屏消息(拉取即置弹屏已展示) +export function popupNotify() { + return request({ + url: '/system/notify/mine/popup', + method: 'get' + }) +} + +// 我的通知列表 +export function listMyNotify(readFlag) { + return request({ + url: '/system/notify/mine/list', + method: 'get', + params: { readFlag } + }) +} + +// 我的未读数 +export function countMyNotify() { + return request({ + url: '/system/notify/mine/count', + method: 'get' + }) +} + +// 标记单条已读 +export function readMyNotify(receiveId) { + return request({ + url: '/system/notify/mine/read/' + receiveId, + method: 'put' + }) +} + +// 全部已读 +export function readAllMyNotify() { + return request({ + url: '/system/notify/mine/readAll', + method: 'put' + }) +} diff --git a/bms-front/src/components/NotifyPopup/index.vue b/bms-front/src/components/NotifyPopup/index.vue new file mode 100644 index 00000000..21b82ca2 --- /dev/null +++ b/bms-front/src/components/NotifyPopup/index.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/bms-front/src/components/NotifyPopup/notifyState.js b/bms-front/src/components/NotifyPopup/notifyState.js new file mode 100644 index 00000000..6573decb --- /dev/null +++ b/bms-front/src/components/NotifyPopup/notifyState.js @@ -0,0 +1,11 @@ +import { reactive } from 'vue' + +/** + * 通知弹屏共享状态(单例): + * - 组件挂载于全局 Layout,负责登录后自动拉取待弹屏消息 + * - Navbar “我的通知”入口通过 open() 打开同一弹窗 + */ +export const notifyState = reactive({ + visible: false, + unreadCount: 0 +}) diff --git a/bms-front/src/layout/components/Navbar.vue b/bms-front/src/layout/components/Navbar.vue index 60bea0fc..c0e5eae4 100644 --- a/bms-front/src/layout/components/Navbar.vue +++ b/bms-front/src/layout/components/Navbar.vue @@ -42,6 +42,11 @@ 个人中心 + + + 我的通知 + + 布局设置 @@ -72,6 +77,7 @@ import useAppStore from '@/store/modules/app' import useUserStore from '@/store/modules/user' import useLockStore from '@/store/modules/lock' import useSettingsStore from '@/store/modules/settings' +import { notifyState } from '@/components/NotifyPopup/notifyState' // import HeaderNotice from './HeaderNotice' // 消息通知铃铛已隐藏 const route = useRoute() @@ -93,6 +99,9 @@ function handleCommand(command) { case "lockScreen": lockScreen() break + case "myNotify": + notifyState.open && notifyState.open() + break case "logout": logout() break diff --git a/bms-front/src/layout/index.vue b/bms-front/src/layout/index.vue index b7328edc..ca6e54f8 100644 --- a/bms-front/src/layout/index.vue +++ b/bms-front/src/layout/index.vue @@ -10,6 +10,8 @@ + + @@ -17,6 +19,7 @@ import { useWindowSize } from '@vueuse/core' import Sidebar from './components/Sidebar/index.vue' import { AppMain, Navbar, Settings, TagsView } from './components' +import NotifyPopup from '@/components/NotifyPopup/index.vue' import useAppStore from '@/store/modules/app' import useSettingsStore from '@/store/modules/settings' diff --git a/bms-front/src/store/modules/user.js b/bms-front/src/store/modules/user.js index 89c6de20..3717e433 100644 --- a/bms-front/src/store/modules/user.js +++ b/bms-front/src/store/modules/user.js @@ -58,17 +58,22 @@ const useUserStore = defineStore( this.nickName = user.nickName this.avatar = avatar cache.session.set('pwrChrtype', res.pwdChrtype) + /* 改密提示展示标记:通知弹屏组件读取该标记,等改密框关闭后再弹通知,避免双框冲突 */ + const showPwdPrompt = (msg) => { + sessionStorage.setItem('bmsPwdPrompt', '1') + ElMessageBox.confirm(msg, '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { + router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } }) + }).catch(() => {}).finally(() => { + sessionStorage.removeItem('bmsPwdPrompt') + }) + } /* 初始密码提示 */ if(res.isDefaultModifyPwd) { - ElMessageBox.confirm('您的密码还是初始密码,请修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { - router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } }) - }).catch(() => {}) + showPwdPrompt('您的密码还是初始密码,请修改密码!') } /* 过期密码提示 */ if(!res.isDefaultModifyPwd && res.isPasswordExpired) { - ElMessageBox.confirm('您的密码已过期,请尽快修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { - router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } }) - }).catch(() => {}) + showPwdPrompt('您的密码已过期,请尽快修改密码!') } resolve(res) }).catch(error => { diff --git a/bms-front/src/views/customersuppliermanagement/customerinfo/index.vue b/bms-front/src/views/customersuppliermanagement/customerinfo/index.vue index 77ee4eb2..15c9151d 100644 --- a/bms-front/src/views/customersuppliermanagement/customerinfo/index.vue +++ b/bms-front/src/views/customersuppliermanagement/customerinfo/index.vue @@ -180,7 +180,7 @@ diff --git a/bms-front/src/views/system/notify/index.vue b/bms-front/src/views/system/notify/index.vue new file mode 100644 index 00000000..8caf7ae6 --- /dev/null +++ b/bms-front/src/views/system/notify/index.vue @@ -0,0 +1,399 @@ + + + + +