refactor:pc views

This commit is contained in:
jiangrui
2025-03-05 18:20:54 +08:00
parent 7bcec7e3b4
commit 1f3a83b84d
25 changed files with 2949 additions and 1117 deletions

View File

@@ -1,61 +1,155 @@
<template>
<div class="resource-card-list">
<div v-for="group in store.resources" :key="group.id" class="resource-list">
<div class="group-header">
<el-link :href="`https://t.me/s/${group.id}`" target="_blank" :underline="false">
<el-image :src="group.channelInfo.channelLogo" class="channel-logo" fit="cover" lazy />
{{ group.channelInfo.name }}</el-link
>
<el-icon class="header-icon" @click="group.displayList = !group.displayList"
><ArrowDown
/></el-icon>
</div>
<div v-show="group.displayList" class="card-item-list">
<div v-for="resource in group.list" :key="resource.messageId" class="card-item-content">
<el-card class="card-item">
<el-image
class="card-item-image"
:src="`/tele-images/?url=${encodeURIComponent(resource.image as string)}`"
fit="cover"
lazy
:alt="resource.title"
hide-on-click-modal
:preview-src-list="[
`${location.origin}/tele-images/?url=${encodeURIComponent(resource.image as string)}`,
]"
/>
<el-link :href="resource.cloudLinks[0]" target="_blank" :underline="false"
><div class="item-name">{{ resource.title }}</div></el-link
>
<div class="item-description" v-html="resource.content"></div>
<div v-if="resource.tags && resource.tags.length" class="tags-list">
<span>标签</span>
<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="`/tele-images/?url=${encodeURIComponent(currentResource.image as string)}`"
fit="cover"
/>
<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">
<span class="tags-label">标签</span>
<div class="tags-list">
<el-tag
v-for="item in resource.tags"
:key="item"
class="resource_tag"
@click="searchMovieforTag(item)"
v-for="tag in currentResource.tags"
:key="tag"
class="tag-item"
@click="searchMovieforTag(tag)"
>
{{ item }}
{{ tag }}
</el-tag>
</div>
<template #footer>
<div class="item-footer">
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button 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">
<el-link
class="group-title"
:href="`https://t.me/s/${group.id}`"
target="_blank"
:underline="false"
>
<el-image :src="group.channelInfo.channelLogo" class="channel-logo" fit="cover" 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" @click="group.displayList = !group.displayList">
<el-icon :class="{ 'is-active': group.displayList }">
<ArrowDown />
</el-icon>
</el-button>
</el-tooltip>
</div>
<div v-show="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
class="cover-image"
:src="`/tele-images/?url=${encodeURIComponent(resource.image as string)}`"
fit="cover"
lazy
: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>
<el-button @click="handleSave(resource)">转存</el-button>
</div>
</template>
<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">
<span class="tags-label">标签</span>
<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" @click="handleSave(resource)">转存</el-button>
</div>
</div>
</div>
</el-card>
</div>
</div>
<div v-show="group.displayList" class="load-more">
<el-button @click="handleLoadMore(group.id)"> 加载更多 </el-button>
<div class="load-more">
<el-button :loading="group.loading" @click="handleLoadMore(group.id)">
<el-icon><Plus /></el-icon>
加载更多
</el-button>
</div>
</div>
</div>
</div>
@@ -63,19 +157,28 @@
<script setup lang="ts">
import { useResourceStore } from "@/stores/resource";
import { computed } from "vue";
import { ref } from "vue";
import type { ResourceItem, TagColor } from "@/types";
import { ArrowDown, Plus } from "@element-plus/icons-vue";
const store = useResourceStore();
const location = computed(() => window.location);
const showDetail = ref(false);
const currentResource = ref<ResourceItem | null>(null);
const emit = defineEmits(["save", "loadMore", "searchMovieforTag"]);
const handleSave = (resource: ResourceItem) => {
if (showDetail.value) {
showDetail.value = false;
}
emit("save", resource);
};
const showResourceDetail = (resource: ResourceItem) => {
currentResource.value = resource;
showDetail.value = true;
};
const searchMovieforTag = (tag: string) => {
emit("searchMovieforTag", tag);
};
@@ -85,105 +188,391 @@ const handleLoadMore = (channelId: string) => {
};
</script>
<style scoped>
.resource-list {
margin-bottom: 30px;
padding: 20px;
border-radius: 15px;
background-color: var(--theme-other_background);
&:last-child {
margin-bottom: 0;
}
}
.card-item-list {
display: grid;
grid-template-columns: repeat(auto-fill, 220px);
grid-row-gap: 30px;
justify-content: space-between;
margin-top: 20px;
/* grid-column-gap: auto-fill; */
/* flex-wrap: wrap; */
}
.card-item-content {
/* height: 520px; */
}
.channel-logo {
height: 40px;
width: 40px;
border-radius: 50%;
overflow: hidden;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
margin-right: 10px;
}
.load-more {
margin-top: 40px;
width: 100%;
text-align: center;
}
.card-item {
max-width: 480px;
<style lang="scss" scoped>
@import "@/styles/common.scss";
.resource-card {
position: relative;
height: 100%;
border-radius: 20px;
}
.card-item-image {
border-radius: 20px;
width: 100%;
height: 220px;
}
.item-name,
.item-description {
max-width: 100%;
margin: 15px 0;
-webkit-box-orient: vertical;
display: -webkit-box;
-webkit-line-clamp: 2;
overflow: hidden;
white-space: all;
}
.item-description {
-webkit-line-clamp: 4;
margin-top: 0;
height: 100px;
}
.item-name {
height: 58px;
font-size: 18px;
}
.tags-list {
display: flex;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
height: 58px;
overflow: hidden;
}
.resource_tag {
cursor: pointer;
margin-right: 10px;
margin-bottom: 5px;
}
.group-header {
height: 50px;
line-height: 50px;
text-align: left;
padding: 0 15px;
font-size: 20px;
display: flex;
align-items: center;
justify-content: space-between;
/* text-align: center; */
.el-link {
font-size: 22px;
// 资源组
.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;
}
}
.header-icon {
cursor: pointer;
width: 50px;
height: 50px;
// 组标题
.group-header {
@include flex-center;
justify-content: space-between;
padding: 12px 20px;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
.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);
}
.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(400px, 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;
}
}
}
.item-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
</style>