Spaces:
Sleeping
Sleeping
<template> | |
<div class="folder-select"> | |
<div class="folder-header"> | |
<div class="folder-path"> | |
<el-icon><FolderOpened /></el-icon> | |
<template v-if="folderPath.length"> | |
<span | |
v-for="(folder, index) in folderPath" | |
:key="folder.cid" | |
class="path-item" | |
@click="handlePathClick(index)" | |
> | |
<span class="folder-name">{{ folder.name }}</span> | |
<el-icon v-if="index < folderPath.length - 1"><ArrowRight /></el-icon> | |
</span> | |
</template> | |
<span v-else class="root-path" @click="handlePathClick(-1)">根目录</span> | |
</div> | |
</div> | |
<div class="folder-list"> | |
<div v-if="!folders.length" class="empty-folder"> | |
<el-empty description="暂无文件夹" /> | |
</div> | |
<div | |
v-for="folder in folders" | |
:key="folder.cid" | |
class="folder-item" | |
:class="{ 'is-selected': folder.cid === selectedFolder?.cid }" | |
@click="handleFolderClick(folder)" | |
> | |
<div class="folder-info"> | |
<el-icon><Folder /></el-icon> | |
<span class="folder-name">{{ folder.name }}</span> | |
</div> | |
<el-icon class="arrow-icon"><ArrowRight /></el-icon> | |
</div> | |
</div> | |
<div v-if="loading" class="loading-overlay"> | |
<el-icon class="loading-icon"><Loading /></el-icon> | |
<span>加载中...</span> | |
</div> | |
</div> | |
</template> | |
<script setup lang="ts"> | |
import { ref, defineProps } from "vue"; | |
import { cloud115Api } from "@/api/cloud115"; | |
import { quarkApi } from "@/api/quark"; | |
import type { Folder as FolderType } from "@/types"; | |
import { Folder, FolderOpened, ArrowRight, Loading } from "@element-plus/icons-vue"; | |
import { ElMessage } from "element-plus"; | |
const props = defineProps({ | |
cloudType: { | |
type: String, | |
required: true, | |
}, | |
}); | |
const loading = ref(false); | |
const folders = ref<FolderType[]>([]); | |
const selectedFolder = ref<FolderType | null>(null); | |
const folderPath = ref<FolderType[]>([{ name: "根目录", cid: "0" }]); | |
const emit = defineEmits<{ | |
(e: "select", folderId: string): void; | |
(e: "close"): void; | |
}>(); | |
const cloudTypeApiMap = { | |
pan115: cloud115Api, | |
quark: quarkApi, | |
}; | |
const getList = async (cid: string = "0") => { | |
const api = cloudTypeApiMap[props.cloudType as keyof typeof cloudTypeApiMap]; | |
loading.value = true; | |
try { | |
const res = await api.getFolderList?.(cid); | |
if (res?.code === 0) { | |
folders.value = res.data || []; | |
} else { | |
throw new Error(res?.message); | |
} | |
} catch (error) { | |
ElMessage.error(error instanceof Error ? error.message : "获取目录失败"); | |
emit("close"); | |
} finally { | |
loading.value = false; | |
} | |
}; | |
const handleFolderClick = async (folder: FolderType) => { | |
selectedFolder.value = folder; | |
folderPath.value = [...folderPath.value, folder]; | |
emit("select", folder.cid); | |
await getList(folder.cid); | |
}; | |
const handlePathClick = async (index: number) => { | |
if (index < 0) { | |
// 点击根目录 | |
folderPath.value = [{ name: "根目录", cid: "0" }]; | |
selectedFolder.value = null; | |
await getList("0"); | |
} else { | |
// 点击路径中的某个文件夹 | |
const targetFolder = folderPath.value[index]; | |
folderPath.value = folderPath.value.slice(0, index + 1); | |
selectedFolder.value = targetFolder; | |
await getList(targetFolder.cid); | |
emit("select", targetFolder.cid); | |
} | |
}; | |
// 初始化加载 | |
getList(); | |
</script> | |
<style lang="scss" scoped> | |
@import "@/styles/common.scss"; | |
.folder-select { | |
position: relative; | |
min-height: 300px; | |
max-height: 500px; | |
display: flex; | |
flex-direction: column; | |
padding: 4px; | |
.folder-header { | |
position: sticky; | |
top: 0; | |
z-index: 1; | |
margin-bottom: 16px; | |
padding: 12px 16px; | |
background: var(--el-fill-color-light); | |
border-radius: var(--theme-radius); | |
.folder-path { | |
display: flex; | |
align-items: center; | |
gap: 8px; | |
color: var(--theme-text-regular); | |
font-size: 14px; | |
overflow-x: auto; | |
&::-webkit-scrollbar { | |
height: 4px; | |
} | |
&::-webkit-scrollbar-thumb { | |
background: rgba(0, 0, 0, 0.1); | |
border-radius: 2px; | |
} | |
.el-icon { | |
flex-shrink: 0; | |
font-size: 16px; | |
color: var(--theme-primary); | |
} | |
.path-item { | |
display: flex; | |
align-items: center; | |
gap: 8px; | |
white-space: nowrap; | |
cursor: pointer; | |
transition: var(--theme-transition); | |
&:hover { | |
color: var(--theme-primary); | |
.folder-name { | |
color: var(--theme-primary); | |
} | |
} | |
.folder-name { | |
color: var(--theme-text-primary); | |
} | |
} | |
.root-path { | |
color: var(--theme-text-secondary); | |
cursor: pointer; | |
transition: var(--theme-transition); | |
&:hover { | |
color: var(--theme-primary); | |
} | |
} | |
} | |
} | |
} | |
.folder-list { | |
flex: 1; | |
overflow-y: auto; | |
padding: 4px; | |
.folder-item { | |
display: flex; | |
align-items: center; | |
justify-content: space-between; | |
padding: 12px 16px; | |
border-radius: var(--theme-radius); | |
cursor: pointer; | |
transition: var(--theme-transition); | |
&:hover { | |
background: var(--el-fill-color-light); | |
} | |
&.is-selected { | |
background: var(--el-color-primary-light-9); | |
color: var(--theme-primary); | |
.el-icon { | |
color: var(--theme-primary); | |
} | |
} | |
.folder-info { | |
display: flex; | |
align-items: center; | |
gap: 8px; | |
font-size: 14px; | |
.el-icon { | |
font-size: 16px; | |
color: var(--theme-text-regular); | |
} | |
.folder-name { | |
color: var(--theme-text-primary); | |
} | |
} | |
.arrow-icon { | |
font-size: 16px; | |
color: var(--theme-text-secondary); | |
} | |
} | |
} | |
.empty-folder { | |
padding: 32px 0; | |
} | |
.loading-overlay { | |
@include flex-center; | |
position: absolute; | |
inset: 0; | |
background: rgba(255, 255, 255, 0.9); | |
backdrop-filter: blur(4px); | |
gap: 8px; | |
font-size: 14px; | |
color: var(--theme-text-regular); | |
.loading-icon { | |
font-size: 20px; | |
animation: rotating 2s linear infinite; | |
} | |
} | |
@keyframes rotating { | |
from { | |
transform: rotate(0); | |
} | |
to { | |
transform: rotate(360deg); | |
} | |
} | |
</style> | |