|
@@ -34,6 +34,12 @@
|
|
|
</div>
|
|
</div>
|
|
|
<template v-if="tip" class="el-upload__tip">{{ tip }}</template>
|
|
<template v-if="tip" class="el-upload__tip">{{ tip }}</template>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
+
|
|
|
|
|
+ <template v-else-if="listType === 'picture-card'">
|
|
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
|
|
|
<template v-else>
|
|
<template v-else>
|
|
|
<slot name="trigger">
|
|
<slot name="trigger">
|
|
@@ -47,8 +53,33 @@
|
|
|
</template>
|
|
</template>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
- <template #file-list>
|
|
|
|
|
- <slot name="file-list" :file-list="fileList">
|
|
|
|
|
|
|
+ <template #file-list="{ file }">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
|
|
|
|
|
+ <span class="el-upload-list__item-actions">
|
|
|
|
|
+ <span
|
|
|
|
|
+ class="el-upload-list__item-preview"
|
|
|
|
|
+ @click="handlePreview(file)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon><zoom-in /></el-icon>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span
|
|
|
|
|
+ v-if="!disabled"
|
|
|
|
|
+ class="el-upload-list__item-delete"
|
|
|
|
|
+ @click="handleDownload(file)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon><Download /></el-icon>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span
|
|
|
|
|
+ v-if="!disabled"
|
|
|
|
|
+ class="el-upload-list__item-delete"
|
|
|
|
|
+ @click="handleRemove(file)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon><Delete /></el-icon>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- <slot name="file-list" :file-list="fileList">
|
|
|
<el-upload-list :file-list="fileList" @remove="handleRemove">
|
|
<el-upload-list :file-list="fileList" @remove="handleRemove">
|
|
|
<template #default="{ file }">
|
|
<template #default="{ file }">
|
|
|
<el-upload-list-item
|
|
<el-upload-list-item
|
|
@@ -85,7 +116,7 @@
|
|
|
</el-upload-list-item>
|
|
</el-upload-list-item>
|
|
|
</template>
|
|
</template>
|
|
|
</el-upload-list>
|
|
</el-upload-list>
|
|
|
- </slot>
|
|
|
|
|
|
|
+ </slot> -->
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<!-- 上传进度条 -->
|
|
<!-- 上传进度条 -->
|
|
@@ -102,7 +133,7 @@
|
|
|
<el-dialog
|
|
<el-dialog
|
|
|
v-model="previewVisible"
|
|
v-model="previewVisible"
|
|
|
:title="previewTitle"
|
|
:title="previewTitle"
|
|
|
- width="80%"
|
|
|
|
|
|
|
+ width="50%"
|
|
|
destroy-on-close
|
|
destroy-on-close
|
|
|
>
|
|
>
|
|
|
<div class="preview-container">
|
|
<div class="preview-container">
|
|
@@ -130,21 +161,27 @@
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
import { ref, computed, watch } from 'vue'
|
|
import { ref, computed, watch } from 'vue'
|
|
|
-import {
|
|
|
|
|
- UploadFilled,
|
|
|
|
|
- ZoomIn,
|
|
|
|
|
- Download,
|
|
|
|
|
- Delete,
|
|
|
|
|
- Document,
|
|
|
|
|
- Plus
|
|
|
|
|
-} from '@element-plus/icons-vue'
|
|
|
|
|
|
|
+import DGTMessage from '@/utils/message'
|
|
|
|
|
+import { downloadFile } from '@/utils/util'
|
|
|
|
|
+// import {
|
|
|
|
|
+// UploadFilled,
|
|
|
|
|
+// ZoomIn,
|
|
|
|
|
+// Download,
|
|
|
|
|
+// Delete,
|
|
|
|
|
+// Document,
|
|
|
|
|
+// Plus
|
|
|
|
|
+// } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
|
// 组件属性
|
|
// 组件属性
|
|
|
const props = defineProps({
|
|
const props = defineProps({
|
|
|
|
|
+ disabled: {
|
|
|
|
|
+ type: Boolean,
|
|
|
|
|
+ default: false
|
|
|
|
|
+ },
|
|
|
// 上传地址
|
|
// 上传地址
|
|
|
uploadUrl: {
|
|
uploadUrl: {
|
|
|
type: String,
|
|
type: String,
|
|
|
- required: true
|
|
|
|
|
|
|
+ default: import.meta.env.VITE_API_BASE_URL + '/upload'
|
|
|
},
|
|
},
|
|
|
// 请求头
|
|
// 请求头
|
|
|
headers: {
|
|
headers: {
|
|
@@ -164,7 +201,10 @@ const props = defineProps({
|
|
|
// 上传时附带的额外参数
|
|
// 上传时附带的额外参数
|
|
|
data: {
|
|
data: {
|
|
|
type: Object,
|
|
type: Object,
|
|
|
- default: () => ({})
|
|
|
|
|
|
|
+ default: () => ({
|
|
|
|
|
+ //"note","工作流""workflow","工作流""video","视频""common","公共"
|
|
|
|
|
+ "directory":"common"
|
|
|
|
|
+ })
|
|
|
},
|
|
},
|
|
|
// 文件字段名
|
|
// 文件字段名
|
|
|
fileFieldName: {
|
|
fileFieldName: {
|
|
@@ -225,7 +265,7 @@ const props = defineProps({
|
|
|
// 最大文件大小 (MB)
|
|
// 最大文件大小 (MB)
|
|
|
maxSize: {
|
|
maxSize: {
|
|
|
type: Number,
|
|
type: Number,
|
|
|
- default: 0 // 0 表示不限制
|
|
|
|
|
|
|
+ default: 10 // 0 表示不限制
|
|
|
},
|
|
},
|
|
|
// 是否显示进度条
|
|
// 是否显示进度条
|
|
|
showProgress: {
|
|
showProgress: {
|
|
@@ -259,14 +299,31 @@ const currentPreviewContent = ref('')
|
|
|
// 计算属性
|
|
// 计算属性
|
|
|
const uploadData = computed(() => props.data)
|
|
const uploadData = computed(() => props.data)
|
|
|
const previewTitle = computed(() => currentPreviewFile.value ? `预览:${currentPreviewFile.value.name}` : '文件预览')
|
|
const previewTitle = computed(() => currentPreviewFile.value ? `预览:${currentPreviewFile.value.name}` : '文件预览')
|
|
|
-
|
|
|
|
|
|
|
+// 添加一个标志位,防止循环更新
|
|
|
|
|
+const isInnerUpdate = ref(false)
|
|
|
// 监听modelValue变化,同步到内部fileList
|
|
// 监听modelValue变化,同步到内部fileList
|
|
|
watch(() => props.modelValue, (newVal) => {
|
|
watch(() => props.modelValue, (newVal) => {
|
|
|
- fileList.value = [...newVal]
|
|
|
|
|
-}, { deep: true })
|
|
|
|
|
|
|
+ // 如果是内部更新触发的,跳过
|
|
|
|
|
+ if (isInnerUpdate.value) {
|
|
|
|
|
+ isInnerUpdate.value = false;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (newVal && newVal.length) {
|
|
|
|
|
+ fileList.value = newVal.map(item => ({
|
|
|
|
|
+ ...item, // 完全保留接口返回的原始数据
|
|
|
|
|
+ name: item.name || item.originalFileName || (item.url ? item.url.substring(item.url.lastIndexOf('/') + 1) : 'unknown'),
|
|
|
|
|
+ url: item.url || item.fileUrl || item.path,
|
|
|
|
|
+ status: item.status || 'success',
|
|
|
|
|
+ uid: item.uid || Date.now() + Math.random()
|
|
|
|
|
+ }));
|
|
|
|
|
+ }
|
|
|
|
|
+ // fileList.value = [...newVal]
|
|
|
|
|
+}, { immediate: true })
|
|
|
|
|
|
|
|
// 监听fileList变化,同步到modelValue
|
|
// 监听fileList变化,同步到modelValue
|
|
|
watch(() => fileList.value, (newVal) => {
|
|
watch(() => fileList.value, (newVal) => {
|
|
|
|
|
+ // 设置内部更新标志
|
|
|
|
|
+ isInnerUpdate.value = true
|
|
|
emit('update:modelValue', [...newVal])
|
|
emit('update:modelValue', [...newVal])
|
|
|
}, { deep: true })
|
|
}, { deep: true })
|
|
|
|
|
|
|
@@ -276,6 +333,7 @@ const handleBeforeUpload = (rawFile) => {
|
|
|
if (props.maxSize > 0) {
|
|
if (props.maxSize > 0) {
|
|
|
const maxSizeBytes = props.maxSize * 1024 * 1024
|
|
const maxSizeBytes = props.maxSize * 1024 * 1024
|
|
|
if (rawFile.size > maxSizeBytes) {
|
|
if (rawFile.size > maxSizeBytes) {
|
|
|
|
|
+ DGTMessage.error(`文件大小超出限制,最大允许 ${props.maxSize}MB`)
|
|
|
emit('error', {
|
|
emit('error', {
|
|
|
message: `文件大小超出限制,最大允许 ${props.maxSize}MB`
|
|
message: `文件大小超出限制,最大允许 ${props.maxSize}MB`
|
|
|
})
|
|
})
|
|
@@ -289,11 +347,20 @@ const handleBeforeUpload = (rawFile) => {
|
|
|
|
|
|
|
|
// 上传成功处理
|
|
// 上传成功处理
|
|
|
const handleSuccess = (response, rawFile, uploadedFiles) => {
|
|
const handleSuccess = (response, rawFile, uploadedFiles) => {
|
|
|
|
|
+ // 将上传成功的文件添加到fileList中
|
|
|
|
|
+ fileList.value.push({
|
|
|
|
|
+ ...response,
|
|
|
|
|
+ name: response.originalFileName || response.name,
|
|
|
|
|
+ url: response.url,
|
|
|
|
|
+ status: 'success',
|
|
|
|
|
+ uid: rawFile.uid
|
|
|
|
|
+ })
|
|
|
emit('success', response, rawFile, uploadedFiles)
|
|
emit('success', response, rawFile, uploadedFiles)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 上传失败处理
|
|
// 上传失败处理
|
|
|
const handleError = (error, rawFile, uploadedFiles) => {
|
|
const handleError = (error, rawFile, uploadedFiles) => {
|
|
|
|
|
+ DGTMessage.error(error.message || '文件上传失败')
|
|
|
emit('error', error, rawFile, uploadedFiles)
|
|
emit('error', error, rawFile, uploadedFiles)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -313,7 +380,18 @@ const handleBeforeRemove = (file, fileList) => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 文件移除处理
|
|
// 文件移除处理
|
|
|
-const handleRemove = (file, fileList) => {
|
|
|
|
|
|
|
+const handleRemove = (file) => {
|
|
|
|
|
+ console.log('移除文件:', file)
|
|
|
|
|
+ // 从fileList中移除该文件
|
|
|
|
|
+ // fileList.value = fileList.value.filter(f => f.url !== file.url)
|
|
|
|
|
+ // 从fileList中移除该文件
|
|
|
|
|
+ const index = fileList.value.findIndex(f =>
|
|
|
|
|
+ f.uid === file.uid || f.url === file.url || f.id === file.id
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (index > -1) {
|
|
|
|
|
+ fileList.value.splice(index, 1)
|
|
|
|
|
+ }
|
|
|
emit('remove', file, fileList)
|
|
emit('remove', file, fileList)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -324,6 +402,7 @@ const handleExceed = (files, fileList) => {
|
|
|
|
|
|
|
|
// 文件预览处理
|
|
// 文件预览处理
|
|
|
const handlePreview = (file) => {
|
|
const handlePreview = (file) => {
|
|
|
|
|
+ console.log('预览文件:', file)
|
|
|
currentPreviewFile.value = file
|
|
currentPreviewFile.value = file
|
|
|
|
|
|
|
|
// 图片文件直接预览
|
|
// 图片文件直接预览
|
|
@@ -336,17 +415,20 @@ const handlePreview = (file) => {
|
|
|
// 文本文件读取内容后预览
|
|
// 文本文件读取内容后预览
|
|
|
if (isTextFile(file)) {
|
|
if (isTextFile(file)) {
|
|
|
if (file.url) {
|
|
if (file.url) {
|
|
|
- fetch(file.url)
|
|
|
|
|
- .then(response => response.text())
|
|
|
|
|
- .then(text => {
|
|
|
|
|
- currentPreviewContent.value = text
|
|
|
|
|
- previewVisible.value = true
|
|
|
|
|
- emit('preview', file)
|
|
|
|
|
- })
|
|
|
|
|
- .catch(error => {
|
|
|
|
|
- console.error('读取文件内容失败:', error)
|
|
|
|
|
- emit('error', error, file)
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ //浏览器下载
|
|
|
|
|
+ downloadFile(file.url, file.name)
|
|
|
|
|
+
|
|
|
|
|
+ // fetch(file.url)
|
|
|
|
|
+ // .then(response => response.text())
|
|
|
|
|
+ // .then(text => {
|
|
|
|
|
+ // currentPreviewContent.value = text
|
|
|
|
|
+ // previewVisible.value = true
|
|
|
|
|
+ // emit('preview', file)
|
|
|
|
|
+ // })
|
|
|
|
|
+ // .catch(error => {
|
|
|
|
|
+ // console.error('读取文件内容失败:', error)
|
|
|
|
|
+ // emit('error', error, file)
|
|
|
|
|
+ // })
|
|
|
} else {
|
|
} else {
|
|
|
previewVisible.value = true
|
|
previewVisible.value = true
|
|
|
emit('preview', file)
|
|
emit('preview', file)
|
|
@@ -366,14 +448,14 @@ const handleDownload = (file) => {
|
|
|
|
|
|
|
|
// 辅助方法:判断是否为图片文件
|
|
// 辅助方法:判断是否为图片文件
|
|
|
const isImageFile = (file) => {
|
|
const isImageFile = (file) => {
|
|
|
- return file.type.startsWith('image/') ||
|
|
|
|
|
|
|
+ return file.type && file.type.startsWith('image/') ||
|
|
|
/\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name)
|
|
/\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 辅助方法:判断是否为文本文件
|
|
// 辅助方法:判断是否为文本文件
|
|
|
const isTextFile = (file) => {
|
|
const isTextFile = (file) => {
|
|
|
- return file.type.startsWith('text/') ||
|
|
|
|
|
- /\.(txt|json|yaml|yml|xml|html|htm|css|js|ts|md|markdown)$/i.test(file.name)
|
|
|
|
|
|
|
+ return file.type && file.type.startsWith('text/') ||
|
|
|
|
|
+ /\.(txt|json|yaml|yml|xml|html|htm|css|js|ts|md|markdown|zip|rar)$/i.test(file.name)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 辅助方法:格式化文件大小
|
|
// 辅助方法:格式化文件大小
|
|
@@ -421,7 +503,7 @@ defineExpose({
|
|
|
|
|
|
|
|
.preview-container {
|
|
.preview-container {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
- max-height: 60vh;
|
|
|
|
|
|
|
+ min-height: 60vh;
|
|
|
overflow: auto;
|
|
overflow: auto;
|
|
|
|
|
|
|
|
.preview-image {
|
|
.preview-image {
|