Как фильтровать поиском вложенную струкутуру компонента дерева?

Поиск по группам только работает по пользователям не работает Пример кода можно еще здесь посмотреть

### UsersTree.vue ###



<template>
    <div class="d-flex flex-column">
        <div class="treeview-title">
            <div class="inner-treeview-title">Группы пользователей</div>
        </div>
            <loading-animation  v-if="statusLoading"/>
            <tree-view-item-users v-else
                              :nodes="treeGroupsUsers"
                              class="tree"

            />
    </div>
</template>
<script>
   import TreeViewItemUsers from "./Items/TreeViewItemUsers";

    export default {
        name: 'UserTree',
        components:{
            TreeViewItemUsers,
        },

        props: {
          group: String,
          statusLoading: Boolean
        },

        data() {
            return {
              treeGroupsUsers = [
                  {
                      "uuid": "86585e18",
                      "name": "Группа 1",
                      "users": [
                          {
                              "uuid": "6d69158b",
                              "name": "Наумов Виктор",
                          }
                      ]
                  },
                  {
                      "uuid": "99033184",
                      "name": "Группа 2",
                      "users": [
                          {
                              "uuid": "14cf75e9",
                              "name": "Иванов Виталий",
                          },
                          {
                              "uuid": "36a2b701",
                              "name": "Петров Петр",
                          },
                          {
                              "uuid": "e86bda49",
                              "name": "Сидоров Иван",
                          }
                      ]
                  }, 
              ]

        }

    },

}


</script>

<style lang="scss" scoped>

</style>



TreeViewItemUsers.vue



  <template>
      <div>
          <div :class="{'tree-view__root': isRoot, 'tree-view__node': isNode}">
              <template v-if="isRoot">
                  <div class="pl-4 pr-4 mb-3">
                      <b-input class="search-control"
                              v-model="search"
                              placeholder="Найти"
                      />
                  </div>
                  <div class="scroll-content">
                      <vuescroll :ops="ops">
                          <div is="tree-view-item-users" class="l1"
                              v-for="node in searchResultGroup"
                              :key="node.uuid"
                              :node="node"
                          ></div>
                      </vuescroll>
                  </div>

              </template>
              <template v-else-if="isNode">
                  <div class="node-name">
                  <span class="toggle">
                          <i v-if="childrenExists"
                            class="fas"
                            :class="{
                                  'fa-minus': isOpen && !isLoading,
                                  'fa-plus': !isOpen || isLoading
                              }"
                            @click="isOpen=!isOpen"
                            :title="(isOpen ? 'Скрыть' : 'Показать') + ' вложенный список'"
                          ></i>
                  </span>
                      <div v-if="isUsers"
                          class="tree-users-item user d-flex flex-grow-1"
                          @click="selectNode">
                          <router-link class="tree-view-link"
                                      :to="'/users/user/' + node.uuid">
                              <span class="label-name"
                                    :group-uuid="node.uuid"
                                    :class="{'tree-view__bold': isSelected}"
                              >
                                  {{ node.name }}
                              </span>
                          </router-link>
                      </div>
                      <div v-else class="tree-users-item has-child d-flex flex-grow-1">
                          <router-link class="tree-view-link"
                                      :to="'/users/group/' + node.uuid">
                              <span class="label-name">
                                  {{ node.name }}
                              </span>
                          </router-link>
                      </div>

                  </div>
                  <div v-show="isOpen" v-if="hasChildren" class="nodes-child">
                      <tree-view-item-users v-for="child in searchResultUser"
                                            :key="child.uuid"
                                            :node="child"
                                            :group-uuid="node.uuid"
                                            ref="users"
                                            class="node-child-tree"
                                            @selected-node="$emit('select-node', $event)"
                      />
                  </div>
              </template>
          </div>
      </div>
  </template>
  <script>
      import EventBus from "@/components/system/event-bus";
      import AE from 'vue-active-element';
      import vuescroll from 'vuescroll'


      export default {
          name: 'tree-view-item-users',
          components: {
              vuescroll
          },
          props: {
              node: {
                  type: Object,
              },
              nodes: {
                  type: Array,
                  default: null
              },
              groupUuid: String,
              filterterm: String

          },
          directives: {isclick: AE.directive},
          data() {
              return {
                  showChildren: true,
                  isOpen: false,
                  isLoading: false,
                  selectedNodeItem: null,
                  isSelectedItem: false,
                  selectItemTree: undefined,
                  search: '',
                  operation: {
                      rail: {
                          size: '20px'
                      },
                      bar: {
                          size: '15px'
                      }
                  },
                  ops: {
                      rail: {
                          opacity: '0',
                          background: '#585d68',
                          border: 'transparent',
                          size: '3px'
                      },
                      bar: {
                          background: 'rgba(88,96,101,0.8)',
                          keepShow: false,
                          size: '10px',
                          minSize: 0.3
                      },

                  }
              }
          },
          computed: {
              isRoot() {
                  return typeof this.nodes == 'object' && this.nodes !== null;
              },
              isNode() {
                  return Boolean(!this.isRoot && typeof this.node == 'object' && this.node);
              },

              isUsers() {
                  return Boolean(!Array.isArray(this.node.users))
              },

              isSelected() {
                  return this.isNode && this.value === this.node.uuid;
              },

              childrenExists() {
                  if (!this.node) {
                      return false;
                  }
                  if (this.canLoadChildren) {
                      return true;
                  }

                  return this.hasChildren;
              },
              hasChildren() {
                  return Boolean(
                      this.node
                      && Array.isArray(this.node.users)
                      && this.node.users.length > 0
                  );
              },

              canLoadChildren() {
                  return Boolean(
                      this.node
                      && Number.isInteger(this.node.users) && this.node.users > 0
                  );
              },

              searchResultGroup () {
                  // по группам ищет
                  return this.nodes.filter(item => item.name.indexOf(this.search) !== -1);
              },

              searchResultUser () {
                      return this.node.users.filter(item => item.name.indexOf(this.search) === 0)

              },



          },
          methods: {
              itemNode(id) {
                  this.selectedNodeItem = id;
              },
              selectNode() {
                  if (!this.node.users) {
                      EventBus.$emit('select-node', this.node.uuid);
                  } else {
                      EventBus.$emit('select-group', {
                          uuid: this.node.uuid,
                          name: this.node.name
                      });
                  }

                  if (this.isSelected && this.isOpen) {
                      return true;
                  }

                  this.updateValue();

              },


              toggleChildren() {
                  this.isOpen = !this.isOpen;

              },


              updateValue(value = null) {
                  this.value = arguments.length || !this.node ? value : this.node.uuid;

                  if (this.$parent && (this.$parent.isNode ||
                      this.$parent.isRoot) &&
                      typeof this.$parent.updateValue === 'function') {
                      this.$parent.updateValue(this.value);
                  }

              },
          }
      }
  </script>

  <style scoped>
      .toggle {
          cursor: pointer;
          width: 10px;
          position: absolute;
          display: block;
          left: 20px;
      }

      .node-name {
          user-select: none;
      }

      .tree-view__node > .node-name {
          /* padding: 0 10px 0 20px;*/
          font-size: 14px;
          font-weight: 600;
          text-transform: capitalize;
          display: flex;
          align-items: center;
          position: relative;
      }

      .tree-view__node > .node-name > .label-name {
          display: block;
          width: 100%;
          padding-top: 10px;
          padding-bottom: 10px;
          padding-left: 10px;
      }

      .tree-view__root > .tree-view__node > .nodes-child > .tree-view__node .node-name {
          /*padding: 0 20px 0 48px;*/
          cursor: pointer;
          font-weight: 400;
          font-size: 14px;
      }

      .node-name {
          cursor: pointer
      }

      .node-name > svg {
          cursor: pointer;
          margin-right: 5px;
      }

      .node-name > .label-name {
          cursor: pointer;
      }

      li.node-tree {
          list-style: none;
      }

      .node-name.active-node {
          background-color: #292c32 !important;
      }

      .selected {
          background-color: #292c32 !important;
      }

      .active {
          background-color: #292c32 !important;
      }

      .label-name {
          text-transform: none;
      }
      .scroll-content {
          height: 662px;
      }
      .search-control {
          border: 1px solid #202225;
      }

  </style>

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

Автор решения: Sergey

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

Во-первых очень странно пытаться одновременно фильтровать и группы и пользователей. Ведь группа будет "вычеркнута" вместе со всеми своими пользователями, даже если они удовлетворяют условию фильтра. Нужен более хитрый алгоритм.

Во-вторых для фильтрации групп используется поле search корневого элемента (для которого предусмотрен ввод).
А пользователи-то фильтруются полем search, компонента, который в "глубине" дерева, которое никогда не меняется, всегда пустая строка. Поэтому все пользователи всегда удовлетворяют условию фильтра.
Можно как-то (через props или другим способом или использовать store) передавать корневой search вверх/вниз по дереву.

П.С. canLoadChildren действительно неправильная. Но по счастливому стечению обстоятельств её отрицательный результат не используется:)

П.С.2 Так как же всё таки фильтровать? Для начала надо чётко определиться, что же должно получится в итоге.
Фильтровать только пользователей, показывая все группы. Для этого и данный образчик можно приспособить, решив проблему с search.
Или более сложный вариант, когда из дерева удаляются любые узлы, но такие, что сами и их дочерние узлы до любой глубины не удовлетворяют условию фильтра. Здесь наверное понадобится обход дерева туда-обратно, либо делать как бы "подстриженную" копию, чтобы не возвращаться обратно.

→ Ссылка