feat: 新建文件/文件夹

This commit is contained in:
overtrue
2024-12-24 20:15:08 +08:00
parent 8fef3c758f
commit 50c2c32c06
3 changed files with 107 additions and 11 deletions
+27 -9
View File
@@ -10,19 +10,22 @@
</div>
</template>
<template #extra>
<div class="flex items-center gap-4">
<object-upload-stats />
<n-button>
<n-button @click="() => handleNewObject(true)">
<Icon name="ri:add-line" class="mr-2" />
<span>新建目录</span>
</n-button>
<n-button @click="() => handleNewObject(false)">
<Icon name="ri:add-line" class="mr-2" />
<span>新建文件</span>
</n-button>
<n-button @click="() => uploadPickerVisible = true">
<Icon name="ri:file-add-line" class="mr-2" />
<span>上传文件/文件夹</span>
</n-button>
<n-button-group>
<n-button @click="goToPreviousPage" :disabled="!previousToken">
<n-button @click="goToPreviousPage" :disabled="!continuationToken">
<Icon name="ri:arrow-left-s-line" class="mr-2" />
<span>上一页</span>
</n-button>
@@ -39,17 +42,18 @@
</template>
</n-page-header>
<n-data-table class="border dark:border-neutral-700 rounded overflow-hidden" :columns="columns" :data="objects" :pagination="false" :bordered="false" />
<object-upload-picker :show="uploadPickerVisible" @update:show="val => (uploadPickerVisible = val)" :bucketName="bucketName" :prefix="prefix" />
<object-upload-picker :show="uploadPickerVisible" @update:show="val => (uploadPickerVisible = val && refresh())" :bucketName="bucketName" :prefix="prefix" />
<object-new-form :show="newObjectFormVisible" :asPrefix="newObjectAsPrefix" @update:show="val => (newObjectFormVisible = val && refresh())" :bucketName="bucketName"
:prefix="prefix" />
</template>
<script setup lang="ts">
const { $s3Client } = useNuxtApp();
import { useAsyncData, useRoute, useRouter } from '#app'
import { NuxtLink } from '#components'
import { ListObjectsV2Command, type _Object, type CommonPrefix } from '@aws-sdk/client-s3'
import { joinRelativeURL } from 'ufo'
import { computed, ref, type VNode } from 'vue'
import { computed, ref, watch, type VNode } from 'vue'
import { useUploadTaskManagerStore } from '~/store/upload-tasks'
const route = useRoute()
@@ -58,6 +62,8 @@ const router = useRouter()
const props = defineProps<{ bucket: string; path: string }>()
const uploadPickerVisible = ref(false)
const newObjectFormVisible = ref(false)
const newObjectAsPrefix = ref(false)
// 上传任务
const uploadTaskStore = useUploadTaskManagerStore()
@@ -77,6 +83,12 @@ const prefix = computed(() => decodeURIComponent(props.path as string))
const pageSize = computed(() => parseInt(route.query.pageSize as string, 10))
const continuationToken = computed(() => route.query.continuationToken as string)
// 新建文件夹
const handleNewObject = (asPrefix: boolean) => {
newObjectFormVisible.value = true
newObjectAsPrefix.value = asPrefix
}
const bucketPath = (path?: string | Array<string>) => {
if (Array.isArray(path)) {
path = path.join('/')
@@ -131,6 +143,10 @@ const { data, refresh } = await useAsyncData<ListObjectsResponse>(`objectsData&$
}
})
watch(continuationToken, () => {
refresh()
}, { deep: true })
const contents = computed(() => data.value?.contents || [])
const commonPrefixes = computed(() => data.value?.commonPrefixes || [])
const nextToken = computed(() => data.value?.nextContinuationToken || null)
@@ -179,13 +195,15 @@ const previousToken = computed(() => {
function goToNextPage() {
if (nextToken.value) {
// 导航到新路由
router.push(`?continuationToken=${nextToken.value}`)
router.push({ query: { ...route.query, continuationToken: nextToken.value } })
// 将当前 token 添加到历史中
tokenHistory.value.push(nextToken.value)
}
}
function goToPreviousPage() {
if (previousToken.value) {
router.push(`?continuationToken=${previousToken.value}`)
router.push({ query: { ...route.query, continuationToken: previousToken.value } })
// 将上一页 token 之后的 tokenHistory 中的 token 删除,以表示回退
const prevIndex = tokenHistory.value.indexOf(previousToken.value)
tokenHistory.value.splice(prevIndex + 1)
@@ -193,7 +211,7 @@ function goToPreviousPage() {
// 如果没有 previousToken,表示已经在第一页
// 导航到没有 token 的路由(第一页)
if (continuationToken.value) {
router.push(``)
router.push({ query: { ...route.query, continuationToken: undefined } })
tokenHistory.value.length = 0
}
}
+59
View File
@@ -0,0 +1,59 @@
<template>
<n-modal :show="show" @update:show="val => $emit('update:show', val)" size="huge">
<n-card class="max-w-screen-md">
<template #header>
<div style="display:flex; justify-content: space-between; align-items:center;">
<span>新建{{ displayType }}</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>新建{{ displayType }}</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="`请输入${displayType}名称`" 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 { joinRelativeURL } from 'ufo'
import { computed, defineEmits, defineProps } from 'vue'
const emit = defineEmits(['update:show'])
const props = defineProps<{ show: boolean; bucketName: string; prefix: string, asPrefix?: boolean }>()
const closeModal = () => emit('update:show', false)
const displayType = computed(() => props.asPrefix ? '文件夹' : '文件')
const objectKey = ref('')
const { putObject } = useObject({ bucket: props.bucketName })
const $message = useMessage()
const handlePutObject = () => {
const suffix = props.asPrefix ? '/' : ''
putObject(joinRelativeURL(props.prefix, objectKey.value, suffix), '').then(() => {
emit('update:show', false)
objectKey.value = ''
$message.success('创建成功')
}).catch((e) => {
$message.error(e.message)
})
}
</script>
+21 -2
View File
@@ -1,4 +1,4 @@
import { GetObjectCommand, HeadObjectCommand } from '@aws-sdk/client-s3'
import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl as _getSignedUrl } from '@aws-sdk/s3-request-presigner'
export function useObject({ bucket, region }: { bucket: string, region?: string }) {
@@ -22,5 +22,24 @@ export function useObject({ bucket, region }: { bucket: string, region?: string
return await _getSignedUrl($client, command, { expiresIn })
}
return { headObject, getSignedUrl }
const putObject = async (key: string, body: Blob | string) => {
const params = {
Bucket: bucket,
Key: key,
Body: body
}
return await $client.send(new PutObjectCommand(params))
}
const deleteObject = async (key: string) => {
const params = {
Bucket: bucket,
Key: key
}
return await $client.send(new DeleteObjectCommand(params))
}
return { headObject, putObject, deleteObject, getSignedUrl }
}