From 6e9057f6eed3882c41cfbe6e8d2515de504c33c7 Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Mon, 17 Nov 2025 15:16:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../.bin/mysql/sql/initMenuData.sql | 2 - .../api/news/resource/ResourceService.java | 4 +- .../auth/filter/JwtAuthenticationFilter.java | 2 - .../java/org/xyzh/common/vo/ResourceVO.java | 24 +- .../crontab/controller/CrontabController.java | 108 +++-- .../service/impl/CrontabServiceImpl.java | 6 +- .../news/controller/ResourceController.java | 4 +- .../org/xyzh/news/mapper/ResourceMapper.java | 8 +- .../service/impl/NCResourceServiceImpl.java | 10 +- .../main/resources/mapper/ResourceMapper.xml | 43 +- schoolNewsWeb/src/apis/resource/resource.ts | 14 + schoolNewsWeb/src/types/resource/index.ts | 4 + .../manage/content/ColumnManagementView.vue | 202 +++++++- .../manage/content/ContentManagementView.vue | 30 -- .../admin/manage/crontab/NewsCrawlerView.vue | 37 +- .../admin/manage/logs/SystemLogsView.vue | 442 +++--------------- 16 files changed, 444 insertions(+), 496 deletions(-) delete mode 100644 schoolNewsWeb/src/views/admin/manage/content/ContentManagementView.vue diff --git a/schoolNewsServ/.bin/mysql/sql/initMenuData.sql b/schoolNewsServ/.bin/mysql/sql/initMenuData.sql index ed9e3ad..fca4d62 100644 --- a/schoolNewsServ/.bin/mysql/sql/initMenuData.sql +++ b/schoolNewsServ/.bin/mysql/sql/initMenuData.sql @@ -148,7 +148,6 @@ INSERT INTO `tb_sys_menu` VALUES ('4001', 'menu_admin_banner', 'Banner管理', 'menu_admin_content_manage', '/admin/manage/content/banner', 'admin/manage/content/BannerManagementView', NULL, 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), ('4002', 'menu_admin_tag', '标签管理', 'menu_admin_content_manage', '/admin/manage/content/tag', 'admin/manage/content/TagManagementView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), ('4003', 'menu_admin_column', '栏目管理', 'menu_admin_content_manage', '/admin/manage/content/column', 'admin/manage/content/ColumnManagementView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), -('4004', 'menu_admin_content', '内容管理', 'menu_admin_content_manage', '/admin/manage/content/content', 'admin/manage/content/ContentManagementView', NULL, 4, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), ('5000', 'menu_admin_study_manage', '学习管理', NULL, '', '', 'admin/study.svg', 5, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:46', NULL, 0), ('5002', 'menu_admin_task_manage', '任务管理', 'menu_admin_study_manage', '/admin/manage/study/task-manage', 'admin/manage/study/TaskManageView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), ('5003', 'menu_admin_study_records', '学习记录', 'menu_admin_study_manage', '/admin/manage/study/study-records', 'admin/manage/study/StudyRecordsView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), @@ -211,7 +210,6 @@ INSERT INTO `tb_sys_menu_permission` (id, permission_id, menu_id, creator, creat ('214', 'perm_news_manage', 'menu_admin_banner', '1', now()), ('215', 'perm_news_manage', 'menu_admin_tag', '1', now()), ('216', 'perm_news_manage', 'menu_admin_column', '1', now()), -('217', 'perm_news_manage', 'menu_admin_content', '1', now()), ('218', 'perm_study_manage', 'menu_admin_study_manage', '1', now()), ('220', 'perm_study_manage', 'menu_admin_task_manage', '1', now()), ('221', 'perm_study_manage', 'menu_admin_study_records', '1', now()), diff --git a/schoolNewsServ/api/api-news/src/main/java/org/xyzh/api/news/resource/ResourceService.java b/schoolNewsServ/api/api-news/src/main/java/org/xyzh/api/news/resource/ResourceService.java index d887108..1ab165f 100644 --- a/schoolNewsServ/api/api-news/src/main/java/org/xyzh/api/news/resource/ResourceService.java +++ b/schoolNewsServ/api/api-news/src/main/java/org/xyzh/api/news/resource/ResourceService.java @@ -39,11 +39,11 @@ public interface ResourceService { * @description 获取资源分页(按浏览次数排序) * @param filter 过滤条件 * @param pageParam 分页参数 - * @return ResultDomain 资源分页 + * @return ResultDomain 资源分页(包含推荐信息) * @author yslg * @since 2025-10-15 */ - ResultDomain getResourcePageOrderByViewCount(TbResource filter, PageParam pageParam); + ResultDomain getResourcePageOrderByViewCount(TbResource filter, PageParam pageParam); /** * @description 根据ID获取资源详情 * @param resourceID 资源ID diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java index cbf45e3..8d2d10c 100644 --- a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java @@ -97,8 +97,6 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); - logger.debug("用户认证成功(从缓存),userId: {}", userId); - // 记录UV:以用户ID为维度,按天去重 try { String today = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); diff --git a/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/vo/ResourceVO.java b/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/vo/ResourceVO.java index 35eb5cc..8a05a94 100644 --- a/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/vo/ResourceVO.java +++ b/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/vo/ResourceVO.java @@ -25,14 +25,14 @@ public class ResourceVO implements Serializable{ private List tags; /** - * 是否推荐(用于首页展示) + * 是否为TOP推荐 */ - private Boolean isRecommended; + private Boolean isTopRecommend; /** - * 推荐类型(1-热门资源,2-思政资源) + * 是否为思政推荐 */ - private Integer recommendType; + private Boolean isIdeologicalRecommend; public TbResource getResource() { return resource; @@ -50,19 +50,19 @@ public class ResourceVO implements Serializable{ this.tags = tags; } - public Boolean getIsRecommended() { - return isRecommended; + public Boolean getIsTopRecommend() { + return isTopRecommend; } - public void setIsRecommended(Boolean isRecommended) { - this.isRecommended = isRecommended; + public void setIsTopRecommend(Boolean isTopRecommend) { + this.isTopRecommend = isTopRecommend; } - public Integer getRecommendType() { - return recommendType; + public Boolean getIsIdeologicalRecommend() { + return isIdeologicalRecommend; } - public void setRecommendType(Integer recommendType) { - this.recommendType = recommendType; + public void setIsIdeologicalRecommend(Boolean isIdeologicalRecommend) { + this.isIdeologicalRecommend = isIdeologicalRecommend; } } diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/controller/CrontabController.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/controller/CrontabController.java index 835eb97..8810b34 100644 --- a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/controller/CrontabController.java +++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/controller/CrontabController.java @@ -10,7 +10,6 @@ import org.xyzh.common.core.page.PageParam; import org.xyzh.common.core.page.PageRequest; import org.xyzh.common.dto.crontab.TbCrontabTask; import org.xyzh.common.dto.crontab.TbCrontabLog; -import org.xyzh.common.utils.IDUtils; import org.xyzh.crontab.pojo.CrontabItem; import com.alibaba.fastjson2.JSON; @@ -19,7 +18,6 @@ import com.alibaba.fastjson2.JSONObject; import org.xyzh.common.utils.spring.SpringContextUtil; import org.xyzh.crontab.config.CrontabProperties; -import java.util.Date; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -57,7 +55,6 @@ public class CrontabController { method->{ method.setClazz(null); method.setExcuete_method(null); - method.setPath(null); })); rd.success("ok", props.getItems()); } catch (Exception e) { @@ -75,25 +72,41 @@ public class CrontabController { public ResultDomain createCrontab(@RequestBody TbCrontabTask crontabItem) { ResultDomain rd = new ResultDomain<>(); try { - // 根据taskGroup和methodName查找配置并填充beanName和methodName - if (crontabItem.getBeanName() == null || crontabItem.getBeanName().isEmpty()) { - CrontabItem.CrontabMethod method = findMethodByTaskGroupAndMethodName( - crontabItem.getTaskGroup(), - crontabItem.getMethodName() - ); - if (method != null) { - crontabItem.setBeanName(method.getClazz()); // 设置Bean名称 - crontabItem.setMethodName(method.getExcuete_method()); // 设置执行方法名 - JSONObject methodParams = JSON.parseObject(crontabItem.getMethodParams()); - methodParams.put("scriptPath", method.getPath()); - crontabItem.setMethodParams(methodParams.toJSONString()); - - } else { - rd.fail("未找到对应的配置: taskGroup=" + crontabItem.getTaskGroup() - + ", methodName=" + crontabItem.getMethodName()); - return rd; - } + // 根据前端传入的taskGroup和methodName(都是中文显示名)查找配置 + if (crontabItem.getTaskGroup() == null || crontabItem.getTaskGroup().isEmpty()) { + rd.fail("任务分组不能为空"); + return rd; } + if (crontabItem.getMethodName() == null || crontabItem.getMethodName().isEmpty()) { + rd.fail("方法名称不能为空"); + return rd; + } + + // 根据taskGroup和methodName查找配置 + CrontabItem.CrontabMethod method = findMethodByTaskGroupAndMethodName( + crontabItem.getTaskGroup(), + crontabItem.getMethodName() + ); + + if (method != null) { + // 填充beanName和实际的Java方法名 + crontabItem.setBeanName(method.getClazz()); + crontabItem.setMethodName(method.getExcuete_method()); + + // 将scriptPath添加到methodParams中 + JSONObject methodParams = JSON.parseObject(crontabItem.getMethodParams()); + methodParams.put("scriptPath", method.getPath()); + crontabItem.setMethodParams(methodParams.toJSONString()); + + logger.info("创建任务 - taskGroup: {}, methodName: {}, beanName: {}, excuete_method: {}, scriptPath: {}", + crontabItem.getTaskGroup(), method.getName(), method.getClazz(), + method.getExcuete_method(), method.getPath()); + } else { + rd.fail("未找到对应的配置: taskGroup=" + crontabItem.getTaskGroup() + + ", methodName=" + crontabItem.getMethodName()); + return rd; + } + return crontabService.createTask(crontabItem); } catch (Exception e) { logger.error("创建定时任务失败", e); @@ -132,21 +145,46 @@ public class CrontabController { public ResultDomain updateCrontab(@RequestBody TbCrontabTask crontabItem) { ResultDomain rd = new ResultDomain<>(); try { - // 根据taskGroup和methodName查找配置并填充beanName和methodName - if (crontabItem.getBeanName() == null || crontabItem.getBeanName().isEmpty()) { - CrontabItem.CrontabMethod method = findMethodByTaskGroupAndMethodName( - crontabItem.getTaskGroup(), - crontabItem.getMethodName() - ); - if (method != null) { - crontabItem.setBeanName(method.getClazz()); // 设置Bean名称 - crontabItem.setMethodName(method.getExcuete_method()); // 设置执行方法名 - } else { - rd.fail("未找到对应的配置: taskGroup=" + crontabItem.getTaskGroup() - + ", methodName=" + crontabItem.getMethodName()); - return rd; - } + // 确保id字段正确传递(用于数据库更新) + if (crontabItem.getTaskId() != null && crontabItem.getID() == null) { + crontabItem.setID(crontabItem.getTaskId()); } + + // 根据前端传入的taskGroup和methodName(都是中文显示名)查找配置 + if (crontabItem.getTaskGroup() == null || crontabItem.getTaskGroup().isEmpty()) { + rd.fail("任务分组不能为空"); + return rd; + } + if (crontabItem.getMethodName() == null || crontabItem.getMethodName().isEmpty()) { + rd.fail("方法名称不能为空"); + return rd; + } + + // 根据taskGroup和methodName查找配置 + CrontabItem.CrontabMethod method = findMethodByTaskGroupAndMethodName( + crontabItem.getTaskGroup(), + crontabItem.getMethodName() + ); + + if (method != null) { + // 填充beanName和实际的Java方法名 + crontabItem.setBeanName(method.getClazz()); + crontabItem.setMethodName(method.getExcuete_method()); + + // 将scriptPath添加到methodParams中 + JSONObject methodParams = JSON.parseObject(crontabItem.getMethodParams()); + methodParams.put("scriptPath", method.getPath()); + crontabItem.setMethodParams(methodParams.toJSONString()); + + logger.info("更新任务 - id: {}, taskGroup: {}, methodName: {}, beanName: {}, excuete_method: {}, scriptPath: {}", + crontabItem.getID(), crontabItem.getTaskGroup(), method.getName(), method.getClazz(), + method.getExcuete_method(), method.getPath()); + } else { + rd.fail("未找到对应的配置: taskGroup=" + crontabItem.getTaskGroup() + + ", methodName=" + crontabItem.getMethodName()); + return rd; + } + return crontabService.updateTask(crontabItem); } catch (Exception e) { logger.error("更新定时任务失败", e); diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/service/impl/CrontabServiceImpl.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/service/impl/CrontabServiceImpl.java index 8db1966..3f41b7e 100644 --- a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/service/impl/CrontabServiceImpl.java +++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/service/impl/CrontabServiceImpl.java @@ -18,8 +18,6 @@ import org.xyzh.crontab.mapper.CrontabTaskMapper; import org.xyzh.crontab.mapper.CrontabLogMapper; import org.xyzh.crontab.scheduler.SchedulerManager; import org.xyzh.api.system.permission.ResourcePermissionService; -import org.xyzh.common.dto.user.TbSysUser; -import org.xyzh.common.dto.user.TbSysUserDeptRole; import org.xyzh.common.core.enums.ResourceType; import org.xyzh.system.utils.LoginUtil; @@ -122,7 +120,7 @@ public class CrontabServiceImpl implements CrontabService { public ResultDomain updateTask(TbCrontabTask task) { ResultDomain resultDomain = new ResultDomain<>(); try { - if (task.getID() == null) { + if (task.getTaskId() == null) { resultDomain.fail("任务ID不能为空"); return resultDomain; } @@ -140,7 +138,7 @@ public class CrontabServiceImpl implements CrontabService { logger.info("更新定时任务成功: {}", task.getTaskName()); // 重新调度任务 - TbCrontabTask dbTask = taskMapper.selectTaskById(task.getID()); + TbCrontabTask dbTask = taskMapper.selectTaskById(task.getTaskId()); if (dbTask != null && dbTask.getStatus() == 1) { schedulerManager.rescheduleTask(dbTask); } diff --git a/schoolNewsServ/news/src/main/java/org/xyzh/news/controller/ResourceController.java b/schoolNewsServ/news/src/main/java/org/xyzh/news/controller/ResourceController.java index b0d1291..78c2b49 100644 --- a/schoolNewsServ/news/src/main/java/org/xyzh/news/controller/ResourceController.java +++ b/schoolNewsServ/news/src/main/java/org/xyzh/news/controller/ResourceController.java @@ -52,10 +52,10 @@ public class ResourceController { } /** - * 获取资源分页(按浏览次数排序) + * 获取资源分页(按浏览次数排序,包含推荐信息) */ @PostMapping("/page/view-count") - public ResultDomain getResourcePageOrderByViewCount(@RequestBody PageRequest request) { + public ResultDomain getResourcePageOrderByViewCount(@RequestBody PageRequest request) { TbResource filter = request.getFilter(); PageParam pageParam = request.getPageParam(); return resourceService.getResourcePageOrderByViewCount(filter, pageParam); diff --git a/schoolNewsServ/news/src/main/java/org/xyzh/news/mapper/ResourceMapper.java b/schoolNewsServ/news/src/main/java/org/xyzh/news/mapper/ResourceMapper.java index 3cf8072..0ed41e2 100644 --- a/schoolNewsServ/news/src/main/java/org/xyzh/news/mapper/ResourceMapper.java +++ b/schoolNewsServ/news/src/main/java/org/xyzh/news/mapper/ResourceMapper.java @@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.xyzh.common.core.page.PageParam; import org.xyzh.common.dto.resource.TbResource; +import org.xyzh.common.vo.ResourceVO; import org.xyzh.common.vo.UserDeptRoleVO; import java.util.List; @@ -166,16 +167,15 @@ public interface ResourceMapper extends BaseMapper { List selectResourcesPage(@Param("filter") TbResource filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List userDeptRoles); /** - * @description 分页查询资源 + * @description 获取资源分页(按浏览次数排序,包含推荐信息) * @param filter 过滤条件 * @param pageParam 分页参数 * @param userDeptRoles 用户部门角色列表 - * @return List 资源列表 + * @return List 资源VO列表(包含推荐信息) * @author yslg * @since 2025-10-15 */ - List selectResourcesPageOrderByViewCount(@Param("filter") TbResource filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List userDeptRoles); - + List selectResourcesPageOrderByViewCount(@Param("filter") TbResource filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List userDeptRoles); /** * @description 统计资源总数 diff --git a/schoolNewsServ/news/src/main/java/org/xyzh/news/service/impl/NCResourceServiceImpl.java b/schoolNewsServ/news/src/main/java/org/xyzh/news/service/impl/NCResourceServiceImpl.java index 6a222e3..8f4a6dd 100644 --- a/schoolNewsServ/news/src/main/java/org/xyzh/news/service/impl/NCResourceServiceImpl.java +++ b/schoolNewsServ/news/src/main/java/org/xyzh/news/service/impl/NCResourceServiceImpl.java @@ -96,19 +96,21 @@ public class NCResourceServiceImpl implements ResourceService { } @Override - public ResultDomain getResourcePageOrderByViewCount(TbResource filter, PageParam pageParam) { - ResultDomain resultDomain = new ResultDomain<>(); + public ResultDomain getResourcePageOrderByViewCount(TbResource filter, PageParam pageParam) { + ResultDomain resultDomain = new ResultDomain<>(); try { if (filter == null) { filter = new TbResource(); } // 获取当前用户的部门角色 List userDeptRoles = LoginUtil.getCurrentDeptRole(); - List list = resourceMapper.selectResourcesPageOrderByViewCount(filter, pageParam, userDeptRoles); + // 直接查询ResourceVO列表,SQL已经LEFT JOIN了recommend表 + List resourceVOList = resourceMapper.selectResourcesPageOrderByViewCount(filter, pageParam, userDeptRoles); long total = resourceMapper.countResources(filter, userDeptRoles); pageParam.setTotalElements(total); pageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize())); - resultDomain.success("获取资源分页(按浏览次数排序)成功", new PageDomain(pageParam, list)); + + resultDomain.success("获取资源分页(按浏览次数排序)成功", new PageDomain(pageParam, resourceVOList)); return resultDomain; } catch (Exception e) { diff --git a/schoolNewsServ/news/src/main/resources/mapper/ResourceMapper.xml b/schoolNewsServ/news/src/main/resources/mapper/ResourceMapper.xml index 9ff64a0..b1f6663 100644 --- a/schoolNewsServ/news/src/main/resources/mapper/ResourceMapper.xml +++ b/schoolNewsServ/news/src/main/resources/mapper/ResourceMapper.xml @@ -336,9 +336,44 @@ LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset} - + SELECT + r.*, + MAX(CASE WHEN rec.recommend_type = 1 THEN 1 ELSE 0 END) AS is_top_recommend, + MAX(CASE WHEN rec.recommend_type = 2 THEN 1 ELSE 0 END) AS is_ideological_recommend FROM tb_resource r + LEFT JOIN tb_resource_recommend rec ON r.resource_id = rec.resource_id AND rec.deleted = 0 WHERE r.deleted = 0 @@ -359,6 +394,10 @@ AND r.is_banner = #{filter.isBanner} + GROUP BY r.id, r.resource_id, r.title, r.content, r.summary, r.cover_image, r.tag_id, + r.author, r.source, r.source_url, r.view_count, r.like_count, r.collect_count, + r.status, r.is_recommend, r.is_banner, r.publish_time, r.creator, r.updater, + r.create_time, r.update_time, r.delete_time, r.deleted ORDER BY r.view_count DESC, r.publish_time DESC, r.create_time DESC LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset} diff --git a/schoolNewsWeb/src/apis/resource/resource.ts b/schoolNewsWeb/src/apis/resource/resource.ts index 173cb39..8b2bdf2 100644 --- a/schoolNewsWeb/src/apis/resource/resource.ts +++ b/schoolNewsWeb/src/apis/resource/resource.ts @@ -39,6 +39,20 @@ export const resourceApi = { return response.data; }, + /** + * 获取资源分页列表(按浏览次数排序,包含推荐信息) + * @param filter 筛选条件 + * @param pageParam 分页参数 + * @returns Promise> + */ + async getResourcePageOrderByViewCount(pageParam: PageParam, filter?: ResourceSearchParams): Promise> { + const response = await api.post('/news/resources/page/view-count', { + pageParam, + filter, + }); + return response.data; + }, + /** * 根据ID获取资源详情 * @param resourceID 资源ID diff --git a/schoolNewsWeb/src/types/resource/index.ts b/schoolNewsWeb/src/types/resource/index.ts index 4223188..0e0f9e5 100644 --- a/schoolNewsWeb/src/types/resource/index.ts +++ b/schoolNewsWeb/src/types/resource/index.ts @@ -151,6 +151,10 @@ export interface Tag extends BaseDTO { export interface ResourceVO extends BaseDTO{ resource: Resource; tags: Tag[]; + /** 是否为TOP推荐 */ + isTopRecommend?: boolean; + /** 是否为思政推荐 */ + isIdeologicalRecommend?: boolean; } export interface tagVO extends BaseDTO{ /** 标签ID */ diff --git a/schoolNewsWeb/src/views/admin/manage/content/ColumnManagementView.vue b/schoolNewsWeb/src/views/admin/manage/content/ColumnManagementView.vue index 4a53098..80300a1 100644 --- a/schoolNewsWeb/src/views/admin/manage/content/ColumnManagementView.vue +++ b/schoolNewsWeb/src/views/admin/manage/content/ColumnManagementView.vue @@ -59,11 +59,12 @@
浏览量
点赞数
发布时间
+
操作
@@ -73,28 +74,50 @@
- {{ article.title }} + {{ article.resource?.title }}
- {{ getTagName(article.tagID) }} + {{ getTagName(article.resource.tagID) }} -
- {{ article.author || '-' }} + {{ article.resource?.author || '-' }}
浏览 - {{ formatNumber(article.viewCount) }} + {{ formatNumber(article.resource?.viewCount) }}
- {{ formatNumber(article.likeCount) }} + {{ formatNumber(article.resource?.likeCount) }}
- {{ formatDate(article.publishTime) }} + {{ formatDate(article.resource?.publishTime) }} +
+
+ + 已在TOP + + + + 已在思政
@@ -338,7 +361,7 @@ import { ElDialog, ElInput, ElButton, ElCheckbox, ElIcon, ElForm, ElFormItem, El import { Search } from '@element-plus/icons-vue'; import { AdminLayout } from '@/views/admin'; import { resourceRecommendApi, resourceApi, resourceTagApi } from '@/apis/resource'; -import type { ResourceRecommendVO, Resource, Tag } from '@/types'; +import type { ResourceRecommendVO, Resource, ResourceVO, Tag } from '@/types'; defineOptions({ name: 'ColumnManagementView' @@ -356,9 +379,15 @@ const topRecommends = ref([]); const ideologicalRecommends = ref([]); // 热门文章列表 -const hotArticles = ref([]); +const hotArticles = ref([]); const hotArticleLimit = ref(20); +// 添加至TOP的加载状态 +const addingToTop = ref>(new Set()); + +// 添加至思政的加载状态 +const addingToIdeological = ref>(new Set()); + // 标签列表 const tags = ref([]); @@ -381,7 +410,7 @@ const editForm = ref({ orderNum: 1 }); -// 挂载时加载标签和当前tab的数据 +// 挂载时加载标签数据和默认tab的数据 onMounted(() => { loadTags(); loadTabData(activeTab.value); @@ -445,7 +474,7 @@ async function loadHotArticles() { loading.value = true; try { // 获取已发布的文章,按浏览量排序 - const result = await resourceApi.getResourcePage( + const result = await resourceApi.getResourcePageOrderByViewCount( { pageNumber: 1, pageSize: hotArticleLimit.value @@ -458,7 +487,7 @@ async function loadHotArticles() { if (result.success && result.pageDomain?.dataList) { // 按浏览量降序排序 hotArticles.value = result.pageDomain.dataList - .sort((a, b) => (b.viewCount || 0) - (a.viewCount || 0)); + .sort((a, b) => (b.resource?.viewCount || 0) - (a.resource?.viewCount || 0)); } } catch (error) { console.error('加载热门文章失败:', error); @@ -510,7 +539,7 @@ async function showAddResourceDialog(recommendType: 1 | 2) { async function searchResources() { searchLoading.value = true; try { - const result = await resourceApi.getResourcePage( + const result = await resourceApi.getResourcePageOrderByViewCount( { pageNumber: 1, pageSize: 50 }, { keyword: searchKeyword.value, @@ -524,9 +553,9 @@ async function searchResources() { ? topRecommends.value.map(r => r.resourceID) : ideologicalRecommends.value.map(r => r.resourceID); - availableResources.value = result.pageDomain.dataList.filter( - r => !existingIds.includes(r.resourceID) - ); + availableResources.value = result.pageDomain.dataList + .map(vo => vo.resource) + .filter(r => r && !existingIds.includes(r.resourceID)) as Resource[]; } } catch (error) { console.error('搜索资源失败:', error); @@ -655,6 +684,79 @@ async function deleteRecommend(item: ResourceRecommendVO) { } } } + +// 检查资源是否已在TOP推荐中 +function isInTopRecommends(resourceID?: string): boolean { + if (!resourceID) return false; + return topRecommends.value.some(r => r.resourceID === resourceID); +} + +// 检查资源是否已在思政推荐中 +function isInIdeologicalRecommends(resourceID?: string): boolean { + if (!resourceID) return false; + return ideologicalRecommends.value.some(r => r.resourceID === resourceID); +} + +// 根据ResourceVO获取resourceID +function getResourceID(articleVO: ResourceVO): string | undefined { + return articleVO.resource?.resourceID; +} + +// 添加至TOP推荐 +async function addToTopRecommends(resourceID?: string) { + if (!resourceID) return; + + addingToTop.value.add(resourceID); + + try { + await resourceRecommendApi.batchAddRecommends([resourceID], 1); + ElMessage.success('添加至TOP推荐成功'); + + // 重新加载TOP推荐列表 + loadedTabs.value.delete('top-resources'); + const topResult = await resourceRecommendApi.getRecommendsByType(1); + if (topResult.success && topResult.dataList) { + topRecommends.value = topResult.dataList.sort((a, b) => (a.orderNum || 0) - (b.orderNum || 0)); + } + + // 重新加载热门文章统计以更新按钮状态 + loadedTabs.value.delete('hot-articles'); + await loadHotArticles(); + } catch (error) { + console.error('添加至TOP推荐失败:', error); + ElMessage.error('添加至TOP推荐失败'); + } finally { + addingToTop.value.delete(resourceID); + } +} + +// 添加至思政推荐 +async function addToIdeologicalRecommends(resourceID?: string) { + if (!resourceID) return; + + addingToIdeological.value.add(resourceID); + + try { + await resourceRecommendApi.batchAddRecommends([resourceID], 2); + ElMessage.success('添加至思政推荐成功'); + + // 重新加载思政推荐列表 + loadedTabs.value.delete('ideological'); + const ideologicalResult = await resourceRecommendApi.getRecommendsByType(2); + if (ideologicalResult.success && ideologicalResult.dataList) { + ideologicalRecommends.value = ideologicalResult.dataList.sort((a, b) => (a.orderNum || 0) - (b.orderNum || 0)); + } + + // 重新加载热门文章统计以更新按钮状态 + loadedTabs.value.delete('hot-articles'); + await loadHotArticles(); + } catch (error) { + console.error('添加至思政推荐失败:', error); + ElMessage.error('添加至思政推荐失败'); + } finally { + addingToIdeological.value.delete(resourceID); + } +} diff --git a/schoolNewsWeb/src/views/admin/manage/crontab/NewsCrawlerView.vue b/schoolNewsWeb/src/views/admin/manage/crontab/NewsCrawlerView.vue index 29a2013..9c0256d 100644 --- a/schoolNewsWeb/src/views/admin/manage/crontab/NewsCrawlerView.vue +++ b/schoolNewsWeb/src/views/admin/manage/crontab/NewsCrawlerView.vue @@ -362,8 +362,10 @@ const formData = reactive>({ }); // 监听模板选择变化 -watch(selectedTemplate, (newTemplate) => { - if (newTemplate) { +watch(selectedTemplate, (newTemplate, oldTemplate) => { + // 只在用户手动切换模板时重置(oldTemplate存在且不为null时才重置) + // 编辑回填时oldTemplate为null,不会触发重置 + if (newTemplate && oldTemplate) { selectedMethod.value = null; dynamicParams.value = {}; } @@ -476,6 +478,11 @@ function handleEdit(row: CrontabTask) { isEdit.value = true; Object.assign(formData, row); + // 重置选择 + selectedTemplate.value = null; + selectedMethod.value = null; + dynamicParams.value = {}; + // 尝试解析methodParams来回填表单 if (row.methodParams) { try { @@ -486,13 +493,20 @@ function handleEdit(row: CrontabTask) { t.methods.some(m => m.path === params.scriptPath) ); if (template) { - selectedTemplate.value = template; const method = template.methods.find(m => m.path === params.scriptPath); if (method) { + // 先设置template和method,触发watch填充默认值 + selectedTemplate.value = template; selectedMethod.value = method; - // 回填动态参数 + + // 然后使用nextTick确保watch执行完后再覆盖为实际值 + // 回填动态参数(排除scriptPath) const { scriptPath, ...restParams } = params; - dynamicParams.value = restParams; + // 延迟设置,确保watch先执行完 + setTimeout(() => { + dynamicParams.value = restParams; + console.log('📝 编辑回填 - template:', template.name, 'method:', method.name, 'params:', restParams); + }, 0); } } } @@ -645,19 +659,20 @@ async function handleSubmit() { submitting.value = true; try { + // 传递taskGroup和methodName(中文名),后端根据这两个name查找配置并填充beanName、methodName和scriptPath const data = { ...formData, - taskGroup: selectedTemplate.value.name, // 第一层name作为taskGroup - methodName: selectedMethod.value.name, // 第二层name作为methodName + taskGroup: selectedTemplate.value.name, // 模板名称(中文) + methodName: selectedMethod.value.name, // 方法名称(中文) methodParams: JSON.stringify({ - scriptPath: selectedMethod.value.path, - ...dynamicParams.value + ...dynamicParams.value // 只传用户输入的参数,scriptPath由后端填充 }) }; console.log('📤 准备提交的数据:', data); - console.log('📤 taskGroup (模板名称):', data.taskGroup); - console.log('📤 methodName (方法名称):', data.methodName); + console.log('📤 taskGroup:', selectedTemplate.value.name); + console.log('📤 methodName:', selectedMethod.value.name); + console.log('📤 动态参数:', dynamicParams.value); let result; if (isEdit.value) { diff --git a/schoolNewsWeb/src/views/admin/manage/logs/SystemLogsView.vue b/schoolNewsWeb/src/views/admin/manage/logs/SystemLogsView.vue index 03dad57..a89a689 100644 --- a/schoolNewsWeb/src/views/admin/manage/logs/SystemLogsView.vue +++ b/schoolNewsWeb/src/views/admin/manage/logs/SystemLogsView.vue @@ -70,133 +70,75 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - -
操作人操作模块操作类型请求链接IP地址操作描述操作时间操作
-
- 加载中... -
-
📋
-

暂无操作日志

-
{{ log.username || '-' }}{{ log.module || '-' }} - - {{ getOperationText(log.operation) }} - - -
{{ log.requestUrl || '-' }}
-
{{ log.ipAddress || '-' }} -
{{ truncateText(log.responseData, 50) }}
-
{{ formatTime(log?.createTime || '') || '-' }} - -
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -314,49 +256,9 @@ const currentPage = ref(1); const pageSize = ref(20); const total = ref(0); const logs = ref([]); -const jumpPage = ref(); const detailVisible = ref(false); const currentLog = ref(null); -// 计算总页数 -const totalPages = computed(() => Math.ceil(total.value / pageSize.value) || 1); - -// 计算显示的页码 -const displayPages = computed(() => { - const pages: number[] = []; - const maxDisplay = 7; - - if (totalPages.value <= maxDisplay) { - for (let i = 1; i <= totalPages.value; i++) { - pages.push(i); - } - } else { - if (currentPage.value <= 4) { - for (let i = 1; i <= 5; i++) { - pages.push(i); - } - pages.push(-1); - pages.push(totalPages.value); - } else if (currentPage.value >= totalPages.value - 3) { - pages.push(1); - pages.push(-1); - for (let i = totalPages.value - 4; i <= totalPages.value; i++) { - pages.push(i); - } - } else { - pages.push(1); - pages.push(-1); - for (let i = currentPage.value - 1; i <= currentPage.value + 1; i++) { - pages.push(i); - } - pages.push(-1); - pages.push(totalPages.value); - } - } - - return pages; -}); - onMounted(() => { loadLogs(); }); @@ -467,20 +369,16 @@ function viewDetail(log: OperationLog) { * 页码改变 */ function handlePageChange(page: number) { - if (page < 1 || page > totalPages.value || page === -1) return; currentPage.value = page; loadLogs(); } /** - * 跳转页面 + * 每页条数改变 */ -function handleJumpPage() { - if (!jumpPage.value || jumpPage.value < 1 || jumpPage.value > totalPages.value) { - alert('请输入有效的页码'); - return; - } - currentPage.value = jumpPage.value; +function handleSizeChange(size: number) { + pageSize.value = size; + currentPage.value = 1; // 重置到第一页 loadLogs(); } @@ -585,60 +483,7 @@ function handleJumpPage() { } } -.log-table-wrapper { - background: #fff; - border-radius: 8px; - overflow: hidden; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); -} - -.log-table { - width: 100%; - border-collapse: collapse; - - th { - background: #f5f7fa; - color: #606266; - font-weight: 600; - font-size: 14px; - padding: 12px 16px; - text-align: left; - border-bottom: 2px solid #e0e0e0; - } - - td { - padding: 12px 16px; - border-bottom: 1px solid #f0f0f0; - font-size: 14px; - color: #606266; - } - - .table-row { - transition: background-color 0.3s; - - &:hover { - background: #f5f7fa; - } - - &:last-child td { - border-bottom: none; - } - } -} - -.log-desc { - max-width: 300px; -} - -.desc-text { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.align-right { - text-align: right; -} +// el-table会自动处理样式,不需要自定义table样式 .status-tag { display: inline-block; @@ -678,154 +523,15 @@ function handleJumpPage() { } } -.action-cell { +// el-table和el-button会自动处理样式和交互 + +.pagination-container { display: flex; justify-content: center; - align-items: center; -} - -.btn-link { - border: none; - padding: 6px 12px; - font-size: 13px; - cursor: pointer; - transition: all 0.3s; - border-radius: 4px; - min-width: 64px; - text-align: center; - color: #fff; - - &:hover { - opacity: 0.8; - } - - &.btn-primary { - background: #409eff; - } -} - -.loading-cell, -.empty-cell { - text-align: center; - padding: 60px 20px !important; - color: #909399; -} - -.loading-spinner { - display: inline-block; - width: 40px; - height: 40px; - border: 4px solid #f3f3f3; - border-top: 4px solid #409eff; - border-radius: 50%; - animation: spin 1s linear infinite; - margin-bottom: 12px; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -.empty-icon { - font-size: 48px; - margin-bottom: 12px; -} - -.pagination { - display: flex; - justify-content: space-between; - align-items: center; margin-top: 20px; - padding: 20px; - background: #fff; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); - flex-wrap: wrap; - gap: 16px; } -.pagination-info { - font-size: 14px; - color: #606266; -} -.pagination-controls { - display: flex; - gap: 8px; - align-items: center; -} - -.page-btn, -.page-number { - padding: 6px 12px; - border: 1px solid #dcdfe6; - background: #fff; - color: #606266; - border-radius: 4px; - font-size: 14px; - cursor: pointer; - transition: all 0.3s; - - &:hover:not(:disabled) { - color: #409eff; - border-color: #409eff; - } - - &:disabled { - cursor: not-allowed; - opacity: 0.5; - } - - &.active { - background: #409eff; - color: #fff; - border-color: #409eff; - } -} - -.page-numbers { - display: flex; - gap: 4px; -} - -.pagination-jump { - display: flex; - align-items: center; - gap: 8px; - font-size: 14px; - color: #606266; - - .jump-input { - width: 60px; - padding: 6px 8px; - border: 1px solid #dcdfe6; - border-radius: 4px; - font-size: 14px; - text-align: center; - - &:focus { - outline: none; - border-color: #409eff; - } - } - - .jump-btn { - padding: 6px 12px; - border: 1px solid #dcdfe6; - background: #fff; - color: #606266; - border-radius: 4px; - font-size: 14px; - cursor: pointer; - transition: all 0.3s; - - &:hover { - color: #409eff; - border-color: #409eff; - } - } -} // 模态框样式 .modal-overlay {