Spaces:
Sleeping
Sleeping
<template> | |
<div class="resource-card"> | |
<!-- 详情弹窗 --> | |
<el-dialog | |
v-model="showDetail" | |
:title="currentResource?.title" | |
width="700px" | |
class="resource-detail-dialog" | |
destroy-on-close | |
> | |
<div v-if="currentResource" class="detail-content"> | |
<div class="detail-cover"> | |
<el-image | |
class="cover-image" | |
:src="getProxyImageUrl(currentResource.image as string)" | |
:fit="currentResource.image ? 'cover' : 'contain'" | |
/> | |
<el-tag | |
class="cloud-type" | |
:type="store.tagColor[currentResource.cloudType as keyof TagColor]" | |
effect="dark" | |
round | |
> | |
{{ currentResource.cloudType }} | |
</el-tag> | |
</div> | |
<div class="detail-info"> | |
<h3 class="detail-title"> | |
<el-link :href="currentResource.cloudLinks[0]" target="_blank" :underline="false"> | |
{{ currentResource.title }} | |
</el-link> | |
</h3> | |
<div class="detail-description" v-html="currentResource.content" /> | |
<div v-if="currentResource.tags?.length" class="detail-tags"> | |
<div class="tags-list"> | |
<el-tag | |
v-for="tag in currentResource.tags" | |
:key="tag" | |
class="tag-item" | |
@click="searchMovieforTag(tag)" | |
> | |
{{ tag }} | |
</el-tag> | |
</div> | |
</div> | |
</div> | |
</div> | |
<template #footer> | |
<div class="dialog-footer"> | |
<el-button type="primary" plain @click="currentResource && handleJump(currentResource)" | |
>跳转</el-button | |
> | |
<el-button | |
v-if="currentResource?.isSupportSave" | |
type="primary" | |
@click="currentResource && handleSave(currentResource)" | |
>转存</el-button | |
> | |
</div> | |
</template> | |
</el-dialog> | |
<div v-for="group in store.resources" :key="group.id" class="resource-group"> | |
<div | |
:class="{ 'group-header': true, 'is-active': group.displayList }" | |
@click="group.displayList = !group.displayList" | |
> | |
<el-link | |
class="group-title" | |
:href="`https://t.me/s/${group.id}`" | |
target="_blank" | |
:underline="false" | |
@click.stop | |
> | |
<el-image | |
:src="getProxyImageUrl(group.channelInfo.channelLogo)" | |
:fit="group.channelInfo.channelLogo ? 'cover' : 'contain'" | |
class="channel-logo" | |
scroll-container="#pc-resources-content" | |
loading="lazy" | |
/> | |
<span>{{ group.channelInfo.name }}</span> | |
<span class="item-count">({{ group.list.length }})</span> | |
</el-link> | |
<el-tooltip effect="dark" :content="group.displayList ? '收起' : '展开'" placement="top"> | |
<el-button class="toggle-btn" type="text"> | |
<el-icon :class="{ 'is-active': group.displayList }"> | |
<ArrowDown /> | |
</el-icon> | |
</el-button> | |
</el-tooltip> | |
</div> | |
<div v-if="group.displayList" class="group-content"> | |
<div class="card-grid"> | |
<el-card | |
v-for="resource in group.list" | |
:key="resource.messageId" | |
class="resource-card-item" | |
:body-style="{ padding: '0' }" | |
> | |
<div class="card-wrapper"> | |
<div class="card-cover"> | |
<el-image | |
loading="lazy" | |
class="cover-image" | |
:src="getProxyImageUrl(resource.image as string)" | |
:fit="resource.image ? 'cover' : 'contain'" | |
:alt="resource.title" | |
@click="showResourceDetail(resource)" | |
/> | |
<el-tag | |
class="cloud-type" | |
:type="store.tagColor[resource.cloudType as keyof TagColor]" | |
effect="dark" | |
round | |
size="small" | |
> | |
{{ resource.cloudType }} | |
</el-tag> | |
</div> | |
<div class="card-body"> | |
<el-link | |
class="card-title" | |
:href="resource.cloudLinks[0]" | |
target="_blank" | |
:underline="false" | |
> | |
{{ resource.title }} | |
</el-link> | |
<div | |
class="card-content" | |
@click="showResourceDetail(resource)" | |
v-html="resource.content" | |
/> | |
<div v-if="resource.tags?.length" class="card-tags"> | |
<div class="tags-list"> | |
<el-tag | |
v-for="tag in resource.tags" | |
:key="tag" | |
class="tag-item" | |
@click="searchMovieforTag(tag)" | |
> | |
{{ tag }} | |
</el-tag> | |
</div> | |
</div> | |
<div class="card-footer"> | |
<el-button type="primary" plain @click="handleJump(resource)">跳转</el-button> | |
<el-button | |
v-if="resource.isSupportSave" | |
type="primary" | |
@click="handleSave(resource)" | |
>转存</el-button | |
> | |
</div> | |
</div> | |
</div> | |
</el-card> | |
</div> | |
<div class="load-more"> | |
<el-button :loading="group.loading" @click="handleLoadMore(group.id)"> | |
<el-icon><Plus /></el-icon> | |
加载更多 | |
</el-button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script setup lang="ts"> | |
import { useResourceStore } from "@/stores/resource"; | |
import { ref } from "vue"; | |
import type { ResourceItem, TagColor } from "@/types"; | |
import { ArrowDown, Plus } from "@element-plus/icons-vue"; | |
import { getProxyImageUrl } from "@/utils/image"; | |
const store = useResourceStore(); | |
const showDetail = ref(false); | |
const currentResource = ref<ResourceItem | null>(null); | |
const emit = defineEmits(["save", "loadMore", "jump", "searchMovieforTag"]); | |
const handleSave = (resource: ResourceItem) => { | |
if (showDetail.value) { | |
showDetail.value = false; | |
} | |
emit("save", resource); | |
}; | |
const handleJump = (resource: ResourceItem) => { | |
emit("jump", resource); | |
}; | |
const showResourceDetail = (resource: ResourceItem) => { | |
currentResource.value = resource; | |
showDetail.value = true; | |
}; | |
const searchMovieforTag = (tag: string) => { | |
emit("searchMovieforTag", tag); | |
}; | |
const handleLoadMore = (channelId: string) => { | |
emit("loadMore", channelId); | |
}; | |
</script> | |
<style lang="scss" scoped> | |
@import "@/styles/common.scss"; | |
.resource-card { | |
position: relative; | |
height: 100%; | |
// 资源组 | |
.resource-group { | |
background: var(--theme-card-bg); | |
backdrop-filter: var(--theme-blur); | |
-webkit-backdrop-filter: var(--theme-blur); | |
margin-bottom: 24px; | |
border-radius: var(--theme-radius); | |
border: 1px solid rgba(0, 0, 0, 0.08); | |
transition: var(--theme-transition); | |
&:last-child { | |
margin-bottom: 100px; | |
} | |
} | |
// 组标题 | |
.group-header { | |
@include flex-center; | |
justify-content: space-between; | |
padding: 12px 20px; | |
border-bottom: 1px solid rgba(0, 0, 0, 0.06); | |
position: sticky; | |
top: 0; | |
background: var(--theme-card-bg); | |
backdrop-filter: var(--theme-blur); | |
-webkit-backdrop-filter: var(--theme-blur); | |
z-index: 10; | |
border-radius: var(--theme-radius); | |
overflow: hidden; | |
cursor: pointer; | |
&.is-active { | |
border-radius: var(--theme-radius) var(--theme-radius) 0 0; | |
} | |
.group-title { | |
@include flex-center; | |
gap: 12px; | |
font-size: 16px; | |
color: var(--theme-text-primary); | |
transition: var(--theme-transition); | |
.channel-logo { | |
width: 32px; | |
height: 32px; | |
border-radius: 50%; | |
overflow: hidden; | |
box-shadow: var(--theme-shadow-sm); | |
margin-right: 8px; | |
} | |
.item-count { | |
font-size: 13px; | |
color: var(--theme-text-secondary); | |
} | |
&:hover { | |
color: var(--theme-primary); | |
transform: translateY(-1px); | |
} | |
} | |
.toggle-btn { | |
width: 32px; | |
height: 32px; | |
padding: 0; | |
color: var(--theme-text-regular); | |
transition: var(--theme-transition); | |
.el-icon { | |
font-size: 16px; | |
transition: transform 0.3s ease; | |
&.is-active { | |
transform: rotate(180deg); | |
} | |
} | |
&:hover { | |
color: var(--theme-primary); | |
transform: translateY(-1px); | |
} | |
} | |
} | |
// 组内容 | |
.group-content { | |
padding: 20px; | |
} | |
// 卡片网格 | |
.card-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); | |
gap: 24px; | |
grid-auto-rows: min-content; | |
} | |
// 资源卡片 | |
.resource-card-item { | |
border-radius: var(--theme-radius); | |
transition: var(--theme-transition); | |
overflow: hidden; | |
max-width: 460px; | |
margin: 0 auto; | |
width: 100%; | |
height: fit-content; | |
&:hover { | |
transform: translateY(-2px); | |
box-shadow: var(--theme-shadow); | |
} | |
.card-wrapper { | |
display: flex; | |
gap: 20px; | |
padding: 16px; | |
height: 100%; | |
} | |
.card-cover { | |
position: relative; | |
width: 120px; | |
height: 180px; | |
flex-shrink: 0; | |
.cover-image { | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
border-radius: var(--theme-radius); | |
cursor: pointer; | |
transition: opacity 0.3s ease; | |
&:hover { | |
opacity: 0.85; | |
} | |
} | |
.cloud-type { | |
position: absolute; | |
top: 8px; | |
left: 8px; | |
z-index: 1; | |
} | |
} | |
.card-body { | |
flex: 1; | |
min-width: 0; | |
display: flex; | |
flex-direction: column; | |
gap: 12px; | |
.card-title { | |
display: -webkit-box; | |
-webkit-line-clamp: 2; | |
-webkit-box-orient: vertical; | |
overflow: hidden; | |
font-size: 16px; | |
line-height: 1.5; | |
color: var(--theme-text-primary); | |
word-break: break-word; | |
height: 3em; | |
transition: var(--theme-transition); | |
&:hover { | |
color: var(--theme-primary); | |
} | |
} | |
.card-content { | |
display: -webkit-box; | |
-webkit-line-clamp: 3; | |
-webkit-box-orient: vertical; | |
overflow: hidden; | |
font-size: 14px; | |
line-height: 1.6; | |
color: var(--theme-text-regular); | |
cursor: pointer; | |
transition: color 0.3s ease; | |
&:hover { | |
color: var(--theme-text-primary); | |
} | |
} | |
.card-tags { | |
margin-top: auto; | |
max-height: 88px; | |
overflow: hidden; | |
.tags-label { | |
font-size: 13px; | |
color: var(--theme-text-secondary); | |
margin-right: 8px; | |
display: block; | |
margin-bottom: 8px; | |
} | |
.tags-list { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 8px; | |
max-height: 72px; | |
overflow: hidden; | |
.tag-item { | |
cursor: pointer; | |
transition: var(--theme-transition); | |
margin: 0; | |
height: 24px; | |
&:hover { | |
color: var(--theme-primary); | |
border-color: var(--theme-primary); | |
transform: translateY(-1px); | |
} | |
} | |
} | |
} | |
} | |
.card-footer { | |
@include flex-center; | |
justify-content: flex-end; | |
margin-top: 8px; | |
.el-button { | |
padding: 6px 16px; | |
font-size: 14px; | |
height: 32px; | |
min-width: 80px; | |
&:hover { | |
transform: translateY(-1px); | |
box-shadow: var(--theme-shadow-sm); | |
} | |
} | |
} | |
} | |
// 加载更多 | |
.load-more { | |
@include flex-center; | |
position: relative; | |
padding: 32px 0 8px; | |
margin-top: 16px; | |
&::before { | |
content: ""; | |
position: absolute; | |
left: 0; | |
right: 0; | |
top: 0; | |
height: 1px; | |
background: linear-gradient( | |
90deg, | |
transparent, | |
var(--el-border-color-lighter) 20%, | |
var(--el-border-color-lighter) 80%, | |
transparent | |
); | |
} | |
.el-button { | |
min-width: 160px; | |
height: 40px; | |
border-radius: 20px; | |
font-size: 14px; | |
color: var(--theme-text-regular); | |
background: var(--theme-card-bg); | |
border: 1px solid var(--el-border-color-lighter); | |
transition: var(--theme-transition); | |
position: relative; | |
overflow: hidden; | |
&::after { | |
content: ""; | |
position: absolute; | |
inset: 0; | |
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); | |
transform: translateX(-100%); | |
transition: transform 0.6s ease; | |
} | |
&:hover { | |
color: var(--theme-primary); | |
border-color: var(--theme-primary); | |
background: var(--el-color-primary-light-9); | |
&::after { | |
transform: translateX(100%); | |
} | |
} | |
&.is-loading { | |
color: var(--theme-text-secondary); | |
&::after { | |
display: none; | |
} | |
} | |
.el-icon { | |
margin-right: 6px; | |
font-size: 16px; | |
} | |
} | |
} | |
// 详情弹窗样式 | |
.resource-detail-dialog { | |
:deep(.el-dialog__body) { | |
padding: 20px; | |
} | |
.detail-content { | |
display: flex; | |
gap: 24px; | |
} | |
.detail-cover { | |
position: relative; | |
width: 200px; | |
flex-shrink: 0; | |
.cover-image { | |
width: 100%; | |
height: 300px; | |
border-radius: var(--theme-radius); | |
overflow: hidden; | |
} | |
.cloud-type { | |
position: absolute; | |
top: 8px; | |
left: 8px; | |
z-index: 1; | |
} | |
} | |
.detail-info { | |
flex: 1; | |
min-width: 0; | |
.detail-title { | |
font-size: 18px; | |
margin: 0 0 16px; | |
line-height: 1.5; | |
color: var(--theme-text-primary); | |
} | |
.detail-description { | |
font-size: 14px; | |
line-height: 1.6; | |
color: var(--theme-text-regular); | |
margin-bottom: 20px; | |
} | |
.detail-tags { | |
.tags-label { | |
font-size: 13px; | |
color: var(--theme-text-secondary); | |
margin-right: 8px; | |
} | |
.tags-list { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 8px; | |
margin-top: 8px; | |
.tag-item { | |
cursor: pointer; | |
transition: var(--theme-transition); | |
&:hover { | |
color: var(--theme-primary); | |
border-color: var(--theme-primary); | |
transform: translateY(-1px); | |
} | |
} | |
} | |
} | |
} | |
.dialog-footer { | |
display: flex; | |
justify-content: flex-end; | |
padding-top: 16px; | |
} | |
} | |
} | |
</style> | |