本文根据平常业务开发中经常接触的有操作权限的可编辑表格需求,给出Vue中另一种实现可编辑表格的方式,即分离数据与其私有属性的解耦方式,下文将以ElementUi为例。
/**
* @description: 实现可编辑表格(分离数据 与 行为状态)
* @param {Map} propMap 存储表格row和状态数据的键值对
* @param {Array} refArr 存储Map中的value值,将其变为响应式(若是在vue3中使用,则无需refArr)
* @param {Function} createRowState 自定义编辑状态
* @return {Object}
*/
function useEditTable (propMap, refArr, createRowState) {
let oldTable = [];
let oldProps = [];
// 求差集
const getDiffSet = (bigSet, smallSet) => bigSet.filter(item => !smallSet.includes(item))
// 增
const addRowState = (addArr, curProps) => {
addArr.forEach(item => {
const rowState = createRowState(curProps, {});
propMap.set(item, rowState);
refArr.push(rowState);
})
}
// 删
const delRowState = (delArr) => {
delArr.forEach(item => {
const rowState = propMap.get(item);
const index = refArr.findIndex(it => it === rowState);
propMap.delete(item);
refArr.splice(index, 1);
})
}
// 改
const modRowState = (addArr, delArr, curProps) => {
addArr.forEach((item, index) => {
const preItem = delArr[index];
const preRowState = propMap.get(preItem);
const curRowState = createRowState(curProps, preRowState);
propMap.set(item, curRowState).delete(preItem);
})
}
// 设置行对应的状态属性
const setRowState = (newTable, curProps) => {
if (!curProps.length) return;
// 取差值
const diffLen = newTable.length - oldTable.length;
if (diffLen > 0) { // add
addRowState(getDiffSet(newTable, oldTable), curProps);
} else if (diffLen < 0) { // del
delRowState(getDiffSet(oldTable, newTable));
} else { // mod
modRowState(getDiffSet(newTable, oldTable), getDiffSet(oldTable, newTable), curProps);
}
// 缓存
oldProps = [...curProps];
oldTable = [...newTable];
}
// 更新单个属性的状态
const updatePropState = (curProps) => {
// propMap为空时(表格从纯展示到可编辑状态)
if (!propMap.size) addRowState(oldTable, curProps);
// 更新属性状态
const addProps = getDiffSet(curProps, oldProps);
const delProps = getDiffSet(oldProps, curProps);
if (!addProps.length && !delProps.length) return;
for (const rowState of propMap.values()) {
// 创建单个状态属性
createRowState(addProps, rowState);
// 删除单个状态属性(置空)
delProps.forEach(prop => rowState[prop] = null);
}
// 缓存当前的可编辑属性列表
oldProps = [...curProps];
}
return {
setRowState,
updatePropState
}
}
<template>
<div class="hello">
<el-form :model="{ tb: tableData }" ref="validForm" size="mini">
<el-table :data="tableData" border style="width: 100%" @cell-click="cellClick">
<el-table-column type="index" width="50">el-table-column>
<el-table-column
v-for="{ prop, label } in columns"
:key="prop"
:prop="prop"
:label="label"
v-slot="{ row, $index }"
>
<span v-if="!isEdit">{{ row[prop] }}span>
<el-form-item v-else :prop="`tb.${$index}.${prop}`" :rules="getRule()">
<span v-if="!getPropState(row, prop, 'edit')">{{ row[prop] }}span>
<el-input v-else v-model="row[prop]" @blur="handleBlur(row, prop)">el-input>
el-form-item>
el-table-column>
el-table>
el-form>
<el-button @click="changeEditProps">change editProps<el-button>
div>
template>
<script>
import { useEditTable , columns, tableData } from "./static.js";
export default {
name: "EditTable",
data() {
return {
columns,
tableData: [],
editProps: ["a", "b", "c", "d", "e"], // 默认编辑的属性名
isEdit: false, // 判断属性的编辑状态是否初始化完成
propMap: new Map(),
refArr: [],
};
},
created() {
// 若表格为可编辑状态
this.isEdit = true;
// 注册可编辑表格通用函数
this.editTable = useEditTable(this.propMap, this.refArr, this.createRowState);
},
watch: {
// 监听表格数据变化,同步状态
tableData(newTable) {
/* setTimeout(() => {
this.editTable.setRowState(newTable, this.editProps);
if (!this.isEdit) this.isEdit = true;
}, 50);*/
this.editTable.setRowState(newTable, this.editProps);
},
// 当编辑属性动态变化时,对应的状态数据也同步新增或删除
editProps(newProps) {
this.editProps.updatePropState(newProps);
},
},
mounted() {
this.renderTable();
},
methods: {
// 自定义可编辑属性状态,在rowState中自定义对应的状态属性,并返回row
createRowState(editProps, rowState) {
editProps.forEach((prop) => {
rowState[prop] = {
edit: false,
showCorner: false,
};
});
return rowState;
},
// 自定义获取单个属性状态集合或状态值
getPropState(row, prop, opType) {
return opType
? this.propMap.get(row)?.[prop]?.[opType]
: this.propMap.get(row)?.[prop];
},
// 自定义设置单个属性状态
setPropState(row, prop, opType, val) {
this.propMap.get(row)[prop][opType] = val;
},
// 点击单元格时,设置对应编辑状态的edit属性为true,即切换到表单项
cellClick(row, { property }) {
if (!this.editProps.includes(property)) return;
this.setPropState(row, property, "edit", true);
},
// 模拟接口渲染
renderTable() {
setTimeout(() => {
this.tableData = tableData;
}, 2000);
},
//
handleBlur(row, prop) {
this.setPropState(row, prop, "edit", false);
},
changeEditProps() {
this.editProps = ['a', 'c'];
},
getRule() {
return [{ required: true, message: "必填", trigger: "blur" }];
},
},
};
</script>