458 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="basic-file-preview">
<view class="section-title">附件</view>
<view class="file-list" v-if="hasAttachments">
<!-- 显示所有解析后的附件 -->
<view
v-for="(file, index) in parsedAttachments"
:key="(file as any).id || index"
class="file-item"
>
<view class="file-icon">
<image
:src="getFileIcon(getFileSuffix(file))"
class="icon-image"
mode="aspectFit"
/>
</view>
<view class="file-info">
<text class="file-name">{{ getFileName(file) }}</text>
</view>
<view class="file-actions">
<u-button
v-if="canPreview(getFileSuffix(file)) && !isVideo(getFileSuffix(file))"
text="预览"
size="mini"
type="primary"
@click="previewFile(file)"
/>
<u-button
v-if="isVideo(getFileSuffix(file))"
text="播放"
size="mini"
type="success"
@click="previewFile(file)"
/>
<u-button
v-if="isImage(getFileSuffix(file))"
text="查看"
size="mini"
type="warning"
@click="previewFile(file)"
/>
<u-button
text="下载"
size="mini"
type="info"
@click="downloadFileAction(file)"
/>
</view>
</view>
</view>
<view v-else class="no-files">
<text>暂无附件</text>
</view>
</view>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import {
isVideo,
isImage,
canPreview,
previewFile as previewFileUtil,
previewVideo as previewVideoUtil,
previewImage as previewImageUtil,
downloadFile as downloadFileUtil
} from "@/utils/filePreview";
import { imagUrl } from "@/utils";
// 定义组件属性
interface Props {
fileUrl?: string;
fileName?: string;
fileFormat?: string;
files?: FileInfo[];
}
interface FileInfo {
id?: string;
name: string;
size: number;
url: string;
resourName?: string;
resourUrl?: string;
resSuf?: string;
}
// 定义默认属性值
const props = withDefaults(defineProps<Props>(), {
fileUrl: '',
fileName: '',
fileFormat: '',
files: () => []
});
// 定义事件
const emit = defineEmits<{
(e: 'preview', file: FileInfo): void;
(e: 'download', file: FileInfo): void;
}>();
// 检查是否有附件
const hasAttachments = computed(() => {
const attachments = [];
// 处理files数组
if (props.files && props.files.length > 0) {
attachments.push(...props.files);
}
// 处理逗号分隔的fileUrl字符串
else if (props.fileUrl) {
const fileUrls = props.fileUrl.split(',').map(url => url.trim()).filter(url => url);
// 解析fileName字符串如果存在
let fileNames: string[] = [];
if (props.fileName) {
fileNames = props.fileName.split(',').map(name => name.trim()).filter(name => name);
}
// 将URL字符串转换为文件对象
fileUrls.forEach((url, index) => {
// 优先使用解析出的文件名如果没有则从URL提取
let displayName = fileNames[index] || url.split('/').pop() || `文件${index + 1}`;
const fileFormat = url.split('.').pop() || 'unknown';
// 如果文件名包含扩展名去掉扩展名保持resourName不包含扩展名
let resourName = displayName;
if (displayName.includes('.' + fileFormat)) {
resourName = displayName.replace('.' + fileFormat, '');
}
attachments.push({
id: `file_${index}`,
name: displayName,
url: url,
resourUrl: url,
resourName: resourName,
resSuf: fileFormat,
size: 0
});
});
}
return attachments.length > 0;
});
// 计算属性:解析后的附件列表
const parsedAttachments = computed(() => {
const attachments = [];
// 处理files数组
if (props.files && props.files.length > 0) {
attachments.push(...props.files);
}
// 处理逗号分隔的fileUrl字符串
else if (props.fileUrl) {
const fileUrls = props.fileUrl.split(',').map(url => url.trim()).filter(url => url);
// 解析fileName字符串如果存在
let fileNames: string[] = [];
if (props.fileName) {
fileNames = props.fileName.split(',').map(name => name.trim()).filter(name => name);
}
// 将URL字符串转换为文件对象
fileUrls.forEach((url, index) => {
// 优先使用解析出的文件名如果没有则从URL提取
let displayName = fileNames[index] || url.split('/').pop() || `文件${index + 1}`;
const fileFormat = url.split('.').pop() || 'unknown';
// 如果文件名包含扩展名去掉扩展名保持resourName不包含扩展名
let resourName = displayName;
if (displayName.includes('.' + fileFormat)) {
resourName = displayName.replace('.' + fileFormat, '');
}
attachments.push({
id: `file_${index}`,
name: displayName,
url: url,
resourUrl: url,
resourName: resourName,
resSuf: fileFormat,
size: 0
});
});
console.log('parsedAttachments', fileNames, fileUrls);
}
return attachments;
});
// 获取文件名
const getFileName = (file: FileInfo) => {
if (file.resourName) {
return file.resourName;
}
if (file.name) {
// 如果name包含扩展名去掉扩展名
const lastDotIndex = file.name.lastIndexOf('.');
if (lastDotIndex > 0) {
return file.name.substring(0, lastDotIndex);
}
return file.name;
}
return '未知文件';
};
// 获取文件后缀
const getFileSuffix = (file: FileInfo) => {
if (file.resSuf) {
return file.resSuf;
}
if (file.url) {
const lastDotIndex = file.url.lastIndexOf('.');
if (lastDotIndex > 0) {
return file.url.substring(lastDotIndex + 1);
}
}
if (file.name) {
const lastDotIndex = file.name.lastIndexOf('.');
if (lastDotIndex > 0) {
return file.name.substring(lastDotIndex + 1);
}
}
return 'unknown';
};
// 获取文件图标
const getFileIcon = (fileType: string) => {
const type = fileType.toLowerCase();
switch (type) {
case 'pdf':
return '/static/base/view/pdf.png';
case 'doc':
case 'docx':
return '/static/base/view/word.png';
case 'xls':
case 'xlsx':
return '/static/base/view/excel.png';
case 'ppt':
case 'pptx':
return '/static/base/view/ppt.png';
case 'zip':
case 'rar':
case '7z':
return '/static/base/view/zip.png';
default:
return '/static/base/view/more.png';
}
};
// 文件预览
const previewFile = (file: FileInfo) => {
emit('preview', file);
// 确定文件URL和名称
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
const fileSuf = file.resSuf || file.name.split('.').pop() || '';
// 根据文件类型选择预览方式
if (isVideo(fileSuf)) {
handlePreviewVideo(file);
} else if (isImage(fileSuf)) {
handlePreviewImage(file);
} else if (canPreview(fileSuf)) {
handlePreviewDocument(file);
} else {
// 不支持预览的文件类型,直接下载
downloadFileAction(file);
}
};
// 文档预览
const handlePreviewDocument = (file: FileInfo) => {
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
const fileSuf = file.resSuf || file.name.split('.').pop() || '';
previewFileUtil(fileUrl, fileName, fileSuf)
.catch((error: any) => {
// 预览失败时尝试下载
downloadFileAction(file);
});
};
// 视频预览
const handlePreviewVideo = (file: FileInfo) => {
const videoUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
const videoName = file.resourName || file.name;
previewVideoUtil(videoUrl, videoName)
.catch((error: any) => {
// 视频预览失败时的处理
});
};
// 图片预览
const handlePreviewImage = (file: FileInfo) => {
const imageUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
previewImageUtil(imageUrl)
.catch((error: any) => {
// 图片预览失败时的处理
});
};
// 文件下载
const downloadFileAction = (file: FileInfo) => {
emit('download', file);
const finalUrl = file.resourUrl ? imagUrl(file.resourUrl) : imagUrl(file.url);
// 获取原始文件名
const originalFileName = getOriginalFileName(file);
// 调用下载工具函数
downloadFileUtil(finalUrl, originalFileName)
.then(() => {
uni.showToast({
title: "下载成功",
icon: "success",
});
})
.catch((error) => {
console.error('下载失败:', error);
uni.showToast({
title: "下载失败",
icon: "error",
});
});
};
// 获取原始文件名
const getOriginalFileName = (file: FileInfo) => {
// 优先使用resourName如果没有则使用name确保文件名正确
let fileName = '';
if (file.resourName) {
// 如果resourName已经包含扩展名直接使用
if (file.resSuf && !file.resourName.includes('.' + file.resSuf)) {
fileName = `${file.resourName}.${file.resSuf}`;
} else {
fileName = file.resourName;
}
} else if (file.name) {
// 如果name已经包含扩展名直接使用
if (file.resSuf && !file.name.includes('.' + file.resSuf)) {
fileName = `${file.name}.${file.resSuf}`;
} else {
fileName = file.name;
}
} else {
// 最后备用方案从URL提取文件名
const urlFileName = file.url.split('/').pop() || '未知文件';
fileName = urlFileName;
}
return fileName;
};
</script>
<style lang="scss" scoped>
.basic-file-preview {
margin-bottom: 20px;
padding: 15px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
border-bottom: 2px solid #007aff;
padding-bottom: 5px;
}
.file-item {
display: flex;
align-items: center;
padding: 12px;
border: 1px solid #eee;
border-radius: 8px;
margin-bottom: 10px;
background: #fafafa;
transition: all 0.3s ease;
&:active {
transform: translateY(1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.file-icon {
margin-right: 12px;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
.icon-image {
width: 28px;
height: 28px;
}
}
.file-info {
flex: 1;
.file-name {
display: block;
font-weight: 500;
margin-bottom: 4px;
color: #333;
font-size: 14px;
}
.file-size {
font-size: 12px;
color: #666;
margin-right: 8px;
}
.file-type {
font-size: 11px;
color: #999;
background: #f0f0f0;
padding: 2px 6px;
border-radius: 4px;
}
}
.file-actions {
display: flex;
gap: 4px;
flex-wrap: wrap;
justify-content: flex-end;
.u-button {
min-width: 20px;
height: 26px;
font-size: 12px;
border-radius: 13px;
padding: 0 6px;
}
}
}
.no-files {
text-align: center;
padding: 40px 20px;
color: #999;
font-size: 14px;
}
</style>