Pārlūkot izejas kodu

```
feat(FileUploader): 增强文件上传组件功能

- 添加picture-card列表类型的支持,显示加号图标
- 实现文件列表的预览、下载、删除操作功能
- 添加文件预览弹窗,支持图片和文本文件预览
- 集成DGTMessage提示组件,优化用户反馈
- 增加disabled属性控制组件是否禁用
- 设置默认上传目录为'common'
- 调整最大文件大小默认限制为10MB
- 优化文件上传前的大小验证和错误提示
- 改进文件移除逻辑,支持通过uid、url、id匹配删除
- 修复modelValue与fileList的双向绑定循环更新问题
- 添加对ZIP、RAR等格式文件的识别支持
- 调整预览弹窗宽度为50%,预览容器最小高度为60vh

refactor(WorkflowAdd): 调整工作流页面文件上传配置

- 更新文件上传大小限制描述为10MB
- 为工作流预览区域添加图片上传组件
- 配置图片上传目录为'workflow'
- 优化文件上传成功和失败的提示逻辑
- 添加表单提交前的调试日志输出
```

zhangningning 1 mēnesi atpakaļ
vecāks
revīzija
e52395848f
4 mainītis faili ar 167 papildinājumiem un 49 dzēšanām
  1. 6 0
      src/api/common.js
  2. 116 34
      src/components/FileUploader.vue
  3. 34 15
      src/pages/WorkflowAdd.vue
  4. 11 0
      src/utils/util.js

+ 6 - 0
src/api/common.js

@@ -0,0 +1,6 @@
+import request from './request.js'
+
+// 上传文件
+export function uploadFile(data = {}) {
+  return request.post('/upload',data)
+}

+ 116 - 34
src/components/FileUploader.vue

@@ -34,6 +34,12 @@
         </div>
         <template v-if="tip" class="el-upload__tip">{{ tip }}</template>
       </template>
+
+      <template v-else-if="listType === 'picture-card'">
+       <el-icon><Plus /></el-icon>
+      </template>
+
+      
       
       <template v-else>
         <slot name="trigger">
@@ -47,8 +53,33 @@
         </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">
             <template #default="{ file }">
               <el-upload-list-item
@@ -85,7 +116,7 @@
               </el-upload-list-item>
             </template>
           </el-upload-list>
-        </slot>
+        </slot> -->
       </template>
       
       <!-- 上传进度条 -->
@@ -102,7 +133,7 @@
     <el-dialog
       v-model="previewVisible"
       :title="previewTitle"
-      width="80%"
+      width="50%"
       destroy-on-close
     >
       <div class="preview-container">
@@ -130,21 +161,27 @@
 
 <script setup>
 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({
+  disabled: {
+    type: Boolean,
+    default: false
+  },
   // 上传地址
   uploadUrl: {
     type: String,
-    required: true
+    default: import.meta.env.VITE_API_BASE_URL + '/upload'
   },
   // 请求头
   headers: {
@@ -164,7 +201,10 @@ const props = defineProps({
   // 上传时附带的额外参数
   data: {
     type: Object,
-    default: () => ({})
+    default: () => ({
+      //"note","工作流""workflow","工作流""video","视频""common","公共"
+      "directory":"common"
+    })
   },
   // 文件字段名
   fileFieldName: {
@@ -225,7 +265,7 @@ const props = defineProps({
   // 最大文件大小 (MB)
   maxSize: {
     type: Number,
-    default: 0 // 0 表示不限制
+    default: 10 // 0 表示不限制
   },
   // 是否显示进度条
   showProgress: {
@@ -259,14 +299,31 @@ const currentPreviewContent = ref('')
 // 计算属性
 const uploadData = computed(() => props.data)
 const previewTitle = computed(() => currentPreviewFile.value ? `预览:${currentPreviewFile.value.name}` : '文件预览')
-
+// 添加一个标志位,防止循环更新
+const isInnerUpdate = ref(false)
 // 监听modelValue变化,同步到内部fileList
 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
 watch(() => fileList.value, (newVal) => {
+  // 设置内部更新标志
+  isInnerUpdate.value = true
   emit('update:modelValue', [...newVal])
 }, { deep: true })
 
@@ -276,6 +333,7 @@ const handleBeforeUpload = (rawFile) => {
   if (props.maxSize > 0) {
     const maxSizeBytes = props.maxSize * 1024 * 1024
     if (rawFile.size > maxSizeBytes) {
+      DGTMessage.error(`文件大小超出限制,最大允许 ${props.maxSize}MB`)
       emit('error', {
         message: `文件大小超出限制,最大允许 ${props.maxSize}MB`
       })
@@ -289,11 +347,20 @@ const handleBeforeUpload = (rawFile) => {
 
 // 上传成功处理
 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)
 }
 
 // 上传失败处理
 const handleError = (error, rawFile, uploadedFiles) => {
+  DGTMessage.error(error.message || '文件上传失败')
   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)
 }
 
@@ -324,6 +402,7 @@ const handleExceed = (files, fileList) => {
 
 // 文件预览处理
 const handlePreview = (file) => {
+  console.log('预览文件:', file)
   currentPreviewFile.value = file
   
   // 图片文件直接预览
@@ -336,17 +415,20 @@ const handlePreview = (file) => {
   // 文本文件读取内容后预览
   if (isTextFile(file)) {
     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 {
       previewVisible.value = true
       emit('preview', file)
@@ -366,14 +448,14 @@ const handleDownload = (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)
 }
 
 // 辅助方法:判断是否为文本文件
 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 {
     width: 100%;
-    max-height: 60vh;
+    min-height: 60vh;
     overflow: auto;
     
     .preview-image {

+ 34 - 15
src/pages/WorkflowAdd.vue

@@ -6,21 +6,18 @@
         <div class="gap10">
           <div class="line_vertical"></div>
           <div class="font_size20 bold">文件上传</div>
-          <div class="gray999 font_size14">支持格式:JSON、YAML、ZIP(最大100MB)</div>
+          <div class="gray999 font_size14">支持格式:JSON、YAML、ZIP(最大10MB)</div>
         </div>
         <div class="mt10">
           <!-- 上传 -->
           <FileUploader
             ref="fileUploader"
-            :upload-url="uploadUrl"
             accept=".json,.yaml,.yml,.zip"
-            :max-size="100"
             :multiple="false"
             :auto-upload="true"
             :drag="true"
-            :list-type="picture-card"
-            v-model="uploadedFiles"
-            tip="请上传JSON、YAML、ZIP格式的文件,最大100MB"
+            v-model="files"
+            tip="请上传JSON、YAML、ZIP格式的文件,最大10MB"
             @success="handleUploadSuccess"
             @error="handleUploadError"
             @progress="handleUploadProgress"
@@ -40,16 +37,19 @@
             <el-input v-model="ruleForm.name" placeholder="请输入工作流标题"  maxlength="50" />
           </el-form-item>
           <el-form-item label="工作流预览">
+            <div>支持批量上传文件,文件格式不限,最多只能上传 5 份文件</div>
+            <!-- 图片类型 -->
             <FileUploader
               ref="fileUploader"
-              :upload-url="uploadUrl"
+              accept="image/*"
               :max-size="100"
               :multiple="true"
               :auto-upload="true"
-              :list-type="picture-card"
-              buttonText="添加文件"
-              v-model="uploadedFiles"
-              tip="支持批量上传文件,文件格式不限,最多只能上传 5 份文件"
+              list-type="picture-card"
+              :data="{ directory: 'workflow' }"
+              buttonText=""
+              v-model="images"
+              tip=""
               @success="handleUploadSuccess"
               @error="handleUploadError"
               @progress="handleUploadProgress"
@@ -112,7 +112,23 @@ const activePlatform = ref(route.query.activePlatform || '');
 
 // 上传文件列表
 const uploadedFiles = ref([])
-const uploadUrl = ref('/api/upload') // 实际项目中替换为真实的上传接口地址
+// 图片列表
+const images = ref(
+  [
+    {
+     url:"http://jcxxpt.oss-cn-beijing.aliyuncs.com/id_card/2025/12/26/MFE1Qdqi9tXdc0cb704ec71877b97f0f0bde3a1bbb72_20251226111105A001.jpg"
+    }
+  ]
+)
+const files = ref(
+  [
+    {
+     url:"http://jcxxpt.oss-cn-beijing.aliyuncs.com/common/2025/12/26/Iwk7y3ArgL5Of52f7c2309b56d382f14540de00dd6ea_20251226111356A002.zip"
+    }
+  ]
+)
+
+// const uploadUrl = ref('/api/upload') // 实际项目中替换为真实的上传接口地址
 
 const editorContent = ref(
   [
@@ -135,6 +151,7 @@ const editorContent = ref(
 const ruleFormRef = ref(null)
 const ruleForm = reactive({
   name: '',
+  uploadedFiles: []
 })
 // 校验规则
 const rules = reactive({
@@ -150,6 +167,8 @@ onMounted(() => {
 
 // 提交表单
 const submitForm = async () => {
+  console.log('提交表单', ruleForm,images.value)
+  return;
   await ruleFormRef.value.validate((valid, fields) => {
     console.log(valid, fields)
     if (!valid) {
@@ -162,13 +181,13 @@ const submitForm = async () => {
 
   // 上传成功处理
 const handleUploadSuccess = (response, rawFile, uploadedFiles) => {
-  DGTMessage.success('文件上传成功')
-  console.log('上传成功:', response)
+  // DGTMessage.success('文件上传成功')
+  console.log('上传成功:', response,uploadedFiles)
 }
 
 // 上传失败处理
 const handleUploadError = (error, rawFile, uploadedFiles) => {
-  DGTMessage.error('文件上传失败')
+  // DGTMessage.error('文件上传失败')
   console.error('上传失败:', error)
 }
 

+ 11 - 0
src/utils/util.js

@@ -0,0 +1,11 @@
+//浏览器下载文件
+export function downloadFile(url, fileName) {
+  const link = document.createElement('a');
+  link.href = url;
+  link.download = fileName || '';
+  document.body.appendChild(link);
+  link.click();
+  document.body.removeChild(link);
+
+}
+