2024-04-24 TTable组件多级表头支持单元格编辑功能
2024-03-20 TTable组件新增新增第一列既显示(复选、单选)和序列号
一、需求
对于后台管理系统项目必不可少的列表数据渲染;而element-plus的table组件还是需要写很多代码;为了方便因此封装了TTable组件(一行代码,可以实现表格编辑/分页/表格内/外按钮操作/排序/显示隐藏表格内操作按钮)
二、组件功能
1、基础表格
2、带斑马线、带边框、固定列/表头
3、多级表头
4、自定义表头
5、单个单元格编辑功能
6、可以设置表格标题
7、可集成了分页组件(复选框支持翻页选中)
8、表格可以自定义插槽渲染某列数据
9、表格可以render渲染某列数据(支持jsx方式)
10、集成了表格内操作和表格外操作
11、支持某列字典过滤渲染
12、支持列--显示隐藏及拖拽排序
13、支持整行--拖拽排序
14、支持单元格编辑键盘事件(向上、向下、回车横向的下一个输入框)
15、表格实现了双击复制单元格内容功能
16、表格实现单选框选中取消功能
17、单元格编辑新增表单校验功能
18、表格内操作按钮权限配置
19、展开行
20、多级表头支持单元格编辑功能
21、支持tree-table
22、第一列显示复选框和序列号
三、最终效果
四、参数配置
1、代码示例:
<t-table
:table="table"
:columns="table.columns"
@size-change="handlesSizeChange"
@page-change="handlesCurrentChange"
/>
2、配置参数(Table Attributes)
参数 | 说明 | 类型 | 默认值 |
---|
table | 表格数据对象 | Object | {} |
—rules | 规则(可依据 elementPlus el-form 配置————对应 columns 的 prop 值) | Object | - |
—data | 展示数据 | Array | [] |
—toolbar | 表格外操作栏选中表格某行,可以将其数据传出 | Array | [] |
—operator | 表格内操作栏数据 | Array | [] |
-------hasPermi | 表格内操作栏按钮权限资源(必须传btnPermissions 属性才生效) | String | - |
-------show | 表格内操作栏根据状态显示 | Object | - |
-------render | render函数渲染使用的 Function(val) 可以用 tsx 方式 | Function | - |
-------noshow | 表格内操作栏根据多种状态不显示 | Array | - |
-------bind | 继承el-button所有Attributes(默认值{ type:‘primary’,link:true,text:true,size:‘small’,}) | Object | - |
-------fun | 事件名 | function | - |
—operatorConfig | 表格内操作栏样式 | Object | - |
--------fixed | 列是否固定在左侧或者右侧。 true 表示固定在左侧(true / ‘left’ / ‘right’) | string / boolean | - |
--------label | 显示的标题 | string | ‘操作’ |
--------width | 对应列的宽度(固定的) | string / number | - |
--------minWidth | 对应列的最小宽度(会把剩余宽度按比例分配给设置了 min-width 的列) | string / number | - |
--------align | 对齐方式 (left / center / right) | string | ‘center’ |
--------bind | el-table-column Attributes | Object | - |
—firstColumn | 表格首列(序号 index,复选框 selection,单选 radio,展开行 expand)排列 | object | - |
—total | 数据总条数 | Number | - |
—pageSize | 页数量 | Number | - |
—currentPage | 是否需要显示切换页条数 | Number | - |
columns | 表头信息 | Array | [] |
----sort | 排序 (设置:sort:true) | Boolean | false |
----renderHeader | 列标题 Label 区域渲染使用的 Function(val) 可以用 jsx 方式 | Function | - |
----render | 某列render函数渲染使用的 Function(val) 可以用 jsx 方式 | Function | - |
----bind | el-table-column Attributes | Object | - |
----width | 对应列的宽度(固定的) | string / number | - |
----minWidth | 对应列的最小宽度(会把剩余宽度按比例分配给设置了 min-width 的列) | string / number | - |
----noShowTip | 是否换行 (设置:noShowTip:false换行,不设置自动隐藏) | Boolean | - |
----slotName | 插槽显示此列数据(其值是具名作用域插槽 | String | - |
----isShowHidden | 是否动态显示隐藏列设置(隐藏/显示列) | Boolean | false |
----slotNameMerge | 合并表头插槽显示此列数据(其值是具名作用域插槽) | String | - |
----------scope | 具名插槽获取此行数据必须用解构接收{scope}.row 是当前行数据 } | Object | - |
----canEdit | 是否开启单元格编辑功能 | Boolean | false |
----configEdit | 表格编辑配置(开启编辑功能有效) | Object | - |
----------rules | 规则(可依据 elementPlus el-form 配置————对应 columns 的 prop 值) | Object | - |
----------label | placeholder 显示 | String | - |
----------editComponent | 组件名称可直接指定全局注册的组件,也可引入’element/abtd’如:‘a-input/el-input’ | String | - |
----------eventHandle | 第三方 UI 的 事件(返回两个参数,第一个自己自带,第二个 scope) | Object | - |
----------bind | 第三方 UI 的 Attributes,如 el-input 中的 clearable 清空功能 | Object | - |
----------event | 触发 handleEvent 事件的标志 | String | - |
----------type | 下拉或者复选框显示(select-arr/select-obj/checkbox) | String | - |
----------list | 下拉选择数据源名称 | String | - |
----------arrLabel | type:select-arr 时对应显示的中文字段 | String | - |
----------arrKey | type:select-arr 时对应显示的数字字段 | String | - |
----filters | 字典过滤 | Object | - |
----------list | listTypeInfo 里面对应的下拉数据源命名 | String | - |
----------key | 数据源的 key 字段 | String | ‘value’ |
----------label | 数据源的 label 字段 | String | ‘label’ |
btnPermissions | 按钮权限数据集(后台返回的按钮权限集合) | Array | - |
listTypeInfo | 下拉选择数据源 | Object | - |
footer | 底部操作区(默认隐藏,使用插槽展示“保存”按钮) | slot | - |
pagination | 分页器自定义内容 设置文案(table设置layout才生效) | slot | - |
isKeyup | 单元格编辑是否开启键盘事件 | Boolean | false |
isShowFooterBtn | 是否显示保存按钮 | Boolean | false |
title | 表格左上标题 | String /slot | - |
isShowPagination | 是否显示分页(默认显示分页) | Boolean | true |
isPaginationCumulative | 序列号显示是否分页累加 | Boolean | false |
isTableColumnHidden | 是否开启合计行隐藏复选框/单选框 | Boolean | false |
isCopy | 是否允许双击单元格复制 | Boolean | false |
defaultRadioCol | 设置默认选中项(单选)defaultRadioCol 值必须大于 0! | Number | - |
rowClickRadio | 是否开启点击整行选中单选框 | Boolean | true |
columnSetting | 是否显示设置(隐藏/显示列) | Boolean | false |
name | 与 columnSetting 配合使用标记隐藏/显示列唯一性 | String | title |
isRowSort | 是否开启行拖拽(row-key 需要设置) | Boolean | false |
isTree | 是否开启Tree-table样式 | Boolean | false |
columnSetBind | 列设置按钮配置(继承el-button 所有属性) | Object | - |
----btnTxt | 按钮显示文字 | String | ‘列设置’ |
----title | 点击按钮下拉显示title | String | ‘列设置’ |
----size | el-button的size | String | ‘default’ |
----icon | el-button的icon | String | ‘Setting’ |
3、events 其他事件按照 el-table 直接使用(如 sort-change 排序事件)
事件名 | 说明 | 返回值 |
---|
page-change | 当前页码 | 当前选中的页码 |
save | 保存按钮 | 编辑后的所有数据 |
handleEvent | 单个输入触发事件 | configEdit 中的 event 值和对应输入的 value 值 |
radioChange | 单选选中事件 | 返回当前选中的整行数据 |
rowSort | 行拖拽排序后触发事件 | 返回排序后的table数据 |
validateError | 单元格编辑保存校验不通过触发 | 返回校验不通过的 prop–label 集合 |
4、Methods 方法继承el-table所有方法
事件名 | 说明 | 参数 |
---|
clearSelection | 用于多选表格,清空用户的选择 | - |
clearSort | 清空排序条件 | - |
toggleRowSelection | 取消某一项选中项 | - |
toggleAllSelection | 全部选中 | - |
saveMethod | 单元格编辑保存方法 | callback(tableData) |
resetFields | 对表单进行重置,并移除校验结果(单元格编辑时生效) | — |
clearValidate | 清空校验规则(单元格编辑时生效) | - |
5、Slots插槽
插槽名 | 说明 | 参数 |
---|
title | TTable 左侧Title | - |
toolbar | TTable 右侧toolbar | - |
expand | table.firstColumn.type:expand 展开行插槽 | scope |
- | el-table-column某列自定义插槽(slotName命名) | scope |
- | el-table-column单元格编辑插槽(editSlotName命名) | scope |
- | el-table-column表头合并插槽(slotNameMerge命名) | scope |
- | 操作列前一列自定义默认内容插槽 | - |
footer | 底部操作区(默认隐藏,使用插槽展示“保存”按钮) | - |
pagination | 分页器自定义内容 设置文案(table设置layout才生效) | - |
<template>
<div class="t-table" ref="TTableBox">
<div class="header_wrap">
<div class="header_title">
{{ title }}
<slot name="title" />
</div>
<div class="toolbar_top">
<slot name="toolbar"></slot>
<div
class="header_right_wrap"
:style="{ marginLeft: isShow('toolbar') ? '12px' : 0 }"
>
<slot name="btn" />
<column-set
v-if="columnSetting"
v-bind="$attrs"
:columns="renderColumns"
@columnSetting="(v) => (state.columnSet = v)"
/>
</div>
</div>
</div>
<el-table
ref="TTable"
:data="state.tableData"
:class="{
cursor: isCopy,
row_sort: isRowSort,
tree_style: isTree,
highlightCurrentRow: highlightCurrentRow,
radioStyle: table.firstColumn && table.firstColumn.type === 'radio',
}"
v-bind="$attrs"
:highlight-current-row="highlightCurrentRow"
:border="table.border || isTableBorder"
@cell-dblclick="cellDblclick"
@row-click="rowClick"
:cell-class-name="cellClassNameFuc"
>
<template v-if="table.firstColumn">
<el-table-column
v-if="table.firstColumn.type === 'selection'"
v-bind="{
type: 'selection',
width: table.firstColumn.width || 55,
label: table.firstColumn.label,
fixed: table.firstColumn.fixed,
align: table.firstColumn.align || 'center',
'reserve-selection': table.firstColumn.isPaging || false,
selectable: table.firstColumn.selectable,
...table.firstColumn.bind,
}"
/>
<el-table-column
v-else
v-bind="{
type: table.firstColumn.type,
width: table.firstColumn.width || 55,
label:
table.firstColumn.label ||
(table.firstColumn.type === 'radio' && '单选') ||
(table.firstColumn.type === 'index' && '序号') ||
(table.firstColumn.type === 'expand' && '') ||
'',
fixed: table.firstColumn.fixed,
align: table.firstColumn.align || 'center',
...table.firstColumn.bind,
}"
>
<template
#default="scope"
v-if="table.firstColumn.type !== 'selection'"
>
<el-radio
v-if="table.firstColumn.type === 'radio'"
v-model="radioVal"
:label="scope.$index + 1"
@click.stop="radioChange($event, scope.row, scope.$index + 1)"
></el-radio>
<template v-if="table.firstColumn.type === 'index'">
<span v-if="isPaginationCumulative && isShowPagination">
{{
(table.currentPage - 1) * table.pageSize + scope.$index + 1
}}
</span>
<span v-else>{{ scope.$index + 1 }}</span>
</template>
<template v-if="table.firstColumn.type === 'expand'">
<slot name="expand" :scope="scope"></slot>
</template>
</template>
</el-table-column>
</template>
<template v-for="(item, index) in renderColumns">
<template v-if="!item.children">
<el-table-column
v-if="item.isShowCol === false ? item.isShowCol : true"
:key="index + 'i'"
:type="item.type"
:label="item.label"
:prop="item.prop"
:min-width="item['min-width'] || item.minWidth"
:width="item.width"
:sortable="item.sort || sortable"
:align="item.align || 'center'"
:fixed="item.fixed"
:show-overflow-tooltip="
item.noShowTip === false ? item.noShowTip : true
"
v-bind="{ ...item.bind, ...$attrs }"
>
<template #header v-if="item.headerRequired || item.renderHeader">
<render-header
v-if="item.renderHeader"
:column="item"
:render="item.renderHeader"
/>
<div style="display: inline" v-if="item.headerRequired">
<span style="color: #f56c6c; fontsize: 16px; marginright: 3px"
>*</span
>
<span>{{ item.label }}</span>
</div>
</template>
<template #default="scope">
<template v-if="item.render">
<render-col
:column="item"
:row="scope.row"
:render="item.render"
:index="scope.$index"
/>
</template>
<template v-if="item.slotName">
<slot :name="item.slotName" :scope="scope"></slot>
</template>
<template v-if="item.canEdit">
<el-form
:model="state.tableData[scope.$index]"
:rules="isEditRules ? table.rules : {}"
class="t_edit_cell_form"
:ref="(el:any) => handleRef(el, scope,item)"
@submit.prevent
>
<single-edit-cell
:canEdit="item.canEdit"
:configEdit="item.configEdit"
v-model="scope.row[scope.column.property]"
:prop="item.prop"
:scope="scope"
@handleEvent="handleEvent($event, scope.$index)"
@keyup-handle="handleKeyup"
v-bind="$attrs"
ref="editCell"
>
<template
v-for="(index, name) in slots"
v-slot:[name]="data"
>
<slot :name="name" v-bind="data"></slot>
</template>
</single-edit-cell>
</el-form>
</template>
<template v-if="item.filters && item.filters.list">
{{
constantEscape(
scope.row[item.prop],
table.listTypeInfo[item.filters.list],
item.filters.key || 'value',
item.filters.label || 'label'
)
}}
</template>
<div
v-if="
!item.render &&
!item.slotName &&
!item.canEdit &&
!item.filters
"
>
<span>{{ scope.row[item.prop] }}</span>
</div>
</template>
</el-table-column>
</template>
<t-table-column v-else :key="index + 'm'" :item="item">
<template v-for="(index, name) in slots" v-slot:[name]="data">
<slot :name="name" v-bind="data"></slot>
</template>
</t-table-column>
</template>
<slot></slot>
<el-table-column
v-if="table.operator"
:fixed="table.operatorConfig && table.operatorConfig.fixed"
:label="(table.operatorConfig && table.operatorConfig.label) || '操作'"
:min-width="table.operatorConfig && table.operatorConfig.minWidth"
:width="table.operatorConfig && table.operatorConfig.width"
:align="
(table.operatorConfig && table.operatorConfig.align) || 'center'
"
v-bind="table.operatorConfig && table.operatorConfig.bind"
class-name="operator"
>
<template #default="scope">
<div
class="operator_btn"
:style="table.operatorConfig && table.operatorConfig.style"
>
<template v-for="(item, index) in table.operator" :key="index">
<el-button
@click="
item.fun && item.fun(scope.row, scope.$index, state.tableData)
"
v-bind="{
type: 'primary',
link: true,
text: true,
size: 'small',
...item.bind,
...$attrs,
}"
v-if="checkIsShow(scope, item)"
>
<template v-if="item.render">
<render-col
:column="item"
:row="scope.row"
:render="item.render"
:index="scope.$index"
/>
</template>
<span v-if="!item.render">{{ item.text }}</span>
</el-button>
</template>
</div>
</template>
</el-table-column>
</el-table>
<el-pagination
v-if="state.tableData && state.tableData.length && isShowPagination"
small
v-model:current-page="table.currentPage"
@current-change="handlesCurrentChange"
:page-sizes="[10, 20, 50, 100]"
v-model:page-size="table.pageSize"
:layout="table.layout || 'total,sizes, prev, pager, next, jumper'"
:prev-text="table.prevText"
:next-text="table.nextText"
:total="table.total || 0"
v-bind="$attrs"
background
>
<slot name="pagination"></slot>
</el-pagination>
<footer
class="handle_wrap"
v-if="isShowFooterBtn && state.tableData && state.tableData.length > 0"
>
<slot name="footer" />
<div v-if="!slots.footer">
<el-button type="primary" @click="save">保存</el-button>
</div>
</footer>
</div>
</template>
<script setup lang="ts" name="TTable">
import { computed, ref, watch, useSlots, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import Sortable from 'sortablejs'
import SingleEditCell from './singleEditCell.vue'
import ColumnSet from './ColumnSet.vue'
import RenderCol from './renderCol.vue'
import RenderHeader from './renderHeader.vue'
import TTableColumn from './TTableColumn.vue'
const props = defineProps({
table: {
type: Object,
default: () => {
return {}
},
required: true,
},
columns: {
type: Array,
default: () => {
return []
},
},
btnPermissions: {
type: Array,
default: () => {
return []
},
},
title: {
type: String,
},
isTree: {
type: Boolean,
default: false,
},
isRowSort: {
type: Boolean,
default: false,
},
isCopy: {
type: Boolean,
default: false,
},
rowClickRadio: {
type: Boolean,
default: true,
},
defaultRadioCol: Number,
isPaginationCumulative: {
type: Boolean,
default: false,
},
isShowPagination: {
type: Boolean,
default: true,
},
isShowFooterBtn: {
type: Boolean,
default: false,
},
columnSetting: {
type: Boolean,
default: false,
},
highlightCurrentRow: {
type: Boolean,
default: false,
},
isTableColumnHidden: {
type: Boolean,
default: false,
},
sortable: {
type: [Boolean, String],
},
isKeyup: {
type: Boolean,
default: false,
},
})
let state = reactive({
tableData: props.table.data,
columnSet: [],
copyTableData: [],
})
const radioVal = ref(null)
const forbidden = ref(true)
const TTable: any = ref<HTMLElement | null>(null)
const TTableBox: any = ref<HTMLElement | null>(null)
const formRef: any = ref({})
const handleRef = (el, scope, item) => {
if (el) {
formRef.value[
`formRef-${scope.$index}-${item.prop || scope.column.property}`
] = el
}
}
const emits = defineEmits([
'save',
'page-change',
'handleEvent',
'radioChange',
'rowSort',
'validateError',
])
const slots = useSlots()
watch(
() => props.table.data,
(val) => {
state.tableData = val
},
{ deep: true }
)
onMounted(() => {
if (props.defaultRadioCol) {
defaultRadioSelect(props.defaultRadioCol)
}
initSort()
})
const defaultRadioSelect = (index) => {
radioVal.value = index
emits('radioChange', state.tableData[index - 1], radioVal.value)
}
const initSort = () => {
if (!props.isRowSort) return
const el = TTableBox.value.querySelector('.el-table__body-wrapper tbody')
Sortable.create(el, {
animation: 150,
onEnd: (evt) => {
const curRow = state.tableData.splice(evt.oldIndex, 1)[0]
state.tableData.splice(evt.newIndex, 0, curRow)
emits('rowSort', state.tableData)
},
})
}
const constantEscape = (value, list, key, label) => {
const res = list.find((item) => {
return item[key] === value
})
return res && res[label]
}
const isEditRules = computed(() => {
return (
(props.table.rules && Object.keys(props.table.rules).length > 0) ||
props.columns.some((item: any) => item?.configEdit?.rules)
)
})
const renderColumns = computed(() => {
return state.columnSet.length > 0
? state.columnSet.reduce((acc: any, cur: any) => {
if (!cur.hidden) {
let columnByProp: any = props.columns.reduce((acc: any, cur: any) => {
acc[cur.prop] = cur
return acc
}, {})
acc.push(columnByProp[cur.prop])
}
return acc
}, [])
: props.columns
})
const isTableBorder = computed(() => {
return props.columns.some((item: any) => item.children)
})
const handleKeyup = (event, index, key) => {
if (!props.isKeyup) return
state.copyTableData = JSON.parse(JSON.stringify(state.tableData))
if (event.keyCode === 38) {
let doms = document.getElementsByClassName(key)
if (!index) {
index = state.copyTableData.length
}
if (doms.length) {
let dom
if (doms[index - 1].getElementsByTagName('input')[0]) {
dom = doms[index - 1].getElementsByTagName('input')[0]
} else {
dom = doms[index - 1].getElementsByTagName('textarea')[0]
}
dom.focus()
}
}
if (event.keyCode === 40) {
let doms = document.getElementsByClassName(key)
if (+index === state.copyTableData.length - 1) {
index = -1
}
if (doms.length) {
let dom
if (doms[index + 1].getElementsByTagName('input')[0]) {
dom = doms[index + 1].getElementsByTagName('input')[0]
} else {
dom = doms[index + 1].getElementsByTagName('textarea')[0]
}
dom.focus()
}
}
if (event.keyCode === 13) {
let keyName = props.columns.map((val: any) => val.prop)
let num = 0
if (key === keyName[keyName.length - 1]) {
if (index === state.copyTableData.length - 1) {
index = 0
} else {
++index
}
} else {
keyName.map((v, i) => {
if (v === key) {
num = i + 1
}
})
}
let doms = document.getElementsByClassName(keyName[num])
if (doms.length) {
let dom
if (doms[index].getElementsByTagName('input')[0]) {
dom = doms[index].getElementsByTagName('input')[0]
} else {
dom = doms[index].getElementsByTagName('textarea')[0]
}
dom.focus()
}
}
}
const cellClassNameFuc = ({ row }) => {
if (!props.isTableColumnHidden) {
return false
}
if (
state.tableData.length -
(state.tableData.length - props.table.pageSize < 0
? 1
: state.tableData.length - props.table.pageSize) <=
row.rowIndex
) {
return 'table_column_hidden'
}
}
const isForbidden = () => {
forbidden.value = false
setTimeout(() => {
forbidden.value = true
}, 0)
}
const radioClick = (row, index) => {
forbidden.value = !!forbidden.value
if (radioVal.value) {
if (radioVal.value === index) {
radioVal.value = null
isForbidden()
emits('radioChange', null, radioVal.value)
} else {
isForbidden()
radioVal.value = index
emits('radioChange', row, radioVal.value)
}
} else {
isForbidden()
radioVal.value = index
emits('radioChange', row, radioVal.value)
}
}
const radioChange = (e, row, index) => {
if (props.rowClickRadio) {
return
}
e.preventDefault()
radioClick(row, index)
}
const rowClick = (row) => {
if (!props.rowClickRadio) {
return
}
radioClick(row, state.tableData.indexOf(row) + 1)
}
const copyDomText = (val) => {
const text = val
const input = document.createElement('input')
input.value = text
document.body.appendChild(input)
input.select()
document.execCommand('copy')
document.body.removeChild(input)
}
const cellDblclick = (row, column) => {
if (!props.isCopy) {
return false
}
try {
copyDomText(row[column.property])
ElMessage.success('复制成功')
} catch (e) {
ElMessage.error('复制失败')
}
}
const isShow = (name) => {
return Object.keys(slots).includes(name)
}
const save = () => {
if (!isEditRules.value) {
emits('save', state.tableData)
return state.tableData
}
let successLength = 0
let rulesList: any = []
let rulesError: any = []
let propError: any = []
let propLabelError: any = []
const refList = Object.keys(formRef.value).filter((item) =>
item.includes('formRef')
)
const arr = renderColumns.value
.filter((val) => {
if (val.configEdit?.rules) {
return val
}
})
.map((item) => item.prop)
const arr1 = props.table.rules && Object.keys(props.table.rules)
const newArr = [...arr, ...arr1]
newArr.map((val) => {
refList.map((item: any) => {
if (item.includes(val)) {
rulesList.push(item)
}
})
})
console.log('最终需要校验的数据', rulesList, formRef.value)
rulesList.map((val) => {
formRef.value[val].validate((valid) => {
if (valid) {
successLength = successLength + 1
} else {
rulesError.push(val)
}
})
})
setTimeout(() => {
if (successLength === rulesList.length) {
if (isEditRules.value) {
emits('save', state.tableData)
return state.tableData
}
} else {
rulesError.map((item) => {
newArr.map((val) => {
if (item.includes(val)) {
propError.push(val)
}
})
})
Array.from(new Set(propError)).map((item) => {
renderColumns.value.map((val) => {
if (item === val.prop) {
propLabelError.push(val.label)
}
})
})
console.log('校验未通过的prop--label', propLabelError)
emits('validateError', propLabelError)
}
}, 300)
}
const checkIsShow = (scope, item) => {
let isNoshow = false
if (item.noshow) {
let nushowFun = JSON.parse(JSON.stringify(item.noshow))
nushowFun.map((rs) => {
rs.isShow =
typeof rs.val === 'string'
? rs.val === 'isHasVal'
? scope.row[rs.key]
? 'true'
: 'false'
: 'true'
: rs.val.includes(scope.row[rs.key])
? 'false'
: 'true'
})
isNoshow = nushowFun.every((key) => {
return key.isShow === 'true'
})
} else {
isNoshow = true
}
let isShow = !item.show || item.show.val.includes(scope.row[item.show.key])
let isPermission = item.hasPermi
? props.btnPermissions?.includes(item.hasPermi)
: true
let totalTxt = Object.values(scope.row).every((key) => {
return key !== '当页合计'
})
let totalTxt1 = Object.values(scope.row).every((key) => {
return key !== '全部合计'
})
return (
isShow &&
isNoshow &&
!scope.row[item.field] &&
(item.isField ? scope.row[item.isField] : true) &&
totalTxt &&
totalTxt1 &&
isPermission
)
}
const handleEvent = ({ type, val }, index) => {
emits('handleEvent', type, val, index)
}
const handlesCurrentChange = (val) => {
emits('page-change', val)
}
const clearSelection = () => {
return TTable.value.clearSelection()
}
const getSelectionRows = () => {
return TTable.value.getSelectionRows()
}
const toggleRowSelection = (row, selected = false) => {
return TTable.value.toggleRowSelection(row, selected)
}
const toggleAllSelection = () => {
return TTable.value.toggleAllSelection()
}
const toggleRowExpansion = (row, expanded) => {
return TTable.value.toggleRowExpansion(row, expanded)
}
const setCurrentRow = (row) => {
return TTable.value.setCurrentRow(row)
}
const clearSort = () => {
return TTable.value.clearSort()
}
const clearFilter = (columnKey) => {
return TTable.value.clearFilter(columnKey)
}
const doLayout = (columnKey) => {
return TTable.value.doLayout(columnKey)
}
const sort = (prop: string, order: string) => {
return TTable.value.sort(prop, order)
}
const scrollTo = (options: any, yCoord: any) => {
return TTable.value.scrollTo(options, yCoord)
}
const setScrollTop = (top) => {
return TTable.value.setScrollTop(top)
}
const setScrollLeft = (left) => {
return TTable.value.setScrollLeft(left)
}
const clearValidate = () => {
const refList = Object.keys(formRef.value).filter((item) =>
item.includes('formRef')
)
refList.map((val) => {
formRef.value[val].clearValidate()
})
}
const resetFields = () => {
const refList = Object.keys(formRef.value).filter((item) =>
item.includes('formRef')
)
refList.map((val) => {
formRef.value[val].resetFields()
})
}
defineExpose({
clearSelection,
getSelectionRows,
toggleRowSelection,
toggleAllSelection,
toggleRowExpansion,
setCurrentRow,
clearSort,
clearFilter,
doLayout,
sort,
scrollTo,
setScrollTop,
setScrollLeft,
state,
radioVal,
clearValidate,
resetFields,
save,
})
</script>
<style lang="scss" scoped>
.t-table {
z-index: 0;
background-color: var(--el-bg-color);
:deep(.el-table__header-wrapper) {
.el-table__header {
margin: 0;
}
}
:deep(.el-table__body-wrapper) {
.el-table__body {
margin: 0;
}
}
:deep(.el-pagination) {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 20px;
// margin-right: 60px;
margin-right: calc(2% - 20px);
background-color: var(--el-bg-color);
}
// ttable过长省略号
.el-table {
.el-tooltip {
div {
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
padding-left: 10px;
padding-right: 10px;
}
.single_edit_cell {
overflow: visible;
}
}
}
// 某行隐藏复选框/单选框
.el-table {
.el-table__row {
:deep(.table_column_hidden) {
.cell {
.el-radio__input,
.el-checkbox__input {
display: none;
}
& > span {
display: none;
}
}
}
}
}
.el-table th,
.el-table td {
padding: 8px 0;
}
.el-table--border th:first-child .cell,
.el-table--border td:first-child .cell {
padding-left: 5px;
}
.el-table .cell {
padding: 0 5px;
}
.el-table--scrollable-y .el-table__fixed-right {
right: 8px !important;
}
.header_wrap {
display: flex;
align-items: center;
.toolbar_top {
flex: 0 70%;
display: flex;
padding: 10px 0;
align-items: center;
justify-content: flex-end;
.toolbar {
display: flex;
justify-content: flex-end;
width: 100%;
}
.el-button--small {
height: 32px;
}
.el-button--success {
background-color: #355db4;
border: 1px solid #355db4;
}
}
.header_title {
display: flex;
align-items: center;
flex: 0 30%;
padding: 10px 0;
font-size: 16px;
font-weight: bold;
line-height: 35px;
margin-left: 10px;
color: var(--el-text-color-primary);
}
}
.marginBttom {
margin-bottom: -8px;
}
// 单选样式
.radioStyle {
:deep(.el-radio) {
.el-radio__label {
display: none;
}
&:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner {
box-shadow: none;
}
}
:deep(tbody) {
.el-table__row {
cursor: pointer;
}
}
}
// 复制功能样式
.cursor {
:deep(tbody) {
.el-table__row {
cursor: pointer;
}
}
}
// 行拖动
.row_sort {
:deep(tbody) {
.el-table__row {
cursor: move;
}
}
}
// 每行高度设置
.el-table {
.el-table__body {
.el-table__row {
:deep(.el-table__cell) {
padding: 8px 0;
.cell {
min-height: 32px;
line-height: 32px;
// display: flex;
// align-items: center;
// justify-content: center;
}
}
}
}
}
// treeTable样式
.tree_style {
:deep(.el-table__body-wrapper) {
.el-table__body {
.cell {
display: flex;
align-items: center;
.el-table__expand-icon {
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
.operator {
// 操作样式
.operator_btn {
.el-button {
margin: 0;
margin-right: 10px;
}
}
}
// 页面缓存时,表格内操作栏每行高度撑满
:deep(.el-table__fixed-right) {
height: 100% !important;
}
// 选中行样式
.highlightCurrentRow {
:deep(.current-row) {
color: var(--el-color-primary);
cursor: pointer;
background-color: #355db4 !important;
}
}
.el-table--scrollable-y .el-table__body-wrapper {
overflow-x: auto;
}
.handle_wrap {
position: sticky;
z-index: 10;
right: 0;
bottom: -8px;
margin: 0 -8px -8px;
padding: 12px 16px;
background-color: var(--el-bg-color);
border-top: 1px solid #ebeef5;
text-align: right;
.el-btn {
margin-left: 8px;
}
}
}
</style>
六、组件地址
gitHub组件地址
gitee码云组件地址
vue3+ts基于Element-plus再次封装基础组件文档
七、相关文章
基于ElementUi再次封装基础组件文档
vue+element-ui的table组件二次封装