上传文件其中包含图片

This commit is contained in:
WindowBird 2025-11-18 14:46:09 +08:00
parent 8dcca60350
commit 37b81d188a

View File

@ -6,19 +6,38 @@
<text class="arrow"></text>
</view>
<view class="files-list" v-if="files.length">
<view class="images-preview" v-if="imageFiles.length">
<view
class="image-item"
v-for="(file, index) in imageFiles"
:key="(file.uid || file.path) + index"
>
<image
:src="file.path"
mode="aspectFill"
class="preview-image"
@click="previewImage(index)"
/>
<view class="remove-btn" @click.stop="removeFile(file)"></view>
</view>
</view>
<view class="files-list" v-if="otherFiles.length">
<view
class="file-item"
v-for="(file, index) in files"
:key="file.path + index"
v-for="(file, index) in otherFiles"
:key="(file.uid || file.path) + index"
@click="previewFile(file)"
>
<text class="file-icon">{{ getFileIcon(file.name) }}</text>
<text class="file-type-badge" :class="getFileTypeClass(file.name)">
{{ getFileTypeLabel(file.name) }}
</text>
<view class="file-info">
<text class="file-name">{{ file.name }}</text>
<text class="file-size" v-if="file.size > 0">{{ formatFileSize(file.size) }}</text>
</view>
<view class="remove-btn" @click.stop="removeFile(index)"></view>
<view class="file-icon">{{ getFileIcon(file.name) }}</view>
<view class="remove-btn" @click.stop="removeFile(file)"></view>
</view>
</view>
</view>
@ -61,6 +80,17 @@ const files = computed({
}
});
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic', 'heif', 'svg'];
const isImageFile = (file) => {
if (!file) return false;
const ext = getFileExtension(file.name);
return imageExtensions.includes(ext);
};
const imageFiles = computed(() => files.value.filter((file) => isImageFile(file)));
const otherFiles = computed(() => files.value.filter((file) => !isImageFile(file)));
const handleChooseFiles = async () => {
const remainingCount = props.maxCount - files.value.length;
if (remainingCount <= 0) {
@ -95,9 +125,10 @@ const chooseFilesWithUni = (remainingCount) => {
extension: props.extensions,
success: async (res) => {
try {
await uploadFiles(res.tempFiles.map(file => ({
await uploadFiles(res.tempFiles.map((file) => ({
path: file.path,
name: file.name
name: file.name,
size: file.size || 0
})));
resolve();
} catch (error) {
@ -251,15 +282,18 @@ const uploadFiles = async (fileList) => {
}
};
const removeFile = (index) => {
const next = [...files.value];
next.splice(index, 1);
const removeFile = (file) => {
const next = files.value.filter((item) => item !== file);
files.value = next;
};
const getFileExtension = (fileName = '') => {
if (!fileName) return '';
return fileName.split('.').pop().toLowerCase();
};
const getFileIcon = (fileName) => {
if (!fileName) return '📄';
const ext = fileName.split('.').pop().toLowerCase();
const ext = getFileExtension(fileName);
const iconMap = {
pdf: '📕',
doc: '📘',
@ -279,6 +313,36 @@ const getFileIcon = (fileName) => {
return iconMap[ext] || '📄';
};
const getFileTypeKey = (fileName) => {
const ext = getFileExtension(fileName);
if (!ext) return 'other';
if (imageExtensions.includes(ext)) return 'image';
if (['pdf'].includes(ext)) return 'pdf';
if (['doc', 'docx', 'wps'].includes(ext)) return 'doc';
if (['xls', 'xlsx', 'csv'].includes(ext)) return 'xls';
if (['ppt', 'pptx'].includes(ext)) return 'ppt';
if (['zip', 'rar', '7z'].includes(ext)) return 'zip';
return 'other';
};
const getFileTypeLabel = (fileName) => {
const type = getFileTypeKey(fileName);
const labelMap = {
image: 'IMG',
pdf: 'PDF',
doc: 'DOC',
xls: 'XLS',
ppt: 'PPT',
zip: 'ZIP',
other: 'FILE'
};
return labelMap[type] || 'FILE';
};
const getFileTypeClass = (fileName) => {
return `badge-${getFileTypeKey(fileName)}`;
};
const formatFileSize = (bytes) => {
if (!bytes || bytes === 0) return '';
const k = 1024;
@ -287,6 +351,15 @@ const formatFileSize = (bytes) => {
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
};
const previewImage = (index) => {
const urls = imageFiles.value.map((file) => file.path);
if (!urls.length) return;
uni.previewImage({
urls,
current: urls[index] || urls[0]
});
};
const previewFile = (file) => {
if (!file?.path) {
uni.showToast({
@ -296,14 +369,9 @@ const previewFile = (file) => {
return;
}
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
const ext = (file.name || '').split('.').pop().toLowerCase();
if (imageExts.includes(ext)) {
uni.previewImage({
urls: [file.path],
current: file.path
});
const ext = getFileExtension(file.name);
if (imageExtensions.includes(ext)) {
previewImage(imageFiles.value.findIndex((item) => item === file));
return;
}
@ -392,6 +460,9 @@ const previewFile = (file) => {
.files-list {
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 8px;
}
.file-item {
@ -408,11 +479,6 @@ const previewFile = (file) => {
}
}
.file-icon {
font-size: 24px;
flex-shrink: 0;
}
.file-info {
flex: 1;
display: flex;
@ -447,5 +513,85 @@ const previewFile = (file) => {
font-size: 14px;
cursor: pointer;
}
.images-preview {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.image-item {
position: relative;
width: calc((100% - 16px) / 3);
aspect-ratio: 1;
border-radius: 8px;
overflow: hidden;
background-color: #f4f4f5;
}
.preview-image {
width: 100%;
height: 100%;
}
.image-item .remove-btn {
position: absolute;
top: 6px;
right: 6px;
width: 22px;
height: 22px;
}
.file-type-badge {
padding: 4px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
color: #333;
background-color: #f5f5f5;
flex-shrink: 0;
}
.badge-pdf {
background-color: #fff1f0;
color: #f5222d;
}
.badge-doc {
background-color: #f0f5ff;
color: #2f54eb;
}
.badge-xls {
background-color: #f6ffed;
color: #52c41a;
}
.badge-ppt {
background-color: #fff7e6;
color: #fa8c16;
}
.badge-zip {
background-color: #f9f0ff;
color: #722ed1;
}
.badge-image {
background-color: #fff7e6;
color: #d48806;
}
.badge-other {
background-color: #f5f5f5;
color: #888;
}
.file-icon {
font-size: 20px;
color: #999;
flex-shrink: 0;
}
</style>