Skip to content

Instantly share code, notes, and snippets.

@dmjcomdem
Created November 14, 2023 16:23
Show Gist options
  • Save dmjcomdem/079193f19c44a65c0d379b266aee843b to your computer and use it in GitHub Desktop.
Save dmjcomdem/079193f19c44a65c0d379b266aee843b to your computer and use it in GitHub Desktop.
<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