Spaces:
Sleeping
Sleeping
| <template> | |
| <div class="pc-resources"> | |
| <!-- 头部工具栏 --> | |
| <div class="pc-resources__header"> | |
| <div class="header__left"> | |
| <el-tooltip effect="dark" content="点击获取最新资源" placement="bottom"> | |
| <el-button class="refresh-btn" type="text" @click="refreshResources"> | |
| <el-icon><Refresh /></el-icon> | |
| <span>最新资源</span> | |
| <span class="update-time"> (上次刷新时间:{{ resourceStore.lastUpdateTime }}) </span> | |
| </el-button> | |
| </el-tooltip> | |
| </div> | |
| <div class="header__right"> | |
| <el-tooltip | |
| effect="dark" | |
| :content=" | |
| userStore.imagesSource === 'local' ? '图片切换到代理模式' : '图片切换到直连模式' | |
| " | |
| placement="bottom" | |
| > | |
| <el-button | |
| type="text" | |
| class="view-toggle" | |
| @click=" | |
| userStore.setImagesSource(userStore.imagesSource === 'proxy' ? 'local' : 'proxy') | |
| " | |
| > | |
| <el-icon> | |
| <component :is="userStore.imagesSource === 'proxy' ? 'Guide' : 'Location'" /> | |
| </el-icon> | |
| </el-button> | |
| </el-tooltip> | |
| <el-tooltip | |
| effect="dark" | |
| :content="userStore.displayStyle === 'card' ? '切换到列表视图' : '切换到卡片视图'" | |
| placement="bottom" | |
| > | |
| <el-button | |
| type="text" | |
| class="view-toggle" | |
| @click="setDisplayStyle(userStore.displayStyle === 'card' ? 'table' : 'card')" | |
| > | |
| <el-icon> | |
| <component :is="userStore.displayStyle === 'card' ? 'Menu' : 'Grid'" /> | |
| </el-icon> | |
| </el-button> | |
| </el-tooltip> | |
| </div> | |
| </div> | |
| <!-- 资源列表 --> | |
| <div id="pc-resources-content" ref="contentRef" class="pc-resources__content"> | |
| <component | |
| :is="userStore.displayStyle === 'table' ? ResourceTable : ResourceCard" | |
| v-if="resourceStore.resources.length > 0" | |
| @load-more="handleLoadMore" | |
| @jump="handleJump" | |
| @search-moviefor-tag="searchMovieforTag" | |
| @save="handleSave" | |
| /> | |
| <!-- 空状态 --> | |
| <div v-if="resourceStore.resources.length === 0" class="pc-resources__empty"> | |
| <el-empty :image-size="200"> | |
| <template #description> | |
| <p class="empty-text">暂无资源</p> | |
| <el-tooltip effect="dark" content="点击获取最新资源" placement="top"> | |
| <el-button type="primary" @click="refreshResources"> | |
| <el-icon><Refresh /></el-icon> | |
| <span>刷新资源</span> | |
| </el-button> | |
| </el-tooltip> | |
| </template> | |
| </el-empty> | |
| </div> | |
| </div> | |
| <!-- 返回顶部 --> | |
| <el-backtop :bottom="40" :right="40" target=".pc-resources__content"> | |
| <div class="pc-resources__backtop"> | |
| <el-icon><ArrowUp /></el-icon> | |
| </div> | |
| </el-backtop> | |
| <!-- 保存对话框 --> | |
| <el-dialog | |
| v-if="currentResource" | |
| v-model="saveDialogVisible" | |
| :title="saveDialogMap[saveDialogStep].title" | |
| width="580px" | |
| destroy-on-close | |
| > | |
| <template #header="{ titleId }"> | |
| <div class="dialog-header"> | |
| <h3 :id="titleId"> | |
| <div class="title-main"> | |
| <el-tag | |
| :type="resourceStore.tagColor[currentResource.cloudType as keyof TagColor]" | |
| effect="dark" | |
| round | |
| > | |
| {{ currentResource.cloudType }} | |
| </el-tag> | |
| {{ saveDialogMap[saveDialogStep].title }} | |
| </div> | |
| <div class="title-sub"> | |
| <span class="resource-title" :title="currentResource.title"> | |
| {{ currentResource.title }} | |
| </span> | |
| <span | |
| v-if="resourceStore.shareInfo.fileSize && saveDialogStep === 1" | |
| class="file-size" | |
| > | |
| ({{ formattedFileSize(resourceStore.shareInfo.fileSize) }}) | |
| </span> | |
| </div> | |
| </h3> | |
| </div> | |
| </template> | |
| <div v-loading="resourceStore.loadTree"> | |
| <resource-select | |
| v-if="saveDialogVisible && saveDialogStep === 1 && resourceStore.resourceSelect.length" | |
| :cloud-type="currentResource.cloudType" | |
| /> | |
| <folder-select | |
| v-if="saveDialogVisible && saveDialogStep === 2" | |
| :cloud-type="currentResource.cloudType" | |
| @select="handleFolderSelect" | |
| @close="saveDialogVisible = false" | |
| /> | |
| </div> | |
| <template #footer> | |
| <div class="dialog-footer"> | |
| <el-button @click="saveDialogVisible = false">取消</el-button> | |
| <el-button type="primary" @click="handleConfirmClick"> | |
| {{ saveDialogMap[saveDialogStep].buttonText }} | |
| </el-button> | |
| </div> | |
| </template> | |
| </el-dialog> | |
| <!-- 加载状态 --> | |
| <div v-if="resourceStore.loading" class="pc-resources__loading"> | |
| <div class="loading-text">加载中...</div> | |
| <div class="is-loading"> | |
| <el-icon><Loading /></el-icon> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| <script setup lang="ts"> | |
| import { ref } from "vue"; | |
| import { useResourceStore } from "@/stores/resource"; | |
| import { useUserSettingStore } from "@/stores/userSetting"; | |
| import FolderSelect from "@/components/Home/FolderSelect.vue"; | |
| import ResourceSelect from "@/components/Home/ResourceSelect.vue"; | |
| import ResourceTable from "@/components/Home/ResourceTable.vue"; | |
| import { formattedFileSize } from "@/utils/index"; | |
| import type { ResourceItem, TagColor } from "@/types"; | |
| import { onMounted, onBeforeUnmount } from "vue"; | |
| import ResourceCard from "@/components/Home/ResourceCard.vue"; | |
| import { useRouter } from "vue-router"; | |
| import { ElMessage } from "element-plus"; | |
| import { ArrowUp } from "@element-plus/icons-vue"; | |
| const router = useRouter(); | |
| const resourceStore = useResourceStore(); | |
| const userStore = useUserSettingStore(); | |
| const saveDialogVisible = ref(false); | |
| const currentResource = ref<ResourceItem | null>(null); | |
| const currentFolderId = ref<string | null>(null); | |
| const saveDialogStep = ref<1 | 2>(1); | |
| const refreshResources = async () => { | |
| resourceStore.searchResources("", false); | |
| }; | |
| const saveDialogMap = { | |
| 1: { | |
| title: "选择资源", | |
| buttonText: "下一步", | |
| }, | |
| 2: { | |
| title: "选择保存目录", | |
| buttonText: "保存到此目录", | |
| }, | |
| }; | |
| const handleSave = async (resource: ResourceItem) => { | |
| currentResource.value = resource; | |
| saveDialogVisible.value = true; | |
| saveDialogStep.value = 1; | |
| if (!(await resourceStore.getResourceListAndSelect(currentResource.value))) { | |
| saveDialogVisible.value = false; | |
| } | |
| }; | |
| const handleFolderSelect = async (folderId: string) => { | |
| if (!currentResource.value) return; | |
| currentFolderId.value = folderId; | |
| }; | |
| const handleConfirmClick = async () => { | |
| if (saveDialogStep.value === 1) { | |
| const selectedFiles = resourceStore.resourceSelect.filter((x) => x.isChecked); | |
| if (selectedFiles.length === 0) { | |
| ElMessage.warning("请选择要保存的资源"); | |
| return; | |
| } | |
| saveDialogStep.value = 2; | |
| } else { | |
| handleSaveBtnClick(); | |
| } | |
| }; | |
| const handleSaveBtnClick = async () => { | |
| if (!currentResource.value || !currentFolderId.value) return; | |
| saveDialogVisible.value = false; | |
| await resourceStore.saveResource(currentResource.value, currentFolderId.value); | |
| }; | |
| const setDisplayStyle = (style: string) => { | |
| userStore.setDisplayStyle(style as "card" | "table"); | |
| }; | |
| // 添加加载更多处理函数 | |
| const handleLoadMore = (channelId: string) => { | |
| resourceStore.searchResources("", true, channelId); | |
| }; | |
| const handleJump = (resource: ResourceItem) => { | |
| window.open(resource.cloudLinks[0], "_blank"); | |
| }; | |
| const searchMovieforTag = (tag: string) => { | |
| router.push({ path: "/resource", query: { keyword: tag } }); | |
| }; | |
| // 页面进入 设置缓存的数据源 | |
| onMounted(() => { | |
| const lastResourceList = localStorage.getItem("last_resource_list"); | |
| if (lastResourceList) { | |
| resourceStore.resources = JSON.parse(lastResourceList).list; | |
| } | |
| }); | |
| // 页面销毁 清除搜索词 | |
| onBeforeUnmount(() => { | |
| resourceStore.keyword = ""; | |
| }); | |
| </script> | |
| <style lang="scss" scoped> | |
| @import "@/styles/common.scss"; | |
| @import "@/styles/responsive.scss"; | |
| .pc-resources { | |
| // 整体容器 | |
| position: relative; | |
| width: 100%; | |
| // 头部工具栏 | |
| &__header { | |
| @include glass-effect; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| min-height: 48px; | |
| padding: 0 20px; | |
| margin-bottom: 16px; | |
| border-radius: var(--theme-radius); | |
| border: 1px solid rgba(0, 0, 0, 0.08); | |
| transition: var(--theme-transition); | |
| &:hover { | |
| border-color: var(--theme-primary); | |
| box-shadow: var(--theme-shadow-sm); | |
| } | |
| .header__left { | |
| display: flex; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| gap: 12px; | |
| padding: 8px 0; | |
| .refresh-btn { | |
| @include flex-center; | |
| gap: 8px; | |
| color: var(--theme-text-regular); | |
| transition: var(--theme-transition); | |
| white-space: nowrap; | |
| .el-icon { | |
| font-size: 18px; | |
| color: var(--theme-primary); | |
| } | |
| .update-time { | |
| margin-left: 4px; | |
| font-size: 13px; | |
| color: var(--theme-text-secondary); | |
| white-space: nowrap; | |
| } | |
| &:hover { | |
| color: var(--theme-primary); | |
| transform: translateY(-1px); | |
| } | |
| } | |
| } | |
| .header__right { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| padding: 8px 0; | |
| .view-toggle { | |
| width: 36px; | |
| height: 36px; | |
| padding: 0; | |
| color: var(--theme-text-regular); | |
| border-radius: var(--theme-radius); | |
| transition: var(--theme-transition); | |
| .el-icon { | |
| font-size: 18px; | |
| } | |
| &:hover { | |
| color: var(--theme-primary); | |
| background: rgba(0, 102, 204, 0.05); | |
| transform: translateY(-1px); | |
| } | |
| } | |
| } | |
| } | |
| // 内容区域 | |
| &__content { | |
| position: relative; | |
| width: 100%; | |
| height: calc(100vh - 180px); | |
| overflow-y: auto; | |
| // 资源列表组件样式覆盖 | |
| :deep(.resource-table), | |
| :deep(.resource-card) { | |
| height: 100%; | |
| // 自定义滚动条 | |
| &::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| &::-webkit-scrollbar-thumb { | |
| background: rgba(0, 0, 0, 0.2); | |
| border-radius: 4px; | |
| &:hover { | |
| background: rgba(0, 0, 0, 0.3); | |
| } | |
| } | |
| &::-webkit-scrollbar-track { | |
| background: transparent; | |
| } | |
| } | |
| } | |
| // 加载状态 | |
| &__loading { | |
| @include glass-effect; | |
| @include flex-center; | |
| position: fixed; | |
| inset: 0; | |
| z-index: 2000; | |
| flex-direction: column; | |
| gap: 16px; | |
| background: rgba(255, 255, 255, 0.3); | |
| backdrop-filter: blur(8px); | |
| -webkit-backdrop-filter: blur(8px); | |
| animation: fadeIn 0.3s ease; | |
| .loading-text { | |
| color: var(--theme-text-primary); | |
| font-size: 14px; | |
| text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); | |
| } | |
| .is-loading { | |
| font-size: 24px; | |
| color: var(--theme-primary); | |
| animation: rotating 2s linear infinite; | |
| filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1)); | |
| } | |
| } | |
| // 空状态 | |
| &__empty { | |
| @include flex-center; | |
| flex-direction: column; | |
| gap: 16px; | |
| height: 100%; | |
| .empty-text { | |
| color: var(--theme-text-primary); | |
| font-size: 14px; | |
| margin: 8px 0 16px; | |
| } | |
| .el-button { | |
| @include flex-center; | |
| gap: 8px; | |
| padding: 8px 20px; | |
| height: 40px; | |
| font-size: 14px; | |
| transition: var(--theme-transition); | |
| background: var(--theme-primary); | |
| border-color: var(--theme-primary); | |
| .el-icon { | |
| font-size: 16px; | |
| } | |
| &:hover { | |
| transform: translateY(-2px); | |
| box-shadow: var(--theme-shadow-sm); | |
| } | |
| } | |
| } | |
| // 返回顶部按钮 | |
| &__backtop { | |
| @include flex-center; | |
| width: 40px; | |
| height: 40px; | |
| color: var(--theme-primary); | |
| background: var(--theme-card-bg); | |
| border-radius: var(--theme-radius); | |
| box-shadow: var(--theme-shadow); | |
| transition: var(--theme-transition); | |
| &:hover { | |
| background: var(--theme-primary); | |
| color: #fff; | |
| transform: translateY(-2px); | |
| } | |
| .el-icon { | |
| font-size: 20px; | |
| } | |
| } | |
| } | |
| // 对话框样式 | |
| .dialog-header { | |
| h3 { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| margin: 0; | |
| font-weight: 600; | |
| color: var(--theme-text-primary); | |
| .title-main { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 16px; | |
| } | |
| .title-sub { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 14px; | |
| color: var(--theme-text-secondary); | |
| font-weight: normal; | |
| } | |
| .resource-title { | |
| max-width: 300px; | |
| @include text-ellipsis; | |
| } | |
| } | |
| .file-size { | |
| color: var(--theme-text-secondary); | |
| } | |
| } | |
| .dialog-footer { | |
| display: flex; | |
| justify-content: flex-end; | |
| gap: 12px; | |
| padding-top: 16px; | |
| } | |
| :deep(.el-dialog) { | |
| border-radius: var(--theme-radius); | |
| overflow: hidden; | |
| .el-dialog__header { | |
| margin: 0; | |
| padding: 20px 24px; | |
| border-bottom: 1px solid var(--el-border-color-lighter); | |
| } | |
| .el-dialog__body { | |
| padding: 24px; | |
| } | |
| .el-dialog__footer { | |
| padding: 16px 24px; | |
| border-top: 1px solid var(--el-border-color-lighter); | |
| } | |
| } | |
| // 表格扩展列样式 | |
| :deep(.el-table) { | |
| .el-table__expand-column { | |
| .cell { | |
| padding: 0; | |
| } | |
| } | |
| .el-table__expanded-cell { | |
| padding: 20px; | |
| } | |
| .el-table__expand-icon { | |
| height: 23px; | |
| line-height: 23px; | |
| } | |
| } | |
| // 加载动画 | |
| @keyframes fadeIn { | |
| from { | |
| opacity: 0; | |
| backdrop-filter: blur(0); | |
| } | |
| to { | |
| opacity: 1; | |
| backdrop-filter: blur(8px); | |
| } | |
| } | |
| </style> | |