微信咨询

微信咨询

13610*910*9

服务热线 7*24小时

电话咨询

明道云视图插件开发-批量上传附件

iamdu2025-07-01 11:34:57 浏览: 934
<template>
<!--
 * 名称: [明道云视图开发工具]
 * 作者: [DU]
 * 联系方式: [iiamdu]
 * 时间: [2024-05-28]
 * 当前版本: [0.0.1]
 * 版本提交时间: 2025-05-28 20:32:58
 * 描述: 上传附件
 *
 -->
  <div class="upload-container">
    <h2>批量上传附件到单条记录</h2>
    <input type="file" @change="handleFileChange" multiple />
    <div class="selected-files" v-if="selectedFiles.length > 0">
      <h3>已选择 {{ selectedFiles.length }} 个附件:</h3>
      <ul>
        <li v-for="(file, index) in selectedFiles" :key="index">
          {{ file.name }} ({{ formatFileSize(file.size) }})
        </li>
      </ul>
    </div>
    <button @click="handleUploadAndAddRecord" :disabled="selectedFiles.length === 0">上传附件并创建记录</button>
    <div v-if="uploading" class="progress">
      正在上传附件... {{ uploadedCount }}/{{ totalCount }}
      <div class="progress-bar">
        <div class="progress-inner" :style="{width: uploadProgress + '%'}"></div>
      </div>
    </div>
    <div v-if="uploadSuccess" class="success">记录创建成功!已上传 {{ uploadedCount }} 个附件</div>
    <div v-if="uploadError" class="error">上传失败:{{ uploadError }}</div>
    <div class="upload-results" v-if="uploadResults.length > 0">
      <h3>上传结果:</h3>
      <ul>
        <li v-for="(result, index) in uploadResults" :key="index" :class="result.success ? 'success' : 'error'">
          {{ result.fileName }}: {{ result.success ? '成功' : '失败 - ' + result.error }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted, onUnmounted, computed, nextTick } from "vue";
import { config, env, api, utils, md_emitter } from "mdye";
import axios from 'axios';

const { appId, worksheetId, viewId } = config;

// 批量上传相关状态
const selectedFiles = ref([]); // 选择的文件列表
const uploadResults = ref([]); // 上传结果记录
const uploadedCount = ref(0); // 已上传文件数
const totalCount = ref(0); // 总文件数
const uploadProgress = ref(0); // 上传进度百分比
const currentFileUrl = ref(''); // 当前文件的URL,全局变量以便在不同函数间共享

// 文件大小格式化工具函数
const formatFileSize = (bytes) => {
  if (bytes === 0) return '0 Bytes';
  const k = 1024;
  const sizes = ['Bytes', '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 file = ref(null);
const files = ref(null);
const keys = ref(null);
const filePath = ref(null);
const Url = ref(null);
const fileName = ref(null);
const fileSize = ref(null);
const uploading = ref(false);
const lastfilePath = ref(null);
const uploadSuccess = ref(false);
const uploadError = ref('');
const originalFileName = ref('');//原始文件名
const fileType = ref('');
const bucketnum = ref(4); //4为图片,3为文档视频等
const preUrl = ref('https://p1.mingdaoyun.cn/');

    // 获取上传凭证
    const getUploadToken = async () => {
      // 确保扩展名格式正确,避免重复的点
      let fileExtension = '';
      if (fileType.value) {
        fileExtension = fileType.value.startsWith('.') ? fileType.value : `.${fileType.value}`;//扩展名
      }
      // 生成一个唯一的文件名,避免只有扩展名的情况
      const uniqueFileName = originalFileName.value || ('file_' + Date.now() + '_' + Math.floor(Math.random() * 1000));
      console.log('使用的文件名:', uniqueFileName);
      const response = await axios.post('https://www.mingdao.com/api/Qiniu/GetUploadToken', {
        files: [
          {
            bucket: bucketnum.value, // 3 表示文档、视频等,4 图片
            ext: fileExtension, // 确保格式正确的扩展名
          }
        ],
        type: 0,//固定
        projectId:config.projectId, // 替换为实际的项目 ID
        appId, // 替换为实际的应用 ID
        worksheetId // 替换为实际的工作表 ID
      }, 
      {
        headers: {
          'authorization': 'md_pss_id 0ca03402f07204706606501004400e0450bc0200af084023', // 替换为实际的授权 Token 用官方页面上传附件 F12下请求头复制
          'content-type': 'application/json'
        }
      });

      const responseData = response.data.data[0];
      console.log('上传凭证响应:', responseData);
      
      // 检查key是否包含完整的文件名
      let modifiedKey = responseData.key;
    
      
      files.value = responseData.serverName + modifiedKey;
      keys.value = modifiedKey;
      fileName.value = responseData.key.split('/')[responseData.key.split('/').length - 1];
 
      Url.value = responseData.url;
      
      return response.data.data[0].uptoken;
    };

    // 上传文件
    const uploadFile = async (file, uptoken) => {
        const formData = new FormData();
        console.log('上传文件对象:', file,uptoken,keys.value);
        formData.append('token', uptoken);
        formData.append('file', file); // 使用实际的文件对象
        // formData.append('name', 'link_record.jpeg');
        formData.append('key', keys.value);
        // formData.append('chunk', 0);
        // formData.append('chunks', 1);
        //formData.append('x:serverName', preUrl.value);
        // 根据文档示例添加filePath参数,使用本地时间而非UTC时间
        const today = new Date();
        const year = today.getFullYear();
        const month = String(today.getMonth() + 1).padStart(2, '0');
        const day = String(today.getDate()).padStart(2, '0');
        const localDateStr = `${year}${month}${day}`;

        
        formData.append('x:filePath', filePath.value || `${config.projectId}/${appId}/${worksheetId}/${localDateStr}/`);
        formData.append('x:fileName', fileName.value);
        //formData.append('x:originalFileName', 'link_record');
        // formData.append('x:fileExt', fileType.value);
        //formData.append('x:fileExt', '.xlsx');

      const response = await axios.post('https://upload.qiniup.com/', formData, {
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      });
              console.log('上传成功,响应数据:', response.data);
        // 保存上传结果数据
        //lastKey.value = response.data.key;
        //lastfileName.value = response.data.fileName;
        lastfilePath.value = response.data.filePath;
        //lastfsize.value = parseInt(response.data.fsize); // 确保是数字类型
        
        // 简化URL构建逻辑,直接使用preUrl + key
        console.log('构建URL的原始数据:', response.data);
        console.log('当前preUrl值:', preUrl.value);
        console.log('当前key值:', response.data.key);
        fileName.value = response.data.fileName;
        // 直接使用preUrl + key构建URL,存储在全局变量中
        currentFileUrl.value = preUrl.value + response.data.key;
        console.log('简化构建的URL:', currentFileUrl.value);
        
        // 返回全局变量中的URL
        return currentFileUrl.value; // 返回完整的文件 URL
    };

          // 准备单个附件的数据对象
    const prepareAttachmentData = (fileId, fileSize, serverName, filePath, fileName, fileExt, originalFileName, key, url) => {
      // 调试输出,帮助检查参数
      console.log('准备附件数据:', {
        fileId, fileSize, serverName, filePath, fileName, fileExt, originalFileName, key, url
      });
      
      // 如果fileName为空,从key中提取
      let finalFileName = fileName;

      return {
        "fileID": fileId,
        "fileSize": parseInt(fileSize) || 0,
        "serverName": serverName,
        "filePath": filePath,
        "fileName": finalFileName,
        //"fileExt": fileExt, // 应该是 .png 格式 不要这行 避免地址多个后缀 因为文件名已包含
        "originalFileName": originalFileName || finalFileName,
        "key": key,
        "url": url,
        "oldOriginalFileName": originalFileName || finalFileName,
        "index": 0,
        "isEdit": false
      };
    };
    
    // 收集所有上传的附件数据
    const allAttachments = ref([]);
    
    // 添加附件到集合
    const addAttachmentToCollection = (fileId, fileSize, serverName, filePath, fileName, fileExt, originalFileName, key, url) => {
      const attachment = prepareAttachmentData(fileId, fileSize, serverName, filePath, fileName, fileExt, originalFileName, key, url);
      allAttachments.value.push(attachment);
      console.log(`附件已添加到集合,当前共有 ${allAttachments.value.length} 个附件`);
    };
    
    // 新增记录并赋值附件字段
    const addRecordWithAttachment = async () => {
      console.log('准备添加记录,附件数量:', allAttachments.value.length);
      
      if (allAttachments.value.length === 0) {
        console.error('没有可用的附件数据');
        return;
      }
      
      // 构建附件字段的值
      const attachmentValue = {
        "attachmentData": [],
        "attachments": allAttachments.value,
        "knowledgeAtts": []
      };
      
      // 将对象转换为JSON字符串
      const attachmentValueStr = JSON.stringify(attachmentValue);
      
      const response = await api.addWorksheetRow({
        appId, // 替换为实际的应用 ID
        worksheetId, // 替换为实际的工作表 ID
        receiveControls: [
          {
            "controlId": "6843b20c82453d0d030cf68f",
            "type": 14,
            "value": attachmentValueStr,
            "controlName": "附件",
            "dot": 0
          }
        ]
      });
      
      return response;
    };

    // 文件选择事件 - 支持多文件
    const handleFileChange = (event) => {
      const files = event.target.files;
      if (!files || files.length === 0) return;
      
      // 清空之前的选择
      selectedFiles.value = [];
      uploadResults.value = [];
      uploadError.value = '';
      
      // 将FileList转换为数组并保存
      for (let i = 0; i < files.length; i++) {
        selectedFiles.value.push(files[i]);
      }
      
      console.log(`已选择 ${selectedFiles.value.length} 个文件`);
      
      // 如果有文件,设置第一个文件的信息(用于兼容旧代码)
      if (selectedFiles.value.length > 0) {
        const firstFile = selectedFiles.value[0];
        file.value = firstFile;
        
        // 提取文件信息 - 正确处理文件名和扩展名
        const fileName = firstFile.name;
        const lastDotIndex = fileName.lastIndexOf('.');
        
        if (lastDotIndex > 0) {
          // 有扩展名的情况
          originalFileName.value = fileName.substring(0, lastDotIndex); // 不包含扩展名的文件名
          fileType.value = fileName.substring(lastDotIndex + 1); // 不带点的扩展名
        } else {
          // 没有扩展名的情况
          originalFileName.value = fileName;
          fileType.value = '';
        }
        
        fileSize.value = firstFile.size; // 文件大小
        
        // 根据文件类型设置存储桶和URL前缀
        if (!firstFile.type.startsWith('image/')) {
          bucketnum.value = 3; // 文档视频等
          preUrl.value = 'https://doc.mingdao.com/';
        } else {
          bucketnum.value = 4; // 图片
          preUrl.value = 'https://p1.mingdaoyun.cn/';
        }
      }
    };

    // 上传单个文件并收集附件数据
    const uploadSingleFile = async (fileObj) => {
      try {
        // 设置当前处理的文件信息
        file.value = fileObj;
        
        // 正确提取文件名和扩展名
        const fileNameStr = fileObj.name; // 使用不同的变量名避免与全局变量冲突
        const lastDotIndex = fileNameStr.lastIndexOf('.');
        
        if (lastDotIndex > 0) {
          // 有扩展名的情况
          originalFileName.value = fileNameStr.substring(0, lastDotIndex); // 不包含扩展名的文件名
          fileType.value = fileNameStr.substring(lastDotIndex + 1); // 不带点的扩展名
        } else {
          // 没有扩展名的情况
          originalFileName.value = fileNameStr;
          fileType.value = '';
        }
        
        fileSize.value = fileObj.size;
        
        // 根据文件类型设置存储桶和URL前缀
        if (!fileObj.type.startsWith('image/')) {
          bucketnum.value = 3; // 文档视频等
          preUrl.value = 'https://doc.mingdao.com/';
        } else {
          bucketnum.value = 4; // 图片
          preUrl.value = 'https://p1.mingdaoyun.cn/';
        }
        
        // 获取上传凭证
        console.log(`正在获取上传凭证: ${fileObj.name}...`);
        const uptoken = await getUploadToken();
        
        // 上传文件
        console.log(`正在上传文件: ${fileObj.name}...`);
        const fileUrl = await uploadFile(fileObj, uptoken);
        
                  // 生成唯一的附件ID
          const fileId = "o_1ir3mg6m1l741oh7" + Date.now() + "_" + Math.floor(Math.random() * 1000);
          
          // 使用简化的URL构建逻辑
          console.log('上传后获取的key:', keys.value);
          
          // 检查文件名是否正确设置
          console.log('当前fileName值:', fileName.value);
          
          // 如果fileName为空,从key中提取
          if (!fileName.value && keys.value) {
            const keyParts = keys.value.split('/');
            if (keyParts.length > 0) {
              const extractedName = keyParts[keyParts.length - 1];
              // 如果提取的是纯扩展名(如.jpeg),则生成一个随机文件名
              if (extractedName.startsWith('.')) {
                fileName.value = 'file_' + Date.now() + '_' + Math.floor(Math.random() * 1000) + extractedName;
                console.log('生成随机文件名:', fileName.value);
              } else {
                fileName.value = extractedName;
              }
              console.log('从key中提取并设置fileName:', fileName.value);
            }
          }
          
          // 如果还是没有文件名,使用原始文件名
          if (!fileName.value && originalFileName.value) {
            fileName.value = originalFileName.value;
            if (fileType.value && !fileName.value.endsWith('.' + fileType.value)) {
              fileName.value += '.' + fileType.value;
            }
            console.log('使用原始文件名作为fileName:', fileName.value);
          }
          
          // 直接使用preUrl + key构建URL,使用全局变量
          currentFileUrl.value = preUrl.value + keys.value;
          console.log('最终使用的URL:', currentFileUrl.value);
          
          // 检查并修复文件路径问题
          if (!lastfilePath.value && keys.value) {
            // 如果没有filePath但有key,尝试从key中提取路径
            const keyParts = keys.value.split('/');
            if (keyParts.length > 1) {
              // 移除最后一个部分(文件名),剩余部分就是路径
              const pathParts = keyParts.slice(0, keyParts.length - 1);
              lastfilePath.value = pathParts.join('/') + '/';
              console.log('从key中提取的文件路径:', lastfilePath.value);
            }
          }
          
          // 确保正确添加扩展名
          const fileExt = fileType.value ? `.${fileType.value}` : '';
          
                  // 添加附件数据到集合
        addAttachmentToCollection(
          fileId,
          fileSize.value,
          preUrl.value,
          lastfilePath.value,
          fileName.value,
          fileExt, // 正确添加扩展名,格式为 .png 或 .jpeg 等
          originalFileName.value,
          keys.value,
          currentFileUrl.value // 使用全局变量中的URL
        );
        
        // 添加到成功结果
        uploadResults.value.push({
          fileName: fileObj.name,
          success: true
        });
        
        return true;
      } catch (error) {
        console.error(`上传文件失败: ${fileObj.name}`, error);
        
        // 添加到失败结果
        uploadResults.value.push({
          fileName: fileObj.name,
          success: false,
          error: error.message || '未知错误'
        });
        
        return false;
      }
    };
    
    // 批量上传附件并创建一条记录
    const handleUploadAndAddRecord = async () => {
      if (selectedFiles.value.length === 0) {
        uploadError.value = '请先选择文件';
        return;
      }

      uploading.value = true;
      uploadSuccess.value = false;
      uploadError.value = '';
      uploadedCount.value = 0;
      totalCount.value = selectedFiles.value.length;
      uploadProgress.value = 0;
      uploadResults.value = [];
      
      // 清空之前的附件集合
      allAttachments.value = [];

      try {
        // 逐个上传文件,收集附件数据
        for (let i = 0; i < selectedFiles.value.length; i++) {
          const currentFile = selectedFiles.value[i];
          const success = await uploadSingleFile(currentFile);
          
          // 更新进度
          uploadedCount.value++;
          uploadProgress.value = Math.floor((uploadedCount.value / totalCount.value) * 100);
        }
        
        // 检查是否有成功上传的文件
        const successCount = uploadResults.value.filter(r => r.success).length;
        if (successCount > 0) {
          // 创建一条包含所有附件的记录
          console.log('创建包含所有附件的记录...');
          const recordResult = await addRecordWithAttachment();
          console.log('记录创建结果:', recordResult);
          
          uploadSuccess.value = true;
          
          if (successCount < totalCount.value) {
            uploadError.value = `部分文件上传失败 (${successCount}/${totalCount.value} 成功),但记录已成功创建`;
          }
        } else {
          uploadError.value = '所有文件上传失败,未创建记录';
        }
      } catch (error) {
        console.error('批量上传或创建记录过程中发生错误:', error);
        uploadError.value = error.message || '批量上传或创建记录过程中发生未知错误';
      } finally {
        uploading.value = false;
      }
    };


</script>

<style>
.app-container {
  padding: 20px;
  max-width: 800px;
  margin: 0 auto;
}

.upload-container {
  background-color: #f9f9f9;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}

.form-group {
  margin-bottom: 20px;
}

.form-group label {
  display: block;
  margin-bottom: 8px;
  font-weight: bold;
}

.selected-record {
  margin-top: 10px;
  padding: 8px;
  background-color: #ecf5ff;
  border-radius: 4px;
  border-left: 3px solid #409eff;
}

.upload-results {
  margin-top: 20px;
  padding: 15px;
  background-color: #f9f9f9;
  border-radius: 8px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}

.upload-results ul {
  padding-left: 20px;
}

.upload-results li {
  margin-bottom: 5px;
}

.upload-results .success {
  color: #67c23a;
}

.upload-results .error {
  color: #f56c6c;
}

.progress {
  margin: 15px 0;
}

.progress-bar {
  height: 10px;
  background-color: #e9e9e9;
  border-radius: 5px;
  margin-top: 5px;
  overflow: hidden;
}

.progress-inner {
  height: 100%;
  background-color: #409eff;
  transition: width 0.3s;
}

.selected-files {
  margin: 15px 0;
  padding: 10px;
  background-color: #f0f9eb;
  border-radius: 4px;
  border-left: 3px solid #67c23a;
}

.selected-files ul {
  padding-left: 20px;
  margin: 10px 0 0;
}

.selected-files li {
  margin-bottom: 5px;
}

.success {
  color: #67c23a;
  font-weight: bold;
}
</style>