5.2.1.快速使用
高级表格组件是对 ElTable 的扩展,极大的简化了查询、分页、排序、筛选等操作,使用示例:
// 表格数据源, 数据源可以直接为数组或一个 function 返回一个 Promise
const datasource: DatasourceFunction = ({ pages }) => {
console.log({ pages });
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
// 当前页数据
list: [
{ userId: 1, username: 'admin', nickname: '管理员' },
{ userId: 2, username: 'user01', nickname: '用户一' }
],
// 总数量
count: 2
});
}, 100);
});
};
</script>
运行代码 新窗口打开
实际项目中数据一般是请求后端接口获取,使用示例:
<template>
<ele-pro-table row-key="userId" :columns="columns" :datasource="datasource"></ele-pro-table>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { pageUsers } from '@/api/system/user';
import type { DatasourceFunction, Columns } from 'ele-admin-plus/es/ele-pro-table/types';
// 表格列配置
const columns = ref<Columns>([
{
type: 'index',
columnKey: 'index',
width: 48,
align: 'center'
},
{
label: '用户账号',
prop: 'username',
sortable: 'custom'
},
{
label: '用户名',
prop: 'nickname',
sortable: 'custom'
},
{
label: '创建时间',
prop: 'createTime',
sortable: 'custom'
}
]);
/* 表格数据源 */
const datasource: DatasourceFunction = ({ pages, orders }) => {
return pageUsers({ ...orders, ...pages });
};
</script>
调用 api 中请求接口分页查询数据的代码示例:
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { User, UserParam } from './model';
/** 分页查询用户 */
export async function pageUsers(params: UserParam) {
const res = await request.get<ApiResult<PageResult<User>>>('/system/user/page', { params });
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
运行代码 新窗口打开
建议在使用高级表格组件的时候都配置 row-key 属性为数据中唯一字段的名称,在很多功能场景中都用到了这个属性。
注意 rowKey 属性可以不设置,但不能设置为错误的或不存在的字段名,否则会无法获取多选和单选的选中数据
5.2.2.属性列表
支持 EleDataTable 的全部属性(见5.3.2),这里只列出额外增加的属性:
属性 v-model:current 和 v-model:selections 不仅可以同步获取到选中的数据,直接赋值也会改变表格的选中状态(v1.1.6新增)。
locale 可以用于实现国际化(支持全局配置),也可以用于修改默认文案:
<template>
<ele-pro-table
:locale="locale"
row-key="userId"
:columns="columns"
:datasource="datasource">
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { pageUsers } from '@/api/system/user';
import type {
TableLocale,
DatasourceFunction,
Columns
} from 'ele-admin-plus/es/ele-pro-table/types';
// 修改默认文案
const locale = ref<Partial<TableLocale>>({
refresh: '刷新',
sizes: '密度',
columns: '列设置',
maximized: '全屏',
export: '导出', // v1.1.9新增
print: '打印', // v1.1.9新增
sizeLarge: '宽松',
sizeDefault: '中等',
sizeSmall: '紧凑',
columnTitle: '全选',
columnReset: '重置',
columnUntitled: '无标题',
columnFixedLeft: '固定在左侧',
columnFixedRight: '固定在右侧',
exportOk: '确定', // v1.1.9新增
exportCancel: '取消', // v1.1.9新增
exportFileName: '文件名', // v1.1.9新增
exportFileNamePlaceholder: '请输入文件名', // v1.1.9新增
exportSelectData: '选择数据', // v1.1.9新增
exportSelectColumn: '选择字段', // v1.1.9新增
exportDataTypePage: '当前页数据', // v1.1.9新增
exportDataTypeSelected: '选中数据', // v1.1.9新增
exportDataTypeAll: '全部数据' // v1.1.9新增
});
const columns = ref<Columns>([
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
const datasource: DatasourceFunction = ({ pages }) => {
return pageUsers({ ...pages });
};
</script>
5.2.5.事件列表
支持 EleDataTable 的全部事件(见5.3.5),这里只列出额外增加的事件:
事件 columnsChange 是当用于在列配置框中勾选或拖动等改变后触发,第一个参数 columns 是返回选中的列, 第二个参数 tableColumns 是返回全部的列包含未选中的列(未选中的列会增加 hideInTable: true 标识), 第三个参数 isReset 是当点击的是 “重置” 按钮触发时为 true ,如果需要记忆用户配置后的列到后端, 可以监听此事件实现,页面加载的时候把后端记忆的列赋值给表格的 columns 属性即可。
5.2.4.插槽列表
5.2.5.实例方法
支持 EleDataTable 的全部实例方法(5.3.6),这里只列出额外增加的实例方法:
5.2.6.表格列配置
表格列配置 columns 的全部属性请查看 5.3.3 ,此外增加的有:
使用插槽渲染复杂的列与 EleDataTable 的用法也是一致的,使用示例:
<template>
<ele-pro-table row-key="userId" :columns="columns" :datasource="datasource">
<!-- 状态列, 做可编辑表格要加 v-if="$index != -1" 判断, v1.1.4版本开始不用加 -->
<template #status="{ row, index }">
<el-switch
v-if="index != -1"
:model-value="row.status === 0"
@change="(checked) => editStatus(checked, row)"/>
<template #action="{ row, $index }">
<el-link type="primary" :underline="false" @click="openEdit(row)">修改
<el-popconfirm :width="190" title="确定要删除此用户吗?" placement="top-end" @confirm="remove(row)">
<template #reference>
删除
要注意 ElTable 内部会有一个复制的 class="hidden-columns" 的隐藏区也会调用插槽,所以建议判断一下 $index 为 -1 不渲染,否则当使用编辑表格做表单验证时会出现验证一直无法通过的问题(v1.1.4版本已在EleProTable内部处理,无需自己判断)。
运行代码 新窗口打开
5.2.7.设置数据源请求
数据源 datasource 如果设置为 Array 类型是进行纯前端的分页、排序,设置为 Function 类型可以调用 api 请求数据进行后端分页、排序:
调用 api 中请求接口分页查询数据的代码示例:
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { User, UserParam } from './model';
/** 分页查询用户 /
export async function pageUsers(params: UserParam) {
const res = await request.get<ApiResult<PageResult>>('/system/user/page', { params });
if (res.data.code === 0 && res.data.data) {
// 返回的数据要包含 list 和 count 两个字段, 字段名称可以通过 response 属性配置(支持全局配置)
return res.data.data;
/
// 不想通过 response 属性配置也可以这样写
return {
list: res.data.rows, // 例如当前数据的字段名接口返回的是 rows
count: res.data.total // 例如总数量的字段名接口返回的是 total
};
*/
}
return Promise.reject(new Error(res.data.message));
}
运行代码 新窗口打开
数据源的方法参数其实提供了很多,方便在一些复杂的场景中使用,完整的参数如下:
上面数据源示例代码使用了 async 和 await 可能不太直观,datasource 需要返回一个 Promise,直观一点是这样的:
// 例如其它框架的 api 里面写的很简单
import request from '@/utils/request';
export function pageUsers(params) {
return request.get('/system/user/page', params);
}
上面这种 api 的写法 datasource 可以这样写:
import { pageUsers } from '@/api/system/user';
const datasource = ({ pages, where, orders }) => {
return new Promise((resolve, reject) => {
pageUsers({
params: { ...where, ...orders, ...pages } // 如果要改参数名参照上面代码
}).then((res) => {
if(res.data.code === 0 && res.data.data) {
// 返回的数据要包含 list 和 count 两个字段, 字段名称可以通过 response 属性配置
resolve(res.data.data);
// 不想通过 response 属性配置也可以这样写
/resolve({
list: res.data.rows,
count: res.data.total
});/
} else {
reject(new Error(res.data.message));
}
}).catch((e) => {
reject(e);
});
});
};
5.2.8.自定义数据源返回格式
还提供了 request 和 response 属性用于设置接口的数据格式,而且支持 全局配置:
还提供了 parseData 属性用于更灵活的处理返回数据格式,是在 response 属性处理之前调用:
5.2.9.数据源设置初始请求参数
如果需要首次查询时也有默认的查询参数可通过表格的 where 属性进行设置,示例:
如果界面也有搜索表单子组件也可以在子组件的 onMounted 中触发搜索,示例:
搜索表单组件的代码:
运行代码 新窗口打开
5.2.10.刷新表格数据
刷新表格的方法 reload 使用示例:
const columns = ref([
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
const datasource: DatasourceFunction = ({ pages, where }) => {
return pageUsers({ ...where, ...pages });
};
const reload = () => {
// 可以不带任何参数,就会保留当前的分页和搜索参数
tableRef.value?.reload?.();
/*
// 回到第一页
tableRef.value?.reload?.({ page: 1 });
// 同时修改页码和每页数量
tableRef.value?.reload?.({ page: 2, limit: 5 });
// 设置搜索条件
tableRef.value?.reload?.({ page: 1, where: { username: 'admin' } });
*/
};
运行代码 新窗口打开
5.2.11.数据源请求完成事件
表格数据请求完成的 done 事件的参数说明如下:
运行代码 新窗口打开
5.2.12.数据源直接指定数据
使用 Array 类型数据源是进行纯前端的分页、排序,可以先给一个空数组,然后调用查询全部的接口获取全部数据:
运行代码 新窗口打开
5.2.13.表头右侧按钮设置
表头工具按钮 tools 默认为 ['reload','size','columns','maximized'] 可调整顺序或为 false 去掉,还可自定义:
运行代码 新窗口打开
5.2.14.自定义表头工具栏
使用插槽自定义表头工具栏内容的示例:
<template #tools>
导出
<ele-tool title="我是按钮" @click="onClick">
运行代码 新窗口打开
5.2.15.分页相关设置
属性 pagination 可为 false 去掉分页功能,也可用于自定义更多的分页组件属性:
运行代码 新窗口打开
自动修正页码 autoAmend 默认为 true 例如当在最后一页时只有一条数据点击删除后调用刷新方法就会没有数据,此时会自动跳转到前一页。
5.2.16.底部分页位置添加内容
使用 footer 插槽在底部添加自定义内容的示例:
运行代码 新窗口打开
也可以让分页组件显示在左侧,自己添加的内容都显示在右侧:
const datasource = ({ pages }) => {
return pageUsers({ ...pages });
};
</script>
运行代码 新窗口打开
5.2.17.表格开启多选列
表格增加多选列后可以使用 v-model 绑定 selections 选中数据会自动同步,非常的方便:
<template>
<div>
<div>选中了 {{ selections.length }} 条数据</div>
<el-button @click="clearSelection">清空选择</el-button>
<ele-pro-table
ref="tableRef"
row-key="userId"
:columns="columns"
:datasource="datasource"
v-model:selections="selections"
@done="onDone">
</ele-pro-table>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick } from 'vue';
import { pageUsers } from '@/api/system/user';
import type { User } from '@/api/system/user/model';
import type {
DatasourceFunction,
Columns,
DoneFunction
} from 'ele-admin-plus/es/ele-pro-table/types';
import type { EleProTable } from 'ele-admin-plus';
// 表格 ref
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格列配置
const columns = ref<Columns>([
{
type: 'selection',
columnKey: 'selection',
width: 48,
align: 'center'
},
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
// 表格选中数据
const selections = ref<User[]>([]);
// 表格数据源
const datasource: DatasourceFunction = ({ pages }) => {
return pageUsers({ ...pages });
};
/* 表格数据请求完成事件 */
const onDone: DoneFunction = () => {
// 回显 id 为 45、47、48 的数据的复选框
// 要注意 done 事件是数据源返回数据后立即触发, 所以需要写在 nextTick 中
nextTick(() => {
const ids = [45, 47, 48];
tableRef.value?.setSelectedRowKeys?.(ids);
// v1.1.6 之后可以直接为 selections 赋值进行选中
//selections.value = data.filter((d) => [45, 47, 48].includes(d.userId));
});
};
/* 清空选择 */
const clearSelection = () => {
tableRef.value?.clearSelection?.();
// v1.1.6 之后可以直接
//selections.value = [];
};
</script>
必须正确设置 row-key 属性(数据中能代表数据唯一值的字段名),否则选中后数据无法同步到 selections 中
运行代码 新窗口打开
5.2.18.表格开启单选列
表格的单选也与多选类似,使用 v-model 绑定 current 选中数据会自动同步,非常的方便:
<template>
<div>
<div>选中的用户: {{ current?.nickname }}</div>
<el-button @click="clearSelection">清空选择</el-button>
<ele-pro-table
ref="tableRef"
row-key="userId"
:columns="columns"
:datasource="datasource"
v-model:current="current"
highlight-current-row
@done="onDone">
</ele-pro-table>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick } from 'vue';
import { pageUsers } from '@/api/system/user';
import type { User } from '@/api/system/user/model';
import type {
DatasourceFunction,
Columns,
DoneFunction
} from 'ele-admin-plus/es/ele-pro-table/types';
import type { EleProTable } from 'ele-admin-plus';
// 表格 ref
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格列配置
const columns = ref<Columns>([
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
// 表格选中数据
const current = ref<User | null>(null);
// 表格数据源
const datasource: DatasourceFunction = ({ pages }) => {
return pageUsers({ ...pages });
};
/* 表格数据请求完成事件 */
const onDone: DoneFunction = ({ data }) => {
// 选中 id 为 45 的数据
// 要注意 done 事件是数据源返回数据后立即触发, 所以需要写在 nextTick 中
nextTick(() => {
tableRef.value?.setCurrentRow?.(data.find(d => d.userId === 45));
// v1.1.6 之后可以直接为 current 赋值进行选中
//current.value = data.find(d => d.userId === 45);
});
};
/* 清空选择 */
const clearSelection = () => {
tableRef.value?.setCurrentRow?.();
// v1.1.6 之后可以直接
//current.value = null;
};
</script>
必须正确设置 row-key 属性(数据中能代表数据唯一值的字段名),否则选中后数据无法同步到 current 中
运行代码 新窗口打开
5.2.19.多选禁用指定数据
多选列实现禁用某些数据的复选框,通过 selectable 属性实现:
<template>
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
v-model:selections="selections">
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { pageUsers } from '@/api/system/user';
import type { DatasourceFunction, Columns } from 'ele-admin-plus/es/ele-pro-table/types';
const columns = ref<Columns>([
{
type: 'selection',
columnKey: 'selection',
width: 48,
align: 'center',
selectable: (row) => {
// 禁用 id 为 45、47、48 的数据的复选框
return ![45, 47, 48].includes(row.userId)
}
},
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
const selections = ref([]);
const datasource: DatasourceFunction = ({ pages }) => {
return pageUsers({ ...pages });
};
</script>
运行代码 新窗口打开
5.2.20.多选时限制单选
多选时限制单选,可加样式去掉全选框并禁止多选框点击然后通过 cellClick 事件实现单选选中效果:
<template>
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
v-model:selections="selections"
class="sys-user-table"
@cell-click="onCellClick">
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { pageUsers } from '@/api/system/user';
/** 表格列配置 */
const columns = ref([
{
type: 'selection',
columnKey: 'selection',
width: 50,
align: 'center'
},
{
prop: 'username',
label: '用户账号'
},
{
prop: 'nickname',
label: '用户名'
}
]);
/** 表格选中数据 */
const selections = ref([]);
/** 表格数据源 */
const datasource = ({ pages }) => {
return pageUsers({ ...pages });
};
/** 表格单元格点击事件 */
const onCellClick = (row, column) => {
if (column.type === 'selection') {
selections.value = [row]; // 设置选中数据(单选效果)
// v-model:selections 改变后表格自动选中是 v1.1.6 新增
// 之前版本需要通过 ref 调用 toggleRowSelection 方法选中
// 所以之前版本实现单选需要先调用 clearSelection 方法清空
}
};
</script>
<style lang="scss" scoped>
.sys-user-table :deep(.el-table) {
/* 去掉表头全选框 */
.el-table__header-wrapper .el-table-column--selection .el-checkbox {
display: none;
}
/* 禁止复选框点击, 从而实现只能通过 cellClick 事件选中 */
.el-table__body-wrapper .el-table-column--selection {
cursor: pointer;
.el-checkbox {
pointer-events: none;
}
}
}
</style>
运行代码 新窗口打开
5.2.21.树形表格懒加载
增加 :lazy="true" 属性开启懒加载后点加载子级的时候也会通过数据源请求,实现非常的简单:
<template>
<ele-pro-table
row-key="menuId"
:columns="columns"
:datasource="datasource"
:pagination="false"
:lazy="true"/>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { listMenus } from '@/api/system/menu';
import type { DatasourceFunction, Columns } from 'ele-admin-plus/es/ele-pro-table/types';
const columns = ref<Columns>([
{
prop: 'title',
label: '菜单名称'
},
{
prop: 'path',
label: '路由地址'
}
]);
const datasource: DatasourceFunction = ({ where, parent }) => {
// parent 就是父级的数据
return listMenus({ ...where, parentId: parent?.menuId || 0 });
};
</script>
懒加载时默认所有级别的数据第一次都会出现展开子级按钮,当点击请求数据后没有子级展开按钮就会自动消失,也可以通过字段控制是否还有子级(见 ElTable 的 treeProps 属性),当数据里面有字段 hasChildren: false 就不会出现展开按钮。
运行代码 新窗口打开
5.2.22.树表懒加载刷新指定节点
例如当删除某个节点后需要刷新父级的子级,或当在父级下添加了一个子级后需要刷新子级可以这样操作:
<template>
<ele-pro-table
ref="tableRef"
row-key="menuId"
:columns="columns"
:datasource="datasource"
:pagination="false"
:lazy="true"
:load="tableLoad"
@done="handleDone">
<template #action="{ row }">
<el-link type="primary" :underline="false" @click="reloadChild(row, $event)">刷新我的子级数据</el-link>
<el-divider direction="vertical" />
<el-link type="primary" :underline="false" @click="reloadParent(row)">刷新我父级的子级数据</el-link>
</template>
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref, nextTick } from 'vue';
import { listMenus } from '@/api/system/menu';
import dayjs from 'dayjs';
const tableRef = ref(null);
const columns = ref([
{
prop: 'title',
label: '菜单名称',
minWidth: 160
},
{
prop: 'path',
label: '路由地址',
minWidth: 228
},
{
prop: 'updateTime',
label: '更新时间',
width: 220,
align: 'center'
},
{
columnKey: 'action',
label: '操作',
slot: 'action',
width: 320
}
]);
const datasource = async ({ where, parent }) => {
const now = Date.now();
const data = await listMenus({ ...where, parentId: parent?.menuId || 0 });
// 为子级数据记录父级的 ElTable 懒加载的 resolve 方法
if(parent) {
data.forEach((d) => {
if(parent && parent._tableResolve) {
d._parentTableResolve = parent._tableResolve;
d.parent = parent;
}
});
parent._oldChildrenLength = parent.children?.length || 0;
}
// 重设 key 以解决数据可能无法更新的问题
data.forEach((d) => {
d.menuKey = d.menuId + '-' + now;
d.updateTime = dayjs(now).format('YYYY-MM-DD HH:mm:ss.SSS');
});
return data;
};
/** 重写树表格懒加载方法 */
const tableLoad = (row, treeNode, resolve) => {
// 记录 ElTable 懒加载的 resolve 方法, 刷新时需要这个方法来更新子级的数据
row._tableResolve = resolve;
tableRef.value?.reload?.(void 0, row, resolve);
};
/** 刷新节点的子级数据 */
const reloadChild = (row) => {
if(row._tableResolve) {
tableRef.value?.reload?.(void 0, row, row._tableResolve);
return;
}
const target = e.currentTarget;
const $tr = target?.parentElement?.parentElement?.parentElement;
const $icon = $tr?.querySelector?.('.el-table__expand-icon');
$icon && $icon.click();
};
/** 刷新节点父级的子级数据 */
const reloadParent = (row) => {
if(row._parentTableResolve) {
tableRef.value?.reload?.(void 0, row.parent, row._parentTableResolve);
}
};
// 例如当节点删除后可使用 reloadParent 刷新父级的整个子级
// 例如父级有添加子级的按钮, 添加完后可以使用 reloadChild 刷新自己的子级
/** 表格数据加载完成事件 */
const handleDone = ({ response }, parent) => {
// 解决懒加载下子级无法清空
if (parent && !response.length && parent._oldChildrenLength) {
nextTick(() => {
//console.log(parent);
parent.hasChildren = false;
parent.menuKey = parent.menuId + '-' + Date.now();
});
}
};
</script>
运行代码 新窗口打开
5.2.23.解决树表刷新时子级未更新
例如当子级节点删除后调用表格的刷新方法子级数据却未更新,需要把数据的 rowKey 也更新下才能解决:
<template>
<div>
<el-button @click="reload">刷新表格</el-button>
<!-- rowKey 重新用一个字段(名字可随便取), 之前是 menuId -->
<ele-pro-table
ref="tableRef"
row-key="menuKey"
:columns="columns"
:datasource="datasource"
:pagination="false"/>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { toTree } from 'ele-admin-plus';
import { listMenus } from '@/api/system/menu';
const tableRef = ref(null);
const columns = ref([
{
prop: 'title',
label: '菜单名称'
},
{
prop: 'path',
label: '路由地址'
}
]);
const datasource = async ({ where }) => {
const data = await listMenus({ ...where });
const now = Date.now(); // 准备一个唯一值, 这里用时间戳, 也可以用 uuid
return toTree({
data: data.map((d) => {
// menuKey 的值用 menuId 拼接一个唯一值以使 ElTable 能更新数据
return { ...d, menuKey: d.menuId + '-' + now };
}),
idField: 'menuId',
parentIdField: 'parentId'
});
// 这里接口返回的数据是 parentId 形式的就可以直接用数组的 map 方法处理
// 如果你的接口返回的数据是 children 形式的可以使用 EleAdminPlus 提供的 mapTree 方法处理
};
const reload = () => {
// 按上面操作后每次调用表格的刷新就能保证子级数据也会更新了
tableRef.value?.reload?.();
};
运行代码 新窗口打开
5.2.24.实现展开行或嵌套表格
同 EleDataTable 的实现方式,实现展开子表格的示例,可以使用 where 属性传递父表对应行的数据:
运行代码 新窗口打开
5.2.25.嵌套表格搜索传递到子表
外部表格搜索时的搜索条件也需要传到子表做搜索的使用示例:
运行代码 新窗口打开
5.2.26.设置默认展开行
如果需要默认展开一些行建议在表格的 done 事件里面调用表格的实例方法 toggleRowExpansion 示例:
运行代码 新窗口打开
5.2.27.获取数据源请求参数
fetch 方法获取同数据源一样的参数,可以用于导出搜索、排序、筛选后的数据:
const columns = ref([
{
label: '用户账号',
prop: 'username',
sortable: 'custom'
},
{
label: '用户名',
prop: 'nickname',
sortable: 'custom'
},
{
prop: 'sexName',
label: '性别',
sortable: 'custom',
columnKey: 'sex',
filters: [
{ text: '男', value: '1' },
{ text: '女', value: '2' }
],
filterMultiple: false
}
]);
const datasource: DatasourceFunction = ({ pages, where, orders, filters }) => {
return pageUsers({ ...where, ...orders, ...filters, ...pages });
};
const search = () => {
tableRef.value?.reload?.({ where: { username: 'admin' } });
};
/* 例如导出排序、筛选、搜索后的不分页的全部的数据 */
const exportData = () => {
const array = [['用户账号', '用户名']];
const loading = EleMessage.loading('请求中..');
tableRef.value?.fetch?.(({ where, orders, filters }) => {
// 方法的参数与数据源的完全一致, 也含有分页相关的参数及其它的参数等
// 请求查询全部接口
listUsers({ ...where, ...orders, ...filters }).then((data) => {
loading.close();
data.forEach((d) => {
array.push([d.username, d.nickname]);
});
writeFile({
SheetNames: ['Sheet1'],
Sheets: {
Sheet1: utils.aoa_to_sheet(array)
}
}, '用户数据.xlsx');
}).catch((e) => {
loading.close();
EleMessage.error(e.message);
});
});
};
运行代码 新窗口打开
5.2.28.直接操作表格数据
getData 和 setData 可以用于操作表格当前页的数据,比如不重新请求接口直接删除一条数据、添加一条数据, 一般不建议这样做,因为使用 Function 数据源是用于后端分页的,如果直接在当前页添加一条数据或删除一条数据不符合分页的逻辑, 不重新请求直接修改一条数据这样是合理的,修改数据直接用插槽里面的行数据即可,也用不到这两个方法,示例:
编辑弹窗 user-edit.vue 的代码示例:
运行代码 新窗口打开
如果想要纯前端添加、修改、删除数据,应该使用 Array 类型的数据源,数据源改变后表格数据也会响应改变。
5.2.29.监听分页事件
如何自己监听分页去改变数据?在其它框架中列表页面大多是自己写一个 ElTable 组件一个 ElPagination 组件,监听分页组件的事件去改变表格组件的数据, 所以在使用 EleProTable 时部分用户会给 datasource 绑定数组,想监听到分页组件的事件去改变数据, 然后发现 EleProTable 没有分页组件的事件,因为 EleProTable 设计的就是 Function 类型数据源才是自己处理分页, Array 类型数据源就是给全部的数据组件内部进行前端分页,Function 类型数据源就相当于分页组件的事件, 分页组件的当前页码和每页数量改变后都会重新调用数据源方法:
5.2.30.动态改变数据源
如果是 function 数据源想要根据不同条件调用不同接口查询,实现示例:
如果执意要去动态修改 datasource 属性,可以这样实现(要用 vue 的 ref 而不是直接用 let 去写):
运行代码 新窗口打开
5.2.31.实现主体高度铺满
表格高度自适应铺满是指自动将窗口剩余的高度(减去搜索栏等的高度)都给表格,这个需求有用 js 来实现的,但无疑用 css 的 flex 实现性能更好:
const datasource = ({ pages }) => {
return pageUsers({ ...pages });
};
</script>
运行代码 新窗口打开
以上仅加了三行样式就实现了,核心就是使用垂直方向的 flex 布局,然后把 ElTable 的样式设置 flex: 1 就实现了, 但要注意如果页面结构的层级很多,要依次把表格的所有父级都改为 flex 布局(可能会比较麻烦), 不过一些常用的组件已经加了属性用于对这个需求的支持:
<!-- ElePage 和 EleCard 以及 EleSplitPanel 以及 EleTabs 等组件都已支持 -->
<template>
<ele-page flex-table>
<ele-card>搜索栏</ele-card>
<ele-card flex-table :body-style="{ paddingBottom: 0 }">
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
:footer-style="{ paddingBottom: '20px' }">
</ele-pro-table>
</ele-card>
</ele-page>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { pageUsers } from '@/api/system/user';
const columns = ref([
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
const datasource = ({ pages }) => {
return pageUsers({ ...pages });
};
</script>
运行代码 新窗口打开
当表格上层的组件不是上面几个组件,也可以自己实现,例如把个人中心我的消息页面的表格修改为高度自适应铺满:
<!-- ele-page 和 ele-card 组件先加 flex-table 属性 -->
<template>
<ele-page :multi-card="false" hide-footer flex-table>
<ele-card :body-style="{ padding: 0 }" flex-table>
<div :class="['message-wrapper', { 'is-mobile': mobile }]">
<div class="message-side">
<ele-menus
:items="menus"
:default-active="active"
:mode="mobile ? 'horizontal' : 'vertical'"/>
</div>
<div class="message-body">
<transition name="slide-right" mode="out-in">
<message-notice v-if="active == 'notice'" @reload="queryBadge" />
<message-todo v-else-if="active == 'todo'" @reload="queryBadge" />
<message-letter v-else @reload="queryBadge" />
</transition>
</div>
</div>
</ele-card>
</ele-page>
</template>
<script setup>//......省略 js 部分</script>
<style lang="scss" scoped>
.message-wrapper {
display: flex;
min-height: 582px;
.message-side {
width: 150px;
flex-shrink: 0;
display: flex;
:deep(.el-menu) {
width: 100%;
padding-top: 12px;
}
}
.message-body {
flex: auto;
padding: 8px 20px 16px 16px;
box-sizing: border-box;
overflow: hidden;
}
&.is-mobile {
flex-direction: column;
min-height: 637px;
.message-side {
width: auto;
display: block;
:deep(.el-menu) {
padding-top: 0;
justify-content: center;
--el-menu-item-height: 46px;
--ele-menu-horizontal-item-margin: 4px;
}
}
.message-body {
padding: 16px;
}
}
}
/* 增加下面这段代码, 上面的代码不需要动 */
.message-wrapper {
flex: 1;
overflow: auto;
min-height: auto !important;
.message-body {
height: 100%;
overflow: auto;
overflow-x: hidden;
box-sizing: border-box;
& > div {
height: 100%;
display: flex;
flex-direction: column;
overflow: auto;
:deep(> .ele-pro-table) {
flex: 1;
display: flex;
flex-direction: column;
overflow: auto;
& > .el-table {
flex: 1;
height: 100%;
}
}
}
}
}
</style>
运行代码 新窗口打开
5.2.32.虚拟滚动表格
只需要增加 virtual 即可开启虚拟滚动,非常的方便,使用示例:
<template>
<ele-pro-table
virtual:height="280"
row-key="userId"
:columns="columns"
:datasource="datasource">
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { pageUsers } from '@/api/system/user';
// 表格列配置
const columns = ref([
{
type: 'index',
columnKey: 'index',
width: 48,
align: 'center'
},
{
label: '用户账号',
prop: 'username',
sortable: 'custom'
},
{
label: '用户名',
prop: 'nickname',
sortable: 'custom'
},
{
label: '创建时间',
prop: 'createTime',
sortable: 'custom'
}
]);
/* 表格数据源 */
const datasource = ({ pages, orders }) => {
return pageUsers({ ...orders, ...pages });
};
</script>
运行代码 新窗口打开
需要注意使用虚拟滚动表格一定要设置 height 属性,否则就失去了虚拟滚动的意义,如果不设置默认为一行数据的高度。
如果使用了内置组件的 flexTable 属性实现表格高度铺满就不需要手动设置高度了:
<template>
<ele-page flex-table>
<ele-card>搜索栏</ele-card>
<ele-card flex-table>
<ele-pro-table
virtualrow-key="userId"
:columns="columns"
:datasource="datasource">
</ele-pro-table>
</ele-card>
</ele-page>
</template>
<script setup>
import { ref } from 'vue';
import { pageUsers } from '@/api/system/user';
const columns = ref([
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
const datasource = ({ pages }) => {
return pageUsers({ ...pages });
};
</script>
运行代码 新窗口打开
包括自己手动实现的表格高度铺满也不需要设置表格高度:
<template>
<div style="flex: 1; display: flex; flex-direction: column; overflow: auto;">
<div>
<div>其它内容</div>
<div>其它内容</div>
<div>其它内容</div>
</div>
<ele-pro-table
virtualrow-key="userId"
:columns="columns"
:datasource="datasource"
style="flex: 1; display: flex; flex-direction: column; overflow: auto"
:table-style="{ flex: 1, height: '100%', overflow: 'hidden' }"/>
<div>
<div>其它内容</div>
<div>其它内容</div>
<div>其它内容</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { pageUsers } from '@/api/system/user';
const columns = ref([
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
const datasource = ({ pages }) => {
return pageUsers({ ...pages });
};
</script>
运行代码 新窗口打开
5.2.33.在前端排序不请求后端
当使用 Function 数据源后端分页时如果仍想前端只排序当前页,增加 :load-on-changed="false" 属性:
<template>
<ele-pro-table
ref="tableRef"
row-key="userId"
:columns="columns"
:datasource="datasource"
:load-on-changed="false">
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref, unref } from 'vue';
import { pageUsers } from '@/api/system/user';
import type { User } from '@/api/system/user/model';
import type { DatasourceFunction, Columns } from 'ele-admin-plus/es/ele-pro-table/types';
import type { EleProTable } from 'ele-admin-plus';
/** 表格列配置, 还可以使用 sortMethod 属性自定义排序函数 */
const columns = ref<Columns>([
{
label: '用户账号',
prop: 'username',
sortable: true,
columnKey: 'username'
},
{
label: '用户名',
prop: 'nickname',
sortable: true,
columnKey: 'nickname',
sortMethod: (a: User, b: User) => {
return a.nickname == b.nickname ? 0 : a.nickname < b.nickname ? -1 : 1;
// 如果属性是数字类型可以简单点直接相减
//return a.nickname - b.nickname;
/*
// 如果需要对某一行数据排序时固定在首行, 可以这样操作
const sortOrder = unref<any>(unref(tableRef.value?.getTableRef?.()?.tableRef)?.store?.states?.sortOrder);
const num = sortOrder === 'descending' ? 1 : -1;
//const num = sortOrder === 'descending' ? -1 : 1; // 如果需要固定在尾行值取相反的即可
if (a.nickname == '用户三') { // 判断是否是需要固定的行
return num;
}
if (b.nickname == '用户三') { // 判断是否是需要固定的行
return -num;
}
return a.nickname == b.nickname ? 0 : a.nickname < b.nickname ? -1 : 1;
*/
}
}
]);
/** 表格数据源 */
const datasource: DatasourceFunction = ({ pages }) => {
return pageUsers({ ...pages });
};
/** 表格实例 */
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
</script>
运行代码 新窗口打开
属性 loadOnChanged 设为 false 后表头筛选也不会自动请求接口了,你可以参考文档下一节实现前端筛选当前页数据。
5.2.34.在前端筛选不请求后端
当使用 Function 数据源后端分页时如果仍想前端只筛选当前页,增加 :load-on-changed="false" 属性:
<template>
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
:load-on-changed="false">
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { pageUsers } from '@/api/system/user';
import type { User } from '@/api/system/user/model';
import type { DatasourceFunction, Columns } from 'ele-admin-plus/es/ele-pro-table/types';
// 表格列配置, 使用 filterMethod 属性自定义筛选函数
const columns = ref<Columns>([
{
label: '用户账号',
prop: 'username',
columnKey: 'username'
},
{
label: '性别',
prop: 'sexName',
columnKey: 'sex',
filters: [
{ text: '男', value: '1' },
{ text: '女', value: '2' }
],
filterMultiple: false,
filterMethod: (value: string, row: User) => {
return row.sex == value;
}
}
]);
/* 表格数据源 */
const datasource: DatasourceFunction = ({ pages }) => {
return pageUsers({ ...pages });
};
</script>
运行代码 新窗口打开
属性 loadOnChanged 设为 false 后排序也不会自动请求接口了,你可以参考文档上一节实现前端排序当前页数据。
5.2.35.开启打印功能
表头工具按钮 tools 中增加 print 即可开启内置的打印,可通过 printConfig 属性自定义,支持的配置有:
打印使用的是静态表格 EleTable 渲染,没有通过 printConfig 设置 datasource 时选择数据下拉没有选择“全部数据”, 表格有多选择列时选择数据的下拉才有选择“选中数据”,否则只有“当前页数据”,例如设置打印“全部数据”的数据源:
<template>
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
:print-config="printConfig"
:tools="['reload', 'print', 'maximized']">
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { pageUsers, listUsers } from '@/api/system/user';
const columns = ref([
{
type: 'index',
columnKey: 'index',
width: 48,
align: 'center'
},
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
const datasource = ({ pages, where, orders }) => {
return pageUsers({ ...pages, ...where, ...orders });
};
const printConfig = reactive({
// 打印全部时的全部数据源, Function 类型, 要返回数组格式的数据
datasource: ({ where, orders }) => {
return listUsers({ ...where, ...orders });
// 要注意这里的数据源要直接返回数组, 不要返回对象(不需要包含 count list)
// datasource 只会在 "打印全部数据" 时才会调用, 当前页和选中数据都不会调用这个
// 如果返回的数据是 null 会直接关闭弹窗
// 如果进入到 reject 会关闭确定按钮的 loading 但不会关闭弹窗
// 所以如果需要在 datasource 中做自定义打印(不想再往后走进入到 beforePrint),
// 就返回 null 或者让 Promise 进入 reject
},
// 例如设置打印数据的选择框默认选中“全部数据”
dataType: 'data',
// 例如设置打印弹窗的更多属性
modalProps: { title: '用户数据打印', width: '800px' },
// 例如设置打印表格的更多属性
tableProps: { stripe: true, style: { background: 'red' } },
// 例如设置打印组件的更多属性
printerProps: { direction: 'landscape' },
// 例如设置打印前的钩子函数
beforePrint: (params) => {
console.log(params); // 这里参数会包含很多方便做自定义打印的操作
// 如果返回 false 会阻止默认的打印行为, 可以用 params 参数完全自己实现打印
//return false;
}
});
</script>
运行代码 新窗口打开
5.2.36.打印时重设显示内容
如果需要某些列打印时显示的内容和表格正常显示的内容完全不一致,可以使用 printSlot 设置打印时使用的插槽,示例:
<template>
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
:tools="['reload', 'print', 'maximized']">
<!-- 用户账号列正常使用的插槽 -->
<template #username="{ row }">
<router-link :to="'/system/user/details/' + row.userId">
<el-link type="primary">{{ row.username }}</el-link>
</router-link>
</template>
<!-- 用户账号列打印时使用的插槽 -->
<template #usernamePrint="{ row }">
<span style="color: red;background: yellow;">{{ row.username }}</span>
</template>
<!-- 用户名列打印时使用的插槽 -->
<template #nicknamePrint="{ row }">
^_^
{{ row.nickname }}
const datasource = ({ pages }) => {
return pageUsers({ ...pages });
};
</script>
运行代码 新窗口打开
也可以使用 css 样式 @media print 控制某些内容在打印时不显示或某些内容只在打印时显示,示例:
<template>
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
:tools="['reload', 'print', 'maximized']">
<template #username="{ row }">
<span>{{ row.username }}</span>
<!-- 例如加了一个编辑图标, 打印的时候不想显示这个图标 -->
<el-icon class="demo-table-hide-print">
<EditPen />
</el-icon>
</template>
<template #nickname="{ row }">
<!-- 例如正常是一个蓝色文本链接, 打印时只想为普通黑色文本 -->
<span class="demo-table-hide-print">
<router-link :to="'/system/user/details/' + row.userId">
<el-link type="primary">{{ row.nickname }}</el-link>
</router-link>
</span>
<span class="demo-table-show-print">{{ row.nickname }}</span>
</template>
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { pageUsers } from '@/api/system/user';
import { EditPen } from '@element-plus/icons-vue';
const columns = ref([
{
label: '用户账号',
prop: 'username',
slot: 'username'
},
{
label: '用户名',
prop: 'nickname',
slot: 'nickname'
}
]);
const datasource = ({ pages }) => {
return pageUsers({ ...pages });
};
</script>
<style lang="scss">
/* 增加样式, 隐藏需要在打印时才显示的元素 */
.demo-table-show-print {
display: none !important;
}
/* 此段样式在打印时才会起作用 */
@media print {
.demo-table-hide-print {
display: none !important;
}
.demo-table-show-print {
display: unset !important;
}
}
/* 如果需要某些内容打印时不显示就给它加 class="demo-table-hide-print" */
/* 如果需要某些内容只在打印时显示就给它加 class="demo-table-show-print" */
</style>
运行代码 新窗口打开
5.2.37.打印时钩子函数
钩子函数 beforePrint 返回的参数数据格式说明(导出的钩子函数返回的参数也是如此):
data 是在打印弹窗中选择的数据(表格当前页数据/选中的数据/全部数据),columns 是在打印弹窗中勾选的列。
headerData、bodyData、footerData 都是二维数组,是根据 data 和 columns 解析后封装的数据,每一项的数据格式为:
打印和导出本质都是根据 data 和 columns 生成一个表格,所以封装后的数据格式是二维数组,对应表格的每一行每一个单元格。
字段 text 表头是取列 label(以及 renderHeader 方法),主体是用列 prop 取数据(以及 formatter 方法)以及序号列 index 方法。
表尾 text 是解析表格 sumText 和 summaryMethod 属性,colspan / rowspan 是解析 spanMethod 方法(表头是解析 columns 层级)。
打印使用静态表格 ElTable 渲染时还会解析列的 slot 和 headerSlot 插槽,但导出不会考虑插槽,因为导出是根据数据生成 csv 没有渲染操作。
字段 text 虽然写的 String 类型,但实际表格的各种方法有些是支持返回 VNode 的,打印时 VNode 也能正常解析,导出时 VNode 是无法解析的。
bodyCols 是主体列的二次封装(是所有叶子节点列),因为表格的 columns 是支持 children 表示多级表头的,而主体不存在多级,数据格式为:
上面对 beforePrint 的参数做这么多封装是为了当需要完全自定义时更方便, 比如 headerData、bodyData、footerData 都是二维数组格式可以很简单的循环生成 tr td, 直接取 text 为单元格文本,无需去管 formatter、renderHeader 等各种方法, 比如可以直接循环 bodyCols 生成 colgroup col 设置列宽,无需自己去管 columns 的 children 的多级表头。
5.2.38.打印时表头表尾不每页显示
打印时生成的表格 headerData 的数据会放在 thead 标签中,bodyData 的数据会放在 tbody 标签中,footerData 的数据会放在 tfoot 标签中,因此浏览器打印时如果有多页 thead 和 tfoot 是每页都会显示的,如果不想让表头和表尾自动每页显示,可以这样操作:
<template>
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
:print-config="printConfig"
:tools="['reload', 'print', 'maximized']">
</ele-pro-table>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { listMenus } from '@/api/system/menu';
import { toTree } from 'ele-admin-plus';
const columns = ref([
{
type: 'index',
columnKey: 'index',
width: 48,
align: 'center'
},
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
const datasource = async ({ pages, where, orders }) => {
const data = await listMenus({ ...where });
return toTree({
data,
idField: 'menuId',
parentIdField: 'parentId'
});
};
const printConfig = reactive({
// 可以在打印前的钩子函数中处理数据
beforePrint: (params) => {
console.log(params);
// 把 footerData 的数据复制到 bodyData 中
params.footerData.forEach((fRow) => {
params.bodyData.push(fRow);
});
// 然后清空 footerData 的数据
params.footerData.splice(0, params.footerData.length);
// 这样操作后表尾合计行就不会每页都显示了
// 同理如果需要表头也别每页都显示把 headerData 的数据移到 bodyData 中
params.headerData.forEach((fRow, index) => {
params.bodyData.splice(index, 0, fRow);
});
params.headerData.splice(0, params.headerData.length);
}
});
</script>
运行代码 新窗口打开
5.2.39.开启导出功能
表头工具按钮 tools 中增加 export 即可开启内置的导出,可通过 exportConfig 属性自定义,支持的配置有:
导出默认导出 csv 格式,没有通过 exportConfig 设置 datasource 时选择数据下拉没有选择“全部数据”, 表格有多选择列时选择数据的下拉才有选择“选中数据”,否则只有“当前页数据”,例如设置导出“全部数据”的数据源:
<template>
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
:export-config="exportConfig"
:tools="['reload', 'export', 'maximized']">
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { pageUsers, listUsers } from '@/api/system/user';
const columns = ref([
{
type: 'index',
columnKey: 'index',
width: 48,
align: 'center'
},
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
const datasource = ({ pages, where, orders }) => {
return pageUsers({ ...pages, ...where, ...orders });
};
const exportConfig = reactive({
// 导出全部时的全部数据源, Function 类型, 要返回数组格式的数据
datasource: ({ where, orders }) => {
return listUsers({ ...where, ...orders });
// 要注意这里的数据源要直接返回数组, 不要返回对象(不需要包含 count list)
// datasource 只会在 "导出全部数据" 时才会调用, 当前页和选中数据都不会调用这个
// 如果返回的数据是 null 会直接关闭弹窗
// 如果进入到 reject 会关闭确定按钮的 loading 但不会关闭弹窗
// 所以如果需要在 datasource 中做自定义导出(不想再往后走进入到 beforeExport),
// 就返回 null 或者让 Promise 进入 reject
},
// 例如设置导出数据的选择框默认选中“全部数据”
dataType: 'data',
// 例如设置导出弹窗的更多属性
modalProps: { title: '用户数据导出', width: '800px' },
// 例如设置导出默认的文件名
fileName: '用户数据列表',
// 例如设置导出前的钩子函数
beforeExport: (params) => {
console.log(params); // 这里参数会包含很多方便做自定义导出的操作
// 如果返回 false 会阻止默认的导出行为, 可以用 params 参数完全自己实现导出
//return false;
}
});
</script>
运行代码 新窗口打开
5.2.40.导出全部时下载后端文件流
如果希望导出全部功能是完全由后端导出文件前端下载,不需要经过前端用数据生成文件,可以在导出全部的数据源中返回 null 即可:
<template>
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
:export-config="exportConfig"
:tools="['reload', 'export', 'maximized']">
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { pageUsers, exportUsers } from '@/api/system/user';
const columns = ref([
{
type: 'index',
columnKey: 'index',
width: 48,
align: 'center'
},
{
label: '用户账号',
prop: 'username',
sortable: 'custom'
},
{
label: '用户名',
prop: 'nickname',
sortable: 'custom'
}
]);
const datasource = ({ pages, where, orders }) => {
return pageUsers({ ...pages, ...where, ...orders });
};
const exportConfig = reactive({
// 数据源方法加 async 然后 return null
datasource: async ({ where, orders }) => {
console.log({ where, orders });
// 这里就可以调用后端接口下载文件, 还可以传递表格当前的搜索、排序参数等
const loading = EleMessage.loading({ message: '正在下载文件..', plain: true });
exportUsers({ ...where, ...orders }).then(() => {
loading.close();
}).catch((e) => {
loading.close();
EleMessage.error(e.message);
});
// 如果接口为 get 请求且无需 token 可直接这样操作
//window.open('https://cdn.eleadmin.com/20200610/20200708224450.docx');
// 上面代码会异步执行, 这里会立即返回
return null;
},
// 例如设置导出默认的文件名
fileName: '用户数据列表'
});
// exportUsers 的 api 方法就是请求接口返回 blob 然后下载
/*
import request from '@/utils/request';
import { download } from '@/utils/common';
export async function exportUsers(data) {
const res = await request({
url: '/system/user/export',
method: 'POST',
data: data,
responseType: 'blob'
});
download(res.data, 'user.xlsx');
}
*/
</script>
运行代码 新窗口打开
5.2.41.导出时重设显示内容
对于使用了插槽自定义的单元格需要在导出时也能自定义,可以同时使用 formatter 方法操作,示例:
<!-- 正常表格的插槽优先级会高于 formatter 方法, 但导出时插槽会被忽略, -->
<!-- 所以同时设置插槽和 formatter 方法时就相当于插槽作为正常显示, formatter 方法作为导出时显示 -->
<template>
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
:tools="['reload', 'export', 'maximized']">
<template #roles="{ row }">
<!-- 例如角色列是一个数组正常是循环后用标签显示的 -->
<el-tag v-for="item in row.roles":key="item.roleId">{{ item.roleName }}</el-tag>
</template>
<template #status="{ row }">
<!-- 例如状态列正常是用的开关组件 -->
<el-switch
:model-value="row.status === 0"
@change="(checked) => editStatus(checked, row)"/>
</template>
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { pageUsers, updateUserStatus } from '@/api/system/user';
const columns = ref([
{
label: '用户名',
prop: 'nickname'
},
{
label: '角色',
columnKey: 'roles',
slot: 'roles',
// 使用 formatter 方法设置导出时显示的文本
formatter: (row) => row.roles.map((d) => d.roleName).join(',')
},
{
label: '状态',
prop: 'status',
slot: 'status',
// 使用 formatter 方法设置导出时显示的文本
formatter: (row) => (row.status == 0 ? '正常' : '冻结')
}
]);
const datasource = ({ pages }) => {
return pageUsers({ ...pages });
};
const editStatus = (checked, row) => {
const status = checked ? 0 : 1;
updateUserStatus(row.userId, status).then((msg) => {
row.status = status;
console.log(msg);
}).catch((e) => {
console.error(e.message);
});
};
</script>
运行代码 新窗口打开
表格的有些自定义方法是支持返回 VNode 的(如 summaryMethod、formatter 等),但导出无法解析 VNode,可以这样操作:
<template>
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
:tools="['reload', 'export', 'maximized']"
:show-summary="true"
:summary-method="getSummaries"
:export-config="{ beforeExport: myBeforeExport }">
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref, h } from 'vue';
import { pageUsers } from '@/api/system/user';
const columns = ref([
{
type: 'index',
columnKey: 'index',
width: 48,
align: 'center'
},
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
const datasource = async ({ pages, filter }) => {
return pageUsers({ ...pages });
};
const getSummaries = ({ columns, data }) => {
const sums = [];
columns.forEach((column, index) => {
if (column.property === 'nickname') {
const sumValue = String(20);
const node1 = h('span', { style: { color: 'red' } }, sumValue);
// 给 VNode 增加一个隐藏属性设置导出时的文本
node1.__export_text = sumValue;
sums[index] = node1;
} else if (index === 1) {
const node2 = h('span', { style: { background: 'yellow' } }, '合计');
// 每个 VNode 都是如此
node2.__export_text = '合计';
sums[index] = node2;
}
});
return sums;
};
const myBeforeExport = (params) => {
// 上面是 summaryMethod 中返回的 VNode 所以这里处理的是 footerData 数据
params.footerData.forEach((r) => {
r.forEach((c) => {
// 判断是 VNode 就使用隐藏的属性
if (typeof c.text === 'object' && c.text.__export_text != null) {
c.text = c.text.__export_text;
}
});
});
// 同理列 formatter 中返回的 VNode 需要对 bodyData 数据进行处理
// 同理列 renderHeader 中返回的 VNode 需要对 headerData 数据进行处理
};
</script>
运行代码 新窗口打开
5.2.42.导出时钩子函数
钩子函数 beforeExport 返回的参数数据格式同打印的 beforePrint 参数见上面说明,例如使用 exceljs 导出:
<!-- 默认导出 csv 格式可以阻止默认导出并自定义导出 xlsx 格式, 还可在 App.vue 中全局配置 -->
<template>
<ele-pro-table
row-key="userId"
:columns="columns"
:datasource="datasource"
:export-config="exportConfig"
:tools="['reload', 'export', 'maximized']">
</ele-pro-table>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { pageUsers } from '@/api/system/user';
import ExcelJS from 'exceljs';
import { download } from '@/utils/common';
const columns = ref([
{
type: 'index',
columnKey: 'index',
width: 48,
align: 'center'
},
{
label: '用户账号',
prop: 'username'
},
{
label: '用户名',
prop: 'nickname'
}
]);
const datasource = ({ pages }) => {
return pageUsers({ ...pages });
};
const exportConfig = reactive({
beforeExport: (params) => {
console.log(params); // 这里的参数格式见上面打印的 beforePrint 说明
const { headerData, bodyData, footerData, bodyCols, fileName, closeModal } = params;
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('Sheet1');
const sheetRows = []; // 行数据
const sheetMerges = []; // 设置单元格合并
[...headerData, ...bodyData, ...footerData].forEach((item, index) => {
const sheetCols = []; // 每一行的单元格数据
item.forEach((d, colIndex) => {
sheetCols.push(d.text ?? ''); // 单元格文本
// 单元格合并
if ((d.rowspan && d.rowspan > 1) || (d.colspan && d.colspan > 1)) {
sheetMerges.push([
index + 1,
colIndex + 1,
index + (d.rowspan || 1),
colIndex + (d.colspan || 1)
]);
}
});
sheetRows.push(sheetCols);
});
sheet.addRows(sheetRows);
sheetMerges.forEach((merges) => {
sheet.mergeCells(merges);
});
// 列宽
bodyCols.forEach((col, colIndex) => {
const w = col.width ?? col.minWidth;
sheet.getColumn(colIndex + 1).width = w == null ? void 0 : w / 8;
// exceljs 的列宽设置的是字符数量而不是 px 所以要做些换算
});
workbook.xlsx.writeBuffer().then((data) => {
download(data, ${fileName}.xlsx);
closeModal(); // 导出完关闭导出弹窗
});
return false; // 阻止默认导出行为
}
});
运行代码 新窗口打开
5.2.43.调用打印导出方法
实例方法直接打印数据 printData 和直接导出数据 exportData 的参数说明及使用示例:
/** 表格列配置 */
const columns = ref([
{
type: 'index',
columnKey: 'index',
width: 48,
align: 'center'
},
{
label: '用户账号',
prop: 'username',
sortable: 'custom'
},
{
label: '用户名',
prop: 'nickname',
sortable: 'custom'
}
]);
/** 表格数据源 */
const datasource = ({ pages, orders }) => {
return pageUsers({ ...orders, ...pages });
};
/** 直接打印 */
const printData = () => {
const data = tableRef.value?.getData?.() || [];
tableRef.value?.printData?.({
data: data, // 打印的数据
columns: columns.value, // 打印的列, 可不写, 不写就会取表格的 columns
showHeader: true, // 是否打印表头
showFooter: false, // 是否打印表尾
showTreeIndex: false, // 是否展示层级序号
dataType: 'pageData' // 数据的来源类型, 可不写, 会传递给 beforePrint
});
};
/** 直接导出 */
const exportData = () => {
const data = tableRef.value?.getData?.() || [];
tableRef.value?.exportData?.({
data: data, // 导出的数据
columns: [ // 导出的列, 可不写, 不写就会取表格的 columns
// 也可以重新指定, 例如去掉操作列
...columns.value.filter((c) => c.columnKey !== 'action')
],
showHeader: true, // 是否导出表头
showFooter: false, // 是否导出表尾
showTreeIndex: false, // 是否展示层级序号
fileName: '用户数据', // 导出的文件名
dataType: 'pageData' // 数据的来源类型, 可不写, 会传递给 beforeExport
});
};
</script>
运行代码 新窗口打开