This commit is contained in:
吴红兵
2026-03-05 18:15:31 +08:00
parent ea7e48fc49
commit 44256b55cc

View File

@@ -18,6 +18,7 @@
<span class="flex-1 text-gray-700 truncate transition-colors duration-200 group-hover:text-blue-600">
{{ getFileName(file) }}
</span>
<el-icon class="mr-2 text-gray-400 transition-colors duration-200 group-hover:text-blue-500" @click.stop="handlePreview(file)"><View /></el-icon>
<el-icon class="text-gray-400 transition-colors duration-200 group-hover:text-blue-500"><Download /></el-icon>
</div>
</div>
@@ -69,6 +70,24 @@
<span class="file-name" style="flex: 1; font-size: 14px; color: #606266; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
{{ getFileName(file) }}
</span>
<el-button
type="primary"
link
size="small"
@click="handlePreview(file)"
style="margin-left: 4px;"
>
<el-icon><View /></el-icon>
</el-button>
<el-button
type="primary"
link
size="small"
@click="handleDownload(file)"
style="margin-left: 4px;"
>
<el-icon><Download /></el-icon>
</el-button>
<el-button
type="danger"
link
@@ -78,15 +97,6 @@
>
<el-icon><Delete /></el-icon>
</el-button>
<el-button
type="primary"
link
size="small"
@click="handlePreview(file)"
style="margin-left: 4px;"
>
<el-icon><Download /></el-icon>
</el-button>
</div>
</div>
</div>
@@ -124,15 +134,48 @@
</template>
</el-upload>
</div>
<!-- 图片预览使用 teleport + 最高 z-index 确保全屏 -->
<teleport to="body">
<div
v-if="imagePreviewVisible"
class="image-preview-overlay"
@click="imagePreviewVisible = false"
>
<img :src="imagePreviewUrl" class="image-preview-img" />
<button class="image-preview-close" @click.stop="imagePreviewVisible = false"></button>
</div>
</teleport>
<!-- PDF预览使用 teleport -->
<teleport to="body">
<el-dialog
v-model="pdfPreviewVisible"
title="文件预览"
:top="'5vh'"
width="90%"
:center-dialog="true"
:z-index="9999"
:close-on-click-modal="true"
append-to-body
class="pdf-preview-teleport"
>
<iframe
:src="pdfPreviewUrl"
style="width: 100%; height: 80vh; border: none;"
/>
</el-dialog>
</teleport>
</template>
<script setup lang="ts" name="upload-file">
import { useMessage } from '/@/hooks/message';
import { Session } from '/@/utils/storage';
import other from '/@/utils/other';
import request from '/@/utils/request';
import { useI18n } from 'vue-i18n';
import { ref, computed, watch } from 'vue';
import { Document, Download, Upload, Delete } from '@element-plus/icons-vue';
import { Document, Download, Upload, Delete, View } from '@element-plus/icons-vue';
// 定义基础URL
const baseUrl = import.meta.env.VITE_API_URL || '';
@@ -207,6 +250,11 @@ const props = defineProps({
type: String,
default: '/admin/sys-file/upload',
},
// 自定义下载/预览URL如果不传则根据uploadFileUrl自动推导
downloadFileUrl: {
type: String,
default: '',
},
type: {
type: String,
default: 'default',
@@ -240,6 +288,14 @@ const uploadList = ref<UploadFileItem[]>([]);
const fileUpload = ref();
const { t } = useI18n();
// 图片预览状态
const imagePreviewVisible = ref(false);
const imagePreviewUrl = ref('');
// PDF预览状态
const pdfPreviewVisible = ref(false);
const pdfPreviewUrl = ref('');
// 请求头处理
const headers = computed(() => {
return {
@@ -248,6 +304,22 @@ const headers = computed(() => {
};
});
// 推导下载URL优先使用传入的downloadFileUrl否则根据uploadFileUrl自动推导
const downloadBaseUrl = computed(() => {
if (props.downloadFileUrl) {
return props.downloadFileUrl;
}
// 根据uploadFileUrl自动推导下载URL
if (props.uploadFileUrl.includes('/purchase/purchasingfiles/')) {
return '/purchase/purchasingfiles/downloadById';
}
if (props.uploadFileUrl.includes('/admin/sys-file/')) {
return '/admin/sys-file/download';
}
// 默认返回空字符串使用URL直接下载
return '';
});
// 请求参数处理
const formData = computed(() => {
return Object.assign(props.data, { dir: props.dir });
@@ -376,11 +448,12 @@ const handleRemove = (file: { name?: string; id?: string; url?: string }) => {
emit('change', listToString(fileList.value), fileList.value);
};
const handlePreview = (file: any) => {
// 优先使用文件ID下载采购附件使用 downloadById 接口
if (file.id) {
// 判断是否是采购附件(通过 fileType 判断)
const downloadUrl = `/purchase/purchasingfiles/downloadById?fileId=${encodeURIComponent(file.id)}`;
const handleDownload = (file: any) => {
// 优先使用文件ID下载如果有自定义下载URL
if (file.id && downloadBaseUrl.value) {
const downloadUrl = downloadBaseUrl.value.includes('?')
? `${downloadBaseUrl.value}&fileId=${encodeURIComponent(file.id)}`
: `${downloadBaseUrl.value}?fileId=${encodeURIComponent(file.id)}`;
other.downBlobFile(downloadUrl, {}, file.name || file.fileTitle || '文件');
return;
}
@@ -392,6 +465,91 @@ const handlePreview = (file: any) => {
useMessage().warning('无法获取文件下载信息');
};
const handlePreview = async (file: any) => {
// 获取文件URL
let fileUrl = '';
if (file.id && downloadBaseUrl.value) {
// 使用配置的下载URL
fileUrl = downloadBaseUrl.value.includes('?')
? `${downloadBaseUrl.value}&fileId=${encodeURIComponent(file.id)}`
: `${downloadBaseUrl.value}?fileId=${encodeURIComponent(file.id)}`;
} else if (file.id) {
// 兼容旧的ID方式
fileUrl = `/purchase/purchasingfiles/downloadById?fileId=${encodeURIComponent(file.id)}`;
} else if (file.url) {
fileUrl = file.url;
} else {
useMessage().warning('无法获取文件信息');
return;
}
// 获取文件名:优先使用 name其次从 url 提取
let fileName = file.name || file.fileTitle || '';
if (!fileName && file.url) {
// 从 URL 中提取文件名
try {
const urlObj = new URL(file.url, window.location.origin);
const nameFromUrl = urlObj.searchParams.get('originalFileName') || urlObj.searchParams.get('fileName');
if (nameFromUrl) {
fileName = nameFromUrl;
}
} catch {}
}
if (!fileName) {
fileName = '文件';
}
// 获取文件扩展名
const ext = fileName.split('.').pop()?.toLowerCase() || '';
// 判断是否是图片
const imageExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'];
// 判断是否是PDF
const isPdf = ext === 'pdf';
// 检查是否是图片
const isImage = imageExts.includes(ext);
try {
if (isImage) {
// 图片预览:使用 el-image-viewerteleported 到 body全屏显示
const response = await request({
url: fileUrl,
method: 'get',
responseType: 'blob',
}) as unknown as Response;
if (!response || response.type === 'error') {
useMessage().error('获取文件失败');
return;
}
const blob = response as unknown as Blob;
imagePreviewUrl.value = window.URL.createObjectURL(blob);
imagePreviewVisible.value = true;
} else if (isPdf) {
// PDF预览使用 el-dialogappend-to-body全屏显示
const response = await request({
url: fileUrl,
method: 'get',
responseType: 'blob',
}) as unknown as Response;
if (!response || response.type === 'error') {
useMessage().error('获取文件失败');
return;
}
const blob = response as unknown as Blob;
pdfPreviewUrl.value = window.URL.createObjectURL(blob);
pdfPreviewVisible.value = true;
} else {
// 其他类型文件,提示无法预览,只能下载
useMessage().warning('该文件类型暂不支持预览,请下载查看');
handleDownload(file);
}
} catch (error) {
console.error('预览失败:', error);
useMessage().error('预览失败');
}
};
// 添加 handleExceed 函数
const handleExceed = () => {
useMessage().warning(`${t('excel.uploadLimit')} ${props.limit} ${t('excel.files')}`);
@@ -481,3 +639,62 @@ defineExpose({
submit,
});
</script>
<style scoped>
.w-full {
width: 100%;
}
.upload-file {
width: 100%;
}
.mb20 {
margin-bottom: 20px;
}
</style>
<!-- 全局样式图片预览遮罩teleport body不受 scoped 限制 -->
<style>
.image-preview-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
cursor: pointer;
}
.image-preview-img {
max-width: 95%;
max-height: 95%;
object-fit: contain;
}
.image-preview-close {
position: absolute;
top: 20px;
right: 30px;
width: 40px;
height: 40px;
background: rgba(255, 255, 255, 0.2);
border: none;
border-radius: 50%;
color: #fff;
font-size: 24px;
cursor: pointer;
transition: background 0.3s;
}
.image-preview-close:hover {
background: rgba(255, 255, 255, 0.4);
}
/* PDF 预览对话框样式 */
.pdf-preview-teleport .el-dialog__body {
padding: 0;
}
</style>