huminmin
2026-06-01 a563ea879ef5fb6897e76d2df661e465dce2ab9b
src/views/fileManagement/document/attachmentManager.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,425 @@
<template>
  <el-dialog v-model="dialogVisible" title="附件管理" width="60%" :before-close="handleClose">
    <div class="attachment-manager">
      <!-- ä¸Šä¼ åŒºåŸŸ -->
      <div class="upload-section">
        <el-upload
          ref="uploadRef"
          :action="uploadUrl"
          :headers="uploadHeaders"
          :before-upload="handleBeforeUpload"
          :on-success="handleUploadSuccess"
          :on-error="handleUploadError"
          :on-remove="handleRemove"
          :file-list="fileList"
          multiple
          :show-file-list="false"
          :data="{documentId: currentDocumentId}"
          accept=".doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt,.xml,.jpg,.jpeg,.png,.gif,.bmp,.rar,.zip,.7z"
        >
          <el-button type="primary" :icon="Plus">上传附件</el-button>
          <template #tip>
            <div class="el-upload__tip">
              æ”¯æŒæ ¼å¼ï¼šdoc,docx,xls,xlsx,ppt,pptx,pdf,txt,xml,jpg,jpeg,png,gif,bmp,rar,zip,7z
              <br>单个文件大小不超过50MB
            </div>
          </template>
        </el-upload>
      </div>
      <!-- é™„件列表 -->
      <div class="attachment-list">
        <el-table :data="fileList" border max-height="400px" v-loading="loading">
          <el-table-column label="序号" type="index" width="60" align="center" />
          <el-table-column label="附件名称" prop="name" min-width="200" show-overflow-tooltip />
          <el-table-column label="文件大小" prop="size" width="100" align="center">
            <template #default="scope">
              {{ formatFileSize(scope.row.size) }}
            </template>
          </el-table-column>
          <el-table-column label="上传时间" prop="uploadTime" width="160" align="center">
            <template #default="scope">
              {{ formatDate(scope.row.uploadTime) }}
            </template>
          </el-table-column>
          <el-table-column label="状态" prop="status" width="80" align="center">
            <template #default="scope">
              <el-tag :type="scope.row.status === 'success' ? 'success' : 'danger'" size="small">
                {{ scope.row.status === 'success' ? '成功' : '失败' }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column fixed="right" label="操作" width="200" align="center">
            <template #default="scope">
              <el-button link type="primary" size="small" @click="previewFile(scope.row)">
                é¢„览
              </el-button>
              <el-button link type="primary" size="small" @click="downloadFile(scope.row)">
                ä¸‹è½½
              </el-button>
              <el-button link type="danger" size="small" @click="removeFile(scope.row)">
                åˆ é™¤
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </div>
    <!-- æ–‡ä»¶é¢„览组件 -->
    <filePreview ref="filePreviewRef" />
  </el-dialog>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { getToken } from "@/utils/auth"
import { addDocumentationFile, getDocumentationFileList, deleteDocumentationFile } from '@/api/fileManagement/document'
import filePreview from '@/components/filePreview/index.vue'
const props = defineProps({
  // documentId é€šè¿‡ open äº‹ä»¶ä¼ å…¥ï¼Œä¸éœ€è¦ä½œä¸º props
})
const emit = defineEmits(['update:attachments'])
const dialogVisible = ref(false)
const loading = ref(false)
const fileList = ref([])
const uploadRef = ref()
const filePreviewRef = ref()
const currentDocumentId = ref('') // å†…部管理当前文档ID
// ä¸Šä¼ é…ç½®
const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/file/upload"
const uploadHeaders = computed(() => ({
  Authorization: "Bearer " + getToken()
}))
// æ‰“开弹框
const open = (attachments = [], documentId = '') => {
  dialogVisible.value = true
  currentDocumentId.value = documentId // è®¾ç½®å½“前文档ID
  // å¦‚果有文档ID,则加载附件列表
  if (documentId) {
    loadAttachmentList(documentId)
  } else {
    fileList.value = attachments || []
    // total.value = fileList.value.length // Removed total.value
  }
  // currentPage.value = 1 // Removed currentPage.value
}
// åŠ è½½é™„ä»¶åˆ—è¡¨
const loadAttachmentList = async (documentId) => {
  try {
    loading.value = true
    const params = {
      page: 1, // Always load from page 1
      size: 1000, // Load all for now
      documentationId: documentId
    }
    const res = await getDocumentationFileList(params)
    if (res.code === 200) {
      const records = res.data
      // è½¬æ¢æ•°æ®æ ¼å¼
      fileList.value = records.map(item => ({
        id: item.id,
        name: item.name,
        size: item.fileSize,
        url: item.url,
        uploadTime: item.createTime || item.uploadTime,
        status: 'success',
        uid: item.id
      }))
      // total.value = totalCount // Removed total.value
    } else {
      ElMessage.error(res.msg || '获取附件列表失败')
      fileList.value = []
      // total.value = 0 // Removed total.value
    }
  } catch (error) {
    console.error('获取附件列表失败:', error)
    ElMessage.error('获取附件列表失败')
    fileList.value = []
    // total.value = 0 // Removed total.value
  } finally {
    loading.value = false
  }
}
// å…³é—­å¼¹æ¡†
const handleClose = () => {
  dialogVisible.value = false
  emit('update:attachments', fileList.value)
}
// æ–‡ä»¶ä¸Šä¼ å‰æ ¡éªŒ
const handleBeforeUpload = (file) => {
  // æ£€æŸ¥æ–‡ä»¶å¤§å°ï¼ˆ50MB)
  const isLt50M = file.size / 1024 / 1024 < 50
  if (!isLt50M) {
    ElMessage.error('文件大小不能超过50MB!')
    return false
  }
  // æ£€æŸ¥æ–‡ä»¶ç±»åž‹
  const allowedTypes = [
    'application/msword',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'application/vnd.ms-excel',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'application/vnd.ms-powerpoint',
    'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    'application/pdf',
    'text/plain',
    'text/xml',
    'image/jpeg',
    'image/png',
    'image/gif',
    'image/bmp',
    'application/x-rar-compressed',
    'application/zip',
    'application/x-7z-compressed'
  ]
  if (!allowedTypes.includes(file.type)) {
    ElMessage.error('不支持的文件类型!')
    return false
  }
  return true
}
// æ–‡ä»¶ä¸Šä¼ æˆåŠŸ
const handleUploadSuccess = (response, file, fileList) => {
  console.log('文件上传成功响应:', response);
  console.log('文件信息:', file);
  if (response.code === 200) {
    // æž„建附件数据 - ç¡®ä¿æ­£ç¡®èŽ·å–URL
    const attachmentData = {
      name: file.name,
      url: response.data.url || response.data.path || response.data.tempPath || file.url,
      fileSize: file.size,
      documentationId: currentDocumentId.value
    };
    console.log('构建的附件数据:', attachmentData);
    // è°ƒç”¨ä¿å­˜é™„件接口
    saveAttachment(attachmentData, file, fileList);
  } else {
    ElMessage.error(response.msg || '文件上传失败')
  }
}
// ä¿å­˜é™„件信息
const saveAttachment = async (attachmentData, file, fileList) => {
  try {
    console.log('开始保存附件,数据:', attachmentData);
    // ç¡®ä¿URL字段存在且有效
    if (!attachmentData.url) {
      console.error('附件URL为空,无法保存');
      ElMessage.error('文件URL获取失败,无法保存附件');
      return;
    }
    const res = await addDocumentationFile(attachmentData);
    console.log('保存附件接口响应:', res);
    if (res.code === 200) {
      const newFile = {
        id: res.data.id || Date.now(),
        name: attachmentData.name,
        size: attachmentData.fileSize,
        url: attachmentData.url,
        uploadTime: new Date().toISOString(),
        status: 'success',
        uid: file.uid
      }
      console.log('创建的新文件对象:', newFile);
      fileList.push(newFile)
      ElMessage.success('文件上传并保存成功')
      // ä¿å­˜æˆåŠŸåŽåˆ·æ–°é™„ä»¶åˆ—è¡¨
      if (currentDocumentId.value) {
        await loadAttachmentList(currentDocumentId.value);
      }
    } else {
      ElMessage.error(res.msg || '保存附件信息失败')
      // ä¿å­˜å¤±è´¥æ—¶ç§»é™¤æ–‡ä»¶
      const index = fileList.findIndex(item => item.uid === file.uid)
      if (index > -1) {
        fileList.splice(index, 1)
      }
    }
  } catch (error) {
    console.error('保存附件失败:', error)
    ElMessage.error('保存附件信息失败')
    // ä¿å­˜å¤±è´¥æ—¶ç§»é™¤æ–‡ä»¶
    const index = fileList.findIndex(item => item.uid === file.uid)
    if (index > -1) {
      fileList.splice(index, 1)
    }
  }
}
// æ–‡ä»¶ä¸Šä¼ å¤±è´¥
const handleUploadError = (error, file, fileList) => {
  console.error('文件上传失败:', error);
  console.error('失败的文件:', file);
  console.error('当前文件列表:', fileList);
  ElMessage.error('文件上传失败,请检查网络连接或文件格式')
}
// ç§»é™¤æ–‡ä»¶
const handleRemove = (file, fileList) => {
  const index = fileList.findIndex(item => item.uid === file.uid)
  if (index > -1) {
    fileList.splice(index, 1)
    // total.value = fileList.length // Removed total.value
  }
}
// åˆ é™¤æ–‡ä»¶
const removeFile = (file) => {
  ElMessageBox.confirm(`确定要删除文件 "${file.name}" å—?`, '删除确认', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(async () => {
    try {
      // è°ƒç”¨åˆ é™¤æŽ¥å£
      const res = await deleteDocumentationFile([file.id]);
      if (res.code === 200) {
        // ä»Žæœ¬åœ°åˆ—表中移除
        const index = fileList.value.findIndex(item => item.id === file.id);
        if (index > -1) {
          fileList.value.splice(index, 1);
        }
        ElMessage.success('删除成功');
        // å¦‚果有文档ID,刷新附件列表
        if (currentDocumentId.value) {
          await loadAttachmentList(currentDocumentId.value);
        }
      } else {
        ElMessage.error(res.msg || '删除失败');
      }
    } catch (error) {
      console.error('删除附件失败:', error);
      ElMessage.error('删除附件失败');
    }
  }).catch(() => {
    // å–消删除
  })
}
// é¢„览文件
const previewFile = (file) => {
  if (file.url) {
    filePreviewRef.value.open(file.url)
  } else {
    ElMessage.warning('文件地址无效,无法预览')
  }
}
// ä¸‹è½½æ–‡ä»¶
const downloadFile = (file) => {
  if (file.url) {
    // åˆ›å»ºä¸‹è½½é“¾æŽ¥
    const link = document.createElement('a')
    link.href = file.url
    link.download = file.name
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    ElMessage.success('开始下载文件')
  } else {
    ElMessage.warning('文件地址无效,无法下载')
  }
}
// æ ¼å¼åŒ–文件大小
const formatFileSize = (bytes) => {
  if (bytes === 0) return '0 B'
  const k = 1024
  const sizes = ['B', 'KB', 'MB', 'GB']
  const i = Math.floor(Math.log(bytes) / Math.log(k))
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
// æ ¼å¼åŒ–日期
const formatDate = (dateString) => {
  if (!dateString) return ''
  const date = new Date(dateString)
  return date.toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit'
  })
}
// æµ‹è¯•文件上传
const testUpload = () => {
  console.log('当前文档ID:', currentDocumentId.value);
  console.log('上传URL:', uploadUrl);
  console.log('上传Headers:', uploadHeaders.value);
}
// æš´éœ²æ–¹æ³•
defineExpose({
  open,
  loadAttachmentList,
  testUpload
})
</script>
<style scoped>
.attachment-manager {
  padding: 20px;
}
.upload-section {
  margin-bottom: 20px;
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 8px;
  border: 2px dashed #d9d9d9;
}
.upload-section:hover {
  border-color: #409eff;
}
.attachment-list {
  margin-bottom: 20px;
}
.el-upload__tip {
  margin-top: 10px;
  color: #666;
  font-size: 12px;
  line-height: 1.5;
}
:deep(.el-upload) {
  width: 100%;
}
:deep(.el-upload-dragger) {
  width: 100%;
  height: 120px;
}
</style>