即拿即用:纯前端树形表格增删改查

30 阅读2分钟

综述:纯前端树形表格增删改查 样式:

image.png 话不多说,直接上完整代码(直接在删除和表单确认方法内部执行打印localFunnelLevelList数组即可):

<template>
  <div>
    <div style="display: flex;justify-content: space-between;margin-top: 3px;">
      <label class="required-label">层级数据(不小于2层)</label>

      <div>
        <el-button type="primary" style="height: 25px;" size="mini" @click="openModuleFilterModal(null, 'add')">
          + 添加
        </el-button>
        <el-button type="primary" style="height: 25px;" size="mini" @click="handleDeleteAll">
          清空
        </el-button>
      </div>

    </div>

    <el-table :data="localFunnelLevelList" style="width: 100%;margin-top: 10px;border-radius: 10px;" row-key="id" border
      ref="treeTable" default-expand-all :tree-props="{ children: 'Details', }" class="compact-tree-table">
      <el-table-column prop="LevelSort" label="排序" width="80"></el-table-column>
      <el-table-column prop="LevelName" label="层级名称" ></el-table-column>
      <el-table-column prop="Unit" label="单位" width="80"></el-table-column>
      <el-table-column prop="IsSql" label="数据来源" 
        :formatter="(row) => row.IsSql === 1 ? 'SQL语句' : '模板数据'"></el-table-column>
      <el-table-column fixed="right" label="操作" width="130">
        <template slot-scope="scope">
          <el-button @click="handleDelete(scope.row)" type="text" size="small">删除</el-button>
          <el-button type="text" size="small" @click="openModuleFilterModal(scope.row, 'edit')">编辑</el-button>
          <el-button type="text" size="small" @click="openModuleFilterModal(scope.row, 'add')"
            v-if="!scope.row.parentId || isTopLevel(scope.row)">
            添加
          </el-button>
        </template>
      </el-table-column>
    </el-table>
 <!--表单弹窗-->
    <!-- <funnel-plot width="600px" @FunnelConfirm="handleFunnelConfirm" @cancel="handleDialogCancel"
      @close="handleDialogCancel" :filterList="filterList" :data="dataDetail" :currentDataSourceType="FormNo"
      :visible.sync="screeningVisible" /> -->
  </div>
</template>

<script>
import FunnelPlot from "./FunnelPlot.vue";

export default {
  components: { FunnelPlot },
  props: {
    FunnelLevelList: {
      type: Array,
      default: () => []
    },
    FormNo: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      screeningVisible: false,
      filterList: [],
      dataDetail: {},
      localFunnelLevelList: [],
      operateType: '',
      currentRow: null,
    }
  },
  watch: {
    FunnelLevelList: {
      handler(newVal) {
        const copiedVal = JSON.parse(JSON.stringify(newVal || []));
        // 为每个元素添加唯一ID和parentId
        this.addIdsToTreeData(copiedVal);
        this.localFunnelLevelList = copiedVal;
      },
      immediate: true,
      deep: true
    }
  },
  methods: {
    // 生成唯一ID
    generateId() {
      return 'id_' + Date.now() + '_' + Math.floor(Math.random() * 100000);
    },

    isTopLevel(row) {
      // 判断是否为顶层节点(根节点,parentId为null)
      return !row.parentId;
    },

    isNodeInRootLevel(id) {
      // 兼容原有逻辑,判断是否是根节点
      return this.localFunnelLevelList.some(item => item.id === id);
    },

    // 递归为树形数据添加唯一ID和parentId
    addIdsToTreeData(treeData) {
      if (!treeData || !Array.isArray(treeData) || treeData.length === 0) {
        return treeData;
      }

      // 重构addId方法,添加parentId参数
      const addId = (node, parentId = null) => {
        if (!node || typeof node !== 'object') {
          return;
        }

        if (!node.id) {
          node.id = this.generateId();
        }
        if (parentId !== null) {
          node.parentId = parentId;
        } else if (!node.hasOwnProperty('parentId')) {
          node.parentId = null; // 根节点显式设置parentId为null
        }

        if (!node.hasOwnProperty('Details') || !Array.isArray(node.Details)) {
          node.Details = [];
        }
        if (node.Details && Array.isArray(node.Details) && node.Details.length > 0) {
          node.Details.forEach(child => {
            addId(child, node.id);
          });
        }
      };
      treeData.forEach(item => {
        addId(item);
      });

      return treeData;
    },

    handleDialogCancel() {
      this.screeningVisible = false;
    },

    // 获取搜索字段
    async getSearchField() {
      if (!this.FormNo) return [];
      try {
        const res = await this.$h.api.System_Report.querySearchFields({ FormNo: this.FormNo });
        this.filterList = res?.Data || [];
        return this.filterList;
      } catch (e) {
        this.filterList = [];
        return [];
      }
    },

    // 打开弹窗
    async openModuleFilterModal(row, type) {
      this.operateType = type;
      this.currentRow = row;
      this.dataDetail = {};
      await this.getSearchField();
      if (type === 'edit') {
        this.dataDetail = JSON.parse(JSON.stringify(row));
      } else if (type === 'add') {
        this.dataDetail = {
          id: this.generateId(),
          LevelName: '',
          LevelSort: 1,
          Unit: '',
          ValueField: '',
          IsSql: 0,
          Aggregate: '',
          LevelRemark: '',
          Details: [],
          parentId: row?.id || null //设置父节点ID
        };
      }

      this.screeningVisible = true;
    },

    // 处理新增/编辑确认
    handleFunnelConfirm(formData) {
      try {
        const newList = JSON.parse(JSON.stringify(this.localFunnelLevelList));
        const submitData = {
          ...formData,
          Details: formData.Details || [],
          // 确保parentId存在
          parentId: formData.parentId || null
        };

        // 确保ID存在
        if (!submitData.id) {
          submitData.id = this.generateId();
        }

        let success = false;
        if (this.operateType === 'edit') {
          success = this.updateTreeNode(newList, submitData);
        } else if (this.operateType === 'add') {
          if (this.currentRow?.id) {
            success = this.addChildNode(newList, this.currentRow.id, submitData);
          } else {
            newList.push(submitData);
            success = true;
          }
        }

        if (success) {
          this.localFunnelLevelList = JSON.parse(JSON.stringify(newList));
          this.$nextTick(() => {
            if (this.$refs.treeTable) {
              this.$refs.treeTable.doLayout();
            }
          });
          this.$emit("FunnelConfirmEnd", newList);
          this.handleDialogCancel();
        }
      } catch (error) {
        console.error('处理数据失败:', error);
      }
    },

    // 新增子节点
    addChildNode(tree, parentId, childData) {
      for (let i = 0; i < tree.length; i++) {
        if (tree[i].id === parentId) {
          tree[i].Details = tree[i].Details || [];
          tree[i].Details.push(childData);
          return true;
        }
        if (tree[i].Details && tree[i].Details.length) {
          const added = this.addChildNode(tree[i].Details, parentId, childData);
          if (added) return true;
        }
      }
      return false;
    },

    // 递归更新树形节点
    updateTreeNode(tree, data) {
      for (let i = 0; i < tree.length; i++) {
        if (tree[i].id === data.id) {
          // 保留子节点数据,只更新当前节点属性(包含parentId)
          tree[i] = {
            ...tree[i],
            ...data,
            Details: tree[i].Details || []
          };
          return true;
        }
        if (tree[i].Details && tree[i].Details.length) {
          const updated = this.updateTreeNode(tree[i].Details, data);
          if (updated) return true;
        }
      }
      return false;
    },

    // 删除全部
    handleDeleteAll() {
      this.localFunnelLevelList = [];
      this.$emit("FunnelConfirmEnd", this.localFunnelLevelList);
    },

    // 删除节点
    handleDelete(row) {
      this.$confirm('确定删除该层级(含子层级)?', '提示', { type: 'warning' })
        .then(() => {
          const newList = JSON.parse(JSON.stringify(this.localFunnelLevelList));
          const isDeleted = this.deleteTreeNode(newList, row.id);
          if (isDeleted) {
            this.localFunnelLevelList = [...newList];
            this.$message.success('删除成功');
            this.$emit("FunnelConfirmEnd", newList);
          } else {
            this.$message.warning('未找到该层级');
          }
        })
        .catch(() => {
          // 取消删除
        });
    },

    // 递归删除节点
    deleteTreeNode(tree, id) {
      for (let i = 0; i < tree.length; i++) {
        if (tree[i].id === id) {
          tree.splice(i, 1);
          return true;
        }
        if (tree[i].Details && tree[i].Details.length) {
          const deleted = this.deleteTreeNode(tree[i].Details, id);
          if (deleted) {
            return true;
          }
        }
      }
      return false;
    }
  }
}
</script>