feat: 创建存储桶 & 删除对象 & 修复上传文件夹时路径拼接错误

This commit is contained in:
overtrue
2024-12-27 20:16:22 +08:00
parent 438c15582e
commit bffc659f85
10 changed files with 139 additions and 92 deletions
+55
View File
@@ -0,0 +1,55 @@
<template>
<n-modal :show="show" @update:show="val => $emit('update:show', val)" size="huge">
<n-card class="max-w-screen-sm">
<template #header>
<div style="display:flex; justify-content: space-between; align-items:center;">
<span>新建存储桶</span>
<n-button size="small" ghost @click="closeModal">关闭</n-button>
</div>
</template>
<div class="flex flex-col gap-4">
<!-- <div class="flex items-center gap-4">
<div>新建存储桶</div>
<div class="text-cyan-600">{{ bucketName }}{{ prefix || '/' }}</div>
</div> -->
<!-- <n-alert title="" type="info">
若上传路径中存在同名文件上传将覆盖原有文件
</n-alert> -->
<div class="flex items-center gap-4">
<n-input :placeholder="`请输入名称`" v-model:value="objectKey" />
</div>
<div class="flex justify-center gap-4">
<n-button type="primary" :disabled="objectKey.trim().length < 1" @click="handlePutObject">创建</n-button>
</div>
</div>
</n-card>
</n-modal>
</template>
<script setup lang="ts">
import { defineEmits, defineProps } from 'vue'
const emit = defineEmits(['update:show'])
const props = defineProps<{ show: boolean; }>()
const closeModal = () => emit('update:show', false)
const objectKey = ref('')
const { createBucket } = useBucket({})
const $message = useMessage()
const handlePutObject = () => {
createBucket(objectKey.value).then(() => {
emit('update:show', false)
objectKey.value = ''
$message.success('创建成功')
}).catch((e) => {
$message.error(e.message)
})
}
</script>
-7
View File
@@ -1,7 +0,0 @@
<template>
<div></div>
</template>
<script setup></script>
<style lang="scss" scoped></style>
+2 -2
View File
@@ -101,7 +101,7 @@ const columns = [
{
key: 'Key', title: '对象', render: (row: { Key: string, type: 'prefix' | 'object' }) => {
const displayKey = prefix.value ? row.Key.substring(prefix.value.length) : row.Key
let label: string | VNode = displayKey || '根目录'
let label: string | VNode = displayKey || '/'
if (row.type === 'prefix') {
label = h('span', { class: 'inline-flex items-center gap-2' }, [icon('ri:folder-line'), label])
@@ -109,7 +109,7 @@ const columns = [
const keyInUri = row.Key
return h(NuxtLink, { href: bucketPath(keyInUri) }, label)
return h(NuxtLink, { href: bucketPath(keyInUri), class: 'block text-cyan-400' }, label)
}
},
{ key: 'Size', title: '大小', render: (row: { Size: number }) => row.Size ? formatBytes(row.Size) : '' },
+32 -15
View File
@@ -65,16 +65,22 @@
</template>
<script setup lang="ts">
import { prefix } from 'naive-ui/es/_utils/cssr'
import { computed, defineEmits, defineProps, ref } from 'vue'
import { useUploadTaskManagerStore } from '~/store/upload-tasks'
const uploadTaskManagerStore = useUploadTaskManagerStore()
interface FileItem {
file: File,
prefix: string
}
interface SelectedItem {
type: 'file' | 'folder'
name: string
size: number
files?: File[]
files?: FileItem[]
}
const emit = defineEmits(['update:show'])
@@ -95,7 +101,9 @@ const handleFileSelect = (e: Event) => {
const input = e.target as HTMLInputElement
if (input.files) {
// 更新展示列表
Array.from(input.files).forEach(f => selectedItems.value.push({ type: 'file', name: f.name, size: f.size, files: [f] }))
Array.from(input.files).forEach(f => selectedItems.value.push(
{ type: 'file', name: f.name, size: f.size, files: [{ file: f, prefix: props.prefix }] }
))
}
}
@@ -104,24 +112,29 @@ const handleFolderSelect = (e: Event) => {
if (input.files) {
const folderFiles = Array.from(input.files)
// 找到每一个文件的 top-level 文件夹
const folderMap = new Map<string, File[]>()
for (const file of folderFiles) {
const parts = file.webkitRelativePath.split('/')
const folderName = parts[0] || '未知文件夹'
// 保留完整目录路径(去掉最后的文件名)
const pathParts = file.webkitRelativePath.split('/')
pathParts.pop() // 移除文件名
const folderPath = pathParts.join('/') || '未知文件夹'
if (!folderMap.has(folderName)) {
folderMap.set(folderName, [])
if (!folderMap.has(folderPath)) {
folderMap.set(folderPath, [])
}
folderMap.get(folderName)?.push(file)
folderMap.get(folderPath)?.push(file)
}
// 只展示文件夹名称,实际上传依然包含 folderFiles
for (const [folderName, files] of folderMap.entries()) {
// 将每个完整路径和对应文件放到 selectedItems
for (const [folderPath, files] of folderMap.entries()) {
const totalSize = files.reduce((acc, file) => acc + file.size, 0)
selectedItems.value.push({ type: 'folder', name: folderName, size: totalSize, files })
selectedItems.value.push({
type: 'folder',
name: folderPath,
size: totalSize,
files: files.map(file => ({ file, prefix: `${props.prefix}${folderPath}/` }))
})
}
}
}
@@ -129,7 +142,9 @@ const handleFolderSelect = (e: Event) => {
const handleDrop = (e: DragEvent) => {
if (e.dataTransfer?.files) {
const droppedFiles = Array.from(e.dataTransfer.files)
droppedFiles.forEach(file => selectedItems.value.push({ type: 'file', name: file.name, size: file.size, files: [file] }))
droppedFiles.forEach(file => selectedItems.value.push({
type: 'file', name: file.name, size: file.size, files: [{ file, prefix: props.prefix }]
}))
}
}
@@ -141,7 +156,9 @@ function removeItem(index: number) {
}
function handleUpload() {
uploadTaskManagerStore.addFiles(allFiles.value, props.bucketName, props.prefix)
allFiles.value.forEach(fileItem => {
uploadTaskManagerStore.addFiles([fileItem.file], props.bucketName, fileItem.prefix)
})
selectedItems.value = []
closeModal()
}
+19
View File
@@ -9,18 +9,32 @@
<Icon name="ri:download-line" class="mr-2" />
<span>下载</span>
</n-button>
<n-button @click="() => copySignedUrl()">
<Icon name="ri:file-copy-line" class="mr-2" />
<span>复制临时链接</span>
</n-button>
<n-button @click="() => showPreview = true">
<Icon name="ri:eye-line" class="mr-2" />
<span>预览</span>
</n-button>
<n-button>
<Icon name="ri:price-tag-3-line" class="mr-2" />
<span>标签</span>
</n-button>
<n-popconfirm @positive-click="deleteObject">
<template #trigger>
<n-button ghost type="error">
<Icon name="ri:delete-bin-7-line" class="mr-2" />
<span>删除</span>
</n-button>
</template>
删除对象 <span class="select-all">{{ key }}</span> ?
</n-popconfirm>
<n-button @click="() => refresh()">
<Icon name="ri:refresh-line" class="mr-2" />
<span>刷新</span>
@@ -82,6 +96,11 @@ const copySignedUrl = async () => {
msg.destroy();
message.success('链接已复制到剪贴板');
}
const deleteObject = async () => {
await objectApi.deleteObject(key.value);
message.success('删除成功');
router.back();
}
</script>