目录
ElementPlus中的 el-table 中没有编辑模式,所以需要自己封装一下
一、思路:
通过插槽(具名插槽、作用域插槽一起使用 )将 el-input 或 el-select 传递给封装的组件,其中 可编辑模式是利用 v-if 切换普通的 div 和 el-input/el-select 组件。
具名插槽用于 插入位置,并按照名字来识别对应的插槽(插入多个el-input、el-select等)
作用于插槽用于 让插槽内容能够访问或使用子组件中才有的数据
二、功能点:
- 根据某个属性去决定当前组件是否 可编辑
- 双击开启编辑模式(可编辑)
- 开启编辑模式后点击其他位置(其他的el-input或dom),关闭编辑模式 (可编辑)
三、封装组件:
1、代码
1.1、capsulation-table 组件
<template>
<div class="table-flex">
<el-table
ref="tableFlex"
:data="tableData"
>
<template v-for="(col) in tableColumns" :key="col.prop">
<el-table-column
:prop="col.prop"
:label="col.label"
:show-overflow-tooltip="col.showTooltip"
>
<template #default="scope" v-if="col.edit">
<div
:class="col.edit + scope.$index + scope.cellIndex"
@dblclick="
() => {
cancelOtherOptions(scope,col.edit);
}
"
>
<div
v-if="!scope.row[col.edit + scope.$index + scope.cellIndex]"
style="
min-height: 23px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
"
>
{{ scope.row[col.prop] }}
</div>
<slot
v-else
:name="col.edit"
:scope="scope.row"
:key="col.prop"
></slot>
</div>
</template>
</el-table-column>
</template>
</el-table>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import { ElTable, ElTableColumn } from "element-plus";
defineOptions({
name: "CapsulationTable",
});
const props = defineProps({
tableData: {
type: Array as any,
default: () => [],
},
tableColumns: Array as any,
});
/**
* 深度监听 tableDatle,数据发生变化后更新表格布局
*/
watch(
() => props.tableData,
() => {
tableLayout();
},
{ deep: true }
);
/**
* 点击事件 用于完成功能点二
*/
const cancelOtherOptions = (scope: any,key:string) => {
scope.row[key + scope.$index + scope.cellIndex] = true;
const dom = document.querySelector(
`.`+key + scope.$index + scope.cellIndex
) as HTMLDivElement;
const monitorClick = (event: Event) => {
let targetElement: any = event.target;
if (!dom.contains(targetElement)) {
document.removeEventListener("click", monitorClick);
delete scope.row[key + scope.$index + scope.cellIndex];
}
};
document.addEventListener("click", monitorClick);
};
const tableFlex = ref();
// 重新计算并更新表格的布局
const tableLayout = () => {
tableFlex.value.doLayout();
};
</script>
<style lang="scss">
.table-flex {
display: flex;
flex-direction: column;
align-items: flex-start;
.el-table__inner-wrapper {
.el-table__header-wrapper {
.el-table__header {
tr,
th {
background: #f5f5f5;
}
}
}
}
}
</style>
1.2、capsulation-table 所要使用的数据
<template>
<capsulation-table
:table-data="tableData"
:table-columns="tableColumn"
>
<template #ddd="{ scope, key }">
<el-input
:autofocus="true"
:autosize="{ minRows: 1, maxRows: 4 }"
v-model="scope[key]"
/>
</template>
<template #abc="{ scope, key }">
<el-select
class="m-2"
placeholder="Select"
v-model="scope[key]"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</capsulation-table>
</template>
<script setup lang="ts">
const tableData = reactive([
{
id: 1,
name: "里斯",
age: 18,
studentID: 1231,
},
{
id: 2,
name: "里斯3",
age: 18,
studentID: 131,
},
{
id: 3,
name: "里斯",
age: 18,
studentID: 1231,
},
{
id: 4,
name: "里斯3",
age: "",
studentID: 131,
},
{
id: 5,
name: "里斯",
age: 18,
studentID: 1231,
},
{
id: 6,
name: "里斯3",
age: 18,
studentID: 131,
},
{
id: 7,
name: "里斯",
age: 18,
studentID: 1231,
},
{
id: 313,
name: "里斯3",
age: 18,
studentID: 131,
},
{
id: 214,
name: "里斯",
age: 18,
studentID: 1231,
},
{
id: 1241,
name: "里斯3",
age: 18,
studentID: 131,
},
{
id: 1241,
name: "里斯3",
age: 18,
studentID: 131,
},
]);
const tableColumn = ref([
{
prop: "studentID",
label: "学生ID",
},
{
edit: "ddd",
prop: "name",
label: "名字",
showTooltip: true
},
{
edit: "abc",
prop: "age",
label: "年龄",
},
]);
const options = [
{
value: 11,
label: 11,
},
{
value: 12,
label: 12,
},
{
value: 13,
label: 13,
},
{
value: 14,
label: 14,
},
{
value: 15,
label: 15,
},
];
</script>
2、分析
2.1、cancelOtherOptions()方法
该方法用于用户点击 table 的时候,给点击的这个 table 添加一个属性,这个属性的键是由 {{传递过来的 edit的值}}+{{当前table所在的行}}+{{当前table所在的列}} 所组成的,是唯一的,值为 true or false。
通过 :class="col.edit + scope.$index + scope.cellIndex" 给每一个div添加一个唯一的class
因为一开始 tableData 中是没有这个属性的,所以 v-if="!scope.row[col.edit + scope.$index + scope.cellIndex]" 为true ,显示正常的div,当点击 table 后,给当前 table添加 scope.row[key + scope.$index + scope.cellIndex] 属性设置为 true ,显示通过插槽传递过来的 el-input 或 el-select组件,这样就可以对数据进行编辑。
当组件处于可编辑状态的时候,点击组件内部不退出编辑状态,但是点击其他dom就需要退出编辑状态并显示原始 div
要实现这个功能我的想法是在点击 table 的时候给 document 添加一个点击事件,用于监听接下来点击的dom是否为处于编辑状态的组件或或其子级div,如果不在则移除绑定在 document 上的点击事件并 删除 scope.row[key + scope.$index + scope.cellIndex] 属性,这样 !scope.row[key + scope.$index + scope.cellIndex] 为 true,退出编辑状态,显示常规 div。
if (!dom.contains(targetElement)) {
document.removeEventListener("click", monitorClick);
delete scope.row[key + scope.$index + scope.cellIndex];
}
根据某个属性去决定当前组件是否 可编辑
这个功能就很简单了,在 tableColumn 中的去添加一个edit属性,然后在 capsulation-table 组件中 <template #default="scope" v-if="col.edit"> 判断是否有这个属性,如果有 则对数据进行进一步处理,如果没有,则走el-table、el-table-column默认显示数据的路线。(这个edit一定要是唯一的,要不然会有问题)
const tableColumn = ref([
{
prop: "studentID",
label: "学生ID",
},
{
edit: "ddd",
prop: "name",
label: "名字",
showTooltip: true
},
{
edit: "abc",
prop: "age",
label: "年龄",
},
]);
四、总结
封装的想法和思路主要是运用插槽的知识点,插槽在 vue 中还是蛮重要的,大家可以多看看插槽有关的知识点,灵活运用插槽。
以上是我自己对于 ElementPlus el-table 二次封装的思路和想法,肯定是有更好的方法去实现这个功能,如果大家有不同的想法和思路 欢迎在评论区留言讨论。