element el-tree 树形图增删改查-逐级操作,懒加载

本文介绍了如何在前端使用El-Tree组件实现动态添加、修改和删除节点,并结合懒加载功能。详细阐述了在操作树形结构数据时的步骤,包括获取子节点、更新DOM以及调用接口的逻辑。同时提供了相关的Vue.js代码示例,展示了如何在节点点击事件中处理业务逻辑。

业务:每一层级单独获取操作 

关键词:updateKeyChildren(更新子节点)

思路:

        添加:

                1.先获取当前节点子集数组

let children = this.node.childNodes.map(function(item) {
        return item.data
      })

                2.把新增数据(调用接口结束后)加到children中然后更新节点

children.unshift(data)
this.$refs.tree.updateKeyChildren(this.node.data.id, children)

        修改:

第一级直接调用接口刷新,

下级先获取父级(parentNode)id,然后通过接口获取数据(调用接口结束后)通过updateKeyChildren(更新节点)  this.$refs.tree.updateKeyChildren(父id, 获取的数据)

1.调用接口获取数据  2.修改dom

dom

<div class="leftArea">
      <el-tree
        accordion
        :props="{ label: 'caption' }"
        :data="tableDataTree" // 接口获取树 可直接获取第一层
        node-key="id"  // 标识必须有
        :load="middleLoadNode" // 动态,懒加载--子集调用接口获取
        @node-click="nodeClick"
        lazy // 懒加载
        ref="tree"
      >
        <span class="custom-tree-node" slot-scope="{ node, data }">
          <span>{{ node.label }}</span>
          <span>
            <el-button type="text" size="mini" @click="() => append(data, node)">
              添加
            </el-button>
            <el-button type="text" size="mini" @click="() => edit(data)">
              修改
            </el-button>
            <el-button type="text" size="mini" @click="() => remove(data.id)">
              删除
            </el-button>
          </span>
        </span></el-tree
      >
    </div>

 js

设置动态懒加载

    async middleLoadNode(tree, resolve) {
      // 懒加载中间树第一层后面所有层
      if (tree.data.id != undefined) {
        const params = {
          parentid: tree.data.id,  // 父id
          column: 'stepno', //排序字段
          order: 'ASC' // 
        }  // 根据情况传参
        getDicWorkProcess(params) // 获取子节点数据接口
          .then(res => {
            console.log(res)
            return resolve(res.result.records)  // 返回数据到树里面
          })
          .catch(err => {
            console.log(err)
          })
      }
    },
    append(row, node) { // 新增按钮
      this.type = 'middleTree'
      this.node = node //此项获取node其他参数为业务数据--用于更新tree 获取node.id
      this.middleData = row
      this.moduelType = 'middle:add' // 接口标识
      this.dialogVisible = true
      this.mainForm = addVo()
      this.mainForm.parentid = row.id // 获取父id
      this.mainForm.stationcode = row.stationcode
      this.dialogTitle = '流程添加'
    },
    edit(row) { // 修改按钮
      this.type = 'middleTree'
      this.moduelType = 'middle:edit' // 接口标识
      this.dialogVisible = true
      this.mainForm = row
      this.dialogTitle = '流程修改' // 右侧向点修改
    },

    async submit() {
      let children = this.node.childNodes.map(function(item) {
        return item.data
      })
      const fn = {
        'middle:edit': editDicWorkProcess,
        'middle:add': addDicWorkProcess
      }
      const query = this.mainForm
      const { message, result: data, success } = await fn[this.moduelType](query)
      if (!success) return this.$alert(message)
      if (success) {
        if (this.state == 'edit') {
          if (this.mainForm.steplevel == 1) {
            this.initTree() // 第一级直接刷新接口
          } else if (this.mainForm.steplevel != 1) { // 其他下级 先获取其父级id调接口
            const params = { 
              parentid: this.parentNode.data.id,
              column: 'stepno', //排序字段
              order: 'ASC' // ASC正序DESC倒序默认倒序
            }
            const { result: res } = await getDicWorkProcess(params)
            console.log(res) // 获取的数据替换父级的子集(自己)
             // 修改
            this.$refs.tree.updateKeyChildren(this.parentNode.data.id, res.records)
          }
        } else if (this.state == 'append') {
          if (data) {
            children.unshift(data)
            this.$refs.tree.updateKeyChildren(this.node.data.id, children)
          }
        }
        this.close()
        this.$alert(message)
      }
    },
    remove(id) { // 删除
      this.$confirm('确定删除吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        deleteDicWorkProcess({ id: id }).then(res => {
          if (res.success) {
            this.$message.success('删除成功!')
            const { tree } = this.$refs
            tree.remove(id)
          } else {
            this.$message.warning('删除失败!')
          }
        })
      })
    },

问题请留言

<template> <div class="page-container"> <el-row :gutter="20"> <!-- 查询区域 --> <el-col :span="24"> <div class="search-wrapper"> <el-form :inline="true" label-width="100px" @submit.prevent="getList"> <el-form-item label="名称"> <el-input v-model="queryParams.name" placeholder="请输入名称" /> </el-form-item> <el-form-item label="责任人"> <el-input v-model="queryParams.respPerson" placeholder="请输入责任人" /> </el-form-item> <el-form-item> <el-button type="primary" @click="getList">查询</el-button> <el-button @click="resetQuery">重置</el-button> </el-form-item> </el-form> </div> </el-col> <!-- 列表与甘特图切换按钮 --> <el-col :span="24"> <div class="toggle-button"> <el-button type="primary" @click="toggleGantt" style="margin-bottom: 15px;" > {{ showGantt ? '收起甘特图' : '展开甘特图' }} </el-button> </div> </el-col> <!-- 左侧列表 --> <el-col :span="showGantt ? 12 : 24"> <div class="table-container"> <el-table ref="table" :data="listData" row-key="uid" border :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" @row-click="rowClick" @expand-change="handleExpandChange" > <el-table-column prop="code" label="编号" /> <el-table-column prop="name" label="名称" /> <el-table-column prop="respPerson" label="责任人" /> <el-table-column prop="schedule" label="完成百分比" /> <el-table-column prop="planStartDate" label="计划开始日期" /> <el-table-column prop="planEndDate" label="计划结束日期" /> <el-table-column label="操作"> <template slot-scope="scope"> <el-button size="mini" icon="el-icon-view" @click="handleUpdate(scope.row)">查看</el-button> </template> </el-table-column> </el-table> <!-- 分页 --> <el-pagination layout="prev, pager, next" :total="total" :page-size="queryParams.pageSize" @current-change="handleCurrentChange" /> </div> </el-col> <!-- 右侧甘特图容器 --> <el-col :span="12" v-if="showGantt"> <div ref="ganttContainer" class="gantt-container" style="width: 100%; height: 600px;"></div> </el-col> </el-row> <!-- 查看弹窗 --> <el-dialog :title="title" :visible.sync="open" width="850px" append-to-body> <el-form ref="form" :model="form" :rules="rules" label-width="100px" :disabled="disable"> <el-row> <el-col :span="12"> <el-form-item label="编号" prop="code"> <el-input v-model="form.code" placeholder="请输入编号" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="名称" prop="name"> <el-input v-model="form.name" placeholder="请输入名称" /> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="备注" prop="remarks"> <el-input v-model="form.remarks" type="textarea" placeholder="请输入备注" /> </el-form-item> </el-col> </el-row> <div class="dialog-footer"> <el-button @click="cancel">取 消</el-button> </div> </el-form> </el-dialog> </div> </template> <script> import gantt from 'dhtmlx-gantt'; import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'; import { listPlan, getPlan } from '@/api/dw/plan/planview'; export default { name: 'Planview', data() { return { listData: [], total: 0, queryParams: { pageNum: 1, pageSize: 10, name: null, respPerson: null }, open: false, title: '', form: {}, rules: { name: [{ required: true, message: '名称不能为空', trigger: 'blur' }], schedule: [ { required: true, message: '完成百分比不能为空', trigger: 'blur' }, { type: 'number', message: '输入内容不是有效的数字', trigger: 'blur' } ] }, disable: true, showGantt: true, // 控制甘特图显示 flatData: [], // 扁平化数据 baseDate: new Date('2023-01-01'), // 基准日期 maxDuration: 365, // 最大工期(天) maxOffset: 365 // 最大偏移天数 }; }, mounted() { this.getList(); this.initGantt(); }, methods: { async getList() { const res = await listPlan(this.queryParams); this.listData = this.handleTree(res.data, 'uid', 'parentUid'); this.total = res.total; this.flatData = this.flattenTree(this.listData); this.getMaxDuration(); this.$nextTick(() => { const tasks = this.ganttData(this.flatData); this.updateGantt(tasks); }); }, // 获取最大工期 getMaxDuration() { const durations = this.flatData.map(item => item.planDuration || 0); this.maxDuration = Math.max(...durations, 1); }, // 计算甘特图宽度 calculateGanttWidth(row) { const duration = row.planDuration || 0; const width = (duration / this.maxDuration) * 100; return `${Math.max(5, width)}%`; }, // 计算甘特图偏移 calculateGanttOffset(row) { if (!row.planStartDate) return '0%'; const startDate = new Date(row.planStartDate); const daysOffset = Math.floor((startDate - this.baseDate) / (1000 * 60 * 60 * 24)); return `${(daysOffset / this.maxOffset) * 100}%`; }, // 初始化甘特图 initGantt() { if (!this.$refs.ganttContainer) return; gantt.config.date_format = '%Y-%m-%d'; gantt.config.columns = [ { name: 'text', label: '任务名称', tree: true, width: '*' }, { name: 'start_date', label: '开始时间', align: 'center' }, { name: 'duration', label: '工期(天)', align: 'center' } ]; gantt.templates.task_text = (start, end, task) => task.text; gantt.init(this.$refs.ganttContainer); gantt.parse({ data: [], links: [] }); // 确保事件监听器只绑定一次 if (!this.ganttEventInitialized) { gantt.attachEvent('onTaskSelected', id => { const row = this.flatData.find(item => item.uid === id); if (row) { this.$refs.table.setCurrentRow(row); } }); this.ganttEventInitialized = true; } }, // 更新甘特图 updateGantt(tasks) { gantt.clearAll(); gantt.parse({ data: tasks, links: [] }); }, // 树形结构转扁平结构 flattenTree(data) { const result = []; const stack = [...data]; while (stack.length) { const node = stack.pop(); result.push(node); if (node.children) { stack.push(...node.children); } } return result; }, // 转换为甘特图数据 ganttData(data) { return data .filter(item => item.uid && item.planStartDate) .map(item => ({ id: item.uid, text: item.name, start_date: item.planStartDate, duration: item.planDuration || 0, progress: (item.schedule || 0) / 100, parent: item.parentUid || 0 })); }, // 处理树形结构 handleTree(data, idKey = 'id', parentKey = 'parentId') { const map = {}; const tree = []; data.forEach(item => (map[item[idKey]] = item)); data.forEach(item => { const parent = map[item[parentKey]]; if (parent) { (parent.children || (parent.children = [])).push(item); } else { tree.push(item); } }); return tree; }, // 行点击事件 rowClick(row) { const taskId = row.uid; this.$nextTick(() => { if (gantt.$initialized) { gantt.showTask(taskId); gantt.selectTask(taskId); // 强制重绘确保高亮生效 gantt.render(); } }); }, // 树展开/折叠更新甘特图 handleExpandChange(row, expanded) { if (expanded) { const allChildren = this.getAllChildren(row); const tasks = this.ganttData(allChildren); this.$nextTick(() => { this.updateGantt(tasks); if (gantt.$initialized && tasks.length > 0) { // 获取展开节点的最早和最晚日期 const dates = tasks .filter(t => t.start_date) .map(t => new Date(t.start_date)); if (dates.length > 0) { const minDate = new Date(Math.min(...dates.map(d => d.getTime()))); const maxDate = new Date(Math.max(...dates.map(d => { const endDate = new Date(t.start_date); endDate.setDate(endDate.getDate() + (t.duration || 0)); return endDate.getTime(); }))); // 设置视图时间范围 gantt.setWorkTime({ start_date: minDate, end_date: maxDate }); // 调整视图缩放级别 gantt.config.scale_unit = 'day'; gantt.config.step = 1; gantt.config.scale_height = 28; // 重新渲染并定位第一个任务 gantt.render(); gantt.showTask(tasks[0].id); gantt.selectTask(tasks[0].id); } } }); } else { const topLevelTasks = this.listData.map(item => ({ id: item.uid, text: item.name, start_date: item.planStartDate, duration: item.planDuration || 0, progress: (item.schedule || 0) / 100, parent: item.parentUid || 0 })); this.$nextTick(() => { this.updateGantt(topLevelTasks); if (gantt.$initialized) { // 恢复默认时间范围 gantt.setWorkTime({ start_date: new Date('2023-01-01'), end_date: new Date('2023-12-31') }); gantt.config.scale_unit = 'month'; gantt.config.step = 1; gantt.config.scale_height = 28; gantt.render(); } }); } }, // 递归获取所有子节点 getAllChildren(node) { let children = [node]; if (node.children && node.children.length > 0) { node.children.forEach(child => { children = children.concat(this.getAllChildren(child)); }); } return children; }, // 切换甘特图 toggleGantt() { this.showGantt = !this.showGantt; if (this.showGantt) { this.$nextTick(() => { const tasks = this.ganttData(this.flatData); this.updateGantt(tasks); }); } }, // 获取数据 async handleUpdate(row) { const res = await getPlan(row.uid); this.form = res.data; this.open = true; this.title = '查看治理计划'; }, // 取消按钮 cancel() { this.open = false; }, // 重置查询 resetQuery() { this.queryParams = { pageNum: 1, pageSize: 10, name: null, respPerson: null }; this.getList(); }, // 分页切换 handleCurrentChange(page) { this.queryParams.pageNum = page; this.getList(); } } }; </script> <style scoped> .page-container { padding: 20px; } .table-container { background-color: #fff; padding: 10px; border-radius: 4px; } .gantt-container { background-color: #f9f9f9; border: 1px solid #ebeef5; padding: 10px; border-radius: 4px; } .dialog-footer { text-align: right; } .search-wrapper { margin-bottom: 20px; background-color: #fff; padding: 10px; border-radius: 4px; } .toggle-button { margin-bottom: 15px; } .gantt-bar-container { position: relative; height: 30px; background-color: #f5f7fa; border-radius: 4px; overflow: hidden; margin: 5px 0; } .gantt-bar { position: absolute; top: 0; left: 0; height: 100%; background-color: #409EFF; color: white; text-align: center; font-size: 12px; line-height: 30px; } </style> 列表的展开和收缩,甘特图没有同步,请重新优化
07-23
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值