feat: file upload composable
This commit is contained in:
parent
7ccdcf51f2
commit
4791a776d4
|
|
@ -6,6 +6,7 @@ import {
|
||||||
circleFlatLearningContents,
|
circleFlatLearningContents,
|
||||||
circleFlatLearningUnits,
|
circleFlatLearningUnits,
|
||||||
} from "@/services/circle";
|
} from "@/services/circle";
|
||||||
|
import { presignUpload, uploadFile } from "@/services/files";
|
||||||
import { useCompletionStore } from "@/stores/completion";
|
import { useCompletionStore } from "@/stores/completion";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import { useDashboardStore } from "@/stores/dashboard";
|
import { useDashboardStore } from "@/stores/dashboard";
|
||||||
|
|
@ -435,3 +436,30 @@ export function useCourseStatistics() {
|
||||||
|
|
||||||
return { courseSessionName, circleMeta };
|
return { courseSessionName, circleMeta };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useFileUpload() {
|
||||||
|
const error = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const fileInfo = ref({} as { id: string; name: string; url: string });
|
||||||
|
|
||||||
|
async function upload(e: Event) {
|
||||||
|
const { files } = e.target as HTMLInputElement;
|
||||||
|
if (!files?.length) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
error.value = false;
|
||||||
|
loading.value = true;
|
||||||
|
const file = files[0];
|
||||||
|
const presignData = await presignUpload(file);
|
||||||
|
await uploadFile(presignData.pre_sign, file);
|
||||||
|
fileInfo.value = presignData.file_info;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
error.value = true;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { upload, error, loading, fileInfo };
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from "vue";
|
import { ref, watch } from "vue";
|
||||||
import { presignUpload, uploadFile } from "@/services/files";
|
|
||||||
import type { UserDataFileInfo } from "@/types";
|
import type { UserDataFileInfo } from "@/types";
|
||||||
|
import { useFileUpload } from "@/composables";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fileInfo: UserDataFileInfo | null;
|
fileInfo: UserDataFileInfo | null;
|
||||||
|
|
@ -10,6 +10,12 @@ const props = defineProps<{
|
||||||
const emit = defineEmits(["fileUploaded", "fileDeleted"]);
|
const emit = defineEmits(["fileUploaded", "fileDeleted"]);
|
||||||
|
|
||||||
const selectedFile = ref();
|
const selectedFile = ref();
|
||||||
|
const {
|
||||||
|
upload: uploadFile,
|
||||||
|
loading: uploadLoading,
|
||||||
|
error: uploadError,
|
||||||
|
fileInfo: uploadInfo,
|
||||||
|
} = useFileUpload();
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.fileInfo,
|
() => props.fileInfo,
|
||||||
|
|
@ -19,28 +25,11 @@ watch(
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
const loading = ref(false);
|
watch(uploadInfo, (info) => {
|
||||||
const uploadError = ref(false);
|
console.log("fileInfo changed", info);
|
||||||
|
selectedFile.value = info;
|
||||||
async function fileSelected(e: Event) {
|
emit("fileUploaded", info.id);
|
||||||
const { files } = e.target as HTMLInputElement;
|
});
|
||||||
if (!files?.length) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
uploadError.value = false;
|
|
||||||
loading.value = true;
|
|
||||||
const file = files[0];
|
|
||||||
const presignData = await presignUpload(file);
|
|
||||||
await uploadFile(presignData.pre_sign, file);
|
|
||||||
selectedFile.value = presignData.file_info;
|
|
||||||
emit("fileUploaded", presignData.file_info.id);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
uploadError.value = true;
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
selectedFile.value = null;
|
selectedFile.value = null;
|
||||||
|
|
@ -52,7 +41,7 @@ function handleDelete() {
|
||||||
<div>
|
<div>
|
||||||
<h4 class="mb-2 text-xl">{{ $t("a.Datei hochladen") }}</h4>
|
<h4 class="mb-2 text-xl">{{ $t("a.Datei hochladen") }}</h4>
|
||||||
|
|
||||||
<template v-if="loading">
|
<template v-if="uploadLoading">
|
||||||
{{ $t("a.Laden...") }}
|
{{ $t("a.Laden...") }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -68,7 +57,8 @@ function handleDelete() {
|
||||||
type="file"
|
type="file"
|
||||||
class="absolute opacity-0"
|
class="absolute opacity-0"
|
||||||
accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,.mov,.ppt,.pptx,.mp4"
|
accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,.mov,.ppt,.pptx,.mp4"
|
||||||
@change="fileSelected"
|
:disabled="uploadLoading"
|
||||||
|
@change="uploadFile"
|
||||||
/>
|
/>
|
||||||
<it-icon-document class="mr-1.5 h-7 w-7 text-blue-800" />
|
<it-icon-document class="mr-1.5 h-7 w-7 text-blue-800" />
|
||||||
{{ $t("a.Datei auswählen") }}
|
{{ $t("a.Datei auswählen") }}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import WizardPage from "@/components/onboarding/WizardPage.vue";
|
import WizardPage from "@/components/onboarding/WizardPage.vue";
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
import { ref } from "vue";
|
import { ref, watch } from "vue";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
||||||
import { presignUpload, uploadFile } from "@/services/files";
|
import { useFileUpload } from "@/composables";
|
||||||
|
|
||||||
const user = useUserStore();
|
const user = useUserStore();
|
||||||
|
|
||||||
|
|
@ -14,29 +14,22 @@ const companies = [
|
||||||
{ id: 3, name: "Firma 3" },
|
{ id: 3, name: "Firma 3" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const uploadError = ref(false);
|
|
||||||
const avatarLoading = ref(false);
|
|
||||||
|
|
||||||
async function fileSelected(e: Event) {
|
|
||||||
const { files } = e.target as HTMLInputElement;
|
|
||||||
if (!files?.length) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
uploadError.value = false;
|
|
||||||
avatarLoading.value = true;
|
|
||||||
const file = files[0];
|
|
||||||
const presignData = await presignUpload(file);
|
|
||||||
await uploadFile(presignData.pre_sign, file);
|
|
||||||
user.avatar_url = presignData.file_info.url;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
uploadError.value = true;
|
|
||||||
} finally {
|
|
||||||
avatarLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedCompany = ref(companies[0]);
|
const selectedCompany = ref(companies[0]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
upload: avatarUpload,
|
||||||
|
loading: avatarLoading,
|
||||||
|
error: avatarError,
|
||||||
|
fileInfo: avatarFileInfo,
|
||||||
|
} = useFileUpload();
|
||||||
|
|
||||||
|
watch(avatarFileInfo, (info) => {
|
||||||
|
console.log("fileInfo changed", info);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(selectedCompany, (company) => {
|
||||||
|
console.log("company changed", company);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -66,12 +59,13 @@ const selectedCompany = ref(companies[0]);
|
||||||
type="file"
|
type="file"
|
||||||
class="absolute opacity-0"
|
class="absolute opacity-0"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
@change="fileSelected"
|
:disabled="avatarLoading"
|
||||||
|
@change="avatarUpload"
|
||||||
/>
|
/>
|
||||||
{{ $t("a.Bild hochladen") }}
|
{{ $t("a.Bild hochladen") }}
|
||||||
<it-icon-upload class="it-icon ml-2 h-6 w-6" />
|
<it-icon-upload class="it-icon ml-2 h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<p v-if="uploadError" class="mt-3 text-red-700">
|
<p v-if="avatarError" class="mt-3 text-red-700">
|
||||||
{{ $t("a.Datei kann nicht gespeichert werden.") }}
|
{{ $t("a.Datei kann nicht gespeichert werden.") }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,19 @@ export async function fetchCourseSessionDocuments(courseSessionId: string) {
|
||||||
return itGetCached(`/api/core/document/list/${courseSessionId}/`);
|
return itGetCached(`/api/core/document/list/${courseSessionId}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function presignUpload(file: File) {
|
type PresignResponse = {
|
||||||
|
pre_sign: {
|
||||||
|
url: string;
|
||||||
|
fields: Record<string, string>;
|
||||||
|
};
|
||||||
|
file_info: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function presignUpload(file: File): Promise<PresignResponse> {
|
||||||
return await itPost(`/api/core/storage/presign/`, {
|
return await itPost(`/api/core/storage/presign/`, {
|
||||||
file_type: file.type,
|
file_type: file.type,
|
||||||
file_name: file.name,
|
file_name: file.name,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue