同form,table编写会出现大量的table-column组件,并且没有另外处理分页组件及分页参数,出现大量的重复代码影响美观及代码维护,另外,大量的使用table+分页组件,会出现大量的重复逻辑,容易出现遗漏细节性的低级bug,各个页面分页管理其实基本一致,这里将分页组件及分页参数也封装在此组件内部,调用组件不在需要管理分页参数
此组件在element-ui、ant-design-vue项目中均可直接使用,实现原理 vue3+ts组件库同时兼容多种ui框架
最终实现的效果是这样滴!!!
我们先看下上述效果图的配置化JOSN实例,最终我们将实现所有table表格+分页都能通过这样一些简单的JSON实现渲染,最后通过一个简单的调用即可渲染一个form表单,具体组件的入参如下:
调用
先看配置JSON对象ts接口定义
/*
* @Author: 陈宇环
* @Date: 2023-01-03 10:56:12
* @LastEditTime: 2023-04-25 14:13:53
* @LastEditors: 陈宇环
* @Description:
*/
// table配置参数
export interface tableConfigFace {
border?: boolean, // 是否需要边框
stripe?: boolean, // 是否斑马纹
ifInitLoadData?: boolean, // 是否初始调用getList方法
rowSelection?: rowSelectionFace // 选择行配置
rowKey?: string, // 行对应key值,选择行功能开启时必传
// ant
nativeProps?: { // ui框架原生属性
[key: string]: any
}
}
// 分页配置参数
export interface pagingConfigFace {
open?: boolean, // 是否需要分页
pageIndex?: number, // 默认pageIndex
pageSize?: number, // 默认pageSize
total?: number, // 默认total
showTotal?: any, // ant 属性
showSizeChanger?: boolean, // ant 属性
layout?: string,
pageIndexChange?: (val: number) => any
pageSizeChange?: (val: number) => any
nativeProps?: { // ui框架原生属性
[key: string]: any
}
}
// table列配置
export type theadConfigFace = theadItemConfig[]
// table列配置项item
export interface theadItemConfig {
prop?: string, // key
label?: string, // 中文名称
type?: 'selection' | 'index' | 'expand' // 类型
width?: string | number, // 宽度
minWidth?: string | number, // 最小宽度
align?: 'left' | 'center' | 'right', // 列align布局
fixed?: 'left' | 'right' | true, // 列是否固定在左侧或者右侧,true 表示固定在左侧
render?: (scope: any) => any, // 自定义渲染函数
children?: theadItemConfig[], // 多级头定义
nativeProps?: { // ui框架原生属性
[key: string]: any
}
}
// table多选配置项
export type rowSelectionFace = {
type: 'checkout' | 'radio', // 多选或者单选
onChange:(selection?: any[]) => any, // 选择变化勾选变化事件
selectable?: (row:any, index:number) => boolean // 当前行勾选是否禁用
}
// table数据获取函数返回值校验
export interface resultInt {
success: boolean, // 接口返回状态
list: any[], // table数据列表
total: number // table数据总数
}
// table数据获取函数接口
export type loadDataFace = ({ pageIndex, pageSize }: { pageIndex: number, pageSize: number }) => Promise
本组件将分页参数也全部封装到了组件内部,组件调用不需要处理分页相关参数,只需要传入数据获取函数loadData既可,需要刷新列表或者手动获取某页参数时可以通过BaseTableRef.value.getList({pageIndex: 1,pageSize:20})的方式进行操作,具体实现部分代码如下:
// @/components/BaseTable/index
const pageInfo = reactive({
pageIndex: clonePagingConfig.pageIndex,
pageSize: clonePagingConfig.pageSize,
total: clonePagingConfig.total,
})
const list = ref([])
// 获取数据函数
const getList = async({ pageIndex = pageInfo.pageIndex, pageSize = pageInfo.pageSize } : { pageIndex?: number, pageSize?: number } = {}) => {
try {
loading.value = true
// 使用内部的分页参数来调用外部传入loadData函数,来获取数据
const result = await loadData.value({
pageIndex,
pageSize,
})
loading.value = false
if (result.success) {
list.value = result.list
pageInfo.total = result.total
}
pageInfo.pageIndex = pageIndex
pageInfo.pageSize = pageSize
} catch (error) {
console.log(error)
}
}
// 暴露getList方法给父组件
expose({
getList,
})
onMounted(function() {
// 如果需要默认调用getList
if (cloneTableConfig.ifInitLoadData) {
getList()
}
})
// 分页size变化
const handleSizeChange = (val: number) => {
console.log(`${val} items per page`)
pageInfo.pageIndex = 1
pageInfo.pageSize = val
clonePagingConfig.pageSizeChange && clonePagingConfig.pageSizeChange(val)
getList()
}
// 当前页变化
const handleCurrentChange = (val: number) => {
console.log(`current page: ${val}`)
pageInfo.pageIndex = val
clonePagingConfig.pageIndexChange && clonePagingConfig.pageIndexChange(val)
getList()
}
不在需要复制粘贴table-column组件,通过如下配置既可生成table列
const thead = ref([
{ type: 'index', fixed: 'left' },
{ prop: 'branchCode', label: '分支编码', minWidth: 120 },
{ prop: 'branchName', label: '分支名称', minWidth: 120 },
{
label: '所属区域',
children: [
{ prop: 'regionName', label: '所属区域名称', minWidth: 120 },
{ prop: 'regionCode', label: '所属区域编码', minWidth: 120 },
]
},
{ prop: 'regionSupervisorName', label: '区域主管', minWidth: 120 },
{ prop: 'accountantName', label: '分支核算会计', minWidth: 120 },
{ prop: 'updateUserName', label: '最后修改人', minWidth: 120 },
{ prop: 'updateTime', label: '最后修改时间', minWidth: 160 },
{
label: '操作',
width: 200,
fixed: 'right',
render(scope: any) {
return
{
opFn('edit', scope)
}}>变更
{
opFn('changeRecord', scope)
}}>变更记录
},
},
])
多级头这里我们采用递归的方式,遍历递归thead数组及children属性递归建立table组件,部分代码如下:
// @/components/BaseTable/index
/*遍历thead生成table列*/
{thead.value.map((item: theadItemConfig, index: any) => {
return (
)
})}
// @/components/BaseTable/BaseTableItem
// 导入组件本身
import BaseTableItem from './BaseTableItem'
// 多级头处理
const childrenDom = itemData.value.children && itemData.value.children.length > 0
? itemData.value.children.map((item:any, index:any) => (
))
: null
return () => {
return (
{
return <>
{
itemData.value.render ?
(typeof itemData.value.render === 'function' ? itemData.value.render(scope) : itemData.value.render) :
scope.row[itemData.value.prop]
}
{/* 多级头部 */}
{childrenDom}
>
},
}}
>
)
}
上述代码中childrenDom用来处理多级头部,如果配置thead中存在children则代表存在多级头部,递归children既可
tableConfig、pagingConfig已经thead的每一项分别兼容element原生el-table、pagination、el-table-column组件所有属性实现方式
实现方式也很简单,在tsx中通过扩展符展开既可
handleSelectionChange(val)}
此功能只有选用element-ui时才支持
element-ui 原生单选是点击行选择然后高亮,个人觉得不是很友好
行单选?如下图:
实现
handleSelectionChange(val)}
>
{/* 只有el-ui走这段渲染逻辑,ant-Design-vue是通过columns直接生成的 */}
{CustomDynamicComponent.language === CustomDynamicComponent.eleLanguage ? <>
{/* 需要多选行选择按钮 */}
{cloneTableConfig.rowSelection && cloneTableConfig.rowSelection.type === 'checkout' ? (
{
return cloneTableConfig.rowSelection?.selectable ? cloneTableConfig.rowSelection?.selectable(row, index) : true
}} />
) : null}
{/* 需要单选行选择按钮 */}
{cloneTableConfig.rowSelection && cloneTableConfig.rowSelection.type === 'radio' ? (
{
return (
handleSelectionChange(val)}
>
)
},
}}
>
) : null}
{columns.value.map((item: theadItemConfig, index: any) => {
return (
// 递归组件
)
})}> : null}
{
clonePagingConfig.open &&
handleSizeChange(val)}
onCurrentChange={(val: any) => handleCurrentChange(val)}
// ant-ui相关属性
current={pageInfo.pageIndex}
onShowSizeChange={(current: number, size: number) => handleSizeChange(size)}
onChange={(page:number) => handleCurrentChange(page)}
/>
}
多选:沿用ui框架原生 type="selection"属性来实现
单选通过:自定义radio组件来实现
props: {
tableConfig: {
type: Object as PropType,
default() {
return {
border: true,
stripe: true,
ifInitLoadData: true,
rowKey: 'id',
}
},
},
},
默认参数如上,当传参如下时:
此时:组件内部props拿到的tableConfig会被整体替换成{ifInitLoadData: false},tableConfig默认值对象里的其他字段全部为空了
props: {
tableConfig: {
type: Object as PropType,
default() {
return {
border: true,
stripe: true,
ifInitLoadData: true,
rowKey: 'id',
}
},
},
},
...
const defaultTableConfig: tableConfigFace = {
border: true,
stripe: true,
ifInitLoadData: true,
rowKey: 'id',
rowSelection: {
type: 'checkout',
onChange: (selection: any) => {
console.log(selection)
},
},
}
const cloneTableConfig: tableConfigFace = reactive({
...defaultTableConfig,
...props.tableConfig,
})
watch(
() => props.tableConfig,
() => {
Object.assign(cloneTableConfig, defaultTableConfig, props.tableConfig)
},
{ immediate: true, deep: true },
)
https://blog.csdn.net/junner443/article/details/131302051
作者:快落的小海疼