Created
November 14, 2023 16:23
-
-
Save dmjcomdem/079193f19c44a65c0d379b266aee843b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div v-if="url"> | |
<template v-if="isAvailableExtension"> | |
<PButton | |
variant="text" | |
color="primary" | |
class="preview-button" | |
:title="fileName" | |
data-testid="preview-button" | |
@click="openPreviewModal" | |
> | |
<slot> | |
<span class="fileName">{{ fileName }}</span> | |
</slot> | |
</PButton> | |
</template> | |
<template v-else> | |
<a | |
:href="url" | |
:title="fileName" | |
class="preview-button button button--text button--primary" | |
download | |
data-testid="preview-link" | |
> | |
<slot> | |
<span class="fileName">{{ fileName || url }}</span> | |
</slot> | |
</a> | |
</template> | |
<PModal v-model="isPreview" width="60vw" :loading="loading"> | |
<h2 class="preview-title" data-testid="preview-title">{{ title ?? fileName }}</h2> | |
<div class="preview-wrapper"> | |
<template v-if="isError"> | |
<div class="preview-error-overlay">Ошибка инициализации файла</div> | |
</template> | |
<template v-else> | |
<template v-if="isImage"> | |
<img | |
:src="url" | |
alt="Предпросмотр файла документа" | |
data-testid="preview-img" | |
@error="handleError" | |
/> | |
</template> | |
<template v-if="isPDF"> | |
<iframe :title="title" :src="pfdBlob" data-testid="preview-pdf"></iframe> | |
</template> | |
<template v-if="isDOC"> | |
<iframe | |
:title="title" | |
:src="'https://docs.google.com/viewer?url=' + url + '&embedded=true'" | |
data-testid="preview-document" | |
></iframe> | |
</template> | |
</template> | |
</div> | |
<div class="text-right space-x-5"> | |
<a :href="url" class="button button--outline button--small" download> Скачать файл </a> | |
<PButton small @click="closePreviewModal"> Закрыть </PButton> | |
</div> | |
</PModal> | |
</div> | |
</template> | |
<script lang="ts"> | |
import { computed, defineComponent, ref, watch } from 'vue'; | |
import PButton from '../PButton/PButton.vue'; | |
import PModal from '../PModal/PModal.vue'; | |
import { getFileNameByUrl, logger } from '@/shared/model/utils'; | |
import { axios } from '@/api/axios'; | |
enum FileEnum { | |
PDF = 'pdf', | |
JPEG = 'jpeg', | |
JPG = 'jpg', | |
WEBP = 'webp', | |
PNG = 'png', | |
DOC = 'doc', | |
DOCX = 'docx' | |
} | |
export default defineComponent({ | |
name: 'FilePreview', | |
components: { | |
PButton, | |
PModal | |
}, | |
props: { | |
url: { | |
type: String, | |
required: true | |
}, | |
title: { | |
type: String, | |
default: '' | |
} | |
}, | |
setup(props) { | |
const loading = ref(false); | |
const isPreview = ref(false); | |
const pfdBlob = ref<string>(''); | |
const isError = ref<boolean>(false); | |
const fileName = computed<string>(() => { | |
return getFileNameByUrl(props.url); | |
}); | |
const validateExtension = (extension: string) => new RegExp(`${extension}$`, 'i').test(fileName.value); | |
const isAvailableExtension = computed<boolean>(() => { | |
return Object.values(FileEnum).some(validateExtension); | |
}); | |
const isImage = computed<boolean>(() => { | |
const extensions: string[] = [FileEnum.JPEG, FileEnum.JPG, FileEnum.PNG, FileEnum.WEBP]; | |
return extensions.some(validateExtension); | |
}); | |
const isPDF = computed(() => { | |
return validateExtension(FileEnum.PDF); | |
}); | |
const isDOC = computed(() => { | |
const extensions: string[] = [FileEnum.DOC, FileEnum.DOCX]; | |
return extensions.some(validateExtension); | |
}); | |
watch(isPreview, async () => { | |
if (!fileName.value) { | |
return handleError('Не удалось получить название файла из ссылки'); | |
} | |
if (isPDF.value) { | |
await generatePDF(); | |
} | |
}); | |
const handleError = (error: unknown) => { | |
isError.value = true; | |
logger.error(error); | |
}; | |
const generatePDF = async () => { | |
try { | |
loading.value = true; | |
isError.value = false; | |
let url = props.url; | |
// костыль для просмотра PDF локально, решается добавлением CORS-заголовков для файлов | |
const LOCAL_ORIGIN = 'http://localhost:5173'; | |
if (process.env.NODE_ENV === 'development' && window.location.origin === LOCAL_ORIGIN) { | |
const re = /^http(s)?.+protek\.ru/i; | |
if (re.test(url)) { | |
url = url.replace(re, LOCAL_ORIGIN); | |
} | |
} | |
const { data } = await axios({ | |
url, | |
responseType: 'blob' | |
}); | |
const blob = new Blob([data], { type: 'application/pdf' }); | |
pfdBlob.value = URL.createObjectURL(blob); | |
} catch (error) { | |
handleError(error); | |
} finally { | |
loading.value = false; | |
} | |
}; | |
const openPreviewModal = () => { | |
isPreview.value = true; | |
}; | |
const closePreviewModal = () => { | |
isPreview.value = false; | |
}; | |
return { | |
loading, | |
fileName, | |
pfdBlob, | |
isImage, | |
isPDF, | |
isDOC, | |
isAvailableExtension, | |
isError, | |
handleError, | |
openPreviewModal, | |
closePreviewModal, | |
isPreview | |
}; | |
} | |
}); | |
</script> | |
<style lang="scss" scoped> | |
.preview-button { | |
font-size: inherit; | |
width: 100%; | |
} | |
.preview-wrapper { | |
min-height: 60vh; | |
margin: 0 -4.4rem 0 -3.2rem; | |
overflow: hidden; | |
overflow-y: auto; | |
max-height: 60vh; | |
} | |
.preview-title { | |
font-size: 1.8rem; | |
word-break: break-word; | |
} | |
.preview-error-overlay { | |
display: grid; | |
place-items: center; | |
height: 100%; | |
} | |
iframe { | |
width: 100%; | |
// Добавить запас для правильного отображения скрола | |
height: calc(100% - 3px); | |
border: none; | |
} | |
img { | |
width: 100%; | |
height: auto; | |
} | |
.fileName { | |
display: block; | |
text-align: left; | |
overflow: hidden; | |
white-space: pre; | |
text-overflow: ellipsis; | |
} | |
</style> | |
<style> | |
.preview-button.button > span { | |
display: block; | |
width: 100%; | |
text-align: left; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment