Ответы пользователя по тегу Vue.js
  • Как вывести сложную таблицу во Vue.js?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Если количество уровней вложенности невелико и известно заранее, то... Для обработки каждого уровня вложенности используется отдельный <template v-for, ячейки с rowspan'ами создаются в зависимости от равенства индексов элементов вложенных массивов нулю, значения rowspan'ов - длины (суммы длин) вложенных массивов. Например:

    const rowspan = item => item.backlinks.reduce((acc, n) => acc + n.recipients.length, 0);

    <tbody>
      <template v-for="item in data">
        <template v-for="(backlink, iBacklink) in item.backlinks">
          <template v-for="(recipient, iRecipient) in backlink.recipients">
            <tr>
              <template v-if="!iBacklink && !iRecipient">
                <td :rowspan="rowspan(item)">{{ item.name }}</td>
                <td :rowspan="rowspan(item)">{{ item['domain score'] }}</td>
              </template>
              <template v-if="!iRecipient">
                <td :rowspan="backlink.recipients.length">{{ backlink.donor }}</td>
                <td :rowspan="backlink.recipients.length">{{ backlink['page score'] }}</td>
              </template>
              <td>{{ recipient.url }}</td>
              <td>{{ recipient.image }}</td>
            </tr>
          </template>
        </template>
      </template>
    </tbody>

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

    const keys = ref([
      'name', 'domain score', 'backlinks',
      'donor', 'page score', 'recipients', 
      'url', 'image',
    ]);

    function createTableData(arr, keys, iKey = 0) {
      return arr.flatMap(n => {
        const row = [];
        const innerRows = [];
    
        for (let i = iKey; i < keys.length; i++) {
          const val = n[keys[i]];
          if (Array.isArray(val)) {
            innerRows.push(...createTableData(val, keys, i + 1));
            row.forEach(cell => cell.rowspan = innerRows.length);
            row.push(...innerRows.shift());
            break;
          } else {
            row.push({ text: val });
          }
        }
    
        return [ row, ...innerRows ];
      });
    }

    Преобразование вложенных данных в плоские можно оформить в виде вычисляемого свойства:

    const tableData = computed(() => {
      return createTableData(props.data, props.keys);
    });

    Ну а построить таблицу на основе плоского массива - дело тривиальное:

    <tbody>
      <tr v-for="row in tableData">
        <td
          v-for="cell in row"
          v-text="cell.text"
          :rowspan="cell.rowspan"
        ></td>
      </tr>
    </tbody>
    Ответ написан
    Комментировать
  • Как вывести списком данные?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Рекурсивный компонент. Проверяем, является ли переданное значение объектом. Если нет - отображаем его. Если да - бежим по его свойствам, под каждое создаём по экземпляру компонента. Например, так.
    Ответ написан
    Комментировать
  • Как переписать код перемещения блока мышью с чистого js на vue?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Вместо обновления стилей напрямую хранить координаты в массиве, обновлять этот массив, значения из массива использовать для задания стилей (можно оформить стили как вычисляемое свойство, имеющее в качестве зависимости координаты). Примерно так:

    const coords = reactive([ 50, 50 ]);
    
    const circleStyles = computed(() => ({
      left: `${coords[0]}px`,
      top: `${coords[1]}px`,
    }));
    
    const updateCoords = e => (coords[0] += e.movementX, coords[1] += e.movementY);
    const updateCoordsOn = () => document.addEventListener('mousemove', updateCoords);
    const updateCoordsOff = () => document.removeEventListener('mousemove', updateCoords);

    <div
      class="circle"
      :style="circleStyles"
      @mousedown="updateCoordsOn"
      @mouseup="updateCoordsOff"
    ></div>
    Ответ написан
    Комментировать
  • Почему не работает двусторонняя привязка с родительским компонентом во vue2?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Потому что возможность указывать аргумент для v-model появилась в третьей версии vue.

    Вместо v-model используйте sync.
    Ответ написан
    3 комментария
  • Как подключить и использовать внешний JS скрипт Choices.js в Vue.js компонент?

    0xD34F
    @0xD34F Куратор тега Vue.js
    <div v-once>
      <select ref="select" v-model="region"></select>
    </div>

    mounted() {
      const choices = new Choices(this.$refs.select);
      this.$watch(
        'regions',
        val => choices.setChoices(val, 'value', 'name', true),
        { immediate: true }
      );
      this.$on('hook:beforeDestroy', () => choices.destroy());
    },
    Ответ написан
  • Как проверить загружается ли изображение?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Назначайте класс в зависимости от значения свойства, которое будет устанавливаться в обработчиках onload и onerror. Например.
    Ответ написан
    1 комментарий
  • Миграция на VUE 3, не работает роутер?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Для vue 3 предназначена четвёртая версия роутера, а не третья:

    - <script src="https://unpkg.com/vue-router@3/dist/vue-router.js"></script>
    + <script src="https://unpkg.com/vue-router@4/dist/vue-router.global.js"></script>

    А вот это надо вырезать:

    import VueRouter from 'vue-router'
    Ответ написан
  • Как получить данные маркеров, которые находятся в области видимости карты (vue-yandex-map)?

    0xD34F
    @0xD34F Куратор тега Яндекс.Карты
    Подписаться на события инициализации карты и изменения её границ. Получить границы видимой области карты. Перебрать массив данных, на основе которых создаются маркеры, проверяя, как координаты маркеров соотносятся с границами видимой области. Как-то так:

    <yandex-map
      ref="map"
      @map-was-initialized="onBoundsChange"
      @boundschange="onBoundsChange"
      ...
    >
      <ymap-marker
        v-for="n in markersData"
        ...
      >

    methods: {
      onBoundsChange() {
        const bounds = this.$refs.map.myMap.getBounds();
        this.markersData.forEach(n => {
          if (
            bounds[0][0] < n.coords[0] && n.coords[0] < bounds[1][0] &&
            bounds[0][1] < n.coords[1] && n.coords[1] < bounds[1][1]
          ) {
            // ...
          }
        });
      },
      ...
    Ответ написан
    Комментировать
  • Почему не получается передать пользовательское событие родительскому компоненту?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Всё получается, всё передаётся.

    метод testemit компонента question не отрабатывает

    А должен? Нет, не должен - вы его нигде не вызываете. Как и не используете в качестве обработчика событий - вижу, что $emit('testemit') есть, а вот @testemit="testemit" отсутствует.
    Ответ написан
  • Как очищать все поля формы поиска, кроме одного?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Сделать observable объект, содержащий ссылку на экземпляр компонента, в котором последний раз осуществлялся пользовательский ввод. Установить наблюдение за этой ссылкой, и если значение не совпадает с текущим экземпляром - сбрасывать значение input'а:

    const lastEdited = Vue.observable({ instance: null });

    props: [ 'value' ],
    methods: {
      onInput(e) {
        this.$emit('input', e.target.value);
        lastEdited.instance = this;
      },
    },
    created() {
      this.$watch(() => lastEdited.instance, val => {
        if (val && val !== this) {
          this.$emit('input', '');
        }
      });
    },
    beforeDestroy() {
      if (lastEdited.instance === this) {
        lastEdited.instance = null;
      }
    },

    <input :value="value" @input="onInput">

    https://jsfiddle.net/jLw096zm/

    Или можно шину событий применить. Если случилось изменение input'а пользователем, кидать событие, в качестве аргумента передавать ссылку на экземпляр компонента, в обработчике сравнивать значение аргумента и this, если не совпали - очищать input:

    const eventBus = new Vue();

    props: [ 'value' ],
    methods: {
      onInput(e) {
        this.$emit('input', e.target.value);
        eventBus.$emit('clear-other-inputs', this);
      },
    },
    created() {
      const clear = e => e !== this && this.$emit('input', '');
      eventBus.$on('clear-other-inputs', clear);
      this.$on('hook:beforeDestroy', () => eventBus.$off('clear-other-inputs', clear));
    },

    <input :value="value" @input="onInput">

    https://jsfiddle.net/jLw096zm/1/
    Ответ написан
    Комментировать
  • Почему не удается получить актуальное значение переменной из Pinia в компоненте?

    0xD34F
    @0xD34F Куратор тега Vue.js
    имеется pinia такого вида
    <...>
    export const useSearchStore = () => {

    Ну и где тут Pinia? Где вызов defineStore?

    const { search, updateSearchQuery } = useSearchStore();

    Так нельзя:

    Note that store is an object wrapped with reactive, meaning there is no need to write .value after getters but, like props in setup, we cannot destructure it

    Вы вообще как, документацию пробовали открывать?

    Исправляем:

    Стор:

    const useSearchStore = defineStore('search', () => {
      const search = ref('');
      const setSearch = val => search.value = val;
      return { search, setSearch };
    });

    Компонент search input:

    const searchStore = useSearchStore();
    const search = computed({
      get: () => searchStore.search,
      set: searchStore.setSearch,
    });

    <input v-model.trim="search">

    Компонент search:

    Не надо слушать никаких событий из search input, не надо трогать стор, уберите это всё отсюда.

    Корневой компонент:

    const searchStore = useSearchStore(); 
    watch(() => searchStore.search, val => console.log(val));
    Ответ написан
  • Как использовать computed внутри v-for?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Никак не использовать. Хотите тут computed - вместо одного значения пусть предоставляет массив:

    const names = computed(() => list.map(n => (n.main ?? n.default).name));

    <div v-for="n in names">
      {{ n }}
    </div>
    Ответ написан
    Комментировать
  • Как в Vue реализовать анимацию пунктов меню с приростом задержки для каждого пункта?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Поставьте значение animation-delay в зависимость от индекса в v-for:

    v-for="(n, i) in items"
    :style="{ 'animation-delay': i * 0.5 + 's' }"

    UPD. Вынесено из комментариев:

    индекс используется для других целей, там текстовая строка, а не число. В этом и сложность задачи

    То есть, данные - это объект, а не массив? Никаких сложностей, всё предусмотрено - v-for для объектов выдаёт три значения, как раз для того, чтобы у свойств тоже мог быть индекс.
    Ответ написан
    3 комментария
  • Vue slots, select страны?

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

    <select>
      <template v-for="n in options">
        <option>
          <slot name="option" :option="n">{{ n }}</slot>
        </option>
      </template>
    </select>

    Снаружи:

    <v-select :options="countries">
      <template #option="{ option }">{{ option.name }}</template>
    </v-select>

    https://jsfiddle.net/w6nuzamx/
    Ответ написан
    2 комментария
  • Не давно начала изучать VUE, какие компоненты правильные?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Правильно так, как вам удобно.

    А ещё, чтобы научиться делать правильно, надо сперва сделать неправильно.

    Так что реализовываете все варианты, какие только сможете придумать.

    Если в реализованном что-то не устраивает, всегда можно оформить вопрос (конечно, сперва погуглив) - вот есть такой-то компонент, решает такую-то задачу, мне в нём не нравится то-то и то-то, какие есть способы сделать лучше?

    Когда решите таким образом задач сорок-пятьдесят, начнёт приходить понимание, как делать правильно.
    Ответ написан
    Комментировать
  • Как избавиться от повторных вызовов метода в шаблоне?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Сделайте отдельный компонент, который будет принимать getDayinLoop(worker.wrdays, mday) как параметр.
    Ответ написан
    Комментировать
  • Как передать ивент из одного компонента в другой?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Если говорить именно о передаче событий, то гуглите, что такое шина событий. Но вообще, стоит подумать, а правда ли оно вам надо. Возможно, ваша задача может быть решена иначе:

    Первый компонент - внутри emit, снаружи обработчик события.

    Второй компонент - внутри watch на параметр, снаружи привязка значения этого параметра (значение обновляется в обработчике события первого компонента); или, внутри метод, снаружи ref (вызываете метод в обработчике события первого компонента).
    Ответ написан
    Комментировать
  • Почему при использовании vue3-mq возникает ошибка 'Property "$mq" was accessed during render but is not defined on instance'?

    0xD34F
    @0xD34F Куратор тега Vue.js
    vue3-mq

    this.$mq

    Что по этому поводу имеет сказать документация:

    $mq has been removed as a global property and must now be injected into components that require it
    Ответ написан
    Комментировать
  • Как кастомизировать datepicker в библиотеке element plus?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Изменить отображаемый формат даты можно через соответствующий параметр компонента.

    Иконки убираются с помощью css.

    "Плейсхолдер" делается через дополнительную обёртку (UPD. Или его можно оформить как кастомную иконку).
    Ответ написан
    Комментировать
  • Как создать динамические поля ввода vue3?

    0xD34F
    @0xD34F Куратор тега Vue.js
    const createTopic = () => ({
      text: '',
      subtopics: [ createSubTopic() ],
    });
    
    const createSubTopic = () => ({
      text: '',
    });
    
    const topics = reactive([ createTopic() ]);

    <div v-for="(topic, iTopic) in topics">
      <div>
        <div>
          Topic #{{ iTopic + 1 }}:
          <input v-model="topic.text">
        </div>
        <div v-for="(subtopic, iSubtopic) in topic.subtopics">
          Subtopic #{{ iTopic + 1 }}.{{ iSubtopic + 1 }}:
          <input v-model="subtopic.text">
          <button v-if="iSubtopic" @click="topic.subtopics.splice(iSubtopic, 1)">x</button>
        </div>
      </div>
      <button @click="topic.subtopics.push(createSubTopic())">Add subtopic</button>
    </div>
    <button @click="topics.push(createTopic())">Add topic</button>
    Ответ написан
    1 комментарий