import { computed, defineComponent, type PropType, ref, } from 'vue' import type ApiInterface from '../../types/api-interface' import { ArkUiConstants } from '../../composables/use-ark-ui' import { type FileData } from './upload-area-types' import './assets/styles.scss' export const useUploadFile = async (params: { api?: ApiInterface, endpoint: string, file: File }) => { const api = params.api || ArkUiConstants.api const endpoint = params.endpoint || '/file/' if (!api) { throw new Error('API is not defined') } try { const data = new FormData() data.append('file', params.file) return (await api.post(endpoint, data)).data } catch (e) { throw new Error('File upload error') } } /** * Кнопка для загрузки файлов. Имеет настраиваемый размер. *

* Дизайн *

*/ export default defineComponent({ props: { /** * Настройка доступности файла, даже если компонент заблокирован */ isFileAvailable: { type: Boolean, default: false, }, /** * Состояние лоадера в кнопке */ isLoading: { type: Boolean, default: false, }, /** * Объект ApiInterface. */ api: { type: Object as PropType, required: true, }, /** * Строка - конечная точка API (по умолчанию '/file/'). */ endpoint: { type: String, default: '/file/', }, /** * Загружаемый файл. */ file: { type: [Object, null] as PropType, default: null, }, /** * Массив строк с допустимыми форматами файлов. * По умолчанию ['.jpg', '.png', '.pdf']. */ formats: { type: Array as PropType, default: () => ['.jpg', '.png', '.pdf'], }, /** * Максимальный вес файла \ * Указывается в килобайтах \ * 1024 = 1мб */ maxFileSize: { type: Number, default: undefined, }, /** * Функция, вызываемая при загрузке файла. */ onUpload: { type: Function as PropType<(file: FileData|null) => void>, required: true, }, /** * Позволяет назначить пользовательскую логику удаления, включая сам запрос */ onDelete: { type: Function as PropType<(file: FileData | null) => void>, default: undefined, }, /** * Размер кнопки, по умолчанию 'M'. */ size: { type: String, default: 'M', }, /** * Флаг отключена ли кнопка. */ isDisabled: { type: Boolean, default: false, }, /** * Кастомная ширина кнопки в px. */ width: { type: String, default: null, }, label: { type: String, default: 'Загрузить файл', }, }, setup(props) { const isLoading = ref(false) const errorText = ref(undefined) const isError = computed(() => typeof errorText.value !== 'undefined') const filenameWithoutExtension = computed(() => { if (!props.file) return undefined const { name, extension } = props.file const lastDotIndex = name.lastIndexOf('.') if (lastDotIndex !== -1) { const maybeExtension = name.substring(lastDotIndex + 1) if (maybeExtension === extension) { return name.substring(0, lastDotIndex) } } return name }) const upload = (value: FileData | null) => { if (props.onUpload) { props.onUpload(value) } } const onChange = (e: Event) => { const { files } = e.target as HTMLInputElement if (files && files.length) { isLoading.value = true if (props.maxFileSize) { const kb = files[0].size / 1024 if (kb > props.maxFileSize) { errorText.value = `Ошибка загрузки, максимальный размер файла превышает ${formatFileSize(props.maxFileSize)}` isLoading.value = false upload(null) return } } useUploadFile({ file: files[0], api: props.api, endpoint: props.endpoint }) .then(upload) .catch((e) => { errorText.value = 'Ошибка загрузки' upload(null) throw new Error(e) }) .finally(() => { isLoading.value = false }) } } const removeFile = () => { if (props.file) { if (props.onDelete) props.onDelete(props.file) else props.api.delete(props.endpoint + props.file.id) } if (props.onUpload) props.onUpload(null) upload(null) } const reloadFile = () => { if (props.onUpload) props.onUpload(null) errorText.value = undefined upload(null) } function formatFileSize(kb: number) { if (kb < 1024) return `${kb}кб` let i = -1 const units = ['мб', 'гб', 'тб'] do { kb /= 1024 i++ } while (kb >= 1024) return `${Math.floor(kb)}${units[i]}` } return () => ( <> } { props.maxFileSize && !isError.value && !props.file ?
Максимальный размер файла: {formatFileSize(props.maxFileSize)}
: '' } ) }, })