mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 22:57:17 +08:00 
			
		
		
		
	Merge pull request #25 from Bull-BCLS/dev
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) { | ||||
|         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(); | ||||
|         for (Sort.Order order : sort) { | ||||
|             if (null != order) { | ||||
|                 queryWrapper.orderBy(true, order.isAscending(), StrUtil.toUnderlineCase(order.getProperty())); | ||||
|             } | ||||
|         } | ||||
|         List<T> entityList = baseMapper.selectList(queryWrapper); | ||||
|         return BeanUtil.copyToList(entityList, targetClass); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -67,4 +67,9 @@ public class SysConsts { | ||||
|      * VO 描述类字段后缀 | ||||
|      */ | ||||
|     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.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; | ||||
| @@ -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.PermissionService; | ||||
| 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.SysConsts; | ||||
| 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.helper.LoginHelper; | ||||
| 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.UserDO; | ||||
| 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.MenuVO; | ||||
| import top.charles7c.cnadmin.system.service.*; | ||||
| @@ -74,6 +78,8 @@ public class LoginServiceImpl implements LoginService { | ||||
|     private final PermissionService permissionService; | ||||
|     private final UserRoleService userRoleService; | ||||
|     private final UserSocialService userSocialService; | ||||
|     private final MessageService messageService; | ||||
|     private final ProjectProperties projectProperties; | ||||
|  | ||||
|     @Override | ||||
|     public String accountLogin(String username, String password) { | ||||
| @@ -131,6 +137,7 @@ public class LoginServiceImpl implements LoginService { | ||||
|             userSocial.setUserId(userId); | ||||
|             userSocial.setSource(source); | ||||
|             userSocial.setOpenId(openId); | ||||
|             this.sendMsg(user); | ||||
|         } else { | ||||
|             user = BeanUtil.toBean(userService.get(userSocial.getUserId()), UserDO.class); | ||||
|         } | ||||
| @@ -180,7 +187,7 @@ public class LoginServiceImpl implements LoginService { | ||||
|  | ||||
|     /** | ||||
|      * 登录并缓存用户信息 | ||||
|      *  | ||||
|      * | ||||
|      * @param user | ||||
|      *            用户信息 | ||||
|      * @return 令牌 | ||||
| @@ -205,4 +212,22 @@ public class LoginServiceImpl implements LoginService { | ||||
|         DeptDetailVO deptDetailVO = deptService.get(user.getDeptId()); | ||||
|         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> | ||||
|   <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 tabList" :key="item.key"> | ||||
|       <a-tab-pane v-for="item in message_type" :key="item.value"> | ||||
|         <template #title> | ||||
|           <span> {{ item.title }}{{ formatUnreadLength(item.key) }} </span> | ||||
|           <span> {{ item.label }}{{ formatUnreadLength(item.value) }} </span> | ||||
|         </template> | ||||
|         <a-result v-if="!renderList.length" status="404"> | ||||
|           <template #subtitle> {{ $t('messageBox.noContent') }} </template> | ||||
| @@ -14,35 +14,26 @@ | ||||
|           @item-click="handleItemClick" | ||||
|         /> | ||||
|       </a-tab-pane> | ||||
|       <template #extra> | ||||
|         <a-button type="text" @click="emptyList"> | ||||
|           {{ $t('messageBox.tab.button') }} | ||||
|         </a-button> | ||||
|       </template> | ||||
|     </a-tabs> | ||||
|   </a-spin> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
|   import { ref, reactive, toRefs, computed } from 'vue'; | ||||
|   import { useI18n } from 'vue-i18n'; | ||||
|   import { computed, getCurrentInstance, reactive, ref, toRefs } from 'vue'; | ||||
|   import { | ||||
|     queryMessageList, | ||||
|     setMessageStatus, | ||||
|     MessageRecord, | ||||
|     MessageListType, | ||||
|   } from '@/api/demo/message'; | ||||
|     MessageRecord, | ||||
|     list, | ||||
|     read, | ||||
|   } from '@/api/system/message'; | ||||
|   import useLoading from '@/hooks/loading'; | ||||
|   import List from './list.vue'; | ||||
|  | ||||
|   interface TabItem { | ||||
|     key: string; | ||||
|     title: string; | ||||
|     avatar?: string; | ||||
|   } | ||||
|   const { proxy } = getCurrentInstance() as any; | ||||
|   const { message_type } = proxy.useDict('message_type'); | ||||
|   const { loading, setLoading } = useLoading(true); | ||||
|   const messageType = ref('message'); | ||||
|   const { t } = useI18n(); | ||||
|   const messageType = ref('1'); | ||||
|  | ||||
|   const messageData = reactive<{ | ||||
|     renderList: MessageRecord[]; | ||||
|     messageList: MessageRecord[]; | ||||
| @@ -51,59 +42,87 @@ | ||||
|     messageList: [], | ||||
|   }); | ||||
|   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() { | ||||
|     setLoading(true); | ||||
|     try { | ||||
|       const { data } = await queryMessageList(); | ||||
|       messageData.messageList = data; | ||||
|       list({ sort: ['createTime,desc'] }).then((res) => { | ||||
|         messageData.messageList = res.data; | ||||
|       }); | ||||
|     } catch (err) { | ||||
|       // you can report use errorHandler or other | ||||
|     } finally { | ||||
|       setLoading(false); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 将消息设置为已读 | ||||
|    * | ||||
|    * @param data 消息列表 | ||||
|    */ | ||||
|   async function readMessage(data: MessageListType) { | ||||
|     const ids = data.map((item) => item.id); | ||||
|     await setMessageStatus({ ids }); | ||||
|     await read(ids); | ||||
|     fetchSourceData(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 每个消息类型下的消息列表 | ||||
|    */ | ||||
|   const renderList = computed(() => { | ||||
|     return messageData.messageList.filter( | ||||
|       (item) => messageType.value === item.type | ||||
|     ); | ||||
|       (item) => item.type === messageType.value && !item.readStatus | ||||
|     ).splice(0,3); | ||||
|   }); | ||||
|  | ||||
|   /** | ||||
|    * 未读消息数量 | ||||
|    */ | ||||
|   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 list = messageData.messageList.filter( | ||||
|       (item) => item.type === type && !item.status | ||||
|     return messageData.messageList.filter( | ||||
|       (item) => item.type === type && !item.readStatus | ||||
|     ); | ||||
|     return list; | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 每个类型的未读消息数量 | ||||
|    * | ||||
|    * @param type 消息类型 | ||||
|    */ | ||||
|   const formatUnreadLength = (type: string) => { | ||||
|     const list = getUnreadList(type); | ||||
|     return list.length ? `(${list.length})` : ``; | ||||
|     const unreadList = getUnreadList(type); | ||||
|     return unreadList.length ? `(${unreadList.length})` : ``; | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 点击消息事件 | ||||
|    * | ||||
|    * @param items 消息 | ||||
|    */ | ||||
|   const handleItemClick = (items: MessageListType) => { | ||||
|     if (renderList.value.length) readMessage([...items]); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 清空消息 | ||||
|    */ | ||||
|   const emptyList = () => { | ||||
|     messageData.messageList = []; | ||||
|     read([]).then((res) => { | ||||
|       messageData.messageList = []; | ||||
|     }); | ||||
|   }; | ||||
|   fetchSourceData(); | ||||
| </script> | ||||
|   | ||||
| @@ -5,15 +5,9 @@ | ||||
|       :key="item.id" | ||||
|       action-layout="vertical" | ||||
|       :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)"> | ||||
|         <a-list-item-meta> | ||||
|           <template v-if="item.avatar" #avatar> | ||||
| @@ -38,11 +32,8 @@ | ||||
|                 }" | ||||
|                 >{{ item.content }}</a-typography-paragraph | ||||
|               > | ||||
|               <a-typography-text | ||||
|                 v-if="item.type === 'message'" | ||||
|                 class="time-text" | ||||
|               > | ||||
|                 {{ item.time }} | ||||
|               <a-typography-text class="time-text"> | ||||
|                 {{ item.createTime }} | ||||
|               </a-typography-text> | ||||
|             </div> | ||||
|           </template> | ||||
| @@ -59,7 +50,7 @@ | ||||
|           <a-link @click="allRead">{{ $t('messageBox.allRead') }}</a-link> | ||||
|         </div> | ||||
|         <div class="footer-wrap"> | ||||
|           <a-link>{{ $t('messageBox.viewMore') }}</a-link> | ||||
|           <a-link @click="toList">{{ $t('messageBox.viewMore') }}</a-link> | ||||
|         </div> | ||||
|       </a-space> | ||||
|     </template> | ||||
| @@ -72,7 +63,10 @@ | ||||
|  | ||||
| <script lang="ts" setup> | ||||
|   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({ | ||||
|     renderList: { | ||||
| @@ -85,12 +79,29 @@ | ||||
|     }, | ||||
|   }); | ||||
|   const emit = defineEmits(['itemClick']); | ||||
|  | ||||
|   /** | ||||
|    * 全部已读 | ||||
|    */ | ||||
|   const allRead = () => { | ||||
|     emit('itemClick', [...props.renderList]); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 查看更多 | ||||
|    */ | ||||
|   const toList = ()=>{ | ||||
|     router.push({ | ||||
|       path: '/system/message', | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 点击消息 | ||||
|    * @param item 消息 | ||||
|    */ | ||||
|   const onItemClick = (item: MessageRecord) => { | ||||
|     if (!item.status) { | ||||
|     if (!item.readStatus) { | ||||
|       emit('itemClick', [item]); | ||||
|     } | ||||
|   }; | ||||
|   | ||||
| @@ -82,7 +82,7 @@ | ||||
|       <li> | ||||
|         <a-tooltip :content="$t('settings.navbar.alerts')"> | ||||
|           <div class="message-box-trigger"> | ||||
|             <a-badge :count="9" dot> | ||||
|             <a-badge :count="unReadMessageCount" dot> | ||||
|               <a-button | ||||
|                 class="nav-btn" | ||||
|                 type="outline" | ||||
| @@ -190,9 +190,10 @@ | ||||
| </template> | ||||
|  | ||||
| <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 { useAppStore, useUserStore } from '@/store'; | ||||
|   import { list } from '@/api/system/message'; | ||||
|   import { LOCALE_OPTIONS } from '@/locale'; | ||||
|   import useLocale from '@/hooks/locale'; | ||||
|   import useUser from '@/hooks/user'; | ||||
| @@ -223,6 +224,13 @@ | ||||
|     }, | ||||
|   }); | ||||
|   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 = () => { | ||||
|     toggleTheme(); | ||||
|   }; | ||||
|   | ||||
| @@ -5,6 +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 localeDict from '@/views/system/dict/locale/en-US'; | ||||
| import localeConfig from '@/views/system/config/locale/en-US'; | ||||
|  | ||||
| @@ -62,6 +63,7 @@ export default { | ||||
|   ...localeMenu, | ||||
|   ...localeDept, | ||||
|   ...localeAnnouncement, | ||||
|   ...localeNotice, | ||||
|   ...localeDict, | ||||
|   ...localeConfig, | ||||
|  | ||||
|   | ||||
| @@ -5,6 +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 localeDict from '@/views/system/dict/locale/zh-CN'; | ||||
| import localeConfig from '@/views/system/config/locale/zh-CN'; | ||||
|  | ||||
| @@ -62,6 +63,7 @@ export default { | ||||
|   ...localeMenu, | ||||
|   ...localeDept, | ||||
|   ...localeAnnouncement, | ||||
|   ...locaoNotice, | ||||
|   ...localeDict, | ||||
|   ...localeConfig, | ||||
|  | ||||
|   | ||||
| @@ -75,6 +75,15 @@ const System: AppRouteRecordRaw = { | ||||
|         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 | ||||
|  | ||||
| -- 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); | ||||
|   | ||||
| @@ -9,4 +9,23 @@ CREATE TABLE IF NOT EXISTS `sys_user_social` ( | ||||
|     `last_login_time` datetime     DEFAULT NULL COMMENT '最后登录时间', | ||||
|     `create_time`     datetime     NOT NULL     COMMENT '创建时间', | ||||
|     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
	 GitHub
						GitHub