mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 10:57:13 +08:00 
			
		
		
		
	feat: 新增系统管理/消息管理(列表、查看详情、标记已读、全部已读、删除)
This commit is contained in:
		| @@ -149,14 +149,26 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO, | |||||||
|     protected <E> List<E> list(Q query, SortQuery sortQuery, Class<E> targetClass) { |     protected <E> List<E> list(Q query, SortQuery sortQuery, Class<E> targetClass) { | ||||||
|         QueryWrapper<T> queryWrapper = QueryHelper.build(query); |         QueryWrapper<T> queryWrapper = QueryHelper.build(query); | ||||||
|         // 设置排序 |         // 设置排序 | ||||||
|  |         this.sort(queryWrapper, sortQuery); | ||||||
|  |         List<T> entityList = baseMapper.selectList(queryWrapper); | ||||||
|  |         return BeanUtil.copyToList(entityList, targetClass); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 设置排序 | ||||||
|  |      *  | ||||||
|  |      * @param queryWrapper | ||||||
|  |      *            查询 Wrapper | ||||||
|  |      * @param sortQuery | ||||||
|  |      *            排序查询条件 | ||||||
|  |      */ | ||||||
|  |     protected void sort(QueryWrapper<T> queryWrapper, SortQuery sortQuery) { | ||||||
|         Sort sort = Opt.ofNullable(sortQuery).orElseGet(SortQuery::new).getSort(); |         Sort sort = Opt.ofNullable(sortQuery).orElseGet(SortQuery::new).getSort(); | ||||||
|         for (Sort.Order order : sort) { |         for (Sort.Order order : sort) { | ||||||
|             if (null != order) { |             if (null != order) { | ||||||
|                 queryWrapper.orderBy(true, order.isAscending(), StrUtil.toUnderlineCase(order.getProperty())); |                 queryWrapper.orderBy(true, order.isAscending(), StrUtil.toUnderlineCase(order.getProperty())); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         List<T> entityList = baseMapper.selectList(queryWrapper); |  | ||||||
|         return BeanUtil.copyToList(entityList, targetClass); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -67,4 +67,9 @@ public class SysConsts { | |||||||
|      * VO 描述类字段后缀 |      * VO 描述类字段后缀 | ||||||
|      */ |      */ | ||||||
|     public static final String VO_DESCRIPTION_FIELD_SUFFIX = "String"; |     public static final String VO_DESCRIPTION_FIELD_SUFFIX = "String"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 系统消息类型 | ||||||
|  |      */ | ||||||
|  |     public static final String SYSTEM_MESSAGE_TYPE = "1"; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ import cn.hutool.core.bean.BeanUtil; | |||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
| import cn.hutool.core.lang.tree.Tree; | import cn.hutool.core.lang.tree.Tree; | ||||||
| import cn.hutool.core.lang.tree.TreeNodeConfig; | import cn.hutool.core.lang.tree.TreeNodeConfig; | ||||||
|  | import cn.hutool.core.map.MapUtil; | ||||||
| import cn.hutool.core.util.IdUtil; | import cn.hutool.core.util.IdUtil; | ||||||
| import cn.hutool.core.util.RandomUtil; | import cn.hutool.core.util.RandomUtil; | ||||||
| import cn.hutool.core.util.ReUtil; | import cn.hutool.core.util.ReUtil; | ||||||
| @@ -38,6 +39,7 @@ import top.charles7c.cnadmin.auth.model.vo.RouteVO; | |||||||
| import top.charles7c.cnadmin.auth.service.LoginService; | import top.charles7c.cnadmin.auth.service.LoginService; | ||||||
| import top.charles7c.cnadmin.auth.service.PermissionService; | import top.charles7c.cnadmin.auth.service.PermissionService; | ||||||
| import top.charles7c.cnadmin.common.annotation.TreeField; | import top.charles7c.cnadmin.common.annotation.TreeField; | ||||||
|  | import top.charles7c.cnadmin.common.config.properties.ProjectProperties; | ||||||
| import top.charles7c.cnadmin.common.constant.RegexConsts; | import top.charles7c.cnadmin.common.constant.RegexConsts; | ||||||
| import top.charles7c.cnadmin.common.constant.SysConsts; | import top.charles7c.cnadmin.common.constant.SysConsts; | ||||||
| import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum; | import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum; | ||||||
| @@ -48,9 +50,11 @@ import top.charles7c.cnadmin.common.util.SecureUtils; | |||||||
| import top.charles7c.cnadmin.common.util.TreeUtils; | import top.charles7c.cnadmin.common.util.TreeUtils; | ||||||
| import top.charles7c.cnadmin.common.util.helper.LoginHelper; | import top.charles7c.cnadmin.common.util.helper.LoginHelper; | ||||||
| import top.charles7c.cnadmin.common.util.validate.CheckUtils; | import top.charles7c.cnadmin.common.util.validate.CheckUtils; | ||||||
|  | import top.charles7c.cnadmin.system.enums.MessageTemplateEnum; | ||||||
| import top.charles7c.cnadmin.system.model.entity.RoleDO; | import top.charles7c.cnadmin.system.model.entity.RoleDO; | ||||||
| import top.charles7c.cnadmin.system.model.entity.UserDO; | import top.charles7c.cnadmin.system.model.entity.UserDO; | ||||||
| import top.charles7c.cnadmin.system.model.entity.UserSocialDO; | import top.charles7c.cnadmin.system.model.entity.UserSocialDO; | ||||||
|  | import top.charles7c.cnadmin.system.model.request.MessageRequest; | ||||||
| import top.charles7c.cnadmin.system.model.vo.DeptDetailVO; | import top.charles7c.cnadmin.system.model.vo.DeptDetailVO; | ||||||
| import top.charles7c.cnadmin.system.model.vo.MenuVO; | import top.charles7c.cnadmin.system.model.vo.MenuVO; | ||||||
| import top.charles7c.cnadmin.system.service.*; | import top.charles7c.cnadmin.system.service.*; | ||||||
| @@ -74,6 +78,8 @@ public class LoginServiceImpl implements LoginService { | |||||||
|     private final PermissionService permissionService; |     private final PermissionService permissionService; | ||||||
|     private final UserRoleService userRoleService; |     private final UserRoleService userRoleService; | ||||||
|     private final UserSocialService userSocialService; |     private final UserSocialService userSocialService; | ||||||
|  |     private final MessageService messageService; | ||||||
|  |     private final ProjectProperties projectProperties; | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public String accountLogin(String username, String password) { |     public String accountLogin(String username, String password) { | ||||||
| @@ -131,6 +137,7 @@ public class LoginServiceImpl implements LoginService { | |||||||
|             userSocial.setUserId(userId); |             userSocial.setUserId(userId); | ||||||
|             userSocial.setSource(source); |             userSocial.setSource(source); | ||||||
|             userSocial.setOpenId(openId); |             userSocial.setOpenId(openId); | ||||||
|  |             this.sendMsg(user); | ||||||
|         } else { |         } else { | ||||||
|             user = BeanUtil.toBean(userService.get(userSocial.getUserId()), UserDO.class); |             user = BeanUtil.toBean(userService.get(userSocial.getUserId()), UserDO.class); | ||||||
|         } |         } | ||||||
| @@ -205,4 +212,22 @@ public class LoginServiceImpl implements LoginService { | |||||||
|         DeptDetailVO deptDetailVO = deptService.get(user.getDeptId()); |         DeptDetailVO deptDetailVO = deptService.get(user.getDeptId()); | ||||||
|         CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, deptDetailVO.getStatus(), "此账号所属部门已被禁用,如有疑问,请联系管理员"); |         CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, deptDetailVO.getStatus(), "此账号所属部门已被禁用,如有疑问,请联系管理员"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 发送消息 | ||||||
|  |      *  | ||||||
|  |      * @param user | ||||||
|  |      *            用户信息 | ||||||
|  |      */ | ||||||
|  |     private void sendMsg(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); | ||||||
|  |         messageService.add(request, CollUtil.toList(user.getId())); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,39 @@ | |||||||
|  | /* | ||||||
|  |  * 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.enums; | ||||||
|  |  | ||||||
|  | import lombok.Getter; | ||||||
|  | import lombok.RequiredArgsConstructor; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息模板枚举 | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 19:51 | ||||||
|  |  */ | ||||||
|  | @Getter | ||||||
|  | @RequiredArgsConstructor | ||||||
|  | public enum MessageTemplateEnum { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 第三方登录 | ||||||
|  |      */ | ||||||
|  |     SOCIAL_REGISTER("欢迎注册 {projectName}", "尊敬的 {nickname},欢迎注册使用,请及时配置您的密码。"); | ||||||
|  |  | ||||||
|  |     private final String title; | ||||||
|  |     private final String content; | ||||||
|  | } | ||||||
| @@ -0,0 +1,58 @@ | |||||||
|  | /* | ||||||
|  |  * 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.mapper; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import org.apache.ibatis.annotations.Param; | ||||||
|  |  | ||||||
|  | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||||
|  | import com.baomidou.mybatisplus.core.metadata.IPage; | ||||||
|  | import com.baomidou.mybatisplus.core.toolkit.Constants; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.base.BaseMapper; | ||||||
|  | import top.charles7c.cnadmin.system.model.entity.MessageDO; | ||||||
|  | import top.charles7c.cnadmin.system.model.vo.MessageVO; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息 Mapper | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 19:05 | ||||||
|  |  */ | ||||||
|  | public interface MessageMapper extends BaseMapper<MessageDO> { | ||||||
|  |     /** | ||||||
|  |      * 分页查询列表 | ||||||
|  |      * | ||||||
|  |      * @param queryWrapper | ||||||
|  |      *            查询条件 | ||||||
|  |      * @param page | ||||||
|  |      *            分页查询条件 | ||||||
|  |      * @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); | ||||||
|  | } | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | /* | ||||||
|  |  * 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.mapper; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.base.BaseMapper; | ||||||
|  | import top.charles7c.cnadmin.system.model.entity.MessageUserDO; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息和用户关联 Mapper | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 20:25 | ||||||
|  |  */ | ||||||
|  | public interface MessageUserMapper extends BaseMapper<MessageUserDO> {} | ||||||
| @@ -0,0 +1,56 @@ | |||||||
|  | /* | ||||||
|  |  * 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.entity; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | import com.baomidou.mybatisplus.annotation.TableName; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.base.BaseDO; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息实体 | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 19:05 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @TableName("sys_message") | ||||||
|  | public class MessageDO extends BaseDO { | ||||||
|  |  | ||||||
|  |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 消息ID | ||||||
|  |      */ | ||||||
|  |     private Long id; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 主题 | ||||||
|  |      */ | ||||||
|  |     private String title; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 内容 | ||||||
|  |      */ | ||||||
|  |     private String content; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 类型(取值于字典 message_type) | ||||||
|  |      */ | ||||||
|  |     private String type; | ||||||
|  | } | ||||||
| @@ -0,0 +1,58 @@ | |||||||
|  | /* | ||||||
|  |  * 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.entity; | ||||||
|  |  | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | import com.baomidou.mybatisplus.annotation.TableName; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.base.BaseDO; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息和用户关联实体 | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 20:25 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @TableName("sys_message_user") | ||||||
|  | public class MessageUserDO extends BaseDO { | ||||||
|  |  | ||||||
|  |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 消息ID | ||||||
|  |      */ | ||||||
|  |     private Long messageId; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户ID | ||||||
|  |      */ | ||||||
|  |     private Long userId; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 读取状态 (0未读 1已读) | ||||||
|  |      */ | ||||||
|  |     private Boolean readStatus; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 读取时间 | ||||||
|  |      */ | ||||||
|  |     private LocalDateTime readTime; | ||||||
|  | } | ||||||
| @@ -0,0 +1,72 @@ | |||||||
|  | /* | ||||||
|  |  * 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.query; | ||||||
|  |  | ||||||
|  | import java.io.Serializable; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.annotation.Query; | ||||||
|  | import top.charles7c.cnadmin.common.enums.QueryTypeEnum; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息查询条件 | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 19:05 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @Schema(description = "消息查询条件") | ||||||
|  | public class MessageQuery implements Serializable { | ||||||
|  |  | ||||||
|  |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * ID | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "ID", example = "1") | ||||||
|  |     @Query(type = QueryTypeEnum.EQUAL) | ||||||
|  |     private Long id; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 类型(取值于字典 message_type) | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "类型(取值于字典 message_type)", example = "1") | ||||||
|  |     @Query(type = QueryTypeEnum.EQUAL) | ||||||
|  |     private String type; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 主题 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "主题", example = "欢迎 xxx") | ||||||
|  |     @Query(type = QueryTypeEnum.INNER_LIKE) | ||||||
|  |     private String title; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户ID | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "用户ID", example = "1") | ||||||
|  |     private Long uid; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 是否已读 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "是否已读", example = "true") | ||||||
|  |     private Boolean readStatus; | ||||||
|  | } | ||||||
| @@ -0,0 +1,72 @@ | |||||||
|  | /* | ||||||
|  |  * 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.request; | ||||||
|  |  | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | import javax.validation.constraints.NotBlank; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 创建消息信息 | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 19:05 | ||||||
|  |  */ | ||||||
|  | @Schema(description = "创建消息信息") | ||||||
|  | @Data | ||||||
|  | public class MessageRequest extends BaseRequest { | ||||||
|  |  | ||||||
|  |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 主题 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "主题", example = "欢迎 xxx") | ||||||
|  |     @NotBlank(message = "主题不能为空") | ||||||
|  |     @Length(max = 50, message = "主题长度不能超过 {max} 个字符") | ||||||
|  |     private String title; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 内容 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "内容", example = "欢迎 xxx 来到 ContiNew Admin") | ||||||
|  |     @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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,62 @@ | |||||||
|  | /* | ||||||
|  |  * 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.time.LocalDateTime; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.base.BaseVO; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息和用户关联信息 | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 20:25 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @Schema(description = "消息和用户关联信息") | ||||||
|  | public class MessageUserVO extends BaseVO { | ||||||
|  |  | ||||||
|  |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 消息ID | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "消息ID", example = "1") | ||||||
|  |     private Long messageId; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户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; | ||||||
|  | } | ||||||
| @@ -0,0 +1,74 @@ | |||||||
|  | /* | ||||||
|  |  * 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.time.LocalDateTime; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.base.BaseVO; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息信息 | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 19:05 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @Schema(description = "消息信息") | ||||||
|  | public class MessageVO extends BaseVO { | ||||||
|  |  | ||||||
|  |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 消息ID | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "消息ID", example = "1") | ||||||
|  |     private Long id; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 主题 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "主题", example = "欢迎 xxx") | ||||||
|  |     private String title; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 内容 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "内容", example = "欢迎 xxx") | ||||||
|  |     private String content; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 类型(取值于字典 message_type) | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "类型(取值于字典 message_type)", example = "1") | ||||||
|  |     private String type; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 是否已读 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "是否已读", example = "true") | ||||||
|  |     private Boolean readStatus; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 读取时间 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "读取时间", example = "2023-08-08 23:59:59", type = "string") | ||||||
|  |     private LocalDateTime readTime; | ||||||
|  | } | ||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | /* | ||||||
|  |  * 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.service; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.base.BaseService; | ||||||
|  | import top.charles7c.cnadmin.system.model.query.MessageQuery; | ||||||
|  | import top.charles7c.cnadmin.system.model.request.MessageRequest; | ||||||
|  | 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> { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 发送消息 | ||||||
|  |      * | ||||||
|  |      * @param request | ||||||
|  |      *            消息 | ||||||
|  |      * @param userIdList | ||||||
|  |      *            接收人 | ||||||
|  |      */ | ||||||
|  |     void add(MessageRequest request, List<Long> userIdList); | ||||||
|  | } | ||||||
| @@ -0,0 +1,54 @@ | |||||||
|  | /* | ||||||
|  |  * 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.service; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息和用户关联业务接口 | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 19:05 | ||||||
|  |  */ | ||||||
|  | public interface MessageUserService { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 发送消息 | ||||||
|  |      * | ||||||
|  |      * @param messageId | ||||||
|  |      *            消息ID | ||||||
|  |      * @param userIdList | ||||||
|  |      *            接收人 | ||||||
|  |      */ | ||||||
|  |     void add(Long messageId, List<Long> userIdList); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 将消息标记已读 | ||||||
|  |      * | ||||||
|  |      * @param ids | ||||||
|  |      *            消息ID(为空则将所有消息标记已读) | ||||||
|  |      */ | ||||||
|  |     void readMessage(List<Long> ids); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 删除消息 | ||||||
|  |      * | ||||||
|  |      * @param ids | ||||||
|  |      *            消息ID | ||||||
|  |      */ | ||||||
|  |     void delete(List<Long> ids); | ||||||
|  | } | ||||||
| @@ -0,0 +1,108 @@ | |||||||
|  | /* | ||||||
|  |  * 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.service.impl; | ||||||
|  |  | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import lombok.RequiredArgsConstructor; | ||||||
|  |  | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  | 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.collection.CollUtil; | ||||||
|  |  | ||||||
|  | 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.util.helper.QueryHelper; | ||||||
|  | import top.charles7c.cnadmin.common.util.validate.CheckUtils; | ||||||
|  | import top.charles7c.cnadmin.system.mapper.MessageMapper; | ||||||
|  | import top.charles7c.cnadmin.system.model.entity.MessageDO; | ||||||
|  | import top.charles7c.cnadmin.system.model.query.MessageQuery; | ||||||
|  | import top.charles7c.cnadmin.system.model.request.MessageRequest; | ||||||
|  | import top.charles7c.cnadmin.system.model.vo.MessageVO; | ||||||
|  | import top.charles7c.cnadmin.system.service.MessageService; | ||||||
|  | import top.charles7c.cnadmin.system.service.MessageUserService; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息业务实现 | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 19:05 | ||||||
|  |  */ | ||||||
|  | @Service | ||||||
|  | @RequiredArgsConstructor | ||||||
|  | public class MessageServiceImpl | ||||||
|  |     extends BaseServiceImpl<MessageMapper, MessageDO, MessageVO, MessageVO, MessageQuery, MessageRequest> | ||||||
|  |     implements MessageService { | ||||||
|  |  | ||||||
|  |     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()); | ||||||
|  |         queryWrapper.apply(null != query.getReadStatus(), "msgUser.read_status={0}", query.getReadStatus()); | ||||||
|  |         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()); | ||||||
|  |         queryWrapper.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); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Transactional(rollbackFor = Exception.class) | ||||||
|  |     @Override | ||||||
|  |     public void delete(List<Long> ids) { | ||||||
|  |         super.delete(ids); | ||||||
|  |         messageUserService.delete(ids); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,74 @@ | |||||||
|  | /* | ||||||
|  |  * 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.service.impl; | ||||||
|  |  | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  |  | ||||||
|  | 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.util.validate.CheckUtils; | ||||||
|  | import top.charles7c.cnadmin.system.mapper.MessageUserMapper; | ||||||
|  | import top.charles7c.cnadmin.system.model.entity.MessageUserDO; | ||||||
|  | import top.charles7c.cnadmin.system.service.MessageUserService; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息和用户关联业务实现 | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 19:05 | ||||||
|  |  */ | ||||||
|  | @Service | ||||||
|  | @RequiredArgsConstructor | ||||||
|  | public class MessageUserServiceImpl implements MessageUserService { | ||||||
|  |  | ||||||
|  |     private final MessageUserMapper messageUserMapper; | ||||||
|  |  | ||||||
|  |     @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; | ||||||
|  |         }).collect(Collectors.toList()); | ||||||
|  |         messageUserMapper.insertBatch(messageUserDOList); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void readMessage(List<Long> ids) { | ||||||
|  |         messageUserMapper.lambdaUpdate().set(MessageUserDO::getReadStatus, true) | ||||||
|  |             .set(MessageUserDO::getReadTime, LocalDateTime.now()).eq(MessageUserDO::getReadStatus, 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)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,32 @@ | |||||||
|  | <?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.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 | ||||||
|  |             ${ew.getCustomSqlSegment} | ||||||
|  |     </select> | ||||||
|  | </mapper> | ||||||
							
								
								
									
										76
									
								
								continew-admin-ui/src/api/system/message.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								continew-admin-ui/src/api/system/message.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | import axios from 'axios'; | ||||||
|  | import qs from 'query-string'; | ||||||
|  | import { DataRecord } from "@/api/system/dict"; | ||||||
|  |  | ||||||
|  | const BASE_URL = '/system/message'; | ||||||
|  |  | ||||||
|  | export interface MessageRecord { | ||||||
|  |   id?: number; | ||||||
|  |   type?: string; | ||||||
|  |   title?: string; | ||||||
|  |   subTitle?: string; | ||||||
|  |   avatar?: string; | ||||||
|  |   content?: string; | ||||||
|  |   createTime?: string; | ||||||
|  |   readStatus?: 0 | 1; | ||||||
|  |   messageType?: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface ChatRecord { | ||||||
|  |   id: number; | ||||||
|  |   username: string; | ||||||
|  |   content: string; | ||||||
|  |   time: string; | ||||||
|  |   isCollect: boolean; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface ListParam { | ||||||
|  |   title?: string; | ||||||
|  |   readStatus?: 0 | 1; | ||||||
|  |   type?: string; | ||||||
|  |   page?: number; | ||||||
|  |   size?: number; | ||||||
|  |   uid?:number | ||||||
|  |   sort?: Array<string>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface PageRes { | ||||||
|  |   list: DataRecord[]; | ||||||
|  |   total: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export type MessageListType = MessageRecord[]; | ||||||
|  |  | ||||||
|  | export function page(params?: ListParam) { | ||||||
|  |   return axios.get<PageRes>(`${BASE_URL}`, { | ||||||
|  |     params, | ||||||
|  |     paramsSerializer: (obj) => { | ||||||
|  |       return qs.stringify(obj); | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 queryChatList() { | ||||||
|  |   return axios.get<ChatRecord[]>('/api/chat/list'); | ||||||
|  | } | ||||||
| @@ -1,9 +1,9 @@ | |||||||
| <template> | <template> | ||||||
|   <a-spin style="display: block" :loading="loading"> |   <a-spin style="display: block" :loading="loading"> | ||||||
|     <a-tabs v-model:activeKey="messageType" type="rounded" destroy-on-hide> |     <a-tabs v-model:activeKey="messageType" type="rounded" destroy-on-hide> | ||||||
|       <a-tab-pane v-for="item in tabList" :key="item.key"> |       <a-tab-pane v-for="item in message_type" :key="item.value"> | ||||||
|         <template #title> |         <template #title> | ||||||
|           <span> {{ item.title }}{{ formatUnreadLength(item.key) }} </span> |           <span> {{ item.label }}{{ formatUnreadLength(item.value) }} </span> | ||||||
|         </template> |         </template> | ||||||
|         <a-result v-if="!renderList.length" status="404"> |         <a-result v-if="!renderList.length" status="404"> | ||||||
|           <template #subtitle> {{ $t('messageBox.noContent') }} </template> |           <template #subtitle> {{ $t('messageBox.noContent') }} </template> | ||||||
| @@ -14,35 +14,26 @@ | |||||||
|           @item-click="handleItemClick" |           @item-click="handleItemClick" | ||||||
|         /> |         /> | ||||||
|       </a-tab-pane> |       </a-tab-pane> | ||||||
|       <template #extra> |  | ||||||
|         <a-button type="text" @click="emptyList"> |  | ||||||
|           {{ $t('messageBox.tab.button') }} |  | ||||||
|         </a-button> |  | ||||||
|       </template> |  | ||||||
|     </a-tabs> |     </a-tabs> | ||||||
|   </a-spin> |   </a-spin> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|   import { ref, reactive, toRefs, computed } from 'vue'; |   import { computed, getCurrentInstance, reactive, ref, toRefs } from 'vue'; | ||||||
|   import { useI18n } from 'vue-i18n'; |  | ||||||
|   import { |   import { | ||||||
|     queryMessageList, |  | ||||||
|     setMessageStatus, |  | ||||||
|     MessageRecord, |  | ||||||
|     MessageListType, |     MessageListType, | ||||||
|   } from '@/api/demo/message'; |     MessageRecord, | ||||||
|  |     list, | ||||||
|  |     read, | ||||||
|  |   } from '@/api/system/message'; | ||||||
|   import useLoading from '@/hooks/loading'; |   import useLoading from '@/hooks/loading'; | ||||||
|   import List from './list.vue'; |   import List from './list.vue'; | ||||||
|  |  | ||||||
|   interface TabItem { |   const { proxy } = getCurrentInstance() as any; | ||||||
|     key: string; |   const { message_type } = proxy.useDict('message_type'); | ||||||
|     title: string; |  | ||||||
|     avatar?: string; |  | ||||||
|   } |  | ||||||
|   const { loading, setLoading } = useLoading(true); |   const { loading, setLoading } = useLoading(true); | ||||||
|   const messageType = ref('message'); |   const messageType = ref('1'); | ||||||
|   const { t } = useI18n(); |  | ||||||
|   const messageData = reactive<{ |   const messageData = reactive<{ | ||||||
|     renderList: MessageRecord[]; |     renderList: MessageRecord[]; | ||||||
|     messageList: MessageRecord[]; |     messageList: MessageRecord[]; | ||||||
| @@ -51,59 +42,87 @@ | |||||||
|     messageList: [], |     messageList: [], | ||||||
|   }); |   }); | ||||||
|   toRefs(messageData); |   toRefs(messageData); | ||||||
|   const tabList: TabItem[] = [ |  | ||||||
|     { |   /** | ||||||
|       key: 'message', |    * 查询列表 | ||||||
|       title: t('messageBox.tab.title.message'), |    */ | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       key: 'notice', |  | ||||||
|       title: t('messageBox.tab.title.notice'), |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       key: 'todo', |  | ||||||
|       title: t('messageBox.tab.title.todo'), |  | ||||||
|     }, |  | ||||||
|   ]; |  | ||||||
|   async function fetchSourceData() { |   async function fetchSourceData() { | ||||||
|     setLoading(true); |     setLoading(true); | ||||||
|     try { |     try { | ||||||
|       const { data } = await queryMessageList(); |       list({ sort: ['createTime,desc'] }).then((res) => { | ||||||
|       messageData.messageList = data; |         messageData.messageList = res.data; | ||||||
|  |       }); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       // you can report use errorHandler or other |       // you can report use errorHandler or other | ||||||
|     } finally { |     } finally { | ||||||
|       setLoading(false); |       setLoading(false); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 将消息设置为已读 | ||||||
|  |    * | ||||||
|  |    * @param data 消息列表 | ||||||
|  |    */ | ||||||
|   async function readMessage(data: MessageListType) { |   async function readMessage(data: MessageListType) { | ||||||
|     const ids = data.map((item) => item.id); |     const ids = data.map((item) => item.id); | ||||||
|     await setMessageStatus({ ids }); |     await read(ids); | ||||||
|     fetchSourceData(); |     fetchSourceData(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 每个消息类型下的消息列表 | ||||||
|  |    */ | ||||||
|   const renderList = computed(() => { |   const renderList = computed(() => { | ||||||
|     return messageData.messageList.filter( |     return messageData.messageList.filter( | ||||||
|       (item) => messageType.value === item.type |       (item) => item.type === messageType.value && !item.readStatus | ||||||
|     ); |     ).splice(0,3); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 未读消息数量 | ||||||
|  |    */ | ||||||
|   const unreadCount = computed(() => { |   const unreadCount = computed(() => { | ||||||
|     return renderList.value.filter((item) => !item.status).length; |     return renderList.value.filter((item) => !item.readStatus).length; | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 未读消息列表 | ||||||
|  |    * | ||||||
|  |    * @param type 消息类型 | ||||||
|  |    */ | ||||||
|   const getUnreadList = (type: string) => { |   const getUnreadList = (type: string) => { | ||||||
|     const list = messageData.messageList.filter( |     return messageData.messageList.filter( | ||||||
|       (item) => item.type === type && !item.status |       (item) => item.type === type && !item.readStatus | ||||||
|     ); |     ); | ||||||
|     return list; |  | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 每个类型的未读消息数量 | ||||||
|  |    * | ||||||
|  |    * @param type 消息类型 | ||||||
|  |    */ | ||||||
|   const formatUnreadLength = (type: string) => { |   const formatUnreadLength = (type: string) => { | ||||||
|     const list = getUnreadList(type); |     const unreadList = getUnreadList(type); | ||||||
|     return list.length ? `(${list.length})` : ``; |     return unreadList.length ? `(${unreadList.length})` : ``; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 点击消息事件 | ||||||
|  |    * | ||||||
|  |    * @param items 消息 | ||||||
|  |    */ | ||||||
|   const handleItemClick = (items: MessageListType) => { |   const handleItemClick = (items: MessageListType) => { | ||||||
|     if (renderList.value.length) readMessage([...items]); |     if (renderList.value.length) readMessage([...items]); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 清空消息 | ||||||
|  |    */ | ||||||
|   const emptyList = () => { |   const emptyList = () => { | ||||||
|  |     read([]).then((res) => { | ||||||
|       messageData.messageList = []; |       messageData.messageList = []; | ||||||
|  |     }); | ||||||
|   }; |   }; | ||||||
|   fetchSourceData(); |   fetchSourceData(); | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -5,15 +5,9 @@ | |||||||
|       :key="item.id" |       :key="item.id" | ||||||
|       action-layout="vertical" |       action-layout="vertical" | ||||||
|       :style="{ |       :style="{ | ||||||
|         opacity: item.status ? 0.5 : 1, |         opacity: item.readStatus == 1 ? 0.5 : 1, | ||||||
|       }" |       }" | ||||||
|     > |     > | ||||||
|       <template #extra> |  | ||||||
|         <a-tag v-if="item.messageType === 0" color="gray">未开始</a-tag> |  | ||||||
|         <a-tag v-else-if="item.messageType === 1" color="green">已开通</a-tag> |  | ||||||
|         <a-tag v-else-if="item.messageType === 2" color="blue">进行中</a-tag> |  | ||||||
|         <a-tag v-else-if="item.messageType === 3" color="red">即将到期</a-tag> |  | ||||||
|       </template> |  | ||||||
|       <div class="item-wrap" @click="onItemClick(item)"> |       <div class="item-wrap" @click="onItemClick(item)"> | ||||||
|         <a-list-item-meta> |         <a-list-item-meta> | ||||||
|           <template v-if="item.avatar" #avatar> |           <template v-if="item.avatar" #avatar> | ||||||
| @@ -38,11 +32,8 @@ | |||||||
|                 }" |                 }" | ||||||
|                 >{{ item.content }}</a-typography-paragraph |                 >{{ item.content }}</a-typography-paragraph | ||||||
|               > |               > | ||||||
|               <a-typography-text |               <a-typography-text class="time-text"> | ||||||
|                 v-if="item.type === 'message'" |                 {{ item.createTime }} | ||||||
|                 class="time-text" |  | ||||||
|               > |  | ||||||
|                 {{ item.time }} |  | ||||||
|               </a-typography-text> |               </a-typography-text> | ||||||
|             </div> |             </div> | ||||||
|           </template> |           </template> | ||||||
| @@ -59,7 +50,7 @@ | |||||||
|           <a-link @click="allRead">{{ $t('messageBox.allRead') }}</a-link> |           <a-link @click="allRead">{{ $t('messageBox.allRead') }}</a-link> | ||||||
|         </div> |         </div> | ||||||
|         <div class="footer-wrap"> |         <div class="footer-wrap"> | ||||||
|           <a-link>{{ $t('messageBox.viewMore') }}</a-link> |           <a-link @click="toList">{{ $t('messageBox.viewMore') }}</a-link> | ||||||
|         </div> |         </div> | ||||||
|       </a-space> |       </a-space> | ||||||
|     </template> |     </template> | ||||||
| @@ -72,7 +63,10 @@ | |||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|   import { PropType } from 'vue'; |   import { PropType } from 'vue'; | ||||||
|   import { MessageRecord, MessageListType } from '@/api/demo/message'; |   import { useRouter } from 'vue-router'; | ||||||
|  |   import { MessageRecord, MessageListType } from '@/api/system/message'; | ||||||
|  |  | ||||||
|  |   const router = useRouter(); | ||||||
|  |  | ||||||
|   const props = defineProps({ |   const props = defineProps({ | ||||||
|     renderList: { |     renderList: { | ||||||
| @@ -85,12 +79,29 @@ | |||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|   const emit = defineEmits(['itemClick']); |   const emit = defineEmits(['itemClick']); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 全部已读 | ||||||
|  |    */ | ||||||
|   const allRead = () => { |   const allRead = () => { | ||||||
|     emit('itemClick', [...props.renderList]); |     emit('itemClick', [...props.renderList]); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 查看更多 | ||||||
|  |    */ | ||||||
|  |   const toList = ()=>{ | ||||||
|  |     router.push({ | ||||||
|  |       path: '/system/message', | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 点击消息 | ||||||
|  |    * @param item 消息 | ||||||
|  |    */ | ||||||
|   const onItemClick = (item: MessageRecord) => { |   const onItemClick = (item: MessageRecord) => { | ||||||
|     if (!item.status) { |     if (!item.readStatus) { | ||||||
|       emit('itemClick', [item]); |       emit('itemClick', [item]); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|   | |||||||
| @@ -82,7 +82,7 @@ | |||||||
|       <li> |       <li> | ||||||
|         <a-tooltip :content="$t('settings.navbar.alerts')"> |         <a-tooltip :content="$t('settings.navbar.alerts')"> | ||||||
|           <div class="message-box-trigger"> |           <div class="message-box-trigger"> | ||||||
|             <a-badge :count="9" dot> |             <a-badge :count="unReadMessageCount" dot> | ||||||
|               <a-button |               <a-button | ||||||
|                 class="nav-btn" |                 class="nav-btn" | ||||||
|                 type="outline" |                 type="outline" | ||||||
| @@ -190,9 +190,10 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|   import { computed, ref, inject } from 'vue'; |   import { computed, ref, inject,watchEffect } from 'vue'; | ||||||
|   import { useDark, useToggle, useFullscreen } from '@vueuse/core'; |   import { useDark, useToggle, useFullscreen } from '@vueuse/core'; | ||||||
|   import { useAppStore, useUserStore } from '@/store'; |   import { useAppStore, useUserStore } from '@/store'; | ||||||
|  |   import { list } from '@/api/system/message'; | ||||||
|   import { LOCALE_OPTIONS } from '@/locale'; |   import { LOCALE_OPTIONS } from '@/locale'; | ||||||
|   import useLocale from '@/hooks/locale'; |   import useLocale from '@/hooks/locale'; | ||||||
|   import useUser from '@/hooks/user'; |   import useUser from '@/hooks/user'; | ||||||
| @@ -223,6 +224,13 @@ | |||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|   const toggleTheme = useToggle(isDark); |   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; | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   const handleToggleTheme = () => { |   const handleToggleTheme = () => { | ||||||
|     toggleTheme(); |     toggleTheme(); | ||||||
|   }; |   }; | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import localeRole from '@/views/system/role/locale/en-US'; | |||||||
| import localeMenu from '@/views/system/menu/locale/en-US'; | import localeMenu from '@/views/system/menu/locale/en-US'; | ||||||
| import localeDept from '@/views/system/dept/locale/en-US'; | import localeDept from '@/views/system/dept/locale/en-US'; | ||||||
| import localeAnnouncement from '@/views/system/announcement/locale/en-US'; | import localeAnnouncement from '@/views/system/announcement/locale/en-US'; | ||||||
|  | import localeNotice from '@/views/system/message/locale/en-US'; | ||||||
| import localeDict from '@/views/system/dict/locale/en-US'; | import localeDict from '@/views/system/dict/locale/en-US'; | ||||||
| import localeConfig from '@/views/system/config/locale/en-US'; | import localeConfig from '@/views/system/config/locale/en-US'; | ||||||
|  |  | ||||||
| @@ -62,6 +63,7 @@ export default { | |||||||
|   ...localeMenu, |   ...localeMenu, | ||||||
|   ...localeDept, |   ...localeDept, | ||||||
|   ...localeAnnouncement, |   ...localeAnnouncement, | ||||||
|  |   ...localeNotice, | ||||||
|   ...localeDict, |   ...localeDict, | ||||||
|   ...localeConfig, |   ...localeConfig, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import localeRole from '@/views/system/role/locale/zh-CN'; | |||||||
| import localeMenu from '@/views/system/menu/locale/zh-CN'; | import localeMenu from '@/views/system/menu/locale/zh-CN'; | ||||||
| import localeDept from '@/views/system/dept/locale/zh-CN'; | import localeDept from '@/views/system/dept/locale/zh-CN'; | ||||||
| import localeAnnouncement from '@/views/system/announcement/locale/zh-CN'; | import localeAnnouncement from '@/views/system/announcement/locale/zh-CN'; | ||||||
|  | import locaoNotice from '@/views/system/message/locale/zh-CN'; | ||||||
| import localeDict from '@/views/system/dict/locale/zh-CN'; | import localeDict from '@/views/system/dict/locale/zh-CN'; | ||||||
| import localeConfig from '@/views/system/config/locale/zh-CN'; | import localeConfig from '@/views/system/config/locale/zh-CN'; | ||||||
|  |  | ||||||
| @@ -62,6 +63,7 @@ export default { | |||||||
|   ...localeMenu, |   ...localeMenu, | ||||||
|   ...localeDept, |   ...localeDept, | ||||||
|   ...localeAnnouncement, |   ...localeAnnouncement, | ||||||
|  |   ...locaoNotice, | ||||||
|   ...localeDict, |   ...localeDict, | ||||||
|   ...localeConfig, |   ...localeConfig, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -75,6 +75,15 @@ const System: AppRouteRecordRaw = { | |||||||
|         requiresAuth: true, |         requiresAuth: true, | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       name: 'Message', | ||||||
|  |       path: '/system/message', | ||||||
|  |       component: () => import('@/views/system/message/index.vue'), | ||||||
|  |       meta: { | ||||||
|  |         locale: 'menu.system.message', | ||||||
|  |         requiresAuth: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|   ], |   ], | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										467
									
								
								continew-admin-ui/src/views/system/message/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										467
									
								
								continew-admin-ui/src/views/system/message/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,467 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="app-container"> | ||||||
|  |     <Breadcrumb :items="['menu.system', 'menu.system.message.list']" /> | ||||||
|  |     <a-card class="general-card" :title="$t('menu.system.message.list')"> | ||||||
|  |       <!-- 头部区域 --> | ||||||
|  |       <div class="header"> | ||||||
|  |         <!-- 搜索栏 --> | ||||||
|  |         <div v-if="showQuery" class="header-query"> | ||||||
|  |           <a-form ref="queryRef" :model="queryParams" layout="inline"> | ||||||
|  |             <a-form-item field="title" hide-label> | ||||||
|  |               <a-input | ||||||
|  |                 v-model="queryParams.title" | ||||||
|  |                 placeholder="输入主题搜索" | ||||||
|  |                 allow-clear | ||||||
|  |                 style="width: 230px" | ||||||
|  |                 @press-enter="handleQuery" | ||||||
|  |               /> | ||||||
|  |             </a-form-item> | ||||||
|  |             <a-form-item field="type" hide-label> | ||||||
|  |               <a-select | ||||||
|  |                 v-model="queryParams.type" | ||||||
|  |                 :options="message_type" | ||||||
|  |                 placeholder="类型搜索" | ||||||
|  |                 allow-clear | ||||||
|  |                 style="width: 150px" | ||||||
|  |               /> | ||||||
|  |             </a-form-item> | ||||||
|  |  | ||||||
|  |             <a-form-item field="type" hide-label> | ||||||
|  |               <a-select :style="{width:'150px'}" placeholder="是否已读" allow-clear v-model="queryParams.readStatus"> | ||||||
|  |                 <a-option :value="true">是</a-option> | ||||||
|  |                 <a-option :value="false">否</a-option> | ||||||
|  |               </a-select> | ||||||
|  |             </a-form-item> | ||||||
|  |             <a-form-item hide-label> | ||||||
|  |               <a-space> | ||||||
|  |                 <a-button type="primary" @click="handleQuery"> | ||||||
|  |                   <template #icon><icon-search /></template>查询 | ||||||
|  |                 </a-button> | ||||||
|  |                 <a-button @click="resetQuery"> | ||||||
|  |                   <template #icon><icon-refresh /></template>重置 | ||||||
|  |                 </a-button> | ||||||
|  |               </a-space> | ||||||
|  |             </a-form-item> | ||||||
|  |           </a-form> | ||||||
|  |         </div> | ||||||
|  |         <!-- 操作栏 --> | ||||||
|  |         <div class="header-operation"> | ||||||
|  |           <a-row> | ||||||
|  |             <a-col :span="12"> | ||||||
|  |               <a-space> | ||||||
|  |                 <a-button | ||||||
|  |                   type="primary" | ||||||
|  |                   status="success" | ||||||
|  |                   :disabled="readMultiple" | ||||||
|  |                   :title="readMultiple ? '请选择要读取的数据' : ''" | ||||||
|  |                   @click="handleBatchRedaMessage" | ||||||
|  |                 > | ||||||
|  |                   <template #icon><icon-check /></template>标记已读 | ||||||
|  |                 </a-button> | ||||||
|  |                 <a-button | ||||||
|  |                   type="primary" | ||||||
|  |                   status="success" | ||||||
|  |                   @click="handleAllRedaMessage" | ||||||
|  |                 > | ||||||
|  |                   <template #icon><icon-check /></template>全部已读 | ||||||
|  |                 </a-button> | ||||||
|  |                 <a-button | ||||||
|  |                   v-permission="['system:announcement:delete']" | ||||||
|  |                   type="primary" | ||||||
|  |                   status="danger" | ||||||
|  |                   :disabled="multiple" | ||||||
|  |                   :title="multiple ? '请选择要删除的数据' : ''" | ||||||
|  |                   @click="handleBatchDelete" | ||||||
|  |                 > | ||||||
|  |                   <template #icon><icon-delete /></template>删除 | ||||||
|  |                 </a-button> | ||||||
|  |               </a-space> | ||||||
|  |             </a-col> | ||||||
|  |             <a-col :span="12"> | ||||||
|  |               <right-toolbar | ||||||
|  |                 v-model:show-query="showQuery" | ||||||
|  |                 @refresh="getList" | ||||||
|  |               /> | ||||||
|  |             </a-col> | ||||||
|  |           </a-row> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <!-- 列表区域 --> | ||||||
|  |       <a-table | ||||||
|  |         ref="tableRef" | ||||||
|  |         row-key="id" | ||||||
|  |         :data="dataList" | ||||||
|  |         :loading="loading" | ||||||
|  |         :row-selection="{ | ||||||
|  |           type: 'checkbox', | ||||||
|  |           showCheckedAll: true, | ||||||
|  |           onlyCurrent: false, | ||||||
|  |         }" | ||||||
|  |         :pagination="{ | ||||||
|  |           showTotal: true, | ||||||
|  |           showPageSize: true, | ||||||
|  |           total: total, | ||||||
|  |           current: queryParams.page, | ||||||
|  |         }" | ||||||
|  |         :bordered="false" | ||||||
|  |         column-resizable | ||||||
|  |         stripe | ||||||
|  |         size="large" | ||||||
|  |         @page-change="handlePageChange" | ||||||
|  |         @page-size-change="handlePageSizeChange" | ||||||
|  |         @selection-change="handleSelectionChange" | ||||||
|  |       > | ||||||
|  |         <template #columns> | ||||||
|  |           <a-table-column title="序号"> | ||||||
|  |             <template #cell="{ rowIndex }"> | ||||||
|  |               {{ rowIndex + 1 + (queryParams.page - 1) * queryParams.size }} | ||||||
|  |             </template> | ||||||
|  |           </a-table-column> | ||||||
|  |           <a-table-column title="主题"> | ||||||
|  |             <template #cell="{ record }"> | ||||||
|  |               <a-link @click="toDetail(record.id)">{{ 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" /> | ||||||
|  |             </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-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" | ||||||
|  |           > | ||||||
|  |             <template #cell="{ record }"> | ||||||
|  |               <a-button | ||||||
|  |                 :disabled="record.readStatus" | ||||||
|  |                 type="text" | ||||||
|  |                 size="small" | ||||||
|  |                 title="标记已读" | ||||||
|  |                 @click="handleReadMessage([record.id])" | ||||||
|  |               > | ||||||
|  |                 <template #icon><icon-check /></template>标记已读 | ||||||
|  |               </a-button> | ||||||
|  |  | ||||||
|  |               <a-popconfirm | ||||||
|  |                 content="确定要删除当前选中的数据吗?" | ||||||
|  |                 type="warning" | ||||||
|  |                 @ok="handleDelete([record.id])" | ||||||
|  |               > | ||||||
|  |                 <a-button | ||||||
|  |                   v-permission="['system:announcement:delete']" | ||||||
|  |                   type="text" | ||||||
|  |                   size="small" | ||||||
|  |                   title="删除" | ||||||
|  |                   :disabled="record.disabled" | ||||||
|  |                 > | ||||||
|  |                   <template #icon><icon-delete /></template>删除 | ||||||
|  |                 </a-button> | ||||||
|  |               </a-popconfirm> | ||||||
|  |             </template> | ||||||
|  |           </a-table-column> | ||||||
|  |         </template> | ||||||
|  |       </a-table> | ||||||
|  |  | ||||||
|  |       <!-- 详情区域 --> | ||||||
|  |       <a-drawer | ||||||
|  |         title="消息详情" | ||||||
|  |         :visible="detailVisible" | ||||||
|  |         :width="580" | ||||||
|  |         :footer="false" | ||||||
|  |         unmount-on-close | ||||||
|  |         render-to-body | ||||||
|  |         @cancel="handleDetailCancel" | ||||||
|  |       > | ||||||
|  |         <a-descriptions :column="2" bordered size="large"> | ||||||
|  |           <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" /> | ||||||
|  |             </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 color="green" v-else> | ||||||
|  |                <a-tag v-if="dataDetail.readStatus" 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.content || '无' }}</span> | ||||||
|  |           </a-descriptions-item> | ||||||
|  |         </a-descriptions> | ||||||
|  |       </a-drawer> | ||||||
|  |     </a-card> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <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'; | ||||||
|  |  | ||||||
|  |   const { proxy } = getCurrentInstance() as any; | ||||||
|  |   const { message_type } = proxy.useDict('message_type'); | ||||||
|  |  | ||||||
|  |   const dataList = ref<MessageRecord[]>([]); | ||||||
|  |   const dataDetail = ref<MessageRecord>({}); | ||||||
|  |   const total = ref(0); | ||||||
|  |   const ids = ref<Array<number>>([]); | ||||||
|  |   const single = ref(true); | ||||||
|  |   const multiple = ref(true); | ||||||
|  |   const readMultiple = ref(true); | ||||||
|  |   const showQuery = ref(true); | ||||||
|  |   const loading = ref(false); | ||||||
|  |   const detailVisible = ref(false); | ||||||
|  |   const detailLoading = ref(false); | ||||||
|  |  | ||||||
|  |   const data = reactive({ | ||||||
|  |     // 查询参数 | ||||||
|  |     queryParams: { | ||||||
|  |       title: undefined, | ||||||
|  |       readStatus: undefined, | ||||||
|  |       type: undefined, | ||||||
|  |       page: 1, | ||||||
|  |       size: 10, | ||||||
|  |       sort: ['createTime,desc'], | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  |   const { queryParams } = toRefs(data); | ||||||
|  |   /** | ||||||
|  |    * 查询列表 | ||||||
|  |    * | ||||||
|  |    */ | ||||||
|  |   const getList = (params: ListParam = { ...queryParams.value }) => { | ||||||
|  |     loading.value = true; | ||||||
|  |     page(params) | ||||||
|  |       .then((res) => { | ||||||
|  |         dataList.value = res.data.list; | ||||||
|  |         total.value = res.data.total; | ||||||
|  |       }) | ||||||
|  |       .finally(() => { | ||||||
|  |         loading.value = false; | ||||||
|  |       }); | ||||||
|  |   }; | ||||||
|  |   getList(); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 查看详情 | ||||||
|  |    * | ||||||
|  |    * @param id ID | ||||||
|  |    */ | ||||||
|  |   const toDetail = async (id: number) => { | ||||||
|  |     detailVisible.value = true; | ||||||
|  |     get(id).then((res) => { | ||||||
|  |       dataDetail.value = res.data; | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 关闭详情 | ||||||
|  |    */ | ||||||
|  |   const handleDetailCancel = () => { | ||||||
|  |     detailVisible.value = false; | ||||||
|  |     dataDetail.value = {}; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 批量删除 | ||||||
|  |    */ | ||||||
|  |   const handleBatchDelete = () => { | ||||||
|  |     if (ids.value.length === 0) { | ||||||
|  |       proxy.$message.info('请选择要删除的数据'); | ||||||
|  |     } else { | ||||||
|  |       proxy.$modal.warning({ | ||||||
|  |         title: '警告', | ||||||
|  |         titleAlign: 'start', | ||||||
|  |         content: '确定要删除当前选中的数据吗?', | ||||||
|  |         hideCancel: false, | ||||||
|  |         onOk: () => { | ||||||
|  |           handleDelete(ids.value); | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 删除 | ||||||
|  |    * | ||||||
|  |    * @param ids ID 列表 | ||||||
|  |    */ | ||||||
|  |   const handleDelete = (ids: Array<number>) => { | ||||||
|  |     del(ids).then((res) => { | ||||||
|  |       proxy.$message.success(res.msg); | ||||||
|  |       getList(); | ||||||
|  |       proxy.$refs.tableRef.selectAll(false); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 批量将消息设置为已读 | ||||||
|  |    */ | ||||||
|  |   const handleBatchRedaMessage = () => { | ||||||
|  |     if (ids.value.length === 0) { | ||||||
|  |       proxy.$message.info('请选择要读取的数据'); | ||||||
|  |     } else { | ||||||
|  |       handleReadMessage(ids.value); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 批量所以消息设置为已读 | ||||||
|  |    */ | ||||||
|  |   const handleAllRedaMessage = () => { | ||||||
|  |       handleReadMessage([]); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 将消息设置为已读 | ||||||
|  |    * | ||||||
|  |    * @param ids ID 列表 | ||||||
|  |    */ | ||||||
|  |   const handleReadMessage = (ids: Array<number>) => { | ||||||
|  |     read(ids).then((res) => { | ||||||
|  |       proxy.$message.success(res.msg); | ||||||
|  |       getList(); | ||||||
|  |       proxy.$refs.tableRef.selectAll(false); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 已选择的数据行发生改变时触发 | ||||||
|  |    * | ||||||
|  |    * @param rowKeys ID 列表 | ||||||
|  |    */ | ||||||
|  |   const handleSelectionChange = (rowKeys: Array<any>) => { | ||||||
|  |    const unReadMessageList = dataList.value.filter( | ||||||
|  |       (item) => rowKeys.indexOf(item.id)!==-1 && !item.readStatus | ||||||
|  |     ); | ||||||
|  |     readMultiple.value=!unReadMessageList.length | ||||||
|  |     ids.value = rowKeys; | ||||||
|  |     single.value = rowKeys.length !== 1; | ||||||
|  |     multiple.value = !rowKeys.length; | ||||||
|  |  | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 查询 | ||||||
|  |    */ | ||||||
|  |   const handleQuery = () => { | ||||||
|  |     getList(); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 重置 | ||||||
|  |    */ | ||||||
|  |   const resetQuery = () => { | ||||||
|  |     proxy.$refs.queryRef.resetFields(); | ||||||
|  |     handleQuery(); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 切换页码 | ||||||
|  |    * | ||||||
|  |    * @param current 页码 | ||||||
|  |    */ | ||||||
|  |   const handlePageChange = (current: number) => { | ||||||
|  |     queryParams.value.page = current; | ||||||
|  |     getList(); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 切换每页条数 | ||||||
|  |    * | ||||||
|  |    * @param pageSize 每页条数 | ||||||
|  |    */ | ||||||
|  |   const handlePageSizeChange = (pageSize: number) => { | ||||||
|  |     queryParams.value.size = pageSize; | ||||||
|  |     getList(); | ||||||
|  |   }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  |   export default { | ||||||
|  |     name: 'Announcement', | ||||||
|  |   }; | ||||||
|  | </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> | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | export default { | ||||||
|  |   'menu.system.message.list': 'Message management', | ||||||
|  | }; | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | export default { | ||||||
|  |   'menu.system.message.list': '消息管理', | ||||||
|  | }; | ||||||
| @@ -0,0 +1,63 @@ | |||||||
|  | /* | ||||||
|  |  * 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.webapi.controller.system; | ||||||
|  |  | ||||||
|  | import static top.charles7c.cnadmin.common.annotation.CrudRequestMapping.Api; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import lombok.RequiredArgsConstructor; | ||||||
|  |  | ||||||
|  | import io.swagger.v3.oas.annotations.Operation; | ||||||
|  | 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 top.charles7c.cnadmin.common.annotation.CrudRequestMapping; | ||||||
|  | import top.charles7c.cnadmin.common.base.BaseController; | ||||||
|  | import top.charles7c.cnadmin.system.model.query.MessageQuery; | ||||||
|  | import top.charles7c.cnadmin.system.model.request.MessageRequest; | ||||||
|  | import top.charles7c.cnadmin.system.model.vo.MessageVO; | ||||||
|  | import top.charles7c.cnadmin.system.service.MessageService; | ||||||
|  | import top.charles7c.cnadmin.system.service.MessageUserService; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息管理 API | ||||||
|  |  * | ||||||
|  |  * @author BULL_BCLS | ||||||
|  |  * @since 2023/10/15 19:05 | ||||||
|  |  */ | ||||||
|  | @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> { | ||||||
|  |  | ||||||
|  |     private final MessageUserService messageUserService; | ||||||
|  |  | ||||||
|  |     @Operation(description = "将消息标记已读", summary = "将消息标记已读") | ||||||
|  |     @Parameter(name = "ids", description = "消息ID", example = "1,2", in = ParameterIn.PATH) | ||||||
|  |     @PatchMapping("/read") | ||||||
|  |     public void readMessage(@RequestParam(required = false) List<Long> ids) { | ||||||
|  |         messageUserService.readMessage(ids); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,2 +1,14 @@ | |||||||
| -- liquibase formatted sql | -- liquibase formatted sql | ||||||
|  |  | ||||||
|  | -- changeset BUSS_BCLS:1 | ||||||
|  | -- 初始化默认字典 | ||||||
|  | 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); | ||||||
|   | |||||||
| @@ -10,3 +10,22 @@ CREATE TABLE IF NOT EXISTS `sys_user_social` ( | |||||||
|     `create_time`     datetime     NOT NULL     COMMENT '创建时间', |     `create_time`     datetime     NOT NULL     COMMENT '创建时间', | ||||||
|     UNIQUE INDEX `uk_source_open_id`(`source`, `open_id`) USING BTREE |     UNIQUE INDEX `uk_source_open_id`(`source`, `open_id`) USING BTREE | ||||||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户社会化关联表'; | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户社会化关联表'; | ||||||
|  |  | ||||||
|  | -- changeset BUSS_BCLS:2 | ||||||
|  | CREATE TABLE IF NOT EXISTS `sys_message` ( | ||||||
|  |     `id`          bigint(20)   AUTO_INCREMENT COMMENT 'ID', | ||||||
|  |     `title`       varchar(50)  NOT NULL       COMMENT '主题', | ||||||
|  |     `content`     varchar(255) DEFAULT NULL   COMMENT '内容', | ||||||
|  |     `type`        varchar(30)  NOT NULL       COMMENT '类型', | ||||||
|  |     `create_user` bigint(20)   DEFAULT NULL   COMMENT '创建人', | ||||||
|  |     `create_time` datetime     NOT NULL       COMMENT '创建时间', | ||||||
|  |     PRIMARY KEY (`id`) USING BTREE | ||||||
|  | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息表'; | ||||||
|  |  | ||||||
|  | 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 '是否已读', | ||||||
|  |     `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
	 Bull-BCLS
					Bull-BCLS