Как реализовать выпадающие списки, где значение следующего будет зависить от предыдущего?

Изучаю рекурсивные компоненты

<div class="container">
  <h4>Vue.js Expandable Tree Menu<br/><small>(Recursive Components)</small></h4>
  <div id="app">
  <tree-menu 
             :nodes="tree.nodes" 
             :depth="0"   
             :label="tree.label"
             ></tree-menu>
  </div>
</div>


<script type="text/x-template" id="tree-menu">
  <div class="tree-menu">
    <div class="label-wrapper" @click="toggleChildren">
      <div :style="indent" :class="labelClasses">
        <i v-if="nodes" class="fa" :class="iconClasses"></i>
        {{ label }}
      </div>
    </div>
    <tree-menu 
      v-if="showChildren"
      v-for="node in nodes" 
      :nodes="node.nodes" 
      :label="node.label"
      :depth="depth + 1"   
    >
    </tree-menu>
  </div>
</script>

let tree = {
  label: 'root',
  nodes: [
    {
      label: 'item1',
      nodes: [
        {
          label: 'item1.1'
        },
        {
          label: 'item1.2',
          nodes: [
            {
              label: 'item1.2.1'
            }
          ]
        }
      ]
    }, 
    {
      label: 'item2'  
    }
  ]
}

Vue.component('tree-menu', { 
  template: '#tree-menu',
  props: [ 'nodes', 'label', 'depth' ],
  data() {
     return {
       showChildren: false
     }
  },
  computed: {
    iconClasses() {
      return {
        'fa-plus-square-o': !this.showChildren,
        'fa-minus-square-o': this.showChildren
      }
    },
    labelClasses() {
      return { 'has-children': this.nodes }
    },
    indent() {
      return { transform: `translate(${this.depth * 50}px)` }
    }
  },
  methods: {
    toggleChildren() {
       this.showChildren = !this.showChildren;
    }
  }
});

new Vue({
  el: '#app',
  data: {
    tree
  }
})

Ссылка на кодпен.

Есть задача, сделать "хлебные крошки" из выпадающих списков. К примеру здесь это обычный селект, при выборе первого или второго элемента справа должен появиться еще выпадающий список с дочерними элементами, принадлежащими соответственно, элементу 1 или 2. И так до бесконечности. Как реализовать с использованием рекурсии? И без использования селекта? Также, слово выбор элемента появляется только при 1-ом выборе.

Очень долго сижу над этим и пока без продвижения.


Ответы (1 шт):

Автор решения: Vasiliy Rusin

App.vue

<template>
  <div>
    {{ selectedLoactions.map(({ label }) => label).join(", ") }}
    <recursive-selection v-model="selectedLoactions" :options="locations" />
  </div>
</template>

<script>
import RecursiveSelection from "@/components/RecursiveSelection.vue";

export default {
  name: "App",

  components: { RecursiveSelection },

  data() {
    return {
      selectedLoactions: [],
    };
  },

  computed: {
    locations() {
      return [
        {
          label: "Россия",
          value: "Russia",
          options: [
            {
              label: "Московская область",
              value: "Moscow",
              options: [
                {
                  label: "Москва",
                  value: "Moscow",
                },
                {
                  label: "Красногорск",
                  value: "Krasnogorsk",
                },
              ],
            },
            {
              label: "Свердловская область",
              value: "Sverdlovsk",
              options: [
                {
                  label: "Екатеринбург",
                  value: "Yekaterinburg",
                },
              ],
            },
          ],
        },
        {
          label: "Япония",
          value: "Japan",
          options: [
            {
              label: "Префектура Токио",
              value: "Tokyo",
              options: [
                {
                  label: "Токио",
                  value: "Tokyo",
                },
              ],
            },
          ],
        },
      ];
    },
  },
};
</script>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

RecursiveSelection.vue

Компонент рекурсивного прохода.

<template>
  <div class="display-flex">
    <reusable-select v-model="computedSelected" :options="options" />

    <template v-if="nextLevelOptions">
      <recursive-selection
        v-model="computedValue"
        :key="key"
        :options="nextLevelOptions"
      />
    </template>
  </div>
</template>

<script>
import ReusableSelect from "@/components/ReusableSelect.vue";

export default {
  name: "RecursiveSelection",

  components: { ReusableSelect },

  props: {
    value: {
      type: Array,
      required: true,
    },

    options: {
      type: Array,
      required: true,
    },
  },

  data() {
    return {
      position: null,
    };
  },

  created() {
    // При создании компонента запоминаем его позицию
    // вложенности
    this.position = this.computedValue.length;
  },

  computed: {
    // Нам нужен ключ что бы обновлять вложенные объекты
    key() {
      return JSON.stringify(this.nextLevelOptions);
    },

    // Этот компьютед отвечает за изменение массива
    // выбранных значений
    computedValue: {
      get() {
        return this.value;
      },

      set(value) {
        // Поднимаясь вверх по иерархии к значению текущего
        // компонента добавляем значения вложенных
        this.$emit("input", [this.computedSelected, ...value]);
      },
    },

    // Этот компьютед отвечает за изменение селекта
    computedSelected: {
      get() {
        return this.computedValue?.[this.position] ?? null;
      },

      set(value) {
        this.$emit("input", [value]);
      },
    },

    nextLevelOptions() {
      return this.computedSelected?.options;
    },
  },
};
</script>

<style lang="scss">
.display-flex {
  display: flex;
}
</style>

ReusableSelect.vue

Компонент селекта.

<template>
  <select v-model="computedValue">
    <slot>
      <option :value="null" selected hidden disabled>Выберите из списка</option>
      <option :value="option" :key="index" v-for="(option, index) in options">
        <slot name="option" :option="option" :index="index">
          {{ getLabel(option) }}
        </slot>
      </option>
    </slot>
  </select>
</template>

<script>
export default {
  name: "ReusableSelect",
  props: {
    value: {
      type: null,
      required: true,
    },

    label: {
      type: String,
      default: "label",
    },

    options: {
      type: Array,
      required: true,
    },
  },

  computed: {
    computedValue: {
      get() {
        return this.value;
      },

      set(value) {
        this.$emit("input", value);
      },
    },
  },

  methods: {
    // Это не обязательный метод, он нужен что бы мы
    // могли использовать option chaining (?.) и
    // nullish assignment (??) в шаблоне.
    getLabel(option) {
      return option?.[this.label] ?? option;
    },
  },
};
</script>

<style>
</style>

Edit recursive-selection

→ Ссылка