mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 10:57:13 +08:00 
			
		
		
		
	refactor: 优化站内信及消息管理
1.新增站内信未读消息轮询 2.优化消息管理 API,移除部分无用 API 3.优化部分代码格式
This commit is contained in:
		| @@ -67,9 +67,4 @@ public class SysConsts { | ||||
|      * VO 描述类字段后缀 | ||||
|      */ | ||||
|     public static final String VO_DESCRIPTION_FIELD_SUFFIX = "String"; | ||||
|  | ||||
|     /** | ||||
|      * 系统消息类型 | ||||
|      */ | ||||
|     public static final String SYSTEM_MESSAGE_TYPE = "1"; | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,41 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.enums; | ||||
|  | ||||
| import lombok.Getter; | ||||
| import lombok.RequiredArgsConstructor; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.IBaseEnum; | ||||
| import top.charles7c.cnadmin.common.constant.UIConsts; | ||||
|  | ||||
| /** | ||||
|  * 消息类型枚举 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/11/2 20:08 | ||||
|  */ | ||||
| @Getter | ||||
| @RequiredArgsConstructor | ||||
| public enum MessageTypeEnum implements IBaseEnum<Integer> { | ||||
|  | ||||
|     /** 系统消息 */ | ||||
|     SYSTEM(1, "系统消息", UIConsts.COLOR_PRIMARY),; | ||||
|  | ||||
|     private final Integer value; | ||||
|     private final String description; | ||||
|     private final String color; | ||||
| } | ||||
| @@ -28,10 +28,10 @@ import cn.hutool.core.bean.BeanUtil; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
| import cn.hutool.core.lang.tree.TreeNodeConfig; | ||||
| import cn.hutool.core.map.MapUtil; | ||||
| import cn.hutool.core.util.IdUtil; | ||||
| import cn.hutool.core.util.RandomUtil; | ||||
| import cn.hutool.core.util.ReUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.json.JSONUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.auth.model.vo.MetaVO; | ||||
| @@ -45,6 +45,7 @@ import top.charles7c.cnadmin.common.constant.SysConsts; | ||||
| import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum; | ||||
| import top.charles7c.cnadmin.common.enums.GenderEnum; | ||||
| import top.charles7c.cnadmin.common.enums.MenuTypeEnum; | ||||
| import top.charles7c.cnadmin.common.enums.MessageTypeEnum; | ||||
| import top.charles7c.cnadmin.common.model.dto.LoginUser; | ||||
| import top.charles7c.cnadmin.common.util.SecureUtils; | ||||
| import top.charles7c.cnadmin.common.util.TreeUtils; | ||||
| @@ -71,6 +72,7 @@ import me.zhyd.oauth.model.AuthUser; | ||||
| @RequiredArgsConstructor | ||||
| public class LoginServiceImpl implements LoginService { | ||||
|  | ||||
|     private final ProjectProperties projectProperties; | ||||
|     private final UserService userService; | ||||
|     private final DeptService deptService; | ||||
|     private final RoleService roleService; | ||||
| @@ -79,7 +81,6 @@ public class LoginServiceImpl implements LoginService { | ||||
|     private final UserRoleService userRoleService; | ||||
|     private final UserSocialService userSocialService; | ||||
|     private final MessageService messageService; | ||||
|     private final ProjectProperties projectProperties; | ||||
|  | ||||
|     @Override | ||||
|     public String accountLogin(String username, String password) { | ||||
| @@ -137,7 +138,7 @@ public class LoginServiceImpl implements LoginService { | ||||
|             userSocial.setUserId(userId); | ||||
|             userSocial.setSource(source); | ||||
|             userSocial.setOpenId(openId); | ||||
|             this.sendMsg(user); | ||||
|             this.sendSystemMsg(user); | ||||
|         } else { | ||||
|             user = BeanUtil.toBean(userService.get(userSocial.getUserId()), UserDO.class); | ||||
|         } | ||||
| @@ -214,20 +215,17 @@ public class LoginServiceImpl implements LoginService { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 发送消息 | ||||
|      * 发送系统消息 | ||||
|      *  | ||||
|      * @param user | ||||
|      *            用户信息 | ||||
|      */ | ||||
|     private void sendMsg(UserDO user) { | ||||
|     private void sendSystemMsg(UserDO user) { | ||||
|         MessageRequest request = new MessageRequest(); | ||||
|         MessageTemplateEnum socialRegister = MessageTemplateEnum.SOCIAL_REGISTER; | ||||
|         request.setTitle(socialRegister.getTitle()); | ||||
|         Map<String, Object> contentMap = MapUtil.newHashMap(2); | ||||
|         contentMap.put("nickname", user.getNickname()); | ||||
|         contentMap.put("projectName", projectProperties.getName()); | ||||
|         request.setContent(socialRegister.getContent(), contentMap); | ||||
|         request.setType(SysConsts.SYSTEM_MESSAGE_TYPE); | ||||
|         request.setTitle(StrUtil.format(socialRegister.getTitle(), projectProperties.getName())); | ||||
|         request.setContent(StrUtil.format(socialRegister.getContent(), user.getNickname())); | ||||
|         request.setType(MessageTypeEnum.SYSTEM); | ||||
|         messageService.add(request, CollUtil.toList(user.getId())); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -32,7 +32,7 @@ public enum MessageTemplateEnum { | ||||
|     /** | ||||
|      * 第三方登录 | ||||
|      */ | ||||
|     SOCIAL_REGISTER("欢迎注册 {projectName}", "尊敬的 {nickname},欢迎注册使用,请及时配置您的密码。"); | ||||
|     SOCIAL_REGISTER("欢迎注册 {}", "尊敬的 {},欢迎注册使用,请及时配置您的密码。"); | ||||
|  | ||||
|     private final String title; | ||||
|     private final String content; | ||||
|   | ||||
| @@ -16,8 +16,6 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.mapper; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import org.apache.ibatis.annotations.Param; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| @@ -35,24 +33,16 @@ import top.charles7c.cnadmin.system.model.vo.MessageVO; | ||||
|  * @since 2023/10/15 19:05 | ||||
|  */ | ||||
| public interface MessageMapper extends BaseMapper<MessageDO> { | ||||
|  | ||||
|     /** | ||||
|      * 分页查询列表 | ||||
|      * | ||||
|      * @param queryWrapper | ||||
|      *            查询条件 | ||||
|      * @param page | ||||
|      *            分页查询条件 | ||||
|      * @param queryWrapper | ||||
|      *            查询条件 | ||||
|      * @return 分页信息 | ||||
|      */ | ||||
|     IPage<MessageVO> selectVoPage(@Param("page") IPage<Object> page, | ||||
|         @Param(Constants.WRAPPER) QueryWrapper<MessageDO> queryWrapper); | ||||
|  | ||||
|     /** | ||||
|      * 查询列表 | ||||
|      * | ||||
|      * @param queryWrapper | ||||
|      *            查询条件 | ||||
|      * @return 列表信息 | ||||
|      */ | ||||
|     List<MessageVO> selectVoList(@Param(Constants.WRAPPER) QueryWrapper<MessageDO> queryWrapper); | ||||
| } | ||||
| @@ -16,13 +16,27 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.mapper; | ||||
|  | ||||
| import org.apache.ibatis.annotations.Param; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.MessageUserDO; | ||||
|  | ||||
| /** | ||||
|  * 消息和用户关联 Mapper | ||||
|  * 消息和用户 Mapper | ||||
|  * | ||||
|  * @author BULL_BCLS | ||||
|  * @since 2023/10/15 20:25 | ||||
|  */ | ||||
| public interface MessageUserMapper extends BaseMapper<MessageUserDO> {} | ||||
| public interface MessageUserMapper extends BaseMapper<MessageUserDO> { | ||||
|  | ||||
|     /** | ||||
|      * 根据用户 ID 和消息类型查询未读消息数量 | ||||
|      *  | ||||
|      * @param userId | ||||
|      *            用户 ID | ||||
|      * @param type | ||||
|      *            消息类型 | ||||
|      * @return 未读消息信息 | ||||
|      */ | ||||
|     Long selectUnreadCountByUserIdAndType(@Param("userId") Long userId, @Param("type") Integer type); | ||||
| } | ||||
| @@ -16,11 +16,17 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.model.entity; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import com.baomidou.mybatisplus.annotation.FieldFill; | ||||
| import com.baomidou.mybatisplus.annotation.TableField; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseDO; | ||||
| import top.charles7c.cnadmin.common.enums.MessageTypeEnum; | ||||
|  | ||||
| /** | ||||
|  * 消息实体 | ||||
| @@ -30,17 +36,18 @@ import top.charles7c.cnadmin.common.base.BaseDO; | ||||
|  */ | ||||
| @Data | ||||
| @TableName("sys_message") | ||||
| public class MessageDO extends BaseDO { | ||||
| public class MessageDO implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 消息ID | ||||
|      * ID | ||||
|      */ | ||||
|     @TableId | ||||
|     private Long id; | ||||
|  | ||||
|     /** | ||||
|      * 主题 | ||||
|      * 标题 | ||||
|      */ | ||||
|     private String title; | ||||
|  | ||||
| @@ -50,7 +57,18 @@ public class MessageDO extends BaseDO { | ||||
|     private String content; | ||||
|  | ||||
|     /** | ||||
|      * 类型(取值于字典 message_type) | ||||
|      * 类型(1:系统消息) | ||||
|      */ | ||||
|     private String type; | ||||
|     private MessageTypeEnum type; | ||||
|  | ||||
|     /** | ||||
|      * 创建人 | ||||
|      */ | ||||
|     private Long createUser; | ||||
|  | ||||
|     /** | ||||
|      * 创建时间 | ||||
|      */ | ||||
|     @TableField(fill = FieldFill.INSERT) | ||||
|     private LocalDateTime createTime; | ||||
| } | ||||
| @@ -16,14 +16,13 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.model.entity; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseDO; | ||||
|  | ||||
| /** | ||||
|  * 消息和用户关联实体 | ||||
|  * | ||||
| @@ -32,24 +31,24 @@ import top.charles7c.cnadmin.common.base.BaseDO; | ||||
|  */ | ||||
| @Data | ||||
| @TableName("sys_message_user") | ||||
| public class MessageUserDO extends BaseDO { | ||||
| public class MessageUserDO implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 消息ID | ||||
|      * 消息 ID | ||||
|      */ | ||||
|     private Long messageId; | ||||
|  | ||||
|     /** | ||||
|      * 用户ID | ||||
|      * 用户 ID | ||||
|      */ | ||||
|     private Long userId; | ||||
|  | ||||
|     /** | ||||
|      * 读取状态 (0未读 1已读) | ||||
|      * 是否已读 | ||||
|      */ | ||||
|     private Boolean readStatus; | ||||
|     private Boolean isRead; | ||||
|  | ||||
|     /** | ||||
|      * 读取时间 | ||||
|   | ||||
| @@ -41,32 +41,32 @@ public class MessageQuery implements Serializable { | ||||
|      * ID | ||||
|      */ | ||||
|     @Schema(description = "ID", example = "1") | ||||
|     @Query(type = QueryTypeEnum.EQUAL) | ||||
|     @Query | ||||
|     private Long id; | ||||
|  | ||||
|     /** | ||||
|      * 类型(取值于字典 message_type) | ||||
|      * 标题 | ||||
|      */ | ||||
|     @Schema(description = "类型(取值于字典 message_type)", example = "1") | ||||
|     @Query(type = QueryTypeEnum.EQUAL) | ||||
|     private String type; | ||||
|  | ||||
|     /** | ||||
|      * 主题 | ||||
|      */ | ||||
|     @Schema(description = "主题", example = "欢迎 xxx") | ||||
|     @Schema(description = "标题", example = "欢迎注册 xxx") | ||||
|     @Query(type = QueryTypeEnum.INNER_LIKE) | ||||
|     private String title; | ||||
|  | ||||
|     /** | ||||
|      * 用户ID | ||||
|      * 类型 | ||||
|      */ | ||||
|     @Schema(description = "用户ID", example = "1") | ||||
|     private Long uid; | ||||
|     @Schema(description = "类型(1:系统消息)", example = "1") | ||||
|     @Query | ||||
|     private Integer type; | ||||
|  | ||||
|     /** | ||||
|      * 是否已读 | ||||
|      */ | ||||
|     @Schema(description = "是否已读", example = "true") | ||||
|     private Boolean readStatus; | ||||
|     private Boolean isRead; | ||||
|  | ||||
|     /** | ||||
|      * 用户 ID | ||||
|      */ | ||||
|     @Schema(hidden = true) | ||||
|     private Long userId; | ||||
| } | ||||
| @@ -16,9 +16,8 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.model.request; | ||||
|  | ||||
| import java.util.Map; | ||||
|  | ||||
| import javax.validation.constraints.NotBlank; | ||||
| import javax.validation.constraints.NotNull; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| @@ -26,9 +25,8 @@ import io.swagger.v3.oas.annotations.media.Schema; | ||||
|  | ||||
| import org.hibernate.validator.constraints.Length; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseRequest; | ||||
| import top.charles7c.cnadmin.common.enums.MessageTypeEnum; | ||||
|  | ||||
| /** | ||||
|  * 创建消息信息 | ||||
| @@ -36,37 +34,32 @@ import top.charles7c.cnadmin.common.base.BaseRequest; | ||||
|  * @author BULL_BCLS | ||||
|  * @since 2023/10/15 19:05 | ||||
|  */ | ||||
| @Schema(description = "创建消息信息") | ||||
| @Data | ||||
| @Schema(description = "创建消息信息") | ||||
| public class MessageRequest extends BaseRequest { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 主题 | ||||
|      * 标题 | ||||
|      */ | ||||
|     @Schema(description = "主题", example = "欢迎 xxx") | ||||
|     @NotBlank(message = "主题不能为空") | ||||
|     @Length(max = 50, message = "主题长度不能超过 {max} 个字符") | ||||
|     @Schema(description = "标题", example = "欢迎注册 xxx") | ||||
|     @NotBlank(message = "标题不能为空") | ||||
|     @Length(max = 50, message = "标题长度不能超过 {max} 个字符") | ||||
|     private String title; | ||||
|  | ||||
|     /** | ||||
|      * 内容 | ||||
|      */ | ||||
|     @Schema(description = "内容", example = "欢迎 xxx 来到 ContiNew Admin") | ||||
|     @Schema(description = "内容", example = "尊敬的 xx,欢迎注册使用,请及时配置您的密码。") | ||||
|     @NotBlank(message = "内容不能为空") | ||||
|     @Length(max = 255, message = "内容长度不能超过 {max} 个字符") | ||||
|     private String content; | ||||
|  | ||||
|     /** | ||||
|      * 类型(取值于字典 message_type) | ||||
|      * 类型 | ||||
|      */ | ||||
|     @Schema(description = "类型(取值于字典 message_type)", example = "1") | ||||
|     @NotBlank(message = "类型不能为空") | ||||
|     @Length(max = 30, message = "类型长度不能超过 {max} 个字符") | ||||
|     private String type; | ||||
|  | ||||
|     public void setContent(String content, Map<String, Object> contentMap) { | ||||
|         this.content = StrUtil.format(content, contentMap); | ||||
|     } | ||||
|     @Schema(description = "类型(1:系统消息)", example = "1") | ||||
|     @NotNull(message = "类型非法") | ||||
|     private MessageTypeEnum type; | ||||
| } | ||||
| @@ -16,47 +16,35 @@ | ||||
| 
 | ||||
| package top.charles7c.cnadmin.system.model.vo; | ||||
| 
 | ||||
| import java.time.LocalDateTime; | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| import lombok.Data; | ||||
| 
 | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| 
 | ||||
| import top.charles7c.cnadmin.common.base.BaseVO; | ||||
| import top.charles7c.cnadmin.common.enums.MessageTypeEnum; | ||||
| 
 | ||||
| /** | ||||
|  * 消息和用户关联信息 | ||||
|  * 各类型未读消息信息 | ||||
|  * | ||||
|  * @author BULL_BCLS | ||||
|  * @since 2023/10/15 20:25 | ||||
|  * @author Charles7c | ||||
|  * @since 2023/11/2 23:00 | ||||
|  */ | ||||
| @Data | ||||
| @Schema(description = "消息和用户关联信息") | ||||
| public class MessageUserVO extends BaseVO { | ||||
| @Schema(description = "各类型未读消息信息") | ||||
| public class MessageTypeUnreadVO implements Serializable { | ||||
| 
 | ||||
|     private static final long serialVersionUID = 1L; | ||||
| 
 | ||||
|     /** | ||||
|      * 消息ID | ||||
|      * 类型 | ||||
|      */ | ||||
|     @Schema(description = "消息ID", example = "1") | ||||
|     private Long messageId; | ||||
|     @Schema(description = "类型(1:系统消息)", example = "1") | ||||
|     private MessageTypeEnum type; | ||||
| 
 | ||||
|     /** | ||||
|      * 用户ID | ||||
|      * 数量 | ||||
|      */ | ||||
|     @Schema(description = "用户ID", example = "1") | ||||
|     private Long userId; | ||||
| 
 | ||||
|     /** | ||||
|      * 是否已读 | ||||
|      */ | ||||
|     @Schema(description = "是否已读", example = "true") | ||||
|     private Boolean readStatus; | ||||
| 
 | ||||
|     /** | ||||
|      * 读取时间 | ||||
|      */ | ||||
|     @Schema(description = "读取时间", example = "2023-08-08 23:59:59", type = "string") | ||||
|     private LocalDateTime readTime; | ||||
|     @Schema(description = "数量", example = "10") | ||||
|     private Long count; | ||||
| } | ||||
| @@ -0,0 +1,52 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.model.vo; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.List; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
|  | ||||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||||
|  | ||||
| /** | ||||
|  * 未读消息信息 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/11/2 23:00 | ||||
|  */ | ||||
| @Data | ||||
| @Schema(description = "未读消息信息") | ||||
| @JsonInclude(JsonInclude.Include.NON_EMPTY) | ||||
| public class MessageUnreadVO implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 未读消息数量 | ||||
|      */ | ||||
|     @Schema(description = "未读消息数量", example = "20") | ||||
|     private Long total; | ||||
|  | ||||
|     /** | ||||
|      * 各类型未读消息数量 | ||||
|      */ | ||||
|     @Schema(description = "各类型未读消息数量") | ||||
|     private List<MessageTypeUnreadVO> details; | ||||
| } | ||||
| @@ -16,13 +16,16 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.model.vo; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseVO; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.enums.MessageTypeEnum; | ||||
|  | ||||
| /** | ||||
|  * 消息信息 | ||||
| @@ -32,43 +35,61 @@ import top.charles7c.cnadmin.common.base.BaseVO; | ||||
|  */ | ||||
| @Data | ||||
| @Schema(description = "消息信息") | ||||
| public class MessageVO extends BaseVO { | ||||
| public class MessageVO implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 消息ID | ||||
|      * ID | ||||
|      */ | ||||
|     @Schema(description = "消息ID", example = "1") | ||||
|     @Schema(description = "ID", example = "1") | ||||
|     private Long id; | ||||
|  | ||||
|     /** | ||||
|      * 主题 | ||||
|      * 标题 | ||||
|      */ | ||||
|     @Schema(description = "主题", example = "欢迎 xxx") | ||||
|     @Schema(description = "标题", example = "欢迎注册 xxx") | ||||
|     private String title; | ||||
|  | ||||
|     /** | ||||
|      * 内容 | ||||
|      */ | ||||
|     @Schema(description = "内容", example = "欢迎 xxx") | ||||
|     @Schema(description = "内容", example = "尊敬的 xx,欢迎注册使用,请及时配置您的密码。") | ||||
|     private String content; | ||||
|  | ||||
|     /** | ||||
|      * 类型(取值于字典 message_type) | ||||
|      * 类型 | ||||
|      */ | ||||
|     @Schema(description = "类型(取值于字典 message_type)", example = "1") | ||||
|     private String type; | ||||
|     @Schema(description = "类型(1:系统消息)", example = "1") | ||||
|     private MessageTypeEnum type; | ||||
|  | ||||
|     /** | ||||
|      * 是否已读 | ||||
|      */ | ||||
|     @Schema(description = "是否已读", example = "true") | ||||
|     private Boolean readStatus; | ||||
|     private Boolean isRead; | ||||
|  | ||||
|     /** | ||||
|      * 读取时间 | ||||
|      */ | ||||
|     @Schema(description = "读取时间", example = "2023-08-08 23:59:59", type = "string") | ||||
|     private LocalDateTime readTime; | ||||
|  | ||||
|     /** | ||||
|      * 创建人 | ||||
|      */ | ||||
|     @JsonIgnore | ||||
|     private Long createUser; | ||||
|  | ||||
|     /** | ||||
|      * 创建人 | ||||
|      */ | ||||
|     @Schema(description = "创建人", example = "超级管理员") | ||||
|     private String createUserString; | ||||
|  | ||||
|     /** | ||||
|      * 创建时间 | ||||
|      */ | ||||
|     @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") | ||||
|     private LocalDateTime createTime; | ||||
| } | ||||
| @@ -18,7 +18,8 @@ package top.charles7c.cnadmin.system.service; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseService; | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageDataVO; | ||||
| import top.charles7c.cnadmin.system.model.query.MessageQuery; | ||||
| import top.charles7c.cnadmin.system.model.request.MessageRequest; | ||||
| import top.charles7c.cnadmin.system.model.vo.MessageVO; | ||||
| @@ -29,15 +30,34 @@ import top.charles7c.cnadmin.system.model.vo.MessageVO; | ||||
|  * @author BULL_BCLS | ||||
|  * @since 2023/10/15 19:05 | ||||
|  */ | ||||
| public interface MessageService extends BaseService<MessageVO, MessageVO, MessageQuery, MessageRequest> { | ||||
| public interface MessageService { | ||||
|  | ||||
|     /** | ||||
|      * 发送消息 | ||||
|      * 分页查询列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param pageQuery | ||||
|      *            分页查询条件 | ||||
|      * @return 分页列表信息 | ||||
|      */ | ||||
|     PageDataVO<MessageVO> page(MessageQuery query, PageQuery pageQuery); | ||||
|  | ||||
|     /** | ||||
|      * 新增 | ||||
|      * | ||||
|      * @param request | ||||
|      *            消息 | ||||
|      * @param userIdList | ||||
|      *            接收人 | ||||
|      *            接收人列表 | ||||
|      */ | ||||
|     void add(MessageRequest request, List<Long> userIdList); | ||||
|  | ||||
|     /** | ||||
|      * 删除 | ||||
|      * | ||||
|      * @param ids | ||||
|      *            ID 列表 | ||||
|      */ | ||||
|     void delete(List<Long> ids); | ||||
| } | ||||
| @@ -18,6 +18,8 @@ package top.charles7c.cnadmin.system.service; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import top.charles7c.cnadmin.system.model.vo.MessageUnreadVO; | ||||
|  | ||||
| /** | ||||
|  * 消息和用户关联业务接口 | ||||
|  * | ||||
| @@ -27,12 +29,23 @@ import java.util.List; | ||||
| public interface MessageUserService { | ||||
|  | ||||
|     /** | ||||
|      * 发送消息 | ||||
|      * 根据用户 ID 查询未读消息数量 | ||||
|      *  | ||||
|      * @param userId | ||||
|      *            用户 ID | ||||
|      * @param isDetail | ||||
|      *            是否查询详情 | ||||
|      * @return 未读消息信息 | ||||
|      */ | ||||
|     MessageUnreadVO countUnreadMessageByUserId(Long userId, Boolean isDetail); | ||||
|  | ||||
|     /** | ||||
|      * 新增 | ||||
|      * | ||||
|      * @param messageId | ||||
|      *            消息ID | ||||
|      *            消息 ID | ||||
|      * @param userIdList | ||||
|      *            接收人 | ||||
|      *            用户 ID 列表 | ||||
|      */ | ||||
|     void add(Long messageId, List<Long> userIdList); | ||||
|  | ||||
| @@ -45,10 +58,10 @@ public interface MessageUserService { | ||||
|     void readMessage(List<Long> ids); | ||||
|  | ||||
|     /** | ||||
|      * 删除消息 | ||||
|      * 根据消息 ID 删除 | ||||
|      * | ||||
|      * @param ids | ||||
|      *            消息ID | ||||
|      * @param messageIds | ||||
|      *            消息 ID 列表 | ||||
|      */ | ||||
|     void delete(List<Long> ids); | ||||
|     void deleteByMessageIds(List<Long> messageIds); | ||||
| } | ||||
| @@ -16,7 +16,6 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.service.impl; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
| @@ -27,13 +26,14 @@ import org.springframework.transaction.annotation.Transactional; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | ||||
|  | ||||
| import cn.hutool.core.bean.BeanUtil; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseServiceImpl; | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.query.SortQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageDataVO; | ||||
| import top.charles7c.cnadmin.common.util.helper.LoginHelper; | ||||
| import top.charles7c.cnadmin.common.service.CommonUserService; | ||||
| import top.charles7c.cnadmin.common.util.ExceptionUtils; | ||||
| import top.charles7c.cnadmin.common.util.helper.QueryHelper; | ||||
| import top.charles7c.cnadmin.common.util.validate.CheckUtils; | ||||
| import top.charles7c.cnadmin.system.mapper.MessageMapper; | ||||
| @@ -52,57 +52,48 @@ import top.charles7c.cnadmin.system.service.MessageUserService; | ||||
|  */ | ||||
| @Service | ||||
| @RequiredArgsConstructor | ||||
| public class MessageServiceImpl | ||||
|     extends BaseServiceImpl<MessageMapper, MessageDO, MessageVO, MessageVO, MessageQuery, MessageRequest> | ||||
|     implements MessageService { | ||||
| public class MessageServiceImpl implements MessageService { | ||||
|  | ||||
|     private final MessageMapper baseMapper; | ||||
|     private final MessageUserService messageUserService; | ||||
|  | ||||
|     @Override | ||||
|     public PageDataVO<MessageVO> page(MessageQuery query, PageQuery pageQuery) { | ||||
|         QueryWrapper<MessageDO> queryWrapper = QueryHelper.build(query); | ||||
|         queryWrapper.apply(null != query.getUid(), "msgUser.user_id={0}", query.getUid()) | ||||
|             .apply(null != query.getReadStatus(), "msgUser.read_status={0}", query.getReadStatus()); | ||||
|         queryWrapper.apply(null != query.getUserId(), "t2.user_id={0}", query.getUserId()) | ||||
|             .apply(null != query.getIsRead(), "t2.is_read={0}", query.getIsRead()); | ||||
|         IPage<MessageVO> page = baseMapper.selectVoPage(pageQuery.toPage(), queryWrapper); | ||||
|         page.getRecords().forEach(this::fill); | ||||
|         return PageDataVO.build(page); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<MessageVO> list(MessageQuery query, SortQuery sortQuery) { | ||||
|         QueryWrapper<MessageDO> queryWrapper = QueryHelper.build(query); | ||||
|         queryWrapper.apply("msgUser.user_id={0}", LoginHelper.getUserId()).apply(null != query.getReadStatus(), | ||||
|             "msgUser.read_status={0}", query.getReadStatus()); | ||||
|         // 设置排序 | ||||
|         this.sort(queryWrapper, sortQuery); | ||||
|         return baseMapper.selectVoList(queryWrapper); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public MessageVO get(Long id) { | ||||
|         MessageQuery messageQuery = new MessageQuery(); | ||||
|         messageQuery.setId(id); | ||||
|         PageDataVO<MessageVO> page = this.page(messageQuery, new PageQuery()); | ||||
|         List<MessageVO> messageVOList = page.getList(); | ||||
|         if (CollUtil.isEmpty(messageVOList)) { | ||||
|             return new MessageVO(); | ||||
|         } | ||||
|         MessageVO messageVO = messageVOList.get(0); | ||||
|         messageUserService.readMessage(Collections.singletonList(messageVO.getId())); | ||||
|         return messageVO; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void add(MessageRequest request, List<Long> userIdList) { | ||||
|         CheckUtils.throwIf(() -> CollUtil.isEmpty(userIdList), "消息接收人不能为空"); | ||||
|         Long messageId = super.add(request); | ||||
|         messageUserService.add(messageId, userIdList); | ||||
|         MessageDO message = BeanUtil.copyProperties(request, MessageDO.class); | ||||
|         baseMapper.insert(message); | ||||
|         messageUserService.add(message.getId(), userIdList); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public void delete(List<Long> ids) { | ||||
|         super.delete(ids); | ||||
|         messageUserService.delete(ids); | ||||
|         baseMapper.deleteBatchIds(ids); | ||||
|         messageUserService.deleteByMessageIds(ids); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 填充数据 | ||||
|      * | ||||
|      * @param message | ||||
|      *            待填充信息 | ||||
|      */ | ||||
|     private void fill(MessageVO message) { | ||||
|         Long createUser = message.getCreateUser(); | ||||
|         if (null == createUser) { | ||||
|             return; | ||||
|         } | ||||
|         CommonUserService userService = SpringUtil.getBean(CommonUserService.class); | ||||
|         message.setCreateUserString(ExceptionUtils.exToNull(() -> userService.getNicknameById(createUser))); | ||||
|     } | ||||
| } | ||||
| @@ -17,6 +17,7 @@ | ||||
| package top.charles7c.cnadmin.system.service.impl; | ||||
|  | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| @@ -24,13 +25,14 @@ import lombok.RequiredArgsConstructor; | ||||
|  | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.toolkit.Wrappers; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.enums.MessageTypeEnum; | ||||
| import top.charles7c.cnadmin.common.util.validate.CheckUtils; | ||||
| import top.charles7c.cnadmin.system.mapper.MessageUserMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.MessageUserDO; | ||||
| import top.charles7c.cnadmin.system.model.vo.MessageTypeUnreadVO; | ||||
| import top.charles7c.cnadmin.system.model.vo.MessageUnreadVO; | ||||
| import top.charles7c.cnadmin.system.service.MessageUserService; | ||||
|  | ||||
| /** | ||||
| @@ -43,32 +45,52 @@ import top.charles7c.cnadmin.system.service.MessageUserService; | ||||
| @RequiredArgsConstructor | ||||
| public class MessageUserServiceImpl implements MessageUserService { | ||||
|  | ||||
|     private final MessageUserMapper messageUserMapper; | ||||
|     private final MessageUserMapper baseMapper; | ||||
|  | ||||
|     @Override | ||||
|     public MessageUnreadVO countUnreadMessageByUserId(Long userId, Boolean isDetail) { | ||||
|         MessageUnreadVO result = new MessageUnreadVO(); | ||||
|         Long total = 0L; | ||||
|         if (Boolean.TRUE.equals(isDetail)) { | ||||
|             List<MessageTypeUnreadVO> detailList = new ArrayList<>(); | ||||
|             for (MessageTypeEnum messageType : MessageTypeEnum.values()) { | ||||
|                 MessageTypeUnreadVO vo = new MessageTypeUnreadVO(); | ||||
|                 vo.setType(messageType); | ||||
|                 Long count = baseMapper.selectUnreadCountByUserIdAndType(userId, messageType.getValue()); | ||||
|                 vo.setCount(count); | ||||
|                 detailList.add(vo); | ||||
|                 total += count; | ||||
|             } | ||||
|             result.setDetails(detailList); | ||||
|         } else { | ||||
|             total = baseMapper.selectUnreadCountByUserIdAndType(userId, null); | ||||
|         } | ||||
|         result.setTotal(total); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void add(Long messageId, List<Long> userIdList) { | ||||
|         CheckUtils.throwIf(() -> CollUtil.isEmpty(userIdList), "消息接收人不能为空"); | ||||
|         List<MessageUserDO> messageUserDOList = userIdList.stream().map(userId -> { | ||||
|             MessageUserDO messageUserDO = new MessageUserDO(); | ||||
|             messageUserDO.setUserId(userId); | ||||
|             messageUserDO.setMessageId(messageId); | ||||
|             messageUserDO.setReadStatus(false); | ||||
|             return messageUserDO; | ||||
|         CheckUtils.throwIfEmpty(userIdList, "消息接收人不能为空"); | ||||
|         List<MessageUserDO> messageUserList = userIdList.stream().map(userId -> { | ||||
|             MessageUserDO messageUser = new MessageUserDO(); | ||||
|             messageUser.setUserId(userId); | ||||
|             messageUser.setMessageId(messageId); | ||||
|             messageUser.setIsRead(false); | ||||
|             return messageUser; | ||||
|         }).collect(Collectors.toList()); | ||||
|         messageUserMapper.insertBatch(messageUserDOList); | ||||
|         baseMapper.insertBatch(messageUserList); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void readMessage(List<Long> ids) { | ||||
|         messageUserMapper.lambdaUpdate().set(MessageUserDO::getReadStatus, true) | ||||
|             .set(MessageUserDO::getReadTime, LocalDateTime.now()).eq(MessageUserDO::getReadStatus, false) | ||||
|         baseMapper.lambdaUpdate().set(MessageUserDO::getIsRead, true) | ||||
|             .set(MessageUserDO::getReadTime, LocalDateTime.now()).eq(MessageUserDO::getIsRead, false) | ||||
|             .in(CollUtil.isNotEmpty(ids), MessageUserDO::getMessageId, ids).update(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void delete(List<Long> ids) { | ||||
|         if (CollUtil.isNotEmpty(ids)) { | ||||
|             messageUserMapper.delete(Wrappers.<MessageUserDO>lambdaQuery().in(MessageUserDO::getMessageId, ids)); | ||||
|         } | ||||
|     public void deleteByMessageIds(List<Long> messageIds) { | ||||
|         baseMapper.lambdaUpdate().in(MessageUserDO::getMessageId, messageIds).remove(); | ||||
|     } | ||||
| } | ||||
| @@ -2,31 +2,13 @@ | ||||
|         "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > | ||||
| <mapper namespace="top.charles7c.cnadmin.system.mapper.MessageMapper"> | ||||
|     <select id="selectVoPage" resultType="top.charles7c.cnadmin.system.model.vo.MessageVO"> | ||||
|         SELECT msg.id, | ||||
|                msg.type, | ||||
|                msg.title, | ||||
|                msg.content, | ||||
|                msg.create_user, | ||||
|                msg.create_time, | ||||
|                msgUser.read_status, | ||||
|                msgUser.read_time, | ||||
|                msgUser.user_id | ||||
|         FROM `sys_message` msg | ||||
|                  LEFT JOIN sys_message_user msgUser ON msg.id = msgUser.message_id | ||||
|         ${ew.getCustomSqlSegment} | ||||
|     </select> | ||||
|     <select id="selectVoList" resultType="top.charles7c.cnadmin.system.model.vo.MessageVO"> | ||||
|         SELECT msg.id, | ||||
|                msg.type, | ||||
|                msg.title, | ||||
|                msg.content, | ||||
|                msg.create_user, | ||||
|                msg.create_time, | ||||
|                msgUser.read_status, | ||||
|                msgUser.read_time, | ||||
|                msgUser.user_id | ||||
|         FROM `sys_message` msg | ||||
|                  LEFT JOIN sys_message_user msgUser ON msg.id = msgUser.message_id | ||||
|         SELECT | ||||
|             t1.*, | ||||
|             t2.`user_id`, | ||||
|             t2.`is_read`, | ||||
|             t2.`read_time` | ||||
|         FROM `sys_message` AS t1 | ||||
|             LEFT JOIN `sys_message_user` AS t2 ON t2.`message_id` = t1.`id` | ||||
|         ${ew.getCustomSqlSegment} | ||||
|     </select> | ||||
| </mapper> | ||||
|   | ||||
| @@ -0,0 +1,14 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" ?> | ||||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > | ||||
| <mapper namespace="top.charles7c.cnadmin.system.mapper.MessageUserMapper"> | ||||
|     <select id="selectUnreadCountByUserIdAndType" resultType="Long"> | ||||
|         SELECT | ||||
|             COUNT(t1.`message_id`) | ||||
|         FROM `sys_message_user` AS t1 | ||||
|             LEFT JOIN `sys_message` AS t2 ON t2.`id` = t1.`message_id` | ||||
|         WHERE t1.`user_id` = #{userId} AND t1.`is_read` = 0 | ||||
|         <if test="type != null"> | ||||
|             AND t2.`type` = #{type} | ||||
|         </if> | ||||
|     </select> | ||||
| </mapper> | ||||
| @@ -3,45 +3,33 @@ import qs from 'query-string'; | ||||
|  | ||||
| const BASE_URL = '/system/message'; | ||||
|  | ||||
| export interface MessageRecord { | ||||
| export interface DataRecord { | ||||
|   id: number; | ||||
|   title: string; | ||||
|   content: string; | ||||
|   type: string; | ||||
|   createUserString: string; | ||||
|   type: number; | ||||
|   createUserString?: string; | ||||
|   createTime: string; | ||||
|   subTitle: string; | ||||
|   readStatus: boolean; | ||||
|   isRead: boolean; | ||||
|   readTime: string; | ||||
| } | ||||
|  | ||||
| export interface ChatRecord { | ||||
|   id: number; | ||||
|   username: string; | ||||
|   content: string; | ||||
|   time: string; | ||||
|   isCollect: boolean; | ||||
| } | ||||
|  | ||||
| export interface ListParam { | ||||
|   title?: string; | ||||
|   readStatus?: 0 | 1; | ||||
|   type?: string; | ||||
|   type?: number; | ||||
|   isRead?: boolean; | ||||
|   page?: number; | ||||
|   size?: number; | ||||
|   uid?: number; | ||||
|   sort?: Array<string>; | ||||
| } | ||||
|  | ||||
| export interface PageRes { | ||||
|   list: MessageRecord[]; | ||||
| export interface ListRes { | ||||
|   list: DataRecord[]; | ||||
|   total: number; | ||||
| } | ||||
|  | ||||
| export type MessageListType = MessageRecord[]; | ||||
|  | ||||
| export function page(params?: ListParam) { | ||||
|   return axios.get<PageRes>(`${BASE_URL}`, { | ||||
| export function list(params: ListParam) { | ||||
|   return axios.get<ListRes>(`${BASE_URL}`, { | ||||
|     params, | ||||
|     paramsSerializer: (obj) => { | ||||
|       return qs.stringify(obj); | ||||
| @@ -49,27 +37,24 @@ export function page(params?: ListParam) { | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export function list(params?: ListParam) { | ||||
|   return axios.get<MessageListType>(`${BASE_URL}/list`, { | ||||
|     params, | ||||
|     paramsSerializer: (obj) => { | ||||
|       return qs.stringify(obj); | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export function get(id: number) { | ||||
|   return axios.get<MessageRecord>(`${BASE_URL}/${id}`); | ||||
| } | ||||
|  | ||||
| export function del(ids: number | Array<number>) { | ||||
|   return axios.delete(`${BASE_URL}/${ids}`); | ||||
| } | ||||
|  | ||||
| export function read(data: Array<number>) { | ||||
|   return axios.patch<MessageListType>(`${BASE_URL}/read?ids=${data}`); | ||||
| export function read(ids: Array<number>) { | ||||
|   return axios.patch(`${BASE_URL}/read?ids=${ids}`); | ||||
| } | ||||
|  | ||||
| export function queryChatList() { | ||||
|   return axios.get<ChatRecord[]>('/api/chat/list'); | ||||
| export interface MessageTypeUnreadRes { | ||||
|   type: number; | ||||
|   count: number; | ||||
| } | ||||
|  | ||||
| export interface MessageUnreadRes { | ||||
|   total: number; | ||||
|   details: MessageTypeUnreadRes[]; | ||||
| } | ||||
|  | ||||
| export function countUnread(detail: boolean) { | ||||
|   return axios.get<MessageUnreadRes>(`${BASE_URL}/unread?detail=${detail}`); | ||||
| } | ||||
|   | ||||
| @@ -1,56 +1,68 @@ | ||||
| <template> | ||||
|   <a-spin style="display: block" :loading="loading"> | ||||
|     <a-tabs v-model:activeKey="messageType" type="rounded" destroy-on-hide> | ||||
|       <a-tab-pane v-for="item in message_type" :key="item.value"> | ||||
|       <a-tab-pane :key="1"> | ||||
|         <template #title> | ||||
|           <span> {{ item.label }}{{ formatUnreadLength(item.value) }} </span> | ||||
|           <span> | ||||
|             {{ $t('messageBox.tab.title.message.system') | ||||
|             }}{{ formatUnreadCount(messageType) }} | ||||
|           </span> | ||||
|         </template> | ||||
|         <a-result v-if="!renderList.length" status="404"> | ||||
|         <a-result v-if="!messageList.length" status="404"> | ||||
|           <template #subtitle> {{ $t('messageBox.noContent') }} </template> | ||||
|         </a-result> | ||||
|         <List | ||||
|           :render-list="renderList" | ||||
|           :unread-count="unreadCount" | ||||
|           @item-click="handleItemClick" | ||||
|         /> | ||||
|         <List :render-list="messageList" @item-click="handleItemClick" /> | ||||
|       </a-tab-pane> | ||||
|     </a-tabs> | ||||
|   </a-spin> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
|   import { computed, getCurrentInstance, reactive, ref, toRefs } from 'vue'; | ||||
|   import { reactive, ref, toRefs } from 'vue'; | ||||
|   import { | ||||
|     MessageListType, | ||||
|     MessageRecord, | ||||
|     DataRecord, | ||||
|     MessageUnreadRes, | ||||
|     list, | ||||
|     read, | ||||
|     countUnread, | ||||
|     ListParam, | ||||
|   } from '@/api/system/message'; | ||||
|   import useLoading from '@/hooks/loading'; | ||||
|   import List from './list.vue'; | ||||
|  | ||||
|   const { proxy } = getCurrentInstance() as any; | ||||
|   const { message_type } = proxy.useDict('message_type'); | ||||
|   const { loading, setLoading } = useLoading(true); | ||||
|   const messageType = ref('1'); | ||||
|  | ||||
|   const messageData = reactive<{ | ||||
|     renderList: MessageRecord[]; | ||||
|     messageList: MessageRecord[]; | ||||
|   }>({ | ||||
|     renderList: [], | ||||
|     messageList: [], | ||||
|   const { loading, setLoading } = useLoading(); | ||||
|   const messageType = ref(1); | ||||
|   const unreadCount = ref<MessageUnreadRes>(); | ||||
|   const messageList = ref<DataRecord[]>([]); | ||||
|   const data = reactive({ | ||||
|     // 查询参数 | ||||
|     queryParams: { | ||||
|       type: messageType.value, | ||||
|       isRead: false, | ||||
|       page: 1, | ||||
|       size: 3, | ||||
|       sort: ['createTime,desc'], | ||||
|     }, | ||||
|   }); | ||||
|   toRefs(messageData); | ||||
|   const { queryParams } = toRefs(data); | ||||
|  | ||||
|   /** | ||||
|    * 查询列表 | ||||
|    * 查询未读消息数量 | ||||
|    */ | ||||
|   async function fetchSourceData() { | ||||
|   async function getUnreadCount() { | ||||
|     const res = await countUnread(true); | ||||
|     unreadCount.value = res.data; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 查询未读消息列表 | ||||
|    */ | ||||
|   async function getList(params: ListParam = { ...queryParams.value }) { | ||||
|     await getUnreadCount(); | ||||
|     setLoading(true); | ||||
|     try { | ||||
|       list({ sort: ['createTime,desc'] }).then((res) => { | ||||
|         messageData.messageList = res.data; | ||||
|       await list(params).then((res) => { | ||||
|         messageList.value = res.data.list; | ||||
|       }); | ||||
|     } finally { | ||||
|       setLoading(false); | ||||
| @@ -60,49 +72,25 @@ | ||||
|   /** | ||||
|    * 将消息设置为已读 | ||||
|    * | ||||
|    * @param data 消息列表 | ||||
|    * @param items 消息列表 | ||||
|    */ | ||||
|   async function readMessage(data: MessageListType) { | ||||
|     const ids = data.map((item) => item.id); | ||||
|   async function readMessage(items: DataRecord[]) { | ||||
|     const ids = items.map((item) => item.id); | ||||
|     await read(ids); | ||||
|     await fetchSourceData(); | ||||
|     await getList(); | ||||
|     await getUnreadCount(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 每个消息类型下的消息列表 | ||||
|    */ | ||||
|   const renderList = computed(() => { | ||||
|     return messageData.messageList | ||||
|       .filter((item) => item.type === messageType.value && !item.readStatus) | ||||
|       .splice(0, 3); | ||||
|   }); | ||||
|  | ||||
|   /** | ||||
|    * 未读消息数量 | ||||
|    */ | ||||
|   const unreadCount = computed(() => { | ||||
|     return renderList.value.filter((item) => !item.readStatus).length; | ||||
|   }); | ||||
|  | ||||
|   /** | ||||
|    * 未读消息列表 | ||||
|    * | ||||
|    * @param type 消息类型 | ||||
|    */ | ||||
|   const getUnreadList = (type: string) => { | ||||
|     return messageData.messageList.filter( | ||||
|       (item) => item.type === type && !item.readStatus | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 每个类型的未读消息数量 | ||||
|    * | ||||
|    * @param type 消息类型 | ||||
|    */ | ||||
|   const formatUnreadLength = (type: string) => { | ||||
|     const unreadList = getUnreadList(type); | ||||
|     return unreadList.length ? `(${unreadList.length})` : ``; | ||||
|   const formatUnreadCount = (type: number) => { | ||||
|     const count = unreadCount.value?.details.find( | ||||
|       (item) => item.type === type | ||||
|     )?.count; | ||||
|     return count && count !== 0 ? `(${count})` : ''; | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
| @@ -110,10 +98,10 @@ | ||||
|    * | ||||
|    * @param items 消息 | ||||
|    */ | ||||
|   const handleItemClick = (items: MessageListType) => { | ||||
|     if (renderList.value.length) readMessage([...items]); | ||||
|   const handleItemClick = (items: DataRecord[]) => { | ||||
|     if (messageList.value.length) readMessage([...items]); | ||||
|   }; | ||||
|   fetchSourceData(); | ||||
|   getList(); | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="less"> | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
|       :key="item.id" | ||||
|       action-layout="vertical" | ||||
|       :style="{ | ||||
|         opacity: item.readStatus ? 0.5 : 1, | ||||
|         opacity: item.isRead ? 0.5 : 1, | ||||
|       }" | ||||
|     > | ||||
|       <div class="item-wrap" @click="onItemClick(item)"> | ||||
| @@ -13,9 +13,6 @@ | ||||
|           <template #title> | ||||
|             <a-space :size="4"> | ||||
|               <span>{{ item.title }}</span> | ||||
|               <a-typography-text type="secondary"> | ||||
|                 {{ item.subTitle }} | ||||
|               </a-typography-text> | ||||
|             </a-space> | ||||
|           </template> | ||||
|           <template #description> | ||||
| @@ -41,10 +38,12 @@ | ||||
|         :class="{ 'add-border-top': renderList.length < showMax }" | ||||
|       > | ||||
|         <div class="footer-wrap"> | ||||
|           <a-link @click="allRead">{{ $t('messageBox.allRead') }}</a-link> | ||||
|           <a-link @click="handleReadAll">{{ $t('messageBox.allRead') }}</a-link> | ||||
|         </div> | ||||
|         <div class="footer-wrap"> | ||||
|           <a-link @click="toList">{{ $t('messageBox.viewMore') }}</a-link> | ||||
|           <a-link @click="handleViewMore">{{ | ||||
|             $t('messageBox.viewMore') | ||||
|           }}</a-link> | ||||
|         </div> | ||||
|       </a-space> | ||||
|     </template> | ||||
| @@ -58,33 +57,28 @@ | ||||
| <script lang="ts" setup> | ||||
|   import { PropType } from 'vue'; | ||||
|   import { useRouter } from 'vue-router'; | ||||
|   import { MessageRecord, MessageListType } from '@/api/system/message'; | ||||
|   import { DataRecord } from '@/api/system/message'; | ||||
|  | ||||
|   const router = useRouter(); | ||||
|  | ||||
|   const props = defineProps({ | ||||
|     renderList: { | ||||
|       type: Array as PropType<MessageListType>, | ||||
|       type: Array as PropType<DataRecord[]>, | ||||
|       required: true, | ||||
|     }, | ||||
|     unreadCount: { | ||||
|       type: Number, | ||||
|       default: 0, | ||||
|     }, | ||||
|   }); | ||||
|   const emit = defineEmits(['itemClick']); | ||||
|  | ||||
|   /** | ||||
|    * 全部已读 | ||||
|    */ | ||||
|   const allRead = () => { | ||||
|   const handleReadAll = () => { | ||||
|     emit('itemClick', [...props.renderList]); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 查看更多 | ||||
|    */ | ||||
|   const toList = () => { | ||||
|   const handleViewMore = () => { | ||||
|     router.push({ | ||||
|       name: 'Message', | ||||
|     }); | ||||
| @@ -92,10 +86,11 @@ | ||||
|  | ||||
|   /** | ||||
|    * 点击消息 | ||||
|    * | ||||
|    * @param item 消息 | ||||
|    */ | ||||
|   const onItemClick = (item: MessageRecord) => { | ||||
|     if (!item.readStatus) { | ||||
|   const onItemClick = (item: DataRecord) => { | ||||
|     if (!item.isRead) { | ||||
|       emit('itemClick', [item]); | ||||
|     } | ||||
|   }; | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| export default { | ||||
|   'messageBox.tab.title.message': 'Message', | ||||
|   'messageBox.tab.title.notice': 'Notice', | ||||
|   'messageBox.tab.title.todo': 'Todo', | ||||
|   'messageBox.tab.title.message.system': 'System Message', | ||||
|   'messageBox.tab.button': 'empty', | ||||
|   'messageBox.allRead': 'All Read', | ||||
|   'messageBox.viewMore': 'View More', | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| export default { | ||||
|   'messageBox.tab.title.message': '消息', | ||||
|   'messageBox.tab.title.notice': '通知', | ||||
|   'messageBox.tab.title.todo': '待办', | ||||
|   'messageBox.tab.title.message.system': '系统消息', | ||||
|   'messageBox.tab.button': '清空', | ||||
|   'messageBox.allRead': '全部已读', | ||||
|   'messageBox.viewMore': '查看更多', | ||||
|   | ||||
| @@ -193,7 +193,7 @@ | ||||
|   import { computed, ref, inject, watchEffect } from 'vue'; | ||||
|   import { useDark, useToggle, useFullscreen } from '@vueuse/core'; | ||||
|   import { useAppStore, useUserStore } from '@/store'; | ||||
|   import { list } from '@/api/system/message'; | ||||
|   import { countUnread } from '@/api/system/message'; | ||||
|   import { LOCALE_OPTIONS } from '@/locale'; | ||||
|   import useLocale from '@/hooks/locale'; | ||||
|   import useUser from '@/hooks/user'; | ||||
| @@ -224,12 +224,17 @@ | ||||
|     }, | ||||
|   }); | ||||
|   const toggleTheme = useToggle(isDark); | ||||
|  | ||||
|   const unReadMessageCount = ref(0); | ||||
|   watchEffect(async () => { | ||||
|     const res = await list({ sort: ['createTime,desc'], readStatus: 0 }); | ||||
|     unReadMessageCount.value = res.data?.length ?? 0; | ||||
|   }); | ||||
|  | ||||
|   /** | ||||
|    * 查询未读消息总数 | ||||
|    */ | ||||
|   async function getUnreadCount() { | ||||
|     const res = await countUnread(false); | ||||
|     unReadMessageCount.value = res.data.total; | ||||
|   } | ||||
|   getUnreadCount(); | ||||
|   setInterval(getUnreadCount, 10000); | ||||
|  | ||||
|   const handleToggleTheme = () => { | ||||
|     toggleTheme(); | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import localeRole from '@/views/system/role/locale/en-US'; | ||||
| import localeMenu from '@/views/system/menu/locale/en-US'; | ||||
| import localeDept from '@/views/system/dept/locale/en-US'; | ||||
| import localeAnnouncement from '@/views/system/announcement/locale/en-US'; | ||||
| import localeNotice from '@/views/system/message/locale/en-US'; | ||||
| import localeMessage from '@/views/system/message/locale/en-US'; | ||||
| import localeDict from '@/views/system/dict/locale/en-US'; | ||||
| import localeConfig from '@/views/system/config/locale/en-US'; | ||||
|  | ||||
| @@ -63,7 +63,7 @@ export default { | ||||
|   ...localeMenu, | ||||
|   ...localeDept, | ||||
|   ...localeAnnouncement, | ||||
|   ...localeNotice, | ||||
|   ...localeMessage, | ||||
|   ...localeDict, | ||||
|   ...localeConfig, | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import localeRole from '@/views/system/role/locale/zh-CN'; | ||||
| import localeMenu from '@/views/system/menu/locale/zh-CN'; | ||||
| import localeDept from '@/views/system/dept/locale/zh-CN'; | ||||
| import localeAnnouncement from '@/views/system/announcement/locale/zh-CN'; | ||||
| import locaoNotice from '@/views/system/message/locale/zh-CN'; | ||||
| import localeMessage from '@/views/system/message/locale/zh-CN'; | ||||
| import localeDict from '@/views/system/dict/locale/zh-CN'; | ||||
| import localeConfig from '@/views/system/config/locale/zh-CN'; | ||||
|  | ||||
| @@ -63,7 +63,7 @@ export default { | ||||
|   ...localeMenu, | ||||
|   ...localeDept, | ||||
|   ...localeAnnouncement, | ||||
|   ...locaoNotice, | ||||
|   ...localeMessage, | ||||
|   ...localeDict, | ||||
|   ...localeConfig, | ||||
|  | ||||
|   | ||||
| @@ -62,7 +62,7 @@ const System: AppRouteRecordRaw = { | ||||
|       path: '/system/message', | ||||
|       component: () => import('@/views/system/message/index.vue'), | ||||
|       meta: { | ||||
|         locale: 'menu.system.message', | ||||
|         locale: 'menu.system.message.list', | ||||
|         requiresAuth: true, | ||||
|       }, | ||||
|     }, | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|             <a-form-item field="title" hide-label> | ||||
|               <a-input | ||||
|                 v-model="queryParams.title" | ||||
|                 placeholder="输入主题搜索" | ||||
|                 placeholder="输入标题搜索" | ||||
|                 allow-clear | ||||
|                 style="width: 230px" | ||||
|                 @press-enter="handleQuery" | ||||
| @@ -19,16 +19,15 @@ | ||||
|             <a-form-item field="type" hide-label> | ||||
|               <a-select | ||||
|                 v-model="queryParams.type" | ||||
|                 :options="message_type" | ||||
|                 :options="message_type_enum" | ||||
|                 placeholder="类型搜索" | ||||
|                 allow-clear | ||||
|                 style="width: 150px" | ||||
|               /> | ||||
|             </a-form-item> | ||||
|  | ||||
|             <a-form-item field="type" hide-label> | ||||
|             <a-form-item field="isRead" hide-label> | ||||
|               <a-select | ||||
|                 v-model="queryParams.readStatus" | ||||
|                 v-model="queryParams.isRead" | ||||
|                 :style="{ width: '150px' }" | ||||
|                 placeholder="是否已读" | ||||
|                 allow-clear | ||||
| @@ -58,7 +57,7 @@ | ||||
|                   type="primary" | ||||
|                   status="success" | ||||
|                   :disabled="readMultiple" | ||||
|                   :title="readMultiple ? '请选择要读取的数据' : ''" | ||||
|                   :title="readMultiple ? '请选择数据' : ''" | ||||
|                   @click="handleBatchRedaMessage" | ||||
|                 > | ||||
|                   <template #icon><icon-check /></template>标记已读 | ||||
| @@ -123,33 +122,27 @@ | ||||
|               {{ rowIndex + 1 + (queryParams.page - 1) * queryParams.size }} | ||||
|             </template> | ||||
|           </a-table-column> | ||||
|           <a-table-column title="主题"> | ||||
|           <a-table-column title="标题"> | ||||
|             <template #cell="{ record }"> | ||||
|               <a-link @click="toDetail(record.id)">{{ record.title }}</a-link> | ||||
|               <a-link @click="toDetail(record)">{{ record.title }}</a-link> | ||||
|             </template> | ||||
|           </a-table-column> | ||||
|           <a-table-column title="类型" align="center"> | ||||
|             <template #cell="{ record }"> | ||||
|               <dict-tag :value="record.type" :dict="message_type" /> | ||||
|               <dict-tag :value="record.type" :dict="message_type_enum" /> | ||||
|             </template> | ||||
|           </a-table-column> | ||||
|           <a-table-column title="是否已读" align="center"> | ||||
|             <template #cell="{ record }"> | ||||
|               <a-tag v-if="record.readStatus" color="green">是</a-tag> | ||||
|               <a-tag v-if="record.isRead" color="green">是</a-tag> | ||||
|               <a-tag v-else color="red">否</a-tag> | ||||
|             </template> | ||||
|           </a-table-column> | ||||
|           <a-table-column title="发送时间" data-index="createTime" /> | ||||
|           <a-table-column | ||||
|             v-if=" | ||||
|               checkPermission(['system:message:delete']) | ||||
|             " | ||||
|             title="操作" | ||||
|             align="center" | ||||
|           > | ||||
|           <a-table-column title="操作" align="center"> | ||||
|             <template #cell="{ record }"> | ||||
|               <a-button | ||||
|                 :disabled="record.readStatus" | ||||
|                 :disabled="record.isRead" | ||||
|                 type="text" | ||||
|                 size="small" | ||||
|                 title="标记已读" | ||||
| @@ -157,7 +150,6 @@ | ||||
|               > | ||||
|                 <template #icon><icon-check /></template>标记已读 | ||||
|               </a-button> | ||||
|  | ||||
|               <a-popconfirm | ||||
|                 content="确定要删除当前选中的数据吗?" | ||||
|                 type="warning" | ||||
| @@ -189,60 +181,47 @@ | ||||
|         @cancel="handleDetailCancel" | ||||
|       > | ||||
|         <a-descriptions :column="2" bordered size="large"> | ||||
|           <a-descriptions-item label="主题"> | ||||
|           <a-descriptions-item label="标题"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ dataDetail.title }}</span> | ||||
|           </a-descriptions-item> | ||||
|  | ||||
|           <a-descriptions-item label="类型"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else> | ||||
|               <dict-tag :value="dataDetail.type" :dict="message_type" /> | ||||
|               <dict-tag :value="dataDetail.type" :dict="message_type_enum" /> | ||||
|             </span> | ||||
|           </a-descriptions-item> | ||||
|  | ||||
|           <a-descriptions-item label="发送人"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else-if="dataDetail.createUserString">{{ | ||||
|               dataDetail.createUserString | ||||
|             }}</span> | ||||
|             <dict-tag | ||||
|               v-if="dataDetail.createUserString == null" | ||||
|               :value="dataDetail.type" | ||||
|               :dict="message_type" | ||||
|             /> | ||||
|           </a-descriptions-item> | ||||
|  | ||||
|           <a-descriptions-item label="发送时间"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ dataDetail.createTime }}</span> | ||||
|           </a-descriptions-item> | ||||
|  | ||||
|           <a-descriptions-item label="是否已读"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else> | ||||
|               <a-tag v-if="dataDetail.readStatus" color="green">是</a-tag> | ||||
|               <a-tag v-if="dataDetail.isRead" color="green">是</a-tag> | ||||
|               <a-tag v-else color="red">否</a-tag> | ||||
|             </span> | ||||
|           </a-descriptions-item> | ||||
|  | ||||
|           <a-descriptions-item label="读取时间"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ dataDetail.readTime }}</span> | ||||
|           </a-descriptions-item> | ||||
|  | ||||
|           <a-descriptions-item label="发送人"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ dataDetail.createUserString }}</span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="发送时间"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
|             </a-skeleton> | ||||
|             <span v-else>{{ dataDetail.createTime }}</span> | ||||
|           </a-descriptions-item> | ||||
|           <a-descriptions-item label="内容"> | ||||
|             <a-skeleton v-if="detailLoading" :animation="true"> | ||||
|               <a-skeleton-line :rows="1" /> | ||||
| @@ -257,29 +236,19 @@ | ||||
|  | ||||
| <script lang="ts" setup> | ||||
|   import { getCurrentInstance, ref, toRefs, reactive } from 'vue'; | ||||
|   import { | ||||
|     MessageRecord, | ||||
|     page, | ||||
|     ListParam, | ||||
|     get, | ||||
|     del, | ||||
|     read, | ||||
|   } from '@/api/system/message'; | ||||
|   import checkPermission from '@/utils/permission'; | ||||
|   import { DataRecord, ListParam, list, del, read } from '@/api/system/message'; | ||||
|  | ||||
|   const { proxy } = getCurrentInstance() as any; | ||||
|   const { message_type } = proxy.useDict('message_type'); | ||||
|  | ||||
|   const dataList = ref<MessageRecord[]>([]); | ||||
|   const dataDetail = ref<MessageRecord>({ | ||||
|   const { message_type_enum } = proxy.useDict('message_type_enum'); | ||||
|   const dataList = ref<DataRecord[]>([]); | ||||
|   const dataDetail = ref<DataRecord>({ | ||||
|     id: 0, | ||||
|     title: '', | ||||
|     content: '', | ||||
|     type: '', | ||||
|     type: 1, | ||||
|     createUserString: '', | ||||
|     createTime: '', | ||||
|     subTitle: '', | ||||
|     readStatus: false, | ||||
|     isRead: false, | ||||
|     readTime: '', | ||||
|   }); | ||||
|   const total = ref(0); | ||||
| @@ -296,21 +265,22 @@ | ||||
|     // 查询参数 | ||||
|     queryParams: { | ||||
|       title: undefined, | ||||
|       readStatus: undefined, | ||||
|       type: undefined, | ||||
|       isRead: undefined, | ||||
|       page: 1, | ||||
|       size: 10, | ||||
|       sort: ['createTime,desc'], | ||||
|       sort: ['isRead,asc', 'createTime,desc'], | ||||
|     }, | ||||
|   }); | ||||
|   const { queryParams } = toRefs(data); | ||||
|  | ||||
|   /** | ||||
|    * 查询列表 | ||||
|    * | ||||
|    */ | ||||
|   const getList = (params: ListParam = { ...queryParams.value }) => { | ||||
|     loading.value = true; | ||||
|     page(params) | ||||
|     list(params) | ||||
|       .then((res) => { | ||||
|         dataList.value = res.data.list; | ||||
|         total.value = res.data.total; | ||||
| @@ -324,13 +294,11 @@ | ||||
|   /** | ||||
|    * 查看详情 | ||||
|    * | ||||
|    * @param id ID | ||||
|    * @param record 记录信息 | ||||
|    */ | ||||
|   const toDetail = async (id: number) => { | ||||
|   const toDetail = async (record: DataRecord) => { | ||||
|     detailVisible.value = true; | ||||
|     get(id).then((res) => { | ||||
|       dataDetail.value = res.data; | ||||
|     }); | ||||
|     dataDetail.value = record; | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
| @@ -377,7 +345,7 @@ | ||||
|    */ | ||||
|   const handleBatchRedaMessage = () => { | ||||
|     if (ids.value.length === 0) { | ||||
|       proxy.$message.info('请选择要读取的数据'); | ||||
|       proxy.$message.info('请选择数据'); | ||||
|     } else { | ||||
|       handleReadMessage(ids.value); | ||||
|     } | ||||
| @@ -410,7 +378,7 @@ | ||||
|    */ | ||||
|   const handleSelectionChange = (rowKeys: Array<any>) => { | ||||
|     const unReadMessageList = dataList.value.filter( | ||||
|       (item) => rowKeys.indexOf(item.id) !== -1 && !item.readStatus | ||||
|       (item) => rowKeys.indexOf(item.id) !== -1 && !item.isRead | ||||
|     ); | ||||
|     readMultiple.value = !unReadMessageList.length; | ||||
|     ids.value = rowKeys; | ||||
| @@ -460,25 +428,4 @@ | ||||
|   }; | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="less"> | ||||
|   :deep(.github-markdown-body) { | ||||
|     padding: 16px 32px 5px; | ||||
|   } | ||||
|  | ||||
|   :deep(.arco-form-item-label-tooltip) { | ||||
|     margin-left: 3px; | ||||
|   } | ||||
|  | ||||
|   .meta-data { | ||||
|     font-size: 15px; | ||||
|     text-align: center; | ||||
|   } | ||||
|  | ||||
|   .icon { | ||||
|     margin-right: 3px; | ||||
|   } | ||||
|  | ||||
|   .update-time-row { | ||||
|     text-align: right; | ||||
|   } | ||||
| </style> | ||||
| <style scoped lang="less"></style> | ||||
|   | ||||
| @@ -16,8 +16,6 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.webapi.controller.system; | ||||
|  | ||||
| import static top.charles7c.cnadmin.common.annotation.CrudRequestMapping.Api; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
| @@ -27,14 +25,16 @@ import io.swagger.v3.oas.annotations.Parameter; | ||||
| import io.swagger.v3.oas.annotations.enums.ParameterIn; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
|  | ||||
| import org.springframework.web.bind.annotation.PatchMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.annotation.CrudRequestMapping; | ||||
| import top.charles7c.cnadmin.common.base.BaseController; | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageDataVO; | ||||
| import top.charles7c.cnadmin.common.model.vo.R; | ||||
| import top.charles7c.cnadmin.common.util.helper.LoginHelper; | ||||
| import top.charles7c.cnadmin.monitor.annotation.Log; | ||||
| import top.charles7c.cnadmin.system.model.query.MessageQuery; | ||||
| import top.charles7c.cnadmin.system.model.request.MessageRequest; | ||||
| import top.charles7c.cnadmin.system.model.vo.MessageUnreadVO; | ||||
| import top.charles7c.cnadmin.system.model.vo.MessageVO; | ||||
| import top.charles7c.cnadmin.system.service.MessageService; | ||||
| import top.charles7c.cnadmin.system.service.MessageUserService; | ||||
| @@ -48,16 +48,39 @@ import top.charles7c.cnadmin.system.service.MessageUserService; | ||||
| @Tag(name = "消息管理 API") | ||||
| @RestController | ||||
| @RequiredArgsConstructor | ||||
| @CrudRequestMapping(value = "/system/message", api = {Api.PAGE, Api.GET, Api.DELETE, Api.LIST}) | ||||
| public class MessageController | ||||
|     extends BaseController<MessageService, MessageVO, MessageVO, MessageQuery, MessageRequest> { | ||||
| @RequestMapping("/system/message") | ||||
| public class MessageController { | ||||
|  | ||||
|     private final MessageService baseService; | ||||
|     private final MessageUserService messageUserService; | ||||
|  | ||||
|     @Operation(summary = "分页查询列表", description = "分页查询列表") | ||||
|     @GetMapping | ||||
|     public PageDataVO<MessageVO> page(MessageQuery query, @Validated PageQuery pageQuery) { | ||||
|         query.setUserId(LoginHelper.getUserId()); | ||||
|         return baseService.page(query, pageQuery); | ||||
|     } | ||||
|  | ||||
|     @Operation(summary = "删除数据", description = "删除数据") | ||||
|     @Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH) | ||||
|     @DeleteMapping("/{ids}") | ||||
|     public R delete(@PathVariable List<Long> ids) { | ||||
|         baseService.delete(ids); | ||||
|         return R.ok("删除成功"); | ||||
|     } | ||||
|  | ||||
|     @Operation(description = "标记已读", summary = "将消息标记为已读状态") | ||||
|     @Parameter(name = "ids", description = "消息ID列表", example = "1,2", in = ParameterIn.QUERY) | ||||
|     @PatchMapping("/read") | ||||
|     public void readMessage(@RequestParam(required = false) List<Long> ids) { | ||||
|         messageUserService.readMessage(ids); | ||||
|     } | ||||
|  | ||||
|     @Log(ignore = true) | ||||
|     @Operation(description = "查询未读消息数量", summary = "查询当前用户的未读消息数量") | ||||
|     @Parameter(name = "isDetail", description = "是否查询详情", example = "true", in = ParameterIn.QUERY) | ||||
|     @GetMapping("/unread") | ||||
|     public MessageUnreadVO countUnreadMessage(@RequestParam(required = false) Boolean detail) { | ||||
|         return messageUserService.countUnreadMessageByUserId(LoginHelper.getUserId(), detail); | ||||
|     } | ||||
| } | ||||
| @@ -7,14 +7,3 @@ INSERT IGNORE INTO `sys_menu` | ||||
| VALUES | ||||
| (1060, '消息管理', 1000, 2, '/system/message', 'Message', 'system/message/index', 'notification', b'0', b'0', b'0', 'system:message:list', 6, 1, 1, NOW(), NULL, NULL), | ||||
| (1061, '消息删除', 1060, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:message:delete', 1, 1, 1, NOW(), NULL, NULL); | ||||
|  | ||||
| -- 初始化默认字典 | ||||
| INSERT IGNORE INTO `sys_dict` | ||||
| (`id`, `name`, `code`, `description`, `is_system`, `create_user`, `create_time`, `update_user`, `update_time`) | ||||
| VALUES | ||||
| (2, '消息类型', 'message_type', NULL, b'1', 1, NOW(), NULL, NULL); | ||||
|  | ||||
| INSERT IGNORE INTO `sys_dict_item` | ||||
| (`id`, `label`, `value`, `color`, `sort`, `description`, `dict_id`, `create_user`, `create_time`, `update_user`, `update_time`) | ||||
| VALUES | ||||
| (3, '系统消息', '1', 'blue', 1, NULL, 2, 1, NOW(), NULL, NULL); | ||||
|   | ||||
| @@ -14,9 +14,9 @@ CREATE TABLE IF NOT EXISTS `sys_user_social` ( | ||||
| -- changeset BUSS_BCLS:2 | ||||
| CREATE TABLE IF NOT EXISTS `sys_message` ( | ||||
|     `id`          bigint(20)   AUTO_INCREMENT              COMMENT 'ID', | ||||
|     `title`       varchar(50)  NOT NULL       COMMENT '主题', | ||||
|     `title`       varchar(50)  NOT NULL                    COMMENT '标题', | ||||
|     `content`     varchar(255) DEFAULT NULL                COMMENT '内容', | ||||
|     `type`        varchar(30)  NOT NULL       COMMENT '类型', | ||||
|     `type`        tinyint(1)   UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(1:系统消息)', | ||||
|     `create_user` bigint(20)   DEFAULT NULL                COMMENT '创建人', | ||||
|     `create_time` datetime     NOT NULL                    COMMENT '创建时间', | ||||
|     PRIMARY KEY (`id`) USING BTREE | ||||
| @@ -25,7 +25,7 @@ CREATE TABLE IF NOT EXISTS `sys_message` ( | ||||
| CREATE TABLE IF NOT EXISTS `sys_message_user` ( | ||||
|     `message_id` bigint(20) NOT NULL              COMMENT '消息ID', | ||||
|     `user_id`    bigint(11) NOT NULL              COMMENT '用户ID', | ||||
|     `read_status` bit(1)     NOT NULL DEFAULT b'0' COMMENT '是否已读', | ||||
|     `is_read`    bit(1)     NOT NULL DEFAULT b'0' COMMENT '是否已读', | ||||
|     `read_time`  datetime   DEFAULT NULL          COMMENT '读取时间', | ||||
|     PRIMARY KEY (`message_id`,`user_id`) USING BTREE | ||||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息和用户关联表'; | ||||
		Reference in New Issue
	
	Block a user