Ответы пользователя по тегу Vue.js
  • Как вызвать событие родительского объекта из шаблона(template) vue js?

    0xD34F
    @0xD34F Куратор тега Vue.js
    В дочернем компоненте:

    <input @input="$emit('fucking-event')">

    В родителе:

    <fucking-component @fucking-event="onFuckingEvent">

    methods: {
      onFuckingEvent() {
        console.log('fuck off');
      },
    Ответ написан
    Комментировать
  • Как написать имя события в подписчике v-on: в виде выражения?

    0xD34F
    @0xD34F Куратор тега Vue.js
    v-on="{ [здесь собираете имя события]: handler }"
    Ответ написан
    Комментировать
  • Как в Vue подставить значение в input?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Ну, НАВЕРНОЕ, надо присвоить значение свойству, которое вы указали в v-model.
    Ответ написан
    3 комментария
  • Как динамически изменять размеры блока?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Нет такого атрибута - transform. Это должно быть свойство style:

    :style="{ transform: computedMatrix }"
    Ответ написан
    Комментировать
  • Как данный код переписать на vue?

    0xD34F
    @0xD34F Куратор тега Vue.js
    В первую очередь следует избавиться от копипасты в разметке. Видим группу схожих элементов - оставляем один, вешаем на него v-for, которому будет передаваться массив с данными. Блок базовой комплектации примет примерно такой вид:

    baseOptions: [
      { name: 'Свойство 1', price: 10000 },
      { name: 'Свойство 2', price: 11000 },
      ...

    <ul>
      <li v-for="n in baseOptions">{{ n.name }} ({{ n.price }})</li>
    </ul>

    Блок дополнительных опций - аналогично, но так эти опции можно выбирать, они, помимо имени и цены, будут содержать ещё одно свойство, отвечающее за состояние чекбоксов (связываются посредством v-model):

    extraOptions: [
      { name: 'Пакет 1', price: 16000, checked: false },
      { name: 'Пакет 2', price: 17000, checked: false },
      ...

    <div v-for="n in extraOptions">
      <input type="checkbox" v-model="n.checked">
      <label>{{ n.name }}</label>
    </div>


    Сделаем метод расчёта суммы. Он будет принимать массив опций и складывать их стоимость:

    methods: {
      sum: arr => arr.reduce((acc, n) => acc + n.price, 0),

    С помощью этого метода посчитаем суммы базовой комплектации и дополнительных опций. Оформим их как вычисляемые свойства:

    computed: {
      baseSum() {
        return this.sum(this.baseOptions);
      },
      extraSum() {
        return this.sum(this.extraOptions.filter(n => n.checked));
      },


    Наконец, разберёмся с выводом сумм. Сделаем фильтр, где число будет приводиться к человекопонятному формату и добавляться наименование валюты:

    filters: {
      price: val => `${val.toLocaleString()} р.`,

    <span>{{ baseSum | price }}</span>

    <span>{{ extraSum | price }}</span>

    https://jsfiddle.net/3gmrs0o1/1/
    Ответ написан
  • Почему infoWindow в vue-google-map не открывается?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Не открывается, потому что вместо изменения значения свойства компонента, отвечающего за состояние infoWindow, у вас в методе onMarkerClick какая-то ересь.

    Делайте так:

    <gmap-info-window 
      :options="infoOptions" 
      :position="infoWindowPos" 
      :opened="infoWinOpen"
      @closeclick="infoWinOpen = false"
    >
      {{ infoContent }}
    </gmap-info-window>

    onMarkerClick(e) {
      this.infoWindowPos = e.latLng; // задаём положение окна, над кликнутым маркером
      this.infoContent = JSON.stringify(e.latLng); // задаём контент окна, передаётся в слот
      this.infoWinOpen = true; // открываем окно
    },
    Ответ написан
    1 комментарий
  • Клик вне области компонента vue?

    0xD34F
    @0xD34F Куратор тега Vue.js
    https://www.npmjs.com/package/v-click-outside

    Ну или самостоятельно можете это дело реализовать:

    created() {
      const onClickOutside = e => this.opened = this.$el.contains(e.target) && this.opened;
      document.addEventListener('click', onClickOutside);
      this.$on('hook:beforeDestroy', () => document.removeEventListener('click', onClickOutside));
    },
    Ответ написан
    2 комментария
  • Как сгруппировать массив объектов по месяцам?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Функция получения имени месяца по его индексу, она нам ниже понадобится:

    const getMonthName = iMonth =>
      new Date(0, iMonth).toLocaleString('ru-RU', { month: 'long' });

    Сгруппированные данные оформляем в виде вычисляемого свойства.

    Бежим по массиву исходных данных, получаем имя месяца, создаём в результирующем объекте массив группы, если таковой отсутствует, добавляем в него текущий элемент. Вот так всё просто:

    computed: {
      groupedByMonth() {
        return this.items.reduce((acc, n) => {
          const key = getMonthName(n.date.split('-')[1] - 1);
          (acc[key] = acc[key] || []).push(n);
          return acc;
        }, {});
      },
    },

    <ul>
      <li v-for="(items, month) in groupedByMonth" :key="month">
        <h3>{{ month }}</h3>
        <ul>
          <li v-for="n in items" :key="n.id">{{ n }}</li>
        </ul>
      </li>
    </ul>

    Если нужны все месяцы, в том числе и те, за которые нет никаких данных, тогда сразу создаём массив на двенадцать элементов, каждый из которых содержит имя месяца и массив для сгруппированных данных. Бежим по исходному массиву, получаем индекс месяца, кладём текущий элемент в массив соответствующей группы. Тоже ничего сложного:

    groupedByMonth() {
      return this.items.reduce((acc, n) => {
        acc[n.date.split('-')[1] - 1].items.push(n);
        return acc;
      }, Array.from({ length: 12 }, (_, i) => ({
        month: getMonthName(i),
        items: [],
      })));
    },

    <ul>
      <li v-for="{ items, month } in groupedByMonth" :key="month">
        <h3>{{ month }}</h3>
        <ul v-if="items.length">
          <li v-for="n in items" :key="n.id">{{ n }}</li>
        </ul>
        <div v-else>данных нет</div>
      </li>
    </ul>
    Ответ написан
    6 комментариев
  • Как динамически менять url адрес api?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Импортируете роутер в своём store.js:

    import router from './router.js';

    При создании url смотрите текущий маршрут:

    const url = `/api/photos?${в зависимости от router.currentRoute}=true&page=${list}&limit=15`;
    Ответ написан
  • Как создать маркер по клику GoogleMap в Vue?

    0xD34F
    @0xD34F Куратор тега Vue.js
    <gmap-map
      ref="map"
      v-bind="options"
      @click="onMapClick"
    >
      <gmap-marker
        v-for="m in markers"
        :key="m.id"
        :position="m.position"
        :clickable="true"
        :draggable="true"
        @click="onMarkerClick"
      />
    </gmap-map>

    data: () => ({
      options: {
        center: { lat: 45.101637, lng: 38.986345 },
        zoom: 15,
      },
      markers: [],
    }),
    methods: {
      onMapClick(e) {
        this.markers.push({
          id: 1 + Math.max(0, ...this.markers.map(n => n.id)),
          position: e.latLng,
        });
      },
      onMarkerClick(e) {
        this.$refs.map.panTo(e.latLng);
        // или
        // this.options.center = e.latLng;
      },
    },

    https://codesandbox.io/s/blissful-fast-pjc7o
    Ответ написан
    1 комментарий
  • Как лучше всего передавать данные из компонентов с множеством инпутов родителю?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Отправляйте наверх не только значение, но и имя параметра. Сами параметры соберите в один объект - так проще и ещё можно будет использовать v-model.
    Ответ написан
    1 комментарий
  • Как во Vue сделать один из чекбоксов по умолчанию checked и disabled?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Вырезаете selectedProducts, вместо него используете в v-model свойства checked элементов products. Отключить чекбокс - свойства disabled у вас уже есть, просто привяжите их значения к чекбоксам.

    <input
      type="checkbox"
      v-model="product.checked"
      :disabled="product.disabled"
    >

    Ну и в отсутствие selectedProducts следует переписать вычисляемое свойство totalPrice:

    computed: {
      totalPrice() {
        return this.products.reduce((acc, n) => acc + n.checked * n.price, 0);
      },
    },
    Ответ написан
    Комментировать
  • Как динамически создавать элементы во Vue.js?

    0xD34F
    @0xD34F Куратор тега Vue.js
    is:

    <component
      v-for="n in flyingButtons"
      :is="n.tag"
      :class="n.class"
    >
      {{ n.title }}
    </component>
    Ответ написан
    Комментировать
  • Как сделать пагинацию через Vuex?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Хранилище

    Какие свойства должны быть в стейте - номер текущей страницы; количество элементов на странице; общее количество элементов; данные текущей страницы; флаг, сигнализирующий о том, что в данный момент осуществляется загрузка данных (первые четыре у вас уже есть, последнее почему-то находится в компоненте - надо переместить):

    state: {
      page: 0,
      perPage: 5,
      total: 0,
      posts: [],
      loading: false,
    },

    Понадобится геттер, вычисляющий количество страниц:

    getters: {
      numPages: state => Math.ceil(state.total / state.perPage),
    },

    Что и как будет обновляться в стейте:

    mutations: {
      updateLoading: (state, loading) => state.loading = loading,
      updatePosts: (state, { posts, total, page }) => Object.assign(state, { posts, total, page }),
    },

    Загрузка данных - до запроса устанавливаете loading в true, после переключаете обратно в false; запрос, сохранение результатов - тут, думаю, всё понятно:

    actions: {
      async fetchPosts({ state, commit }, page) {
        commit('updateLoading', true);
    
        const start = (page - 1) * state.perPage;
        const end = page * state.perPage;
        const url = `https://jsonplaceholder.typicode.com/posts?_start=${start}&_end=${end}`;
    
        try {
          const response = await fetch(url);
          const posts = await response.json();
          const total = response.headers.get('x-total-count');
          commit('updatePosts', { posts, total, page });
        } catch (e) {
          console.error(e);
        }
    
        commit('updateLoading', false);
      },
    },

    Компонент пагинации

    Получает два параметра - номер текущей страницы и количество страниц:

    props: [ 'page', 'numPages' ],

    Имеет методы для перехода к конкретной странице (отправляет номер новой страницы в родительский компонент - тому виднее, что с ним делать) и к странице относительно текущей:

    methods: {
      goTo(page) {
        this.$emit('paginate', page);
      },
      next(step) {
        this.goTo(this.page + step);
      },
    },

    Чтобы не давать пользователю переходить к несуществующим страницам, сделаем вычисляемые свойства, указывающие, является ли текущая страница первой/последней,...

    computed: {
      isFirst() {
        return this.page <= 1;
      },
      isLast() {
        return this.page >= this.numPages;
      },
    },

    ...и используем эти свойства для блокировки кнопок перехода на предыдущую/следующую/первую/последнюю страницы:

    <button @click="goTo(1)" :disabled="isFirst">&lt;&lt;</button>
    <button @click="next(-1)" :disabled="isFirst">&lt;</button>
    {{ page }} / {{ numPages }}
    <button @click="next(+1)" :disabled="isLast">&gt;</button>
    <button @click="goTo(numPages)" :disabled="isLast">&gt;&gt;</button>

    Ну и добавим поддержку директивы v-model:

    model: {
      prop: 'page',
      event: 'paginate',
    },


    Как всем этим пользоваться

    В компоненте, из которого планируется осуществление пагинации, делаем вычисляемое свойство, значением которого является номер текущей страницы из хранилища, а при установке нового значения будет вызываться действие, загружающее данные:

    computed: {
      page: {
        get() {
          return this.$store.state.page;
        },
        set(page) {
          this.$store.dispatch('fetchPosts', page);
        },
      },

    Привязываем через v-model это свойство к экземпляру компонента пагинации (ну и количество страниц не забываем ему передать):

    <компонент-пагинации
      v-model="page"
      :num-pages="$store.getters.numPages"
    />

    Не забываем указать, какую страницу хотим видеть по умолчанию:

    created() {
      this.page = 1;
    },

    Отображение данных:

    <div v-if="$store.state.loading">данные загружаются, ждите</div>
    <компонент-для-отображения-данных v-else :данные="$store.state.posts" />


    https://jsfiddle.net/08hno57q/
    Ответ написан
    Комментировать
  • Vue.js Калькулятор цен. Как справиться с radio кнопками?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Указывать в v-model радиокнопок следует не разные свойства, а одно и то же - в качестве значения оно будет принимать value выбранной кнопки.
    Ответ написан
    3 комментария
  • Почему json выводится таким образом?

    0xD34F
    @0xD34F Куратор тега Vue.js
    выводит побуквенно

    Тот факт, что вы ожидали иного, ясно говорит следующее: вы не знаете, что такое json. Не понимаете, что это строка. Что сам json и объекты, получаемые при его парсинге - не одно и то же.

    Строки v-for так и перебирает - "побуквенно". То есть, значениями user являются строки единичной длины. Ну а свойств id, name, price и т.д. у строк нет, отсюда пустота там, где вы выводите свойства элементов users.
    Ответ написан
    Комментировать
  • Как через input загрузить картинку в cropper?

    0xD34F
    @0xD34F Куратор тега Vue.js
    mounted() {
      this.cropper = new Cropper(this.$refs.image, {
        zoomable: false,
        scalable: false,
        aspectRatio: 1,
      });
    },
    methods: {
      selectFile(e) {
        const file = (e.target.files || e.dataTransfer.files)[0];
        if (file) {
          const reader = new FileReader();
          reader.onload = e => this.cropper.replace(e.target.result);
          reader.readAsDataURL(file);
        }
      },
    },
    Ответ написан
    1 комментарий
  • Как в js вызвать событие change?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Во-первых, не change - v-model по умолчанию слушает событие input (если хотите, чтобы использовалось change, добавьте модификатор lazy).

    Во-вторых, dispatchEvent.
    Ответ написан
    Комментировать
  • Как добавить обработчик двойного клика на попап 2gis?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Что, не хочет добавляться обработчик? Видимо, такой возможности не предусмотрено в 2gis api. Давайте закостылим - добавим его нативными средствами. Ну, почти. Так как элемент popup'а создаётся при его первом открытии, понадобится однократно срабатывающий обработчик popupopen, в котором будет выполняться назначение обработчика dblclick.

    Добавляете компоненту методы:

    methods: {
      onPopupOpen(e) {
        e.target
          .getPopup()
          .getElement()
          .querySelector('.leaflet-popup-content-wrapper')
          .addEventListener('dblclick', this.onPopupDblClick);
      },
      onPopupDblClick(e) {
        const
          id = +e.currentTarget.querySelector('[data-id]').dataset.id,
          // теперь, зная id, можно найти соответствующий объект
        ...

    А создаваемым маркерам обработчик:

    .once('popupopen', this.onPopupOpen)

    https://jsfiddle.net/3jz059mb/
    Ответ написан
    Комментировать