javascript - 如何在 Vuetify 自动完成组件中创建无限滚动?

标签 javascript vue.js vuetify.js

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

我在用户输入事件上使用惰性列表加载编写了一些代码。但现在我想添加在用户滚动事件 上加载此列表的功能。

例如,默认情况下有一个包含 100 个 vendor 的列表。用户滚动此列表直到结束,然后调用“某些事件”并加载下 100 个 vendor 。然后用户继续滚动并重复操作。

是否可以使用 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>

最佳答案

我能够使用 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>

Before scrolling down

After scrolling down

关于javascript - 如何在 Vuetify 自动完成组件中创建无限滚动?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56492851/

相关文章:

javascript - 按部分路径过滤对象

javascript - Vue js 在输入字段中对 v-model 应用过滤器

laravel - Vue JS 和 Laravel 在同一台服务器上但端口不同(SSL 问题)

vue.js - Vuetify 自动完成显示 [object Object]

javascript - Node.js Promise 引用错误

javascript - 在图像后面旋转两个图像

vue.js - axios 下载带有原始正确扩展名的文件

vue.js - Vuetify 删除 v-data-table 上的分页

javascript - 为什么 Vuetify 数据表没有正确更新?

javascript - Switch 语句在 javascript 中无法正常工作