介绍: 这是基于 vue3 + el-table 封装的通用表格组件 的 tsx写法,想要参考模板写法的可以到我另一篇博客喔~
【通用表格组件】vue3 + element-ui + template 实现通用表格组件
话不多说,本组件分为以下四部分,并且在同一层级:
1、CommonTable.module.scss 文件为组件样式文件
:global {
.common-table {
.el-table__header,
.el-table__body {
margin: 0;
}
.el-table::before {
height: 0;
}
.el-button {
padding: 0;
border: none;
margin: 0 4px;
padding: 0 4px 0 8px;
border-left: 1px solid #e2e2e2;
font-size: 14px;
min-height: 14px;
&:first-child {
border-left: none;
}
}
.el-button+.el-button {
margin-left: 0;
}
.btn-right div {
margin-right: 5px;
}
.btn-right div:empty {
margin-right: 0px;
}
//斑马纹表格背景色
.el-table .even-row {
--el-table-tr-background-color: #f5fafb;
}
.el-table .odd-row {
--el-table-tr-background-color: #ffffff;
}
.el-table--border::after,
.el-table--group::after {
width: 0;
}
.el-table__fixed-right::before,
.el-table__fixed::before {
background-color: transparent;
}
.custom-table-header {
th {
background-color: #fff4d9 !important;
}
}
.progress-line {
.el-progress-bar__outer {
height: 16px !important;
}
.el-progress-bar__outer,
.el-progress-bar__inner {
border-radius: 0 !important;
}
}
.text-no-wrap {
cursor: pointer;
display: inline;
}
.el-table {
td.el-table__cell div,
th.el-table__cell>.cell {
font-size: 14px;
}
th.el-table__cell>.cell {
font-weight: normal;
}
.cell {
padding: 0 10px;
line-height: 39px;
}
.el-table__header-wrapper .checkBoxRadio .el-checkbox {
display: none;
}
.el-checkbox {
display: flex;
align-items: center;
justify-content: center;
}
.table-img {
width: 60px;
height: 60px;
object-fit: cover;
padding: 6px 0;
display: flex;
align-items: center;
margin: 0 auto;
justify-content: center;
}
}
.el-table--small .el-table__cell {
padding: 0;
}
.el-dropdown-menu__item {
padding: 5px 10px !important;
.el-button {
width: 100%;
text-align: center;
padding: 0 8px;
margin: 0;
}
}
.flex-box {
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
.item {
margin: 0 10px;
}
}
}
.el-table{
.el-table__expand-icon{
.el-icon-arrow-right{
display: none;
}
}
}
}
2、CommonTable.module.ts为组件逻辑文件
import {
ref,
watch,
nextTick
} from 'vue';
export default function(props, emit, commonTable) {
const curPageCheck = ref([])
const selectId = ref()
watch(() => props.selectRadioId, () => {
selectId.value = props.selectRadioId
}, { immediate: true })
watch(() => props.data, () => {
if (props.showCheckBox || props.turnRadio) {
nextTick(() => {
commonTable.value.clearSelection()
curPageCheck.value = []
if (props.showCheckBox && props.turnRadio) {
props.data.filter((item) => {
if (item.id === props.selectedIdArr[0]) {
commonTable.value.toggleRowSelection(item, true)
}
})
} else if (props.showCheckBox) {
props.data.filter((item) => {
if (props.selectedIdArr.includes(item.id)) {
commonTable.value.toggleRowSelection(item, true)
curPageCheck.value.push(item.id)
}
})
}
})
}
}, {
immediate: true
})
watch(() => props.selectedIdArr, (val) => {
if (props.showCheckBox || props.turnRadio) {
nextTick(() => {
commonTable.value.clearSelection()
curPageCheck.value = []
if (props.showCheckBox && props.turnRadio) {
props.data.filter((item) => {
if (item.id === val[0]) {
commonTable.value.toggleRowSelection(item, true)
}
})
} else if (props.showCheckBox) {
props.data.filter((item) => {
if (val.includes(item.id)) {
commonTable.value.toggleRowSelection(item, true)
curPageCheck.value.push(item.id)
}
})
}
})
}
}, {
immediate: true
})
const methods = {
/**
* prop 单值 或者 数组过滤(此处为针对时间组,不作为通用处理)
*/
propFilter(prop, row) {
const res = prop.reduce((total, cur) => {
if (row[cur]) {
return (total += row[cur] + '~')
} else {
return ''
}
}, '')
return res ? res.replace(/~$/, '') : ''
},
handleTableButton(row, type) {
emit('operation', row, type);
},
/**
* 后续扩展位
* @param {*} methods
* @param {*} row
*/
handleClickon(methods, row) {
emit(methods, { methods, row })
},
handleSelectionChange(val) {
if (props.showCheckBox && props.turnRadio) {
// 选择项大于1时
if (val.length > 1) {
const del_row = val.shift()
commonTable.value.toggleRowSelection(del_row, false)
}
}
// 全选
if (props.showCheckBox && props.selectedIdArr) {
if (props.turnRadio) {
emit('handle-selection-change', val)
} else {
// 一般复选框都是走到这一步
emit('handle-selection-change', val)
}
} else {
emit('handle-selection-change', val)
}
},
getRowKeys(row) {
return row.id
},
selectAll(val) {
if (props.showCheckBox && props.turnRadio) {
// 选择项大于1时
if (val.length > 1) {
val.length = 1
}
}
emit('handle-selection-change', val)
},
// 斑马纹表格背景色
tabRowClassName({ rowIndex }) {
const index = rowIndex + 1
if (index % 2 === 0) {
return 'even-row'
} else {
return 'odd-row'
}
},
cellClassName({ row, columnIndex }) {
if (row.confirmTag === 2 && columnIndex < props.tableLabel.length) {
return 'height_light_cell'
} else {
return ''
}
},
buttonDisabled(item, row) {
if (typeof item.disabled === 'function') return item.disabled(row) || false
if (!item.disabled) return item.disabled
},
/**
* 单选框选中事件
*/
rowClick(row) {
if (row) {
selectId.value = row.id;
emit('rowClick', row)
}
}
};
return {
selectId,
methods
}
}
3、CommonTable.tsx为组件渲染文件
import {
defineComponent,
ref,
PropType
} from 'vue';
import {
OptionLabel,
TableLabel
} from './types';
import useModule from './commonTable.module';
import './commonTable.module.scss';
export default defineComponent({
name: 'CommonTable',
props: {
/**
* 表格最高高度
*/
maxHeight: {
type: [String, Number],
default: ''
},
/**
* 表格自定义属性展示
*/
tableLabel: {
type: Array as PropType<TableLabel[]>,
default: () => []
},
/**
* 表格数据源
*/
data: {
type: Array,
default: () => []
},
/**
* 配置需要显示的操作菜单
*/
option: {
type: Object as PropType<OptionLabel>,
default: () => {}
},
showCheckBox: {
// 配置是否显示全选(复选框)
type: Boolean,
default: false
},
/** 是否复选框禁用 */
isEnable: {
type: Boolean,
default: false
},
/** 是否复选框禁用的方法 */
enableCheckBox: {
type: Function as PropType<(row: any) => boolean>,
default: null
},
/**
* 是否显示索引
*/
showIndex: {
type: Boolean,
default: false
},
turnRadio: {
type: Boolean,
default: false
},
selectedIdArr: {
type: Array as PropType<number[] | string[]>,
default: () => []
},
/**
* 是否 隐藏文字过长
*/
overflowText: {
type: Boolean,
default: false
},
/**
* 加载提示
*/
loading: {
type: Boolean,
default: false
},
/**
* 是否保持之前复选框的数据
*/
keep: {
type: Boolean,
default: false
},
/**
* 动态绑定 key 值
*/
keyId: {
type: String,
default: 'id'
},
/**
* 行内自定义样式配置
*/
rowStyle: {
type: Object,
default: () => {
return {
height: '40px'
}
}
},
/**
* 是否展示展开按钮
*/
showExpand: {
type: Boolean,
default: false
},
/**
* 单选模式
*/
showRadio: {
type: Boolean,
default: false
},
selectRadioId: {
type: String,
default: ''
}
},
setup(props, { emit, slots }) {
const commonTable = ref(null)
const {
selectId,
methods
} = useModule(props, emit, commonTable)
return () => (
<div v-loading={props.loading}>
<el-table
class='common-table'
ref={commonTable}
data={props.data}
border
highlight-current-row={props.showRadio}
max-height={props.maxHeight}
row-class-name={methods.tabRowClassName}
row-style={props.rowStyle}
cell-class-name={methods.cellClassName}
header-row-class-name='custom-table-header'
row-key={props.keyId}
on-select={methods.handleSelectionChange}
on-select-all={methods.handleSelectionChange}
on-current-change={methods.rowClick}
>
{
props.showRadio && <el-table-column
label='选择'
align='center'
width='55'
scopedSlots={{
default: scope => (
<el-radio
label={scope.row.url}
vModel={selectId.value}
onChange={() => methods.rowClick(scope.row)}
>
</el-radio>
)
}}
/>
}
{ props.isEnable ? (props.showCheckBox && <el-table-column
key='showCheckBox'
width='55'
type='selection'
reserve-selection={props.keep}
selectable={props.enableCheckBox}
class-name={props.turnRadio ? 'checkBoxRadio' : ''}
align='center'
/>) : (props.showCheckBox && <el-table-column
key='showCheckBox'
width='55'
type='selection'
reserve-selection={props.keep}
class-name={props.turnRadio ? 'checkBoxRadio' : ''}
align='center'
/>)
}
{
props.showExpand && <el-table-column
key='showExpand'
type='expand'
scopedSlots={{
default: scope => {
return <fragment row={scope.row}>
{
slots.expand?.()
}
</fragment>
}
}}
>
</el-table-column>
}
{
props.showIndex && <el-table-column
align='center'
label='序号'
width='50'
scopedSlots={{
default: scope => {
return scope.$index + 1
}
}}
>
</el-table-column>
}
{
props.tableLabel.map((item: TableLabel) => {
return <el-table-column
key={item[props.keyId]}
width={item.width ?? ''}
align={item.align ?? 'center'}
label={item.label}
show-overflow-tooltip={props.overflowText}
fixed={item.fixed}
prop={item.prop}
scopedSlots={{
default: (scope) => {
if (item.render) {
return <div
style='cursor: pointer'
onClick={() => item.methods && methods.handleClickon(item.methods, scope.row)}
domPropsInnerHTML={item.render(scope.row)}
>
</div>
} else {
return <div
class='text-no-wrap'
onClick={() => item.methods && methods.handleClickon(item.methods, scope.row)}>
{
Object.prototype.toString.call(item.prop) === '[object Array]' ? methods.propFilter(item.prop, scope.row) : (scope.row[item.prop] ?? '--')
}
</div>
}
}
}}
>
</el-table-column>
})
}
{
props.option && <el-table-column
width={props.option.width}
label={props.option.label}
fixed={props.option.fixed}
align={props.option.align ?? 'center'}
scopedSlots={{
default: scope => {
return props.option.children.length && <div
class='flex-box'
>
{
props.option.children.map(item => {
return (typeof item.hidden === 'function' ? !item.hidden(scope.row) : !item.hidden) && (
<el-tooltip
class='item'
effect='light'
popper-class='common-tooltip-primary'
content={typeof item.label === 'function' ? item.label(scope.row) : item.label}
placement='top'
>
<i
class={['common-tooltip-icon', typeof item.icon === 'function' ? item.icon(scope.row) : item.icon]}
plain='true'
v-permission={item.permission ?? ''}
onClick={() => methods.handleTableButton(scope.row, item.methods)}
/>
</el-tooltip>
)
})
}
</div>
}
}}
>
</el-table-column>
}
</el-table>
</div>
)
}
})
4、types.ts为类型定义文件
/** 表格基础类型配置 */
interface BasicLabel {
label: string; // 标题
width?: number | string; // 宽度
fixed?: string; // 固定位置
align?: string; // 行排列
}
/** 表格顶部类型配置 */
export interface TableLabel extends BasicLabel {
prop: string;
methods?: string;
render?: render;
}
/** 表格顶部自定义函数类型 */
interface render {
(row: any): string | any
}
/** 表格操作栏类型 */
export interface OptionLabel extends BasicLabel {
children: OptionChild[]
}
/** 表格操作栏子选项类型 */
export interface OptionChild {
label: string | ((row: any) => string); // 标题
icon: string | ((row: any) => string); // icon图标
methods: string; // 执行方法
permission?: string | string[] | object; // 权限
hidden?: boolean | ((row: any) => boolean);
}
5、组件调用方式
组件调用:
<CommonTable
show-index
show-check-box={true}
loading={loading.value}
max-height={550}
table-label={tableHeaderData}
data={tableData.value}
option={tableOptionsData}
on-operation={methods.operationHandler}
on-handle-selection-change={methods.handleSelectionChange}
/>
属性及方法使用说明:
注意:如果你在使用Sortable插件想要拖动排序表格时,tableOptionsData 下的fixed参数请不要写,不然会导致无法拖动!
/** 表格头部配置 */
const tableHeaderData = [
{
label: '文件大小',
prop: 'size',
width: '100',
methods: 'fileEntry'
render(row: TableDataItem) {
return `<span>${row.size}</span>`
}
}
]
/** 表格操作栏配置 */
const tableOptionsData = {
label: '操作',
width: '150',
fixed: 'right',
children: [
{
hidden: false || (row: TableDataItem) { // 可为boolean 或者 函数
return !row.status
},
label: '操作记录',
icon: 'xxx',
methods: 'record',
permission: ''
}
]
}
const methods = {
/**
* 操作栏分发逻辑
* @param row 当行数据
* @param type 分发函数名
*/
operationHandler(row: TableDataItem, type: string) {
if (type === 'record') { // 操作记录
}
},
/**
* 复选框处理回调
* @param val 复选框选中的数据
*/
handleSelectionChange(val: TableDataItem[]) {
multipleSelection.value = val
}
}
---觉得好用且实用的,那就点赞收藏吧---