跟新
This commit is contained in:
@@ -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-viewer(teleported 到 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-dialog(append-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>
|
||||
|
||||
Reference in New Issue
Block a user