文章、课程标签的默认封面

This commit is contained in:
2025-12-24 15:44:07 +08:00
parent 46464f36a0
commit 878133fb40
22 changed files with 1278 additions and 25 deletions

View File

@@ -147,6 +147,78 @@ export const resourceTagApi = {
async getResourcesByTag(tagID: string): Promise<ResultDomain<string>> {
const response = await api.get<string>(`/news/tags/tag/${tagID}/resources`);
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;
}
};

View File

@@ -34,6 +34,8 @@ export interface Course extends BaseDTO {
learnCount?: number;
/** 排序号 */
orderNum?: number;
/** 标签ID用于获取默认封面 */
tagID?: string;
/** 创建者 */
creator?: string;
/** 更新者 */
@@ -144,6 +146,8 @@ export interface CourseItemVO extends BaseDTO {
viewCount?: number;
/** 学习人数 */
learnCount?: number;
/** 标签ID用于获取默认封面和分类 */
tagID?: string;
/** 课程创建时间 */
createTime?: string;
@@ -342,6 +346,8 @@ export interface TaskItemVO extends LearningTask {
viewCount?: number;
/** 发布时间(用于搜索结果展示) */
publishTime?: Date | string;
/** 标签ID用于获取默认封面 */
tagID?: string;
}
/**

View 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);
}

View File

@@ -161,6 +161,48 @@
placeholder="请输入标签描述"
/>
</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>
<template #footer>
@@ -175,10 +217,13 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
import { Delete } from '@element-plus/icons-vue';
import { resourceTagApi } from '@/apis/resource';
import type { Tag } 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({
name: 'TagManagementView'
@@ -198,6 +243,9 @@ const currentTag = ref<Partial<Tag>>({
description: ''
});
// 默认封面列表
const defaultCovers = ref<any[]>([]);
// 按类型分类的标签
const articleTags = computed(() => {
return tags.value.filter(tag => tag.tagType === TagType.ARTICLE_CATEGORY);
@@ -256,16 +304,65 @@ function showCreateDialog() {
color: '#409EFF',
description: ''
};
defaultCovers.value = [];
dialogVisible.value = true;
}
// 编辑标签
function editTag(row: Tag) {
async function editTag(row: Tag) {
isEdit.value = true;
currentTag.value = { ...row };
// 加载默认封面
await loadDefaultCovers(row.tagID!);
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) {
try {
@@ -310,6 +407,17 @@ async function handleSubmit() {
}
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 ? '更新成功' : '创建成功');
dialogVisible.value = false;
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() {
formRef.value?.resetFields();
defaultCovers.value = [];
}
</script>
@@ -577,4 +712,48 @@ function handleDialogClose() {
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>

View File

@@ -1,7 +1,7 @@
<template>
<div class="article-card" @click="handleClick">
<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>
<div class="article-content">
@@ -21,12 +21,13 @@
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { computed, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { Document } from '@element-plus/icons-vue';
import type { ResourceRecommendVO } from '@/types';
import { FILE_DOWNLOAD_URL } from '@/config';
import defaultArticleImg from '@/assets/imgs/article-default.png';
import { getRandomDefaultCover } from '@/utils/defaultCover';
const props = defineProps<{
resource?: ResourceRecommendVO;
@@ -34,6 +35,28 @@ const props = defineProps<{
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 {
if (count < 1000) {

View File

@@ -1,7 +1,7 @@
<template>
<div class="ideological-card" @click="handleClick">
<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 class="date-box" v-if="publishDate">
<div class="day">{{ publishDate.day }}</div>
@@ -17,11 +17,12 @@
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { computed, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import type { ResourceRecommendVO } from '@/types';
import { FILE_DOWNLOAD_URL } from '@/config';
import defaultArticleImg from '@/assets/imgs/article-default.png';
import { getRandomDefaultCover } from '@/utils/defaultCover';
const props = defineProps<{
resource?: ResourceRecommendVO;
@@ -29,6 +30,25 @@ const props = defineProps<{
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(() => {
if (!props.resource?.publishTime) return null;

View File

@@ -50,6 +50,24 @@
</div>
</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">
<div style="display: flex; gap: 8px; align-items: flex-start;">
<el-select
@@ -396,12 +414,13 @@ import { Plus, ArrowLeft, Refresh } from '@element-plus/icons-vue';
import { FileUpload } from '@/components/file';
import { RichTextComponent } from '@/components/text';
import { courseApi } from '@/apis/study';
import { resourceApi } from '@/apis/resource';
import { resourceApi, resourceTagApi } from '@/apis/resource';
import { userApi } from '@/apis/system';
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 { FILE_DOWNLOAD_URL } from '@/config';
import { TagType as TagTypeEnum } from '@/types/resource';
defineOptions({
name: 'CourseAdd'
});
@@ -428,6 +447,10 @@ const editMode = ref(true);
const teacherList = ref<UserVO[]>([]);
const teachersLoading = ref(false);
// 标签列表
const tagList = ref<Tag[]>([]);
const tagsLoading = ref(false);
// 原始数据(用于比对)
const originalCourseItemVO = ref<CourseItemVO>();
// 当前编辑的数据
@@ -445,12 +468,15 @@ const currentCourseItemVO = ref<CourseItemVO>({
// 表单验证规则
const rules = {
'name': [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
'tagID': [{ required: true, message: '请选择课程分类', trigger: 'change' }],
'teacher': [{ required: true, message: '请输入授课老师', trigger: 'blur' }]
};
onMounted(() => {
// 加载教师列表
loadTeacherList();
// 加载标签列表
loadTagList();
if (props.courseID) {
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() {
// 如果还没有加载教师列表,则加载

View File

@@ -92,7 +92,7 @@
<!-- 文章封面 -->
<div class="article-cover">
<img
:src="article.coverImage ? (FILE_DOWNLOAD_URL + article.coverImage) : defaultArticleImg"
:src="getCoverUrl(article)"
:alt="article.resourceName"
/>
<div class="cover-overlay">
@@ -155,7 +155,7 @@
<!-- 课程封面 -->
<div class="course-cover">
<img
:src="course.coverImage ? (FILE_DOWNLOAD_URL + course.coverImage) : defaultCourseImg"
:src="getCoverUrl(course)"
:alt="course.courseName"
/>
<div class="cover-overlay">
@@ -226,12 +226,13 @@ import { resourceApi } from '@/apis/resource';
import { FILE_DOWNLOAD_URL } from '@/config';
import type { TaskItemVO } from '@/types/study';
import defaultArticleImg from '@/assets/imgs/article-default.png';
import { getRandomDefaultCover } from '@/utils/defaultCover';
const router = useRouter();
const route = useRoute();
// 默认封面(文章和课程使用同一个
const defaultCourseImg = defaultArticleImg;
// 封面URL映射resourceID -> coverUrl
const coverUrls = ref<Map<string, string>>(new Map());
// 响应式数据
const loading = ref(false);
@@ -275,6 +276,9 @@ async function loadSearchResults() {
if (result.success && result.dataList) {
searchResults.value = result.dataList;
total.value = result.dataList.length;
// 加载封面
await loadCovers(result.dataList);
} else {
searchResults.value = [];
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;
}
/**
* 执行搜索
*/

View File

@@ -9,7 +9,7 @@
>
<div class="resource-cover">
<img
:src="resource.coverImage ? (FILE_DOWNLOAD_URL + resource.coverImage) : staticAssets.defaultArticleImg"
:src="getCoverUrl(resource)"
alt="cover"
/>
</div>
@@ -55,6 +55,7 @@ import type { Resource } from '@/types/resource';
import type { PageParam } from '@/types';
import defaultArticleImgUrl from '@/assets/imgs/article-default.png';
import { useDevice } from '@/utils/deviceUtils';
import { getRandomDefaultCover } from '@/utils/defaultCover';
// 创建响应式数据对象,包含静态资源
const staticAssets = reactive({
@@ -82,6 +83,7 @@ const listContainerRef = ref<HTMLElement>();
const hasMoreData = ref(true);
const isLoadingMore = ref(false);
const hasTriggeredLoadMore = ref(false); // 标记是否触发过加载更多
const coverUrls = ref<Map<string, string>>(new Map()); // 存储资源封面URL
// 设备检测
const { isMobileDevice } = useDevice();
@@ -171,6 +173,9 @@ async function loadResources(isAppend = false) {
const totalPages = Math.ceil(total.value / pageSize.value);
hasMoreData.value = currentPage.value < totalPages;
// 加载封面
await loadCovers(newData);
// 通知父组件列表已更新
emit('list-updated', resources.value);
}
@@ -254,6 +259,35 @@ async function loadMoreData() {
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({
loadResources,
getResources,

View File

@@ -44,7 +44,7 @@
<!-- 课程封面 -->
<div class="course-cover">
<img
:src="course.coverImage ? FILE_DOWNLOAD_URL + course.coverImage : defaultCover"
:src="getCoverUrl(course)"
:alt="course.name"
class="cover-image"
/>
@@ -111,6 +111,7 @@ import type { Course, PageParam } from '@/types';
import { StudyPlanLayout } from '@/views/user/study-plan';
import defaultCoverImg from '@/assets/imgs/default-course-bg.png'
import { FILE_DOWNLOAD_URL } from '@/config';
import { getRandomDefaultCover } from '@/utils/defaultCover';
defineOptions({
name: 'CourseCenterView'
@@ -124,6 +125,7 @@ const courseList = ref<Course[]>([]);
const total = ref(0);
const isMobile = ref(false);
const hasMore = ref(true);
const coverUrls = ref<Map<string, string>>(new Map()); // 存储课程封面URL
// 默认封面图片
const defaultCover = defaultCoverImg;
@@ -200,6 +202,9 @@ async function loadMoreCourses() {
pageParam.value.pageNumber = nextPage;
total.value = res.pageParam?.totalElements || 0;
// 加载封面
await loadCovers(res.dataList);
// 检查是否还有更多数据
hasMore.value = courseList.value.length < total.value;
} else {
@@ -236,6 +241,9 @@ async function loadCourseList(isRefresh = false) {
courseList.value = res.dataList || [];
total.value = res.pageParam?.totalElements || 0;
// 加载封面
await loadCovers(res.dataList || []);
// 移动端下检查是否还有更多数据
if (isMobile.value) {
hasMore.value = courseList.value.length < total.value;
@@ -292,6 +300,35 @@ function getCategoryName(): string {
// TODO: 从 courseTags 中获取第一个标签作为分类
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>
<style lang="scss" scoped>