This commit is contained in:
guochunsi
2026-01-07 18:33:03 +08:00
parent bb06c81997
commit 9e3e775b0f
11 changed files with 277 additions and 155 deletions

View File

@@ -0,0 +1,141 @@
<template>
<!-- 组件不占据任何布局空间所有预览组件都是 teleported -->
<div style="display: none;">
<!-- 图片直接使用 el-image-viewer 全屏预览 -->
<el-image-viewer
v-if="!showIframe && imageSrc && imagePreviewVisible"
:url-list="[imageSrc]"
:teleported="true"
hide-on-click-modal
@close="imagePreviewVisible = false"
/>
<!-- PDF dialog 中显示 -->
<el-dialog
v-if="showIframe"
v-model="pdfDialogVisible"
:title="dialogTitle || '文件预览'"
append-to-body
width="90%"
class="pdf-preview-dialog"
>
<iframe ref="authIframeRef" :style="{ width: '100%', height: pdfIframeHeight }" />
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick, computed, onUnmounted } from 'vue';
import { ElImageViewer } from 'element-plus';
import { Session } from "/@/utils/storage";
// 定义 props
const props = defineProps<{
authSrc: string;
imgWidth?: string;
imgHeight?: string;
dialogTitle?: string;
}>();
// 定义响应式数据
const showIframe = ref(false);
const imageSrc = ref<string>('');
const imagePreviewVisible = ref(false);
const pdfDialogVisible = ref(false);
const authIframeRef = ref<HTMLIFrameElement | null>(null);
const windowHeight = ref(window.innerHeight);
// 计算 PDF iframe 的合适高度(优先使用外部传入的 imgHeight否则根据窗口高度动态计算
const pdfIframeHeight = computed(() => {
// 如果外部传入了 imgHeight优先使用
if (props.imgHeight) {
return props.imgHeight;
}
// 否则根据窗口高度动态计算dialog header 约 50pxpadding 约 40px留一些余量
return `${windowHeight.value - 120}px`;
});
// 监听窗口大小变化
const handleResize = () => {
windowHeight.value = window.innerHeight;
};
onMounted(() => {
window.addEventListener('resize', handleResize);
getImgSrcByToken();
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
// 携带token请求img的src
const getImgSrcByToken = (src?: string) => {
if (props.authSrc.indexOf(".pdf") >= 0) {
showIframe.value = true;
pdfDialogVisible.value = true;
nextTick(() => {
const imgSrc = src || props.authSrc;
const tenantId = Session.getTenant();
const iframe = authIframeRef.value;
if (!iframe) return;
const request = new XMLHttpRequest();
request.responseType = 'blob';
request.open('get', imgSrc, true);
request.setRequestHeader('Authorization', "Bearer " + Session.getToken());
request.setRequestHeader('TENANT-ID', tenantId);
request.onreadystatechange = () => {
if (request.readyState == XMLHttpRequest.DONE && request.status == 200) {
const binaryData: BlobPart[] = [];
binaryData.push(request.response);
iframe.src = window.URL.createObjectURL(new Blob(binaryData, { type: 'application/pdf' }));
iframe.onload = () => {
URL.revokeObjectURL(iframe.src);
};
}
};
request.send(null);
});
} else {
// 图片处理逻辑:加载后直接打开预览
showIframe.value = false;
pdfDialogVisible.value = false;
const imgSrc = src || props.authSrc;
const tenantId = Session.getTenant();
const request = new XMLHttpRequest();
request.responseType = 'blob';
request.open('get', imgSrc, true);
request.setRequestHeader('Authorization', "Bearer " + Session.getToken());
request.setRequestHeader('TENANT-ID', tenantId);
request.onreadystatechange = () => {
if (request.readyState == XMLHttpRequest.DONE && request.status == 200) {
imageSrc.value = URL.createObjectURL(request.response);
imagePreviewVisible.value = true;
}
};
request.send(null);
}
};
// 刷新图片
const refreshImg = (src?: string) => {
getImgSrcByToken(src);
};
// 暴露方法供外部调用
defineExpose({
refreshImg
});
</script>
<style scoped>
.pdf-preview-dialog :deep(.el-dialog__body) {
padding: 20px !important;
overflow-y: hidden !important;
}
</style>