ElementUI- 利用Map容器实现可编辑表格

前言

本文根据平常业务开发中经常接触的有操作权限的可编辑表格需求,给出Vue中另一种实现可编辑表格的方式,即分离数据与其私有属性的解耦方式,下文将以ElementUi为例。

一、设计思路
  • 用Map结构存储表格行数据与状态属性的映射关系,即key(表格行数据)-> value(行编辑字段的状态数据)
  • 根据可编辑表格产生变化的因素,即表格行、可编辑字段的变化,同步状态数据的变化
二、优点
  • 解耦表格行与状态数据,保持表格数据的纯粹性,模板数据绑定更简洁
  • 当表格编辑字段改变时,可动态增减对应的状态数据,提高内存利用率
  • 更高的灵活性和可操作性
三、核心代码
/**
 * @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>

你可能感兴趣的:(Vue,javascript,vue.js,elementui)