el-table 表格渲染封装公共组件(提供三种格式化渲染模板)
单纯的想记录一下第一次封装的成果,写着玩,希望对你也有帮助。。。
示例效果
可以根据自己喜好自定义选择格式化渲染方式
1.render函数式渲染
自定义函数式组件expandDom用于渲染render函数,相对复杂(忘了借鉴哪位大佬的代码)
/**
* // 用于渲染表格的基础内容,必传字段,表头数据 Table-column Attributes
* @example 使用 render 函数更灵活,可生成 elementUI 元素(formatter 函数无法办到),同时可以格式化展示内容
* js:
* columns:[
* {
* prop: 'name',
* label: '姓名',
* width: '120px',
* // 使用 render 函数更灵活,可生成 elementUI 元素,formatter 无法办到
* render: (h, params) => {
* return h(
* // 元素或组件名
* 'el-link',
* // 属性
* {
* // 组件的 props
* props: {
* // 需要使用-立即执行函数-并返回字符串,type 属性不接收 Function
* type: (function () {
* if (params.row.name === '王小虎1') {
* return 'success'
* } else if (params.row.name === '王虎') {
* return 'warning'
* } else {
* return ''
* }
* })(),
* },
* // 添加事件
* on: {
* click: () => {
* console.log('row', params.row, 'column', params.column)
* },
* },
* },
* // 组件内容
* params.row.name
* )
* },
* },
* ]
*/
2.formatter函数式渲染
使用 v-html 指令渲染内容
/**
* @example 使用 formatter 函数可以格式化改动展示内容
* @param row 行对象
* @param column 列对象
* @param cellValue 单元格的值
* @param $index 单元格的行索引
* @returns {string|number} 格式化后的数据,可返回 html 标签元素,但是无法返回 elementUI 标签元素(因为 v-html 不会解析标签)
* js:
* columns:[
* {
* prop: 'address',
* label: '地址',
* minWidth: '120px',
* sortable: true,
* ['sort-method']: this.sortMethod,
* formatter: ({row, column, cellValue, $index}) => {
* return `<span style="color: red">cellValue:</span>${cellValue}<span style="color: red">,index:</span>${$index}`
* },
* },
* ]
*/
3.插槽式渲染
自定义插槽名(不能使用append作为自定义插槽名)向外提供具名(作用域)插槽,插槽名需要和 props 传入的 columns 内 slotName 的值全等
/**
* @example slotName 提供插槽名,对外生成对应具名作用于插槽,自定义列的内容,参数为 { row, column, cellValue, $index }
* @param row 行对象
* @param column 列对象
* @param cellValue 单元格的值
* @param $index 单元格的行索引
* @important 注:slotName 不能使用 'append', append 为 el-table 内部插槽
* html:
* <template #operate="scope"> // #??? => 需要和下方的 slotName 一致
* <el-button type="primary" @click="handleAdd(scope)">新增</el-button>
* <el-button type="warning" @click="handleDel(scope)">删除</el-button>
* </template>
* js:
* columns:[
* {
* prop: 'operate',
* label: '操作',
* width: '160',
* fixed: 'right',
* slotName: 'operate', // 使用 slotName 提供插槽名,对外生成对应具名作用于插槽
* },
* {
* prop: 'expand',
* width: 40,
* type: 'expand', //* '展开行'内容也可以使用插槽写入,对外生成对应具名作用于插槽
* slotName: 'expand',
* },
* ]
*/
完整代码
y-table.vue代码:
<template>
<el-table ref="table" v-bind="$attrs" v-on="$listeners">
<!-- 多选和索引渲染插槽 -->
<slot />
<template v-for="(column, key) in columns">
<el-table-column
v-bind="column"
:key="column.prop + key"
show-overflow-tooltip
>
<template #default="scope">
<!-- render函数式渲染 -->
<template v-if="column.render">
<expand-dom
:column="column"
:row="scope.row"
:render="column.render"
:index="key"
/>
</template>
<!-- formatter函数式渲染 -->
<template v-else-if="column.formatter">
<span
v-html="
heightLight(
column.formatter({
row: scope.row,
column: column,
cellValue: scope.row[column.prop],
$index: scope.$index,
})
)
"
/>
</template>
<!-- 插槽式渲染 -->
<template v-else-if="column.slotName">
<slot
:name="column.slotName"
v-bind="{ cellValue: scope.row[column.prop], ...scope } || {}"
/>
</template>
<!-- 无任何处理 -->
<template v-else>
<span v-html="heightLight(scope.row[column.prop])" />
</template>
</template>
</el-table-column>
</template>
<!-- 插入至表格最后一行之后的内容,如果需要对表格的内容进行无限滚动操作,可能需要用到这个 slot。若表格有合计行,该 slot 会位于合计行之上。 -->
<template #append><slot name="append" /></template>
</el-table>
</template>
<script>
export default {
name: 'YTable',
components: {
// 生成元素自定义组件
expandDom: {
// 函数式组件,无状态 (没有响应式数据),也没有实例 (没有 this 上下文)
functional: true,
props: {
row: Object,
render: Function,
index: Number,
column: {
type: Object,
default: null,
},
},
render: (h, ctx) => {
const params = {
row: ctx.props.row,
index: ctx.props.index,
}
if (ctx.props.column) params.column = ctx.props.column
return ctx.props.render(h, params)
},
},
},
props: {
// 用于渲染表格的基础内容,必传字段,表头数据 Table-column Attributes
columns: {
validator: function (value) {
return value.map((item) => {
return (
typeof item.prop === 'string' && typeof item.label === 'string'
)
})
},
required: true,
},
// 用于搜索匹配高亮关键字
search: {
type: String,
default: '',
},
// ...其余属性同 ElementUI 表格 Attributes 直接绑定传入即可
},
updated() {
// 更新时,解决错位问题,修复滚动条丢失的 bug
this.$refs.table.doLayout()
},
activated() {
// 激活时,解决错位问题,修复滚动条丢失的 bug
this.$refs.table.doLayout()
},
mounted() {
// 暴露 el-table 组件原有方法给外层 $refs,或者使用 this.$refs[外层ref名].$refs.table.xxx() 调用
const entries = Object.entries(this.$refs.table)
for (const [key, value] of entries) {
// 排除含有 $ 或 _ 的属性,防止浏览器页面卡顿
if (!(key.includes('$') || key.includes('_'))) {
this[key] = value
}
}
},
methods: {
/**
* 高亮搜索关键字,暂时仅支持无任何处理的渲染方式和 formatter 函数式渲染还需要测验
* 不支持 render 函数式渲染和插槽式渲染 ‘自动高亮‘
* @param {number|string} value 源数据值
* @return {*|string}
*/
heightLight(value) {
if (this.search !== '') {
return String(value).replace(this.regex, (match) => {
return `<mark style="color: red;font-weight: bold;">${match}</mark>`
})
} else {
return value
}
},
},
}
</script>
使用方法
示例代码:
<template>
<section>
<y-table
v-loading="loading"
:data="table"
:columns="columns"
:row-class-name="tableRowClassName"
border
@selection-change="handleSelectionChange"
>
<!-- columns中自定义插槽 -->
<template #operate="scope">
<el-button type="primary" @click="handleAdd(scope)">新增</el-button>
<el-button type="warning" @click="handleDel(scope)">删除</el-button>
</template>
<!-- el-table内置插槽 -->
<template #append>el-table 内置插槽:append 插槽</template>
</y-table>
</section>
</template>
<script>
import YTable from '@/components/y-table.vue'
export default {
name: 'Test',
components: { YTable },
data() {
return {
loading: false,
columns: [
{
prop: 'date',
label: '时间',
width: '120px',
filters: [
{ text: '2016-05-04', value: '2016-05-04' },
{ text: '2016-05-00', value: '2016-05-00' },
{ text: '2016-05-03', value: '2016-05-03' },
],
['filter-multiple']: false, // 是否多选
['filter-method']: this.filterMethod,
['filter-placement']: 'bottom', // 筛选框展示的位置
['filtered-value']: ['2016-05-04'], // 默认筛选的值
},
{
prop: 'name',
label: '姓名',
width: '120px',
// 使用 render 函数更灵活,可生成 elementUI 元素,formatter 无法办到
render: (h, params) => {
return h(
// 元素或组件名
'el-link',
// 属性
{
// 组件的 props
props: {
// 需要使用-立即执行函数-并返回字符串,type 属性不接收 Function
type: (function () {
if (params.row.name === '王小虎1') {
return 'success'
} else if (params.row.name === '王虎') {
return 'warning'
} else {
return ''
}
})(),
},
// 添加事件
on: {
click: () => {
console.log('row', params.row, 'column', params.column)
},
},
},
// 组件内容
params.row.name
)
},
},
{
prop: 'address',
label: '地址',
minWidth: '120px',
sortable: true,
['sort-method']: this.sortMethod,
formatter: ({row, column, cellValue, $index}) => {
return `<span style="color: red">cellValue:</span>${cellValue}<span style="color: red">,index:</span>${$index}`
},
},
{
prop: 'operate',
label: '操作',
width: '160',
fixed: 'right',
slotName: 'operate', // 使用 slotName 提供插槽名,对外生成对应具名作用于插槽
},
],
table: [
{
date: '2016-05-04',
name: '王小虎1',
address: '上海市普陀区金沙江路 1518 弄',
},
{
date: '2016-05-04',
name: '小虎',
address: '普陀区金沙江路 1517 弄',
},
{
date: '2016-05-01',
name: '王虎',
address: '上海市金沙江路 1519 弄',
},
{
date: '2016-05-03',
name: '王小',
address: '上海市普陀区 1516 弄',
},
],
}
},
methods: {
onSubmit() {
console.log('submit!')
},
handleAdd(...args) {
console.log('右侧固定栏新增按钮', args)
},
handleDel(...args) {
console.log('右侧固定栏删除按钮', args)
},
/**
* 筛选方法
* @param value
* @param row
* @param column
* @returns {boolean}
*/
filterMethod(value, row, column) {
return row[column.property] === value
},
/**
* 修改行的背景颜色(添加class类名)受 scoped 限制,如果要修改颜色,在 App.vue 中添加下列class类名对应颜色
* @param row 行对象
* @param rowIndex 行的索引
* @returns {String} ''|'primary-row'|success-row'|'warning-row'|'danger-row'|'info-row'
*/
tableRowClassName({ row, rowIndex }) {
if (rowIndex === 1) {
return 'warning-row'
} else if (rowIndex === 3) {
return 'success-row'
}
return ''
},
// 多选框变化
handleSelectionChange(val) {
console.log('多选框变化', val)
},
},
}
</script>
App.vue
<style lang="scss">
//* el-tooltip 文字提示最大宽度
.el-tooltip__popper {
max-width: 600px;
}
.el-table__fixed-right {
height: 100% !important;
}
//* 公共组件行带状态表格样式
.el-table .primary-row {
background: #79bbff;
}
.el-table .success-row {
background: #95d475;
}
.el-table .warning-row {
background: #eebe77;
}
.el-table .danger-row {
background: #f89898;
}
.el-table .info-row {
background: #b1b3b8;
}
//* el-table hover 状态的行高亮背景颜色修改
.el-table__body tr.hover-row > td.el-table__cell {
background-color: #dedfe0 !important;
}
//* el-table 单选状态高亮背景颜色修改
.el-table__body tr.current-row > td.el-table__cell {
background-color: #a0cfff !important;
}
//* 修复 el-table 单选框溢出隐藏
.el-table-filter__list {
overflow-y: scroll;
max-height: 400px;
&::-webkit-scrollbar {
width: 13px;
height: 13px;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.4);
background-clip: padding-box;
border: 3px solid transparent;
border-radius: 7px;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
}
</style>
总结
最开始也有想过将各种参数功能内置在公共组件中,例如:把各种elementUI组件加入组件中,但是实际写业务的时候发现需要兼容的东西太多了,需求不断增加,需要兼容的东西也越来越多,还不如放权到每个业务下自定义,更加通用方便。毕竟二次封装也只是为了在使用时更加便捷,而不是平白增加工作量的是吧^~^!