vue3 模板写法的通用表格
TS + vue3.2 + vite2 + element-plus 通用表格组件封装
vue3 + tsx写法的通用表格
【TSX】vue3 + element-ui + tsx 通用表格组件
1、父组件调用方式:
<common-table
show-index
show-check-box
key-id="id"
:loading="loading"
:max-height="390"
:table-label="tableHeaderData"
:row-class-name="tableSortRowClassName"
:data="tableData"
:option="tableOptionsData"
@operation="operationHandler"
@handle-selection-change="handleSelectionChange"
>
<template slot="expand" slot-scope="{ row }">
<making-status v-if="row.status === 1" :list="row.processList || []" />
<div v-else class="making-status-none">
<span>未制作</span>
</div>
</template>
</common-table>
2、参数详解:
其实我定义的参数还蛮多的,基本能够把常用的功能包含进去了,我着重讲几个:
1、tableLabel:表格头部标题
有以下四个参数,最重要的是render,他的参数是从slot-scope抛出,可以进行判断显示
{
label: '制作格式',
prop: 'handleType',
width: 150,
render(row) {
return `<span>${row.handleType === 1 ? '查题格式' : (row.handleType === 2 ? '拍题格式' : '未设置')}</span>`
}
}
2、showCheckBox: 显示复选框
与之对应的回调函数是 @handle-selection-change,我们可以从中取得当前选中的复选框数组
3、showExpand: 拓展插槽
在 slot="expand" 即可以使用
<template slot="expand" slot-scope="{ row }">
// xxx
</template>
4、showIndex: 是否展示序号
这里序号不是你自己的自定义id喔~是当前数组索引值 + 1
5、option: 配置需要显示的操作菜单
{
label: '操作',
width: '300',
fixed: 'right',
children: [
{
label: '查看制作详情',
icon: 'el-icon-view',
methods: 'view',
permission: 'xxx',
render(row) {
return row.status !== 0
}
},
{
type: 'drop',
icon: 'el-icon-paperclip',
permission: 'xxx',
children: [
{
label: '切题查看模式',
methods: '1'
},
{
label: '编题查看模式',
methods: '2'
}
]
}
]
}
我们主要看children里的,主要参数有4个
label: 显示名字
icon: 显示图标
permission: 即v-permission,根据按钮是否有权限再进行展示,可为字符也可为数组,这种自定义指令我就不单独写出来了,可以参考我博客,链接如下
vue学习(6)自定义指令详解及常见自定义指令,里面有个checkPermission函数,所有判断你皆可自行定义
type: 为drop 则相当于 el-dropdown,把很多按钮收缩在一起
render: 这个和上面tableLabel不一样的是,这里render返回值为true或false来决定v-show的值(只在type不为drop生效)
methods: 点击后回调触发的方法,由 @operation={row, type} 抛出,type即为methods对应值(只在type不为drop生效)
children: drop展示子数组(label和methods与上面一致)(在type为drop生效)
3、组件源码 :
<template>
<div v-loading="loading">
<el-table
ref="commonTable"
:data="data"
border
:style="{
width: '100%',
borderBottom: maxHeight === 'auto' ? 'none' : '1px solid #e8eded'
}"
:max-height="maxHeight"
:row-class-name="tabRowClassName"
:row-style="rowStyle"
:cell-class-name="cellClassName"
header-row-class-name="custom-table-header"
:row-key="keyId"
@select="handleSelectionChange"
@select-all="handleSelectionChange"
>
<!-- 单选框 -->
<el-table-column
v-if="showCheckBox"
key="showCheckBox"
width="55"
type="selection"
:reserve-selection="keep"
:class-name="turnRadio ? `checkBoxRadio` : ``"
align="center"
/>
<!-- 展开 -->
<el-table-column
v-if="showExpand"
key="showExpand"
type="expand"
>
<template slot-scope="{ row }">
<slot name="expand" :row="row" />
</template>
</el-table-column>
<!-- 序号 -->
<el-table-column v-if="showIndex" align="center" label="序号" width="50">
<template slot-scope="{ $index }">{{ $index + 1 }}</template>
</el-table-column>
<!-- 表格 -->
<el-table-column
v-for="item in tableLabel.filter((item) => item.label)"
:key="item[keyId]"
:width="item.width ? item.width : ''"
:align="!!item.align ? item.align : 'center'"
:label="item.label"
:show-overflow-tooltip="overflowText"
:fixed="item.fixed"
:prop="item.prop"
>
<template slot-scope="{ row }">
<template v-if="item.Image">
<div>
<el-image class="table-img" :src="row.attachment" :preview-src-list="[row.attachment]">
<template slot="error">
<span>无</span>
</template>
</el-image>
</div>
</template>
<!--进度条-->
<template v-else-if="item.progress">
<el-progress class="progress-line" :text-inside="true" :stroke-width="26" :percentage="row[item.prop]" />
</template>
<template v-else-if="item.render">
<template>
<div style="cursor: pointer" @click="!!item.methods && handleClickon(item.methods, row)" v-html="item.render(row)" />
</template>
</template>
<template v-else>
<div class="text-no-wrap" @click="!!item.methods && handleClickon(item.methods, row)">
{{ Object.prototype.toString.call(item.prop) == '[object Array]' ? propFilter(item.prop, row) : (row[item.prop] ? row[item.prop] : '--') }}
</div>
</template>
</template>
</el-table-column>
<el-table-column v-if="!!option" :width="option.width" :label="option.label" :fixed="option.fixed" align="center">
<div
v-if="!!option.children"
slot-scope="{row}"
class="flex-box"
>
<div
v-for="(item, index) in option.children.filter(item => item.render ? item.render(row) : true)"
:key="index"
>
<el-tooltip
v-if="!item.type"
v-permission="{ permission: item.permission }"
class="item"
effect="light"
:popper-class="item.popperClass || 'default-tooltip-primary'"
:content="item.label"
placement="top"
>
<i
:class="['default-tooltip-icon', item.icon]"
:plain="true"
@click="handleTableButton(row, item.methods)"
/>
</el-tooltip>
<el-dropdown
v-if="item.type==='drop'"
v-permission="{ permission: item.permission }"
class="item"
@command="(command) => {handleTableButton(row, command)}"
>
<span class="el-dropdown-link">
<i :class="['default-tooltip-icon', item.icon]" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-for="itm in item.children.filter(itm_child => itm_child.render ? itm_child.render(row) : true)"
:key="itm.methods"
v-permission="{ permission: itm.permission }"
:command="itm.methods"
>
<i
v-if="itm.icon"
:class="['default-tooltip-icon', itm.icon]"
:plain="true"
/>
{{ itm.label }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
name: 'CommonTable',
props: {
/**
* 表格最高高度
*/
maxHeight: {
type: [String, Number],
default: 'auto'
},
/**
* 表格自定义属性展示
*/
tableLabel: {
type: Array,
default: () => {
return []
}
},
/**
* 表格数据源
*/
data: {
type: Array,
default: () => {
return []
}
},
/**
* 配置需要显示的操作菜单
*/
option: {
type: Object,
default: () => {}
},
showCheckBox: {
// 配置是否显示全选(复选框)
type: Boolean,
default: false
},
/**
* 是否显示索引
*/
showIndex: {
type: Boolean,
default: false
},
turnRadio: {
type: Boolean,
default: false
},
selectedIdArr: {
type: Array,
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
},
/**
* 行内自定义class
*/
rowClassName: {
type: Function,
default: () => {
return () => {}
}
}
},
data() {
return {
curPageCheck: [],
radioId: '',
showVertical: false
}
},
watch: {
data: {
handler() {
if (this.showCheckBox || this.turnRadio) {
this.$nextTick(() => {
this.$refs.commonTable.clearSelection()
this.curPageCheck = []
if (this.showCheckBox && this.turnRadio) {
this.data.filter((item) => {
if (item.id === this.selectedIdArr[0]) {
this.$refs.commonTable.toggleRowSelection(item, true)
}
})
} else if (this.showCheckBox) {
this.data.filter((item) => {
if (this.selectedIdArr.includes(item.id)) {
this.$refs.commonTable.toggleRowSelection(item, true)
this.curPageCheck.push(item.id)
}
})
}
})
}
},
deep: true,
immediate: true
},
selectedIdArr: {
handler(val) {
if (this.showCheckBox || this.turnRadio) {
this.$nextTick(() => {
this.$refs.commonTable.clearSelection()
this.curPageCheck = []
if (this.showCheckBox && this.turnRadio) {
this.data.filter((item) => {
if (item.id === val[0]) {
this.$refs.commonTable.toggleRowSelection(item, true)
}
})
} else if (this.showCheckBox) {
this.data.filter((item) => {
if (val.includes(item.id)) {
this.$refs.commonTable.toggleRowSelection(item, true)
this.curPageCheck.push(item.id)
}
})
}
})
}
},
deep: true,
immediate: true
}
},
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) {
if (!type) return;
this.$emit('operation', row, type);
},
/**
* 后续扩展位
* @param {*} methods
* @param {*} row
*/
handleClickon(methods, row) {
if (!methods) return;
this.$emit(methods, { methods, row })
},
handleSelectionChange(val) {
if (this.showCheckBox && this.turnRadio) {
// 选择项大于1时
if (val.length > 1) {
const del_row = val.shift()
this.$refs.commonTable.toggleRowSelection(del_row, false)
}
}
// 全选
if (this.showCheckBox && this.selectedIdArr) {
if (this.turnRadio) {
this.$emit('handle-selection-change', val)
} else {
// 一般复选框都是走到这一步
this.$emit('handle-selection-change', val)
}
} else {
this.$emit('handle-selection-change', val)
}
},
getRowKeys(row) {
return row.id
},
selectAll(val) {
if (this.showCheckBox && this.turnRadio) {
// 选择项大于1时
if (val.length > 1) {
val.length = 1
}
}
this.$emit('handle-selection-change', val)
},
// 斑马纹表格背景色
tabRowClassName({ row, rowIndex }) {
const classList = []
// 默认样式配置
const index = rowIndex + 1;
if (index % 2 === 0) {
classList.push('even-row');
} else {
classList.push('odd-row');
}
// 自定义样式配置
classList.push(this.rowClassName({ row, rowIndex }))
return classList.join(' ');
},
cellClassName({ row, column, rowIndex, columnIndex }) {
if (row.confirmTag === 2 && columnIndex < this.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) {
this.$emit('rowClick', row)
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .el-table__header,
::v-deep .el-table__body {
margin: 0;
}
::v-deep .el-table::before {
height: 0;
}
::v-deep .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;
}
}
::v-deep .el-button + .el-button {
margin-left: 0;
}
::v-deep .btn-right div {
margin-right: 5px;
}
.btn-right div:empty {
margin-right: 0px;
}
//斑马纹表格背景色
::v-deep .el-table .even-row {
--el-table-tr-background-color: #f5fafb;
}
::v-deep .el-table .odd-row {
--el-table-tr-background-color: #ffffff;
}
.el-table--border::after,
.el-table--group::after {
width: 0;
}
::v-deep .el-table__fixed-right::before,
.el-table__fixed::before {
background-color: transparent;
}
::v-deep .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;
}
::v-deep .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;
}
}
::v-deep .el-table--small .el-table__cell {
padding: 0;
}
::v-deep .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;
}
}
::v-deep .el-tooltip {
font-size: 16px;
}
</style>