@pilolin
HTML программист

Как создать набор блоков для загрузки изображений?

Необходимо создать поля для загрузки изображений с подгрузкой превью на vuejs. Ранее в данном проекте был создан компонент для загрузки одного изображения и без функции удаления этого изображения из поля. Сейчас стало необходимо создать набор блоков появляющихся друг за другом при загрузке изображения в предыдущий блок. И дополнительно удаление изображения если было загружено не то что нужно. Весь исходный код представлен ниже, сократил оставив только нужное. Попробовал реализовать самостоятельно но столкнулся с проблемами.

1) если переменная в которой будут храниться изображения будет массивом то при удалении элементы сдвигаются и происходит косяк с отображением новых

2) так же наблюдается косяк переодически с удалением блока

Т.е. сейчас в сторону загрузки изображений все работает, а в обратную нет

фидл для демонстрации работы


Код для наглядности и вдруг если фидл потеряется
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <set-imgs
    :name="'instagram-gig-'" 
    :images="images"
    :max-amount="3"
    :count-per-row="3"/>
</div>


Vue.component('input-img', {
  template: `
    <div class="card-input-img" :style="{'padding-top': height}">
      <i 
        class="card-input-img__icon material-icons card-input-img__size_sm"
        :class="{'card-input-img__icon_remove': (hasRemove && img.src)}" 
        v-text="(hasRemove && img.src) ? 'x' : '+'" 
        @click="removeImg"/>
      <img 
        v-if="img.src || background" :src="img.src || background" 
        class="card-input-img__image card-input-img__image_size_cover">
      <input 
        type="file" 
        :name="name"
        accept="image/*"
        @change="fileChangeHandler">
    </div>`,
  props: {
    name: {
      type: String,
      required: true
    },
    height: {
      type: String,
      default: '100%'
    },
    img: {
      type: Object,
      default: null
    },
    background: {
      type: String
    },
    hasRemove: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      newImg: {
        file: null,
        src: null
      },
      firstUpload: true,
    }
  },
  created() {
    this.newImg = this.img
  },
  methods: {
    fileChangeHandler(e) {
      this.newImg.file = e.target.files[0];
      if( this.newImg.file ) {
        let reader = new FileReader();
        reader.onloadend = () => {
          const image  = new Image();
          image.onload = () => {

            this.newImg.src = image.src;

            this.$emit('update:img', this.newImg);
            if(this.firstUpload) {
              this.$emit('first-uploaded', this.newImg);
              this.firstUpload = false;
            }
          }
          image.src = reader.result;
				}
        reader.readAsDataURL(this.newImg.file);
      } 
    },
    removeImg() {
      document.querySelector(`[type="file"][name="${this.name}"]`).value = null;
      this.$emit('remove');
      this.newImg = {
        file: null,
        src: null
      };
      this.firstUpload = true;
    }
  }
});

Vue.component('set-imgs', {
  template: `
    <div class="grid grid_col_3 grid_gap_s">
      <input-img 
        v-for="(image, i) in images"
        :key="i"
        :name="name + i" 
        :background="image.src"
        :img.sync="image" 
        @update:img="image = $event"
        @first-uploaded="addFieldNextImage"
        @remove="removeElem(i)" />
    </div>`,
  props: {
    name: {
      type: String,
      required: true
    },
    img: Object,
    maxAmount: {
      type: Number,
      default: 3,
    },
    countPerRow: {
      type: Number,
      default: 3,
    },
    images: {
      type: Array,
      default: () =>  [{file: null, src: null}]
    }
  },
  methods: {
    addFieldNextImage() {
      if(this.images.length < this.maxAmount) { 
        this.images.push({ file: null, src: null });
      }
    },
    removeElem(i) {
      this.images.splice(i, 1);
    },
  }
});

new Vue({
  el: '#app',
  data() {
    return {
      images: [{file: null, src: null}],
    }
  },
});
  • Вопрос задан
  • 128 просмотров
Решения вопроса 1
0xD34F
@0xD34F Куратор тега Vue.js
Компонент input-img

Вырезаете секцию data, ничего этого не надо. Задача компонента - показывать картинку (если она есть), после загрузки отправлять новую картинку родителю, отправлять родителю запрос на удаление картинки:

methods: {
  onFileChange(e) {
    const file = e.target.files[0];
    if (file) {
      const reader = new FileReader();
      reader.onloadend = () => {
        const image = new Image();
        image.onload = () => {
          this.$emit('uploaded', {
            file,
            src: image.src,
          });
        };
        image.src = reader.result;
      };
      reader.readAsDataURL(file);
    }
  },
},

<i
  v-text="img.src ? 'x' : '+'"
  @click="$emit('remove')"
  ...
/>
<img
  v-if="img.src"
  :src="img.src"
  ...
>
<input type="file" accept="image/*" @change="onFileChange">

Компонент set-imgs

Добавим поддержку директивы v-model. В props переименовываете images в value, убираете из дефолтного значения объект с пустыми полями. Добавляете images в computed - в качестве значения будет копия value плюс, если длина value меньше максимально допустимой, тот объект с пустыми полями; сеттер - отправляет родителю images, из которого выкинут пустой объект:

props: {
  value: Array,
  max: {
    type: Number,
    default: 3,
  },
},
computed: {
  images: {
    get() {
      const images = [...this.value];
      if (images.length < this.max) {
        images.push({
          file: null,
          src: null,
        });
      }
      return images;
    },
    set(val) {
      this.$emit('input', val.filter(n => n.file));
    },
  },
},

Как в этом случае будет выглядеть добавление/изменение картинки - images заменяется на копию, у которой заменён элемент с указанным индексом. А удаление, соответственно, будет выглядеть как фильтрация, тоже по индексу:

methods: {
  onUpload(index, img) {
    this.images = this.images.map((n, i) => i !== index ? n : img);
  },
  remove(index) {
    this.images = this.images.filter((n, i) => i !== index);
  },
},

Собираем всё вместе:

<input-img
  v-for="(n, i) in images"
  :img="n"
  @uploaded="onUpload(i, $event)"
  @remove="remove(i)"
/>

Наконец, использование set-imgs

data: () => ({
  images: [],
}),

<set-imgs v-model="images" :max="5" />

https://jsfiddle.net/7umwaqfd/
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы