287 lines
5.9 KiB
Vue
287 lines
5.9 KiB
Vue
<template>
|
|
<div class="resource-list">
|
|
<div class="list-container" ref="listContainerRef">
|
|
<div
|
|
v-for="resource in resources"
|
|
:key="resource.resourceID "
|
|
class="resource-item"
|
|
@click="handleResourceClick(resource)"
|
|
>
|
|
<div class="resource-cover">
|
|
<img
|
|
:src="resource.coverImage ? (FILE_DOWNLOAD_URL + resource.coverImage) : defaultArticleImg"
|
|
alt="cover"
|
|
/>
|
|
</div>
|
|
<div class="resource-info">
|
|
<h3 class="resource-title">{{ resource.title }}</h3>
|
|
<div class="resource-collect">
|
|
<img src="@/assets/imgs/star-icon.svg" alt="collect" />
|
|
<span>{{ (resource.collectCount || 0) > 0 ? '已收藏' : '收藏' }}</span>
|
|
</div>
|
|
<p class="resource-summary">{{ resource.summary || '暂无简介' }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="loading" class="loading-more">加载中...</div>
|
|
<div v-if="resources.length === 0 && !loading" class="empty">暂无数据</div>
|
|
</div>
|
|
|
|
<div v-if="total > 0" class="pagination-wrapper">
|
|
<el-pagination
|
|
v-model:current-page="currentPage"
|
|
:page-size="pageSize"
|
|
:total="total"
|
|
layout="prev, pager, next, jumper"
|
|
@current-change="handlePageChange"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch, onMounted, nextTick } from 'vue';
|
|
import { resourceApi } from '@/apis/resource';
|
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
|
import type { Resource, ResourceSearchParams } from '@/types/resource';
|
|
import type { PageParam } from '@/types';
|
|
import defaultArticleImg from '@/assets/imgs/article-default.png';
|
|
|
|
interface Props {
|
|
tagID?: string;
|
|
searchKeyword?: string;
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
const emit = defineEmits<{
|
|
'resource-click': [resource: Resource];
|
|
'list-updated': [resources: Resource[]];
|
|
}>();
|
|
|
|
const resources = ref<Resource[]>([]);
|
|
const loading = ref(false);
|
|
const total = ref(0);
|
|
const currentPage = ref(1);
|
|
const pageSize = 10;
|
|
const listContainerRef = ref<HTMLElement>();
|
|
|
|
onMounted(() => {
|
|
loadResources();
|
|
});
|
|
|
|
watch(() => [props.tagID, props.searchKeyword], () => {
|
|
currentPage.value = 1;
|
|
loadResources();
|
|
}, { deep: true });
|
|
|
|
async function loadResources() {
|
|
if (loading.value) return;
|
|
|
|
loading.value = true;
|
|
|
|
try {
|
|
const filter: ResourceSearchParams = {
|
|
tagID: props.tagID,
|
|
keyword: props.searchKeyword,
|
|
// status: 1 // 只加载已发布的
|
|
};
|
|
|
|
const pageParam: PageParam = {
|
|
pageNumber: currentPage.value,
|
|
pageSize: pageSize
|
|
};
|
|
|
|
const res = await resourceApi.getResourcePage(pageParam, filter);
|
|
|
|
if (res.success && res.dataList) {
|
|
resources.value = res.dataList;
|
|
total.value = res.pageDomain?.pageParam.totalElements || 0;
|
|
|
|
// 通知父组件列表已更新
|
|
emit('list-updated', res.dataList);
|
|
|
|
// 重置滚动位置到顶部
|
|
await nextTick();
|
|
if (listContainerRef.value) {
|
|
listContainerRef.value.scrollTop = 0;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('加载资源列表失败:', error);
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
function handlePageChange(page: number) {
|
|
currentPage.value = page;
|
|
loadResources();
|
|
}
|
|
|
|
function getResources() {
|
|
return resources.value;
|
|
}
|
|
|
|
function getPageInfo() {
|
|
return { currentPage: currentPage.value, total: total.value };
|
|
}
|
|
|
|
async function loadNextPage() {
|
|
const totalPages = Math.ceil(total.value / pageSize);
|
|
if (currentPage.value < totalPages) {
|
|
currentPage.value++;
|
|
await loadResources();
|
|
}
|
|
}
|
|
|
|
async function loadPrevPage() {
|
|
if (currentPage.value > 1) {
|
|
currentPage.value--;
|
|
await loadResources();
|
|
}
|
|
}
|
|
|
|
function handleResourceClick(resource: Resource) {
|
|
emit('resource-click', resource);
|
|
}
|
|
|
|
defineExpose({
|
|
loadResources,
|
|
getResources,
|
|
getPageInfo,
|
|
loadNextPage,
|
|
loadPrevPage
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.resource-list {
|
|
flex: 1;
|
|
align-self: stretch;
|
|
background: #FFFFFF;
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.list-container {
|
|
flex: 1;
|
|
padding: 30px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 30px;
|
|
min-height: 400px;
|
|
}
|
|
|
|
.resource-item {
|
|
display: flex;
|
|
gap: 20px;
|
|
padding-bottom: 30px;
|
|
border-bottom: 1px solid #EEEEEE;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
|
|
&:last-child {
|
|
border-bottom: none;
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
&:hover {
|
|
.resource-title {
|
|
color: #C62828;
|
|
}
|
|
}
|
|
}
|
|
|
|
.resource-cover {
|
|
width: 202px;
|
|
height: 123px;
|
|
border-radius: 6px;
|
|
overflow: hidden;
|
|
flex-shrink: 0;
|
|
|
|
img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.default-cover {
|
|
width: 100%;
|
|
height: 100%;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
}
|
|
}
|
|
|
|
.resource-info {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.resource-title {
|
|
font-family: 'PingFang SC';
|
|
font-weight: 600;
|
|
font-size: 28px;
|
|
line-height: 28px;
|
|
color: #C62828;
|
|
margin: 0;
|
|
transition: color 0.3s;
|
|
}
|
|
|
|
.resource-collect {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
|
|
img {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
span {
|
|
font-family: 'PingFang SC';
|
|
font-weight: 400;
|
|
font-size: 14px;
|
|
line-height: 24px;
|
|
color: #979797;
|
|
}
|
|
}
|
|
|
|
.resource-summary {
|
|
font-family: 'PingFang SC';
|
|
font-weight: 400;
|
|
font-size: 16px;
|
|
line-height: 24px;
|
|
color: #334155;
|
|
margin: 8px 0 0 0;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 3;
|
|
line-clamp: 3;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.loading-more,
|
|
.empty {
|
|
text-align: center;
|
|
padding: 20px;
|
|
font-family: 'PingFang SC';
|
|
font-size: 14px;
|
|
color: #979797;
|
|
}
|
|
|
|
.pagination-wrapper {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 30px 0;
|
|
background: #FFFFFF;
|
|
border-top: 1px solid #EEEEEE;
|
|
}
|
|
</style>
|
|
|