From 37b81d188a295b850873594415b52637d890ba98 Mon Sep 17 00:00:00 2001
From: WindowBird <13870814+windows-bird@user.noreply.gitee.com>
Date: Tue, 18 Nov 2025 14:46:09 +0800
Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E5=85=B6?=
=?UTF-8?q?=E4=B8=AD=E5=8C=85=E5=90=AB=E5=9B=BE=E7=89=87?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
components/task/AttachmentFileUploader.vue | 196 ++++++++++++++++++---
1 file changed, 171 insertions(+), 25 deletions(-)
diff --git a/components/task/AttachmentFileUploader.vue b/components/task/AttachmentFileUploader.vue
index c476117..a423f09 100644
--- a/components/task/AttachmentFileUploader.vue
+++ b/components/task/AttachmentFileUploader.vue
@@ -6,19 +6,38 @@
›
-
+
+
+
+ ✕
+
+
+
+
- {{ getFileIcon(file.name) }}
+
+ {{ getFileTypeLabel(file.name) }}
+
{{ file.name }}
{{ formatFileSize(file.size) }}
- ✕
+ {{ getFileIcon(file.name) }}
+ ✕
@@ -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;
+}