引言
前后端配合分页表格的二次封装一直是前端头痛的事情
普遍的UI组件的表格插件实现功能良莠不齐,项目实现代码过多使用插槽和render配置,使代码冗余
一个项目中的表格实现方案不统一,甚至分页参数每个接口都不一致,看上去就急需优化。。
鉴于vxe-table 的表格实现方案全面,这里我们采取vxe-table
的vxe-grid
表格组件二次封装,主要解决的分页前后端约定配置问题,灵活定制数据结构,适用于大型金融PC前端管理系统的通用表格方案
简介
- 基于vue2+
vxe-table
的vxe-grid
表格组件封装 - 主要功能如下
- 外部请求 非分页
- 外部请求 前端分页
- 外部请求 后端分页
- 内部请求 非分页
- 内部请求 前端分页
- 内部请求 后端分页
- render渲染
- slot渲染
- 空数据:三个方面限制
- formatter
- slots slot
- slots render
- 数据自定义格式化
- 冻结列配置
- 虚拟滚动配置
- 筛选配置(前端筛选,后端筛选)待扩展
- 其他功能参考 https://vxetable.cn/#/grid/api 自行扩展
约定
主要约定如下
- 非分页请求参数约定
{
"body":{
// ...params
},
"head":{}
}
- 非分页返回参数约定
{
"head":{},
"body":{
"data":[
// {},
]
}
}
- 分页请求参数约定
{
"body":{
"pageNum":1,
"pageSize":10
// ...other params
},
"head":{}
}
- 分页返回参数
{
"head":{},
"body":{
"data":[
// {},
],
"total":1000,
"pageSize":10
}
}
说明文档使用示例
- template
<template>
<div class="virtual">
虚拟表格测试
<button @click="searchData">外部查询</button>
<button @click="update">内部查询</button>
<button @click="reset">重置</button>
<input type="text" v-model="searchText" />
<!-- 1. 外部请求 非分页 -->
<!-- <VirtualTable ref="virtualTable" :columns="columns" :data="dataList" getDataListFromBodyKeysStr="data" @change="tableChange">
</VirtualTable> -->
<!-- 2. 外部请求 前端分页 -->
<!-- <VirtualTable ref="virtualTable" :columns="columns" :data="dataList" getDataListFromBodyKeysStr="data" frontPage @change="tableChange">
</VirtualTable> -->
<!-- 3. 外部请求 后端分页 -->
<!-- <VirtualTable ref="virtualTable" :columns="columns" :data="dataList" getDataListFromBodyKeysStr="data"
:otherParams="{ name: 'virtualTable' }" endPage @change="tableChange">
</VirtualTable> -->
<!-- 4. 内部请求 非分页 -->
<!-- <VirtualTable ref="virtualTable" :columns="columns" :apiRequestPromiseFun="tablePageListApi"
:otherParams="{ name: 'virtualTable' }" :autoRequest="true"
@change="tableChange">
</VirtualTable> -->
<!-- 5. 内部请求 前端分页 -->
<!-- <VirtualTable ref="virtualTable" :columns="columns" :apiRequestPromiseFun="tablePageListApi" frontPage
:otherParams="{ name: 'virtualTable' }" :autoRequest="true"
@change="tableChange">
</VirtualTable> -->
<!-- 6. 内部请求 后端分页 -->
<!-- <VirtualTable ref="virtualTable" :columns="columns" :apiRequestPromiseFun="tablePageListApi" endPage
:otherParams="{ name: 'virtualTable' }" :autoRequest="true" @change="tableChange">
</VirtualTable> -->
<!-- 7. 插槽渲染 -->
<!-- <VirtualTable ref="virtualTable" :columns="columns" :apiRequestPromiseFun="tablePageListApi" endPage
:otherParams="{ name: 'virtualTable' }" :autoRequest="true" :showFooter="true" :footerMethod="footerMethod"
@change="tableChange">
<template #cell-empty="data"> {{ data.row }} </template>
<template #name-header="data"> {{ 'name' + data.columnIndex }} </template>
<template #name-footer="data"> {{ 'footer>>' + JSON.stringify(Object.keys(data)) }} </template>
</VirtualTable> -->
<!-- 8. render渲染 -->
<!-- 略... 查看 https://vxetable.cn/#/grid/api -->
<!-- 9. 空数据拦截-三种空数据拦截 formatter/slots/render -->
<!-- <VirtualTable ref="virtualTable" :columns="columns" :apiRequestPromiseFun="tablePageListApi" endPage
:otherParams="{ name: 'virtualTable' }" :autoRequest="true" @change="tableChange">
<template #amount10slot="data">
{{ `slot定义插槽:${data.cellValue}` }}
</template>
</VirtualTable> -->
<!-- 9. 字典转义-三种数据字典结构 -->
<!-- <VirtualTable ref="virtualTable" :columns="columns" :apiRequestPromiseFun="tablePageListApi" endPage
:otherParams="{ name: 'virtualTable' }" :autoRequest="true" @change="tableChange">
</VirtualTable> -->
<!-- 10. 冻结列配置 fixed / 虚拟滚动配置 scrollX scrollY [已经默认开启] -->
<!-- <VirtualTable ref="virtualTable" :columns="columns" :apiRequestPromiseFun="tablePageListApi" endPage
:otherParams="{ name: 'virtualTable' }" :autoRequest="true" @change="tableChange">
</VirtualTable> -->
<!-- 11. 外部筛选-适用于前端分页或者不分页的模式,因为后端分页的分页参数取决于后端 -->
<!-- <VirtualTable ref="virtualTable" :columns="columns" :apiRequestPromiseFun="tablePageListApi" endPage
:otherParams="{ name: 'virtualTable' }" :autoRequest="true"
:filterGridOptionsDataMethod="filterGridOptionsDataMethod" @change="tableChange">
</VirtualTable> -->
<VirtualTable ref="virtualTable" :columns="columns" :data="dataList" getDataListFromBodyKeysStr="data" frontPage
:filterGridOptionsDataMethod="filterGridOptionsDataMethod" @change="tableChange">
</VirtualTable>
</div>
</template>
- script
/**
* [虚拟滚动表格二次封装组件-功能目录]
* 1. 外部请求 非分页
* 2. 外部请求 前端分页
* 3. 外部请求 后端分页
* 4. 内部请求 非分页
* 5. 内部请求 前端分页
* 6. 内部请求 后端分页
* 7. render渲染
* 8. slot渲染
* 9. 空数据:三个方面限制
* 1. formatter
* 2. slots slot
* 3. slots render
* 10. 数据自定义格式化
* 11. 冻结列配置
* 12. 虚拟滚动配置
* 13. 筛选配置(前端筛选,后端筛选)待扩展
* ... 其他功能参考 https://vxetable.cn/#/grid/api 自行扩展
*/
import VirtualTable from './Virtual-table-coms';
/**
* 转义字典配置
*/
const types1 = {
1: 'one',
2: 'two',
3: 'three',
4: 'four',
}
const types2 = {
A: { text: '一', value: '1' },
B: { text: '二', value: '2' },
C: { text: '三', value: '3' },
D: { text: '四', value: '4' },
}
const types3 = [
{ text: '1111', value: '1' },
{ text: '2222', value: '2' },
{ text: '333', value: '3' },
{ text: '4444', value: '4' },
]
const formatEmpty = (v) => {
if (v === '' || v === null || v === undefined || isNaN(v)) {
return '--';
} else {
return v;
}
}
export default {
name: 'VirtualTable-test',
components: {
VirtualTable
},
props: {
},
mounted() {
// let res = this.$HttpTool.post('table-list/', { name: 'abc' });
// let res2 = this.$HttpTool.post('table-page-list/', { name: 'abc' });
// console.log(res)
// console.log(res2)
},
data() {
return {
searchText: '',
rowConfig: {
useKey: true,
keyField: 'uuid',
},
dataList: [],
columns: [
{
field: "id",
title: "字段名id",
minWidth: '150px',
slots: {
default: ({ row }) => {
return [<div style={{ color: 'red' }}>{row.id}</div>]
},
header: () => {
return [<span>{"自定义表头字段名ID"}</span>]
}
},
fixed: "left"
},
{ field: "uuid", title: "字段名uuid" },
{
field: "name", title: "字段名name",
// 自定义格式化渲染
formatter: ({ cellValue: v }) => {
return 'formatter>>>' + formatEmpty(v);
},
slots: {
// slot 渲染
// default: ({ row, column }) => {
// const v = row[column.property];
// return 'slots>>>'+formatEmpty(v)
// }
// 插槽渲染
// default: 'cell-empty',
// header: 'name-header',
// footer: 'name-footer',
}
},
{ field: "amount1", title: "字段名amount1", formatterType: 'options', formatterOptions: types1, minWidth: '150px' },
{ field: "amount2", title: "字段名amount2", formatterType: 'options', formatterOptions: types1, minWidth: '150px' },
{ field: "amount3", title: "字段名amount3", minWidth: '150px' },
{ field: "amount4", title: "字段名amount4", formatterType: 'options', formatterOptions: types2, minWidth: '150px' },
{ field: "amount5", title: "字段名amount5", formatterType: 'options', formatterOptions: types2, minWidth: '150px' },
{ field: "amount6", title: "字段名amount6", formatterType: 'options', formatterOptions: types3, minWidth: '150px' },
{ field: "amount7", title: "字段名amount7", formatterType: 'options', formatterOptions: types3, minWidth: '150px' },
{
field: "amount8", title: "字段名amount8", formatterType: 'custom', formatter: ({ cellValue: v }) => {
return '自定义格式化' + v;
}
},
{
field: "amount9",
title: "字段名amount9--",
minWidth: '150px',
// slotDefaultType:'custom',
slots: {
default: ({ ...p }) => {
const { row, column } = { ...p };
// console.log({ row, column })
// console.log(this.columns[columnIndex])
const v = row[column.property];
return 'slots-render>>>' + formatEmpty(v)
}
}
},
{
field: "amount10", title: "字段名amount10",
minWidth: '150px',
// slotDefaultType:'custom',
slots: {
default: 'amount10slot'
}
},
{ field: "amount11", title: "字段名amount11" },
{ field: "amount12", title: "字段名amount12" },
{ field: "amount13", title: "字段名amount13" },
{ field: "amount14", title: "字段名amount14" },
{ field: "amount15", title: "字段名amount15" },
{ field: "amount16", title: "字段名amount16" },
{ field: "amount17", title: "字段名amount17" },
{ field: "amount18", title: "字段名amount18" },
{ field: "amount19", title: "字段名amount19" },
{ field: "amount20", title: "字段名amount20" },
{ field: "amount21", title: "字段名amount21" },
{ field: "amount22", title: "字段名amount22" },
{ field: "amount23", title: "字段名amount23" },
{ field: "amount24", title: "字段名amount24" },
{ field: "amount25", title: "字段名amount25" },
{ field: "amount26", title: "字段名amount26" },
{ field: "amount27", title: "字段名amount27" },
{ field: "amount28", title: "字段名amount28" },
{ field: "amount29", title: "字段名amount29" },
{ field: "amount30", title: "字段名amount30", minWidth: '150px', fixed: "right" },
]
};
},
watch: {
searchText() {
this.$refs['virtualTable'].outerFilterUpdate();
}
},
methods: {
filterGridOptionsDataMethod(item) {
return item.uuid.includes(this.searchText)
},
footerMethod({ columns, data }) {
return [
columns.map(column => {
if (['sex', 'num'].includes(column.field)) {
return data
}
return null
})
]
},
tableChange(d, p, r) {
console.log(d, p, r);
{/* this.tableUpdateData(v); // 外部数据传入,后端分页,需要在这里再次查询 */ }
},
searchData() {
console.log('查询表格');
this.tableUpdateData({ name: '外部请求非分页' });
},
async tableUpdateData(p) {
try {
this.$refs['virtualTable'].setLoading(true);
// let res = await this.$HttpTool.post('table-list/', p);
let res = await this.$HttpTool.post('table-page-list/', p);
this.dataList = res.data.body;
console.log(this.dataList)
} catch (e) {
console.error(e)
} finally {
this.$refs['virtualTable'].setLoading(false);
}
},
tableListApi(p) {
return this.$HttpTool.post('table-list/', p);
},
tablePageListApi(p) {
return this.$HttpTool.post('table-page-list/', p);
},
update() {
console.log('update')
this.$refs['virtualTable'].requestList(); // 主动跟新
},
reset() {
console.log('resetTable')
this.$refs['virtualTable'].resetTable(); // 主动重置
}
}
}
- style
.virtual {
min-width: 1200px;
}
</style>
- 介绍和使用文档到此结束,接下来上源码
- template
<template>
<div class="virtual-table">
<vxe-grid ref="vxe-grid" v-bind="{ ...gridOptions, scrollY, scrollX, ...$attrs }" v-on="$listeners">
<div slot="empty">空白数据定制模版</div>
<template v-for="defaultName in defaultSlots" v-slot:[defaultName]="{ row, column, rowIndex, columnIndex }">
<!-- 默认插槽空值处理 -->
<template v-if="columns[columnIndex]?.slotDefaultType === 'default'">
<template v-if="!tableCellEmpty(row[column.property])">
<EmwAmount v-if="defaultName === 'EmwAmount'" :key="`default_${defaultName}_EmwAmount`" slot="EmwAmount"
v-bind="{ value: row[column.property], ...columns[columnIndex].defaultSlotProps }">
</EmwAmount>
<slot v-else :name="defaultName" :row="row" :column="column" :rowIndex="rowIndex" :columnIndex="columnIndex"
:cellValue="row[column.property]"></slot>
</template>
<span v-else :key="`default_${defaultName}_empty`">{{ '--' }}</span>
</template>
<!-- 如果为自定义插槽类型没有空值处理 -->
<template v-else>
<slot :name="defaultName" :row="row" :column="column" :rowIndex="rowIndex" :columnIndex="columnIndex"
:cellValue="row[column.property]"></slot>
</template>
</template>
<template v-for="headerName in headerSlots" v-slot:[headerName]="{ row, column, rowIndex, columnIndex }">
<slot :name="headerName" :row="row" :column="column" :rowIndex="rowIndex" :columnIndex="columnIndex"></slot>
</template>
<template v-for="footerName in footerSlots" v-slot:[footerName]="{ row, column, rowIndex, columnIndex }">
<slot :name="footerName" :row="row" :column="column" :rowIndex="rowIndex" :columnIndex="columnIndex"></slot>
</template>
<template #pager>
<vxe-pager v-if="ifPage && gridOptions?.data?.length" background
:layouts="['Total', 'Sizes', 'PrevJump', 'PrevPage', 'Number', 'NextPage', 'NextJump', 'FullJump']"
:current-page.sync="tablePage.currentPage" :page-size.sync="tablePage.pageSize" :total="tablePage.total"
:page-sizes="tablePage.pageSizes" align="center" @page-change="handlePageChange">
<div slot="right">{{ `共${totalPageNum}页` }}</div>
</vxe-pager>
<div class="foot-total-tag" v-if="ifPage && footTotalTag && gridOptions?.data?.length">{{
`共${tablePage.total || 0}条记录` }}</div>
</template>
</vxe-grid>
</div>
</template>
- script
const getObjectByKeys = (obj, keys) => {
const len = keys.length;
if (len === 0) return obj;
const tK = keys.shift();
if (tK in obj) {
return getObjectByKeys(obj[tK], keys);
} else {
return undefined;
}
}
const splitStrByChar = (str, tag = '.') => {
return str?.split(tag) || [];
}
const tableCellEmpty = (v) => {
return v === '' || v === undefined || v === null;
}
/**
* 为了让虚拟滚动表格更好适应项目
* 在虚拟滚动表格插槽中植入一些高频组件
* 配置插槽时直接用
* columns:{
* defaultSlotProps:{...组件的props}
* slots:{
* default:'EmwAmount'
* }
* }
*/
const CommonComNames = ['EmwAmount'];
import EmwAmount from '../EmwAmount.vue'
export default {
components: {
EmwAmount
},
props: {
/**
* 外部传入展示数据
* 支持数组和对象,如果为对象要使用 getDataListFromBodyKeysStr 取值
* 最终传给表格的data一定是数组格式
*/
data: {
type: [Array, Object],
default: () => []
},
columns: {
type: Array,
default: () => []
},
loading: {
type: Boolean,
default: false
},
/**
* 当需要表格内部查询数据需要传入一个查询函数
* 返回一个promise,查询成功后会抛出 on-change 事件返回查询结果(body)
*/
apiRequestPromiseFun: {
type: [Function, undefined],
// default: () => Promise.resolve(true)
default: undefined
},
/**
* 有些时候后端数给的并不规范,如果需要表格内部查询,就必须保证取得正确的key
* 默认为body.data ,但有些时候 可能是层级比较深的数据 比如说 body.fundsA.list
* 那么这个字符串应该取 “fundA.list”
* 前端上送的分页参数也是依据后端制定的,所以如果是前度分页,
* 那么抛出的分页参数默认为 keysStr 是由 ‘.’ 分割的最后一端字符串
* 如 'data.body.pageSize' => pageSize
*/
getDataListFromBodyKeysStr: {
type: String,
default: 'data.body.data'
},
getPageNumFromBodyKeysStr: {
type: String,
default: 'data.body.pageNum'
},
getPageSizeFromBodyKeysStr: {
type: String,
default: 'data.body.pageSize'
},
getTotalFromBodyKeysStr: {
type: String,
default: 'data.body.total'
},
/**
* 是否前端分页
* 前端处理分页,展示分页组件
* 后端数据结构不变
*/
frontPage: {
type: Boolean,
default: false
},
/**
* 是否后端分页
* 后端处理分页,要与前端约定好数据结构
* 展示分页组件
*/
endPage: {
type: Boolean,
default: false
},
/**
* 非分页时是否展示记录数量
*/
footTotalTag: {
type: Boolean,
default: true
},
/**
* 默认初始化分页设置
* 一般也就配置 pageSize 和 pageSizes
*/
pageOptions: {
type: Object,
default: () => ({
pageSize: 23,
pageSizes: [10, 23, 30, 50, 60, 100, 200]
})
},
/**
* 除了分页参数以外的其他参数
*/
otherParams: {
type: [Object, null],
default: () => null
},
/**
* 当内部请求时是否自动请求
* 如果手动请求请使用 requestList
*/
autoRequest: {
type: Boolean,
default: true
},
/**
* 虚拟滚动配置
* 默认开启
*/
scrollY: {
type: Object,
default: () => ({
enabled: true,
gt: 30,
oSize: 0
})
},
scrollX: {
type: Object,
default: () => ({
enabled: true,
gt: 20,
oSize: 0
})
},
/**
* 外部筛选函数配置
* 适用于前端自己的筛选显示
* 外部要配置筛选调教
* 配合 outerFilterUpdate 对外api使用
*/
filterGridOptionsDataMethod: {
type: Function,
default: () => true
},
/**
* 外部列表处理函数配置
* 有些接口返回数据不理想,需要表格自动处理成理想的数据列表
* 最好要求后端处理返回理想的数据,实在不行出此下策
*/
mapGridOptionsDataListFactory: {
type: Function,
default: i => i
}
},
watch: {
data(v) {
this.outerRequestDataTableInit(v);
},
columns: {
handler(val) {
/**
* 列配置变化,表格变化
*/
this.gridOptions.columns = val || [];
this.columnsInit();
},
immediate: true
},
loading(val) {
/**
* 外部配置loading,表格进入加载状态
*/
// console.log('loading change>>', val);
this.gridOptions.loading = val;
}
},
computed: {
/**
* 根据 page 参数在返回体中的取值字符串获取分页参数
* 分页参数默认配置 pageSize pageNum, total
*/
pageNumKey() {
return this.getPageNumFromBodyKeysStr?.split('.')?.pop() || 'pageNum';
},
pageSizeKey() {
return this.getPageSizeFromBodyKeysStr?.split('.')?.pop() || 'pageSize';
},
totalKey() {
return this.getTotalFromBodyKeysStr?.split('.')?.pop() || 'total';
},
ifPage() {
return this.frontPage || this.endPage;
},
totalPageNum() {
return Math.ceil(this.tablePage?.total / this.tablePage?.pageSize);
},
/**
* 获取数据模式
* 1. 外部传入data数据
* 如果data为数组,那么直接渲染表格
* 如果data为对象,需要传入 getDataListFromBodyKeysStr 由内部取值
* 2. 内部请求获取列表数据
* 需要传入 getDataListFromBodyKeysStr 取值,getDataListFromBodyKeysStr默认为 ‘data’
*/
getDataModal() {
return typeof this.apiRequestPromiseFun === 'function' ? 'inter_request_data' : 'exter_props_data'
},
pageRequestParams() {
const { currentPage, pageSize } = this.tablePage;
return { [this.pageNumKey]: currentPage, [this.pageSizeKey]: pageSize, ...this.otherParams };
},
/**
* 插槽名获取 start
*/
defaultSlots() {
return this.slotsFilter('default')
},
headerSlots() {
return this.slotsFilter('header')
},
footerSlots() {
return this.slotsFilter('footer')
},
/**
* 插槽名获取 end
*/
},
data() {
return {
// 内部请求数据
inerRequestData: null,
tablePage: {
total: 0,
currentPage: 1,
pageSize: 10,
pageSizes: [10, 30, 50, 100, 200]
},
gridOptions: {
border: false,
resizable: true,
showOverflow: true,
loading: false,
height: 560,
align: 'center',// 表头位置居中
showHeaderOverflow: true,//表头单行,悬浮显示
columns: [],
data: []
}
}
},
created() {
this.resetTablePage();
// 内部请求数据
if (this.getDataModal === 'inter_request_data' && this.autoRequest) {
this.requestList();
}
},
mounted() {
// this.throwCurrentPageParams();
},
methods: {
outerRequestDataTableInit(v) {
// 外部传入数据
/**
* 直接传入数据渲染时
* 数据变动,表格的数据重新刷新
* 如果外部传入的是数组,那就直接取
* 如果外部传入的非数组,就要依赖 getDataListFromBodyKeysStr 取值
*/
if (!v) return;
const list = this.tableListData(v);
!this.frontPage && (this.gridOptions.data = list);
this.tablePage.total = list.length;
/**
* 如果是前端分页需要做数据处理
*/
this.frontPage && this.seqDataMethod(v);
this.throwCurrentPageParams();
},
interRequestDataTableInit() {
// 内部请求数据
/**
* 内部请求数据渲染
*/
if (this.inerRequestData === null) return;
const list = this.tableListData(this.inerRequestData);
this.gridOptions.data = list;
if (this.frontPage) {
this.tablePage.total = list.length;
this.seqDataMethod(this.inerRequestData);
}
if (this.endPage) {
this.tablePage.total = getObjectByKeys(this.inerRequestData, splitStrByChar(this.getTotalFromBodyKeysStr)) || 0;
}
this.throwCurrentPageParams();
},
/**
* 对外API
* 当配置了 filterGridOptionsDataMethod 时
* 主动触发筛选数据
* 无论是内部请求还是外部请求,都不做请求,直接进行数据筛选
* 但一定要重置分页参数
*/
outerFilterUpdate() {
this.resetTablePage();
if (this.getDataModal === 'inter_request_data') {
// 内部请求
this.interRequestDataTableInit();
} else {
// 外部请求
this.outerRequestDataTableInit(this.data);
}
this.$refs['vxe-grid'].updateData();
},
tableCellEmpty,
columnsInit() {
/**
* 空数据默认格式化
*/
this.formatEmptyColumns();
/**
* 扩展通用组件自定义参数
*/
this.addSlotPropsColumns();
},
formatEmptyColumns() {
this.columns.forEach(col => {
/**
* 没有插槽设置的时候采用的是 formatter 渲染
* 涉及相关配置项
* 原生 formatter [function]
* 拓展 formatterOptions [Object/Array] 下文有注释
* 拓展 formatterType [enum: 'default'/'custom'/'options']
*/
if (!col?.slots?.default) {
const f = col?.formatter;
/**
* 格式化类型枚举
* 默认 default (如果空值显示 ‘--’)
* 自定义 custom (不会添加空值 ‘--’ 机制)
* 枚举 options (根据字典转义码值)
*/
const formatterOptions = col?.formatterOptions;
const type = col?.formatterType || 'default';
const emptyF = ({ cellValue: v }) => tableCellEmpty(v) ? '--' : v;
if (typeof f === 'function') {
// 如果定义了格式化,则对格式化函数进行一次包装
if (type === 'default') {
col.formatter = ({ ...p }) => {
const { cellValue: v } = p;
return tableCellEmpty(v) ? '--' : f({ ...p });
}
} else if (type === 'custom') {
// 如果是自定义直接返回
col.formatter = ({ ...p }) => {
return f({ ...p });
}
}
} else if (!f) {
if (type !== 'options') {
// 如果没有设置格式化,则设置默认值
col.formatter = emptyF;
} else {
// 定义了options 就根据options进行 码-值 转义
if (Array.isArray(formatterOptions)) {
// formatterOptions 兼容
// [
// {value:<code>,text:<label>},
// {value:<code>,text:<label>},
// ...
// ]
// 数据格式
col.formatter = ({ ...p }) => {
const { cellValue: v } = p;
// console.log(v, formatterOptions)
return formatterOptions.find(i => i?.value === v)?.text || '--';
}
} else if (typeof formatterOptions === 'object') {
const keys = Object.keys(formatterOptions);
col.formatter = ({ ...p }) => {
const { cellValue: v } = p;
if (tableCellEmpty(v) || !keys.includes(v)) {
// return JSON.stringify(keys);
return '--';
} else {
if (typeof formatterOptions[v] === 'object') {
// formatterOptions 兼容
// {
// <code1>:{text:<label>,value:<code>},
// <code2>:{text:<label>,value:<code>},
// ...
// }
// 数据格式
return formatterOptions[v]?.text || '--';
} else {
// formatterOptions 兼容
// {
// <code1>:value1,
// <code2>:value2,
// ...
// }
// 数据格式
return formatterOptions[v] || '--';
}
}
}
}
}
}
}
/**
* 有render函数时涉及配置参数
* 拓展 slotDefaultType [enum: 'default'/'custom']
* 当 default 时 数据为空默认 ‘--’
* 当为 custom 时 自定义
*/
const slotDefaultType = col?.slotDefaultType || 'default';
col.slotDefaultType = slotDefaultType; // 这里赋值,模版需要做判断
if (typeof col?.slots?.default === 'function' && slotDefaultType === 'default') {
const render_f = col?.slots?.default;
col.slots.default = ({ ...p }) => {
const { row, column } = p;
const v = row[column.property];
return tableCellEmpty(v) ? '--' : render_f({ ...p });
}
}
/**
* 有插槽,如果数据为空,数据默认 ‘--’
*/
// if(typeof col?.slots?.default === 'string' && slotDefaultType === 'default'){
// 具体实现在模版里实现
// }
// if(slotDefaultType === 'custom'){
// 不做任何操作
// }
});
},
addSlotPropsColumns() {
this.columns.forEach(col => {
/**
* 如果配置有通用插槽组件的话,默认props为 defaultSlotProps
*/
if (CommonComNames.includes(col?.slots?.default)) {
col.defaultSlotProps = col?.defaultSlotProps || {}
}
})
},
slotsFilter(slotType) {
return this.columns.filter(i => i?.slots && i?.slots[slotType] && typeof i?.slots[slotType] === 'string').map(x => x?.slots[slotType]);
},
async requestList() {
if (this.getDataModal === 'exter_props_data') throw new Error('参数错误,未配置内部请求api');
this.gridOptions.loading = true;
try {
const params = this.endPage ? this.pageRequestParams : this.otherParams;
this.inerRequestData = await this.apiRequestPromiseFun(params);
this.interRequestDataTableInit();
} catch (e) {
this.resetTable(false);
console.log(e);
} finally {
this.gridOptions.loading = false;
}
},
resetTable(ifRequestTag = true) {
this.gridOptions.data = [];
this.inerRequestData = null;
this.setLoading(false);
this.resetTablePage();
ifRequestTag && this.autoRequest && this.getDataModal === 'inter_request_data' && this.requestList();
},
tableListData(v) {
const originList = Array.isArray(v) ? v : (getObjectByKeys(v, splitStrByChar(this.getDataListFromBodyKeysStr)) || []);
return originList.filter(this.filterGridOptionsDataMethod).map(this.mapGridOptionsDataListFactory);
},
resetTablePage(p = {}) {
this.tablePage = {
total: 0,
currentPage: 1,
pageSize: this.pageOptions.pageSize[0],
pageSizes: this.pageOptions.pageSizes,
...this.pageOptions,
...p
}
},
seqDataMethod(data) {
this.gridOptions.data = this.tableListData(data).filter((item, rowIndex) => {
const s = (this.tablePage.currentPage - 1) * this.tablePage.pageSize;
const e = this.tablePage.currentPage * this.tablePage.pageSize;
return s <= rowIndex && rowIndex < e;
});
// console.log(this.gridOptions.data)
},
throwCurrentPageParams() {
const tableData = this.gridOptions.data;
let requestParams;
let pageParams;
// 分页-前端分页
if (this.ifPage && this.frontPage) {
requestParams = this.otherParams;
pageParams = this.tablePage;
}
// 分页-后端分页
if (this.ifPage && this.endPage) {
requestParams = this.pageRequestParams;
pageParams = this.tablePage;
}
// 非分页
if (!this.ifPage) {
requestParams = this.otherParams;
pageParams = null;
}
this.$emit('change', tableData, requestParams, pageParams);
},
async handlePageChange({ currentPage, pageSize }) {
// console.log({ currentPage, pageSize })
this.tablePage.currentPage = currentPage;
this.tablePage.pageSize = pageSize;
if (this.getDataModal === 'exter_props_data') {
// 如果是后端分页,外部传入数据的话,直接抛出分页参数即可
this.frontPage && this.seqDataMethod(this.data);
this.throwCurrentPageParams();
} else if (this.getDataModal === 'inter_request_data') {
if (this.frontPage) {
this.seqDataMethod(this.inerRequestData);
this.throwCurrentPageParams();
}
// 如果是内部请求数据又是后端分页,那么必须在request响应结果后才能跑出change事件
this.endPage && await this.requestList();
}
},
getGridTable() {
return this.$refs['vxe-grid'];
},
/**
*
* @param {外部设置loading} loading
* 支持两种设置loading的模式,还可以直接传入props:loading设置
*/
setLoading(loading) {
this.gridOptions.loading = loading;
}
}
}
- style
.virtual-table {
width: 100%;
.vxe-pager .vxe-pager--right-wrapper {
&>div {
line-height: 2.2;
}
margin: 0;
}
.foot-total-tag {
text-align: right;
}
}