halo/src/modules/contents/attachments/components/selector-providers/UploadSelectorProvider.vue

240 lines
7.2 KiB
Vue

<script lang="ts" setup>
import {
VEmpty,
IconCheckboxFill,
VCard,
IconDeleteBin,
Dialog,
} from "@halo-dev/components";
import type { AttachmentLike } from "@halo-dev/console-shared";
import { apiClient } from "@/utils/api-client";
import LazyImage from "@/components/image/LazyImage.vue";
import type { Attachment } from "@halo-dev/api-client";
import FilePondUpload from "@/components/upload/FilePondUpload.vue";
import AttachmentFileTypeIcon from "../AttachmentFileTypeIcon.vue";
import { computed, ref, watchEffect } from "vue";
import type { AxiosResponse } from "axios";
import { isImage } from "@/utils/image";
import { useFetchAttachmentPolicy } from "../../composables/use-attachment-policy";
import { useFetchAttachmentGroup } from "../../composables/use-attachment-group";
withDefaults(
defineProps<{
selected: AttachmentLike[];
}>(),
{
selected: () => [],
}
);
const emit = defineEmits<{
(event: "update:selected", attachments: AttachmentLike[]): void;
}>();
const { policies } = useFetchAttachmentPolicy({ fetchOnMounted: true });
const policyMap = computed(() => {
return [
{
label: "选择存储策略",
value: "",
},
...policies.value.map((policy) => {
return {
label: policy.spec.displayName,
value: policy.metadata.name,
};
}),
];
});
const selectedPolicy = ref("");
const { groups } = useFetchAttachmentGroup({ fetchOnMounted: true });
const groupMap = computed(() => {
return [
{
label: "选择分组",
value: "",
},
...groups.value.map((group) => {
return {
label: group.spec.displayName,
value: group.metadata.name,
};
}),
];
});
const selectedGroup = ref("");
const attachments = ref<Set<Attachment>>(new Set<Attachment>());
const selectedAttachments = ref<Set<Attachment>>(new Set<Attachment>());
const uploadHandler = computed(() => {
return (file, config) =>
apiClient.attachment.uploadAttachment(
{
file,
policyName: selectedPolicy.value,
groupName: selectedGroup.value,
},
config
);
});
const onUploaded = async (response: AxiosResponse) => {
const attachment = response.data as Attachment;
const { data } =
await apiClient.extension.storage.attachment.getstorageHaloRunV1alpha1Attachment(
{
name: attachment.metadata.name,
}
);
attachments.value.add(data);
selectedAttachments.value.add(data);
};
const handleSelect = async (attachment: Attachment | undefined) => {
if (!attachment) return;
if (selectedAttachments.value.has(attachment)) {
selectedAttachments.value.delete(attachment);
return;
}
selectedAttachments.value.add(attachment);
};
const handleDelete = async (attachment: Attachment) => {
Dialog.warning({
title: "确定要删除当前的附件吗?",
confirmType: "danger",
onConfirm: async () => {
try {
await apiClient.extension.storage.attachment.deletestorageHaloRunV1alpha1Attachment(
{
name: attachment.metadata.name,
}
);
attachments.value.delete(attachment);
selectedAttachments.value.delete(attachment);
} catch (e) {
console.error("Failed to delete attachment", e);
}
},
});
};
watchEffect(() => {
emit("update:selected", Array.from(selectedAttachments.value));
});
</script>
<template>
<div class="flex h-full flex-col gap-4 sm:flex-row">
<div class="h-full w-full space-y-4 overflow-auto sm:w-96">
<FormKit type="form">
<FormKit
v-model="selectedPolicy"
type="select"
:options="policyMap"
label="存储策略"
></FormKit>
<FormKit
v-model="selectedGroup"
:options="groupMap"
type="select"
label="分组"
></FormKit>
</FormKit>
<FilePondUpload
ref="FilePondUploadRef"
:allow-multiple="true"
:handler="uploadHandler"
:max-parallel-uploads="5"
:disabled="!selectedPolicy"
:label-idle="
selectedPolicy ? '点击选择文件或者拖拽文件到此处' : '请先选择存储策略'
"
@uploaded="onUploaded"
/>
</div>
<div class="h-full flex-1 overflow-auto">
<VEmpty
v-if="!attachments.size"
message="当前没有上传的文件,你可以点击左侧区域上传文件"
title="当前没有上传的文件"
>
</VEmpty>
<div
v-else
class="grid grid-cols-3 gap-x-2 gap-y-3 p-0.5 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-4 2xl:grid-cols-6"
role="list"
>
<VCard
v-for="(attachment, index) in Array.from(attachments)"
:key="index"
:body-class="['!p-0']"
:class="{
'ring-1 ring-primary': selectedAttachments.has(attachment),
}"
class="hover:shadow"
@click="handleSelect(attachment)"
>
<div class="group relative bg-white">
<div
class="aspect-w-10 aspect-h-8 block h-full w-full cursor-pointer overflow-hidden bg-gray-100"
>
<LazyImage
v-if="isImage(attachment.spec.mediaType)"
:key="attachment.metadata.name"
:alt="attachment.spec.displayName"
:src="attachment.status?.permalink"
class="pointer-events-none object-cover group-hover:opacity-75"
>
<template #loading>
<div
class="flex h-full items-center justify-center object-cover"
>
<span class="text-xs text-gray-400">加载中...</span>
</div>
</template>
<template #error>
<div
class="flex h-full items-center justify-center object-cover"
>
<span class="text-xs text-red-400"></span>
</div>
</template>
</LazyImage>
<AttachmentFileTypeIcon
v-else
:file-name="attachment.spec.displayName"
/>
</div>
<p
class="pointer-events-none block truncate px-2 py-1 text-center text-xs font-medium text-gray-700"
>
{{ attachment.spec.displayName }}
</p>
<div
:class="{ '!flex': selectedAttachments.has(attachment) }"
class="absolute top-0 left-0 hidden h-1/3 w-full justify-end bg-gradient-to-b from-gray-300 to-transparent ease-in-out group-hover:flex"
>
<IconDeleteBin
class="mt-1 mr-1 hidden h-5 w-5 cursor-pointer text-red-400 transition-all hover:text-red-600 group-hover:block"
@click.stop="handleDelete(attachment)"
/>
<IconCheckboxFill
:class="{
'!text-primary': selectedAttachments.has(attachment),
}"
class="mt-1 mr-1 h-5 w-5 cursor-pointer text-white transition-all hover:text-primary"
/>
</div>
</div>
</VCard>
</div>
</div>
</div>
</template>