【问题标题】:How to create infinite scroll in Vuetify Autocomplete component?如何在 Vuetify 自动完成组件中创建无限滚动?
【发布时间】:2019-10-22 20:59:08
【问题描述】:

我有一个带有 Vuetify 自动完成组件的页面,以及带有 '/vendors' 方法的 REST API 后端。此方法接受 limitpagename 参数并返回带有 idname 的 JSON > 字段。

我在用户输入事件上做了一些延迟列表加载的代码。但现在我想添加在用户 滚动事件 上加载此列表的功能。

例如,默认情况下有 100 个供应商的列表。用户滚动此列表直到最后,然后调用“某个事件”并加载下 100 个供应商。然后用户继续滚动并重复操作。

是否可以使用 Vuetify 自动完成组件来实现,或者我应该使用其他库吗?

当前组件示例代码如下:

<template>
  <v-autocomplete
          :items="vendors"
          v-model="selectedVendorId"
          item-text="name"
          item-value="id"
          label="Select a vendor"
          @input.native="getVendorsFromApi"
  ></v-autocomplete>
</template>

<script>
  export default {
    data () {
      return {
        page: 0,
        limit: 100,
        selectedVendorId: null,
        vendors: [],
        loading: true
      }
    },
    created: function (){
      this.getVendorsFromApi();
    },
    methods: {
      getVendorsFromApi (event) {
        return new Promise(() => {
          this.$axios.get(this.$backendLink 
                  + '/vendors?limit=' + this.limit 
                  + '&page=' + this.page 
                  + '&name=' + (event ? event.target.value : ''))
            .then(response => {
              this.vendors = response.data;
            })
        })
      }
    }
  }
</script>

【问题讨论】:

    标签: javascript vue.js vuetify.js


    【解决方案1】:

    2021 年 6 月 12 日更新:

    如果您使用的是 Vuetify 2.X,请根据 append-item 插槽和 v-intersect 指令使用 Brettins' solution


    旧答案:

    看起来默认的 v-autocomplete 组件是不可能的(至少在 vuetify 1.5.16 或更低版本中)。 提供最相似功能的组件是VueInfiniteAutocomplete

    但请记住,在这种情况下,样式、验证等可能存在问题。

    这个库有一个例子。

    <template>
        <div>
        <vue-infinite-autocomplete
          :data-source="getAsyncOptions"
          :fetch-size="limit"
          v-on:select="handleOnSelect"
          :value="autocompleteViewValue"
        >
        </vue-infinite-autocomplete>
      </div>
    </template>
    <script>
      export default {
        data () {
          return {
              selectedVendorId : null,
              limit: 100,
              autocompleteViewValue: null
          }
        },
        methods: {
            getAsyncOptions(text, page, fetchSize) {
                return new Promise((resolve, reject) => {
                    resolve(
                        this.$axios.get(this.$backendLink
                            + '/vendors?limit=' + fetchSize
                            + '&page=' + page
                            + '&name=' + text)
                            .then(response => {
                                //Response MUST contain 'id' and 'text' fields, and nothing else.
                                //If there are other fields, you should remove it here
                                //and create 'id' and 'text' fields in response JSON by yourself
                                return response.data;
                            })
                    )
                });
            },
    
            handleOnSelect(selectedItem) {
                this.autocompleteViewValue = selectedItem.text;
                this.selectedVendorId = selectedItem.id;
            }
        }
      }
    </script>
    

    PS:如果您只想使用 v-autocomplete 组件和服务器端分页,您可以使用 append-item 插槽创建一个“加载更多...”按钮,如 @ 中所建议的那样987654323@.

    【讨论】:

    • 有同样的问题。 Vuetify 现在可以做到这一点吗?
    • @timebandit 不幸的是,即使在最新的 vuetify 2.2.12 中也没有这样的机会。
    【解决方案2】:

    我能够使用 Vuetify AutoComplete 组件进行自动加载。这有点 hack,但基本上解决方案是使用 v-slot 附加项,v-intersect 指令来检测该附加项是否可见,如果是,则调用您的 API 以获取更多项并附加它到您当前的列表。

      <v-autocomplete
              :items="vendors"
              v-model="selectedVendorId"
              item-text="name"
              item-value="id"
              label="Select a vendor"
              @input.native="getVendorsFromApi"
      >
      <template v-slot:append-item>
        <div v-intersect="endIntersect" />
      </template>
    </v-autocomplete>
    
    
    

    ...

    export default {
      methods: {
        endIntersect(entries, observer, isIntersecting) {
          if (isIntersecting) {
            let moreVendors = loadMoreFromApi()
            this.vendors = [ ...this.vendors, ...moreVendors]
          }
        }
      }
    }
    

    在我的用例中,我使用 API 平台作为后端,使用 GraphQL 使用游标进行分页。

       <component
          v-bind:is="canAdd ? 'v-combobox' : 'v-autocomplete'"
          v-model="user"
          :items="computedUsers"
          :search-input.sync="search"
          item-text="item.node.userProfile.username"
          hide-details
          rounded
          solo
          :filter="
          (item, queryText, itemText) => { 
            return item.node.userProfile.username.toLocaleLowerCase().indexOf(queryText.toLocaleLowerCase()) > -1
            } "
          :loading="loading"
          item-value="username"
          class="text-left pl-1"
          color="blue-grey lighten-2"
          :label="label"
        >
          <template v-slot:selection="{ item }">
            <v-chip v-if="typeof item == 'object'">
              <v-avatar left>
                <v-img v-if="item.node.userProfile.image" :src="item.node.userProfile.image" />
                <v-icon v-else>mdi-account-circle</v-icon>
              </v-avatar>
              {{ item.node.userProfile.firstName }} {{ item.node.userProfile.lastName }}
            </v-chip>
            <v-chip v-else-if="typeof item == 'string'">
              {{ item }}
            </v-chip>
          </template>
          <template v-slot:item="{ item: { node } }">
            <v-list-item-avatar >
              <img v-if="node.userProfile.avatar" :src="node.userProfile.avatar" />
              <v-icon v-else>mdi-account-circle</v-icon>
            </v-list-item-avatar>
            <v-list-item-content class="text-left">
              <v-list-item-title>
                {{ $t('fullName', { firstName: node.userProfile.firstName, lastName: node.userProfile.lastName } )}}
              </v-list-item-title>
              <v-list-item-subtitle v-html="node.userProfile.username"></v-list-item-subtitle>
            </v-list-item-content>
          </template>
          <template v-slot:append-item="">
            <div v-intersect="endIntersect" >
            </div>
          </template>
        </component>
    import { VCombobox, VAutocomplete } from "vuetify/lib";
    import debounce from "@/helpers/debounce"
    import { SEARCH_USER_BY_USERNAME } from "@/graphql/UserQueries";
    const RESULTS_TO_SHOW = 5
    export default {
      props: {
        canAdd: {
          type: Boolean,
          default: false,
        },
        value: [Object, String],
        label: String,
      },
      components: { VCombobox, VAutocomplete },
      apollo: {
        users: {
          query: SEARCH_USER_BY_USERNAME,
          variables() { 
            return  {
              username: this.search,
              numberToShow: RESULTS_TO_SHOW,
              cursor: null,
            }
          },
          watchLoading(isLoading) {
            this.loading = isLoading
          },
          skip() {
            if (this.search) {
              return !(this.search.length > 1)
            }
            return true
          },
        },
      },
      data() {
        return {
          user: this.value,
          search: "",
          cursor: null,
          loading: false,
        };
      },
      watch: {
        user(newValue) {
          let emit = newValue
          if (newValue) {
            emit = newValue.node
          }
          this.$emit("input", emit);
        },
        value(newValue) {
          if (this.user && this.user.node != newValue) {
            if (newValue == null) {
              this.user = null
            }
            else {
              this.user =  { node: newValue };
            }
          }
        },
        search(newValue) {
          this.debouncedSearch(newValue)
        },
      },
      methods: {
        endIntersect(entries, observer, isIntersecting) {
          if (isIntersecting && this.users && this.users.pageInfo.hasNextPage) {
            let cursor = this.users.pageInfo.endCursor
            
            this.$apollo.queries.users.fetchMore({
              variables: { cursor: cursor},
              updateQuery: (previousResult, { fetchMoreResult }) => {
                let edges = [
                  ...previousResult.users.edges,
                  ...fetchMoreResult.users.edges,
                ]
    
                let pageInfo = fetchMoreResult.users.pageInfo;
                return { 
                  users: {
                    edges: edges,
                    pageInfo: pageInfo,
                    __typename: previousResult.users.__typename,
                  }
                }
              }
            })
          }
        },
        debouncedSearch: debounce(function (search) {
          if (this.users) {
            this.$apollo.queries.users.refetch({
              username: search,
              numberToShow: RESULTS_TO_SHOW, 
              cursor: null,
            });
          }
        }, 500),
        filter(item, queryText) {
          return item.node.userProfile.username.toLocaleLowerCase().indexOf(queryText.toLocaleLowerCase()) > -1
        }
      },
      computed: {
        computedUsers() {
          if (this.users){
            return this.users.edges
          }
          return []
        },
        skip() {
          if (this.search) {
            return this.search.length > 1
          }
          return false
        }
      }
    };
    </script>
    

    【讨论】:

    • 终于花时间测试了这个。真的很好的解决方案。非常感谢!
    • 如何将初始值设置为 v-autocomplete?我尝试为 this.search 设置值,但 .sync 修饰符似乎清除了它。
    • @KDani 最好将这个问题作为新的 Stack Overflow 帖子提出。
    猜你喜欢
    • 2021-08-28
    • 2019-08-19
    • 2021-05-31
    • 2019-08-22
    • 2018-12-19
    • 1970-01-01
    • 1970-01-01
    • 2017-08-28
    • 2020-06-07
    相关资源
    最近更新 更多