javascript - 使用文件输入预览在 v-for 循环中添加/删除行

标签 javascript vuejs2

我有一个带有文本字段和文件输入的 v-for 循环,我可以使用该项目上的按钮在循环中任何项目的下一个索引处添加新行。新项目最初将是前一个项目的副本,但可以独立更改。

如果您仅使用文本字段进行尝试,则此方法有效,问题是当我选择图像然后在索引之间添加新行时,文件预览无法正确更新,实际对象似乎是正确的,如果您查看每一行上的字符串。

codepen

试试这个: 添加 2 个新行(总共 3 行),在第二行中选择一个图像,现在尝试从第一行添加一个新行,这会在第一行之后推送一个新行,现在应该将带有图像的行推送到第三行,似乎是这样做的,但缩略图也停留在第二个。

我已经构建这个组件并摆弄它有一段时间了,所以也许我只是错过了需要更新或观看的地方。

Vue.component("file-drop-zone", {
  data() {
    return {
      draggingFile: false,
      previewFiles: [],
      files: [],
      input: null
    };
  },
  props: ["setItems", "returnDataUrl"],
  mounted() {
    this.input = this.$refs.fileInput;
  },
  created() {
    console.log("this.setItems", this.setItems);
    if (this.setItems) {
      this.loadFiles(this.setItems);
      this.input = null;

    }
  },
  methods: {
    openFileExplorer() {
      this.input.click();
    },
    onFileSelection() {
      // add all selected files
      const filesArr = Array.prototype.slice.call(this.input.files);
      // show previews
      this.loadFiles(filesArr);
      // reset file input
      this.input.value = null;
    },
    dropFiles(e) {
      const filesArr = Array.prototype.slice.call(e.dataTransfer.files);
      this.loadFiles(filesArr);
    },
    loadFiles(filesArr) {
      let self = this;
      self.draggingFile = false;
      // const files = e.dataTransfer.files
      // let filesArr = Array.prototype.slice.call(files)

      filesArr.forEach(function(f) {
        // let resizedImage = self.rs(f)
        // console.log('f', f)
        let filename = f.name;
        let extenstion = filename.split(".").reverse()[0];
        // console.log(extenstion)
        const mimeType = f.type;
        // console.log(f.type)

        if (!f.type.match(/image.*/)) {
          let promise = self.getBase64(f, self.updateFilesArr());
          promise.then(function(result) {
            // console.log(result)
            let dataUrl = result.reader;
            self.previewFiles.push({
              name: result.file.name,
              src: dataUrl,
              ext: extenstion,
              type: mimeType
            });
            let blob = self.dataURItoBlob(dataUrl, filename, extenstion);
            let file = new File([blob], filename, { type: blob.type });
            self.files.push(file);
            self.updateFilesArr();
            // console.log('result', blob)
          });
        } else {
          self.rs(f);
        }
      });
    },
    async rs(f) {
      const self = this;
      const config = {
        file: f,
        maxSize: 1024
      };
      const resizedImage = await self.resizeImage(config).then(res => {
        self.files.push(res);
        self.updateFilesArr();
        // console.log('upload res', res)
      });
      console.log("resizedImage", resizedImage);

      return resizedImage;
    },
    updateFilesArr() {
      // let finalFiles = _.cloneDeep(this.files)
      if (this.returnDataUrl) {
        this.$emit("listen-update-files", this.previewFiles);
      } else {
        this.$emit("listen-update-files", this.files);
      }
      this.$refs.fileInput = this.files;
    },
    removeFile(file) {
      // console.log('remove file', file)
      // remove from preview array
      this.previewFiles.splice(
        _.findKey(this.previewFiles, { name: file.name }),
        1
      );
      // remove from file array
      this.files.splice(_.findKey(this.files, { name: file.name }), 1);
      this.updateFilesArr();
    },
    getBase64(file, onLoadCallback) {
      return new Promise(function(resolve, reject) {
        let reader = new FileReader();
        reader.onload = function() {
          resolve({ reader: reader.result, file: file });
        };
        reader.onerror = reject;
        reader.readAsDataURL(file);
      });
    },
    resizeImage(settings) {
      const self = this;
      let file = settings.file;
      let filename = file.name;
      let mimeType = file.type;
      let extenstion = filename.split(".").reverse()[0];
      let maxSize = settings.maxSize;
      let reader = new FileReader();
      let image = new Image();
      let canvas = document.createElement("canvas");

      let resize = function() {
        let width = image.width;
        let height = image.height;
        if (width > height) {
          if (width > maxSize) {
            height *= maxSize / width;
            width = maxSize;
          }
        } else {
          if (height > maxSize) {
            width *= maxSize / height;
            height = maxSize;
          }
        }
        canvas.width = width;
        canvas.height = height;
        canvas.getContext("2d").drawImage(image, 0, 0, width, height);
        let dataUrl = canvas.toDataURL("image/jpeg");
        self.previewFiles.push({
          name: filename,
          src: dataUrl,
          ext: extenstion,
          type: mimeType
        });
        let blob = self.dataURItoBlob(dataUrl, filename, extenstion);
        return new File([blob], filename, { type: blob.type });
      };
      return new Promise(function(resolve, reject) {
        reader.onload = function(readerEvent) {
          image.onload = function() {
            return resolve(resize());
          };
          image.src = readerEvent.target.result;
        };
        reader.readAsDataURL(file);
      });
    },
    dataURItoBlob(dataURI, filename = null, ext = null) {
      var byteString = atob(dataURI.split(",")[1]);
      var mimeString = dataURI
        .split(",")[0]
        .split(":")[1]
        .split(";")[0];
      var ab = new ArrayBuffer(byteString.length);
      var ia = new Uint8Array(ab);
      for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
      }
      var bb = new Blob([ab], {
        type: mimeString
      });
      bb.name = filename;
      bb.ext = ext;
      return bb;
    }
  },
  template: `
<v-container fluid pa-0 ma-0 style="height:100%">
<input
      ref="fileInput"
      type="file"
      name="files[]"
      :multiple="true"
      @change="onFileSelection"
    />

<v-container
      @dragenter="draggingFile = true"
      @dragleave="draggingFile = false"
      @drop.prevent="dropFiles"
      @dragover.prevent="draggingFile = true"
      fluid
      grid-list-lg
      :class="['dropzone', draggingFile ? 'dropzone-over' : '']"
    >
      <v-layout row wrap align-center justify-center>
        <v-flex xs12 text-xs-center>
          <span class="subtitle-1 font-weight-light"
            >Drop files here or
            <a @click.prevent="openFileExplorer">browse...</a></span
          >
        </v-flex>
      </v-layout>

      <v-layout row wrap>
        <v-flex
          xs12
         sm4
          v-for="(file, i) in previewFiles"
          :key="i"
        >
          <v-card flat outlined>
            <v-img

              :src="file.src"
              height="150px"
            >
              <v-container fill-height fluid>
                <v-layout fill-height>
                  <v-flex xs12 align-end flexbox pa-0>
                    <span
                      class="white--text"
                      v-text="file.name"
                      style="word-break: break-all;"
                    ></span>
                  </v-flex>
                </v-layout>
              </v-container>
            </v-img>



            <v-card-actions
              class="white black--text"
              style="border-top:solid 1px #ccc !important;"
            >
              <v-layout justify-center row wrap>
                <v-flex shrink>
                  <v-btn icon color="error" @click="removeFile(file)">
                    <v-icon>delete</v-icon>
                  </v-btn>
                </v-flex>
              </v-layout>
            </v-card-actions>
          </v-card>
        </v-flex>
      </v-layout>
    </v-container>

</v-container>
 `
});

new Vue({
  el: "#app",
  vuetify: new Vuetify(),
  data: () => ({
    areaObject: {
      text: "",
      images: []
    },
    areas: [],
    final: []
  }),
  created() {
    this.areas.push(this.areaObject);
  },
  watch: {
    areas: {
      handler(val) {
        this.final = _.cloneDeep(val);
      },
      deep: true
    }
  },
  methods: {
    addArea(i) {
      this.areas.splice(i + 1, 0, {
        text: this.areas[i].text,
        images: this.areas[i].images
      });
    },
    removeArea(i) {
      if (this.areas.length > 1) {
        this.areas.splice(i, 1);
      }
    },
    updateImages(i, payload) {
      this.areas[i].images = payload;
    }
  }
});


最佳答案

好吧,我更深入地了解了。让我们看看,错误:

 <v-card flat width="100%" v-for="(item, i) in areas" :key="`card${i}`" class="my-2">

这意味着每个项目的 id 取决于其位置...这不是一个好主意,您将看到。

克隆第一个 ImageView 。

    addArea(i) {
      this.areas.splice(i + 1, 0, {
        text: this.areas[i].text,
        images: this.areas[i].images
      });

其次,您的图像甚至不用于渲染。

    updateImages(i, payload) {
      this.areas[i].images = payload;
    }

仔细看看这个( https://codepen.io/anon/pen/oroRNK )并考虑一下。我在每个 file-drop-zone 中添加了一个 id 标记和 PreviewFiles。

Vue 并不关心你如何通过拼接来修改数组。不管怎样,观察新的 id 是如何添加到后面的。

这是因为你的 key 。

要解决这个问题,只需修复按键即可。 (https://codepen.io/anon/pen/WqXBjM)

通常这不是问题,只是文件放置区中存在的状态并未完全封装在父级中。这两个错误的结合会导致其更新错误。

关于javascript - 使用文件输入预览在 v-for 循环中添加/删除行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56781983/

相关文章:

javascript - 如何检查 vue.js 数组中是否已存在项目?

vue.js - 我可以在 Vuex 存储中拥有多个状态吗?

javascript - Jquery touchend 在手指释放之前触发

javascript - 无法使用 javascript 下载 html 表

javascript - 在 TypeScript 中键入对象数组

twitter-bootstrap - 试图使 Bootstrap 4 导航栏折叠在 Vue 中工作

javascript - 在 Vue 中向用户列表添加功能复选框

javascript - D3.js - 检测交叉区域

javascript - 提交按钮不会调用该操作

vue.js - vuetify 翻译 v-text-field 标签