# 使用指南

agel-table 面向 Vue 2 + Element UI 2 项目。它将表格配置集中到一个对象中,并在 Element UI 表格上增加分页请求、操作列、列配置、合并、自适应高度和虚拟滚动能力。

# 环境要求

  • Vue 2.x
  • Element UI 2.x

请先在宿主项目中安装并注册 Element UI。agel-table 不会替应用加载 Element UI 样式。

npm install agel-table

# 注册组件

在应用入口全局注册一次即可。传入的第二个参数是所有表格共享的默认配置;页面级配置优先于默认值。

import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import agelTable from 'agel-table'

Vue.use(ElementUI)
Vue.use(agelTable, {
  table: {
    border: true
  },
  page: {
    height: 45,
    pageSizes: [10, 20, 50, 100],
    layout: 'total, sizes, prev, pager, next, jumper'
  },
  menu: {
    width: 140
  }
})

Vue.use(agelTable, options) 会注册 <agel-table>,无需再手动调用 Vue.component。全局选项只提供默认值;例如 page.enable 仍需在某个表格上显式开启。

# 创建本地数据表格

v-model 绑定一个响应式对象。表格属性、列定义和数据都放在这个对象中。

<template>
  <agel-table v-model="table" />
</template>

<script>
export default {
  data() {
    return {
      table: {
        height: 320,
        columns: [
          { prop: 'name', label: '姓名', minWidth: 120 },
          { prop: 'department', label: '部门', width: 160 }
        ],
        data: [
          { name: '张三', department: '生产部' },
          { name: '李四', department: '安全部' }
        ]
      }
    }
  }
}
</script>

table.data 变化时,表格会响应更新。需要直接调用 Element UI 的表格方法时,使用 table.getRef() 获取内部 el-table 实例。

# 接入服务端数据和分页

在 table.page.enable 开启分页,并用三参数形式定义 request(query, done, fail)。分页或服务端排序变化时,组件会用最新查询参数调用 request;调用 done 回填当前页数据和总数。

下面的示例用定时器模拟服务端响应:

<template>
  <div>
    <el-input v-model="table.query.keyword" placeholder="姓名" />
    <el-button @click="search">查询</el-button>
    <agel-table v-model="table" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      table: {
        height: 360,
        query: { keyword: '' },
        page: { enable: true, pageSize: 10 },
        columns: [
          { prop: 'name', label: '姓名', sortable: 'custom' },
          { prop: 'department', label: '部门' }
        ],
        request: (query, done, fail) => {
          setTimeout(() => {
            const allRows = Array.from({ length: 37 }, (_, index) => ({
              name: '员工 ' + (index + 1),
              department: index % 2 === 0 ? '生产部' : '安全部'
            }))
            const filteredRows = allRows.filter((row) => row.name.includes(query.keyword))
            const sortedRows = query.orderColumn === 'name'
              ? filteredRows.slice().sort((left, right) => {
                  const result = left.name.localeCompare(right.name)
                  return query.order === 'descending' ? -result : result
                })
              : filteredRows
            const start = (query.currentPage - 1) * query.pageSize

            done({
              data: sortedRows.slice(start, start + query.pageSize),
              total: sortedRows.length
            })
          }, 200)
        }
      }
    }
  },
  mounted() {
    this.table.getData()
  },
  methods: {
    search() {
      this.table.getData({ currentPage: 1 })
    }
  }
}
</script>

默认查询字段为 currentPage、pageSize、orderColumn 和 order,可以通过 queryProps 映射到后端字段。分页组件负责展示页码和触发查询,不会在浏览器端自动切分完整数据;使用分页时,request 应返回当前页数据。

done 接收数组,或 { data, total }。失败时调用 fail(error);异步请求的拒绝也会结束 loading。table.getData() 仅在使用三参数请求代理时注入。请求协议和映射详情见 API 参考。

# 自定义单元格和表头

字符串形式的 slotColumn / slotHeader 对应 <agel-table> 上的具名作用域插槽:

<agel-table v-model="table">
  <template slot="status" slot-scope="{ row }">
    <el-tag :type="row.status === '正常' ? 'success' : 'warning'">
      {{ row.status }}
    </el-tag>
  </template>
</agel-table>

columns: [
  { prop: 'status', label: '状态', slotColumn: 'status' }
]

也可以直接传入渲染函数:slotColumn(h, scope) 和 slotHeader(h, scope)。完整示例包含展开行、自定义插槽和 render 函数:

查看示例源码
<template>
  <agel-table v-model="table">
    <template v-slot:dateHeader>
      <el-tag>模板自定义列-表头</el-tag>
    </template>
    <template v-slot:date="props">
      <el-input v-model="props.row.date"></el-input>
    </template>
    <template v-slot:expand="props">
      <div style="text-align:center">{{props.row.date}}=>template展开行内容</div>
    </template>
    <template v-slot:append>
      <p slot="append" style="text-align:center">最后一行 slot append...</p>
    </template>
  </agel-table>
</template>
 
<script>
export default {
  data() {
    return {
      table: {
        border: true,
        columns: [
          {
            label: "展开行",
            type: "expand",
            width: 80,
            slotColumn: "expand",
          },
          {
            minWidth: 200,
            slotColumn: (h, { row }) => {
              return <el-tag>{row.name}</el-tag>;
            },
            slotHeader: () => {
              return <el-tag>render函数自定义列-表头</el-tag>;
            },
          },
          {
            minWidth: 200,
            slotColumn: "date",
            slotHeader: "dateHeader",
          },
        ],
        data: [
          { date: "2016-05-02", name: "王小虎", address: "上海市" },
          { date: "2016-05-04", name: "王小虎", address: "上海市" },
        ],
      },
    };
  },
};
</script>

# 扩展能力示例

# 动态列显隐与嵌套表头

列支持 Element UI 的常用列属性,以及 display、children 等 agel-table 扩展项。display 可为布尔值或返回布尔值的函数。

# 操作列

设置 menu.enable 后,可以配置编辑、删除回调和自定义操作内容。菜单列默认追加到末尾,也可通过 insertIndex 指定插入位置。

查看请求代理与操作列示例源码
<template>
  <div class="demo">
    <p><code v-show="queryString">{{queryString}}</code></p>
    <p>
      <el-input v-model="table.query.name" style="width:100px;margin-right:10px;"></el-input>
      <el-button icon="el-icon-search" @click="onSearch">查询</el-button>
    </p>
    <agel-table v-model="table"></agel-table>
  </div>
</template>
 
<script>
export default {
  data() {
    return {
      table: {
        border: true,
        data: [],
        height:300,
        // 该对象放置table 对象的查询参数,默认有 currentPage,pageSize,orderColumn,order
        query: { name: "小虎" },
        // 默认排序列
        defaultSort: { prop: "date", order: "descending" },
        // 分页组件在此配置,建议配置在全局,页面可省略
        page: {
          enable: true,
          currentPage: 1,
          pageSize: 5,
          pageSizes: [5, 10, 15, 20],
        },
        // 菜单列配置
        menu: {
          enable: true,
          fixed: "right",
          onEdit: ({ row }) => {
            this.$message.info("编辑", row.date);
          },
          onDel: ({ row }) => {
            this.$message.info("删除", row.date);
          },
        },
        // 表格列配置
        columns: [
          { label: "日期", prop: "date", width: 200, sortable: "custom" },
          { label: "姓名", prop: "name", width: 200 },
          { label: "地址", prop: "address", minWidth: 300 },
        ],
        // 接口函数
        request: (query, done, err) => {
          // query == this.table.query
          this.getHttpData(query)
            .then((res) => done({ data: res.data, total: res.total }))
            .catch(err);
        },
      },
    };
  },
  computed: {
    queryString() {
      return  JSON.stringify(this.table.query);
    },
  },
  // table.getData 只能在 mounted 生命周期之后调用
  mounted() {
    this.table.getData();
  },
  methods: {
    onSearch() {
      // 传递参数可 重置 分页 为 1
      this.table.getData({ currentPage: 1 });
    },
    getHttpData(query) {
      // 模拟一个 http 请求
      return new Promise((reslove) => {
        setTimeout(() => {
          let data = [];
          for (let i = 0; i < query.pageSize; i++) {
            let index = (query.currentPage - 1) * query.pageSize + (i + 1);
            data.push({
              date: "2016-05-02",
              name: "王小虎" + index,
              address: "上海市" + index,
            });
          }
          reslove({ data: data, total: 100 });
        }, 1000);
      });
    },
  },
};
</script>

# 合并单元格

merge.auto 会按相同字段值自动合并;也可以仅在指定列上设置 merge: true。横向合并使用 direction: 'horizontal'。

# 自适应高度

设置 resize.enable 后,表格会根据参照元素和底部偏移量计算可用高度。可将 relative 设为 CSS 选择器或 DOM 元素;省略时使用表格容器的 offsetParent。

# 虚拟滚动

将 virtual 配置为 { enable: true, rowHeight: 32 } 开启固定行高虚拟滚动。rowHeight 以像素为单位,必须与实际行布局保持一致。

支持固定列、序号列、选择列、客户端排序、数据替换、行定位和容器尺寸变化。虚拟模式下不支持树形/懒加载、表格筛选、展开行、单元格合并或可变行高;不要使用会改变行高的单元格内容或样式。

示例默认加载 1 万行,并提供 1 万和 10 万行快捷加载;输入框不设置固定行数上限。组件不会按 1000 行截断传入的 data,实际可加载规模取决于浏览器内存和数据对象大小。

查看示例源码
<template>
  <div class="demo">
    <el-row style="margin-bottom:10px; display:flex; flex-wrap:wrap; align-items:center; gap:8px">
      <el-input-number v-model="number" :min="1" :step="100" placeholder="数据条数"></el-input-number>
      <el-button @click="setData()">加载指定行数</el-button>
      <el-button @click="setData(10000)">加载 1 万行</el-button>
      <el-button @click="setData(100000)">加载 10 万行</el-button>
      <el-input-number v-model="rowIndex" :min="1" :max="Math.max(table.data.length, 1)" placeholder="指定跳转行数"></el-input-number>
      <el-button @click="jump">跳转到指定行数</el-button>
    </el-row>
    <agel-table v-model="table"></agel-table>
  </div>
</template>
 
<script>
export default {
  data() {
    return {
      number: 10000,
      rowIndex: 100,
      table: {
        border: true,
        height: 200,
        virtual: { enable: true, rowHeight: 32 },
        columns: [
          {
            type: "selection",
            width: 60,
            align: "center",
            selectable: (row, index) => {
              // console.log(index)
              return index > 2;
            },
          },
          { label: "#", type: "index", width: 50, align: "center" },
          { label: "姓名", prop: "name", width: 200 },
          { label: "随机数", prop: "address", minWidth: 100, sortable: true },
        ],
        data: [],
      },
    };
  },
  mounted() {
    this.setData();
  },
  methods: {
    setData(count = this.number) {
      this.number = count
      let data = [];
      for (let i = 0; i < count; i++) {
        data.push({
          name: "王小虎" + (i + 1) + "号",
          address: Math.random() * 100,
        });
      }
      this.table.data = data;
    },
    jump() {
      const row = this.table.data[this.rowIndex - 1]
      if (row) this.table.virtualScrollToRow(row)
    },
  },
};
</script>

更多限制和滚动定位方法见 API 参考。

# 下一步