成就等界面接口调整
This commit is contained in:
@@ -21,6 +21,23 @@ CREATE TABLE `tb_learning_task` (
|
|||||||
KEY `idx_time` (`start_time`, `end_time`)
|
KEY `idx_time` (`start_time`, `end_time`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='学习任务表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='学习任务表';
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `tb_learning_task_tag`;
|
||||||
|
CREATE TABLE `tb_learning_task_tag` (
|
||||||
|
`id` VARCHAR(50) NOT NULL COMMENT '关联ID',
|
||||||
|
`task_id` VARCHAR(50) NOT NULL COMMENT '任务ID',
|
||||||
|
`tag_id` VARCHAR(50) NOT NULL COMMENT '标签ID',
|
||||||
|
`creator` VARCHAR(50) DEFAULT NULL COMMENT '创建者',
|
||||||
|
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_task_tag` (`task_id`, `tag_id`),
|
||||||
|
KEY `idx_task_id` (`task_id`),
|
||||||
|
KEY `idx_tag_id` (`tag_id`),
|
||||||
|
CONSTRAINT `fk_task_tag_task` FOREIGN KEY (`task_id`) REFERENCES `tb_learning_task` (`task_id`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `fk_task_tag_tag` FOREIGN KEY (`tag_id`) REFERENCES `tb_tag` (`tag_id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='学习任务标签关联表';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- 任务项关联表(统一管理资源和课程)
|
-- 任务项关联表(统一管理资源和课程)
|
||||||
DROP TABLE IF EXISTS `tb_task_item`;
|
DROP TABLE IF EXISTS `tb_task_item`;
|
||||||
CREATE TABLE `tb_task_item` (
|
CREATE TABLE `tb_task_item` (
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ DROP TABLE IF EXISTS `tb_resource_recommend`;
|
|||||||
CREATE TABLE `tb_resource_recommend` (
|
CREATE TABLE `tb_resource_recommend` (
|
||||||
`id` VARCHAR(50) NOT NULL COMMENT '推荐ID',
|
`id` VARCHAR(50) NOT NULL COMMENT '推荐ID',
|
||||||
`resource_id` VARCHAR(50) NOT NULL COMMENT '资源ID',
|
`resource_id` VARCHAR(50) NOT NULL COMMENT '资源ID',
|
||||||
|
`recommend_type` INT(4) DEFAULT 1 COMMENT '推荐类型(1-热门资源,2-思政资源)',
|
||||||
`order_num` INT(4) DEFAULT 0 COMMENT '排序号',
|
`order_num` INT(4) DEFAULT 0 COMMENT '排序号',
|
||||||
`reason` VARCHAR(255) DEFAULT NULL COMMENT '推荐理由',
|
`reason` VARCHAR(255) DEFAULT NULL COMMENT '推荐理由',
|
||||||
`creator` VARCHAR(50) DEFAULT NULL COMMENT '创建者',
|
`creator` VARCHAR(50) DEFAULT NULL COMMENT '创建者',
|
||||||
@@ -72,8 +73,9 @@ CREATE TABLE `tb_resource_recommend` (
|
|||||||
`delete_time` TIMESTAMP NULL DEFAULT NULL COMMENT '删除时间',
|
`delete_time` TIMESTAMP NULL DEFAULT NULL COMMENT '删除时间',
|
||||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `uk_resource` (`resource_id`),
|
KEY `idx_resource_id` (`resource_id`),
|
||||||
KEY `idx_order` (`order_num`)
|
KEY `idx_recommend_type` (`recommend_type`),
|
||||||
|
CONSTRAINT `fk_resource_recommend_resource` FOREIGN KEY (`resource_id`) REFERENCES `tb_resource` (`resource_id`) ON DELETE CASCADE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='资源推荐表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='资源推荐表';
|
||||||
|
|
||||||
-- 标签表
|
-- 标签表
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, creat
|
|||||||
INSERT INTO `tb_sys_menu` VALUES
|
INSERT INTO `tb_sys_menu` VALUES
|
||||||
-- 用户前端菜单 (100-699)
|
-- 用户前端菜单 (100-699)
|
||||||
('100', 'menu_home', '首页', NULL, '/home', 'user/home/HomeView', NULL, 1, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
('100', 'menu_home', '首页', NULL, '/home', 'user/home/HomeView', NULL, 1, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||||
|
('101', 'menu_resource_hot', '热门资源', NULL, '/resource-hot', 'user/resource-center/HotResourceView', NULL, 2, 3, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||||
('200', 'menu_resource_center', '资源中心', NULL, '/resource-center', 'user/resource-center/ResourceCenterView', NULL, 2, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
('200', 'menu_resource_center', '资源中心', NULL, '/resource-center', 'user/resource-center/ResourceCenterView', NULL, 2, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0),
|
||||||
('300', 'menu_study_plan', '学习计划', NULL, '/study-plan', 'user/study-plan/StudyPlanView', NULL, 3, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
('300', 'menu_study_plan', '学习计划', NULL, '/study-plan', 'user/study-plan/StudyPlanView', NULL, 3, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||||
('301', 'menu_study_tasks', '学习任务', 'menu_study_plan', '/study-plan/tasks', 'user/study-plan/StudyTasksView', NULL, 1, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
('301', 'menu_study_tasks', '学习任务', 'menu_study_plan', '/study-plan/tasks', 'user/study-plan/StudyTasksView', NULL, 1, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||||
@@ -146,6 +147,7 @@ INSERT INTO `tb_sys_menu` VALUES
|
|||||||
INSERT INTO `tb_sys_menu_permission` (id, permission_id, menu_id, creator, create_time) VALUES
|
INSERT INTO `tb_sys_menu_permission` (id, permission_id, menu_id, creator, create_time) VALUES
|
||||||
-- 前端菜单权限关联
|
-- 前端菜单权限关联
|
||||||
('100', 'perm_default', 'menu_home', '1', now()),
|
('100', 'perm_default', 'menu_home', '1', now()),
|
||||||
|
('102', 'perm_default', 'menu_resource_hot', '1', now()),
|
||||||
('101', 'perm_default', 'menu_resource_center', '1', now()),
|
('101', 'perm_default', 'menu_resource_center', '1', now()),
|
||||||
('108', 'perm_default', 'menu_study_plan', '1', now()),
|
('108', 'perm_default', 'menu_study_plan', '1', now()),
|
||||||
('109', 'perm_default', 'menu_study_tasks', '1', now()),
|
('109', 'perm_default', 'menu_study_tasks', '1', now()),
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package org.xyzh.api.news.recommend;
|
package org.xyzh.api.news.recommend;
|
||||||
|
|
||||||
import org.xyzh.common.core.domain.ResultDomain;
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
|
import org.xyzh.common.core.page.PageParam;
|
||||||
import org.xyzh.common.dto.resource.TbResourceRecommend;
|
import org.xyzh.common.dto.resource.TbResourceRecommend;
|
||||||
|
import org.xyzh.common.vo.ResourceRecommendVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -15,12 +17,12 @@ import java.util.List;
|
|||||||
public interface ResourceRecommendService {
|
public interface ResourceRecommendService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 获取推荐资源列表
|
* @description 获取推荐资源列表(包含资源详情和权限信息)
|
||||||
* @return ResultDomain<TbResourceRecommend> 推荐资源列表
|
* @return ResultDomain<ResourceRecommendVO> 推荐资源列表
|
||||||
* @author yslg
|
* @author yslg
|
||||||
* @since 2025-10-15
|
* @since 2025-10-15
|
||||||
*/
|
*/
|
||||||
ResultDomain<TbResourceRecommend> getRecommendList();
|
ResultDomain<ResourceRecommendVO> getRecommendList();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 添加推荐资源
|
* @description 添加推荐资源
|
||||||
@@ -89,12 +91,13 @@ public interface ResourceRecommendService {
|
|||||||
/**
|
/**
|
||||||
* @description 批量添加推荐资源
|
* @description 批量添加推荐资源
|
||||||
* @param resourceIDs 资源ID列表
|
* @param resourceIDs 资源ID列表
|
||||||
|
* @param recommendType 推荐类型
|
||||||
* @param reason 推荐理由
|
* @param reason 推荐理由
|
||||||
* @return ResultDomain<TbResourceRecommend> 添加结果
|
* @return ResultDomain<TbResourceRecommend> 添加结果
|
||||||
* @author yslg
|
* @author yslg
|
||||||
* @since 2025-10-15
|
* @since 2025-10-15
|
||||||
*/
|
*/
|
||||||
ResultDomain<TbResourceRecommend> batchAddRecommends(List<String> resourceIDs, String reason);
|
ResultDomain<TbResourceRecommend> batchAddRecommends(List<String> resourceIDs, Integer recommendType, String reason);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 批量移除推荐资源
|
* @description 批量移除推荐资源
|
||||||
@@ -113,4 +116,43 @@ public interface ResourceRecommendService {
|
|||||||
* @since 2025-10-15
|
* @since 2025-10-15
|
||||||
*/
|
*/
|
||||||
ResultDomain<TbResourceRecommend> getRecommendDetail(String recommendID);
|
ResultDomain<TbResourceRecommend> getRecommendDetail(String recommendID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 分页查询推荐资源列表(包含资源详情)
|
||||||
|
* @param filter 过滤条件
|
||||||
|
* @param pageParam 分页参数
|
||||||
|
* @return ResultDomain<ResourceRecommendVO> 推荐资源分页列表
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-01-XX
|
||||||
|
*/
|
||||||
|
ResultDomain<ResourceRecommendVO> getRecommendPage(TbResourceRecommend filter, PageParam pageParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 统计推荐资源总数
|
||||||
|
* @param filter 过滤条件
|
||||||
|
* @return ResultDomain<Long> 总数
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-01-XX
|
||||||
|
*/
|
||||||
|
ResultDomain<Long> countRecommends(TbResourceRecommend filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 根据推荐类型获取推荐资源列表(包含资源详情)
|
||||||
|
* @param recommendType 推荐类型(1-热门资源,2-思政资源)
|
||||||
|
* @param limit 限制数量
|
||||||
|
* @return ResultDomain<ResourceRecommendVO> 推荐资源列表
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-01-XX
|
||||||
|
*/
|
||||||
|
ResultDomain<ResourceRecommendVO> getRecommendsByType(Integer recommendType, Integer limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 根据推荐类型和资源ID检查是否已推荐
|
||||||
|
* @param resourceID 资源ID
|
||||||
|
* @param recommendType 推荐类型
|
||||||
|
* @return ResultDomain<Boolean> 是否已推荐
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-01-XX
|
||||||
|
*/
|
||||||
|
ResultDomain<Boolean> isResourceRecommendedByType(String resourceID, Integer recommendType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,15 @@ public interface ResourceService {
|
|||||||
*/
|
*/
|
||||||
ResultDomain<TbResource> getResourcePage(TbResource filter, PageParam pageParam);
|
ResultDomain<TbResource> getResourcePage(TbResource filter, PageParam pageParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取资源分页(按浏览次数排序)
|
||||||
|
* @param filter 过滤条件
|
||||||
|
* @param pageParam 分页参数
|
||||||
|
* @return ResultDomain<TbResource> 资源分页
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-10-15
|
||||||
|
*/
|
||||||
|
ResultDomain<TbResource> getResourcePageOrderByViewCount(TbResource filter, PageParam pageParam);
|
||||||
/**
|
/**
|
||||||
* @description 根据ID获取资源详情
|
* @description 根据ID获取资源详情
|
||||||
* @param resourceID 资源ID
|
* @param resourceID 资源ID
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public interface TagService {
|
|||||||
* @author yslg
|
* @author yslg
|
||||||
* @since 2025-10-15
|
* @since 2025-10-15
|
||||||
*/
|
*/
|
||||||
ResultDomain<TbTag> getAllTags();
|
ResultDomain<TbTag> getTagList(TbTag filter);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 根据ID获取标签详情
|
* @description 根据ID获取标签详情
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.xyzh.api.usercenter.collection;
|
|||||||
|
|
||||||
import org.xyzh.common.core.domain.ResultDomain;
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
import org.xyzh.common.dto.usercenter.TbUserCollection;
|
import org.xyzh.common.dto.usercenter.TbUserCollection;
|
||||||
|
import org.xyzh.common.vo.UserCollectionVO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 用户收藏服务接口
|
* @description 用户收藏服务接口
|
||||||
@@ -42,14 +43,14 @@ public interface UserCollectionService {
|
|||||||
ResultDomain<Boolean> isCollected(String userID, Integer collectionType, String collectionID);
|
ResultDomain<Boolean> isCollected(String userID, Integer collectionType, String collectionID);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 获取用户收藏列表
|
* @description 获取用户收藏列表(扁平化VO,包含详情)
|
||||||
* @param userID 用户ID
|
* @param userID 用户ID
|
||||||
* @param collectionType 收藏类型(可选)
|
* @param collectionType 收藏类型(可选)
|
||||||
* @return ResultDomain<TbUserCollection> 收藏列表
|
* @return ResultDomain<UserCollectionVO> 收藏列表(包含资源/课程详情)
|
||||||
* @author yslg
|
* @author yslg
|
||||||
* @since 2025-10-15
|
* @since 2025-10-31
|
||||||
*/
|
*/
|
||||||
ResultDomain<TbUserCollection> getUserCollections(String userID, Integer collectionType);
|
ResultDomain<UserCollectionVO> getUserCollections(String userID, Integer collectionType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 获取收藏详情
|
* @description 获取收藏详情
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ public class TbResourceRecommend extends BaseDTO {
|
|||||||
*/
|
*/
|
||||||
private Integer orderNum;
|
private Integer orderNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 推荐类型(1-热门资源,2-思政资源)
|
||||||
|
*/
|
||||||
|
private Integer recommendType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 推荐理由
|
* @description 推荐理由
|
||||||
*/
|
*/
|
||||||
@@ -54,6 +59,14 @@ public class TbResourceRecommend extends BaseDTO {
|
|||||||
this.orderNum = orderNum;
|
this.orderNum = orderNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getRecommendType() {
|
||||||
|
return recommendType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecommendType(Integer recommendType) {
|
||||||
|
this.recommendType = recommendType;
|
||||||
|
}
|
||||||
|
|
||||||
public String getReason() {
|
public String getReason() {
|
||||||
return reason;
|
return reason;
|
||||||
}
|
}
|
||||||
@@ -83,6 +96,7 @@ public class TbResourceRecommend extends BaseDTO {
|
|||||||
return "TbResourceRecommend{" +
|
return "TbResourceRecommend{" +
|
||||||
"id=" + getID() +
|
"id=" + getID() +
|
||||||
", resourceID='" + resourceID + '\'' +
|
", resourceID='" + resourceID + '\'' +
|
||||||
|
", recommendType=" + recommendType +
|
||||||
", orderNum=" + orderNum +
|
", orderNum=" + orderNum +
|
||||||
", reason='" + reason + '\'' +
|
", reason='" + reason + '\'' +
|
||||||
", createTime=" + getCreateTime() +
|
", createTime=" + getCreateTime() +
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.xyzh.common.dto.study;
|
||||||
|
|
||||||
|
import org.xyzh.common.dto.BaseDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 学习任务标签关联实体
|
||||||
|
* @filename TbLearningTaskTag.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-10-31
|
||||||
|
*/
|
||||||
|
public class TbLearningTaskTag extends BaseDTO {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务ID
|
||||||
|
*/
|
||||||
|
private String taskID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签ID
|
||||||
|
*/
|
||||||
|
private String tagID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
private String creator;
|
||||||
|
|
||||||
|
public String getTaskID() {
|
||||||
|
return taskID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTaskID(String taskID) {
|
||||||
|
this.taskID = taskID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTagID() {
|
||||||
|
return tagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTagID(String tagID) {
|
||||||
|
this.tagID = tagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreator() {
|
||||||
|
return creator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreator(String creator) {
|
||||||
|
this.creator = creator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TbLearningTaskTag{" +
|
||||||
|
"id='" + getID() + '\'' +
|
||||||
|
", taskID='" + taskID + '\'' +
|
||||||
|
", tagID='" + tagID + '\'' +
|
||||||
|
", creator='" + creator + '\'' +
|
||||||
|
", createTime=" + getCreateTime() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
package org.xyzh.common.vo;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 资源推荐VO(用于管理端,平铺结构)
|
||||||
|
* @filename ResourceRecommendVO.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-01-XX
|
||||||
|
*/
|
||||||
|
public class ResourceRecommendVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
// ==================== 推荐表字段 ====================
|
||||||
|
/**
|
||||||
|
* 推荐ID
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源ID
|
||||||
|
*/
|
||||||
|
private String resourceID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐类型(1-热门资源,2-思政资源)
|
||||||
|
*/
|
||||||
|
private Integer recommendType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序号
|
||||||
|
*/
|
||||||
|
private Integer orderNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐理由
|
||||||
|
*/
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐创建者
|
||||||
|
*/
|
||||||
|
private String creator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐更新者
|
||||||
|
*/
|
||||||
|
private String updater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐创建时间
|
||||||
|
*/
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐更新时间
|
||||||
|
*/
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐删除时间
|
||||||
|
*/
|
||||||
|
private Date deleteTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐是否删除
|
||||||
|
*/
|
||||||
|
private Boolean deleted;
|
||||||
|
|
||||||
|
// ==================== 资源表字段 ====================
|
||||||
|
/**
|
||||||
|
* 资源标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源简介
|
||||||
|
*/
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封面图片
|
||||||
|
*/
|
||||||
|
private String coverImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签ID(文章分类标签,tagType=1)
|
||||||
|
*/
|
||||||
|
private String tagID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作者
|
||||||
|
*/
|
||||||
|
private String author;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 来源
|
||||||
|
*/
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 来源URL
|
||||||
|
*/
|
||||||
|
private String sourceUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览次数
|
||||||
|
*/
|
||||||
|
private Integer viewCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点赞次数
|
||||||
|
*/
|
||||||
|
private Integer likeCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收藏次数
|
||||||
|
*/
|
||||||
|
private Integer collectCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态(0草稿 1已发布 2下架)
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否推荐
|
||||||
|
*/
|
||||||
|
private Boolean isRecommend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否轮播
|
||||||
|
*/
|
||||||
|
private Boolean isBanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布时间
|
||||||
|
*/
|
||||||
|
private Date publishTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源创建者
|
||||||
|
*/
|
||||||
|
private String resourceCreator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源更新者
|
||||||
|
*/
|
||||||
|
private String resourceUpdater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源创建时间
|
||||||
|
*/
|
||||||
|
private Date resourceCreateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源更新时间
|
||||||
|
*/
|
||||||
|
private Date resourceUpdateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户是否有读权限
|
||||||
|
*/
|
||||||
|
private Boolean canRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户是否有写权限
|
||||||
|
*/
|
||||||
|
private Boolean canWrite;
|
||||||
|
|
||||||
|
// ==================== Getter and Setter ====================
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceID() {
|
||||||
|
return resourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceID(String resourceID) {
|
||||||
|
this.resourceID = resourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getRecommendType() {
|
||||||
|
return recommendType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecommendType(Integer recommendType) {
|
||||||
|
this.recommendType = recommendType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getOrderNum() {
|
||||||
|
return orderNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderNum(Integer orderNum) {
|
||||||
|
this.orderNum = orderNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReason(String reason) {
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreator() {
|
||||||
|
return creator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreator(String creator) {
|
||||||
|
this.creator = creator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdater() {
|
||||||
|
return updater;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdater(String updater) {
|
||||||
|
this.updater = updater;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreateTime() {
|
||||||
|
return createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateTime(Date createTime) {
|
||||||
|
this.createTime = createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getUpdateTime() {
|
||||||
|
return updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateTime(Date updateTime) {
|
||||||
|
this.updateTime = updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDeleteTime() {
|
||||||
|
return deleteTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeleteTime(Date deleteTime) {
|
||||||
|
this.deleteTime = deleteTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getDeleted() {
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeleted(Boolean deleted) {
|
||||||
|
this.deleted = deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSummary(String summary) {
|
||||||
|
this.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCoverImage() {
|
||||||
|
return coverImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCoverImage(String coverImage) {
|
||||||
|
this.coverImage = coverImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTagID() {
|
||||||
|
return tagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTagID(String tagID) {
|
||||||
|
this.tagID = tagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSource(String source) {
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceUrl() {
|
||||||
|
return sourceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceUrl(String sourceUrl) {
|
||||||
|
this.sourceUrl = sourceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getViewCount() {
|
||||||
|
return viewCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setViewCount(Integer viewCount) {
|
||||||
|
this.viewCount = viewCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLikeCount() {
|
||||||
|
return likeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLikeCount(Integer likeCount) {
|
||||||
|
this.likeCount = likeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCollectCount() {
|
||||||
|
return collectCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCollectCount(Integer collectCount) {
|
||||||
|
this.collectCount = collectCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Integer status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getIsRecommend() {
|
||||||
|
return isRecommend;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsRecommend(Boolean isRecommend) {
|
||||||
|
this.isRecommend = isRecommend;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getIsBanner() {
|
||||||
|
return isBanner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsBanner(Boolean isBanner) {
|
||||||
|
this.isBanner = isBanner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getPublishTime() {
|
||||||
|
return publishTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPublishTime(Date publishTime) {
|
||||||
|
this.publishTime = publishTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceCreator() {
|
||||||
|
return resourceCreator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceCreator(String resourceCreator) {
|
||||||
|
this.resourceCreator = resourceCreator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceUpdater() {
|
||||||
|
return resourceUpdater;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceUpdater(String resourceUpdater) {
|
||||||
|
this.resourceUpdater = resourceUpdater;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getResourceCreateTime() {
|
||||||
|
return resourceCreateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceCreateTime(Date resourceCreateTime) {
|
||||||
|
this.resourceCreateTime = resourceCreateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getResourceUpdateTime() {
|
||||||
|
return resourceUpdateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceUpdateTime(Date resourceUpdateTime) {
|
||||||
|
this.resourceUpdateTime = resourceUpdateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getCanRead() {
|
||||||
|
return canRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanRead(Boolean canRead) {
|
||||||
|
this.canRead = canRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getCanWrite() {
|
||||||
|
return canWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanWrite(Boolean canWrite) {
|
||||||
|
this.canWrite = canWrite;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,16 @@ public class ResourceVO implements Serializable{
|
|||||||
*/
|
*/
|
||||||
private List<TbTag> tags;
|
private List<TbTag> tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否推荐(用于首页展示)
|
||||||
|
*/
|
||||||
|
private Boolean isRecommended;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐类型(1-热门资源,2-思政资源)
|
||||||
|
*/
|
||||||
|
private Integer recommendType;
|
||||||
|
|
||||||
public TbResource getResource() {
|
public TbResource getResource() {
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
@@ -39,4 +49,20 @@ public class ResourceVO implements Serializable{
|
|||||||
public void setTags(List<TbTag> tags) {
|
public void setTags(List<TbTag> tags) {
|
||||||
this.tags = tags;
|
this.tags = tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getIsRecommended() {
|
||||||
|
return isRecommended;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsRecommended(Boolean isRecommended) {
|
||||||
|
this.isRecommended = isRecommended;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getRecommendType() {
|
||||||
|
return recommendType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecommendType(Integer recommendType) {
|
||||||
|
this.recommendType = recommendType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.xyzh.common.dto.BaseDTO;
|
|||||||
import org.xyzh.common.dto.study.TbLearningTask;
|
import org.xyzh.common.dto.study.TbLearningTask;
|
||||||
import org.xyzh.common.dto.study.TbTaskItem;
|
import org.xyzh.common.dto.study.TbTaskItem;
|
||||||
import org.xyzh.common.dto.study.TbTaskUser;
|
import org.xyzh.common.dto.study.TbTaskUser;
|
||||||
|
import org.xyzh.common.dto.resource.TbTag;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -15,6 +16,7 @@ public class TaskVO extends BaseDTO{
|
|||||||
private List<TaskItemVO> taskCourses;
|
private List<TaskItemVO> taskCourses;
|
||||||
private List<TaskItemVO> taskResources;
|
private List<TaskItemVO> taskResources;
|
||||||
private List<TaskItemVO> taskUsers;
|
private List<TaskItemVO> taskUsers;
|
||||||
|
private List<TagVO> taskTags;
|
||||||
private Integer totalTaskNum;
|
private Integer totalTaskNum;
|
||||||
private Integer completedTaskNum;
|
private Integer completedTaskNum;
|
||||||
private Integer learningTaskNum;
|
private Integer learningTaskNum;
|
||||||
@@ -86,6 +88,14 @@ public class TaskVO extends BaseDTO{
|
|||||||
return getTaskUsers().stream().map(TaskItemVO::toTaskUser).collect(Collectors.toList());
|
return getTaskUsers().stream().map(TaskItemVO::toTaskUser).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<TagVO> getTaskTags() {
|
||||||
|
return taskTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTaskTags(List<TagVO> taskTags) {
|
||||||
|
this.taskTags = taskTags;
|
||||||
|
}
|
||||||
|
|
||||||
public void setTaskItemList(List<TbTaskItem> taskItems) {
|
public void setTaskItemList(List<TbTaskItem> taskItems) {
|
||||||
if (taskItems == null) {
|
if (taskItems == null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
package org.xyzh.common.vo;
|
||||||
|
|
||||||
|
import org.xyzh.common.dto.BaseDTO;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 用户收藏VO - 扁平化收藏信息和关联资源/课程详情
|
||||||
|
* @filename UserCollectionVO.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-10-31
|
||||||
|
*/
|
||||||
|
public class UserCollectionVO extends BaseDTO {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
// ========== 收藏基本信息 ==========
|
||||||
|
/**
|
||||||
|
* @description 用户ID
|
||||||
|
*/
|
||||||
|
private String userID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 收藏类型(1资源 2课程)
|
||||||
|
*/
|
||||||
|
private Integer collectionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 收藏对象ID
|
||||||
|
*/
|
||||||
|
private String collectionID;
|
||||||
|
|
||||||
|
private Integer collectionValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 收藏时间
|
||||||
|
*/
|
||||||
|
private Date collectionTime;
|
||||||
|
|
||||||
|
// ========== 资源详情(collectionType=1时有效) ==========
|
||||||
|
/**
|
||||||
|
* @description 资源ID
|
||||||
|
*/
|
||||||
|
private String resourceID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 资源标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 资源内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 资源简介
|
||||||
|
*/
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 封面图片
|
||||||
|
*/
|
||||||
|
private String coverImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 标签ID
|
||||||
|
*/
|
||||||
|
private String tagID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 作者
|
||||||
|
*/
|
||||||
|
private String author;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 来源
|
||||||
|
*/
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 浏览次数
|
||||||
|
*/
|
||||||
|
private Integer viewCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 点赞次数
|
||||||
|
*/
|
||||||
|
private Integer likeCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 收藏次数
|
||||||
|
*/
|
||||||
|
private Integer collectCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 资源状态
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 发布时间
|
||||||
|
*/
|
||||||
|
private Date publishTime;
|
||||||
|
|
||||||
|
// ========== 课程详情(collectionType=2时有效) ==========
|
||||||
|
/**
|
||||||
|
* @description 课程ID
|
||||||
|
*/
|
||||||
|
private String courseID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 课程名称
|
||||||
|
*/
|
||||||
|
private String courseName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 课程描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 课程时长(分钟)
|
||||||
|
*/
|
||||||
|
private Integer duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 授课老师
|
||||||
|
*/
|
||||||
|
private String teacher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 课程状态
|
||||||
|
*/
|
||||||
|
private Integer courseStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 学习人数
|
||||||
|
*/
|
||||||
|
private Integer learnCount;
|
||||||
|
|
||||||
|
// ========== Getters and Setters ==========
|
||||||
|
|
||||||
|
public String getUserID() {
|
||||||
|
return userID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserID(String userID) {
|
||||||
|
this.userID = userID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCollectionType() {
|
||||||
|
return collectionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCollectionType(Integer collectionType) {
|
||||||
|
this.collectionType = collectionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCollectionID() {
|
||||||
|
return collectionID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCollectionID(String collectionID) {
|
||||||
|
this.collectionID = collectionID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCollectionValue() {
|
||||||
|
return collectionValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCollectionValue(Integer collectionValue) {
|
||||||
|
this.collectionValue = collectionValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCollectionTime() {
|
||||||
|
return collectionTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCollectionTime(Date collectionTime) {
|
||||||
|
this.collectionTime = collectionTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceID() {
|
||||||
|
return resourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceID(String resourceID) {
|
||||||
|
this.resourceID = resourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSummary(String summary) {
|
||||||
|
this.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCoverImage() {
|
||||||
|
return coverImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCoverImage(String coverImage) {
|
||||||
|
this.coverImage = coverImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTagID() {
|
||||||
|
return tagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTagID(String tagID) {
|
||||||
|
this.tagID = tagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSource(String source) {
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getViewCount() {
|
||||||
|
return viewCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setViewCount(Integer viewCount) {
|
||||||
|
this.viewCount = viewCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLikeCount() {
|
||||||
|
return likeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLikeCount(Integer likeCount) {
|
||||||
|
this.likeCount = likeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCollectCount() {
|
||||||
|
return collectCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCollectCount(Integer collectCount) {
|
||||||
|
this.collectCount = collectCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Integer status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getPublishTime() {
|
||||||
|
return publishTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPublishTime(Date publishTime) {
|
||||||
|
this.publishTime = publishTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCourseID() {
|
||||||
|
return courseID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCourseID(String courseID) {
|
||||||
|
this.courseID = courseID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCourseName() {
|
||||||
|
return courseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCourseName(String courseName) {
|
||||||
|
this.courseName = courseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getDuration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDuration(Integer duration) {
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTeacher() {
|
||||||
|
return teacher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTeacher(String teacher) {
|
||||||
|
this.teacher = teacher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCourseStatus() {
|
||||||
|
return courseStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCourseStatus(Integer courseStatus) {
|
||||||
|
this.courseStatus = courseStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLearnCount() {
|
||||||
|
return learnCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLearnCount(Integer learnCount) {
|
||||||
|
this.learnCount = learnCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "UserCollectionVO{" +
|
||||||
|
"id=" + getID() +
|
||||||
|
", userID='" + userID + '\'' +
|
||||||
|
", collectionType=" + collectionType +
|
||||||
|
", collectionID='" + collectionID + '\'' +
|
||||||
|
", collectionTime=" + collectionTime +
|
||||||
|
", resourceID='" + resourceID + '\'' +
|
||||||
|
", title='" + title + '\'' +
|
||||||
|
", courseID='" + courseID + '\'' +
|
||||||
|
", courseName='" + courseName + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,10 +2,13 @@ package org.xyzh.news.controller;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.xyzh.api.news.recommend.ResourceRecommendService;
|
||||||
import org.xyzh.common.core.domain.ResultDomain;
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
import org.xyzh.common.dto.resource.TbResource;
|
import org.xyzh.common.dto.resource.TbResource;
|
||||||
import org.xyzh.common.dto.resource.TbBanner;
|
import org.xyzh.common.dto.resource.TbBanner;
|
||||||
|
import org.xyzh.common.vo.ResourceRecommendVO;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -21,197 +24,23 @@ import java.util.Map;
|
|||||||
public class HomePageController {
|
public class HomePageController {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(HomePageController.class);
|
private static final Logger logger = LoggerFactory.getLogger(HomePageController.class);
|
||||||
|
|
||||||
// ==================== 轮播组件管理 ====================
|
@Autowired
|
||||||
|
private ResourceRecommendService resourceRecommendService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取轮播组件数据
|
* 获取热门资源列表
|
||||||
*/
|
*/
|
||||||
@GetMapping("/banner/list")
|
@GetMapping("/recommend/hot")
|
||||||
public ResultDomain<TbBanner> getBannerList() {
|
public ResultDomain<ResourceRecommendVO> getHotResources(@RequestParam(required = false, name = "limit") Integer limit) {
|
||||||
// TODO: 实现获取轮播组件数据(自动轮播核心新闻,支持手动切换)
|
return resourceRecommendService.getRecommendsByType(1, limit); // 1-热门资源
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 点击轮播跳转新闻详情
|
|
||||||
*/
|
|
||||||
@GetMapping("/banner/click/{bannerID}")
|
|
||||||
public ResultDomain<TbResource> getBannerNewsDetail(@PathVariable String bannerID) {
|
|
||||||
// TODO: 实现点击跳转新闻详情
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取活跃轮播列表
|
* 获取思政资源列表
|
||||||
*/
|
*/
|
||||||
@GetMapping("/banner/active")
|
@GetMapping("/recommend/ideological")
|
||||||
public ResultDomain<TbBanner> getActiveBanners() {
|
public ResultDomain<ResourceRecommendVO> getIdeologicalResources(@RequestParam(required = false, name = "limit") Integer limit) {
|
||||||
// TODO: 实现获取活跃轮播列表
|
return resourceRecommendService.getRecommendsByType(2, limit); // 2-思政资源
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== TOP资源推荐 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取TOP资源推荐列表
|
|
||||||
*/
|
|
||||||
@GetMapping("/recommend/top-list")
|
|
||||||
public ResultDomain<TbResource> getTopRecommendList() {
|
|
||||||
// TODO: 实现获取TOP资源推荐列表(按浏览数据展示高热度新闻)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 后台调控展示顺序
|
|
||||||
*/
|
|
||||||
@PutMapping("/recommend/order")
|
|
||||||
public ResultDomain<Boolean> updateRecommendOrder(@RequestBody Map<String, Object> orderData) {
|
|
||||||
// TODO: 实现后台调控展示顺序与内容
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取高热度新闻
|
|
||||||
*/
|
|
||||||
@GetMapping("/recommend/hot-news")
|
|
||||||
public ResultDomain<TbResource> getHotNews(@RequestParam(required = false) Integer limit) {
|
|
||||||
// TODO: 实现获取高热度新闻
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 思政新闻概览 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取思政新闻概览
|
|
||||||
*/
|
|
||||||
@GetMapping("/news/overview")
|
|
||||||
public ResultDomain<TbResource> getNewsOverview(
|
|
||||||
@RequestParam(required = false) Integer pageNum,
|
|
||||||
@RequestParam(required = false) Integer pageSize) {
|
|
||||||
// TODO: 实现获取思政新闻概览(以卡片形式展示新闻标题、发布时间、简介)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 点击跳转二级详情页
|
|
||||||
*/
|
|
||||||
@GetMapping("/news/detail/{newsID}")
|
|
||||||
public ResultDomain<TbResource> getNewsDetail(@PathVariable String newsID) {
|
|
||||||
// TODO: 实现点击跳转二级详情页
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取最新思政新闻
|
|
||||||
*/
|
|
||||||
@GetMapping("/news/latest")
|
|
||||||
public ResultDomain<TbResource> getLatestNews(@RequestParam(required = false) Integer limit) {
|
|
||||||
// TODO: 实现获取最新思政新闻
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 顶部菜单栏 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取顶部菜单栏配置
|
|
||||||
*/
|
|
||||||
@GetMapping("/menu/top-menu")
|
|
||||||
public ResultDomain<Map<String, Object>> getTopMenuConfig() {
|
|
||||||
// TODO: 实现获取顶部菜单栏(包含首页、资源中心、学习计划、专题活动、红色常信)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 后台修改菜单名称
|
|
||||||
*/
|
|
||||||
@PutMapping("/menu/update-name")
|
|
||||||
public ResultDomain<Boolean> updateMenuName(@RequestBody Map<String, Object> menuData) {
|
|
||||||
// TODO: 实现支持后台修改菜单名称
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取菜单项列表
|
|
||||||
*/
|
|
||||||
@GetMapping("/menu/list")
|
|
||||||
public ResultDomain<Map<String, Object>> getMenuList() {
|
|
||||||
// TODO: 实现获取菜单项列表
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 模糊检索 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模糊检索资源
|
|
||||||
*/
|
|
||||||
@GetMapping("/search")
|
|
||||||
public ResultDomain<TbResource> searchResources(@RequestParam String keyword) {
|
|
||||||
// TODO: 实现模糊检索(输入关键词实时匹配资源(新闻、课程))
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实时搜索建议
|
|
||||||
*/
|
|
||||||
@GetMapping("/search/suggestions")
|
|
||||||
public ResultDomain<Map<String, Object>> getSearchSuggestions(@RequestParam String keyword) {
|
|
||||||
// TODO: 实现实时搜索建议
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取热门搜索词
|
|
||||||
*/
|
|
||||||
@GetMapping("/search/hot-keywords")
|
|
||||||
public ResultDomain<Map<String, Object>> getHotKeywords() {
|
|
||||||
// TODO: 实现获取热门搜索词
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 搜索新闻
|
|
||||||
*/
|
|
||||||
@GetMapping("/search/news")
|
|
||||||
public ResultDomain<TbResource> searchNews(@RequestParam String keyword) {
|
|
||||||
// TODO: 实现搜索新闻
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 搜索课程
|
|
||||||
*/
|
|
||||||
@GetMapping("/search/courses")
|
|
||||||
public ResultDomain<Map<String, Object>> searchCourses(@RequestParam String keyword) {
|
|
||||||
// TODO: 实现搜索课程
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 首页统计数据 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取首页统计数据
|
|
||||||
*/
|
|
||||||
@GetMapping("/statistics")
|
|
||||||
public ResultDomain<Map<String, Object>> getHomePageStatistics() {
|
|
||||||
// TODO: 实现获取首页统计数据
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取今日访问量
|
|
||||||
*/
|
|
||||||
@GetMapping("/statistics/today-visits")
|
|
||||||
public ResultDomain<Map<String, Object>> getTodayVisits() {
|
|
||||||
// TODO: 实现获取今日访问量
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取资源总数
|
|
||||||
*/
|
|
||||||
@GetMapping("/statistics/total-resources")
|
|
||||||
public ResultDomain<Map<String, Object>> getTotalResources() {
|
|
||||||
// TODO: 实现获取资源总数
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,16 @@ public class ResourceController {
|
|||||||
return resourceService.getResourcePage(filter, pageParam);
|
return resourceService.getResourcePage(filter, pageParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取资源分页(按浏览次数排序)
|
||||||
|
*/
|
||||||
|
@PostMapping("/page/view-count")
|
||||||
|
public ResultDomain<TbResource> getResourcePageOrderByViewCount(@RequestBody PageRequest<TbResource> request) {
|
||||||
|
TbResource filter = request.getFilter();
|
||||||
|
PageParam pageParam = request.getPageParam();
|
||||||
|
return resourceService.getResourcePageOrderByViewCount(filter, pageParam);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据ID获取资源详情
|
* 根据ID获取资源详情
|
||||||
*/
|
*/
|
||||||
@@ -136,14 +146,6 @@ public class ResourceController {
|
|||||||
return resourceService.incrementLikeCount(resourceID);
|
return resourceService.incrementLikeCount(resourceID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 增加收藏次数
|
|
||||||
*/
|
|
||||||
@PostMapping("/resource/collect")
|
|
||||||
public ResultDomain<TbResource> resourceCollect(@RequestBody TbUserCollection collection) {
|
|
||||||
return resourceService.resourceCollect(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置资源推荐
|
* 设置资源推荐
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,7 +6,15 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.xyzh.api.news.recommend.ResourceRecommendService;
|
import org.xyzh.api.news.recommend.ResourceRecommendService;
|
||||||
import org.xyzh.common.core.domain.ResultDomain;
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
|
import org.xyzh.common.core.page.PageParam;
|
||||||
|
import org.xyzh.common.core.page.PageRequest;
|
||||||
import org.xyzh.common.dto.resource.TbResourceRecommend;
|
import org.xyzh.common.dto.resource.TbResourceRecommend;
|
||||||
|
import org.xyzh.common.dto.user.TbSysUser;
|
||||||
|
import org.xyzh.common.vo.ResourceRecommendVO;
|
||||||
|
import org.xyzh.system.utils.LoginUtil;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 资源推荐控制器
|
* @description 资源推荐控制器
|
||||||
@@ -24,19 +32,36 @@ public class ResourceRecommendController {
|
|||||||
private ResourceRecommendService resourceRecommendService;
|
private ResourceRecommendService resourceRecommendService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取推荐列表
|
* 获取推荐列表(返回VO,包含权限信息)
|
||||||
*/
|
*/
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public ResultDomain<TbResourceRecommend> getRecommendList() {
|
public ResultDomain<ResourceRecommendVO> getRecommendList() {
|
||||||
return resourceRecommendService.getRecommendList();
|
ResultDomain<ResourceRecommendVO> result = resourceRecommendService.getRecommendList();
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据ID获取推荐详情
|
* 根据ID获取推荐详情(返回VO,包含权限信息)
|
||||||
*/
|
*/
|
||||||
@GetMapping("/recommend/{recommendID}")
|
@GetMapping("/recommend/{recommendID}")
|
||||||
public ResultDomain<TbResourceRecommend> getRecommendById(@PathVariable String recommendID) {
|
public ResultDomain<ResourceRecommendVO> getRecommendById(@PathVariable("recommendID") String recommendID) {
|
||||||
return resourceRecommendService.getRecommendDetail(recommendID);
|
TbResourceRecommend filter = new TbResourceRecommend();
|
||||||
|
filter.setID(recommendID);
|
||||||
|
PageParam pageParam = new PageParam();
|
||||||
|
pageParam.setPageNumber(1);
|
||||||
|
pageParam.setPageSize(1);
|
||||||
|
ResultDomain<ResourceRecommendVO> result = resourceRecommendService.getRecommendPage(filter, pageParam);
|
||||||
|
// 从PageDomain中提取dataList的第一个元素
|
||||||
|
if (result.getCode() == 200 && result.getPageDomain() != null
|
||||||
|
&& result.getPageDomain().getDataList() != null && !result.getPageDomain().getDataList().isEmpty()) {
|
||||||
|
ResultDomain<ResourceRecommendVO> singleResult = new ResultDomain<>();
|
||||||
|
singleResult.setCode(200);
|
||||||
|
singleResult.setMessage(result.getMessage());
|
||||||
|
singleResult.setData(result.getPageDomain().getDataList().get(0));
|
||||||
|
return singleResult;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,6 +69,12 @@ public class ResourceRecommendController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/recommend")
|
@PostMapping("/recommend")
|
||||||
public ResultDomain<TbResourceRecommend> createRecommend(@RequestBody TbResourceRecommend recommend) {
|
public ResultDomain<TbResourceRecommend> createRecommend(@RequestBody TbResourceRecommend recommend) {
|
||||||
|
TbSysUser user = LoginUtil.getCurrentUser();
|
||||||
|
if (user != null) {
|
||||||
|
if (recommend.getCreator() == null) {
|
||||||
|
recommend.setCreator(user.getID());
|
||||||
|
}
|
||||||
|
}
|
||||||
return resourceRecommendService.addRecommend(recommend);
|
return resourceRecommendService.addRecommend(recommend);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,15 +90,26 @@ public class ResourceRecommendController {
|
|||||||
* 删除推荐
|
* 删除推荐
|
||||||
*/
|
*/
|
||||||
@DeleteMapping("/recommend/{recommendID}")
|
@DeleteMapping("/recommend/{recommendID}")
|
||||||
public ResultDomain<Boolean> deleteRecommend(@PathVariable String recommendID) {
|
public ResultDomain<Boolean> deleteRecommend(@PathVariable("recommendID") String recommendID) {
|
||||||
return resourceRecommendService.deleteRecommend(recommendID);
|
return resourceRecommendService.deleteRecommend(recommendID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加推荐资源
|
||||||
|
*/
|
||||||
|
@PostMapping("/recommend/batch")
|
||||||
|
public ResultDomain<TbResourceRecommend> batchAddRecommends(@RequestBody Map<String, Object> params) {
|
||||||
|
List<String> resourceIDs = (List<String>) params.get("resourceIDs");
|
||||||
|
Integer recommendType = (Integer) params.get("recommendType");
|
||||||
|
String reason = (String) params.get("reason");
|
||||||
|
return resourceRecommendService.batchAddRecommends(resourceIDs, recommendType, reason);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新推荐状态
|
* 更新推荐状态
|
||||||
*/
|
*/
|
||||||
@PutMapping("/recommend/{recommendID}/status")
|
@PutMapping("/recommend/{recommendID}/status")
|
||||||
public ResultDomain<TbResourceRecommend> updateRecommendStatus(@PathVariable String recommendID, @RequestParam Integer status) {
|
public ResultDomain<TbResourceRecommend> updateRecommendStatus(@PathVariable("recommendID") String recommendID, @RequestParam("status") Integer status) {
|
||||||
return null;
|
return null;
|
||||||
// return resourceRecommendService.updateRecommendStatus(recommendID, status);
|
// return resourceRecommendService.updateRecommendStatus(recommendID, status);
|
||||||
}
|
}
|
||||||
@@ -76,7 +118,7 @@ public class ResourceRecommendController {
|
|||||||
* 更新推荐排序
|
* 更新推荐排序
|
||||||
*/
|
*/
|
||||||
@PutMapping("/recommend/{recommendID}/order")
|
@PutMapping("/recommend/{recommendID}/order")
|
||||||
public ResultDomain<TbResourceRecommend> updateRecommendOrder(@PathVariable String recommendID, @RequestParam Integer orderNum) {
|
public ResultDomain<TbResourceRecommend> updateRecommendOrder(@PathVariable("recommendID") String recommendID, @RequestParam("orderNum") Integer orderNum) {
|
||||||
return resourceRecommendService.updateRecommendOrder(recommendID, orderNum);
|
return resourceRecommendService.updateRecommendOrder(recommendID, orderNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,8 +126,46 @@ public class ResourceRecommendController {
|
|||||||
* 获取活跃推荐
|
* 获取活跃推荐
|
||||||
*/
|
*/
|
||||||
@GetMapping("/active")
|
@GetMapping("/active")
|
||||||
public ResultDomain<TbResourceRecommend> getActiveRecommends(@RequestParam(required = false) Integer limit) {
|
public ResultDomain<TbResourceRecommend> getActiveRecommends(@RequestParam(required = false, name = "limit") Integer limit) {
|
||||||
return null;
|
return null;
|
||||||
// return resourceRecommendService.getActiveRecommends(limit);
|
// return resourceRecommendService.getActiveRecommends(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询推荐资源列表
|
||||||
|
*/
|
||||||
|
@PostMapping("/page")
|
||||||
|
public ResultDomain<ResourceRecommendVO> getRecommendPage(@RequestBody PageRequest<TbResourceRecommend> request) {
|
||||||
|
TbResourceRecommend filter = request.getFilter();
|
||||||
|
PageParam pageParam = request.getPageParam();
|
||||||
|
return resourceRecommendService.getRecommendPage(filter, pageParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计推荐资源总数
|
||||||
|
*/
|
||||||
|
@PostMapping("/count")
|
||||||
|
public ResultDomain<Long> countRecommends(@RequestBody TbResourceRecommend filter) {
|
||||||
|
return resourceRecommendService.countRecommends(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据推荐类型获取推荐资源列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/type/{recommendType}")
|
||||||
|
public ResultDomain<ResourceRecommendVO> getRecommendsByType(
|
||||||
|
@PathVariable("recommendType") Integer recommendType,
|
||||||
|
@RequestParam(required = false, name = "limit") Integer limit) {
|
||||||
|
return resourceRecommendService.getRecommendsByType(recommendType, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查资源是否已推荐(按类型)
|
||||||
|
*/
|
||||||
|
@GetMapping("/check/{resourceID}")
|
||||||
|
public ResultDomain<Boolean> isResourceRecommendedByType(
|
||||||
|
@PathVariable("resourceID") String resourceID,
|
||||||
|
@RequestParam(required = true, name = "recommendType") Integer recommendType) {
|
||||||
|
return resourceRecommendService.isResourceRecommendedByType(resourceID, recommendType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ public class TagController {
|
|||||||
* 获取标签列表
|
* 获取标签列表
|
||||||
*/
|
*/
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public ResultDomain<TbTag> getTagList() {
|
public ResultDomain<TbTag> getTagList(TbTag filter) {
|
||||||
return tagService.getAllTags();
|
return tagService.getTagList(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -165,6 +165,18 @@ public interface ResourceMapper extends BaseMapper<TbResource> {
|
|||||||
*/
|
*/
|
||||||
List<TbResource> selectResourcesPage(@Param("filter") TbResource filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
List<TbResource> selectResourcesPage(@Param("filter") TbResource filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 分页查询资源
|
||||||
|
* @param filter 过滤条件
|
||||||
|
* @param pageParam 分页参数
|
||||||
|
* @param userDeptRoles 用户部门角色列表
|
||||||
|
* @return List<TbResource> 资源列表
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-10-15
|
||||||
|
*/
|
||||||
|
List<TbResource> selectResourcesPageOrderByViewCount(@Param("filter") TbResource filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 统计资源总数
|
* @description 统计资源总数
|
||||||
* @param filter 过滤条件
|
* @param filter 过滤条件
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import org.apache.ibatis.annotations.Mapper;
|
|||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.xyzh.common.core.page.PageParam;
|
import org.xyzh.common.core.page.PageParam;
|
||||||
import org.xyzh.common.dto.resource.TbResourceRecommend;
|
import org.xyzh.common.dto.resource.TbResourceRecommend;
|
||||||
|
import org.xyzh.common.vo.ResourceRecommendVO;
|
||||||
|
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -21,11 +23,12 @@ public interface ResourceRecommendMapper extends BaseMapper<TbResourceRecommend>
|
|||||||
/**
|
/**
|
||||||
* @description 查询资源推荐列表
|
* @description 查询资源推荐列表
|
||||||
* @param filter 过滤条件
|
* @param filter 过滤条件
|
||||||
* @return List<TbResourceRecommend> 资源推荐列表
|
* @param userDeptRoles 用户部门角色列表(用于权限过滤)
|
||||||
|
* @return List<ResourceRecommendVO> 资源推荐列表(包含资源信息和权限)
|
||||||
* @author yslg
|
* @author yslg
|
||||||
* @since 2025-10-15
|
* @since 2025-10-15
|
||||||
*/
|
*/
|
||||||
List<TbResourceRecommend> selectResourceRecommends(TbResourceRecommend filter);
|
List<ResourceRecommendVO> selectResourceRecommends(@Param("filter") TbResourceRecommend filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 根据推荐ID查询推荐信息
|
* @description 根据推荐ID查询推荐信息
|
||||||
@@ -55,13 +58,25 @@ public interface ResourceRecommendMapper extends BaseMapper<TbResourceRecommend>
|
|||||||
List<TbResourceRecommend> selectByStatus(@Param("status") Integer status);
|
List<TbResourceRecommend> selectByStatus(@Param("status") Integer status);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 根据类型查询推荐列表
|
* @description 根据推荐类型查询推荐列表
|
||||||
* @param type 类型
|
* @param recommendType 推荐类型(1-热门资源,2-思政资源)
|
||||||
* @return List<TbResourceRecommend> 推荐列表
|
* @param limit 限制数量
|
||||||
|
* @param userDeptRoles 用户部门角色列表(用于权限过滤)
|
||||||
|
* @return List<ResourceRecommendVO> 推荐列表(包含资源信息和权限)
|
||||||
* @author yslg
|
* @author yslg
|
||||||
* @since 2025-10-15
|
* @since 2025-10-15
|
||||||
*/
|
*/
|
||||||
List<TbResourceRecommend> selectByType(@Param("type") Integer type);
|
List<ResourceRecommendVO> selectByRecommendType(@Param("recommendType") Integer recommendType, @Param("limit") Integer limit, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 根据资源ID和推荐类型查询
|
||||||
|
* @param resourceId 资源ID
|
||||||
|
* @param recommendType 推荐类型
|
||||||
|
* @return List<TbResourceRecommend> 推荐列表
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-01-XX
|
||||||
|
*/
|
||||||
|
List<TbResourceRecommend> selectByResourceIdAndType(@Param("resourceId") String resourceId, @Param("recommendType") Integer recommendType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 查询热门推荐列表
|
* @description 查询热门推荐列表
|
||||||
@@ -130,18 +145,20 @@ public interface ResourceRecommendMapper extends BaseMapper<TbResourceRecommend>
|
|||||||
* @description 分页查询资源推荐
|
* @description 分页查询资源推荐
|
||||||
* @param filter 过滤条件
|
* @param filter 过滤条件
|
||||||
* @param pageParam 分页参数
|
* @param pageParam 分页参数
|
||||||
* @return List<TbResourceRecommend> 资源推荐列表
|
* @param userDeptRoles 用户部门角色列表(用于权限过滤)
|
||||||
|
* @return List<ResourceRecommendVO> 资源推荐列表(包含资源信息和权限)
|
||||||
* @author yslg
|
* @author yslg
|
||||||
* @since 2025-10-15
|
* @since 2025-10-15
|
||||||
*/
|
*/
|
||||||
List<TbResourceRecommend> selectResourceRecommendsPage(@Param("filter") TbResourceRecommend filter, @Param("pageParam") PageParam pageParam);
|
List<ResourceRecommendVO> selectResourceRecommendsPage(@Param("filter") TbResourceRecommend filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 统计资源推荐总数
|
* @description 统计资源推荐总数
|
||||||
* @param filter 过滤条件
|
* @param filter 过滤条件
|
||||||
|
* @param userDeptRoles 用户部门角色列表(用于权限过滤)
|
||||||
* @return long 总数
|
* @return long 总数
|
||||||
* @author yslg
|
* @author yslg
|
||||||
* @since 2025-10-15
|
* @since 2025-10-15
|
||||||
*/
|
*/
|
||||||
long countResourceRecommends(@Param("filter") TbResourceRecommend filter);
|
long countResourceRecommends(@Param("filter") TbResourceRecommend filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,15 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.xyzh.common.core.domain.ResultDomain;
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
|
import org.xyzh.common.core.page.PageDomain;
|
||||||
|
import org.xyzh.common.core.page.PageParam;
|
||||||
import org.xyzh.common.dto.resource.TbResourceRecommend;
|
import org.xyzh.common.dto.resource.TbResourceRecommend;
|
||||||
import org.xyzh.common.utils.IDUtils;
|
import org.xyzh.common.utils.IDUtils;
|
||||||
|
import org.xyzh.common.vo.ResourceRecommendVO;
|
||||||
|
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||||
import org.xyzh.news.mapper.ResourceRecommendMapper;
|
import org.xyzh.news.mapper.ResourceRecommendMapper;
|
||||||
import org.xyzh.api.news.recommend.ResourceRecommendService;
|
import org.xyzh.api.news.recommend.ResourceRecommendService;
|
||||||
|
import org.xyzh.system.utils.LoginUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 资源推荐服务实现类
|
* @description 资源推荐服务实现类
|
||||||
@@ -32,11 +37,13 @@ public class NCResourceRecommendServiceImpl implements ResourceRecommendService
|
|||||||
private ResourceRecommendMapper resourceRecommendMapper;
|
private ResourceRecommendMapper resourceRecommendMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResultDomain<TbResourceRecommend> getRecommendList() {
|
public ResultDomain<ResourceRecommendVO> getRecommendList() {
|
||||||
ResultDomain<TbResourceRecommend> resultDomain = new ResultDomain<>();
|
ResultDomain<ResourceRecommendVO> resultDomain = new ResultDomain<>();
|
||||||
try {
|
try {
|
||||||
List<TbResourceRecommend> list = resourceRecommendMapper.selectResourceRecommends(new TbResourceRecommend());
|
// 获取当前用户的部门角色(用于权限过滤)
|
||||||
resultDomain.success("获取推荐列表成功", list);
|
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||||
|
List<ResourceRecommendVO> voList = resourceRecommendMapper.selectResourceRecommends(new TbResourceRecommend(), userDeptRoles);
|
||||||
|
resultDomain.success("获取推荐列表成功", voList);
|
||||||
return resultDomain;
|
return resultDomain;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("获取推荐列表异常: {}", e.getMessage(), e);
|
logger.error("获取推荐列表异常: {}", e.getMessage(), e);
|
||||||
@@ -56,12 +63,22 @@ public class NCResourceRecommendServiceImpl implements ResourceRecommendService
|
|||||||
return resultDomain;
|
return resultDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查资源是否已被推荐
|
// 检查资源是否已在该类型下被推荐
|
||||||
|
if (recommend.getRecommendType() != null) {
|
||||||
|
List<TbResourceRecommend> existingList = resourceRecommendMapper.selectByResourceIdAndType(
|
||||||
|
recommend.getResourceID(), recommend.getRecommendType());
|
||||||
|
if (existingList != null && !existingList.isEmpty()) {
|
||||||
|
resultDomain.fail("该资源已在此类型下被推荐");
|
||||||
|
return resultDomain;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有指定类型,检查是否在任何类型下已被推荐
|
||||||
List<TbResourceRecommend> existingList = resourceRecommendMapper.selectByResourceId(recommend.getResourceID());
|
List<TbResourceRecommend> existingList = resourceRecommendMapper.selectByResourceId(recommend.getResourceID());
|
||||||
if (existingList != null && !existingList.isEmpty()) {
|
if (existingList != null && !existingList.isEmpty()) {
|
||||||
resultDomain.fail("该资源已被推荐");
|
resultDomain.fail("该资源已被推荐");
|
||||||
return resultDomain;
|
return resultDomain;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 设置默认值
|
// 设置默认值
|
||||||
if (recommend.getID() == null) {
|
if (recommend.getID() == null) {
|
||||||
@@ -178,7 +195,7 @@ public class NCResourceRecommendServiceImpl implements ResourceRecommendService
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public ResultDomain<TbResourceRecommend> batchAddRecommends(List<String> resourceIDs, String reason) {
|
public ResultDomain<TbResourceRecommend> batchAddRecommends(List<String> resourceIDs, Integer recommendType, String reason) {
|
||||||
ResultDomain<TbResourceRecommend> resultDomain = new ResultDomain<>();
|
ResultDomain<TbResourceRecommend> resultDomain = new ResultDomain<>();
|
||||||
try {
|
try {
|
||||||
// 参数验证
|
// 参数验证
|
||||||
@@ -195,15 +212,24 @@ public class NCResourceRecommendServiceImpl implements ResourceRecommendService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否已推荐
|
// 检查是否已在该类型下推荐
|
||||||
|
if (recommendType != null) {
|
||||||
|
List<TbResourceRecommend> existingList = resourceRecommendMapper.selectByResourceIdAndType(resourceID, recommendType);
|
||||||
|
if (existingList != null && !existingList.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有指定类型,检查是否在任何类型下已被推荐
|
||||||
List<TbResourceRecommend> existingList = resourceRecommendMapper.selectByResourceId(resourceID);
|
List<TbResourceRecommend> existingList = resourceRecommendMapper.selectByResourceId(resourceID);
|
||||||
if (existingList != null && !existingList.isEmpty()) {
|
if (existingList != null && !existingList.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TbResourceRecommend recommend = new TbResourceRecommend();
|
TbResourceRecommend recommend = new TbResourceRecommend();
|
||||||
recommend.setID(IDUtils.generateID());
|
recommend.setID(IDUtils.generateID());
|
||||||
recommend.setResourceID(resourceID);
|
recommend.setResourceID(resourceID);
|
||||||
|
recommend.setRecommendType(recommendType);
|
||||||
recommend.setReason(reason);
|
recommend.setReason(reason);
|
||||||
recommend.setOrderNum(0);
|
recommend.setOrderNum(0);
|
||||||
recommend.setCreateTime(now);
|
recommend.setCreateTime(now);
|
||||||
@@ -416,4 +442,104 @@ public class NCResourceRecommendServiceImpl implements ResourceRecommendService
|
|||||||
return resultDomain;
|
return resultDomain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<ResourceRecommendVO> getRecommendPage(TbResourceRecommend filter, PageParam pageParam) {
|
||||||
|
ResultDomain<ResourceRecommendVO> resultDomain = new ResultDomain<>();
|
||||||
|
try {
|
||||||
|
if (filter == null) {
|
||||||
|
filter = new TbResourceRecommend();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前用户的部门角色(用于权限过滤)
|
||||||
|
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||||
|
|
||||||
|
// 计算偏移量
|
||||||
|
if (pageParam.getOffset() == 0 && pageParam.getPageNumber() > 0) {
|
||||||
|
pageParam.setOffset((long) (pageParam.getPageNumber() - 1) * pageParam.getPageSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ResourceRecommendVO> voList = resourceRecommendMapper.selectResourceRecommendsPage(filter, pageParam, userDeptRoles);
|
||||||
|
long total = resourceRecommendMapper.countResourceRecommends(filter, userDeptRoles);
|
||||||
|
|
||||||
|
pageParam.setTotalElements(total);
|
||||||
|
pageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize()));
|
||||||
|
|
||||||
|
resultDomain.success("获取推荐资源分页成功", new PageDomain<ResourceRecommendVO>(pageParam, voList));
|
||||||
|
return resultDomain;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取推荐资源分页异常: {}", e.getMessage(), e);
|
||||||
|
resultDomain.fail("获取推荐资源分页失败: " + e.getMessage());
|
||||||
|
return resultDomain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<Long> countRecommends(TbResourceRecommend filter) {
|
||||||
|
ResultDomain<Long> resultDomain = new ResultDomain<>();
|
||||||
|
try {
|
||||||
|
if (filter == null) {
|
||||||
|
filter = new TbResourceRecommend();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前用户的部门角色(用于权限过滤)
|
||||||
|
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||||
|
|
||||||
|
long count = resourceRecommendMapper.countResourceRecommends(filter, userDeptRoles);
|
||||||
|
resultDomain.success("统计成功", count);
|
||||||
|
return resultDomain;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("统计推荐资源异常: {}", e.getMessage(), e);
|
||||||
|
resultDomain.fail("统计失败: " + e.getMessage());
|
||||||
|
return resultDomain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<ResourceRecommendVO> getRecommendsByType(Integer recommendType, Integer limit) {
|
||||||
|
ResultDomain<ResourceRecommendVO> resultDomain = new ResultDomain<>();
|
||||||
|
try {
|
||||||
|
if (recommendType == null) {
|
||||||
|
resultDomain.fail("推荐类型不能为空");
|
||||||
|
return resultDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前用户的部门角色(用于权限过滤)
|
||||||
|
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||||
|
|
||||||
|
List<ResourceRecommendVO> voList = resourceRecommendMapper.selectByRecommendType(recommendType, limit, userDeptRoles);
|
||||||
|
|
||||||
|
resultDomain.success("获取推荐资源列表成功", voList);
|
||||||
|
return resultDomain;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("根据类型获取推荐资源异常: {}", e.getMessage(), e);
|
||||||
|
resultDomain.fail("获取推荐资源列表失败: " + e.getMessage());
|
||||||
|
return resultDomain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<Boolean> isResourceRecommendedByType(String resourceID, Integer recommendType) {
|
||||||
|
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
|
||||||
|
try {
|
||||||
|
if (!StringUtils.hasText(resourceID)) {
|
||||||
|
resultDomain.fail("资源ID不能为空");
|
||||||
|
return resultDomain;
|
||||||
|
}
|
||||||
|
if (recommendType == null) {
|
||||||
|
resultDomain.fail("推荐类型不能为空");
|
||||||
|
return resultDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TbResourceRecommend> recommendList = resourceRecommendMapper.selectByResourceIdAndType(resourceID, recommendType);
|
||||||
|
boolean isRecommended = recommendList != null && !recommendList.isEmpty();
|
||||||
|
|
||||||
|
resultDomain.success("检查成功", isRecommended);
|
||||||
|
return resultDomain;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("检查资源是否推荐异常: {}", e.getMessage(), e);
|
||||||
|
resultDomain.fail("检查失败: " + e.getMessage());
|
||||||
|
return resultDomain;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import org.xyzh.news.mapper.ResourceMapper;
|
|||||||
import org.xyzh.news.mapper.ResourceTagMapper;
|
import org.xyzh.news.mapper.ResourceTagMapper;
|
||||||
import org.xyzh.system.utils.LoginUtil;
|
import org.xyzh.system.utils.LoginUtil;
|
||||||
import org.xyzh.api.news.resource.ResourceService;
|
import org.xyzh.api.news.resource.ResourceService;
|
||||||
import org.xyzh.api.usercenter.collection.UserCollectionService;
|
|
||||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||||
import org.xyzh.common.core.enums.ResourceType;
|
import org.xyzh.common.core.enums.ResourceType;
|
||||||
@@ -49,9 +48,6 @@ public class NCResourceServiceImpl implements ResourceService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ResourceTagMapper resourceTagMapper;
|
private ResourceTagMapper resourceTagMapper;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserCollectionService userCollectionService;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ResourcePermissionService resourcePermissionService;
|
private ResourcePermissionService resourcePermissionService;
|
||||||
|
|
||||||
@@ -98,6 +94,29 @@ public class NCResourceServiceImpl implements ResourceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbResource> getResourcePageOrderByViewCount(TbResource filter, PageParam pageParam) {
|
||||||
|
ResultDomain<TbResource> resultDomain = new ResultDomain<>();
|
||||||
|
try {
|
||||||
|
if (filter == null) {
|
||||||
|
filter = new TbResource();
|
||||||
|
}
|
||||||
|
// 获取当前用户的部门角色
|
||||||
|
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||||
|
List<TbResource> list = 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<TbResource>(pageParam, list));
|
||||||
|
return resultDomain;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
logger.error("获取资源分页(按浏览次数排序)异常: {}", e.getMessage(), e);
|
||||||
|
resultDomain.fail("获取资源分页(按浏览次数排序)失败: " + e.getMessage());
|
||||||
|
return resultDomain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResultDomain<ResourceVO> getResourceById(String resourceID) {
|
public ResultDomain<ResourceVO> getResourceById(String resourceID) {
|
||||||
ResultDomain<ResourceVO> resultDomain = new ResultDomain<>();
|
ResultDomain<ResourceVO> resultDomain = new ResultDomain<>();
|
||||||
@@ -560,7 +579,6 @@ public class NCResourceServiceImpl implements ResourceService {
|
|||||||
return resultDomain;
|
return resultDomain;
|
||||||
}
|
}
|
||||||
collection.setUserID(user.getID());
|
collection.setUserID(user.getID());
|
||||||
try {
|
|
||||||
// 参数验证
|
// 参数验证
|
||||||
if (!StringUtils.hasText(resourceID)) {
|
if (!StringUtils.hasText(resourceID)) {
|
||||||
resultDomain.fail("资源ID不能为空");
|
resultDomain.fail("资源ID不能为空");
|
||||||
@@ -570,45 +588,12 @@ public class NCResourceServiceImpl implements ResourceService {
|
|||||||
resultDomain.fail("收藏值错误");
|
resultDomain.fail("收藏值错误");
|
||||||
return resultDomain;
|
return resultDomain;
|
||||||
}
|
}
|
||||||
ResultDomain<Boolean> isCollected = userCollectionService.isCollected(user.getID(), collection.getCollectionType(), resourceID);
|
|
||||||
TbResource resource = new TbResource();
|
TbResource resource = new TbResource();
|
||||||
resource.setResourceID(resourceID);
|
resource.setResourceID(resourceID);
|
||||||
if (isCollected.isSuccess() && isCollected.getData() && collectionValue == 1) {
|
resourceMapper.updateResourceCollectCount(resourceID, collectionValue);
|
||||||
resultDomain.success("已收藏", resource);
|
resultDomain.success("更新资源收藏次数成功", resource);
|
||||||
return resultDomain;
|
return resultDomain;
|
||||||
}
|
}
|
||||||
else if (isCollected.isSuccess() && isCollected.getData() && collectionValue == -1) {
|
|
||||||
ResultDomain<Boolean> removeCollection = userCollectionService.removeCollection(collection);
|
|
||||||
if (removeCollection.isSuccess()) {
|
|
||||||
resourceMapper.updateResourceCollectCount(resourceID, -1);
|
|
||||||
resultDomain.success("已取消收藏", resource);
|
|
||||||
return resultDomain;
|
|
||||||
} else {
|
|
||||||
resultDomain.fail("取消收藏失败");
|
|
||||||
return resultDomain;
|
|
||||||
}
|
|
||||||
}else if (isCollected.isSuccess() && !isCollected.getData() && collectionValue == 1) {
|
|
||||||
collection.setID(IDUtils.generateID());
|
|
||||||
collection.setCreateTime(new Date());
|
|
||||||
ResultDomain<TbUserCollection> addCollection = userCollectionService.addCollection(collection);
|
|
||||||
if (addCollection.isSuccess() && addCollection.getData() != null) {
|
|
||||||
resourceMapper.updateResourceCollectCount(resourceID, 1);
|
|
||||||
resultDomain.success("已收藏", resource);
|
|
||||||
return resultDomain;
|
|
||||||
} else {
|
|
||||||
resultDomain.fail("收藏失败");
|
|
||||||
return resultDomain;
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
resultDomain.success("未收藏", resource);
|
|
||||||
return resultDomain;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("增加资源收藏次数异常: {}", e.getMessage(), e);
|
|
||||||
resultDomain.fail("增加收藏次数失败: " + e.getMessage());
|
|
||||||
return resultDomain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
|||||||
@@ -215,12 +215,12 @@ public class NCTagServiceImpl implements TagService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResultDomain<TbTag> getAllTags() {
|
public ResultDomain<TbTag> getTagList(TbTag filter) {
|
||||||
ResultDomain<TbTag> resultDomain = new ResultDomain<>();
|
ResultDomain<TbTag> resultDomain = new ResultDomain<>();
|
||||||
try {
|
try {
|
||||||
// 获取当前用户的部门角色
|
// 获取当前用户的部门角色
|
||||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||||
List<TbTag> tags = tagMapper.selectTags(new TbTag(), userDeptRoles);
|
List<TbTag> tags = tagMapper.selectTags(filter, userDeptRoles);
|
||||||
resultDomain.success("查询成功", tags);
|
resultDomain.success("查询成功", tags);
|
||||||
return resultDomain;
|
return resultDomain;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -336,6 +336,33 @@
|
|||||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectResourcesPageOrderByViewCount" resultMap="BaseResultMap">
|
||||||
|
SELECT DISTINCT r.*
|
||||||
|
FROM tb_resource r
|
||||||
|
<include refid="Permission_Filter"/>
|
||||||
|
WHERE r.deleted = 0
|
||||||
|
<if test="filter.title != null and filter.title != ''">
|
||||||
|
AND r.title LIKE CONCAT('%', #{filter.title}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="filter.tagID != null and filter.tagID != ''">
|
||||||
|
AND r.tag_id = #{filter.tagID}
|
||||||
|
</if>
|
||||||
|
<if test="filter.author != null and filter.author != ''">
|
||||||
|
AND r.author LIKE CONCAT('%', #{filter.author}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="filter.status != null">
|
||||||
|
AND r.status = #{filter.status}
|
||||||
|
</if>
|
||||||
|
<if test="filter.isRecommend != null">
|
||||||
|
AND r.is_recommend = #{filter.isRecommend}
|
||||||
|
</if>
|
||||||
|
<if test="filter.isBanner != null">
|
||||||
|
AND r.is_banner = #{filter.isBanner}
|
||||||
|
</if>
|
||||||
|
ORDER BY r.view_count DESC, r.publish_time DESC, r.create_time DESC
|
||||||
|
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||||
|
</select>
|
||||||
|
|
||||||
<!-- 统计资源总数 - 添加权限过滤 -->
|
<!-- 统计资源总数 - 添加权限过滤 -->
|
||||||
<select id="countResources" resultType="long">
|
<select id="countResources" resultType="long">
|
||||||
SELECT COUNT(DISTINCT r.id)
|
SELECT COUNT(DISTINCT r.id)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.resource.TbResourceRecommend">
|
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.resource.TbResourceRecommend">
|
||||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||||
<result column="resource_id" property="resourceID" jdbcType="VARCHAR"/>
|
<result column="resource_id" property="resourceID" jdbcType="VARCHAR"/>
|
||||||
|
<result column="recommend_type" property="recommendType" jdbcType="INTEGER"/>
|
||||||
<result column="order_num" property="orderNum" jdbcType="INTEGER"/>
|
<result column="order_num" property="orderNum" jdbcType="INTEGER"/>
|
||||||
<result column="reason" property="reason" jdbcType="VARCHAR"/>
|
<result column="reason" property="reason" jdbcType="VARCHAR"/>
|
||||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||||
@@ -16,32 +17,119 @@
|
|||||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- ResourceRecommendVO结果映射(包含资源信息和权限信息) -->
|
||||||
|
<resultMap id="ResourceRecommendVOResultMap" type="org.xyzh.common.vo.ResourceRecommendVO">
|
||||||
|
<!-- 推荐表字段 -->
|
||||||
|
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||||
|
<result column="resource_id" property="resourceID" jdbcType="VARCHAR"/>
|
||||||
|
<result column="recommend_type" property="recommendType" jdbcType="INTEGER"/>
|
||||||
|
<result column="order_num" property="orderNum" jdbcType="INTEGER"/>
|
||||||
|
<result column="reason" property="reason" jdbcType="VARCHAR"/>
|
||||||
|
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||||
|
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||||
|
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||||
|
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||||
|
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||||
|
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||||
|
<!-- 资源表字段 -->
|
||||||
|
<result column="title" property="title" jdbcType="VARCHAR"/>
|
||||||
|
<result column="summary" property="summary" jdbcType="VARCHAR"/>
|
||||||
|
<result column="cover_image" property="coverImage" jdbcType="VARCHAR"/>
|
||||||
|
<result column="tag_id" property="tagID" jdbcType="VARCHAR"/>
|
||||||
|
<result column="author" property="author" jdbcType="VARCHAR"/>
|
||||||
|
<result column="source" property="source" jdbcType="VARCHAR"/>
|
||||||
|
<result column="source_url" property="sourceUrl" jdbcType="VARCHAR"/>
|
||||||
|
<result column="view_count" property="viewCount" jdbcType="INTEGER"/>
|
||||||
|
<result column="like_count" property="likeCount" jdbcType="INTEGER"/>
|
||||||
|
<result column="collect_count" property="collectCount" jdbcType="INTEGER"/>
|
||||||
|
<result column="status" property="status" jdbcType="INTEGER"/>
|
||||||
|
<result column="is_recommend" property="isRecommend" jdbcType="BOOLEAN"/>
|
||||||
|
<result column="is_banner" property="isBanner" jdbcType="BOOLEAN"/>
|
||||||
|
<result column="publish_time" property="publishTime" jdbcType="TIMESTAMP"/>
|
||||||
|
<result column="resource_creator" property="resourceCreator" jdbcType="VARCHAR"/>
|
||||||
|
<result column="resource_updater" property="resourceUpdater" jdbcType="VARCHAR"/>
|
||||||
|
<result column="resource_create_time" property="resourceCreateTime" jdbcType="TIMESTAMP"/>
|
||||||
|
<result column="resource_update_time" property="resourceUpdateTime" jdbcType="TIMESTAMP"/>
|
||||||
|
<!-- 权限字段 -->
|
||||||
|
<result column="can_read" property="canRead" jdbcType="TINYINT"/>
|
||||||
|
<result column="can_write" property="canWrite" jdbcType="TINYINT"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
<!-- 基础字段 -->
|
<!-- 基础字段 -->
|
||||||
<sql id="Base_Column_List">
|
<sql id="Base_Column_List">
|
||||||
id, resource_id, order_num, reason, creator, updater, create_time,
|
id, resource_id, recommend_type, order_num, reason, creator, updater, create_time,
|
||||||
update_time, delete_time, deleted
|
update_time, delete_time, deleted
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<!-- 通用条件 -->
|
<!-- 通用条件 -->
|
||||||
<sql id="Where_Clause">
|
<sql id="Where_Clause">
|
||||||
<where>
|
<where>
|
||||||
deleted = 0
|
rr.deleted = 0
|
||||||
<if test="resourceID != null and resourceID != ''">
|
<if test="filter != null">
|
||||||
AND resource_id = #{resourceID}
|
<if test="filter.id != null and filter.id != ''">
|
||||||
|
AND rr.id = #{filter.id}
|
||||||
|
</if>
|
||||||
|
<if test="filter.resourceID != null and filter.resourceID != ''">
|
||||||
|
AND rr.resource_id = #{filter.resourceID}
|
||||||
|
</if>
|
||||||
|
<if test="filter.recommendType != null">
|
||||||
|
AND rr.recommend_type = #{filter.recommendType}
|
||||||
|
</if>
|
||||||
|
<if test="filter.reason != null and filter.reason != ''">
|
||||||
|
AND rr.reason LIKE CONCAT('%', #{filter.reason}, '%')
|
||||||
</if>
|
</if>
|
||||||
<if test="reason != null and reason != ''">
|
|
||||||
AND reason LIKE CONCAT('%', #{reason}, '%')
|
|
||||||
</if>
|
</if>
|
||||||
</where>
|
</where>
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<!-- selectResourceRecommends -->
|
<!-- 权限过滤条件(基于dept_path的高效继承,注意:需要先JOIN tb_resource表) -->
|
||||||
<select id="selectResourceRecommends" resultMap="BaseResultMap">
|
<sql id="Permission_Filter">
|
||||||
SELECT
|
INNER JOIN tb_resource_permission rp ON r.resource_id = rp.resource_id
|
||||||
<include refid="Base_Column_List"/>
|
AND rp.resource_type = 1
|
||||||
FROM tb_resource_recommend
|
AND rp.deleted = 0
|
||||||
|
AND rp.can_read = 1
|
||||||
|
AND (
|
||||||
|
-- 全局权限:所有用户可访问
|
||||||
|
(rp.dept_id IS NULL AND rp.role_id IS NULL)
|
||||||
|
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM (
|
||||||
|
<foreach collection="userDeptRoles" item="udr" separator=" UNION ALL ">
|
||||||
|
SELECT #{udr.deptID} AS dept_id, #{udr.deptPath} AS dept_path, #{udr.roleID} AS role_id
|
||||||
|
</foreach>
|
||||||
|
) user_roles
|
||||||
|
LEFT JOIN tb_sys_dept perm_dept ON perm_dept.dept_id = rp.dept_id AND perm_dept.deleted = 0
|
||||||
|
WHERE
|
||||||
|
(rp.role_id IS NULL AND rp.dept_id IS NOT NULL
|
||||||
|
AND user_roles.dept_path LIKE CONCAT(perm_dept.dept_path, '%'))
|
||||||
|
OR (rp.dept_id IS NULL AND rp.role_id = user_roles.role_id)
|
||||||
|
OR (rp.dept_id = user_roles.dept_id AND rp.role_id = user_roles.role_id)
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
)
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- selectResourceRecommends - 返回VO,包含资源信息和权限 -->
|
||||||
|
<select id="selectResourceRecommends" resultMap="ResourceRecommendVOResultMap">
|
||||||
|
SELECT DISTINCT
|
||||||
|
rr.id, rr.resource_id, rr.recommend_type, rr.order_num, rr.reason,
|
||||||
|
rr.creator, rr.updater, rr.create_time, rr.update_time, rr.delete_time, rr.deleted,
|
||||||
|
r.title, 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 AS resource_creator, r.updater AS resource_updater,
|
||||||
|
r.create_time AS resource_create_time, r.update_time AS resource_update_time,
|
||||||
|
MAX(rp.can_read) AS can_read, MAX(rp.can_write) AS can_write
|
||||||
|
FROM tb_resource_recommend rr
|
||||||
|
INNER JOIN tb_resource r ON rr.resource_id = r.resource_id AND r.deleted = 0
|
||||||
|
<include refid="Permission_Filter"/>
|
||||||
<include refid="Where_Clause"/>
|
<include refid="Where_Clause"/>
|
||||||
ORDER BY order_num ASC, create_time DESC
|
GROUP BY rr.id, rr.resource_id, rr.recommend_type, rr.order_num, rr.reason,
|
||||||
|
rr.creator, rr.updater, rr.create_time, rr.update_time, rr.delete_time, rr.deleted,
|
||||||
|
r.title, 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
|
||||||
|
ORDER BY rr.order_num ASC, rr.create_time DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 根据推荐ID查询推荐信息 -->
|
<!-- 根据推荐ID查询推荐信息 -->
|
||||||
@@ -70,12 +158,43 @@
|
|||||||
ORDER BY order_num ASC, create_time DESC
|
ORDER BY order_num ASC, create_time DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 根据类型查询推荐列表 -->
|
<!-- 根据推荐类型查询推荐列表 - 返回VO,包含资源信息和权限 -->
|
||||||
<select id="selectByType" resultMap="BaseResultMap">
|
<select id="selectByRecommendType" resultMap="ResourceRecommendVOResultMap">
|
||||||
|
SELECT DISTINCT
|
||||||
|
rr.id, rr.resource_id, rr.recommend_type, rr.order_num, rr.reason,
|
||||||
|
rr.creator, rr.updater, rr.create_time, rr.update_time, rr.delete_time, rr.deleted,
|
||||||
|
r.title, 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 AS resource_creator, r.updater AS resource_updater,
|
||||||
|
r.create_time AS resource_create_time, r.update_time AS resource_update_time,
|
||||||
|
MAX(rp.can_read) AS can_read, MAX(rp.can_write) AS can_write
|
||||||
|
FROM tb_resource_recommend rr
|
||||||
|
INNER JOIN tb_resource r ON rr.resource_id = r.resource_id AND r.deleted = 0
|
||||||
|
<include refid="Permission_Filter"/>
|
||||||
|
WHERE rr.deleted = 0
|
||||||
|
<if test="recommendType != null">
|
||||||
|
AND rr.recommend_type = #{recommendType}
|
||||||
|
</if>
|
||||||
|
GROUP BY rr.id, rr.resource_id, rr.recommend_type, rr.order_num, rr.reason,
|
||||||
|
rr.creator, rr.updater, rr.create_time, rr.update_time, rr.delete_time, rr.deleted,
|
||||||
|
r.title, 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
|
||||||
|
ORDER BY rr.order_num ASC, rr.create_time DESC
|
||||||
|
<if test="limit != null and limit > 0">
|
||||||
|
LIMIT #{limit}
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据资源ID和推荐类型查询 -->
|
||||||
|
<select id="selectByResourceIdAndType" resultMap="BaseResultMap">
|
||||||
SELECT
|
SELECT
|
||||||
<include refid="Base_Column_List" />
|
<include refid="Base_Column_List" />
|
||||||
FROM tb_resource_recommend
|
FROM tb_resource_recommend
|
||||||
WHERE deleted = 0
|
WHERE resource_id = #{resourceId} AND deleted = 0
|
||||||
|
<if test="recommendType != null">
|
||||||
|
AND recommend_type = #{recommendType}
|
||||||
|
</if>
|
||||||
ORDER BY order_num ASC, create_time DESC
|
ORDER BY order_num ASC, create_time DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
@@ -106,10 +225,10 @@
|
|||||||
<!-- 插入资源推荐 -->
|
<!-- 插入资源推荐 -->
|
||||||
<insert id="insertResourceRecommend" parameterType="org.xyzh.common.dto.resource.TbResourceRecommend">
|
<insert id="insertResourceRecommend" parameterType="org.xyzh.common.dto.resource.TbResourceRecommend">
|
||||||
INSERT INTO tb_resource_recommend (
|
INSERT INTO tb_resource_recommend (
|
||||||
id, resource_id, order_num, reason, creator, updater, create_time,
|
id, resource_id, recommend_type, order_num, reason, creator, updater, create_time,
|
||||||
update_time, delete_time, deleted
|
update_time, delete_time, deleted
|
||||||
) VALUES (
|
) VALUES (
|
||||||
#{id}, #{resourceID}, #{orderNum}, #{reason}, #{creator}, #{updater}, #{createTime},
|
#{id}, #{resourceID}, #{recommendType}, #{orderNum}, #{reason}, #{creator}, #{updater}, #{createTime},
|
||||||
#{updateTime}, #{deleteTime}, #{deleted}
|
#{updateTime}, #{deleteTime}, #{deleted}
|
||||||
)
|
)
|
||||||
</insert>
|
</insert>
|
||||||
@@ -121,6 +240,9 @@
|
|||||||
<if test="resourceID != null and resourceID != ''">
|
<if test="resourceID != null and resourceID != ''">
|
||||||
resource_id = #{resourceID},
|
resource_id = #{resourceID},
|
||||||
</if>
|
</if>
|
||||||
|
<if test="recommendType != null">
|
||||||
|
recommend_type = #{recommendType},
|
||||||
|
</if>
|
||||||
<if test="orderNum != null">
|
<if test="orderNum != null">
|
||||||
order_num = #{orderNum},
|
order_num = #{orderNum},
|
||||||
</if>
|
</if>
|
||||||
@@ -152,12 +274,12 @@
|
|||||||
<!-- 批量插入资源推荐 -->
|
<!-- 批量插入资源推荐 -->
|
||||||
<insert id="batchInsertResourceRecommends" parameterType="java.util.List">
|
<insert id="batchInsertResourceRecommends" parameterType="java.util.List">
|
||||||
INSERT INTO tb_resource_recommend (
|
INSERT INTO tb_resource_recommend (
|
||||||
id, resource_id, order_num, reason, creator, updater, create_time,
|
id, resource_id, recommend_type, order_num, reason, creator, updater, create_time,
|
||||||
update_time, delete_time, deleted
|
update_time, delete_time, deleted
|
||||||
) VALUES
|
) VALUES
|
||||||
<foreach collection="resourceRecommendList" item="item" separator=",">
|
<foreach collection="resourceRecommendList" item="item" separator=",">
|
||||||
(
|
(
|
||||||
#{item.id}, #{item.resourceID}, #{item.orderNum}, #{item.reason}, #{item.creator},
|
#{item.id}, #{item.resourceID}, #{item.recommendType}, #{item.orderNum}, #{item.reason}, #{item.creator},
|
||||||
#{item.updater}, #{item.createTime}, #{item.updateTime}, #{item.deleteTime}, #{item.deleted}
|
#{item.updater}, #{item.createTime}, #{item.updateTime}, #{item.deleteTime}, #{item.deleted}
|
||||||
)
|
)
|
||||||
</foreach>
|
</foreach>
|
||||||
@@ -172,21 +294,37 @@
|
|||||||
</foreach>
|
</foreach>
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
<!-- 分页查询资源推荐 -->
|
<!-- 分页查询资源推荐 - 返回VO,包含资源信息和权限 -->
|
||||||
<select id="selectResourceRecommendsPage" resultMap="BaseResultMap">
|
<select id="selectResourceRecommendsPage" resultMap="ResourceRecommendVOResultMap">
|
||||||
SELECT
|
SELECT DISTINCT
|
||||||
<include refid="Base_Column_List" />
|
rr.id, rr.resource_id, rr.recommend_type, rr.order_num, rr.reason,
|
||||||
FROM tb_resource_recommend
|
rr.creator, rr.updater, rr.create_time, rr.update_time, rr.delete_time, rr.deleted,
|
||||||
|
r.title, 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 AS resource_creator, r.updater AS resource_updater,
|
||||||
|
r.create_time AS resource_create_time, r.update_time AS resource_update_time,
|
||||||
|
MAX(rp.can_read) AS can_read, MAX(rp.can_write) AS can_write
|
||||||
|
FROM tb_resource_recommend rr
|
||||||
|
INNER JOIN tb_resource r ON rr.resource_id = r.resource_id AND r.deleted = 0
|
||||||
|
<include refid="Permission_Filter"/>
|
||||||
<include refid="Where_Clause" />
|
<include refid="Where_Clause" />
|
||||||
ORDER BY order_num ASC, create_time DESC
|
GROUP BY rr.id, rr.resource_id, rr.recommend_type, rr.order_num, rr.reason,
|
||||||
|
rr.creator, rr.updater, rr.create_time, rr.update_time, rr.delete_time, rr.deleted,
|
||||||
|
r.title, 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
|
||||||
|
ORDER BY rr.order_num ASC, rr.create_time DESC
|
||||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 统计资源推荐总数 -->
|
<!-- 统计资源推荐总数 - 添加权限过滤 -->
|
||||||
<select id="countResourceRecommends" resultType="long">
|
<select id="countResourceRecommends" resultType="long">
|
||||||
SELECT COUNT(1)
|
SELECT COUNT(DISTINCT rr.id)
|
||||||
FROM tb_resource_recommend
|
FROM tb_resource_recommend rr
|
||||||
|
INNER JOIN tb_resource r ON rr.resource_id = r.resource_id AND r.deleted = 0
|
||||||
|
<include refid="Permission_Filter"/>
|
||||||
<include refid="Where_Clause" />
|
<include refid="Where_Clause" />
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.xyzh.study.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.xyzh.common.dto.study.TbLearningTaskTag;
|
||||||
|
import org.xyzh.common.vo.TagVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 学习任务标签关联Mapper
|
||||||
|
* @filename LearningTaskTagMapper.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-10-31
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface LearningTaskTagMapper extends BaseMapper<TbLearningTaskTag> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据任务ID查询标签VO列表(包含标签详细信息)
|
||||||
|
*/
|
||||||
|
List<TagVO> selectByTaskId(@Param("taskId") String taskId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据标签ID查询任务关联列表
|
||||||
|
*/
|
||||||
|
List<TbLearningTaskTag> selectByTagId(@Param("tagId") String tagId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入任务标签关联
|
||||||
|
*/
|
||||||
|
int batchInsert(@Param("list") List<TbLearningTaskTag> list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据任务ID删除所有标签关联
|
||||||
|
*/
|
||||||
|
int deleteByTaskId(@Param("taskId") String taskId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定任务的指定标签
|
||||||
|
*/
|
||||||
|
int deleteByTaskIdAndTagId(@Param("taskId") String taskId, @Param("tagId") String tagId);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,10 @@ import org.xyzh.study.mapper.TaskUserMapper;
|
|||||||
import org.xyzh.study.mapper.CourseMapper;
|
import org.xyzh.study.mapper.CourseMapper;
|
||||||
import org.xyzh.system.utils.LoginUtil;
|
import org.xyzh.system.utils.LoginUtil;
|
||||||
import org.xyzh.study.mapper.TaskItemMapper;
|
import org.xyzh.study.mapper.TaskItemMapper;
|
||||||
|
import org.xyzh.study.mapper.LearningTaskTagMapper;
|
||||||
import org.xyzh.api.study.task.LearningTaskService;
|
import org.xyzh.api.study.task.LearningTaskService;
|
||||||
|
import org.xyzh.common.dto.study.TbLearningTaskTag;
|
||||||
|
import org.xyzh.common.vo.TagVO;
|
||||||
import org.xyzh.common.core.enums.TaskItemType;
|
import org.xyzh.common.core.enums.TaskItemType;
|
||||||
import org.xyzh.api.system.permission.ResourcePermissionService;
|
import org.xyzh.api.system.permission.ResourcePermissionService;
|
||||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||||
@@ -59,6 +62,9 @@ public class SCLearningTaskServiceImpl implements LearningTaskService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private TaskItemMapper taskItemMapper;
|
private TaskItemMapper taskItemMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LearningTaskTagMapper learningTaskTagMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ResourcePermissionService resourcePermissionService;
|
private ResourcePermissionService resourcePermissionService;
|
||||||
|
|
||||||
@@ -237,6 +243,22 @@ public class SCLearningTaskServiceImpl implements LearningTaskService {
|
|||||||
int learnCount = courseMapper.incrementLearnCount(item.getItemID(), taskUsers.size());
|
int learnCount = courseMapper.incrementLearnCount(item.getItemID(), taskUsers.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 绑定标签
|
||||||
|
List<TagVO> taskTags = taskVO.getTaskTags();
|
||||||
|
if (taskTags != null && !taskTags.isEmpty()) {
|
||||||
|
List<TbLearningTaskTag> taskTagList = new ArrayList<>();
|
||||||
|
for (TagVO tag : taskTags) {
|
||||||
|
TbLearningTaskTag taskTag = new TbLearningTaskTag();
|
||||||
|
taskTag.setID(IDUtils.generateID());
|
||||||
|
taskTag.setTaskID(taskID);
|
||||||
|
taskTag.setTagID(tag.getTagID());
|
||||||
|
taskTag.setCreator(currentUser.getID());
|
||||||
|
taskTag.setCreateTime(now);
|
||||||
|
taskTagList.add(taskTag);
|
||||||
|
}
|
||||||
|
learningTaskTagMapper.batchInsert(taskTagList);
|
||||||
|
}
|
||||||
|
|
||||||
// 创建任务资源权限
|
// 创建任务资源权限
|
||||||
try {
|
try {
|
||||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||||
@@ -423,6 +445,57 @@ public class SCLearningTaskServiceImpl implements LearningTaskService {
|
|||||||
int learnCount = courseMapper.incrementLearnCount(taskID, usersToInsert.size());
|
int learnCount = courseMapper.incrementLearnCount(taskID, usersToInsert.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. 处理标签关联
|
||||||
|
List<TagVO> newTags = taskVO.getTaskTags();
|
||||||
|
if (newTags == null) {
|
||||||
|
newTags = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取现有的标签关联
|
||||||
|
List<TagVO> existingTags = learningTaskTagMapper.selectByTaskId(taskID);
|
||||||
|
Map<String, TagVO> existingTagMap = existingTags.stream()
|
||||||
|
.collect(Collectors.toMap(TagVO::getTagID, tag -> tag));
|
||||||
|
|
||||||
|
Set<String> newTagIDs = new HashSet<>();
|
||||||
|
List<TbLearningTaskTag> tagsToInsert = new ArrayList<>();
|
||||||
|
|
||||||
|
// 处理新的标签关联
|
||||||
|
for (TagVO tag : newTags) {
|
||||||
|
String tagID = tag.getTagID();
|
||||||
|
if (tagID == null || tagID.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newTagIDs.add(tagID);
|
||||||
|
|
||||||
|
// 如果不存在,则新增
|
||||||
|
if (!existingTagMap.containsKey(tagID)) {
|
||||||
|
TbLearningTaskTag taskTag = new TbLearningTaskTag();
|
||||||
|
taskTag.setID(IDUtils.generateID());
|
||||||
|
taskTag.setTaskID(taskID);
|
||||||
|
taskTag.setTagID(tagID);
|
||||||
|
taskTag.setCreator(user.getID());
|
||||||
|
taskTag.setCreateTime(now);
|
||||||
|
tagsToInsert.add(taskTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找出要删除的标签关联
|
||||||
|
List<String> tagIDsToDelete = existingTagMap.values().stream()
|
||||||
|
.filter(taskTag -> !newTagIDs.contains(taskTag.getTagID()))
|
||||||
|
.map(TagVO::getTagID)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 删除不再需要的标签
|
||||||
|
for (String tagID : tagIDsToDelete) {
|
||||||
|
learningTaskTagMapper.deleteByTaskIdAndTagId(taskID, tagID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入新标签
|
||||||
|
if (!tagsToInsert.isEmpty()) {
|
||||||
|
learningTaskTagMapper.batchInsert(tagsToInsert);
|
||||||
|
}
|
||||||
|
|
||||||
resultDomain.success("更新任务成功", taskVO);
|
resultDomain.success("更新任务成功", taskVO);
|
||||||
return resultDomain;
|
return resultDomain;
|
||||||
}
|
}
|
||||||
@@ -495,10 +568,12 @@ public class SCLearningTaskServiceImpl implements LearningTaskService {
|
|||||||
taskVO.setTaskResources(taskResources);
|
taskVO.setTaskResources(taskResources);
|
||||||
taskVO.setTaskUsers(taskUsers);
|
taskVO.setTaskUsers(taskUsers);
|
||||||
|
|
||||||
|
// 获取任务标签
|
||||||
|
List<TagVO> taskTags = learningTaskTagMapper.selectByTaskId(taskID);
|
||||||
|
taskVO.setTaskTags(taskTags);
|
||||||
|
|
||||||
taskVO.setTotalTaskNum(allTaskItems.size());
|
taskVO.setTotalTaskNum(allTaskItems.size());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
resultDomain.success("获取任务详情成功", taskVO);
|
resultDomain.success("获取任务详情成功", taskVO);
|
||||||
return resultDomain;
|
return resultDomain;
|
||||||
}
|
}
|
||||||
@@ -557,10 +632,12 @@ public class SCLearningTaskServiceImpl implements LearningTaskService {
|
|||||||
taskVO.setTaskResources(taskResources);
|
taskVO.setTaskResources(taskResources);
|
||||||
taskVO.setTaskUsers(taskUsers);
|
taskVO.setTaskUsers(taskUsers);
|
||||||
|
|
||||||
|
// 获取任务标签
|
||||||
|
List<TagVO> taskTags = learningTaskTagMapper.selectByTaskId(taskID);
|
||||||
|
taskVO.setTaskTags(taskTags);
|
||||||
|
|
||||||
taskVO.setTotalTaskNum(allTaskItems.size());
|
taskVO.setTotalTaskNum(allTaskItems.size());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
resultDomain.success("获取任务详情成功", taskVO);
|
resultDomain.success("获取任务详情成功", taskVO);
|
||||||
return resultDomain;
|
return resultDomain;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?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="org.xyzh.study.mapper.LearningTaskTagMapper">
|
||||||
|
|
||||||
|
<!-- 结果映射 -->
|
||||||
|
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.study.TbLearningTaskTag">
|
||||||
|
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||||
|
<result column="task_id" property="taskID" jdbcType="VARCHAR"/>
|
||||||
|
<result column="tag_id" property="tagID" jdbcType="VARCHAR"/>
|
||||||
|
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||||
|
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- TagVO 结果映射 -->
|
||||||
|
<resultMap id="TagVOResultMap" type="org.xyzh.common.vo.TagVO">
|
||||||
|
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||||
|
<result column="tag_id" property="tagID" jdbcType="VARCHAR"/>
|
||||||
|
<result column="tag_name" property="tagName" jdbcType="VARCHAR"/>
|
||||||
|
<result column="tag_color" property="tagColor" jdbcType="VARCHAR"/>
|
||||||
|
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- 基础列 -->
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id, task_id, tag_id, creator, create_time
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 根据任务ID查询标签VO列表(包含标签详细信息) -->
|
||||||
|
<select id="selectByTaskId" resultMap="TagVOResultMap">
|
||||||
|
SELECT
|
||||||
|
tlt.id,
|
||||||
|
tlt.tag_id,
|
||||||
|
t.name AS tag_name,
|
||||||
|
t.color AS tag_color,
|
||||||
|
tlt.creator
|
||||||
|
FROM tb_learning_task_tag tlt
|
||||||
|
INNER JOIN tb_tag t ON tlt.tag_id = t.tag_id AND t.deleted = 0
|
||||||
|
WHERE tlt.task_id = #{taskId}
|
||||||
|
ORDER BY tlt.create_time ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据标签ID查询任务关联列表 -->
|
||||||
|
<select id="selectByTagId" resultMap="BaseResultMap">
|
||||||
|
SELECT
|
||||||
|
<include refid="Base_Column_List"/>
|
||||||
|
FROM tb_learning_task_tag
|
||||||
|
WHERE tag_id = #{tagId}
|
||||||
|
ORDER BY create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 批量插入任务标签关联 -->
|
||||||
|
<insert id="batchInsert">
|
||||||
|
INSERT INTO tb_learning_task_tag (
|
||||||
|
id, task_id, tag_id, creator, create_time
|
||||||
|
) VALUES
|
||||||
|
<foreach collection="list" item="item" separator=",">
|
||||||
|
(
|
||||||
|
#{item.id}, #{item.taskID}, #{item.tagID},
|
||||||
|
#{item.creator}, #{item.createTime}
|
||||||
|
)
|
||||||
|
</foreach>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<!-- 根据任务ID删除所有标签关联 -->
|
||||||
|
<delete id="deleteByTaskId">
|
||||||
|
DELETE FROM tb_learning_task_tag
|
||||||
|
WHERE task_id = #{taskId}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<!-- 删除指定任务的指定标签 -->
|
||||||
|
<delete id="deleteByTaskIdAndTagId">
|
||||||
|
DELETE FROM tb_learning_task_tag
|
||||||
|
WHERE task_id = #{taskId} AND tag_id = #{tagId}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -32,6 +32,16 @@
|
|||||||
<artifactId>api-system</artifactId>
|
<artifactId>api-system</artifactId>
|
||||||
<version>${school-news.version}</version>
|
<version>${school-news.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xyzh</groupId>
|
||||||
|
<artifactId>api-news</artifactId>
|
||||||
|
<version>${school-news.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xyzh</groupId>
|
||||||
|
<artifactId>api-study</artifactId>
|
||||||
|
<version>${school-news.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.xyzh</groupId>
|
<groupId>org.xyzh</groupId>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import org.xyzh.api.usercenter.collection.UserCollectionService;
|
|||||||
import org.xyzh.common.core.domain.ResultDomain;
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
import org.xyzh.common.dto.user.TbSysUser;
|
import org.xyzh.common.dto.user.TbSysUser;
|
||||||
import org.xyzh.common.dto.usercenter.TbUserCollection;
|
import org.xyzh.common.dto.usercenter.TbUserCollection;
|
||||||
import org.xyzh.common.dto.user.TbSysUser;
|
import org.xyzh.common.vo.UserCollectionVO;
|
||||||
import org.xyzh.system.utils.LoginUtil;
|
import org.xyzh.system.utils.LoginUtil;
|
||||||
/**
|
/**
|
||||||
* @description 用户收藏控制器
|
* @description 用户收藏控制器
|
||||||
@@ -26,14 +26,13 @@ public class UserCollectionController {
|
|||||||
private UserCollectionService userCollectionService;
|
private UserCollectionService userCollectionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户收藏列表
|
* 获取用户收藏列表(扁平化VO,包含资源/课程详情)
|
||||||
*/
|
*/
|
||||||
@GetMapping("/user/{userID}")
|
@GetMapping("/user/{userID}")
|
||||||
public ResultDomain<TbUserCollection> getUserCollections(
|
public ResultDomain<UserCollectionVO> getUserCollections(
|
||||||
@PathVariable("userID") String userID,
|
@PathVariable("userID") String userID,
|
||||||
@RequestParam(required = false) Integer resourceType,
|
@RequestParam(required = false, name = "collectionType") Integer collectionType) {
|
||||||
@RequestParam(required = false) String resourceID) {
|
return userCollectionService.getUserCollections(userID, collectionType);
|
||||||
return userCollectionService.getUserCollections(userID, resourceType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,8 +56,8 @@ public class UserCollectionController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/check")
|
@GetMapping("/check")
|
||||||
public ResultDomain<Boolean> isCollected(
|
public ResultDomain<Boolean> isCollected(
|
||||||
@RequestParam(value = "collectionID", required = false) String collectionID,
|
@RequestParam(name = "collectionID", required = false) String collectionID,
|
||||||
@RequestParam(value = "collectionType", required = false) Integer collectionType) {
|
@RequestParam(name = "collectionType", required = false) Integer collectionType) {
|
||||||
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
|
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
|
||||||
TbSysUser user = LoginUtil.getCurrentUser();
|
TbSysUser user = LoginUtil.getCurrentUser();
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
@@ -74,8 +73,8 @@ public class UserCollectionController {
|
|||||||
@GetMapping("/count/{userID}")
|
@GetMapping("/count/{userID}")
|
||||||
public ResultDomain<Integer> getCollectionCount(
|
public ResultDomain<Integer> getCollectionCount(
|
||||||
@PathVariable("userID") String userID,
|
@PathVariable("userID") String userID,
|
||||||
@RequestParam(required = false) Integer resourceType,
|
@RequestParam(name = "collectionType", required = false) Integer collectionType,
|
||||||
@RequestParam(required = false) String resourceID) {
|
@RequestParam(name = "collectionID", required = false) String collectionID) {
|
||||||
return userCollectionService.getCollectionCount(userID, resourceType, resourceID);
|
return userCollectionService.getCollectionCount(userID, collectionType, collectionID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Mapper;
|
|||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.xyzh.common.core.page.PageParam;
|
import org.xyzh.common.core.page.PageParam;
|
||||||
import org.xyzh.common.dto.usercenter.TbUserCollection;
|
import org.xyzh.common.dto.usercenter.TbUserCollection;
|
||||||
|
import org.xyzh.common.vo.UserCollectionVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -27,6 +28,16 @@ public interface UserCollectionMapper extends BaseMapper<TbUserCollection> {
|
|||||||
*/
|
*/
|
||||||
List<TbUserCollection> selectUserCollections(TbUserCollection filter);
|
List<TbUserCollection> selectUserCollections(TbUserCollection filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 查询用户收藏列表(包含资源/课程详情)
|
||||||
|
* @param userID 用户ID
|
||||||
|
* @param collectionType 收藏类型(可选)
|
||||||
|
* @return List<UserCollectionVO> 用户收藏VO列表
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-10-31
|
||||||
|
*/
|
||||||
|
List<UserCollectionVO> selectUserCollectionsVO(@Param("userID") String userID, @Param("collectionType") Integer collectionType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 根据用户ID查询收藏记录
|
* @description 根据用户ID查询收藏记录
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.xyzh.usercenter.service.impl;
|
package org.xyzh.usercenter.service.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -7,9 +8,13 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.xyzh.api.news.resource.ResourceService;
|
||||||
import org.xyzh.common.core.domain.ResultDomain;
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
|
import org.xyzh.common.dto.resource.TbResource;
|
||||||
import org.xyzh.common.dto.user.TbSysUser;
|
import org.xyzh.common.dto.user.TbSysUser;
|
||||||
import org.xyzh.common.dto.usercenter.TbUserCollection;
|
import org.xyzh.common.dto.usercenter.TbUserCollection;
|
||||||
|
import org.xyzh.common.vo.UserCollectionVO;
|
||||||
import org.xyzh.system.utils.LoginUtil;
|
import org.xyzh.system.utils.LoginUtil;
|
||||||
import org.xyzh.usercenter.mapper.UserCollectionMapper;
|
import org.xyzh.usercenter.mapper.UserCollectionMapper;
|
||||||
import org.xyzh.usercenter.service.UCUserCollectionService;
|
import org.xyzh.usercenter.service.UCUserCollectionService;
|
||||||
@@ -29,27 +34,32 @@ public class UCUserCollectionServiceImpl implements UCUserCollectionService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserCollectionMapper userCollectionMapper;
|
private UserCollectionMapper userCollectionMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ResourceService resourceService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public ResultDomain<TbUserCollection> addCollection(TbUserCollection userCollection) {
|
public ResultDomain<TbUserCollection> addCollection(TbUserCollection userCollection) {
|
||||||
|
ResultDomain<TbUserCollection> resultDomain = new ResultDomain<>();
|
||||||
TbSysUser user = LoginUtil.getCurrentUser();
|
TbSysUser user = LoginUtil.getCurrentUser();
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
ResultDomain<TbUserCollection> resultDomain = new ResultDomain<>();
|
throw new RuntimeException("请先登录");
|
||||||
resultDomain.fail("请先登录");
|
|
||||||
return resultDomain;
|
|
||||||
}
|
}
|
||||||
userCollection.setUserID(user.getID());
|
userCollection.setUserID(user.getID());
|
||||||
userCollection.setCreateTime(new Date());
|
userCollection.setCreateTime(new Date());
|
||||||
int result = userCollectionMapper.insertUserCollection(userCollection);
|
int result = userCollectionMapper.insertUserCollection(userCollection);
|
||||||
if (result > 0) {
|
if (result > 0) {
|
||||||
ResultDomain<TbUserCollection> resultDomain = new ResultDomain<>();
|
userCollection.setCollectionValue(1);
|
||||||
|
ResultDomain<TbResource> resultDomainResource = resourceService.resourceCollect(userCollection);
|
||||||
|
if (!resultDomainResource.isSuccess()) {
|
||||||
|
throw new RuntimeException(resultDomainResource.getMessage());
|
||||||
|
}
|
||||||
resultDomain.success("添加收藏成功", userCollection);
|
resultDomain.success("添加收藏成功", userCollection);
|
||||||
return resultDomain;
|
return resultDomain;
|
||||||
} else {
|
}
|
||||||
ResultDomain<TbUserCollection> resultDomain = new ResultDomain<>();
|
|
||||||
resultDomain.fail("添加收藏失败");
|
resultDomain.fail("添加收藏失败");
|
||||||
return resultDomain;
|
return resultDomain;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResultDomain<TbUserCollection> getCollectionDetail(String userID, Integer collectionType,
|
public ResultDomain<TbUserCollection> getCollectionDetail(String userID, Integer collectionType,
|
||||||
@@ -58,20 +68,24 @@ public class UCUserCollectionServiceImpl implements UCUserCollectionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResultDomain<TbUserCollection> getUserCollections(String userID, Integer collectionType) {
|
public ResultDomain<UserCollectionVO> getUserCollections(String userID, Integer collectionType) {
|
||||||
TbUserCollection filter = new TbUserCollection();
|
ResultDomain<UserCollectionVO> resultDomain = new ResultDomain<>();
|
||||||
filter.setUserID(userID);
|
|
||||||
filter.setCollectionType(collectionType);
|
try {
|
||||||
List<TbUserCollection> list = userCollectionMapper.selectUserCollections(filter);
|
// 直接通过SQL JOIN一次性查询所有数据,避免N+1查询问题
|
||||||
if (list != null && list.size() > 0) {
|
List<UserCollectionVO> voList = userCollectionMapper.selectUserCollectionsVO(userID, collectionType);
|
||||||
ResultDomain<TbUserCollection> resultDomain = new ResultDomain<>();
|
|
||||||
resultDomain.success("获取收藏列表成功", list);
|
if (voList == null || voList.isEmpty()) {
|
||||||
return resultDomain;
|
resultDomain.success("获取收藏列表成功", new ArrayList<>());
|
||||||
} else {
|
} else {
|
||||||
ResultDomain<TbUserCollection> resultDomain = new ResultDomain<>();
|
resultDomain.success("获取收藏列表成功", voList);
|
||||||
resultDomain.fail("获取收藏列表失败");
|
|
||||||
return resultDomain;
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取收藏列表失败: userID={}, collectionType={}", userID, collectionType, e);
|
||||||
|
resultDomain.fail("获取收藏列表失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -93,9 +107,22 @@ public class UCUserCollectionServiceImpl implements UCUserCollectionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public ResultDomain<Boolean> removeCollection(TbUserCollection userCollection) {
|
public ResultDomain<Boolean> removeCollection(TbUserCollection userCollection) {
|
||||||
|
TbSysUser user = LoginUtil.getCurrentUser();
|
||||||
|
if (user == null) {
|
||||||
|
throw new RuntimeException("请先登录");
|
||||||
|
}
|
||||||
|
userCollection.setUserID(user.getID());
|
||||||
|
userCollection.setCollectionValue(-1);
|
||||||
int result = userCollectionMapper.deleteUserCollection(userCollection);
|
int result = userCollectionMapper.deleteUserCollection(userCollection);
|
||||||
|
|
||||||
if (result > 0) {
|
if (result > 0) {
|
||||||
|
userCollection.setCollectionValue(-1);
|
||||||
|
ResultDomain<TbResource> resultDomainResource = resourceService.resourceCollect(userCollection);
|
||||||
|
if (!resultDomainResource.isSuccess()) {
|
||||||
|
throw new RuntimeException(resultDomainResource.getMessage());
|
||||||
|
}
|
||||||
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
|
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
|
||||||
resultDomain.success("删除收藏成功", true);
|
resultDomain.success("删除收藏成功", true);
|
||||||
return resultDomain;
|
return resultDomain;
|
||||||
|
|||||||
@@ -5,12 +5,48 @@
|
|||||||
<!-- 结果映射 -->
|
<!-- 结果映射 -->
|
||||||
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.usercenter.TbUserCollection">
|
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.usercenter.TbUserCollection">
|
||||||
<id column="id" property="id" />
|
<id column="id" property="id" />
|
||||||
<result column="user_id" property=".userID" />
|
<result column="user_id" property="userID" />
|
||||||
<result column="collection_type" property="collectionType" />
|
<result column="collection_type" property="collectionType" />
|
||||||
<result column="collection_id" property="collectionID" />
|
<result column="collection_id" property="collectionID" />
|
||||||
<result column="create_time" property="createTime" />
|
<result column="create_time" property="createTime" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- UserCollectionVO结果映射 -->
|
||||||
|
<resultMap id="UserCollectionVOResultMap" type="org.xyzh.common.vo.UserCollectionVO">
|
||||||
|
<!-- 收藏基本信息 -->
|
||||||
|
<id column="id" property="id" />
|
||||||
|
<result column="user_id" property="userID" />
|
||||||
|
<result column="collection_type" property="collectionType" />
|
||||||
|
<result column="collection_id" property="collectionID" />
|
||||||
|
<result column="collection_time" property="collectionTime" />
|
||||||
|
<result column="create_time" property="createTime" />
|
||||||
|
|
||||||
|
<!-- 资源详情(collection_type=1时有效) -->
|
||||||
|
<result column="resource_id" property="resourceID" />
|
||||||
|
<result column="resource_title" property="title" />
|
||||||
|
<result column="resource_content" property="content" />
|
||||||
|
<result column="resource_summary" property="summary" />
|
||||||
|
<result column="resource_cover_image" property="coverImage" />
|
||||||
|
<result column="resource_tag_id" property="tagID" />
|
||||||
|
<result column="resource_author" property="author" />
|
||||||
|
<result column="resource_source" property="source" />
|
||||||
|
<result column="resource_view_count" property="viewCount" />
|
||||||
|
<result column="resource_like_count" property="likeCount" />
|
||||||
|
<result column="resource_collect_count" property="collectCount" />
|
||||||
|
<result column="resource_status" property="status" />
|
||||||
|
<result column="resource_publish_time" property="publishTime" />
|
||||||
|
|
||||||
|
<!-- 课程详情(collection_type=2时有效) -->
|
||||||
|
<result column="course_id" property="courseID" />
|
||||||
|
<result column="course_name" property="courseName" />
|
||||||
|
<result column="course_description" property="description" />
|
||||||
|
<result column="course_duration" property="duration" />
|
||||||
|
<result column="course_teacher" property="teacher" />
|
||||||
|
<result column="course_status" property="courseStatus" />
|
||||||
|
<result column="course_view_count" property="viewCount" />
|
||||||
|
<result column="course_learn_count" property="learnCount" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
<!-- 字段列表 -->
|
<!-- 字段列表 -->
|
||||||
<sql id="Base_Column_List">
|
<sql id="Base_Column_List">
|
||||||
id, user_id, collection_type, collection_id, create_time
|
id, user_id, collection_type, collection_id, create_time
|
||||||
@@ -292,4 +328,49 @@
|
|||||||
<include refid="Base_Where_Clause" />
|
<include refid="Base_Where_Clause" />
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询用户收藏列表(包含资源/课程详情)- 一次性JOIN查询 -->
|
||||||
|
<select id="selectUserCollectionsVO" resultMap="UserCollectionVOResultMap">
|
||||||
|
SELECT
|
||||||
|
uc.id,
|
||||||
|
uc.user_id,
|
||||||
|
uc.collection_type,
|
||||||
|
uc.collection_id,
|
||||||
|
uc.create_time as collection_time,
|
||||||
|
uc.create_time,
|
||||||
|
|
||||||
|
<!-- 资源字段 -->
|
||||||
|
r.resource_id,
|
||||||
|
r.title as resource_title,
|
||||||
|
r.content as resource_content,
|
||||||
|
r.summary as resource_summary,
|
||||||
|
r.cover_image as resource_cover_image,
|
||||||
|
r.tag_id as resource_tag_id,
|
||||||
|
r.author as resource_author,
|
||||||
|
r.source as resource_source,
|
||||||
|
r.view_count as resource_view_count,
|
||||||
|
r.like_count as resource_like_count,
|
||||||
|
r.collect_count as resource_collect_count,
|
||||||
|
r.status as resource_status,
|
||||||
|
r.publish_time as resource_publish_time,
|
||||||
|
|
||||||
|
<!-- 课程字段 -->
|
||||||
|
c.course_id,
|
||||||
|
c.name as course_name,
|
||||||
|
c.description as course_description,
|
||||||
|
c.cover_image as course_cover_image,
|
||||||
|
c.duration as course_duration,
|
||||||
|
c.teacher as course_teacher,
|
||||||
|
c.status as course_status,
|
||||||
|
c.view_count as course_view_count,
|
||||||
|
c.learn_count as course_learn_count
|
||||||
|
FROM tb_user_collection uc
|
||||||
|
LEFT JOIN tb_resource r ON uc.collection_type = 1 AND uc.collection_id = r.resource_id
|
||||||
|
LEFT JOIN tb_course c ON uc.collection_type = 2 AND uc.collection_id = c.course_id
|
||||||
|
WHERE uc.user_id = #{userID}
|
||||||
|
<if test="collectionType != null">
|
||||||
|
AND uc.collection_type = #{collectionType}
|
||||||
|
</if>
|
||||||
|
ORDER BY uc.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { api } from '@/apis/index';
|
import { api } from '@/apis/index';
|
||||||
import type { Resource, ResultDomain } from '@/types';
|
import type { Resource, ResourceRecommendVO, ResultDomain } from '@/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 推荐API服务
|
* 推荐API服务
|
||||||
@@ -38,5 +38,27 @@ export const recommendApi = {
|
|||||||
async getHotNews(limit?: number): Promise<ResultDomain<Resource>> {
|
async getHotNews(limit?: number): Promise<ResultDomain<Resource>> {
|
||||||
const response = await api.get<Resource>('/homepage/recommend/hot-news', { limit });
|
const response = await api.get<Resource>('/homepage/recommend/hot-news', { limit });
|
||||||
return response.data;
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取热门资源列表(推荐类型:1)
|
||||||
|
* @param limit 限制数量
|
||||||
|
* @returns Promise<ResultDomain<ResourceRecommendVO>>
|
||||||
|
*/
|
||||||
|
async getHotResources(limit?: number): Promise<ResultDomain<ResourceRecommendVO>> {
|
||||||
|
|
||||||
|
const response = await api.get<ResourceRecommendVO>('/homepage/recommend/hot', { limit });
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取思政资源列表(推荐类型:2)
|
||||||
|
* @param limit 限制数量
|
||||||
|
* @returns Promise<ResultDomain<ResourceRecommendVO>>
|
||||||
|
*/
|
||||||
|
async getIdeologicalResources(limit?: number): Promise<ResultDomain<ResourceRecommendVO>> {
|
||||||
|
|
||||||
|
const response = await api.get<ResourceRecommendVO>('/homepage/recommend/ideological', { limit });
|
||||||
|
return response.data;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,4 +6,5 @@
|
|||||||
|
|
||||||
export * from './resourceTag';
|
export * from './resourceTag';
|
||||||
export * from './resource';
|
export * from './resource';
|
||||||
export { bannerApi} from './banner';
|
export * from './resourceRecommend';
|
||||||
|
export { bannerApi } from './banner';
|
||||||
@@ -136,16 +136,6 @@ export const resourceApi = {
|
|||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 收藏次数增减
|
|
||||||
* @param resourceID 资源ID
|
|
||||||
* @returns Promise<ResultDomain<Resource>>
|
|
||||||
*/
|
|
||||||
async resourceCollect(collect: UserCollection): Promise<ResultDomain<Resource>> {
|
|
||||||
const response = await api.post<Resource>(`/news/resources/resource/collect`, collect);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
// ==================== 资源推荐和轮播操作 ====================
|
// ==================== 资源推荐和轮播操作 ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
155
schoolNewsWeb/src/apis/resource/resourceRecommend.ts
Normal file
155
schoolNewsWeb/src/apis/resource/resourceRecommend.ts
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
/**
|
||||||
|
* @description 资源推荐管理API接口
|
||||||
|
* @filename resourceRecommend.ts
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-10-31
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { api } from '@/apis';
|
||||||
|
import type { ResultDomain, ResourceRecommendVO, PageParam } from '@/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐类型枚举
|
||||||
|
*/
|
||||||
|
export enum RecommendType {
|
||||||
|
/** 热门资源推荐 */
|
||||||
|
HOT = 1,
|
||||||
|
/** 思政资源推荐 */
|
||||||
|
IDEOLOGICAL = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源推荐API服务
|
||||||
|
*/
|
||||||
|
export const resourceRecommendApi = {
|
||||||
|
/**
|
||||||
|
* 获取推荐列表
|
||||||
|
* @returns Promise<ResultDomain<ResourceRecommendVO>>
|
||||||
|
*/
|
||||||
|
async getRecommendList(): Promise<ResultDomain<ResourceRecommendVO>> {
|
||||||
|
const response = await api.get<ResourceRecommendVO>('/news/recommends/list');
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据推荐类型获取推荐资源列表
|
||||||
|
* @param recommendType 推荐类型(1-热门资源,2-思政资源)
|
||||||
|
* @param limit 限制数量
|
||||||
|
* @returns Promise<ResultDomain<ResourceRecommendVO>>
|
||||||
|
*/
|
||||||
|
async getRecommendsByType(recommendType: number, limit?: number): Promise<ResultDomain<ResourceRecommendVO>> {
|
||||||
|
const params = limit ? { limit } : null;
|
||||||
|
const response = await api.get<ResourceRecommendVO>(`/news/recommends/type/${recommendType}`, params);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询推荐资源列表
|
||||||
|
* @param filter 筛选条件
|
||||||
|
* @param pageParam 分页参数
|
||||||
|
* @returns Promise<ResultDomain<ResourceRecommendVO>>
|
||||||
|
*/
|
||||||
|
async getRecommendPage(pageParam: PageParam, filter?: any): Promise<ResultDomain<ResourceRecommendVO>> {
|
||||||
|
const response = await api.post<ResourceRecommendVO>('/news/recommends/page', {
|
||||||
|
pageParam,
|
||||||
|
filter,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID获取推荐详情
|
||||||
|
* @param recommendID 推荐ID
|
||||||
|
* @returns Promise<ResultDomain<ResourceRecommendVO>>
|
||||||
|
*/
|
||||||
|
async getRecommendById(recommendID: string): Promise<ResultDomain<ResourceRecommendVO>> {
|
||||||
|
const response = await api.get<ResourceRecommendVO>(`/news/recommends/recommend/${recommendID}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建推荐
|
||||||
|
* @param recommend 推荐信息
|
||||||
|
* @returns Promise<ResultDomain<any>>
|
||||||
|
*/
|
||||||
|
async createRecommend(recommend: any): Promise<ResultDomain<any>> {
|
||||||
|
const response = await api.post<any>('/news/recommends/recommend', recommend);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加推荐资源
|
||||||
|
* @param resourceIDs 资源ID列表
|
||||||
|
* @param recommendType 推荐类型
|
||||||
|
* @param reason 推荐理由(可选)
|
||||||
|
* @returns Promise<ResultDomain<any>>
|
||||||
|
*/
|
||||||
|
async batchAddRecommends(resourceIDs: string[], recommendType: number, reason?: string): Promise<ResultDomain<any>> {
|
||||||
|
const response = await api.post<any>('/news/recommends/recommend/batch', {
|
||||||
|
resourceIDs,
|
||||||
|
recommendType,
|
||||||
|
reason
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新推荐
|
||||||
|
* @param recommend 推荐信息
|
||||||
|
* @returns Promise<ResultDomain<any>>
|
||||||
|
*/
|
||||||
|
async updateRecommend(recommend: any): Promise<ResultDomain<any>> {
|
||||||
|
const response = await api.put<any>('/news/recommends/recommend', recommend);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除推荐
|
||||||
|
* @param recommendID 推荐ID
|
||||||
|
* @returns Promise<ResultDomain<boolean>>
|
||||||
|
*/
|
||||||
|
async deleteRecommend(recommendID: string): Promise<ResultDomain<boolean>> {
|
||||||
|
const response = await api.delete<boolean>(`/news/recommends/recommend/${recommendID}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新推荐排序
|
||||||
|
* @param recommendID 推荐ID
|
||||||
|
* @param orderNum 排序号
|
||||||
|
* @returns Promise<ResultDomain<any>>
|
||||||
|
*/
|
||||||
|
async updateRecommendOrder(recommendID: string, orderNum: number): Promise<ResultDomain<any>> {
|
||||||
|
const response = await api.put<any>(`/news/recommends/recommend/${recommendID}/order`, null, {
|
||||||
|
params: { orderNum }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查资源是否已推荐(按类型)
|
||||||
|
* @param resourceID 资源ID
|
||||||
|
* @param recommendType 推荐类型
|
||||||
|
* @returns Promise<ResultDomain<boolean>>
|
||||||
|
*/
|
||||||
|
async isResourceRecommendedByType(resourceID: string, recommendType: number): Promise<ResultDomain<boolean>> {
|
||||||
|
const response = await api.get<boolean>(`/news/recommends/check/${resourceID}`, {
|
||||||
|
recommendType
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计推荐资源总数
|
||||||
|
* @param filter 筛选条件
|
||||||
|
* @returns Promise<ResultDomain<number>>
|
||||||
|
*/
|
||||||
|
async countRecommends(filter?: any): Promise<ResultDomain<number>> {
|
||||||
|
const response = await api.post<number>('/news/recommends/count', filter);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default resourceRecommendApi;
|
||||||
|
|
||||||
@@ -19,8 +19,8 @@ export const resourceTagApi = {
|
|||||||
* 获取标签列表(获取所有标签)
|
* 获取标签列表(获取所有标签)
|
||||||
* @returns Promise<ResultDomain<Tag>>
|
* @returns Promise<ResultDomain<Tag>>
|
||||||
*/
|
*/
|
||||||
async getTagList(): Promise<ResultDomain<Tag>> {
|
async getTagList(filter: Tag): Promise<ResultDomain<Tag>> {
|
||||||
const response = await api.get<Tag>('/news/tags/list');
|
const response = await api.get<Tag>('/news/tags/list', filter);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { api } from '@/apis/index';
|
import { api } from '@/apis/index';
|
||||||
import type { UserCollection, ResultDomain } from '@/types';
|
import type { UserCollection, UserCollectionVO, ResultDomain } from '@/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户收藏API服务
|
* 用户收藏API服务
|
||||||
@@ -13,14 +13,13 @@ import type { UserCollection, ResultDomain } from '@/types';
|
|||||||
export const userCollectionApi = {
|
export const userCollectionApi = {
|
||||||
baseUrl: '/usercenter/collections',
|
baseUrl: '/usercenter/collections',
|
||||||
/**
|
/**
|
||||||
* 获取用户收藏列表
|
* 获取用户收藏列表(扁平化VO,包含资源/课程详情)
|
||||||
* @param userID 用户ID
|
* @param userID 用户ID
|
||||||
* @param collectionType 收藏类型
|
* @param collectionType 收藏类型
|
||||||
* @returns Promise<ResultDomain<UserCollection>>
|
* @returns Promise<ResultDomain<UserCollectionVO>>
|
||||||
*/
|
*/
|
||||||
async getUserCollections(userID: string, collectionType?: number): Promise<ResultDomain<UserCollection>> {
|
async getUserCollections(userID: string, collectionType?: number): Promise<ResultDomain<UserCollectionVO>> {
|
||||||
const response = await api.get<UserCollection>(`${this.baseUrl}/list`, {
|
const response = await api.get<UserCollectionVO>(`${this.baseUrl}/user/${userID}`, {
|
||||||
userID,
|
|
||||||
collectionType
|
collectionType
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
@@ -43,12 +42,8 @@ export const userCollectionApi = {
|
|||||||
* @param collectionID 收藏对象ID
|
* @param collectionID 收藏对象ID
|
||||||
* @returns Promise<ResultDomain<boolean>>
|
* @returns Promise<ResultDomain<boolean>>
|
||||||
*/
|
*/
|
||||||
async removeCollection(userID: string, collectionType: number, collectionID: string): Promise<ResultDomain<boolean>> {
|
async removeCollection(collection: UserCollection): Promise<ResultDomain<UserCollection>> {
|
||||||
const response = await api.delete<boolean>(`${this.baseUrl}/collect`, {
|
const response = await api.delete<UserCollection>(`${this.baseUrl}/collect`, collection);
|
||||||
userID,
|
|
||||||
collectionType,
|
|
||||||
collectionID
|
|
||||||
});
|
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
4
schoolNewsWeb/src/assets/imgs/hot.svg
Normal file
4
schoolNewsWeb/src/assets/imgs/hot.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.99992 2C8.44436 3.77778 9.33325 5.22222 10.6666 6.33333C11.9999 7.44444 12.6666 8.66667 12.6666 10C12.6666 11.2377 12.1749 12.4247 11.2997 13.2998C10.4246 14.175 9.2376 14.6667 7.99992 14.6667C6.76224 14.6667 5.57526 14.175 4.70009 13.2998C3.82492 12.4247 3.33325 11.2377 3.33325 10C3.33325 9.27877 3.56718 8.57699 3.99992 8C3.99992 8.44203 4.17551 8.86595 4.48807 9.17851C4.80063 9.49107 5.22456 9.66667 5.66659 9.66667C6.10861 9.66667 6.53254 9.49107 6.8451 9.17851C7.15766 8.86595 7.33325 8.44203 7.33325 8C7.33325 6.66667 6.33325 6 6.33325 4.66667C6.33325 3.77778 6.88881 2.88889 7.99992 2Z" stroke="#0A0A0A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 806 B |
@@ -37,7 +37,15 @@ $spacing-xl: 20px;
|
|||||||
$spacing-xxl: 24px;
|
$spacing-xxl: 24px;
|
||||||
|
|
||||||
// ============ 按钮样式 ============
|
// ============ 按钮样式 ============
|
||||||
|
.btn-default {
|
||||||
|
background: #409eff;
|
||||||
|
border: none;
|
||||||
|
border-radius: $border-radius-medium;
|
||||||
|
color: $color-bg-white;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
// 主要操作按钮
|
// 主要操作按钮
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -188,7 +196,7 @@ $spacing-xxl: 24px;
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: $spacing-md 0;
|
padding: $spacing-md 0;
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
background: #F9FAFB;
|
// background: #F9FAFB;
|
||||||
border-radius: $border-radius-large;
|
border-radius: $border-radius-large;
|
||||||
|
|
||||||
// Element Plus 分页组件自定义样式
|
// Element Plus 分页组件自定义样式
|
||||||
|
|||||||
@@ -178,6 +178,79 @@ export interface ResourceRecommend extends BaseDTO {
|
|||||||
status?: number;
|
status?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源推荐VO(平铺结构,包含资源信息和权限信息)
|
||||||
|
*/
|
||||||
|
export interface ResourceRecommendVO extends BaseDTO {
|
||||||
|
// ==================== 推荐表字段 ====================
|
||||||
|
/** 推荐ID */
|
||||||
|
id?: string;
|
||||||
|
/** 资源ID */
|
||||||
|
resourceID?: string;
|
||||||
|
/** 推荐类型(1-热门资源,2-思政资源) */
|
||||||
|
recommendType?: number;
|
||||||
|
/** 排序号 */
|
||||||
|
orderNum?: number;
|
||||||
|
/** 推荐理由 */
|
||||||
|
reason?: string;
|
||||||
|
/** 推荐创建者 */
|
||||||
|
creator?: string;
|
||||||
|
/** 推荐更新者 */
|
||||||
|
updater?: string;
|
||||||
|
/** 推荐创建时间 */
|
||||||
|
createTime?: string;
|
||||||
|
/** 推荐更新时间 */
|
||||||
|
updateTime?: string;
|
||||||
|
/** 推荐删除时间 */
|
||||||
|
deleteTime?: string;
|
||||||
|
/** 推荐是否删除 */
|
||||||
|
deleted?: boolean;
|
||||||
|
|
||||||
|
// ==================== 资源表字段 ====================
|
||||||
|
/** 资源标题 */
|
||||||
|
title?: string;
|
||||||
|
/** 资源简介 */
|
||||||
|
summary?: string;
|
||||||
|
/** 封面图片 */
|
||||||
|
coverImage?: string;
|
||||||
|
/** 标签ID(文章分类标签,tagType=1) */
|
||||||
|
tagID?: string;
|
||||||
|
/** 作者 */
|
||||||
|
author?: string;
|
||||||
|
/** 来源 */
|
||||||
|
source?: string;
|
||||||
|
/** 来源URL */
|
||||||
|
sourceUrl?: string;
|
||||||
|
/** 浏览次数 */
|
||||||
|
viewCount?: number;
|
||||||
|
/** 点赞次数 */
|
||||||
|
likeCount?: number;
|
||||||
|
/** 收藏次数 */
|
||||||
|
collectCount?: number;
|
||||||
|
/** 状态(0草稿 1已发布 2下架) */
|
||||||
|
status?: number;
|
||||||
|
/** 是否推荐 */
|
||||||
|
isRecommend?: boolean;
|
||||||
|
/** 是否轮播 */
|
||||||
|
isBanner?: boolean;
|
||||||
|
/** 发布时间 */
|
||||||
|
publishTime?: string;
|
||||||
|
/** 资源创建者 */
|
||||||
|
resourceCreator?: string;
|
||||||
|
/** 资源更新者 */
|
||||||
|
resourceUpdater?: string;
|
||||||
|
/** 资源创建时间 */
|
||||||
|
resourceCreateTime?: string;
|
||||||
|
/** 资源更新时间 */
|
||||||
|
resourceUpdateTime?: string;
|
||||||
|
|
||||||
|
// ==================== 权限字段 ====================
|
||||||
|
/** 是否可读 */
|
||||||
|
canRead?: boolean;
|
||||||
|
/** 是否可写 */
|
||||||
|
canWrite?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据采集配置实体
|
* 数据采集配置实体
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { BaseDTO } from '@/types';
|
import type { BaseDTO } from '@/types';
|
||||||
|
import type { Tag } from '@/types/resource';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -344,6 +345,8 @@ export interface TaskVO extends BaseDTO {
|
|||||||
taskResources: TaskItemVO[];
|
taskResources: TaskItemVO[];
|
||||||
/** 任务关联的用户列表 */
|
/** 任务关联的用户列表 */
|
||||||
taskUsers: TaskItemVO[];
|
taskUsers: TaskItemVO[];
|
||||||
|
/** 任务关联的标签列表 */
|
||||||
|
taskTags?: Tag[];
|
||||||
/** 总任务数 */
|
/** 总任务数 */
|
||||||
totalTaskNum?: number;
|
totalTaskNum?: number;
|
||||||
/** 已完成任务数 */
|
/** 已完成任务数 */
|
||||||
|
|||||||
@@ -20,6 +20,67 @@ export interface UserCollection extends BaseDTO {
|
|||||||
collectionValue?: number;
|
collectionValue?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户收藏VO - 扁平化收藏信息和关联资源/课程详情
|
||||||
|
*/
|
||||||
|
export interface UserCollectionVO extends BaseDTO {
|
||||||
|
// ========== 收藏基本信息 ==========
|
||||||
|
/** 用户ID */
|
||||||
|
userID?: string;
|
||||||
|
/** 收藏类型(1资源 2课程) */
|
||||||
|
collectionType?: CollectionType;
|
||||||
|
/** 收藏对象ID */
|
||||||
|
collectionID?: string;
|
||||||
|
/** 是否新增收藏(1是 -1否) */
|
||||||
|
collectionValue?: number;
|
||||||
|
/** 收藏时间 */
|
||||||
|
collectionTime?: string;
|
||||||
|
|
||||||
|
// ========== 资源详情(collectionType=1时有效) ==========
|
||||||
|
/** 资源ID */
|
||||||
|
resourceID?: string;
|
||||||
|
/** 资源标题 */
|
||||||
|
title?: string;
|
||||||
|
/** 资源内容 */
|
||||||
|
content?: string;
|
||||||
|
/** 资源简介 */
|
||||||
|
summary?: string;
|
||||||
|
/** 封面图片 */
|
||||||
|
coverImage?: string;
|
||||||
|
/** 标签ID */
|
||||||
|
tagID?: string;
|
||||||
|
/** 作者 */
|
||||||
|
author?: string;
|
||||||
|
/** 来源 */
|
||||||
|
source?: string;
|
||||||
|
/** 浏览次数 */
|
||||||
|
viewCount?: number;
|
||||||
|
/** 点赞次数 */
|
||||||
|
likeCount?: number;
|
||||||
|
/** 收藏次数 */
|
||||||
|
collectCount?: number;
|
||||||
|
/** 资源状态 */
|
||||||
|
status?: number;
|
||||||
|
/** 发布时间 */
|
||||||
|
publishTime?: string;
|
||||||
|
|
||||||
|
// ========== 课程详情(collectionType=2时有效) ==========
|
||||||
|
/** 课程ID */
|
||||||
|
courseID?: string;
|
||||||
|
/** 课程名称 */
|
||||||
|
courseName?: string;
|
||||||
|
/** 课程描述 */
|
||||||
|
description?: string;
|
||||||
|
/** 课程时长(分钟) */
|
||||||
|
duration?: number;
|
||||||
|
/** 授课老师 */
|
||||||
|
teacher?: string;
|
||||||
|
/** 课程状态 */
|
||||||
|
courseStatus?: number;
|
||||||
|
/** 学习人数 */
|
||||||
|
learnCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户浏览记录实体
|
* 用户浏览记录实体
|
||||||
*/
|
*/
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -13,10 +13,17 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 标签卡片网格 -->
|
<!-- 按类型分类展示 -->
|
||||||
<div v-loading="loading" class="tag-grid">
|
<div class="tag-categories" v-loading="loading">
|
||||||
|
<!-- 文章分类标签 -->
|
||||||
|
<div class="tag-category-section">
|
||||||
|
<div class="category-header">
|
||||||
|
<h3 class="category-title">文章分类标签</h3>
|
||||||
|
<span class="category-count">({{ articleTags.length }})</span>
|
||||||
|
</div>
|
||||||
|
<div class="tag-grid">
|
||||||
<div
|
<div
|
||||||
v-for="tag in tags"
|
v-for="tag in articleTags"
|
||||||
:key="tag.tagID"
|
:key="tag.tagID"
|
||||||
class="tag-card"
|
class="tag-card"
|
||||||
>
|
>
|
||||||
@@ -25,9 +32,7 @@
|
|||||||
<div class="tag-dot" :style="{ background: tag.color }"></div>
|
<div class="tag-dot" :style="{ background: tag.color }"></div>
|
||||||
<span class="tag-name">{{ tag.name }}</span>
|
<span class="tag-name">{{ tag.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="tag-badge">{{ tag.usageCount || 0 }}</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tag-actions">
|
<div class="tag-actions">
|
||||||
<button class="edit-button" @click="editTag(tag)">
|
<button class="edit-button" @click="editTag(tag)">
|
||||||
<img src="@/assets/imgs/edit.svg" alt="编辑" />
|
<img src="@/assets/imgs/edit.svg" alt="编辑" />
|
||||||
@@ -38,9 +43,85 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<div v-if="!loading && tags.length === 0" class="empty-state">
|
<div v-if="!loading && articleTags.length === 0" class="empty-state">
|
||||||
|
暂无文章分类标签
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 课程分类标签 -->
|
||||||
|
<div class="tag-category-section">
|
||||||
|
<div class="category-header">
|
||||||
|
<h3 class="category-title">课程分类标签</h3>
|
||||||
|
<span class="category-count">({{ courseTags.length }})</span>
|
||||||
|
</div>
|
||||||
|
<div class="tag-grid">
|
||||||
|
<div
|
||||||
|
v-for="tag in courseTags"
|
||||||
|
:key="tag.tagID"
|
||||||
|
class="tag-card"
|
||||||
|
>
|
||||||
|
<div class="tag-content">
|
||||||
|
<div class="tag-info">
|
||||||
|
<div class="tag-dot" :style="{ background: tag.color }"></div>
|
||||||
|
<span class="tag-name">{{ tag.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tag-actions">
|
||||||
|
<button class="edit-button" @click="editTag(tag)">
|
||||||
|
<img src="@/assets/imgs/edit.svg" alt="编辑" />
|
||||||
|
<span>编辑</span>
|
||||||
|
</button>
|
||||||
|
<button class="delete-button" @click="deleteTag(tag)">
|
||||||
|
<img src="@/assets/imgs/trashbin.svg" alt="删除" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-if="!loading && courseTags.length === 0" class="empty-state">
|
||||||
|
暂无课程分类标签
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 学习任务分类标签 -->
|
||||||
|
<div class="tag-category-section">
|
||||||
|
<div class="category-header">
|
||||||
|
<h3 class="category-title">学习任务分类标签</h3>
|
||||||
|
<span class="category-count">({{ taskTags.length }})</span>
|
||||||
|
</div>
|
||||||
|
<div class="tag-grid">
|
||||||
|
<div
|
||||||
|
v-for="tag in taskTags"
|
||||||
|
:key="tag.tagID"
|
||||||
|
class="tag-card"
|
||||||
|
>
|
||||||
|
<div class="tag-content">
|
||||||
|
<div class="tag-info">
|
||||||
|
<div class="tag-dot" :style="{ background: tag.color }"></div>
|
||||||
|
<span class="tag-name">{{ tag.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tag-actions">
|
||||||
|
<button class="edit-button" @click="editTag(tag)">
|
||||||
|
<img src="@/assets/imgs/edit.svg" alt="编辑" />
|
||||||
|
<span>编辑</span>
|
||||||
|
</button>
|
||||||
|
<button class="delete-button" @click="deleteTag(tag)">
|
||||||
|
<img src="@/assets/imgs/trashbin.svg" alt="删除" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-if="!loading && taskTags.length === 0" class="empty-state">
|
||||||
|
暂无学习任务分类标签
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 全局空状态 -->
|
||||||
|
<div v-if="!loading && tags.length === 0" class="global-empty-state">
|
||||||
暂无标签数据
|
暂无标签数据
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,10 +173,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
|
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
|
||||||
import { resourceTagApi } from '@/apis/resource';
|
import { resourceTagApi } from '@/apis/resource';
|
||||||
import type { Tag } from '@/types/resource';
|
import type { Tag } from '@/types/resource';
|
||||||
|
import { TagType } from '@/types/resource';
|
||||||
import {AdminLayout} from '@/views/admin';
|
import {AdminLayout} from '@/views/admin';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -116,6 +198,19 @@ const currentTag = ref<Partial<Tag>>({
|
|||||||
description: ''
|
description: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 按类型分类的标签
|
||||||
|
const articleTags = computed(() => {
|
||||||
|
return tags.value.filter(tag => tag.tagType === TagType.ARTICLE_CATEGORY);
|
||||||
|
});
|
||||||
|
|
||||||
|
const courseTags = computed(() => {
|
||||||
|
return tags.value.filter(tag => tag.tagType === TagType.COURSE_CATEGORY);
|
||||||
|
});
|
||||||
|
|
||||||
|
const taskTags = computed(() => {
|
||||||
|
return tags.value.filter(tag => tag.tagType === TagType.LEARNING_TASK_CATEGORY);
|
||||||
|
});
|
||||||
|
|
||||||
const rules: FormRules = {
|
const rules: FormRules = {
|
||||||
name: [
|
name: [
|
||||||
{ required: true, message: '请输入标签名称', trigger: 'blur' }
|
{ required: true, message: '请输入标签名称', trigger: 'blur' }
|
||||||
@@ -136,7 +231,7 @@ onMounted(() => {
|
|||||||
async function loadTags() {
|
async function loadTags() {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const result = await resourceTagApi.getTagList();
|
const result = await resourceTagApi.getTagList({});
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
tags.value = result.dataList || [];
|
tags.value = result.dataList || [];
|
||||||
// TODO: 加载每个标签的使用计数
|
// TODO: 加载每个标签的使用计数
|
||||||
@@ -292,11 +387,48 @@ function handleDialogClose() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag-categories {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-category-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 2px solid #F0F0F0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-title {
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Inter', 'PingFang SC', sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.5em;
|
||||||
|
color: #101828;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-count {
|
||||||
|
font-family: 'Inter', 'PingFang SC', sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5em;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
.tag-grid {
|
.tag-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
min-height: 200px;
|
min-height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-card {
|
.tag-card {
|
||||||
@@ -419,6 +551,15 @@ function handleDialogClose() {
|
|||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.global-empty-state {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 100px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
.color-picker-wrapper {
|
.color-picker-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -102,7 +102,7 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div class="pagination-wrapper" v-if="total > 0">
|
<div class="pagination-container" v-if="total > 0">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="pageParam.pageNumber"
|
v-model:current-page="pageParam.pageNumber"
|
||||||
v-model:page-size="pageParam.pageSize"
|
v-model:page-size="pageParam.pageSize"
|
||||||
@@ -431,12 +431,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-content {
|
.detail-content {
|
||||||
.detail-item {
|
.detail-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -154,11 +154,11 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div class="pagination-wrapper" v-if="total > 0">
|
<div class="pagination-container" v-if="total > 0">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="pageParam.pageNumber"
|
v-model:current-page="pageParam.pageNumber"
|
||||||
v-model:page-size="pageParam.pageSize"
|
v-model:page-size="pageParam.pageSize"
|
||||||
:page-sizes="[9, 18, 36]"
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
:total="total"
|
:total="total"
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@@ -671,12 +671,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-content {
|
.form-content {
|
||||||
.form-item {
|
.form-item {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|||||||
@@ -127,7 +127,7 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div class="pagination-wrapper" v-if="total > 0">
|
<div class="pagination-container" v-if="total > 0">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="pageParam.pageNumber"
|
v-model:current-page="pageParam.pageNumber"
|
||||||
v-model:page-size="pageParam.pageSize"
|
v-model:page-size="pageParam.pageSize"
|
||||||
@@ -593,12 +593,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-content {
|
.form-content {
|
||||||
.form-item {
|
.form-item {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|||||||
@@ -1,27 +1,56 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="article-card">
|
<div class="article-card" @click="handleClick">
|
||||||
<div class="article-image">
|
<div class="article-image">
|
||||||
<div class="image-placeholder"></div>
|
<img v-if="resource?.coverImage" :src="FILE_DOWNLOAD_URL + resource.coverImage" :alt="resource.title" />
|
||||||
|
<div v-else class="image-placeholder"></div>
|
||||||
<div class="article-tag">精选文章</div>
|
<div class="article-tag">精选文章</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="article-content">
|
<div class="article-content">
|
||||||
<h3 class="article-title">新时代中国特色社会主义发展历程</h3>
|
<h3 class="article-title">{{ resource?.title || '标题' }}</h3>
|
||||||
<p class="article-desc">
|
<p class="article-desc">
|
||||||
习近平新时代中国特色社会主义思想是当代中国马克思主义、二十一世纪马克思主义,是中华文化和中国精神的时代精华,其核心要义与实践要求内涵丰富、意义深远。
|
{{ resource?.summary || '暂无简介' }}
|
||||||
</p>
|
</p>
|
||||||
<div class="article-footer">
|
<div class="article-footer">
|
||||||
<div class="meta-tag">
|
<div class="meta-tag">
|
||||||
<el-icon><Document /></el-icon>
|
<el-icon><Document /></el-icon>
|
||||||
<span>热门文章</span>
|
<span>热门文章</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="view-count">2.1w次浏览</span>
|
<span class="view-count">{{ formatViewCount(resource?.viewCount || 0) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
import { Document } from '@element-plus/icons-vue';
|
import { Document } from '@element-plus/icons-vue';
|
||||||
|
import type { ResourceRecommendVO } from '@/types';
|
||||||
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
resource?: ResourceRecommendVO;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 格式化浏览量
|
||||||
|
function formatViewCount(count: number): string {
|
||||||
|
if (count < 1000) {
|
||||||
|
return `${count}次浏览`;
|
||||||
|
} else if (count < 10000) {
|
||||||
|
return `${(count / 1000).toFixed(1)}k次浏览`;
|
||||||
|
} else {
|
||||||
|
return `${(count / 10000).toFixed(1)}w次浏览`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击卡片
|
||||||
|
function handleClick() {
|
||||||
|
if (props.resource?.resourceID) {
|
||||||
|
router.push(`/article/show?articleId=${props.resource.resourceID}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -47,6 +76,12 @@ import { Document } from '@element-plus/icons-vue';
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
.image-placeholder {
|
.image-placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,22 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="ideological-card">
|
<div class="ideological-card" @click="handleClick">
|
||||||
<div class="card-image">
|
<div class="card-image">
|
||||||
<div class="image-placeholder"></div>
|
<img v-if="resource?.coverImage" :src="FILE_DOWNLOAD_URL + resource.coverImage" :alt="resource.title" />
|
||||||
|
<div v-else class="image-placeholder"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="date-box">
|
<div class="date-box" v-if="publishDate">
|
||||||
<div class="day">10</div>
|
<div class="day">{{ publishDate.day }}</div>
|
||||||
<div class="month">2025.10</div>
|
<div class="month">{{ publishDate.month }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<h3 class="card-title">学校召开"习近平新时代中国特色社会主义思想概论"课程集体备课会</h3>
|
<h3 class="card-title">{{ resource?.title || '标题' }}</h3>
|
||||||
<p class="card-desc">
|
<p class="card-desc">
|
||||||
深入贯彻习近平总书记关于思政课建设的重要论述,持续推进思政课教学改革创新。
|
{{ resource?.summary || '暂无简介' }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import type { ResourceRecommendVO } from '@/types';
|
||||||
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
resource?: ResourceRecommendVO;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 格式化发布日期
|
||||||
|
const publishDate = computed(() => {
|
||||||
|
if (!props.resource?.publishTime) return null;
|
||||||
|
|
||||||
|
const date = new Date(props.resource.publishTime);
|
||||||
|
const day = date.getDate();
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = date.getMonth() + 1;
|
||||||
|
|
||||||
|
return {
|
||||||
|
day: day.toString(),
|
||||||
|
month: `${year}.${month.toString().padStart(2, '0')}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点击卡片
|
||||||
|
function handleClick() {
|
||||||
|
if (props.resource?.resourceID) {
|
||||||
|
router.push(`/article/show?articleId=${props.resource.resourceID}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -42,6 +75,12 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
.image-placeholder {
|
.image-placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -59,9 +98,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.date-box {
|
.date-box {
|
||||||
position: absolute;
|
margin: 0 5.7%;
|
||||||
top: calc(57.55% - 3.5em);
|
transform: translateY(-50%);
|
||||||
left: 5.7%;
|
|
||||||
width: 18.75%;
|
width: 18.75%;
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
background: #C62828;
|
background: #C62828;
|
||||||
@@ -73,7 +111,6 @@
|
|||||||
gap: 0.3em;
|
gap: 0.3em;
|
||||||
padding: 0.4em 0.3em;
|
padding: 0.4em 0.3em;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
.day {
|
.day {
|
||||||
font-family: 'PingFang SC';
|
font-family: 'PingFang SC';
|
||||||
@@ -97,7 +134,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-content {
|
.card-content {
|
||||||
padding: 17.4% 5.7% 5.7% 5.7%;
|
padding: 0 5.7% 5.7% 5.7%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -645,8 +645,7 @@ defineExpose({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// 路由页面模式样式
|
// 路由页面模式样式
|
||||||
.article-page-view {
|
.article-page-view {
|
||||||
min-height: 100vh;
|
// background: #f5f7fa;
|
||||||
background: #f5f7fa;
|
|
||||||
padding-bottom: 60px;
|
padding-bottom: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,7 +674,7 @@ defineExpose({
|
|||||||
padding: 40px 24px;
|
padding: 40px 24px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
// box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-container {
|
.error-container {
|
||||||
|
|||||||
@@ -60,6 +60,30 @@
|
|||||||
<span v-if="errors.endTime" class="error-msg">{{ errors.endTime }}</span>
|
<span v-if="errors.endTime" class="error-msg">{{ errors.endTime }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">任务分类标签</label>
|
||||||
|
<select
|
||||||
|
v-model="selectedTagID"
|
||||||
|
class="form-input form-select"
|
||||||
|
@change="handleTagChange"
|
||||||
|
>
|
||||||
|
<option value="">请选择分类标签(可选)</option>
|
||||||
|
<option
|
||||||
|
v-for="tag in availableTags"
|
||||||
|
:key="tag.tagID"
|
||||||
|
:value="tag.tagID"
|
||||||
|
>
|
||||||
|
{{ tag.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div v-if="selectedTag" class="selected-tag-preview">
|
||||||
|
<div class="tag-badge" :style="{ backgroundColor: selectedTag.color || '#409eff' }">
|
||||||
|
{{ selectedTag.name }}
|
||||||
|
</div>
|
||||||
|
<span v-if="selectedTag.description" class="tag-hint">{{ selectedTag.description }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 选择课程 -->
|
<!-- 选择课程 -->
|
||||||
@@ -273,6 +297,7 @@
|
|||||||
@confirm="handleUserSelectConfirm"
|
@confirm="handleUserSelectConfirm"
|
||||||
@cancel="showUserSelector = false"
|
@cancel="showUserSelector = false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -283,9 +308,10 @@ import { courseApi } from '@/apis/study';
|
|||||||
import { resourceApi } from '@/apis/resource';
|
import { resourceApi } from '@/apis/resource';
|
||||||
import { userApi } from '@/apis/system';
|
import { userApi } from '@/apis/system';
|
||||||
import { learningTaskApi } from '@/apis/study';
|
import { learningTaskApi } from '@/apis/study';
|
||||||
|
import { resourceTagApi } from '@/apis/resource';
|
||||||
import { GenericSelector } from '@/components/base';
|
import { GenericSelector } from '@/components/base';
|
||||||
import type { TaskVO, Course, TaskItemVO } from '@/types/study';
|
import type { TaskVO, Course, TaskItemVO } from '@/types/study';
|
||||||
import type { Resource } from '@/types/resource';
|
import type { Resource, Tag } from '@/types/resource';
|
||||||
import type { SysUser } from '@/types/user';
|
import type { SysUser } from '@/types/user';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -331,11 +357,14 @@ const errors = ref({
|
|||||||
const selectedCourses = ref<Course[]>([]);
|
const selectedCourses = ref<Course[]>([]);
|
||||||
const selectedResources = ref<Resource[]>([]);
|
const selectedResources = ref<Resource[]>([]);
|
||||||
const selectedUsers = ref<SysUser[]>([]);
|
const selectedUsers = ref<SysUser[]>([]);
|
||||||
|
const selectedTagID = ref<string>('');
|
||||||
|
const selectedTag = ref<Tag | null>(null);
|
||||||
|
|
||||||
// 可选数据
|
// 可选数据
|
||||||
const availableCourses = ref<Course[]>([]);
|
const availableCourses = ref<Course[]>([]);
|
||||||
const availableResources = ref<Resource[]>([]);
|
const availableResources = ref<Resource[]>([]);
|
||||||
const availableUsers = ref<SysUser[]>([]);
|
const availableUsers = ref<SysUser[]>([]);
|
||||||
|
const availableTags = ref<Tag[]>([]);
|
||||||
|
|
||||||
// 弹窗控制
|
// 弹窗控制
|
||||||
const showCourseSelector = ref(false);
|
const showCourseSelector = ref(false);
|
||||||
@@ -350,6 +379,7 @@ const resourceSearchKeyword = ref('');
|
|||||||
const courseLoading = ref(false);
|
const courseLoading = ref(false);
|
||||||
const resourceLoading = ref(false);
|
const resourceLoading = ref(false);
|
||||||
const userLoading = ref(false);
|
const userLoading = ref(false);
|
||||||
|
const tagLoading = ref(false);
|
||||||
const submitting = ref(false);
|
const submitting = ref(false);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -357,7 +387,8 @@ onMounted(async () => {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
loadCourses(),
|
loadCourses(),
|
||||||
loadResources(),
|
loadResources(),
|
||||||
loadUsers()
|
loadUsers(),
|
||||||
|
loadTags()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 如果是编辑模式,加载任务数据并恢复选择
|
// 如果是编辑模式,加载任务数据并恢复选择
|
||||||
@@ -398,6 +429,13 @@ async function loadTask() {
|
|||||||
const userIds = taskData.value.taskUsers.map(tu => tu.userID);
|
const userIds = taskData.value.taskUsers.map(tu => tu.userID);
|
||||||
selectedUsers.value = availableUsers.value.filter(u => userIds.includes(u.id));
|
selectedUsers.value = availableUsers.value.filter(u => userIds.includes(u.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 恢复标签选择
|
||||||
|
if (taskData.value.taskTags && taskData.value.taskTags.length > 0) {
|
||||||
|
const firstTag = taskData.value.taskTags[0];
|
||||||
|
selectedTagID.value = firstTag.tagID || '';
|
||||||
|
selectedTag.value = firstTag;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载任务失败:', error);
|
console.error('加载任务失败:', error);
|
||||||
@@ -671,6 +709,31 @@ function removeUser(index: number) {
|
|||||||
selectedUsers.value.splice(index, 1);
|
selectedUsers.value.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载标签列表
|
||||||
|
async function loadTags() {
|
||||||
|
tagLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await resourceTagApi.getTagList({ tagType: 3 }); // 获取所有标签
|
||||||
|
if (res.success && res.dataList) {
|
||||||
|
// 只保留 tagType 为 3 的学习任务分类标签
|
||||||
|
availableTags.value = res.dataList.filter(tag => tag.tagType === 3);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载标签失败:', error);
|
||||||
|
} finally {
|
||||||
|
tagLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理标签选择变化
|
||||||
|
function handleTagChange() {
|
||||||
|
if (selectedTagID.value) {
|
||||||
|
selectedTag.value = availableTags.value.find(tag => tag.tagID === selectedTagID.value) || null;
|
||||||
|
} else {
|
||||||
|
selectedTag.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 表单验证
|
// 表单验证
|
||||||
function validateTaskName() {
|
function validateTaskName() {
|
||||||
if (!taskData.value.learningTask.name) {
|
if (!taskData.value.learningTask.name) {
|
||||||
@@ -750,6 +813,9 @@ async function handleSubmit() {
|
|||||||
status: 0
|
status: 0
|
||||||
} as TaskItemVO));
|
} as TaskItemVO));
|
||||||
|
|
||||||
|
// 组装任务标签数据
|
||||||
|
taskData.value.taskTags = selectedTag.value ? [selectedTag.value] : [];
|
||||||
|
|
||||||
let res;
|
let res;
|
||||||
if (props.taskId) {
|
if (props.taskId) {
|
||||||
res = await learningTaskApi.updateTask(taskData.value);
|
res = await learningTaskApi.updateTask(taskData.value);
|
||||||
@@ -890,6 +956,38 @@ function handleCancel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-select {
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath fill='%23606266' d='M831.872 340.864 512 652.672 192.128 340.864a30.592 30.592 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728 30.592 30.592 0 0 0-42.752 0z'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 8px center;
|
||||||
|
background-size: 14px;
|
||||||
|
padding-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-tag-preview {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
|
.tag-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.form-textarea {
|
.form-textarea {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
@@ -1185,5 +1283,6 @@ function handleCancel() {
|
|||||||
color: #909399;
|
color: #909399;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -31,13 +31,24 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2 class="section-title">热门资源推荐</h2>
|
<h2 class="section-title">热门资源推荐</h2>
|
||||||
<div class="more-link">
|
<div class="more-link" @click="handleMoreClick('hot')">
|
||||||
<span>查看更多</span>
|
<span>查看更多</span>
|
||||||
<el-icon><ArrowRight /></el-icon>
|
<el-icon><ArrowRight /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="article-grid">
|
<div v-if="hotResourcesLoading" class="loading-container">
|
||||||
<HotArticleCard v-for="item in 3" :key="item" />
|
<div class="loading-spinner"></div>
|
||||||
|
<p>加载中...</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="hotResources.length > 0" class="article-grid">
|
||||||
|
<HotArticleCard
|
||||||
|
v-for="resource in hotResources"
|
||||||
|
:key="resource.id || resource.resourceID"
|
||||||
|
:resource="resource"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="empty-container">
|
||||||
|
<p>暂无热门资源</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -45,13 +56,24 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2 class="section-title">思政新闻概览</h2>
|
<h2 class="section-title">思政新闻概览</h2>
|
||||||
<div class="more-link">
|
<div class="more-link" @click="handleMoreClick('ideological')">
|
||||||
<span>查看更多</span>
|
<span>查看更多</span>
|
||||||
<el-icon><ArrowRight /></el-icon>
|
<el-icon><ArrowRight /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="article-grid">
|
<div v-if="ideologicalResourcesLoading" class="loading-container">
|
||||||
<IdeologicalArticleCard v-for="item in 3" :key="item" />
|
<div class="loading-spinner"></div>
|
||||||
|
<p>加载中...</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="ideologicalResources.length > 0" class="article-grid">
|
||||||
|
<IdeologicalArticleCard
|
||||||
|
v-for="resource in ideologicalResources"
|
||||||
|
:key="resource.id || resource.resourceID"
|
||||||
|
:resource="resource"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="empty-container">
|
||||||
|
<p>暂无思政资源</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -59,7 +81,7 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2 class="section-title">我的学习数据</h2>
|
<h2 class="section-title">我的学习数据</h2>
|
||||||
<div class="more-link">
|
<div class="more-link" @click="handleMoreClick('learning')">
|
||||||
<span>查看更多</span>
|
<span>查看更多</span>
|
||||||
<el-icon><ArrowRight /></el-icon>
|
<el-icon><ArrowRight /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,14 +98,26 @@ import { HotArticleCard, IdeologicalArticleCard } from '@/views/public/article';
|
|||||||
import { Carousel } from '@/components/base';
|
import { Carousel } from '@/components/base';
|
||||||
import { ArrowRight } from '@element-plus/icons-vue';
|
import { ArrowRight } from '@element-plus/icons-vue';
|
||||||
import { bannerApi } from '@/apis/resource/banner';
|
import { bannerApi } from '@/apis/resource/banner';
|
||||||
|
import { recommendApi } from '@/apis/homepage';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import type { Banner } from '@/types';
|
import type { Banner, ResourceRecommendVO } from '@/types';
|
||||||
import dangIcon from '@/assets/imgs/dang.svg';
|
import dangIcon from '@/assets/imgs/dang.svg';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
// 轮播数据
|
// 轮播数据
|
||||||
const banners = ref<Banner[]>([]);
|
const banners = ref<Banner[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
|
// 热门资源数据
|
||||||
|
const hotResources = ref<ResourceRecommendVO[]>([]);
|
||||||
|
const hotResourcesLoading = ref(false);
|
||||||
|
|
||||||
|
// 思政资源数据
|
||||||
|
const ideologicalResources = ref<ResourceRecommendVO[]>([]);
|
||||||
|
const ideologicalResourcesLoading = ref(false);
|
||||||
|
|
||||||
// 加载轮播图数据
|
// 加载轮播图数据
|
||||||
async function loadBanners() {
|
async function loadBanners() {
|
||||||
try {
|
try {
|
||||||
@@ -104,9 +138,55 @@ async function loadBanners() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载热门资源数据
|
||||||
|
async function loadHotResources() {
|
||||||
|
try {
|
||||||
|
hotResourcesLoading.value = true;
|
||||||
|
const result = await recommendApi.getHotResources(3);
|
||||||
|
|
||||||
|
if (result.code === 200 && result.dataList) {
|
||||||
|
hotResources.value = result.dataList;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载热门资源失败:', error);
|
||||||
|
ElMessage.error('加载热门资源失败');
|
||||||
|
} finally {
|
||||||
|
hotResourcesLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载思政资源数据
|
||||||
|
async function loadIdeologicalResources() {
|
||||||
|
try {
|
||||||
|
ideologicalResourcesLoading.value = true;
|
||||||
|
const result = await recommendApi.getIdeologicalResources(3);
|
||||||
|
|
||||||
|
if (result.code === 200 && result.dataList) {
|
||||||
|
ideologicalResources.value = result.dataList;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载思政资源失败:', error);
|
||||||
|
ElMessage.error('加载思政资源失败');
|
||||||
|
} finally {
|
||||||
|
ideologicalResourcesLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMoreClick(type: string) {
|
||||||
|
if (type === 'hot') {
|
||||||
|
router.push('/resource-hot');
|
||||||
|
} else if (type === 'ideological') {
|
||||||
|
router.push('/resource-center');
|
||||||
|
} else if (type === 'learning') {
|
||||||
|
router.push('/learning-center');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 组件挂载时加载数据
|
// 组件挂载时加载数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadBanners();
|
loadBanners();
|
||||||
|
loadHotResources();
|
||||||
|
loadIdeologicalResources();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -208,5 +288,34 @@ onMounted(() => {
|
|||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-container,
|
||||||
|
.empty-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 60px 0;
|
||||||
|
min-height: 200px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 16px;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid #e4e7ed;
|
||||||
|
border-top-color: #E7000B;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
716
schoolNewsWeb/src/views/user/resource-center/HotResourceView.vue
Normal file
716
schoolNewsWeb/src/views/user/resource-center/HotResourceView.vue
Normal file
@@ -0,0 +1,716 @@
|
|||||||
|
<template>
|
||||||
|
<div class="hot-resource-view">
|
||||||
|
<div class="hot-resource-view-head">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="header-left">
|
||||||
|
<button class="back-button" @click="goBack">
|
||||||
|
<el-icon>
|
||||||
|
<ArrowLeft />
|
||||||
|
</el-icon>
|
||||||
|
<span>返回</span>
|
||||||
|
</button>
|
||||||
|
<div class="header-info">
|
||||||
|
<h1 class="page-title">
|
||||||
|
<img src="@/assets/imgs/hot.svg" alt="热门" class="title-icon" />
|
||||||
|
热门文章
|
||||||
|
</h1>
|
||||||
|
<p class="page-desc">根据浏览量为您推荐最受欢迎的文章内容</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 排序和筛选 -->
|
||||||
|
<div class="filter-controls">
|
||||||
|
<!-- <el-select :model-value="sortType" @change="handleSortChange" placeholder="排序方式" style="width: 150px;">
|
||||||
|
<el-option label="浏览量最多" value="viewCount" />
|
||||||
|
<el-option label="点赞最多" value="likeCount" />
|
||||||
|
<el-option label="收藏最多" value="collectCount" />
|
||||||
|
<el-option label="最新发布" value="publishTime" />
|
||||||
|
</el-select> -->
|
||||||
|
|
||||||
|
<el-select :model-value="selectedTagID" @change="handleTagChange" placeholder="文章分类" clearable
|
||||||
|
style="width: 150px;">
|
||||||
|
<el-option label="全部分类" :value="''" />
|
||||||
|
<el-option v-for="tag in articleTags" :key="tag.tagID" :label="tag.name"
|
||||||
|
:value="tag.tagID || ''" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 统计信息 -->
|
||||||
|
<div class="stats-bar">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">共找到</span>
|
||||||
|
<span class="stat-value">{{ total }}</span>
|
||||||
|
<span class="stat-label">篇热门文章</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-divider"></div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">总浏览量</span>
|
||||||
|
<span class="stat-value">{{ formatNumber(totalViews) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 页面头部 -->
|
||||||
|
|
||||||
|
<!-- 文章列表 -->
|
||||||
|
<div v-loading="loading" class="articles-container">
|
||||||
|
<div class="articles-grid">
|
||||||
|
<div v-for="(article, index) in articles" :key="article.resourceID" class="article-card"
|
||||||
|
:class="{ 'top-rank': index < 3 }" @click="handleArticleClick(article)">
|
||||||
|
<!-- 排名徽章 -->
|
||||||
|
<div v-if="index < 3" class="rank-badge" :class="`rank-${index + 1}`">
|
||||||
|
<span>{{ index + 1 }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-else class="rank-number">{{ (currentPage - 1) * pageSize + index + 1 }}</div>
|
||||||
|
|
||||||
|
<!-- 文章封面 -->
|
||||||
|
<div class="article-cover">
|
||||||
|
<img v-if="article.coverImage" :src="FILE_DOWNLOAD_URL + article.coverImage"
|
||||||
|
:alt="article.title" />
|
||||||
|
<div v-else class="cover-placeholder">
|
||||||
|
<el-icon>
|
||||||
|
<Document />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="cover-overlay">
|
||||||
|
<span class="view-button">查看详情</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文章信息 -->
|
||||||
|
<div class="article-info">
|
||||||
|
|
||||||
|
<h3 class="article-title" :title="article.title">{{ article.title }}</h3>
|
||||||
|
|
||||||
|
<!-- 标签 -->
|
||||||
|
<div v-if="article.tagID" class="article-tag">
|
||||||
|
{{ getTagName(article.tagID) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 简介 -->
|
||||||
|
<p class="article-summary">{{ article.summary || '暂无简介' }}</p>
|
||||||
|
|
||||||
|
<!-- 底部元信息 -->
|
||||||
|
<div class="article-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<img src="@/assets/imgs/hot.svg" alt="浏览" class="meta-icon" />
|
||||||
|
<span>{{ formatNumber(article.viewCount) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<el-icon>
|
||||||
|
<Star />
|
||||||
|
</el-icon>
|
||||||
|
<span>{{ formatNumber(article.likeCount) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<el-icon>
|
||||||
|
<Star />
|
||||||
|
</el-icon>
|
||||||
|
<span>{{ formatNumber(article.collectCount) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item author">
|
||||||
|
<el-icon>
|
||||||
|
<User />
|
||||||
|
</el-icon>
|
||||||
|
<span>{{ article.author || '未知' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 发布时间 -->
|
||||||
|
<div class="article-time">
|
||||||
|
{{ formatDate(article.publishTime) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-if="!loading && articles.length === 0" class="empty-state">
|
||||||
|
<el-icon class="empty-icon">
|
||||||
|
<Document />
|
||||||
|
</el-icon>
|
||||||
|
<p>暂无热门文章</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div v-if="total > 0" class="pagination-container">
|
||||||
|
<el-pagination :current-page="currentPage" :page-size="pageSize" :total="total"
|
||||||
|
:page-sizes="[9, 18, 27, 36]" layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="handleSizeChange" @current-change="handlePageChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { ElMessage, ElIcon, ElSelect, ElOption, ElPagination } from 'element-plus';
|
||||||
|
import { ArrowLeft, Document, Star, User } from '@element-plus/icons-vue';
|
||||||
|
import { resourceApi, resourceTagApi } from '@/apis/resource';
|
||||||
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
|
import type { Resource, Tag, ResourceSearchParams, PageParam } from '@/types';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'HotResourceView'
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 数据状态
|
||||||
|
const articles = ref<Resource[]>([]);
|
||||||
|
const articleTags = ref<Tag[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const total = ref(0);
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const pageSize = ref(9);
|
||||||
|
|
||||||
|
// 筛选和排序
|
||||||
|
const sortType = ref<'viewCount' | 'likeCount' | 'collectCount' | 'publishTime'>('viewCount');
|
||||||
|
const selectedTagID = ref<string>('');
|
||||||
|
|
||||||
|
// 计算总浏览量
|
||||||
|
const totalViews = computed(() => {
|
||||||
|
return articles.value.reduce((sum, article) => sum + (article.viewCount || 0), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面挂载
|
||||||
|
onMounted(() => {
|
||||||
|
loadTags();
|
||||||
|
loadArticles();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载标签列表
|
||||||
|
async function loadTags() {
|
||||||
|
try {
|
||||||
|
const result = await resourceTagApi.getTagList({ tagType: 1 }); // 1-文章分类标签
|
||||||
|
if (result.success && result.dataList) {
|
||||||
|
articleTags.value = result.dataList;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载标签失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载热门文章
|
||||||
|
async function loadArticles() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const filter: ResourceSearchParams = {
|
||||||
|
status: 1, // 只显示已发布的
|
||||||
|
tagID: selectedTagID.value || undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
const pageParam: PageParam = {
|
||||||
|
pageNumber: currentPage.value,
|
||||||
|
pageSize: pageSize.value
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await resourceApi.getResourcePage(pageParam, filter);
|
||||||
|
|
||||||
|
if (result.success && result.pageDomain?.dataList) {
|
||||||
|
// 根据选择的排序方式进行排序
|
||||||
|
let sortedArticles = [...result.pageDomain.dataList];
|
||||||
|
|
||||||
|
switch (sortType.value) {
|
||||||
|
case 'viewCount':
|
||||||
|
sortedArticles.sort((a, b) => (b.viewCount || 0) - (a.viewCount || 0));
|
||||||
|
break;
|
||||||
|
case 'likeCount':
|
||||||
|
sortedArticles.sort((a, b) => (b.likeCount || 0) - (a.likeCount || 0));
|
||||||
|
break;
|
||||||
|
case 'collectCount':
|
||||||
|
sortedArticles.sort((a, b) => (b.collectCount || 0) - (a.collectCount || 0));
|
||||||
|
break;
|
||||||
|
case 'publishTime':
|
||||||
|
sortedArticles.sort((a, b) => {
|
||||||
|
const timeA = a.publishTime ? new Date(a.publishTime).getTime() : 0;
|
||||||
|
const timeB = b.publishTime ? new Date(b.publishTime).getTime() : 0;
|
||||||
|
return timeB - timeA;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
articles.value = sortedArticles;
|
||||||
|
total.value = result.pageDomain.pageParam?.totalElements || 0;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载热门文章失败:', error);
|
||||||
|
ElMessage.error('加载热门文章失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取标签名称
|
||||||
|
function getTagName(tagID: string): string {
|
||||||
|
const tag = articleTags.value.find(t => t.tagID === tagID);
|
||||||
|
return tag?.name || '未知分类';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化数字
|
||||||
|
function formatNumber(num?: number): string {
|
||||||
|
if (num === undefined || num === null) return '0';
|
||||||
|
if (num < 1000) return num.toString();
|
||||||
|
if (num < 10000) return `${(num / 1000).toFixed(1)}k`;
|
||||||
|
return `${(num / 10000).toFixed(1)}w`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
function formatDate(dateStr?: string): string {
|
||||||
|
if (!dateStr) return '未知时间';
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
const now = new Date();
|
||||||
|
const diff = now.getTime() - date.getTime();
|
||||||
|
|
||||||
|
// 小于1小时
|
||||||
|
if (diff < 3600000) {
|
||||||
|
const minutes = Math.floor(diff / 60000);
|
||||||
|
return `${minutes}分钟前`;
|
||||||
|
}
|
||||||
|
// 小于1天
|
||||||
|
if (diff < 86400000) {
|
||||||
|
const hours = Math.floor(diff / 3600000);
|
||||||
|
return `${hours}小时前`;
|
||||||
|
}
|
||||||
|
// 小于7天
|
||||||
|
if (diff < 604800000) {
|
||||||
|
const days = Math.floor(diff / 86400000);
|
||||||
|
return `${days}天前`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则显示完整日期
|
||||||
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理排序变化
|
||||||
|
function handleSortChange(value: string) {
|
||||||
|
sortType.value = value as typeof sortType.value;
|
||||||
|
currentPage.value = 1;
|
||||||
|
loadArticles();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理标签筛选
|
||||||
|
function handleTagChange(value: string) {
|
||||||
|
selectedTagID.value = value;
|
||||||
|
currentPage.value = 1;
|
||||||
|
loadArticles();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理分页变化
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
currentPage.value = page;
|
||||||
|
loadArticles();
|
||||||
|
// 滚动到顶部
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理每页大小变化
|
||||||
|
function handleSizeChange(size: number) {
|
||||||
|
pageSize.value = size;
|
||||||
|
currentPage.value = 1;
|
||||||
|
loadArticles();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击文章卡片
|
||||||
|
function handleArticleClick(article: Resource) {
|
||||||
|
if (article.resourceID) {
|
||||||
|
// 增加浏览次数
|
||||||
|
resourceApi.incrementViewCount(article.resourceID);
|
||||||
|
// 跳转到文章详情页
|
||||||
|
router.push(`/article/show?articleId=${article.resourceID}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
function goBack() {
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.hot-resource-view {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(180deg, #FEF2F2 0%, #F9F9F9 100%);
|
||||||
|
padding: 0 0 20px 0;
|
||||||
|
|
||||||
|
.hot-resource-view-head {
|
||||||
|
height: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面头部
|
||||||
|
.page-header {
|
||||||
|
background: #FFFFFF;
|
||||||
|
padding: 5px 0;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
// margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #F9FAFB;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #4A5565;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #F3F4F6;
|
||||||
|
color: #E7000B;
|
||||||
|
border-color: #E7000B;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #101828;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-desc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6B7280;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计信息栏
|
||||||
|
.stats-bar {
|
||||||
|
|
||||||
|
margin: 0 auto 24px;
|
||||||
|
padding: 0 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6B7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #E7000B;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文章容器
|
||||||
|
.articles-container {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 24px;
|
||||||
|
height: 80%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.articles-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文章卡片
|
||||||
|
.article-card {
|
||||||
|
position: relative;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: calc((100% - 32px) / 3);
|
||||||
|
height: 50%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 24px rgba(231, 0, 11, 0.15);
|
||||||
|
|
||||||
|
.cover-overlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.top-rank {
|
||||||
|
border: 2px solid;
|
||||||
|
|
||||||
|
&.article-card:nth-child(1) {
|
||||||
|
border-color: #FFD700;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.article-card:nth-child(2) {
|
||||||
|
border-color: #C0C0C0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.article-card:nth-child(3) {
|
||||||
|
border-color: #CD7F32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排名徽章
|
||||||
|
.rank-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #FFFFFF;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
&.rank-1 {
|
||||||
|
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.rank-2 {
|
||||||
|
background: linear-gradient(135deg, #C0C0C0 0%, #A8A8A8 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.rank-3 {
|
||||||
|
background: linear-gradient(135deg, #CD7F32 0%, #B8722B 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-number {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #FFFFFF;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文章封面
|
||||||
|
.article-cover {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 45%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #F3F4F6;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-card:hover & img {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 48px;
|
||||||
|
color: #9CA3AF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.7) 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
|
||||||
|
.view-button {
|
||||||
|
padding: 8px 20px;
|
||||||
|
background: #E7000B;
|
||||||
|
color: #FFFFFF;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文章信息
|
||||||
|
.article-info {
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
height: 50%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #101828;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.4;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-self: flex-start;
|
||||||
|
padding: 4px 12px;
|
||||||
|
background: #FEF2F2;
|
||||||
|
border: 1px solid #FCA5A5;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #E7000B;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-summary {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6B7280;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #F3F4F6;
|
||||||
|
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6B7280;
|
||||||
|
|
||||||
|
.meta-icon {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.author {
|
||||||
|
margin-left: auto;
|
||||||
|
max-width: 80px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-time {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #9CA3AF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 空状态
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 80px 20px;
|
||||||
|
color: #9CA3AF;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 64px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
.pagination-container {
|
||||||
|
height: 5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -41,6 +41,7 @@ import { ref } from 'vue';
|
|||||||
import { ResourceSideBar, ResourceList, ResourceArticle } from './components';
|
import { ResourceSideBar, ResourceList, ResourceArticle } from './components';
|
||||||
import { Search, CenterHead } from '@/components/base';
|
import { Search, CenterHead } from '@/components/base';
|
||||||
import type { Resource, Tag } from '@/types/resource';
|
import type { Resource, Tag } from '@/types/resource';
|
||||||
|
import { resourceApi } from '@/apis/resource';
|
||||||
|
|
||||||
const showArticle = ref(false);
|
const showArticle = ref(false);
|
||||||
const currentCategoryId = ref('tag_article_001');
|
const currentCategoryId = ref('tag_article_001');
|
||||||
@@ -67,6 +68,8 @@ function handleListUpdated(list: Resource[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleResourceClick(resource: Resource) {
|
function handleResourceClick(resource: Resource) {
|
||||||
|
// 增加浏览次数
|
||||||
|
resourceApi.incrementViewCount(resource.resourceID || '');
|
||||||
currentResourceId.value = resource.resourceID || '';
|
currentResourceId.value = resource.resourceID || '';
|
||||||
showArticle.value = true;
|
showArticle.value = true;
|
||||||
}
|
}
|
||||||
@@ -100,6 +103,7 @@ async function handleArticleNavigate(direction: 'prev' | 'next', resourceId: str
|
|||||||
.resource-center-view {
|
.resource-center-view {
|
||||||
background: #F9F9F9;
|
background: #F9F9F9;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-wrapper {
|
.search-wrapper {
|
||||||
@@ -138,7 +142,6 @@ async function handleArticleNavigate(direction: 'prev' | 'next', resourceId: str
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-bottom: 60px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-container {
|
.content-container {
|
||||||
|
|||||||
@@ -26,9 +26,10 @@ import { ref, watch } from 'vue';
|
|||||||
import { ArticleShow } from '@/views/public/article';
|
import { ArticleShow } from '@/views/public/article';
|
||||||
import { ResouceCollect, ResouceBottom } from '@/views/user/resource-center/components';
|
import { ResouceCollect, ResouceBottom } from '@/views/user/resource-center/components';
|
||||||
import { resourceApi } from '@/apis/resource';
|
import { resourceApi } from '@/apis/resource';
|
||||||
|
import { userCollectionApi } from '@/apis/usercenter';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import type { Resource } from '@/types/resource';
|
import type { Resource } from '@/types/resource';
|
||||||
import { CollectionType, type UserCollection } from '@/types';
|
import { CollectionType, ResultDomain, type UserCollection } from '@/types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
resourceId?: string;
|
resourceId?: string;
|
||||||
@@ -110,15 +111,15 @@ async function handleCollect(type: number) {
|
|||||||
collectionID: resourceID,
|
collectionID: resourceID,
|
||||||
collectionValue: type
|
collectionValue: type
|
||||||
}
|
}
|
||||||
const res = await resourceApi.resourceCollect(collect);
|
let res: ResultDomain<UserCollection> | null = null;
|
||||||
if (res.success) {
|
|
||||||
if (type === 1) {
|
if (type === 1) {
|
||||||
isCollected.value = true;
|
res = await userCollectionApi.addCollection(collect);
|
||||||
ElMessage.success('收藏成功');
|
} else {
|
||||||
} else if (type === -1) {
|
res = await userCollectionApi.removeCollection(collect);
|
||||||
isCollected.value = false;
|
|
||||||
ElMessage.success('已取消收藏');
|
|
||||||
}
|
}
|
||||||
|
if (res && res.success) {
|
||||||
|
isCollected.value = type === 1;
|
||||||
|
ElMessage.success(type === 1 ? '收藏成功' : '已取消收藏');
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(type === 1 ? '收藏失败' : '取消收藏失败');
|
ElMessage.error(type === 1 ? '收藏失败' : '取消收藏失败');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,14 @@
|
|||||||
<div v-if="resources.length === 0 && !loading" class="empty">暂无数据</div>
|
<div v-if="resources.length === 0 && !loading" class="empty">暂无数据</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="total > 0" class="pagination-wrapper">
|
<div v-if="total > 0" class="pagination-container">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="currentPage"
|
v-model:current-page="currentPage"
|
||||||
:page-size="pageSize"
|
:page-size="pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
layout="prev, pager, next, jumper"
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
@current-change="handlePageChange"
|
@current-change="handlePageChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,7 +65,7 @@ const resources = ref<Resource[]>([]);
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const currentPage = ref(1);
|
const currentPage = ref(1);
|
||||||
const pageSize = 10;
|
const pageSize = ref(10);
|
||||||
const listContainerRef = ref<HTMLElement>();
|
const listContainerRef = ref<HTMLElement>();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -89,7 +91,7 @@ async function loadResources() {
|
|||||||
|
|
||||||
const pageParam: PageParam = {
|
const pageParam: PageParam = {
|
||||||
pageNumber: currentPage.value,
|
pageNumber: currentPage.value,
|
||||||
pageSize: pageSize
|
pageSize: pageSize.value
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await resourceApi.getResourcePage(pageParam, filter);
|
const res = await resourceApi.getResourcePage(pageParam, filter);
|
||||||
@@ -119,6 +121,12 @@ function handlePageChange(page: number) {
|
|||||||
loadResources();
|
loadResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleSizeChange(size: number) {
|
||||||
|
pageSize.value = size;
|
||||||
|
currentPage.value = 1;
|
||||||
|
loadResources();
|
||||||
|
}
|
||||||
|
|
||||||
function getResources() {
|
function getResources() {
|
||||||
return resources.value;
|
return resources.value;
|
||||||
}
|
}
|
||||||
@@ -128,7 +136,7 @@ function getPageInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadNextPage() {
|
async function loadNextPage() {
|
||||||
const totalPages = Math.ceil(total.value / pageSize);
|
const totalPages = Math.ceil(total.value / pageSize.value);
|
||||||
if (currentPage.value < totalPages) {
|
if (currentPage.value < totalPages) {
|
||||||
currentPage.value++;
|
currentPage.value++;
|
||||||
await loadResources();
|
await loadResources();
|
||||||
|
|||||||
@@ -243,13 +243,15 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.my-achievements {
|
.my-achievements {
|
||||||
padding: 20px 0;
|
// padding: 20px 0;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
.achievements-header {
|
.achievements-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 32px;
|
height: 10%;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
@@ -262,7 +264,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.achievement-stats {
|
.achievement-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 32px;
|
gap: 20px;
|
||||||
|
|
||||||
.stat-item {
|
.stat-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -289,10 +291,10 @@ onMounted(() => {
|
|||||||
|
|
||||||
.filter-tabs {
|
.filter-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
height: 5%;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 24px;
|
margin: 10px;
|
||||||
padding: 16px;
|
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
@@ -303,6 +305,8 @@ onMounted(() => {
|
|||||||
|
|
||||||
.achievements-grid {
|
.achievements-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
height: 80%;
|
||||||
|
overflow-y: auto;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div
|
<div
|
||||||
class="filter-tab"
|
class="filter-tab"
|
||||||
v-for="filter in filters"
|
v-for="filter in filters"
|
||||||
:key="filter.key"
|
:key="String(filter.key)"
|
||||||
:class="{ active: activeFilter === filter.key }"
|
:class="{ active: activeFilter === filter.key }"
|
||||||
@click="activeFilter = filter.key"
|
@click="activeFilter = filter.key"
|
||||||
>
|
>
|
||||||
@@ -15,17 +15,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="favorites-grid">
|
<div v-loading="loading" class="favorites-grid">
|
||||||
<div class="favorite-item" v-for="item in filteredFavorites" :key="item.id">
|
<div class="favorite-item" v-for="item in filteredFavorites" :key="item.id">
|
||||||
<div class="item-thumbnail">
|
<div class="item-thumbnail">
|
||||||
<img :src="item.thumbnail" :alt="item.title" />
|
<img v-if="getThumbnail(item)" :src="getThumbnail(item)" :alt="getTitle(item)" />
|
||||||
<div class="item-type">{{ item.typeName }}</div>
|
<div v-else class="thumbnail-placeholder">
|
||||||
|
<el-icon :size="48"><Document /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="item-type">{{ getTypeName(item) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-info">
|
<div class="item-info">
|
||||||
<h3>{{ item.title }}</h3>
|
<h3>{{ getTitle(item) }}</h3>
|
||||||
<p class="item-summary">{{ item.summary }}</p>
|
<p class="item-summary">{{ getSummary(item) }}</p>
|
||||||
<div class="item-footer">
|
<div class="item-footer">
|
||||||
<span class="item-date">收藏于 {{ item.favoriteDate }}</span>
|
<span class="item-date">收藏于 {{ formatDate(item.collectionTime) }}</span>
|
||||||
<div class="item-actions">
|
<div class="item-actions">
|
||||||
<el-button size="small" @click="viewItem(item)">查看</el-button>
|
<el-button size="small" @click="viewItem(item)">查看</el-button>
|
||||||
<el-button size="small" type="danger" @click="removeFavorite(item)">取消收藏</el-button>
|
<el-button size="small" type="danger" @click="removeFavorite(item)">取消收藏</el-button>
|
||||||
@@ -33,41 +36,173 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-if="!loading && filteredFavorites.length === 0" class="empty-state">
|
||||||
|
<el-icon :size="64" class="empty-icon"><Star /></el-icon>
|
||||||
|
<p>暂无收藏内容</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import { ElButton, ElMessage } from 'element-plus';
|
import { useStore } from 'vuex';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { ElButton, ElMessage, ElMessageBox, ElIcon } from 'element-plus';
|
||||||
|
import { Document, Star } from '@element-plus/icons-vue';
|
||||||
|
import { userCollectionApi } from '@/apis/usercenter';
|
||||||
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
|
import type { UserCollectionVO } from '@/types';
|
||||||
|
import { CollectionType } from '@/types/enums';
|
||||||
|
|
||||||
const activeFilter = ref('all');
|
defineOptions({
|
||||||
const favorites = ref<any[]>([]);
|
name: 'MyFavoritesView'
|
||||||
|
});
|
||||||
|
|
||||||
const filters = [
|
const store = useStore();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const activeFilter = ref<'all' | number>('all');
|
||||||
|
const favorites = ref<UserCollectionVO[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const filters: Array<{ key: 'all' | number; label: string }> = [
|
||||||
{ key: 'all', label: '全部' },
|
{ key: 'all', label: '全部' },
|
||||||
{ key: 'article', label: '文章' },
|
{ key: CollectionType.RESOURCE, label: '资源' },
|
||||||
{ key: 'video', label: '视频' },
|
{ key: CollectionType.COURSE, label: '课程' }
|
||||||
{ key: 'audio', label: '音频' },
|
|
||||||
{ key: 'course', label: '课程' }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const filteredFavorites = computed(() => {
|
const filteredFavorites = computed(() => {
|
||||||
if (activeFilter.value === 'all') return favorites.value;
|
if (activeFilter.value === 'all') return favorites.value;
|
||||||
return favorites.value.filter(item => item.type === activeFilter.value);
|
return favorites.value.filter(item => item.collectionType === activeFilter.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 获取显示标题
|
||||||
|
const getTitle = (item: UserCollectionVO): string => {
|
||||||
|
if (item.collectionType === CollectionType.RESOURCE) {
|
||||||
|
return item.title || '未命名资源';
|
||||||
|
} else if (item.collectionType === CollectionType.COURSE) {
|
||||||
|
return item.courseName || '未命名课程';
|
||||||
|
}
|
||||||
|
return '未知类型';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取显示简介
|
||||||
|
const getSummary = (item: UserCollectionVO): string => {
|
||||||
|
if (item.collectionType === CollectionType.RESOURCE) {
|
||||||
|
return item.summary || '暂无简介';
|
||||||
|
} else if (item.collectionType === CollectionType.COURSE) {
|
||||||
|
return item.description || '暂无简介';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取显示缩略图
|
||||||
|
const getThumbnail = (item: UserCollectionVO): string => {
|
||||||
|
if (item.coverImage) {
|
||||||
|
return `${FILE_DOWNLOAD_URL}${item.coverImage}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取类型名称
|
||||||
|
const getTypeName = (item: UserCollectionVO): string => {
|
||||||
|
if (item.collectionType === CollectionType.RESOURCE) {
|
||||||
|
return '资源';
|
||||||
|
} else if (item.collectionType === CollectionType.COURSE) {
|
||||||
|
return '课程';
|
||||||
|
}
|
||||||
|
return '未知';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前用户ID
|
||||||
|
const currentUser = computed(() => store.getters['auth/user']);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// TODO: 加载收藏数据
|
loadFavorites();
|
||||||
});
|
});
|
||||||
|
|
||||||
function viewItem(item: any) {
|
// 加载收藏列表
|
||||||
// TODO: 跳转到详情页
|
async function loadFavorites() {
|
||||||
|
if (!currentUser.value?.id) {
|
||||||
|
ElMessage.warning('请先登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const result = await userCollectionApi.getUserCollections(currentUser.value.id);
|
||||||
|
|
||||||
|
if (result.success && result.dataList) {
|
||||||
|
// 后端已返回扁平化的VO,包含详情,无需再次查询
|
||||||
|
favorites.value = result.dataList;
|
||||||
|
} else {
|
||||||
|
favorites.value = [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载收藏列表失败:', error);
|
||||||
|
ElMessage.error('加载收藏列表失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFavorite(item: any) {
|
// 查看详情
|
||||||
// TODO: 取消收藏
|
function viewItem(item: UserCollectionVO) {
|
||||||
|
if (item.collectionType === CollectionType.RESOURCE && item.resourceID) {
|
||||||
|
router.push(`/article/show?articleId=${item.resourceID}`);
|
||||||
|
} else if (item.collectionType === CollectionType.COURSE && item.courseID) {
|
||||||
|
// TODO: 跳转到课程详情页
|
||||||
|
ElMessage.info('课程详情页开发中');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消收藏
|
||||||
|
async function removeFavorite(item: UserCollectionVO) {
|
||||||
|
if (!currentUser.value?.id || !item.collectionType || !item.collectionID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
'确定要取消收藏吗?',
|
||||||
|
'提示',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await userCollectionApi.removeCollection(
|
||||||
|
{
|
||||||
|
collectionType: item.collectionType,
|
||||||
|
collectionID: item.collectionID
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
ElMessage.success('已取消收藏');
|
ElMessage.success('已取消收藏');
|
||||||
|
// 从列表中移除
|
||||||
|
favorites.value = favorites.value.filter(f => f.id !== item.id);
|
||||||
|
} else {
|
||||||
|
ElMessage.error(result.message || '取消收藏失败');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
console.error('取消收藏失败:', error);
|
||||||
|
ElMessage.error('取消收藏失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
function formatDate(dateStr?: string): string {
|
||||||
|
if (!dateStr) return '';
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -172,6 +307,7 @@ function removeFavorite(item: any) {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -193,5 +329,35 @@ function removeFavorite(item: any) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thumbnail-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 80px 20px;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -51,24 +51,27 @@ const menus = computed(() => {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.user-center-page {
|
.user-center-page {
|
||||||
min-height: 100vh;
|
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
height: 100%;
|
||||||
|
// overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-card-wrapper {
|
.user-card-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 25%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1200px;
|
|
||||||
|
height: 75%;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ onMounted(async () => {
|
|||||||
.user-card {
|
.user-card {
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
max-width: 1200px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 190px;
|
min-height: 190px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
Reference in New Issue
Block a user