161 lines
4.7 KiB
Vue
161 lines
4.7 KiB
Vue
<template>
|
|
<component :is="Default || 'div'">
|
|
<div class="p-4">
|
|
<div v-if="loading" class="flex justify-center py-8">
|
|
<ULoader />
|
|
</div>
|
|
<div v-else-if="error" class="text-red-500">
|
|
{{ error }}
|
|
</div>
|
|
<UTree v-if="showTree" :items="treeItems" v-model:expanded="expandedFolders" :key="treeKey" @toggle="onToggle" :get-key="item => item.value">
|
|
<template #item-wrapper="{ item }">
|
|
<div class="flex items-start cursor-pointer" @click.stop="!item.isFolder">
|
|
<div class="flex items-center gap-1">
|
|
<UIcon :name="item.icon" :size="30" />
|
|
<div class="flex gap-1 items-center">
|
|
<span class="text-[15px] font-medium">
|
|
{{ item.label }}
|
|
</span>
|
|
<UButton v-if="!item.isFolder && item.fileName" size="xxs" color="neutral"
|
|
variant="outline" icon="i-lucide-download"
|
|
@click.stop="downloadFile(item.path, item.fileName)" :ui="{ base: 'ring-0!' }" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</UTree>
|
|
</div>
|
|
</component>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
import { useFetchJson } from '@/composable/useFetchJson'
|
|
import Default from '@/layouts/default.vue'
|
|
|
|
interface FileItemRaw {
|
|
Name: string
|
|
IsFolder: boolean
|
|
}
|
|
|
|
interface FileItem {
|
|
name: string
|
|
type: 'file' | 'folder'
|
|
}
|
|
|
|
interface TreeItem {
|
|
label: string
|
|
icon: string
|
|
children?: TreeItem[]
|
|
isFolder: boolean
|
|
path: string
|
|
value: string
|
|
fileName?: string
|
|
}
|
|
|
|
const props = defineProps<{ initialPath?: string }>()
|
|
|
|
const currentPath = ref(props.initialPath || '')
|
|
|
|
const allData = ref<Record<string, FileItem[]>>({})
|
|
const expandedFolders = ref<string[]>([])
|
|
const treeKey = ref(0)
|
|
|
|
const loading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
|
|
const showTree = computed(() => !error.value)
|
|
|
|
async function fetchFolderContents(path: string): Promise<FileItem[]> {
|
|
const url = `/api/v1/restricted/storage/list-content/${path}`
|
|
const data = await useFetchJson<FileItemRaw[]>(url)
|
|
|
|
return (data.items || []).map(i => ({
|
|
name: i.Name,
|
|
type: i.IsFolder ? 'folder' : 'file'
|
|
}))
|
|
}
|
|
|
|
async function loadFolder(path: string) {
|
|
if (allData.value[path]) return
|
|
|
|
try {
|
|
const items = await fetchFolderContents(path)
|
|
allData.value[path] = items
|
|
} catch (e) {
|
|
error.value = e instanceof Error ? e.message : 'Failed to load folder contents'
|
|
}
|
|
}
|
|
|
|
function buildTree(path: string): TreeItem[] {
|
|
const items = allData.value[path] || []
|
|
|
|
return items.map(item => {
|
|
const itemPath = path ? `${path}/${item.name}` : item.name
|
|
const isFolder = item.type === 'folder'
|
|
|
|
const isExpanded = expandedFolders.value.includes(itemPath)
|
|
const isLoaded = !!allData.value[itemPath]
|
|
|
|
return {
|
|
label: item.name,
|
|
icon: isFolder ? 'fxemoji:folder' : 'flat-color-icons:file',
|
|
isFolder,
|
|
path: isFolder ? itemPath : path,
|
|
value: itemPath,
|
|
fileName: isFolder ? undefined : item.name,
|
|
|
|
children: isFolder && isExpanded && isLoaded
|
|
? buildTree(itemPath)
|
|
: []
|
|
}
|
|
})
|
|
}
|
|
|
|
const treeItems = computed(() => buildTree(currentPath.value))
|
|
|
|
async function toggleFolder(item: TreeItem) {
|
|
if (!item.isFolder) return
|
|
|
|
const isOpen = expandedFolders.value.includes(item.value)
|
|
|
|
if (!isOpen) {
|
|
await loadFolder(item.value)
|
|
treeKey.value++
|
|
} else {
|
|
treeKey.value++
|
|
}
|
|
}
|
|
|
|
function onToggle(_event: unknown, item: TreeItem) {
|
|
console.log('Toggle:', item)
|
|
if (item.isFolder) {
|
|
toggleFolder(item)
|
|
}
|
|
}
|
|
|
|
async function downloadFile(path: string, fileName: string) {
|
|
try {
|
|
const response = await fetch(`/api/v1/restricted/storage/download-file/${path}/${fileName}`)
|
|
if (!response.ok) throw new Error('Download failed')
|
|
|
|
const blob = await response.blob()
|
|
const url = window.URL.createObjectURL(blob)
|
|
|
|
const a = document.createElement('a')
|
|
a.href = url
|
|
a.download = fileName
|
|
|
|
document.body.appendChild(a)
|
|
a.click()
|
|
a.remove()
|
|
|
|
window.URL.revokeObjectURL(url)
|
|
} catch (e) {
|
|
console.error(e)
|
|
alert('Download failed')
|
|
}
|
|
}
|
|
|
|
loadFolder(currentPath.value)
|
|
</script> |