TinyVue Grid 表格 fetchData 完全指南:从入门到精通

TinyVue Grid 表格 fetchData 完全指南:从入门到精通

前言

TinyVue 的 Grid 组件是企业级后台管理系统中最常用的数据展示组件之一。在处理大量数据时,我们通常需要从后端分页获取数据,而不是一次性加载所有数据到前端。TinyVue Grid 提供了两种优雅的方式来处理远程数据加载:fetchDataproxyConfig

本文将详细介绍 fetchData 的使用方式,涵盖基础配置、分页、排序、筛选、手动刷新等核心场景,并附带完整的代码示例。

基础概念

TinyVue Grid 组件基于 vxe-table 封装,在保留原生 vxe-table 全部能力的同时,提供了更简洁的 fetchData API。fetchData 本质上是对 vxe-table proxy-config.ajax.query 的高层封装,让你能以更符合 Vue 开发习惯的方式来配置远程数据获取。

一、最简示例:自动加载数据

最基本的用法是配置 fetchDataapi 属性,指向你的异步数据获取方法。Grid 会在挂载时自动调用该方法加载数据。

vue 复制代码
<template>
  <tiny-grid :fetch-data="fetchData">
    <tiny-grid-column type="index" width="60"></tiny-grid-column>
    <tiny-grid-column field="name" title="名称"></tiny-grid-column>
    <tiny-grid-column field="area" title="所属区域"></tiny-grid-column>
    <tiny-grid-column field="address" title="地址"></tiny-grid-column>
    <tiny-grid-column field="introduction" title="公司简介" show-overflow></tiny-grid-column>
  </tiny-grid>
</template>

<script>
import { TinyGrid, TinyGridColumn } from '@opentiny/vue'

export default {
  components: { TinyGrid, TinyGridColumn },
  data() {
    return {
      fetchData: {
        api: this.getData
      }
    }
  },
  methods: {
    getData() {
      return new Promise((resolve) => {
        // 这里替换成你的真实接口调用
        fetch('/api/company/list')
          .then(res => res.json())
          .then(data => {
            resolve({
              result: data.list,
              page: { total: data.total }
            })
          })
      })
    }
  }
}
</script>

返回数据结构说明:

getData 方法必须返回一个 Promise,resolve 的对象需遵循以下格式:

typescript 复制代码
{
  result: Array<RowVO>,  // 当前页数据列表(必需)
  page: {
    total: number        // 总记录数(启用分页时必需)
  }
}

二、禁用自动加载:auto-load

如果不想在组件挂载时自动加载数据(比如需要条件筛选后才加载),可以设置 auto-loadfalse

vue 复制代码
<tiny-grid :auto-load="false" :fetch-data="fetchData">
  <!-- columns... -->
</tiny-grid>

设置为 false 后,需要手动调用 Grid 的 handleFetch() 方法来触发数据加载:

javascript 复制代码
this.$refs.grid.handleFetch()

三、分页加载

分页是远程数据加载最常见的场景。TinyVue Grid 通过 pagerConfig 配置分页器,fetchData 的回调会自动接收到页码和每页条数。

vue 复制代码
<template>
  <tiny-grid :fetch-data="fetchData" :pager="pagerConfig">
    <tiny-grid-column type="index" width="60"></tiny-grid-column>
    <tiny-grid-column field="name" title="名称"></tiny-grid-column>
    <tiny-grid-column field="area" title="所属区域"></tiny-grid-column>
    <tiny-grid-column field="address" title="地址"></tiny-grid-column>
    <tiny-grid-column field="introduction" title="公司简介" show-overflow></tiny-grid-column>
  </tiny-grid>
</template>

<script>
import { TinyGrid, TinyGridColumn, TinyPager } from '@opentiny/vue'

export default {
  components: { TinyGrid, TinyGridColumn },
  data() {
    return {
      pagerConfig: {
        component: TinyPager,
        attrs: {
          currentPage: 1,
          pageSize: 5,
          pageSizes: [5, 10, 20],
          total: 0,
          layout: 'total, sizes, prev, pager, next, jumper'
        }
      },
      fetchData: {
        api: this.getData
      }
    }
  },
  methods: {
    getData({ page }) {
      // page 对象包含 currentPage 和 pageSize
      let curPage = page.currentPage
      let pageSize = page.pageSize

      return new Promise((resolve) => {
        fetch(`/api/company/list?page=${curPage}&pageSize=${pageSize}`)
          .then(res => res.json())
          .then(data => {
            resolve({
              result: data.list,
              page: { total: data.total }
            })
          })
      })
    }
  }
}
</script>

pagerConfig 配置详解

属性 类型 说明
component Component 分页器组件,使用 TinyPager
attrs.currentPage Number 当前页码,默认 1
attrs.pageSize Number 每页条数,默认 5
attrs.pageSizes Number[] 可选的每页条数,如 [5, 10, 20]
attrs.total Number 总记录数,由服务端动态赋值
attrs.layout String 分页器布局,支持 total, sizes, prev, pager, next, jumper

当用户切换页码或每页条数时,Grid 会自动重新调用 getData 方法,并传入更新后的 page 参数。

四、排序与预排序

4.1 初始排序(prefetchArgs)

使用 prefetchArgs 可以在首次加载数据时指定排序规则:

vue 复制代码
<tiny-grid :fetch-data="fetchData" :prefetch="prefetchArgs">
  <!-- columns... -->
</tiny-grid>
javascript 复制代码
data() {
  return {
    prefetchArgs: [
      { property: 'name', sort: 'desc' }
    ],
    fetchData: {
      api: this.getData
    }
  }
},
methods: {
  getData({ page, sortBy }) {
    // sortBy 包含当前的排序信息
    let curPage = page.currentPage
    let pageSize = page.pageSize

    return new Promise((resolve) => {
      fetch(`/api/company/list?page=${curPage}&pageSize=${pageSize}&sortBy=${sortBy}`)
        .then(res => res.json())
        .then(data => {
          resolve({
            result: data.list,
            page: { total: data.total }
          })
        })
    })
  }
}

4.2 动态排序

用户点击列头排序时,Grid 会自动触发数据刷新,getDatasortBy 参数会自动更新。

五、数据筛选

筛选是最常见的交互需求之一。TinyVue Grid 支持通过动态修改 fetchData.args 来实现带筛选条件的数据加载。

vue 复制代码
<template>
  <div>
    <!-- 筛选按钮 -->
    <tiny-button @click="filterData('华南区')"> 筛选华南区 </tiny-button>
    <tiny-button @click="filterData('华东区')"> 筛选华东区 </tiny-button>
    <tiny-button @click="filterData('')"> 全部数据 </tiny-button>

    <tiny-grid ref="grid" :fetch-data="fetchData" :pager="pagerConfig">
      <tiny-grid-column type="index" width="60"></tiny-grid-column>
      <tiny-grid-column field="name" title="名称"></tiny-grid-column>
      <tiny-grid-column field="area" title="所属区域"></tiny-grid-column>
      <tiny-grid-column field="address" title="地址"></tiny-grid-column>
      <tiny-grid-column field="introduction" title="公司简介" show-overflow></tiny-grid-column>
    </tiny-grid>
  </div>
</template>

<script>
import { TinyGrid, TinyGridColumn, TinyPager, TinyButton } from '@opentiny/vue'

export default {
  components: { TinyGrid, TinyGridColumn, TinyButton },
  data() {
    return {
      pagerConfig: {
        component: TinyPager,
        attrs: {
          currentPage: 1,
          pageSize: 5,
          pageSizes: [5, 10],
          total: 0,
          layout: 'total, sizes, prev, pager, next, jumper'
        }
      },
      fetchData: {
        api: this.getData
      }
    }
  },
  methods: {
    getData({ page, filterArgs }) {
      let curPage = page.currentPage
      let pageSize = page.pageSize

      return new Promise((resolve) => {
        const params = { page: curPage, pageSize }
        if (filterArgs) {
          params.area = filterArgs
        }
        fetch(`/api/company/list?${new URLSearchParams(params)}`)
          .then(res => res.json())
          .then(data => {
            resolve({
              result: data.list,
              page: { total: data.total }
            })
          })
      })
    },
    filterData(area) {
      // 动态设置筛选参数
      this.fetchData.args = { filterArgs: area }
      // 手动触发数据刷新
      this.$refs.grid.handleFetch()
    }
  }
}
</script>

关键步骤:

  1. 在筛选事件中,动态赋值 this.fetchData.args 对象
  2. 调用 this.$refs.grid.handleFetch() 手动触发数据重载
  3. getData 方法通过第二个参数 filterArgs 获取当前的筛选条件

六、getData 回调参数完整说明

fetchData.api 指向的方法接收一个参数对象,包含以下字段:

参数 类型 说明
page Object 分页信息
page.currentPage Number 当前页码
page.pageSize Number 每页条数
filterArgs any 通过 fetchData.args 传入的筛选参数
sortBy Object/Array 当前排序信息

七、手动刷新与重新加载

除了筛选场景外,在以下场景中你也可能需要手动触发数据刷新:

  • 新增 / 编辑 / 删除数据后
  • 切换 Tab 后
  • 收到 WebSocket 实时通知后
  • 父组件状态变化后
javascript 复制代码
// 方式一:重新加载(保持当前分页状态)
this.$refs.grid.handleFetch()

// 方式二:重置到第一页并重新加载
this.fetchData.args = { filterArgs: newValue }
this.$refs.grid.handleFetch()

八、进阶:使用 proxyConfig(vxe-table 原生方式)

TinyVue Grid 同时也完全兼容 vxe-table 的 proxyConfig 配置方式。如果你需要更细粒度的控制(如自定义 loading 状态、响应字段映射),可以使用这种方式:

vue 复制代码
<template>
  <tiny-grid v-bind="gridOptions">
    <tiny-grid-column type="index" width="60"></tiny-grid-column>
    <tiny-grid-column field="name" title="名称"></tiny-grid-column>
    <tiny-grid-column field="area" title="所属区域"></tiny-grid-column>
    <tiny-grid-column field="address" title="地址" show-overflow></tiny-grid-column>
  </tiny-grid>
</template>

<script>
import { reactive } from 'vue'

export default {
  setup() {
    const gridOptions = reactive({
      border: true,
      height: 500,
      pagerConfig: {},  // 启用分页
      proxyConfig: {
        // 关闭 loading(默认 true)
        showLoading: false,
        // 响应字段映射(当后端返回的字段名不同时使用)
        response: {
          result: 'data.list',      // 后端返回数据的路径
          total: 'data.totalCount'  // 后端返回总数的路径
        },
        ajax: {
          query: ({ page, sorts, filters }) => {
            return fetch(`/api/list?page=${page.currentPage}&pageSize=${page.pageSize}`)
              .then(res => res.json())
          }
        }
      },
      columns: [
        { type: 'seq', width: 70 },
        { field: 'name', title: 'Name' },
        { field: 'area', title: 'Area' },
        { field: 'address', title: 'Address', showOverflow: true }
      ]
    })

    return { gridOptions }
  }
}
</script>

proxyConfig 配置项

配置项 类型 说明
showLoading Boolean 是否显示加载中状态,默认 true
response.result String 响应结果列表字段的路径,支持点号嵌套(如 'data.list'
response.total String 响应总数字段的路径,支持点号嵌套(如 'data.totalCount'
ajax.query Function 查询接口函数,接收 { page, sorts, filters },返回 Promise

九、fetchData vs proxyConfig 对比

特性 fetchData proxyConfig
API 风格 TinyVue 封装风格 vxe-table 原生风格
配置复杂度 简洁 灵活
响应字段映射 固定 result / page.total 支持自定义路径映射
Loading 状态 自动控制 可配置 showLoading
分页参数 通过 pagerConfig 单独配置 一体化配置
筛选/排序 通过 args 手动传递 内置 sorts / filters 参数
适用场景 简单列表页,快速开发 复杂场景,需要精细控制的场景

十、实战:完整的 CRUD 列表页示例

以下是整合了查询、分页、新增、编辑、删除功能的完整示例:

vue 复制代码
<template>
  <div class="list-container">
    <!-- 搜索区域 -->
    <div class="search-area">
      <tiny-input v-model="searchName" placeholder="请输入名称" style="width: 200px"></tiny-input>
      <tiny-select v-model="searchArea" placeholder="请选择区域" style="width: 150px; margin-left: 10px">
        <tiny-option label="全部" value=""></tiny-option>
        <tiny-option label="华南区" value="华南区"></tiny-option>
        <tiny-option label="华东区" value="华东区"></tiny-option>
        <tiny-option label="华北区" value="华北区"></tiny-option>
      </tiny-select>
      <tiny-button type="primary" @click="handleSearch" style="margin-left: 10px">查询</tiny-button>
      <tiny-button type="success" @click="handleAdd" style="margin-left: 10px">新增</tiny-button>
    </div>

    <!-- 表格区域 -->
    <tiny-grid ref="grid" :fetch-data="fetchData" :pager="pagerConfig" style="margin-top: 16px">
      <tiny-grid-column type="index" width="60" title="序号"></tiny-grid-column>
      <tiny-grid-column field="name" title="名称" sortable></tiny-grid-column>
      <tiny-grid-column field="area" title="所属区域"></tiny-grid-column>
      <tiny-grid-column field="address" title="地址" show-overflow></tiny-grid-column>
      <tiny-grid-column field="introduction" title="公司简介" show-overflow></tiny-grid-column>
      <tiny-grid-column title="操作" width="180">
        <template #default="{ row }">
          <tiny-button type="text" size="small" @click="handleEdit(row)">编辑</tiny-button>
          <tiny-button type="text" size="small" style="color: red" @click="handleDelete(row)">删除</tiny-button>
        </template>
      </tiny-grid-column>
    </tiny-grid>

    <!-- 新增/编辑弹窗 -->
    <tiny-dialog :visible="dialogVisible" :title="dialogTitle" @close="dialogVisible = false">
      <tiny-form :model="formData" label-width="80px">
        <tiny-form-item label="名称">
          <tiny-input v-model="formData.name"></tiny-input>
        </tiny-form-item>
        <tiny-form-item label="区域">
          <tiny-select v-model="formData.area">
            <tiny-option label="华南区" value="华南区"></tiny-option>
            <tiny-option label="华东区" value="华东区"></tiny-option>
            <tiny-option label="华北区" value="华北区"></tiny-option>
          </tiny-select>
        </tiny-form-item>
        <tiny-form-item label="地址">
          <tiny-input v-model="formData.address"></tiny-input>
        </tiny-form-item>
        <tiny-form-item label="简介">
          <tiny-input v-model="formData.introduction" type="textarea"></tiny-input>
        </tiny-form-item>
      </tiny-form>
      <template #footer>
        <tiny-button @click="dialogVisible = false">取消</tiny-button>
        <tiny-button type="primary" @click="handleSubmit">确定</tiny-button>
      </template>
    </tiny-dialog>
  </div>
</template>

<script>
import {
  TinyGrid, TinyGridColumn, TinyPager, TinyButton,
  TinyInput, TinySelect, TinyOption, TinyDialog, TinyForm, TinyFormItem
} from '@opentiny/vue'

export default {
  components: {
    TinyGrid, TinyGridColumn, TinyButton,
    TinyInput, TinySelect, TinyOption, TinyDialog, TinyForm, TinyFormItem
  },
  data() {
    return {
      searchName: '',
      searchArea: '',
      dialogVisible: false,
      dialogTitle: '新增',
      isEdit: false,
      editId: null,
      formData: {
        name: '',
        area: '',
        address: '',
        introduction: ''
      },
      pagerConfig: {
        component: TinyPager,
        attrs: {
          currentPage: 1,
          pageSize: 10,
          pageSizes: [5, 10, 20, 50],
          total: 0,
          layout: 'total, sizes, prev, pager, next, jumper'
        }
      },
      fetchData: {
        api: this.getData
      }
    }
  },
  methods: {
    // 获取列表数据
    getData({ page }) {
      const params = {
        page: page.currentPage,
        pageSize: page.pageSize,
        name: this.searchName,
        area: this.searchArea
      }

      return fetch(`/api/company/list?${new URLSearchParams(params)}`)
        .then(res => res.json())
        .then(data => ({
          result: data.list,
          page: { total: data.total }
        }))
    },

    // 查询按钮
    handleSearch() {
      this.$refs.grid.handleFetch()
    },

    // 新增
    handleAdd() {
      this.dialogTitle = '新增'
      this.isEdit = false
      this.formData = { name: '', area: '', address: '', introduction: '' }
      this.dialogVisible = true
    },

    // 编辑
    handleEdit(row) {
      this.dialogTitle = '编辑'
      this.isEdit = true
      this.editId = row.id
      this.formData = { ...row }
      this.dialogVisible = true
    },

    // 删除
    handleDelete(row) {
      this.$confirm('确定要删除该条记录吗?').then(() => {
        fetch(`/api/company/delete/${row.id}`, { method: 'DELETE' })
          .then(() => {
            this.$message.success('删除成功')
            this.$refs.grid.handleFetch()  // 刷新表格
          })
      })
    },

    // 提交表单
    handleSubmit() {
      const url = this.isEdit ? `/api/company/update/${this.editId}` : '/api/company/add'
      const method = this.isEdit ? 'PUT' : 'POST'

      fetch(url, {
        method,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(this.formData)
      })
        .then(res => res.json())
        .then(() => {
          this.$message.success(this.isEdit ? '编辑成功' : '新增成功')
          this.dialogVisible = false
          this.$refs.grid.handleFetch()  // 刷新表格
        })
    }
  }
}
</script>

十一、常见问题与最佳实践

1. 数据不刷新?

确保:

  • getData 方法返回的是 Promise 对象
  • 返回格式正确:{ result: Array, page: { total: Number } }
  • 手动刷新时调用了 this.$refs.grid.handleFetch()

2. 分页器不显示?

确保:

  • pagerConfig 正确传入,且设置了 component: TinyPager
  • getData 返回值中正确设置了 page.total(total 为 0 时分页器不会渲染)

3. 如何使用 TypeScript?

typescript 复制代码
interface CompanyVO {
  id: number
  name: string
  area: string
  address: string
  introduction: string
}

interface FetchParams {
  page: {
    currentPage: number
    pageSize: number
  }
  filterArgs?: string
  sortBy?: { property: string; sort: 'asc' | 'desc' }
}

async getData({ page, filterArgs, sortBy }: FetchParams): Promise<{
  result: CompanyVO[]
  page: { total: number }
}> {
  const response = await fetch(`/api/company/list?page=${page.currentPage}&pageSize=${page.pageSize}`)
  const data = await response.json()
  return {
    result: data.list,
    page: { total: data.total }
  }
}

4. 首次加载不想要默认数据?

设置 :auto-load="false",然后在合适的时机手动调用 handleFetch()

5. 如何保存和恢复查询状态?

javascript 复制代码
// 保存查询参数
const savedQuery = {
  searchName: this.searchName,
  searchArea: this.searchArea,
  page: this.pagerConfig.attrs.currentPage
}

// 恢复查询参数
this.searchName = savedQuery.searchName
this.searchArea = savedQuery.searchArea
this.pagerConfig.attrs.currentPage = savedQuery.page
this.$refs.grid.handleFetch()

总结

TinyVue Grid 的 fetchData 提供了一个简洁且强大的远程数据加载方案。核心要点:

  • 通过 fetchData: { api: this.getData } 配置数据获取方法
  • getData 回调接收分页、排序、筛选参数,返回标准格式的 Promise
  • 结合 pagerConfig 实现分页,结合 fetchData.args 实现筛选
  • 通过 this.$refs.grid.handleFetch() 手动触发刷新
  • 复杂场景可退回到 proxyConfig 原生模式

掌握这些用法后,你可以轻松应对各种企业级表格数据管理场景。Happy coding! 🚀

相关推荐
kyriewen2 小时前
手写虚拟DOM后,我反问面试官:key为什么不能用index?
前端·react.js·面试
Doris_20232 小时前
说一说ESLint+Prettier生效的原理
前端·设计模式·架构
ZC跨境爬虫2 小时前
跟着 MDN 学CSS day_21:(图像溢出控制与表单元素样式定制)
前端·javascript·css·ui·交互
卷帘依旧2 小时前
微前端解决方案-qiankun
前端
moshuying2 小时前
你做的,比汇报出来的多得多
前端
shuye2162 小时前
google chrome 离线下载地址
前端·chrome
yqcoder3 小时前
闭包是什么?优缺点、怎么防内存泄漏?
前端·http
lichenyang4533 小时前
鸿蒙 ArkUI 组件基础复盘:从两个 UI 卡片回到 ComponentV2、状态管理和组件分层
前端
biubiubiu_LYQ3 小时前
萌新小白基础理解篇之 this 关键字
前端·javascript