文章、课程标签的默认封面
This commit is contained in:
@@ -31,9 +31,7 @@ CREATE TABLE `tb_learning_task_tag` (
|
|||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `uk_task_tag` (`task_id`, `tag_id`),
|
UNIQUE KEY `uk_task_tag` (`task_id`, `tag_id`),
|
||||||
KEY `idx_task_id` (`task_id`),
|
KEY `idx_task_id` (`task_id`),
|
||||||
KEY `idx_tag_id` (`tag_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='学习任务标签关联表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='学习任务标签关联表';
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,22 @@ CREATE TABLE `tb_tag` (
|
|||||||
KEY `idx_tag_type` (`tag_type`)
|
KEY `idx_tag_type` (`tag_type`)
|
||||||
) 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_tag_default_cover`;
|
||||||
|
CREATE TABLE IF NOT EXISTS `tb_tag_default_cover` (
|
||||||
|
`id` VARCHAR(32) NOT NULL COMMENT '主键ID',
|
||||||
|
`tag_id` VARCHAR(32) NOT NULL COMMENT '标签ID',
|
||||||
|
`cover_image` VARCHAR(255) NOT NULL COMMENT '封面图片fileID',
|
||||||
|
`order_num` INT DEFAULT 0 COMMENT '排序号',
|
||||||
|
`is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用:0-禁用,1-启用',
|
||||||
|
`creator` VARCHAR(32) DEFAULT NULL COMMENT '创建人',
|
||||||
|
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updater` VARCHAR(32) DEFAULT NULL COMMENT '更新人',
|
||||||
|
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
INDEX `idx_tag_id` (`tag_id`),
|
||||||
|
INDEX `idx_active` (`is_active`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签默认封面配置表';
|
||||||
|
|
||||||
-- 资源标签关联表
|
-- 资源标签关联表
|
||||||
DROP TABLE IF EXISTS `tb_resource_tag`;
|
DROP TABLE IF EXISTS `tb_resource_tag`;
|
||||||
CREATE TABLE `tb_resource_tag` (
|
CREATE TABLE `tb_resource_tag` (
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.xyzh.api.news.tag;
|
||||||
|
|
||||||
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
|
import org.xyzh.common.dto.resource.TbTagDefaultCover;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 标签默认封面服务接口
|
||||||
|
* @author system
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
public interface TagDefaultCoverService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据标签ID获取启用的默认封面列表
|
||||||
|
* @param tagID 标签ID
|
||||||
|
* @return 默认封面列表
|
||||||
|
*/
|
||||||
|
ResultDomain<TbTagDefaultCover> getDefaultCovers(String tagID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据标签ID获取所有默认封面列表(包括禁用的)
|
||||||
|
* @param tagID 标签ID
|
||||||
|
* @return 默认封面列表
|
||||||
|
*/
|
||||||
|
ResultDomain<TbTagDefaultCover> getAllDefaultCovers(String tagID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加默认封面
|
||||||
|
* @param cover 默认封面对象
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
ResultDomain<String> addDefaultCover(TbTagDefaultCover cover);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新默认封面
|
||||||
|
* @param cover 默认封面对象
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
ResultDomain<String> updateDefaultCover(TbTagDefaultCover cover);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除默认封面
|
||||||
|
* @param id 主键ID
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
ResultDomain<String> deleteDefaultCover(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加默认封面
|
||||||
|
* @param covers 默认封面列表
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
ResultDomain<String> batchAddDefaultCovers(List<TbTagDefaultCover> covers);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 根据标签ID删除所有默认封面
|
||||||
|
* @param tagID 标签ID
|
||||||
|
* @return ResultDomain<String> 操作结果
|
||||||
|
* @author system
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
ResultDomain<String> deleteDefaultCoversByTagId(String tagID);
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package org.xyzh.common.dto.resource;
|
||||||
|
|
||||||
|
import org.xyzh.common.dto.BaseDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 标签默认封面配置表
|
||||||
|
* @filename TbTagDefaultCover.java
|
||||||
|
* @author system
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
public class TbTagDefaultCover extends BaseDTO {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签ID
|
||||||
|
*/
|
||||||
|
private String tagID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封面图片fileID
|
||||||
|
*/
|
||||||
|
private String coverImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序号
|
||||||
|
*/
|
||||||
|
private Integer orderNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用:0-禁用,1-启用
|
||||||
|
*/
|
||||||
|
private Integer isActive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建人
|
||||||
|
*/
|
||||||
|
private String creator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新人
|
||||||
|
*/
|
||||||
|
private String updater;
|
||||||
|
|
||||||
|
public String getTagID() {
|
||||||
|
return tagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTagID(String tagID) {
|
||||||
|
this.tagID = tagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCoverImage() {
|
||||||
|
return coverImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCoverImage(String coverImage) {
|
||||||
|
this.coverImage = coverImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getOrderNum() {
|
||||||
|
return orderNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderNum(Integer orderNum) {
|
||||||
|
this.orderNum = orderNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getIsActive() {
|
||||||
|
return isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsActive(Integer isActive) {
|
||||||
|
this.isActive = isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TbTagDefaultCover{" +
|
||||||
|
"id='" + getId() + '\'' +
|
||||||
|
", tagID='" + tagID + '\'' +
|
||||||
|
", coverImage='" + coverImage + '\'' +
|
||||||
|
", orderNum=" + orderNum +
|
||||||
|
", isActive=" + isActive +
|
||||||
|
", creator='" + creator + '\'' +
|
||||||
|
", createTime=" + getCreateTime() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,6 +69,11 @@ public class TbCourse extends BaseDTO {
|
|||||||
*/
|
*/
|
||||||
private Integer orderNum;
|
private Integer orderNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 标签ID(用于获取默认封面)
|
||||||
|
*/
|
||||||
|
private String tagID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 创建者
|
* @description 创建者
|
||||||
*/
|
*/
|
||||||
@@ -182,6 +187,14 @@ public class TbCourse extends BaseDTO {
|
|||||||
this.orderNum = orderNum;
|
this.orderNum = orderNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTagID() {
|
||||||
|
return tagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTagID(String tagID) {
|
||||||
|
this.tagID = tagID;
|
||||||
|
}
|
||||||
|
|
||||||
public String getCreator() {
|
public String getCreator() {
|
||||||
return creator;
|
return creator;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ public class CourseItemVO extends BaseDTO {
|
|||||||
*/
|
*/
|
||||||
private Integer learnCount;
|
private Integer learnCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 标签ID(用于获取默认封面和分类)
|
||||||
|
*/
|
||||||
|
private String tagID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 课程创建时间
|
* @description 课程创建时间
|
||||||
*/
|
*/
|
||||||
@@ -235,6 +240,14 @@ public class CourseItemVO extends BaseDTO {
|
|||||||
this.learnCount = learnCount;
|
this.learnCount = learnCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTagID() {
|
||||||
|
return tagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTagID(String tagID) {
|
||||||
|
this.tagID = tagID;
|
||||||
|
}
|
||||||
|
|
||||||
public Date getCreateTime() {
|
public Date getCreateTime() {
|
||||||
return createTime;
|
return createTime;
|
||||||
}
|
}
|
||||||
@@ -390,6 +403,7 @@ public class CourseItemVO extends BaseDTO {
|
|||||||
course.setId(this.getId());
|
course.setId(this.getId());
|
||||||
course.setCourseID(this.courseID);
|
course.setCourseID(this.courseID);
|
||||||
course.setName(this.name);
|
course.setName(this.name);
|
||||||
|
course.setTagID(this.tagID);
|
||||||
course.setCoverImage(this.coverImage);
|
course.setCoverImage(this.coverImage);
|
||||||
course.setDescription(this.description);
|
course.setDescription(this.description);
|
||||||
course.setDuration(this.duration);
|
course.setDuration(this.duration);
|
||||||
@@ -423,6 +437,7 @@ public class CourseItemVO extends BaseDTO {
|
|||||||
vo.setViewCount(course.getViewCount());
|
vo.setViewCount(course.getViewCount());
|
||||||
vo.setLearnCount(course.getLearnCount());
|
vo.setLearnCount(course.getLearnCount());
|
||||||
vo.setCreateTime(course.getCreateTime());
|
vo.setCreateTime(course.getCreateTime());
|
||||||
|
vo.setTagID(course.getTagID());
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.xyzh.api.news.tag.TagService;
|
import org.xyzh.api.news.tag.TagService;
|
||||||
|
import org.xyzh.api.news.tag.TagDefaultCoverService;
|
||||||
import org.xyzh.common.core.domain.ResultDomain;
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
import org.xyzh.common.dto.resource.TbTag;
|
import org.xyzh.common.dto.resource.TbTag;
|
||||||
import org.xyzh.common.dto.resource.TbResourceTag;
|
import org.xyzh.common.dto.resource.TbResourceTag;
|
||||||
|
import org.xyzh.common.dto.resource.TbTagDefaultCover;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -25,6 +27,9 @@ public class TagController {
|
|||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private TagService tagService;
|
private TagService tagService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TagDefaultCoverService tagDefaultCoverService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取标签列表
|
* 获取标签列表
|
||||||
@@ -138,4 +143,62 @@ public class TagController {
|
|||||||
public ResultDomain<String> getResourcesByTag(@PathVariable("tagID") String tagID) {
|
public ResultDomain<String> getResourcesByTag(@PathVariable("tagID") String tagID) {
|
||||||
return tagService.getResourcesByTag(tagID);
|
return tagService.getResourcesByTag(tagID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------标签默认封面相关--------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取标签的启用默认封面列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/tag/{tagID}/default-covers")
|
||||||
|
public ResultDomain<TbTagDefaultCover> getDefaultCovers(@PathVariable("tagID") String tagID) {
|
||||||
|
return tagDefaultCoverService.getDefaultCovers(tagID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取标签的所有默认封面列表(管理端使用)
|
||||||
|
*/
|
||||||
|
@GetMapping("/tag/{tagID}/all-default-covers")
|
||||||
|
public ResultDomain<TbTagDefaultCover> getAllDefaultCovers(@PathVariable("tagID") String tagID) {
|
||||||
|
return tagDefaultCoverService.getAllDefaultCovers(tagID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加默认封面
|
||||||
|
*/
|
||||||
|
@PostMapping("/default-cover")
|
||||||
|
public ResultDomain<String> addDefaultCover(@RequestBody TbTagDefaultCover cover) {
|
||||||
|
return tagDefaultCoverService.addDefaultCover(cover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新默认封面
|
||||||
|
*/
|
||||||
|
@PutMapping("/default-cover")
|
||||||
|
public ResultDomain<String> updateDefaultCover(@RequestBody TbTagDefaultCover cover) {
|
||||||
|
return tagDefaultCoverService.updateDefaultCover(cover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除默认封面
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/default-cover/{id}")
|
||||||
|
public ResultDomain<String> deleteDefaultCover(@PathVariable("id") String id) {
|
||||||
|
return tagDefaultCoverService.deleteDefaultCover(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加默认封面
|
||||||
|
*/
|
||||||
|
@PostMapping("/default-covers/batch")
|
||||||
|
public ResultDomain<String> batchAddDefaultCovers(@RequestBody List<TbTagDefaultCover> covers) {
|
||||||
|
return tagDefaultCoverService.batchAddDefaultCovers(covers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据标签ID删除所有默认封面
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/tag/{tagID}/default-covers")
|
||||||
|
public ResultDomain<String> deleteDefaultCoversByTagId(@PathVariable("tagID") String tagID) {
|
||||||
|
return tagDefaultCoverService.deleteDefaultCoversByTagId(tagID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package org.xyzh.news.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.xyzh.common.dto.resource.TbTagDefaultCover;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 标签默认封面Mapper接口
|
||||||
|
* @author system
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface TagDefaultCoverMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 根据标签ID查询默认封面列表
|
||||||
|
* @param tagId 标签ID
|
||||||
|
* @return List<TbTagDefaultCover> 默认封面列表
|
||||||
|
* @author system
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
List<TbTagDefaultCover> selectDefaultCoversByTagId(@Param("tagId") String tagId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 根据标签ID查询启用的默认封面列表
|
||||||
|
* @param tagId 标签ID
|
||||||
|
* @return List<TbTagDefaultCover> 启用的默认封面列表
|
||||||
|
* @author system
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
List<TbTagDefaultCover> selectActiveDefaultCoversByTagId(@Param("tagId") String tagId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 插入默认封面
|
||||||
|
* @param cover 默认封面对象
|
||||||
|
* @return int 影响行数
|
||||||
|
* @author system
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
int insertDefaultCover(TbTagDefaultCover cover);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 更新默认封面
|
||||||
|
* @param cover 默认封面对象
|
||||||
|
* @return int 影响行数
|
||||||
|
* @author system
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
int updateDefaultCover(TbTagDefaultCover cover);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 删除默认封面
|
||||||
|
* @param id 主键ID
|
||||||
|
* @return int 影响行数
|
||||||
|
* @author system
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
int deleteDefaultCover(@Param("id") String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 根据标签ID删除所有默认封面
|
||||||
|
* @param tagId 标签ID
|
||||||
|
* @return int 影响行数
|
||||||
|
* @author system
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
int deleteDefaultCoversByTagId(@Param("tagId") String tagId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 根据ID查询默认封面
|
||||||
|
* @param id 主键ID
|
||||||
|
* @return TbTagDefaultCover 默认封面对象
|
||||||
|
* @author system
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
TbTagDefaultCover selectDefaultCoverById(@Param("id") String id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
package org.xyzh.news.service.impl;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.xyzh.api.news.tag.TagDefaultCoverService;
|
||||||
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
|
import org.xyzh.common.dto.resource.TbTagDefaultCover;
|
||||||
|
import org.xyzh.news.mapper.TagDefaultCoverMapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 标签默认封面服务实现
|
||||||
|
* @author system
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class NCTagDefaultCoverServiceImpl implements TagDefaultCoverService {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(NCTagDefaultCoverServiceImpl.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TagDefaultCoverMapper tagDefaultCoverMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbTagDefaultCover> getDefaultCovers(String tagID) {
|
||||||
|
ResultDomain<TbTagDefaultCover> result = new ResultDomain<>();
|
||||||
|
try {
|
||||||
|
if (tagID == null || tagID.trim().isEmpty()) {
|
||||||
|
result.fail("标签ID不能为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TbTagDefaultCover> covers = tagDefaultCoverMapper.selectActiveDefaultCoversByTagId(tagID);
|
||||||
|
logger.info("获取标签[{}]的默认封面列表,数量:{}", tagID, covers.size());
|
||||||
|
|
||||||
|
result.success("获取成功", covers);
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取标签默认封面列表失败", e);
|
||||||
|
result.fail("获取默认封面列表失败:" + e.getMessage());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbTagDefaultCover> getAllDefaultCovers(String tagID) {
|
||||||
|
ResultDomain<TbTagDefaultCover> result = new ResultDomain<>();
|
||||||
|
try {
|
||||||
|
if (tagID == null || tagID.trim().isEmpty()) {
|
||||||
|
result.fail("标签ID不能为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TbTagDefaultCover> covers = tagDefaultCoverMapper.selectDefaultCoversByTagId(tagID);
|
||||||
|
logger.info("获取标签[{}]的所有默认封面列表,数量:{}", tagID, covers.size());
|
||||||
|
|
||||||
|
result.success("获取成功", covers);
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取标签所有默认封面列表失败", e);
|
||||||
|
result.fail("获取默认封面列表失败:" + e.getMessage());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public ResultDomain<String> addDefaultCover(TbTagDefaultCover cover) {
|
||||||
|
ResultDomain<String> result = new ResultDomain<>();
|
||||||
|
try {
|
||||||
|
if (cover.getTagID() == null || cover.getTagID().trim().isEmpty()) {
|
||||||
|
result.fail("标签ID不能为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (cover.getCoverImage() == null || cover.getCoverImage().trim().isEmpty()) {
|
||||||
|
result.fail("封面图片不能为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
cover.setId(UUID.randomUUID().toString().replace("-", ""));
|
||||||
|
if (cover.getIsActive() == null) {
|
||||||
|
cover.setIsActive(1);
|
||||||
|
}
|
||||||
|
if (cover.getOrderNum() == null) {
|
||||||
|
cover.setOrderNum(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rows = tagDefaultCoverMapper.insertDefaultCover(cover);
|
||||||
|
if (rows > 0) {
|
||||||
|
logger.info("添加标签默认封面成功,ID:{}", cover.getId());
|
||||||
|
result.success("添加成功", cover.getId());
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
result.fail("添加失败");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("添加标签默认封面失败", e);
|
||||||
|
result.fail("添加失败:" + e.getMessage());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public ResultDomain<String> updateDefaultCover(TbTagDefaultCover cover) {
|
||||||
|
ResultDomain<String> result = new ResultDomain<>();
|
||||||
|
try {
|
||||||
|
if (cover.getId() == null || cover.getId().trim().isEmpty()) {
|
||||||
|
result.fail("ID不能为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rows = tagDefaultCoverMapper.updateDefaultCover(cover);
|
||||||
|
if (rows > 0) {
|
||||||
|
logger.info("更新标签默认封面成功,ID:{}", cover.getId());
|
||||||
|
result.success("更新成功", cover.getId());
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
result.fail("更新失败,记录不存在");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("更新标签默认封面失败", e);
|
||||||
|
result.fail("更新失败:" + e.getMessage());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public ResultDomain<String> deleteDefaultCover(String id) {
|
||||||
|
ResultDomain<String> result = new ResultDomain<>();
|
||||||
|
try {
|
||||||
|
if (id == null || id.trim().isEmpty()) {
|
||||||
|
result.fail("ID不能为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rows = tagDefaultCoverMapper.deleteDefaultCover(id);
|
||||||
|
if (rows > 0) {
|
||||||
|
logger.info("删除标签默认封面成功,ID:{}", id);
|
||||||
|
result.success("删除成功", id);
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
result.fail("删除失败,记录不存在");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("删除标签默认封面失败", e);
|
||||||
|
result.fail("删除失败:" + e.getMessage());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public ResultDomain<String> batchAddDefaultCovers(List<TbTagDefaultCover> covers) {
|
||||||
|
ResultDomain<String> result = new ResultDomain<>();
|
||||||
|
try {
|
||||||
|
if (covers == null || covers.isEmpty()) {
|
||||||
|
result.fail("封面列表不能为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int successCount = 0;
|
||||||
|
for (TbTagDefaultCover cover : covers) {
|
||||||
|
cover.setId(UUID.randomUUID().toString().replace("-", ""));
|
||||||
|
if (cover.getIsActive() == null) {
|
||||||
|
cover.setIsActive(1);
|
||||||
|
}
|
||||||
|
if (cover.getOrderNum() == null) {
|
||||||
|
cover.setOrderNum(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rows = tagDefaultCoverMapper.insertDefaultCover(cover);
|
||||||
|
if (rows > 0) {
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("批量添加标签默认封面完成,成功:{}/总数:{}", successCount, covers.size());
|
||||||
|
result.success("批量添加成功,成功数量:" + successCount, String.valueOf(successCount));
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("批量添加标签默认封面失败", e);
|
||||||
|
result.fail("批量添加失败:" + e.getMessage());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public ResultDomain<String> deleteDefaultCoversByTagId(String tagID) {
|
||||||
|
ResultDomain<String> result = new ResultDomain<>();
|
||||||
|
try {
|
||||||
|
if (tagID == null || tagID.trim().isEmpty()) {
|
||||||
|
result.fail("标签ID不能为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rows = tagDefaultCoverMapper.deleteDefaultCoversByTagId(tagID);
|
||||||
|
logger.info("删除标签[{}]的所有默认封面成功,删除数量:{}", tagID, rows);
|
||||||
|
result.success("删除成功", String.valueOf(rows));
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("删除标签默认封面失败", e);
|
||||||
|
result.fail("删除失败:" + e.getMessage());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<?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.news.mapper.TagDefaultCoverMapper">
|
||||||
|
|
||||||
|
<!-- 基础结果映射 -->
|
||||||
|
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.resource.TbTagDefaultCover">
|
||||||
|
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||||
|
<result column="tag_id" property="tagID" jdbcType="VARCHAR"/>
|
||||||
|
<result column="cover_image" property="coverImage" jdbcType="VARCHAR"/>
|
||||||
|
<result column="order_num" property="orderNum" jdbcType="INTEGER"/>
|
||||||
|
<result column="is_active" property="isActive" jdbcType="TINYINT"/>
|
||||||
|
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||||
|
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||||
|
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||||
|
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- 基础字段 -->
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id, tag_id, cover_image, order_num, is_active, creator, create_time, updater, update_time
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 根据标签ID查询默认封面列表 -->
|
||||||
|
<select id="selectDefaultCoversByTagId" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM tb_tag_default_cover
|
||||||
|
WHERE tag_id = #{tagId}
|
||||||
|
ORDER BY order_num ASC, create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据标签ID查询启用的默认封面列表 -->
|
||||||
|
<select id="selectActiveDefaultCoversByTagId" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM tb_tag_default_cover
|
||||||
|
WHERE tag_id = #{tagId} AND is_active = 1
|
||||||
|
ORDER BY order_num ASC, create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据ID查询默认封面 -->
|
||||||
|
<select id="selectDefaultCoverById" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM tb_tag_default_cover
|
||||||
|
WHERE id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 插入默认封面 -->
|
||||||
|
<insert id="insertDefaultCover">
|
||||||
|
INSERT INTO tb_tag_default_cover
|
||||||
|
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||||
|
<if test="id != null">id,</if>
|
||||||
|
<if test="tagID != null">tag_id,</if>
|
||||||
|
<if test="coverImage != null">cover_image,</if>
|
||||||
|
<if test="orderNum != null">order_num,</if>
|
||||||
|
<if test="isActive != null">is_active,</if>
|
||||||
|
<if test="creator != null">creator,</if>
|
||||||
|
create_time,
|
||||||
|
</trim>
|
||||||
|
<trim prefix="VALUES (" suffix=")" suffixOverrides=",">
|
||||||
|
<if test="id != null">#{id},</if>
|
||||||
|
<if test="tagID != null">#{tagID},</if>
|
||||||
|
<if test="coverImage != null">#{coverImage},</if>
|
||||||
|
<if test="orderNum != null">#{orderNum},</if>
|
||||||
|
<if test="isActive != null">#{isActive},</if>
|
||||||
|
<if test="creator != null">#{creator},</if>
|
||||||
|
NOW(),
|
||||||
|
</trim>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<!-- 更新默认封面 -->
|
||||||
|
<update id="updateDefaultCover">
|
||||||
|
UPDATE tb_tag_default_cover
|
||||||
|
<set>
|
||||||
|
<if test="coverImage != null">cover_image = #{coverImage},</if>
|
||||||
|
<if test="orderNum != null">order_num = #{orderNum},</if>
|
||||||
|
<if test="isActive != null">is_active = #{isActive},</if>
|
||||||
|
<if test="updater != null">updater = #{updater},</if>
|
||||||
|
update_time = NOW()
|
||||||
|
</set>
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!-- 删除默认封面 -->
|
||||||
|
<delete id="deleteDefaultCover">
|
||||||
|
DELETE FROM tb_tag_default_cover WHERE id = #{id}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<!-- 根据标签ID删除所有默认封面 -->
|
||||||
|
<delete id="deleteDefaultCoversByTagId">
|
||||||
|
DELETE FROM tb_tag_default_cover WHERE tag_id = #{tagId}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -22,13 +22,15 @@ import org.xyzh.common.dto.resource.TbResource;
|
|||||||
import org.xyzh.common.dto.study.TbCourse;
|
import org.xyzh.common.dto.study.TbCourse;
|
||||||
import org.xyzh.common.dto.study.TbCourseChapter;
|
import org.xyzh.common.dto.study.TbCourseChapter;
|
||||||
import org.xyzh.common.dto.study.TbCourseNode;
|
import org.xyzh.common.dto.study.TbCourseNode;
|
||||||
|
import org.xyzh.common.dto.study.TbCourseTag;
|
||||||
import org.xyzh.common.dto.user.TbSysUser;
|
import org.xyzh.common.dto.user.TbSysUser;
|
||||||
import org.xyzh.common.utils.IDUtils;
|
import org.xyzh.common.utils.IDUtils;
|
||||||
import org.xyzh.common.vo.CourseItemVO;
|
import org.xyzh.common.vo.CourseItemVO;
|
||||||
import org.xyzh.common.vo.ResourceVO;
|
import org.xyzh.common.vo.ResourceVO;
|
||||||
import org.xyzh.study.mapper.CourseMapper;
|
|
||||||
import org.xyzh.study.mapper.CourseChapterMapper;
|
import org.xyzh.study.mapper.CourseChapterMapper;
|
||||||
|
import org.xyzh.study.mapper.CourseMapper;
|
||||||
import org.xyzh.study.mapper.CourseNodeMapper;
|
import org.xyzh.study.mapper.CourseNodeMapper;
|
||||||
|
import org.xyzh.study.mapper.CourseTagMapper;
|
||||||
import org.xyzh.study.service.SCCourseService;
|
import org.xyzh.study.service.SCCourseService;
|
||||||
import org.xyzh.system.utils.LoginUtil;
|
import org.xyzh.system.utils.LoginUtil;
|
||||||
import org.xyzh.api.news.resource.ResourceAuditService;
|
import org.xyzh.api.news.resource.ResourceAuditService;
|
||||||
@@ -58,6 +60,9 @@ public class SCCourseServiceImpl implements SCCourseService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private CourseNodeMapper courseNodeMapper;
|
private CourseNodeMapper courseNodeMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CourseTagMapper courseTagMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ResourceAuditService auditService;
|
private ResourceAuditService auditService;
|
||||||
|
|
||||||
@@ -231,6 +236,22 @@ public class SCCourseServiceImpl implements SCCourseService {
|
|||||||
logger.error("创建课程权限异常,但不影响课程创建: {}", e.getMessage(), e);
|
logger.error("创建课程权限异常,但不影响课程创建: {}", e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理课程标签关联
|
||||||
|
if (courseItemVO.getTagID() != null && !courseItemVO.getTagID().isEmpty()) {
|
||||||
|
try {
|
||||||
|
TbCourseTag courseTag = new TbCourseTag();
|
||||||
|
courseTag.setId(IDUtils.generateID());
|
||||||
|
courseTag.setCourseID(courseID);
|
||||||
|
courseTag.setTagID(courseItemVO.getTagID());
|
||||||
|
courseTag.setCreator(user.getId());
|
||||||
|
courseTag.setCreateTime(now);
|
||||||
|
courseTagMapper.insertCourseTag(courseTag);
|
||||||
|
logger.info("创建课程标签关联成功: courseID={}, tagID={}", courseID, courseItemVO.getTagID());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("创建课程标签关联失败: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resultDomain.success("创建课程成功", courseItemVO);
|
resultDomain.success("创建课程成功", courseItemVO);
|
||||||
return resultDomain;
|
return resultDomain;
|
||||||
}
|
}
|
||||||
@@ -259,6 +280,34 @@ public class SCCourseServiceImpl implements SCCourseService {
|
|||||||
course.setUpdateTime(now);
|
course.setUpdateTime(now);
|
||||||
courseMapper.updateCourse(course);
|
courseMapper.updateCourse(course);
|
||||||
|
|
||||||
|
// 1.5. 处理课程标签关联
|
||||||
|
if (courseItemVO.getTagID() != null && !courseItemVO.getTagID().isEmpty()) {
|
||||||
|
try {
|
||||||
|
// 先删除旧的标签关联
|
||||||
|
courseTagMapper.deleteByCourseId(courseID);
|
||||||
|
|
||||||
|
// 创建新的标签关联
|
||||||
|
TbCourseTag courseTag = new TbCourseTag();
|
||||||
|
courseTag.setId(IDUtils.generateID());
|
||||||
|
courseTag.setCourseID(courseID);
|
||||||
|
courseTag.setTagID(courseItemVO.getTagID());
|
||||||
|
courseTag.setCreator(user.getId());
|
||||||
|
courseTag.setCreateTime(now);
|
||||||
|
courseTagMapper.insertCourseTag(courseTag);
|
||||||
|
logger.info("更新课程标签关联成功: courseID={}, tagID={}", courseID, courseItemVO.getTagID());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("更新课程标签关联失败: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有tagID,删除所有标签关联
|
||||||
|
try {
|
||||||
|
courseTagMapper.deleteByCourseId(courseID);
|
||||||
|
logger.info("清空课程标签关联: courseID={}", courseID);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("清空课程标签关联失败: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 处理章节和节点
|
// 2. 处理章节和节点
|
||||||
List<CourseItemVO> newChapterVOs = courseItemVO.getChapters();
|
List<CourseItemVO> newChapterVOs = courseItemVO.getChapters();
|
||||||
if (newChapterVOs == null) {
|
if (newChapterVOs == null) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
<result column="view_count" property="viewCount" jdbcType="INTEGER"/>
|
<result column="view_count" property="viewCount" jdbcType="INTEGER"/>
|
||||||
<result column="learn_count" property="learnCount" jdbcType="INTEGER"/>
|
<result column="learn_count" property="learnCount" jdbcType="INTEGER"/>
|
||||||
<result column="order_num" property="orderNum" jdbcType="INTEGER"/>
|
<result column="order_num" property="orderNum" jdbcType="INTEGER"/>
|
||||||
|
<result column="tag_id" property="tagID" jdbcType="VARCHAR"/>
|
||||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||||
@@ -26,9 +27,9 @@
|
|||||||
|
|
||||||
<!-- 基础字段 -->
|
<!-- 基础字段 -->
|
||||||
<sql id="Base_Column_List">
|
<sql id="Base_Column_List">
|
||||||
id, course_id, name, cover_image, description, content, duration,
|
c.id, c.course_id, c.name, c.cover_image, c.description, c.content, c.duration,
|
||||||
teacher, status, view_count, learn_count, order_num, creator, updater,
|
c.teacher, c.status, c.view_count, c.learn_count, c.order_num, ct.tag_id, c.creator, c.updater,
|
||||||
create_time, update_time, delete_time, deleted
|
c.create_time, c.update_time, c.delete_time, c.deleted
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<!-- 通用条件 -->
|
<!-- 通用条件 -->
|
||||||
@@ -111,8 +112,9 @@
|
|||||||
|
|
||||||
<!-- selectCourses - 添加权限过滤 -->
|
<!-- selectCourses - 添加权限过滤 -->
|
||||||
<select id="selectCourses" resultMap="BaseResultMap">
|
<select id="selectCourses" resultMap="BaseResultMap">
|
||||||
SELECT DISTINCT c.*
|
SELECT DISTINCT <include refid="Base_Column_List" />
|
||||||
FROM tb_course c
|
FROM tb_course c
|
||||||
|
LEFT JOIN tb_course_tag ct ON c.course_id = ct.course_id
|
||||||
<include refid="Permission_Filter"/>
|
<include refid="Permission_Filter"/>
|
||||||
WHERE c.deleted = 0
|
WHERE c.deleted = 0
|
||||||
<if test="filter.courseID != null and filter.courseID != ''">
|
<if test="filter.courseID != null and filter.courseID != ''">
|
||||||
@@ -134,8 +136,9 @@
|
|||||||
<select id="selectByCourseId" resultMap="BaseResultMap">
|
<select id="selectByCourseId" resultMap="BaseResultMap">
|
||||||
SELECT
|
SELECT
|
||||||
<include refid="Base_Column_List" />
|
<include refid="Base_Column_List" />
|
||||||
FROM tb_course
|
FROM tb_course c
|
||||||
WHERE course_id = #{courseId} AND deleted = 0
|
LEFT JOIN tb_course_tag ct ON c.course_id = ct.course_id
|
||||||
|
WHERE c.course_id = #{courseId} AND c.deleted = 0
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 根据课程名称查询课程 -->
|
<!-- 根据课程名称查询课程 -->
|
||||||
@@ -316,8 +319,9 @@
|
|||||||
<!-- 分页查询课程 -->
|
<!-- 分页查询课程 -->
|
||||||
<!-- selectCoursesPage - 添加权限过滤 -->
|
<!-- selectCoursesPage - 添加权限过滤 -->
|
||||||
<select id="selectCoursesPage" resultMap="BaseResultMap">
|
<select id="selectCoursesPage" resultMap="BaseResultMap">
|
||||||
SELECT DISTINCT c.*
|
SELECT DISTINCT <include refid="Base_Column_List" />
|
||||||
FROM tb_course c
|
FROM tb_course c
|
||||||
|
LEFT JOIN tb_course_tag ct ON c.course_id = ct.course_id
|
||||||
<include refid="Permission_Filter"/>
|
<include refid="Permission_Filter"/>
|
||||||
WHERE c.deleted = 0
|
WHERE c.deleted = 0
|
||||||
<if test="filter != null">
|
<if test="filter != null">
|
||||||
|
|||||||
@@ -147,6 +147,78 @@ export const resourceTagApi = {
|
|||||||
async getResourcesByTag(tagID: string): Promise<ResultDomain<string>> {
|
async getResourcesByTag(tagID: string): Promise<ResultDomain<string>> {
|
||||||
const response = await api.get<string>(`/news/tags/tag/${tagID}/resources`);
|
const response = await api.get<string>(`/news/tags/tag/${tagID}/resources`);
|
||||||
return response.data;
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// ==================== 标签默认封面操作 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取标签的启用默认封面列表
|
||||||
|
* @param tagID 标签ID
|
||||||
|
* @returns Promise<ResultDomain<any>>
|
||||||
|
*/
|
||||||
|
async getDefaultCovers(tagID: string): Promise<ResultDomain<any>> {
|
||||||
|
const response = await api.get<any>(`/news/tags/tag/${tagID}/default-covers`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取标签的所有默认封面列表(管理端使用)
|
||||||
|
* @param tagID 标签ID
|
||||||
|
* @returns Promise<ResultDomain<any>>
|
||||||
|
*/
|
||||||
|
async getAllDefaultCovers(tagID: string): Promise<ResultDomain<any>> {
|
||||||
|
const response = await api.get<any>(`/news/tags/tag/${tagID}/all-default-covers`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加默认封面
|
||||||
|
* @param cover 默认封面对象
|
||||||
|
* @returns Promise<ResultDomain<string>>
|
||||||
|
*/
|
||||||
|
async addDefaultCover(cover: any): Promise<ResultDomain<string>> {
|
||||||
|
const response = await api.post<string>('/news/tags/default-cover', cover);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新默认封面
|
||||||
|
* @param cover 默认封面对象
|
||||||
|
* @returns Promise<ResultDomain<string>>
|
||||||
|
*/
|
||||||
|
async updateDefaultCover(cover: any): Promise<ResultDomain<string>> {
|
||||||
|
const response = await api.put<string>('/news/tags/default-cover', cover);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除默认封面
|
||||||
|
* @param id 主键ID
|
||||||
|
* @returns Promise<ResultDomain<string>>
|
||||||
|
*/
|
||||||
|
async deleteDefaultCover(id: string): Promise<ResultDomain<string>> {
|
||||||
|
const response = await api.delete<string>(`/news/tags/default-cover/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加默认封面
|
||||||
|
* @param covers 默认封面列表
|
||||||
|
* @returns Promise<ResultDomain<string>>
|
||||||
|
*/
|
||||||
|
async batchAddDefaultCovers(covers: any[]): Promise<ResultDomain<string>> {
|
||||||
|
const response = await api.post<string>('/news/tags/default-covers/batch', covers);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据标签ID删除所有默认封面
|
||||||
|
* @param tagID 标签ID
|
||||||
|
* @returns Promise<ResultDomain<string>>
|
||||||
|
*/
|
||||||
|
async deleteDefaultCoversByTagId(tagID: string): Promise<ResultDomain<string>> {
|
||||||
|
const response = await api.delete<string>(`/news/tags/tag/${tagID}/default-covers`);
|
||||||
|
return response.data;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ export interface Course extends BaseDTO {
|
|||||||
learnCount?: number;
|
learnCount?: number;
|
||||||
/** 排序号 */
|
/** 排序号 */
|
||||||
orderNum?: number;
|
orderNum?: number;
|
||||||
|
/** 标签ID(用于获取默认封面) */
|
||||||
|
tagID?: string;
|
||||||
/** 创建者 */
|
/** 创建者 */
|
||||||
creator?: string;
|
creator?: string;
|
||||||
/** 更新者 */
|
/** 更新者 */
|
||||||
@@ -144,6 +146,8 @@ export interface CourseItemVO extends BaseDTO {
|
|||||||
viewCount?: number;
|
viewCount?: number;
|
||||||
/** 学习人数 */
|
/** 学习人数 */
|
||||||
learnCount?: number;
|
learnCount?: number;
|
||||||
|
/** 标签ID(用于获取默认封面和分类) */
|
||||||
|
tagID?: string;
|
||||||
/** 课程创建时间 */
|
/** 课程创建时间 */
|
||||||
createTime?: string;
|
createTime?: string;
|
||||||
|
|
||||||
@@ -342,6 +346,8 @@ export interface TaskItemVO extends LearningTask {
|
|||||||
viewCount?: number;
|
viewCount?: number;
|
||||||
/** 发布时间(用于搜索结果展示) */
|
/** 发布时间(用于搜索结果展示) */
|
||||||
publishTime?: Date | string;
|
publishTime?: Date | string;
|
||||||
|
/** 标签ID(用于获取默认封面) */
|
||||||
|
tagID?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
82
schoolNewsWeb/src/utils/defaultCover.ts
Normal file
82
schoolNewsWeb/src/utils/defaultCover.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* @description 默认封面工具函数
|
||||||
|
* @filename defaultCover.ts
|
||||||
|
* @author system
|
||||||
|
* @since 2025-12-24
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { resourceTagApi } from '@/apis/resource/resourceTag';
|
||||||
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
|
import defaultArticleImg from '@/assets/imgs/article-default.png';
|
||||||
|
|
||||||
|
// 缓存标签默认封面,避免重复请求
|
||||||
|
const tagCoverCache: Map<string, string[]> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据标签ID获取随机默认封面
|
||||||
|
* @param tagID 标签ID
|
||||||
|
* @returns Promise<string> 封面URL
|
||||||
|
*/
|
||||||
|
export async function getRandomDefaultCover(tagID: string | undefined): Promise<string> {
|
||||||
|
// 如果没有tagID,返回默认封面
|
||||||
|
if (!tagID) {
|
||||||
|
return defaultArticleImg;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查缓存
|
||||||
|
if (tagCoverCache.has(tagID)) {
|
||||||
|
const covers = tagCoverCache.get(tagID)!;
|
||||||
|
if (covers.length > 0) {
|
||||||
|
// 随机选择一个
|
||||||
|
const randomIndex = Math.floor(Math.random() * covers.length);
|
||||||
|
return covers[randomIndex];
|
||||||
|
}
|
||||||
|
return defaultArticleImg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从后端获取
|
||||||
|
const result = await resourceTagApi.getDefaultCovers(tagID);
|
||||||
|
|
||||||
|
if (result.success && result.dataList && result.dataList.length > 0) {
|
||||||
|
// 构建完整的URL列表
|
||||||
|
const coverUrls = result.dataList.map((cover: any) =>
|
||||||
|
`${FILE_DOWNLOAD_URL}${cover.coverImage}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 存入缓存
|
||||||
|
tagCoverCache.set(tagID, coverUrls);
|
||||||
|
|
||||||
|
// 随机选择一个
|
||||||
|
const randomIndex = Math.floor(Math.random() * coverUrls.length);
|
||||||
|
return coverUrls[randomIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有配置默认封面,返回通用默认封面
|
||||||
|
return defaultArticleImg;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取标签默认封面失败:', error);
|
||||||
|
return defaultArticleImg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除标签封面缓存
|
||||||
|
* @param tagID 可选,指定清除某个标签的缓存,不传则清除所有
|
||||||
|
*/
|
||||||
|
export function clearTagCoverCache(tagID?: string) {
|
||||||
|
if (tagID) {
|
||||||
|
tagCoverCache.delete(tagID);
|
||||||
|
} else {
|
||||||
|
tagCoverCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预加载标签默认封面到缓存
|
||||||
|
* @param tagIDs 标签ID列表
|
||||||
|
*/
|
||||||
|
export async function preloadTagCovers(tagIDs: string[]) {
|
||||||
|
const promises = tagIDs.map(tagID => getRandomDefaultCover(tagID));
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
@@ -161,6 +161,48 @@
|
|||||||
placeholder="请输入标签描述"
|
placeholder="请输入标签描述"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="默认封面">
|
||||||
|
<div class="default-covers-section">
|
||||||
|
<div class="covers-tip">为该标签配置多张默认封面,资源没有封面时会随机显示其中一张</div>
|
||||||
|
|
||||||
|
<!-- 已上传的封面列表 -->
|
||||||
|
<div class="covers-list" v-if="defaultCovers.length > 0">
|
||||||
|
<div v-for="(cover, index) in defaultCovers" :key="cover.id || index" class="cover-item">
|
||||||
|
<img :src="cover.coverImage" alt="默认封面" class="cover-preview" />
|
||||||
|
<div class="cover-actions">
|
||||||
|
<el-switch
|
||||||
|
v-model="cover.isActive"
|
||||||
|
:active-value="1"
|
||||||
|
:inactive-value="0"
|
||||||
|
active-text="启用"
|
||||||
|
inactive-text="禁用"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
@click="removeCover(index)"
|
||||||
|
:icon="Delete"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加封面 -->
|
||||||
|
<FileUpload
|
||||||
|
list-type="cover"
|
||||||
|
:cover-url="''"
|
||||||
|
@update:cover-url="handleAddCover"
|
||||||
|
accept="image/*"
|
||||||
|
:max-size="5"
|
||||||
|
module="tag-cover"
|
||||||
|
:as-dialog="false"
|
||||||
|
tip="点击上传默认封面图片"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -175,10 +217,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } 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 { Delete } from '@element-plus/icons-vue';
|
||||||
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 { TagType } from '@/types/resource';
|
||||||
import {AdminLayout} from '@/views/admin';
|
import { AdminLayout } from '@/views/admin';
|
||||||
|
import {FileUpload} from '@/components';
|
||||||
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'TagManagementView'
|
name: 'TagManagementView'
|
||||||
@@ -198,6 +243,9 @@ const currentTag = ref<Partial<Tag>>({
|
|||||||
description: ''
|
description: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 默认封面列表
|
||||||
|
const defaultCovers = ref<any[]>([]);
|
||||||
|
|
||||||
// 按类型分类的标签
|
// 按类型分类的标签
|
||||||
const articleTags = computed(() => {
|
const articleTags = computed(() => {
|
||||||
return tags.value.filter(tag => tag.tagType === TagType.ARTICLE_CATEGORY);
|
return tags.value.filter(tag => tag.tagType === TagType.ARTICLE_CATEGORY);
|
||||||
@@ -256,16 +304,65 @@ function showCreateDialog() {
|
|||||||
color: '#409EFF',
|
color: '#409EFF',
|
||||||
description: ''
|
description: ''
|
||||||
};
|
};
|
||||||
|
defaultCovers.value = [];
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑标签
|
// 编辑标签
|
||||||
function editTag(row: Tag) {
|
async function editTag(row: Tag) {
|
||||||
isEdit.value = true;
|
isEdit.value = true;
|
||||||
currentTag.value = { ...row };
|
currentTag.value = { ...row };
|
||||||
|
|
||||||
|
// 加载默认封面
|
||||||
|
await loadDefaultCovers(row.tagID!);
|
||||||
|
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载默认封面
|
||||||
|
async function loadDefaultCovers(tagID: string) {
|
||||||
|
try {
|
||||||
|
const result = await resourceTagApi.getAllDefaultCovers(tagID);
|
||||||
|
console.log('加载默认封面结果:', result);
|
||||||
|
if (result.success && result.dataList) {
|
||||||
|
defaultCovers.value = result.dataList.map((cover: any) => ({
|
||||||
|
...cover,
|
||||||
|
coverImage: FILE_DOWNLOAD_URL + cover.coverImage
|
||||||
|
}));
|
||||||
|
console.log('处理后的封面列表:', defaultCovers.value);
|
||||||
|
} else {
|
||||||
|
defaultCovers.value = [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载默认封面失败:', error);
|
||||||
|
defaultCovers.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加封面
|
||||||
|
function handleAddCover(fileIdOrUrl: string) {
|
||||||
|
if (fileIdOrUrl) {
|
||||||
|
// 如果是完整URL,提取fileId;否则直接使用
|
||||||
|
let fileId = fileIdOrUrl;
|
||||||
|
|
||||||
|
// 如果包含FILE_DOWNLOAD_URL,说明是完整URL,需要提取fileId
|
||||||
|
if (fileIdOrUrl.includes(FILE_DOWNLOAD_URL)) {
|
||||||
|
fileId = fileIdOrUrl.replace(FILE_DOWNLOAD_URL, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultCovers.value.push({
|
||||||
|
coverImage: FILE_DOWNLOAD_URL + fileId,
|
||||||
|
isActive: 1,
|
||||||
|
orderNum: defaultCovers.value.length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除封面
|
||||||
|
function removeCover(index: number) {
|
||||||
|
defaultCovers.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
// 删除标签
|
// 删除标签
|
||||||
async function deleteTag(row: Tag) {
|
async function deleteTag(row: Tag) {
|
||||||
try {
|
try {
|
||||||
@@ -310,6 +407,17 @@ async function handleSubmit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
// 保存默认封面
|
||||||
|
let tagID = currentTag.value.tagID;
|
||||||
|
if (!tagID && result.data) {
|
||||||
|
// 新建标签,从返回数据中获取tagID
|
||||||
|
tagID = typeof result.data === 'string' ? result.data : result.data.tagID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagID) {
|
||||||
|
await saveDefaultCovers(tagID);
|
||||||
|
}
|
||||||
|
|
||||||
ElMessage.success(isEdit.value ? '更新成功' : '创建成功');
|
ElMessage.success(isEdit.value ? '更新成功' : '创建成功');
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
loadTags();
|
loadTags();
|
||||||
@@ -323,9 +431,36 @@ async function handleSubmit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存默认封面
|
||||||
|
async function saveDefaultCovers(tagID: string) {
|
||||||
|
try {
|
||||||
|
// 编辑模式下,先删除旧的默认封面
|
||||||
|
if (isEdit.value) {
|
||||||
|
await resourceTagApi.deleteDefaultCoversByTagId(tagID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备要保存的封面数据
|
||||||
|
const coversToSave = defaultCovers.value.map((cover, index) => ({
|
||||||
|
tagID: tagID,
|
||||||
|
coverImage: cover.coverImage.replace(FILE_DOWNLOAD_URL, ''),
|
||||||
|
isActive: cover.isActive,
|
||||||
|
orderNum: index
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 批量添加新的默认封面
|
||||||
|
if (coversToSave.length > 0) {
|
||||||
|
await resourceTagApi.batchAddDefaultCovers(coversToSave);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存默认封面失败:', error);
|
||||||
|
ElMessage.warning('默认封面保存失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 对话框关闭处理
|
// 对话框关闭处理
|
||||||
function handleDialogClose() {
|
function handleDialogClose() {
|
||||||
formRef.value?.resetFields();
|
formRef.value?.resetFields();
|
||||||
|
defaultCovers.value = [];
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -577,4 +712,48 @@ function handleDialogClose() {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.default-covers-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.covers-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.covers-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-preview {
|
||||||
|
width: 100%;
|
||||||
|
height: 120px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="article-card" @click="handleClick">
|
<div class="article-card" @click="handleClick">
|
||||||
<div class="article-image">
|
<div class="article-image">
|
||||||
<img :src="resource?.coverImage ? (FILE_DOWNLOAD_URL + resource.coverImage) : defaultArticleImg" :alt="resource.title" />
|
<img :src="coverUrl" :alt="resource?.title" />
|
||||||
<div class="article-tag">精选文章</div>
|
<div class="article-tag">精选文章</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="article-content">
|
<div class="article-content">
|
||||||
@@ -21,12 +21,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
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 type { ResourceRecommendVO } from '@/types';
|
||||||
import { FILE_DOWNLOAD_URL } from '@/config';
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
import defaultArticleImg from '@/assets/imgs/article-default.png';
|
import defaultArticleImg from '@/assets/imgs/article-default.png';
|
||||||
|
import { getRandomDefaultCover } from '@/utils/defaultCover';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
resource?: ResourceRecommendVO;
|
resource?: ResourceRecommendVO;
|
||||||
@@ -34,6 +35,28 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 封面URL(响应式)
|
||||||
|
const coverUrl = ref<string>(defaultArticleImg);
|
||||||
|
|
||||||
|
// 加载封面
|
||||||
|
async function loadCover() {
|
||||||
|
if (props.resource?.coverImage) {
|
||||||
|
// 有封面直接使用
|
||||||
|
coverUrl.value = FILE_DOWNLOAD_URL + props.resource.coverImage;
|
||||||
|
} else if (props.resource?.tagID) {
|
||||||
|
// 没有封面,根据tagID获取默认封面
|
||||||
|
coverUrl.value = await getRandomDefaultCover(props.resource.tagID);
|
||||||
|
} else {
|
||||||
|
// 既没有封面也没有tagID,使用全局默认
|
||||||
|
coverUrl.value = defaultArticleImg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听resource变化
|
||||||
|
watch(() => props.resource, () => {
|
||||||
|
loadCover();
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
// 格式化浏览量
|
// 格式化浏览量
|
||||||
function formatViewCount(count: number): string {
|
function formatViewCount(count: number): string {
|
||||||
if (count < 1000) {
|
if (count < 1000) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="ideological-card" @click="handleClick">
|
<div class="ideological-card" @click="handleClick">
|
||||||
<div class="card-image">
|
<div class="card-image">
|
||||||
<img :src="resource?.coverImage ? (FILE_DOWNLOAD_URL + resource.coverImage) : defaultArticleImg" :alt="resource.title" />
|
<img :src="coverUrl" :alt="resource?.title" />
|
||||||
</div>
|
</div>
|
||||||
<div class="date-box" v-if="publishDate">
|
<div class="date-box" v-if="publishDate">
|
||||||
<div class="day">{{ publishDate.day }}</div>
|
<div class="day">{{ publishDate.day }}</div>
|
||||||
@@ -17,11 +17,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import type { ResourceRecommendVO } from '@/types';
|
import type { ResourceRecommendVO } from '@/types';
|
||||||
import { FILE_DOWNLOAD_URL } from '@/config';
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
import defaultArticleImg from '@/assets/imgs/article-default.png';
|
import defaultArticleImg from '@/assets/imgs/article-default.png';
|
||||||
|
import { getRandomDefaultCover } from '@/utils/defaultCover';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
resource?: ResourceRecommendVO;
|
resource?: ResourceRecommendVO;
|
||||||
@@ -29,6 +30,25 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 封面URL(响应式)
|
||||||
|
const coverUrl = ref<string>(defaultArticleImg);
|
||||||
|
|
||||||
|
// 加载封面
|
||||||
|
async function loadCover() {
|
||||||
|
if (props.resource?.coverImage) {
|
||||||
|
coverUrl.value = FILE_DOWNLOAD_URL + props.resource.coverImage;
|
||||||
|
} else if (props.resource?.tagID) {
|
||||||
|
coverUrl.value = await getRandomDefaultCover(props.resource.tagID);
|
||||||
|
} else {
|
||||||
|
coverUrl.value = defaultArticleImg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听resource变化
|
||||||
|
watch(() => props.resource, () => {
|
||||||
|
loadCover();
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
// 格式化发布日期
|
// 格式化发布日期
|
||||||
const publishDate = computed(() => {
|
const publishDate = computed(() => {
|
||||||
if (!props.resource?.publishTime) return null;
|
if (!props.resource?.publishTime) return null;
|
||||||
|
|||||||
@@ -50,6 +50,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="课程分类" prop="tagID">
|
||||||
|
<el-select
|
||||||
|
v-model="currentCourseItemVO.tagID"
|
||||||
|
placeholder="请选择课程分类"
|
||||||
|
:loading="tagsLoading"
|
||||||
|
:disabled="!editMode"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="tag in tagList"
|
||||||
|
:key="tag.tagID || tag.id"
|
||||||
|
:label="tag.name"
|
||||||
|
:value="tag.tagID || ''"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="授课老师" prop="teacher">
|
<el-form-item label="授课老师" prop="teacher">
|
||||||
<div style="display: flex; gap: 8px; align-items: flex-start;">
|
<div style="display: flex; gap: 8px; align-items: flex-start;">
|
||||||
<el-select
|
<el-select
|
||||||
@@ -396,12 +414,13 @@ import { Plus, ArrowLeft, Refresh } from '@element-plus/icons-vue';
|
|||||||
import { FileUpload } from '@/components/file';
|
import { FileUpload } from '@/components/file';
|
||||||
import { RichTextComponent } from '@/components/text';
|
import { RichTextComponent } from '@/components/text';
|
||||||
import { courseApi } from '@/apis/study';
|
import { courseApi } from '@/apis/study';
|
||||||
import { resourceApi } from '@/apis/resource';
|
import { resourceApi, resourceTagApi } from '@/apis/resource';
|
||||||
import { userApi } from '@/apis/system';
|
import { userApi } from '@/apis/system';
|
||||||
import type { CourseItemVO } from '@/types/study';
|
import type { CourseItemVO } from '@/types/study';
|
||||||
import type { Resource } from '@/types/resource';
|
import type { Resource, Tag, TagType } from '@/types/resource';
|
||||||
import type { SysFile, UserVO } from '@/types';
|
import type { SysFile, UserVO } from '@/types';
|
||||||
import { FILE_DOWNLOAD_URL } from '@/config';
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
|
import { TagType as TagTypeEnum } from '@/types/resource';
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'CourseAdd'
|
name: 'CourseAdd'
|
||||||
});
|
});
|
||||||
@@ -428,6 +447,10 @@ const editMode = ref(true);
|
|||||||
const teacherList = ref<UserVO[]>([]);
|
const teacherList = ref<UserVO[]>([]);
|
||||||
const teachersLoading = ref(false);
|
const teachersLoading = ref(false);
|
||||||
|
|
||||||
|
// 标签列表
|
||||||
|
const tagList = ref<Tag[]>([]);
|
||||||
|
const tagsLoading = ref(false);
|
||||||
|
|
||||||
// 原始数据(用于比对)
|
// 原始数据(用于比对)
|
||||||
const originalCourseItemVO = ref<CourseItemVO>();
|
const originalCourseItemVO = ref<CourseItemVO>();
|
||||||
// 当前编辑的数据
|
// 当前编辑的数据
|
||||||
@@ -445,12 +468,15 @@ const currentCourseItemVO = ref<CourseItemVO>({
|
|||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const rules = {
|
const rules = {
|
||||||
'name': [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
|
'name': [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
|
||||||
|
'tagID': [{ required: true, message: '请选择课程分类', trigger: 'change' }],
|
||||||
'teacher': [{ required: true, message: '请输入授课老师', trigger: 'blur' }]
|
'teacher': [{ required: true, message: '请输入授课老师', trigger: 'blur' }]
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 加载教师列表
|
// 加载教师列表
|
||||||
loadTeacherList();
|
loadTeacherList();
|
||||||
|
// 加载标签列表
|
||||||
|
loadTagList();
|
||||||
|
|
||||||
if (props.courseID) {
|
if (props.courseID) {
|
||||||
loadCourse();
|
loadCourse();
|
||||||
@@ -641,6 +667,25 @@ async function loadTeacherList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载标签列表
|
||||||
|
async function loadTagList() {
|
||||||
|
try {
|
||||||
|
tagsLoading.value = true;
|
||||||
|
const result = await resourceTagApi.getTagsByType(TagTypeEnum.COURSE_CATEGORY);
|
||||||
|
if (result.success) {
|
||||||
|
tagList.value = result.dataList || [];
|
||||||
|
console.log(`✅ 已加载 ${tagList.value.length} 个课程分类标签`);
|
||||||
|
} else {
|
||||||
|
ElMessage.error(result.message || '加载课程分类失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载课程分类失败:', error);
|
||||||
|
ElMessage.error('加载课程分类失败');
|
||||||
|
} finally {
|
||||||
|
tagsLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 处理教师选择框获得焦点
|
// 处理教师选择框获得焦点
|
||||||
function handleTeacherSelectFocus() {
|
function handleTeacherSelectFocus() {
|
||||||
// 如果还没有加载教师列表,则加载
|
// 如果还没有加载教师列表,则加载
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
<!-- 文章封面 -->
|
<!-- 文章封面 -->
|
||||||
<div class="article-cover">
|
<div class="article-cover">
|
||||||
<img
|
<img
|
||||||
:src="article.coverImage ? (FILE_DOWNLOAD_URL + article.coverImage) : defaultArticleImg"
|
:src="getCoverUrl(article)"
|
||||||
:alt="article.resourceName"
|
:alt="article.resourceName"
|
||||||
/>
|
/>
|
||||||
<div class="cover-overlay">
|
<div class="cover-overlay">
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
<!-- 课程封面 -->
|
<!-- 课程封面 -->
|
||||||
<div class="course-cover">
|
<div class="course-cover">
|
||||||
<img
|
<img
|
||||||
:src="course.coverImage ? (FILE_DOWNLOAD_URL + course.coverImage) : defaultCourseImg"
|
:src="getCoverUrl(course)"
|
||||||
:alt="course.courseName"
|
:alt="course.courseName"
|
||||||
/>
|
/>
|
||||||
<div class="cover-overlay">
|
<div class="cover-overlay">
|
||||||
@@ -226,12 +226,13 @@ import { resourceApi } from '@/apis/resource';
|
|||||||
import { FILE_DOWNLOAD_URL } from '@/config';
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
import type { TaskItemVO } from '@/types/study';
|
import type { TaskItemVO } from '@/types/study';
|
||||||
import defaultArticleImg from '@/assets/imgs/article-default.png';
|
import defaultArticleImg from '@/assets/imgs/article-default.png';
|
||||||
|
import { getRandomDefaultCover } from '@/utils/defaultCover';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
// 默认封面(文章和课程使用同一个)
|
// 封面URL映射(resourceID -> coverUrl)
|
||||||
const defaultCourseImg = defaultArticleImg;
|
const coverUrls = ref<Map<string, string>>(new Map());
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@@ -275,6 +276,9 @@ async function loadSearchResults() {
|
|||||||
if (result.success && result.dataList) {
|
if (result.success && result.dataList) {
|
||||||
searchResults.value = result.dataList;
|
searchResults.value = result.dataList;
|
||||||
total.value = result.dataList.length;
|
total.value = result.dataList.length;
|
||||||
|
|
||||||
|
// 加载封面
|
||||||
|
await loadCovers(result.dataList);
|
||||||
} else {
|
} else {
|
||||||
searchResults.value = [];
|
searchResults.value = [];
|
||||||
total.value = 0;
|
total.value = 0;
|
||||||
@@ -289,6 +293,40 @@ async function loadSearchResults() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载资源封面
|
||||||
|
*/
|
||||||
|
async function loadCovers(items: TaskItemVO[]) {
|
||||||
|
const newCoverUrls = new Map<string, string>();
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const itemId = item.resourceID || item.courseID || '';
|
||||||
|
if (!itemId) continue;
|
||||||
|
|
||||||
|
if (item.coverImage) {
|
||||||
|
// 有封面直接使用
|
||||||
|
newCoverUrls.set(itemId, FILE_DOWNLOAD_URL + item.coverImage);
|
||||||
|
} else if (item.tagID) {
|
||||||
|
// 没有封面,根据tagID获取默认封面
|
||||||
|
const cover = await getRandomDefaultCover(item.tagID);
|
||||||
|
newCoverUrls.set(itemId, cover);
|
||||||
|
} else {
|
||||||
|
// 使用全局默认
|
||||||
|
newCoverUrls.set(itemId, defaultArticleImg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coverUrls.value = newCoverUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取资源封面URL
|
||||||
|
*/
|
||||||
|
function getCoverUrl(item: TaskItemVO): string {
|
||||||
|
const itemId = item.resourceID || item.courseID || '';
|
||||||
|
return coverUrls.value.get(itemId) || defaultArticleImg;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行搜索
|
* 执行搜索
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
>
|
>
|
||||||
<div class="resource-cover">
|
<div class="resource-cover">
|
||||||
<img
|
<img
|
||||||
:src="resource.coverImage ? (FILE_DOWNLOAD_URL + resource.coverImage) : staticAssets.defaultArticleImg"
|
:src="getCoverUrl(resource)"
|
||||||
alt="cover"
|
alt="cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,6 +55,7 @@ import type { Resource } from '@/types/resource';
|
|||||||
import type { PageParam } from '@/types';
|
import type { PageParam } from '@/types';
|
||||||
import defaultArticleImgUrl from '@/assets/imgs/article-default.png';
|
import defaultArticleImgUrl from '@/assets/imgs/article-default.png';
|
||||||
import { useDevice } from '@/utils/deviceUtils';
|
import { useDevice } from '@/utils/deviceUtils';
|
||||||
|
import { getRandomDefaultCover } from '@/utils/defaultCover';
|
||||||
|
|
||||||
// 创建响应式数据对象,包含静态资源
|
// 创建响应式数据对象,包含静态资源
|
||||||
const staticAssets = reactive({
|
const staticAssets = reactive({
|
||||||
@@ -82,6 +83,7 @@ const listContainerRef = ref<HTMLElement>();
|
|||||||
const hasMoreData = ref(true);
|
const hasMoreData = ref(true);
|
||||||
const isLoadingMore = ref(false);
|
const isLoadingMore = ref(false);
|
||||||
const hasTriggeredLoadMore = ref(false); // 标记是否触发过加载更多
|
const hasTriggeredLoadMore = ref(false); // 标记是否触发过加载更多
|
||||||
|
const coverUrls = ref<Map<string, string>>(new Map()); // 存储资源封面URL
|
||||||
|
|
||||||
// 设备检测
|
// 设备检测
|
||||||
const { isMobileDevice } = useDevice();
|
const { isMobileDevice } = useDevice();
|
||||||
@@ -171,6 +173,9 @@ async function loadResources(isAppend = false) {
|
|||||||
const totalPages = Math.ceil(total.value / pageSize.value);
|
const totalPages = Math.ceil(total.value / pageSize.value);
|
||||||
hasMoreData.value = currentPage.value < totalPages;
|
hasMoreData.value = currentPage.value < totalPages;
|
||||||
|
|
||||||
|
// 加载封面
|
||||||
|
await loadCovers(newData);
|
||||||
|
|
||||||
// 通知父组件列表已更新
|
// 通知父组件列表已更新
|
||||||
emit('list-updated', resources.value);
|
emit('list-updated', resources.value);
|
||||||
}
|
}
|
||||||
@@ -254,6 +259,35 @@ async function loadMoreData() {
|
|||||||
await loadResources(true); // true 表示追加模式
|
await loadResources(true); // true 表示追加模式
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载资源封面
|
||||||
|
*/
|
||||||
|
async function loadCovers(items: Resource[]) {
|
||||||
|
for (const item of items) {
|
||||||
|
const itemId = item.resourceID || '';
|
||||||
|
if (!itemId) continue;
|
||||||
|
|
||||||
|
if (item.coverImage) {
|
||||||
|
// 有封面直接使用
|
||||||
|
coverUrls.value.set(itemId, FILE_DOWNLOAD_URL + item.coverImage);
|
||||||
|
} else if (item.tagID) {
|
||||||
|
// 没有封面,根据tagID获取默认封面
|
||||||
|
const cover = await getRandomDefaultCover(item.tagID);
|
||||||
|
coverUrls.value.set(itemId, cover);
|
||||||
|
} else {
|
||||||
|
// 使用全局默认
|
||||||
|
coverUrls.value.set(itemId, defaultArticleImgUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取资源封面URL
|
||||||
|
*/
|
||||||
|
function getCoverUrl(resource: Resource): string {
|
||||||
|
return coverUrls.value.get(resource.resourceID || '') || defaultArticleImgUrl;
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
loadResources,
|
loadResources,
|
||||||
getResources,
|
getResources,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<!-- 课程封面 -->
|
<!-- 课程封面 -->
|
||||||
<div class="course-cover">
|
<div class="course-cover">
|
||||||
<img
|
<img
|
||||||
:src="course.coverImage ? FILE_DOWNLOAD_URL + course.coverImage : defaultCover"
|
:src="getCoverUrl(course)"
|
||||||
:alt="course.name"
|
:alt="course.name"
|
||||||
class="cover-image"
|
class="cover-image"
|
||||||
/>
|
/>
|
||||||
@@ -111,6 +111,7 @@ import type { Course, PageParam } from '@/types';
|
|||||||
import { StudyPlanLayout } from '@/views/user/study-plan';
|
import { StudyPlanLayout } from '@/views/user/study-plan';
|
||||||
import defaultCoverImg from '@/assets/imgs/default-course-bg.png'
|
import defaultCoverImg from '@/assets/imgs/default-course-bg.png'
|
||||||
import { FILE_DOWNLOAD_URL } from '@/config';
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
|
import { getRandomDefaultCover } from '@/utils/defaultCover';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'CourseCenterView'
|
name: 'CourseCenterView'
|
||||||
@@ -124,6 +125,7 @@ const courseList = ref<Course[]>([]);
|
|||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const isMobile = ref(false);
|
const isMobile = ref(false);
|
||||||
const hasMore = ref(true);
|
const hasMore = ref(true);
|
||||||
|
const coverUrls = ref<Map<string, string>>(new Map()); // 存储课程封面URL
|
||||||
|
|
||||||
// 默认封面图片
|
// 默认封面图片
|
||||||
const defaultCover = defaultCoverImg;
|
const defaultCover = defaultCoverImg;
|
||||||
@@ -200,6 +202,9 @@ async function loadMoreCourses() {
|
|||||||
pageParam.value.pageNumber = nextPage;
|
pageParam.value.pageNumber = nextPage;
|
||||||
total.value = res.pageParam?.totalElements || 0;
|
total.value = res.pageParam?.totalElements || 0;
|
||||||
|
|
||||||
|
// 加载封面
|
||||||
|
await loadCovers(res.dataList);
|
||||||
|
|
||||||
// 检查是否还有更多数据
|
// 检查是否还有更多数据
|
||||||
hasMore.value = courseList.value.length < total.value;
|
hasMore.value = courseList.value.length < total.value;
|
||||||
} else {
|
} else {
|
||||||
@@ -236,6 +241,9 @@ async function loadCourseList(isRefresh = false) {
|
|||||||
courseList.value = res.dataList || [];
|
courseList.value = res.dataList || [];
|
||||||
total.value = res.pageParam?.totalElements || 0;
|
total.value = res.pageParam?.totalElements || 0;
|
||||||
|
|
||||||
|
// 加载封面
|
||||||
|
await loadCovers(res.dataList || []);
|
||||||
|
|
||||||
// 移动端下检查是否还有更多数据
|
// 移动端下检查是否还有更多数据
|
||||||
if (isMobile.value) {
|
if (isMobile.value) {
|
||||||
hasMore.value = courseList.value.length < total.value;
|
hasMore.value = courseList.value.length < total.value;
|
||||||
@@ -292,6 +300,35 @@ function getCategoryName(): string {
|
|||||||
// TODO: 从 courseTags 中获取第一个标签作为分类
|
// TODO: 从 courseTags 中获取第一个标签作为分类
|
||||||
return '党史学习';
|
return '党史学习';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载课程封面
|
||||||
|
*/
|
||||||
|
async function loadCovers(courses: Course[]) {
|
||||||
|
for (const course of courses) {
|
||||||
|
const courseId = course.courseID || '';
|
||||||
|
if (!courseId) continue;
|
||||||
|
|
||||||
|
if (course.coverImage) {
|
||||||
|
// 有封面直接使用
|
||||||
|
coverUrls.value.set(courseId, FILE_DOWNLOAD_URL + course.coverImage);
|
||||||
|
} else if (course.tagID) {
|
||||||
|
// 没有封面,根据tagID获取默认封面
|
||||||
|
const cover = await getRandomDefaultCover(course.tagID);
|
||||||
|
coverUrls.value.set(courseId, cover);
|
||||||
|
} else {
|
||||||
|
// 使用全局默认
|
||||||
|
coverUrls.value.set(courseId, defaultCover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取课程封面URL
|
||||||
|
*/
|
||||||
|
function getCoverUrl(course: Course): string {
|
||||||
|
return coverUrls.value.get(course.courseID || '') || defaultCover;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user