ren
This commit is contained in:
92
src/components/tools/action-dropdown.vue
Normal file
92
src/components/tools/action-dropdown.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<el-dropdown
|
||||
v-if="hasVisibleItems"
|
||||
trigger="click"
|
||||
@command="handleCommand"
|
||||
:style="dropdownStyle"
|
||||
>
|
||||
<el-button
|
||||
:type="buttonType"
|
||||
link
|
||||
:style="buttonStyle"
|
||||
>
|
||||
<slot name="button">
|
||||
{{ buttonText }}
|
||||
<el-icon v-if="buttonIcon" class="el-icon--right" :style="iconStyle">
|
||||
<component :is="buttonIcon" v-if="buttonIcon" />
|
||||
</el-icon>
|
||||
</slot>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in visibleItems"
|
||||
:key="item.command"
|
||||
:command="item.command"
|
||||
>
|
||||
<el-icon v-if="item.icon">
|
||||
<component :is="item.icon" />
|
||||
</el-icon>
|
||||
<span :style="item.icon ? { marginLeft: '8px' } : {}">{{ item.label }}</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Operation } from '@element-plus/icons-vue'
|
||||
|
||||
interface MenuItem {
|
||||
command: string
|
||||
label: string
|
||||
icon?: any
|
||||
visible?: boolean | (() => boolean)
|
||||
}
|
||||
|
||||
interface Props {
|
||||
items: MenuItem[]
|
||||
buttonText?: string
|
||||
buttonIcon?: any
|
||||
buttonType?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'text'
|
||||
buttonStyle?: string | Record<string, any>
|
||||
dropdownStyle?: string | Record<string, any>
|
||||
iconStyle?: string | Record<string, any>
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
buttonText: '更多',
|
||||
buttonIcon: Operation,
|
||||
buttonType: 'primary',
|
||||
buttonStyle: () => ({ whiteSpace: 'nowrap' }),
|
||||
dropdownStyle: () => ({ marginLeft: '12px' }),
|
||||
iconStyle: () => ({ marginLeft: '4px' })
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
command: [command: string]
|
||||
}>()
|
||||
|
||||
// 计算可见的菜单项
|
||||
const visibleItems = computed(() => {
|
||||
return props.items.filter(item => {
|
||||
if (item.visible === undefined) return true
|
||||
if (typeof item.visible === 'boolean') return item.visible
|
||||
if (typeof item.visible === 'function') return item.visible()
|
||||
return false
|
||||
})
|
||||
})
|
||||
|
||||
// 是否有可见的菜单项
|
||||
const hasVisibleItems = computed(() => {
|
||||
return visibleItems.value.length > 0
|
||||
})
|
||||
|
||||
// 处理命令
|
||||
const handleCommand = (command: string) => {
|
||||
emit('command', command)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,150 +1,85 @@
|
||||
<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
|
||||
<div style="width: 100%; height: 100%;">
|
||||
<!-- 图片:直接使用原始地址展示(保持原有行为) -->
|
||||
<viewer :images="[authSrc]" v-if="!showIframe">
|
||||
<img
|
||||
v-if="!showIframe"
|
||||
ref="imgRef"
|
||||
:width="imgWidth ? imgWidth : '100%;'"
|
||||
:height="imgHeight ? imgHeight : '100%;'"
|
||||
:src="authSrc"
|
||||
/>
|
||||
</viewer>
|
||||
|
||||
<!-- PDF:通过 iframe + 带 token 的请求展示 -->
|
||||
<iframe
|
||||
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 }" />
|
||||
<!-- <div class="pdf-iframe-wrapper">
|
||||
<iframe
|
||||
ref="authIframeRef"
|
||||
:style="{
|
||||
width: '100%',
|
||||
height: pdfIframeHeight,
|
||||
border: 'none',
|
||||
display: 'block'
|
||||
}"
|
||||
/>
|
||||
</div> -->
|
||||
</el-dialog>
|
||||
ref="authIframeRef"
|
||||
style="width: 100%; height: 100%;"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, nextTick, computed, onUnmounted } from 'vue';
|
||||
import { ElImageViewer } from 'element-plus';
|
||||
import { Session } from "/@/utils/storage";
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { Session } from '/@/utils/storage'
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps<{
|
||||
authSrc: string;
|
||||
imgWidth?: string;
|
||||
imgHeight?: string;
|
||||
dialogTitle?: string;
|
||||
}>();
|
||||
authSrc: string
|
||||
imgWidth?: string
|
||||
imgHeight?: 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);
|
||||
const showIframe = ref(false)
|
||||
const authIframeRef = ref<HTMLIFrameElement | null>(null)
|
||||
const imgRef = ref<HTMLImageElement | null>(null)
|
||||
|
||||
// 计算 PDF iframe 的合适高度(优先使用外部传入的 imgHeight,否则根据窗口高度动态计算)
|
||||
const pdfIframeHeight = computed(() => {
|
||||
// 如果外部传入了 imgHeight,优先使用
|
||||
if (props.imgHeight) {
|
||||
return props.imgHeight;
|
||||
// 携带 token 请求 img/pdf 的 src
|
||||
const getImgSrcByToken = (src?: string) => {
|
||||
const targetSrc = src || props.authSrc
|
||||
|
||||
if (targetSrc.indexOf('.pdf') >= 0) {
|
||||
// PDF:通过 iframe 展示
|
||||
showIframe.value = true
|
||||
nextTick(() => {
|
||||
const tenantId = Session.getTenant()
|
||||
const iframe = authIframeRef.value
|
||||
if (!iframe) return
|
||||
|
||||
const request = new XMLHttpRequest()
|
||||
request.responseType = 'blob'
|
||||
request.open('get', targetSrc, 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 {
|
||||
// 图片:保持原有行为(直接使用 authSrc,不做 token 转发)
|
||||
showIframe.value = false
|
||||
// 如需带 token 加载图片,可参考被注释的旧逻辑在此扩展
|
||||
}
|
||||
// 否则根据窗口高度动态计算:dialog header 约 50px,padding 约 40px,留一些余量
|
||||
return `${windowHeight.value - 120}px`;
|
||||
});
|
||||
}
|
||||
|
||||
// 监听窗口大小变化
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
};
|
||||
const refreshImg = (src?: string) => {
|
||||
getImgSrcByToken(src)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize);
|
||||
getImgSrcByToken();
|
||||
});
|
||||
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
|
||||
});
|
||||
|
||||
refreshImg,
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.pdf-preview-dialog :deep(.el-dialog__body) {
|
||||
padding: 20px !important;
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
141
src/components/tools/preview-file.vue
Normal file
141
src/components/tools/preview-file.vue
Normal 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 约 50px,padding 约 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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user