Browse Source

保证上传一个文件

chrislee 2 weeks ago
parent
commit
70c27eba26

+ 54 - 23
src/views/modules/common/upload-component-v2.vue

@@ -100,39 +100,67 @@ export default {
     }
   },
   methods: {
-    handleUpload(file) {
-      function getBase64(file) {
-        return new Promise((resolve, reject) => {
-          const reader = new FileReader()
-          reader.readAsDataURL(file)
-          reader.onload = () => resolve(reader.result)
-          reader.onerror = (error) => reject(error)
-        })
+    // 自定义上传(避免重复触发,配合 onSuccess/onError 明确结束一次上传)
+    handleUpload(req) {
+      // el-upload 自定义请求入参:{ file, onProgress, onSuccess, onError, data, filename, headers, withCredentials }
+      const rawFile = req && (req.file || req)
+      const uid = (rawFile && rawFile.uid) || (rawFile && `${rawFile.name}_${rawFile.size}_${rawFile.lastModified}`)
+
+      // 去重保护:相同 uid 的文件只处理一次
+      if (!this._uploadingUids) this._uploadingUids = {}
+      if (uid && this._uploadingUids[uid]) {
+        // 已在上传中,直接结束这次调用
+        try { req && req.onSuccess && req.onSuccess({}, rawFile) } catch (e) { }
+        return
+      }
+      if (uid) this._uploadingUids[uid] = true
+
+      // 时间节流(双保险),同一文件 1500ms 内只允许一次真正上传
+      const key = uid || (rawFile && `${rawFile.name}_${rawFile.size}_${rawFile.lastModified}`) || rawFile && rawFile.name
+      if (!this._lastUploadByKey) this._lastUploadByKey = {}
+      const now = Date.now()
+      if (key && this._lastUploadByKey[key] && (now - this._lastUploadByKey[key] < 1500)) {
+        try { req && req.onSuccess && req.onSuccess({}, rawFile) } catch (e) { }
+        if (uid && this._uploadingUids) delete this._uploadingUids[uid]
+        return
       }
+      if (key) this._lastUploadByKey[key] = now
+
+      const formData = new FormData()
+      formData.append('file', rawFile)
 
-      return getBase64(file.file).then((res) => {
-        // 需要return才会显示上传成功状态,不加return不好使
-        // 开始上传
-        const formData = new FormData()
-        formData.append('file', file.file)
-        uploadFiles(formData).then(({ data }) => {
+      uploadFiles(formData)
+        .then(({ data }) => {
           if (data && data.code === '200') {
+            // 后端可能返回数组(支持多文件),逐条写回到当前 fileList
             data.data.forEach((item) => {
-              let fileData = this.fileList.find(
-                (file) => file.name === item.originFileName
-              )
-              fileData.fileName = item.originFileName
-              fileData.url = item.fileUrl
-              this.uploadFileList.push(fileData)
+              let fileData = this.fileList.find(f => f.name === item.originFileName)
+              if (!fileData) {
+                fileData = { name: item.originFileName, fileName: item.originFileName, url: item.fileUrl }
+              } else {
+                fileData.fileName = item.originFileName
+                fileData.url = item.fileUrl
+              }
+              if (!this.uploadFileList.find(f => f.name === fileData.name)) {
+                this.uploadFileList.push(fileData)
+              }
             })
-
+            // 双向绑定回传给父组件
             this.$emit('input', this.uploadFileList)
             this.$message.success('上传成功')
+            try { req && req.onSuccess && req.onSuccess(data, rawFile) } catch (e) { }
           } else {
             this.$message.error('上传失败')
+            try { req && req.onError && req.onError(new Error('上传失败')) } catch (e) { }
           }
         })
-      })
+        .catch((err) => {
+          this.$message.error('上传失败')
+          try { req && req.onError && req.onError(err) } catch (e) { }
+        })
+        .finally(() => {
+          if (uid && this._uploadingUids) delete this._uploadingUids[uid]
+        })
     },
     // 上传
     submitUpload() {
@@ -140,7 +168,10 @@ export default {
     },
     // 移除
     handleRemove(file, fileList) {
-      this.$emit('input', fileList)
+      // 同步内部已上传列表,防止再次 emit 带回已删除项
+      const names = (fileList || []).map(f => f.name)
+      this.uploadFileList = this.uploadFileList.filter(f => names.includes(f.name))
+      this.$emit('input', this.uploadFileList)
     },
     // 预览
     handlePreview(file) {

+ 42 - 83
src/views/modules/tech/ctafts-add-or-detail-v2.vue

@@ -1,62 +1,31 @@
 <template>
   <div>
     <div class="my-title">{{ isEdit ? "编辑" : "新增" }}</div>
-    <el-form
-      :model="dataForm"
-      :rules="dataRule"
-      ref="dataForm"
-      label-width="auto"
-    >
+    <el-form :model="dataForm" :rules="dataRule" ref="dataForm" label-width="auto">
       <el-row class="my-row">
         <el-col :span="8">
           <el-form-item label="对应的产品" prop="productId">
-            <craft-product-component
-              :disabled="display || isEdit"
-              v-model="dataForm.productId"
-              :product-id.sync="dataForm.productId"
-              @productSelected="prodSelected"
-            />
+            <craft-product-component :disabled="display || isEdit" v-model="dataForm.productId"
+              :product-id.sync="dataForm.productId" @productSelected="prodSelected" />
           </el-form-item>
         </el-col>
         <el-col :span="8">
           <el-form-item label="任务单" prop="orderId">
-            <el-select
-              v-model="dataForm.orderId"
-              :disabled="display"
-              filterable
-              remote
-              reserve-keyword
-              placeholder="请输入关键词"
-              :remote-method="debouncedSearch"
-              :loading="loading"
-              style="width: 100%"
-            >
-              <el-option
-                v-for="item in orderOptions"
-                :key="item.value"
-                :label="item.label"
-                :value="item.value"
-              >
+            <el-select v-model="dataForm.orderId" :disabled="display" filterable remote reserve-keyword
+              placeholder="请输入关键词" :remote-method="debouncedSearch" :loading="loading" style="width: 100%">
+              <el-option v-for="item in orderOptions" :key="item.value" :label="item.label" :value="item.value">
               </el-option>
             </el-select>
           </el-form-item>
         </el-col>
         <el-col :span="8">
           <el-form-item label="工艺版本" prop="techVersion">
-            <el-input
-              v-model="dataForm.techVersion"
-              :disabled="display || isEdit"
-              placeholder=""
-            ></el-input>
+            <el-input v-model="dataForm.techVersion" :disabled="display || isEdit" placeholder=""></el-input>
           </el-form-item>
         </el-col>
       </el-row>
       <el-form-item label="备注说明">
-        <el-input
-          v-model="dataForm.notes"
-          :disabled="display || isEdit"
-          placeholder="请输入介绍信息"
-        ></el-input>
+        <el-input v-model="dataForm.notes" :disabled="display || isEdit" placeholder="请输入介绍信息"></el-input>
       </el-form-item>
       <el-form-item label="产品主图">
         <!-- <upload-component
@@ -72,22 +41,13 @@
       </el-form-item>
       <el-form-item label="工艺步骤" prop="nodeList"> </el-form-item>
       <el-row class="my-row" style="height: 350px; background-color: #efefef">
-        <work-flow
-          ref="workFlow"
-          :nodeData="workFlowData"
-          :nodeTypeList="workTypeOptions"
-          @saveWorkFlow="saveWorkFlow"
-          :isEdit="isEdit"
-          @dataChange="workFlowDataChange"
-          sourceType="1"
-        ></work-flow>
+        <work-flow ref="workFlow" :nodeData="workFlowData" :nodeTypeList="workTypeOptions" @saveWorkFlow="saveWorkFlow"
+          :isEdit="isEdit" @dataChange="workFlowDataChange" sourceType="1"></work-flow>
       </el-row>
     </el-form>
     <span slot="footer" class="dialog-footer">
       <el-button @click="onChose">取消</el-button>
-      <el-button v-if="!display" type="primary" @click="dataFormSubmit()"
-        >确定</el-button
-      >
+      <el-button v-if="!display" type="primary" @click="dataFormSubmit()">确定</el-button>
     </span>
     <!-- </el-dialog> -->
   </div>
@@ -107,12 +67,12 @@ export default {
   components: { CraftProductComponent, UploadComponent, WorkFlow },
   computed: {
     orgId: {
-      get () {
+      get() {
         return this.$store.state.user.orgId
       }
     }
   },
-  data () {
+  data() {
     return {
       datas: {},
       visible: false,
@@ -168,32 +128,32 @@ export default {
       }
     }
   },
-  created () {
+  created() {
     this.initNode()
     // 创建防抖函数(500ms延迟)
     this.debouncedSearch = _.debounce(this.remoteMethod, 500)
   },
-  beforeDestroy () {
+  beforeDestroy() {
     // 清除防抖定时器,避免内存泄漏
     this.debouncedSearch.cancel()
   },
-  mounted () {
+  mounted() {
     this.initNode()
   },
   methods: {
-    onChose () {
+    onChose() {
       this.$emit('onChose')
     },
-    initNode () {
+    initNode() {
       // this.workFlowData = data;
     },
-    resetWorkFlow () {
+    resetWorkFlow() {
       this.workFlowData = {
         nodeList: [],
         lineList: []
       }
     },
-    async init (id, display, isEdit, isCopy, isResubmit, orderId, productId) {
+    async init(id, display, isEdit, isCopy, isResubmit, orderId, productId) {
       this.remoteMethod()
 
       this.fileList = []
@@ -275,30 +235,29 @@ export default {
         }
       })
     },
-    productIdChangeHandle (val) {
+    productIdChangeHandle(val) {
       if (val) {
         let item = this.optionLevel.find((t) => t.productId === val)
         this.dataForm.techName = item.productName
       }
     },
-    handleRemove (file, fileList) {
+    handleRemove(file, fileList) {
       this.fileList = fileList
     },
-    handleChange (file, fileList) {
+    handleChange(file, fileList) {
       this.fileList = fileList
     },
-    handleExceed (files, fileList) {
+    handleExceed(files, fileList) {
       this.$message.warning(
-        `当前限制选择 5 个文件,本次选择了 ${files.length} 个文件,共选择了 ${
-          files.length + fileList.length
+        `当前限制选择 5 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length
         } 个文件`
       )
     },
-    remoteMethod (query) {
+    remoteMethod(query) {
       this.loading = true
       this.getOrderByCode(query)
     },
-    getOrderByCode (orderCode) {
+    getOrderByCode(orderCode) {
       getOrderByCode(orderCode).then(({ data }) => {
         this.loading = false
 
@@ -309,11 +268,11 @@ export default {
         }
       })
     },
-    validateField (type) {
+    validateField(type) {
       this.$refs.dataForm.validateField(type)
     },
     // 表单提交
-    dataFormSubmit () {
+    dataFormSubmit() {
       let flowData = this.$refs.workFlow.getFlowData()
       if (!flowData) {
         this.$message.error('请先完成流程图!')
@@ -401,21 +360,21 @@ export default {
         }
       })
     },
-    uploadSuccess (fileList) {
+    uploadSuccess(fileList) {
       this.fileList = fileList
     },
     // 保存流程图
-    saveWorkFlow (workFlowData) {
+    saveWorkFlow(workFlowData) {
       console.log('save work flow.')
       this.workFlowData = workFlowData
     },
     // 流程图数据变更通知
-    workFlowDataChange () {},
-    handleClose () {
+    workFlowDataChange() { },
+    handleClose() {
       // this.visible = false
       this.$emit('close')
     },
-    async getWorkTypeOptions () {
+    async getWorkTypeOptions() {
       this.workTypeOptions = []
       await getWorkType().then(({ data }) => {
         if (data && data.code === '200') {
@@ -424,7 +383,7 @@ export default {
         }
       })
     },
-    prodSelected (item) {
+    prodSelected(item) {
       this.dataForm.productId = item.value
     }
   }
@@ -434,7 +393,7 @@ export default {
 <style lang="less" scoped>
 .super-flow__node {
   .flow-node {
-    > header {
+    >header {
       font-size: 14px;
       height: 32px;
       line-height: 32px;
@@ -442,7 +401,7 @@ export default {
       color: #ffffff;
     }
 
-    > section {
+    >section {
       text-align: center;
       line-height: 20px;
       overflow: hidden;
@@ -451,31 +410,31 @@ export default {
     }
 
     &.flow-node-start {
-      > header {
+      >header {
         background-color: #55abfc;
       }
     }
 
     &.flow-node-condition {
-      > header {
+      >header {
         background-color: #bc1d16;
       }
     }
 
     &.flow-node-approval {
-      > header {
+      >header {
         background-color: rgba(188, 181, 58, 0.76);
       }
     }
 
     &.flow-node-cc {
-      > header {
+      >header {
         background-color: #30b95c;
       }
     }
 
     &.flow-node-end {
-      > header {
+      >header {
         height: 50px;
         line-height: 50px;
         background-color: rgb(0, 0, 0);