vue-tree-color 组件实现组织架构图遇到的坑和解决方案以及实现

news2025/1/17 3:55:06

**1、前期工作可以先看看大佬的文章 **https://blog.csdn.net/Try_your_best_l/article/details/120173192?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2defaultCTRLISTRate-5-120173192-blog-128109597.235%5Ev38%5Epc_relevant_default_base&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2defaultCTRLISTRate-5-120173192-blog-128109597.235%5Ev38%5Epc_relevant_default_base&utm_relevant_index=10

2、安装完 vue-tree-color 和 less less-loader 后启动项目,报错,两种情况,一种是less 版本问题(less 使用 3.9.0) 和 autoprefixer 版本问题(使用 7.1.6)

3、进入开发,主要是样式问题了(先看看最终效果)
在这里插入图片描述
4、并不是单纯的展示数据而已,还要操作,自定义样式,按钮权限等

// index.vue
<template>
  <div v-loading="loading" class="culture_box" :style="{ transform: scale }" @mousewheel="handleMouseWheel">
    <vue2-org-tree v-if="isShow" :data="treeData" :render-content="renderContent" :props="props" collapsable @on-expand="onExpand" />
    <el-dialog :title="popTitle" append-to-body :visible.sync="addPop" width="30%" :close-on-click-modal="false" @close="closeDialog">
      <el-form ref="editForm" label-width="120px" :model="editForm" :rules="rules">
        <el-form-item label="部(所)名称" prop="deptName" class="add_form_item">
          <el-input v-model="editForm.deptName" :maxlength="10" auto-complete="off" :disabled="type == 3" placeholder="请输入部(所)名称" />
        </el-form-item>
        <el-form-item label="文化路径展示" class="add_form_item">
          <el-upload
            v-if="type === 1 || type === 2"
            ref="uploadImg"
            class="avatar-uploader"
            :show-file-list="false"
            action="/tk/api/icomx-resource/oss/endpoint/put-file-attach"
            :on-success="handleAvatarSuccess"
            :before-upload="beforeAvatarUpload"
          >
            <img v-if="editForm.photographs !== ''" style="width: 100px;" :src="editForm.photographs" alt="">
            <i v-else class="el-icon-plus upload_plus" />
          </el-upload>
          <el-image v-else style="width: 100px;" :src="editForm.photographs" alt="" :preview-src-list="[editForm.photographs]" />
          <div v-if="(type === 2 || type === 1) && editForm.photographs != ''" class="btn_img">
            <el-button @click="delImg">删除</el-button>
            <el-button @click="changeImg">更换</el-button>
          </div>
        </el-form-item>
        <el-form-item label="部(所)简介" class="add_form_item">
          <el-input v-model="editForm.deptDescribe" type="textarea" auto-complete="off" :disabled="type == 3" placeholder="请输入部(所)简介" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="closeDialog">{{ type === 3? '关闭': '取消' }}</el-button>
        <el-button v-if="type === 1 || type === 2" type="primary" class="title" :loading="saveLoading" @click="handleSave">保存</el-button>
      </div>
    </el-dialog>
    <el-dialog :visible.sync="dialogVisible">
      <img width="100%" :src="dialogImageUrl" alt="">
    </el-dialog>
  </div>
</template>
<script>
import { getTreeData, getTreeAdd, getTreeEdit, getTreeDel } from '@/api/culture/index.js'
export default {
  data() {
    return {
      treeData: {},
      props: {
        label: 'deptName',
        children: 'children',
        expand: 'expand'
      },
      isShow: false,
      imgUrl: '',
      dialogVisible: false,
      dialogImageUrl: '',
      type: 1, // 新增修改详情参数
      addPop: false,
      editForm: {
        deptName: '',
        photographs: '',
        deptDescribe: ''
      },
      saveLoading: false,
      rules: {
        deptName: [
          { required: true, message: '请输入部(所)名称', trigger: 'blur' }
        ]
      },
      scaleRatio: 1,
      loading: false,
      popTitle: ''
    }
  },
  computed: {
    scale() {
      return `scale(${this.scaleRatio})`
    }
  },
  created() {
    this.init()
  },
  methods: {
    // 初始化数据
    async init() {
      this.loading = true
      let res = await getTreeData()
      this.loading = false
      if (res.data.code === 200) {
        this.treeData = res.data.data[0]
        this.toggleExpand(this.treeData, true)
        this.isShow = true
      }
    },
    // 自定义事件
    handleEdit(data, type) {
      this.type = type
      this.addPop = true
      this.editForm = { ...data }
      this.popTitle = this.type === 1 ? data.deptName + '新增' : this.type === 2 ? data.deptName + '编辑' : data.deptName + '详情'
      // 新增清空项
      if (type === 1) {
        this.editForm.deptName = ''
        this.editForm.photographs = ''
        this.editForm.deptDescribe = ''
      }
    },
    // 删除某节点
    handleDel(data) {
      this.$confirm('确认是否删除该部(所)?', '温馨提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async() => {
        return getTreeDel(data.id).then(() => {
          this.init()
          this.$message({
            type: 'success',
            message: '操作成功!'
          })
        })
      }).catch(() => {
      })
    },
    // 渲染节点
    renderContent(h, data) {
      return h('div', { class: 'tree_box' }, [
        h('div', { class: 'tree_title' }, [
          h('span', {}, data.deptName)
        ]),
        h('div', { class: 'tree_content' }, [
          h('div', { class: 'culture_show' }, [
            h('span', '文化路径展示'),
            h('el-image', {
              attrs: {
                src: data.photographs,
                alt: '',
                class: 'img_box'
              },
              props: {
                'preview-src-list': [data.photographs] // 添加preview-src-list属性
              }
            })
          ]),
          h('div', { class: 'des_box' }, [
            h('span', '部(所)简介'),
            h('el-input', {
              attrs: {
                type: 'textarea',
                disabled: true
              },
              props: {
                'value': [data.deptDescribe]
              }
            })
          ])
        ]),
        h('div', { class: 'tree_btn' }, [
          h('i', {
            class: 'el-icon-edit',
            on: {
              click: () => this.handleEdit(data, 2)
            }}
          ),
          h('span', {
            on: {
              click: () => this.handleEdit(data, 3)
            }
          }, '详情'),
          h('span', {
            on: {
              click: () => this.handleEdit(data, 1)
            }
          }, '新增下级')
        ]),
        h('div', { class: 'del_btn' }, [
          h('i', {
            class: 'el-icon-close',
            style: {
              display: data.parentId === '0' ? 'none' : 'inline-block'
            },
            on: {
              click: () => this.handleDel(data)
            }}
          )
        ])
      ])
    },
    // 默认展开还是收起 -- 第一个参数是data,第二个参数是全部展开或否
    toggleExpand(data, val) {
      if (Array.isArray(data)) {
        data.forEach(item => {
          this.$set(item, 'expand', val)
          if (item.children) {
            this.toggleExpand(item.children, val)
          }
        })
      } else {
        this.$set(data, 'expand', val)
        if (data.children) {
          this.toggleExpand(data.children, val)
        }
      }
    },
    collapse(list) {
      list.forEach(child => {
        if (child.expand) {
          child.expand = false
        }
        child.children && this.collapse(child.children)
      })
    },
    // 收起展开
    onExpand(e, data) {
      if ('expand' in data) {
        data.expand = !data.expand
        if (!data.expand && data.children) {
          this.collapse(data.children)
        }
      } else {
        this.$set(data, 'expand', true)
      }
    },
    // 弹窗保存
    async handleSave() {
      this.$refs.editForm.validate(async(valid) => {
        if (valid) {
          this.saveLoading = true
          // 判断新增或是修改
          let res
          if (this.type === 1) {
            this.editForm.parentId = this.editForm.id
            delete this.editForm.id
            res = await getTreeAdd(this.editForm)
          }
          if (this.type === 2) {
            res = await getTreeEdit(this.editForm)
          }
          this.saveLoading = false
          if (res.data.code === 200) {
            this.$nextTick(() => {
              this.$refs.editForm.resetFields()
            })
            this.$message.success(this.type === 1 ? '新增成功!' : '修改成功!')
            this.addPop = false
            this.init()
          } else {
            this.$message.error(res.data.msg)
          }
        } else {
          return false
        }
      })
    },
    // 弹窗关闭
    closeDialog() {
      this.$nextTick(() => {
        this.$refs.editForm.resetFields()
      })
      this.addPop = false
    },
    // 上传成功
    handleAvatarSuccess(res, file) {
      // console.log(res, file)
      if (res.code === 200) {
        // this.editForm.photographs = URL.createObjectURL(file.raw)
        this.editForm.photographs = res.data.link
      }
      this.$refs.uploadImg.clearFiles()
    },
    // 上传前
    beforeAvatarUpload(file) {
      // const isJPG = file.type === 'image/jpeg'
      // const isLt2M = file.size / 1024 / 1024 < 2
      // if (!isJPG) {
      //   this.$message.error('上传头像图片只能是 JPG 格式!')
      // }
      // if (!isLt2M) {
      //   this.$message.error('上传头像图片大小不能超过 2MB!')
      // }
      // return isJPG && isLt2M
    },
    // 弹窗文化路径展示删除
    delImg() {
      this.editForm.photographs = ''
    },
    // 更换
    changeImg() {
      // 触发上传
      this.$nextTick(() => {
        this.$refs.uploadImg.$children[0].$refs.input.click()
      })
    },
    // 鼠标滚动事件(wheelDelta值上滚为负下滚为正)
    handleMouseWheel(e) {
      // 同时按下shift键
      if (e.wheelDelta > 0 && e.shiftKey === true) {
        this.decreaseScale()
      }
      if (e.wheelDelta < 0 && e.shiftKey === true) {
        this.increaseScale()
      }
    },
    // 放大
    increaseScale() {
      this.scaleRatio += 0.1
    },
    // 缩小
    decreaseScale() {
      if (this.scaleRatio > 0.2) {
        this.scaleRatio -= 0.1
      }
    }
  }
}
</script>
<style lang="less">
@import './tree';
</style>

6、重点代码是渲染节点的方法,如果加按钮权限,则是下面的代码

import { mapGetters } from 'vuex'
computed: {
  ...mapGetters(['permission']),
  scale() {
    return `scale(${this.scaleRatio})`
  },
  // 按钮权限
  permissionList() {
    return {
      addBtn: this.vaildData(this.permission.businessCulture_add, false),
      viewBtn: this.vaildData(this.permission.businessCulture_view, false),
      delBtn: this.vaildData(this.permission.businessCulture_remove, false),
      editBtn: this.vaildData(this.permission.businessCulture_edit, false)
    }
  }
},
methods:{
	// 渲染节点
    renderContent(h, data) {
      const btnElements = []
      const delElements = []
      // 根据权限显隐
      if (this.permissionList.editBtn) {
        btnElements.push(
          h('i', {
            class: 'el-icon-edit',
            on: {
              click: () => this.handleEdit(data, 2)
            }
          })
        )
      }
      if (this.permissionList.viewBtn) {
        btnElements.push(
          h('span', {
            on: {
              click: () => this.handleEdit(data, 3)
            }
          }, '详情')
        )
      }
      if (this.permissionList.addBtn) {
        btnElements.push(
          h('span', {
            on: {
              click: () => this.handleEdit(data, 1)
            }
          }, '新增下级')
        )
      }
      if (this.permissionList.delBtn) {
        delElements.push(
          h('i', {
            class: 'el-icon-close',
            style: {
              display: data.parentId === '0' ? 'none' : 'inline-block'
            },
            on: {
              click: () => this.handleDel(data)
            }}
          )
        )
      }
      return h('div', { class: 'tree_box' }, [
        h('div', { class: 'tree_title' }, [
          h('span', {}, data.deptName)
        ]),
        h('div', { class: 'tree_content' }, [
          h('div', { class: 'culture_show' }, [
            h('span', '文化路径展示'),
            h('el-image', {
              attrs: {
                src: data.photographs,
                alt: '',
                class: 'img_box'
              },
              props: {
                'preview-src-list': [data.photographs] // 添加preview-src-list属性
              }
            })
          ]),
          h('div', { class: 'des_box' }, [
            h('span', '部(所)简介'),
            h('el-input', {
              attrs: {
                type: 'textarea',
                disabled: true
              },
              props: {
                'value': [data.deptDescribe]
              }
            })
          ])
        ]),
        h('div', { class: 'tree_btn' }, btnElements),
        h('div', { class: 'del_btn' }, delElements)
      ])
    }
}

7、less 代码也贴贴

// tree.less
.culture_box {
  // width: calc(100vw - 240px);
  height: calc(100vh - 122px);
  overflow-x: auto;
  overflow-y: auto;
}
.tree_box {
  width: 100%;
  font-size: 14px;
  display: flex;
  flex-direction: column;
  .tree_title {
    width: 100%;
    font-weight: 600;
    margin-bottom: 10px;
    span {
      width: 100%;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
  }
  .tree_content {
    .culture_show {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      margin-bottom: 10px;
      .el-image {
        width: 170px;
        height: 50px;
        margin-top: 4px;
      }
    }
    .des_box {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      margin-bottom: 10px;
      .el-textarea {
        font-size: 14px;
        margin-top: 4px;
        background: #f2f2f2;
      }
    }
  }
  .tree_btn {
    display: flex;
    justify-content: space-around;
    align-items: center;
    .el-icon-edit {
      cursor: pointer;
    }
    span {
      cursor: pointer;
      text-decoration: underline;
    }
  }
  .del_btn {
    position: absolute;
    top: 2px;
    right: 4px;
    font-size: 16px;
    cursor: pointer;
  }
  .avatar {
    width: 100px;
  }
  .upload_plus {
    font-size: 30px;
  }
}
.org-tree-node-label {
  width: 200px;
}

在这里插入图片描述

磕磕碰碰才是人生哈

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1293967.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

案例精选|聚铭网络助力莱阳市人民医院打造合规性网络安全保障体系

莱阳市人民医院是一所集医疗、教学、科研、急救、康复、医养结合于一体的大型二级甲等综合性公立医院&#xff0c;占地总面积约3万平方米&#xff0c;建筑面积约7万平方米&#xff0c;设置科室48个&#xff0c;开放床位500张。医院先后获得山东省首批医养结合典型、山东省卒中防…

案例060:基于微信小程序考试系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

学习设计模式的一个好网址

常用设计模式有哪些&#xff1f; (refactoringguru.cn)https://refactoringguru.cn/design-patterns

【Proteus仿真】【STM32单片机】蓝牙遥控小车

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使LCD1602液晶&#xff0c;L298电机&#xff0c;直流电机&#xff0c;HC05/06蓝牙模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显…

Matlab 点云对称性检测

文章目录 一、简介二、实现代码三、实现效果参考文献一、简介 这是一个很有趣的功能,它的思路其实与ICP算法的思路有些相似: 首先,它会初始化两个旋转角度,即绕x轴旋转与绕y轴旋转,初始的过程是将点对称(镜像)过去,计算与匹配点之间的距离误差,误差最小者为最优初始值…

【Docker二】docker网络模式、网络通信、数据管理

目录 一、docker网络模式&#xff1a; 1、概述 2、docker网络实现原理&#xff1a; 3、docker的网络模式&#xff1a; 3.1、bridge模式&#xff1a; 3.2、host模式&#xff1a; 3.3、container模式&#xff1a; 3.4、none模式&#xff1a; 3.5、自定义网络模式&#xf…

Spring基于注解存储对象

小王学习录 前言基于注解存储对象Controller (控制器存储)Service (服务存储)Repository (仓库存储)Component (组件存储)Configuration (配置存储)Bean(方法注解) 前言 上一篇文章中已经介绍了在Spring中存储Bean和取Bean的方法. 而在 Spring 中想要更简单的存储和读取对象的…

【面试经典150 | 二叉树】从前序与中序遍历序列构造二叉树

文章目录 写在前面Tag题目来源题目解读解题思路方法一&#xff1a;递归 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等内容…

docker基本管理和概念

1、定义&#xff1a;一个开源的应用容器引擎&#xff0c;基于go语言开发&#xff0c;运行在liunx系统中的开源的、轻量级的“虚拟机” docker的容器技术可以在一台主机上轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器 docker的宿主机是liunx系统&#xff0c;集…

Halcon threshold_sub_pix (Operator)

read_image(Image,fabrik) threshold_sub_pix(Image,Border,35) dev_display(Border)Image是输入的原始图像&#xff0c;Threshold是设定的阈值&#xff0c;Width和Height是像素值计算区域的大小&#xff0c;ThresholdedRegion是经过分割后得到的二值化结果。 在对图像进行二值…

Stable Diffusion AI绘画系列【20】:美丽动人的雀羽婚纱风,你心动了吗?

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

springboot基础(80):redis geo的应用

文章目录 前言redis GEO如何从地图上获取经纬度springboot 的相关方法调用准备redis服务器引用的依赖预设位置的keyGEOADD 添加位置GEORADIUS 获取指定经纬度附件的停车场&#xff08;deprecated&#xff09;GEORADIUS 获取指定成员附件的停车场&#xff08;deprecated&#xf…

百面嵌入式专栏(岗位分析)大疆嵌入式工程师【通信/流媒体】

文章目录 一、岗位简介二、解析2.1、网络协议2.2、音视频传输算法2.3、大规模音视频会议或直播系统 三、简历 沉淀、分享、成长&#xff0c;让自己和他人都能有所收获&#xff01;&#x1f604; &#x1f4e2;本篇我们将对大疆嵌入式工程师【通信/流媒体】岗位进行分析 。 一、…

uni-app 微信小程序之好看的ui登录页面(三)

文章目录 1. 页面效果2. 页面样式代码 更多登录ui页面 uni-app 微信小程序之好看的ui登录页面&#xff08;一&#xff09; uni-app 微信小程序之好看的ui登录页面&#xff08;二&#xff09; uni-app 微信小程序之好看的ui登录页面&#xff08;三&#xff09; uni-app 微信小程…

Redis--12--Redis分布式锁的实现

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Redis分布式锁最简单的实现如何避免死锁&#xff1f;锁被别人释放怎么办&#xff1f;锁过期时间不好评估怎么办&#xff1f;--看门狗分布式锁加入看门狗 redissonRe…

Rask AI引领革新,推出多扬声器口型同步技术,打造本地化内容新纪元

“ Rask AI是一个先进的AI驱动视频和音频本地化工具&#xff0c;旨在帮助内容创作者和公司快速、高效地将他们的视频转换成60多种语言。通过不断创新和改进产品功能&#xff0c;Rask AI正塑造着未来媒体产业的发展趋势。 ” 在多语种内容创作的新时代&#xff0c;Rask AI不断突…

C++新经典模板与泛型编程:将trait类模板用作模板参数

将trait类模板用作模板参数 template<typename T> struct SumFixedTraits;template<> struct SumFixedTraits<char> {using sumT int;static sumT initValue() {return 0;} };template<> struct SumFixedTraits<int> {using sumT __int64;sta…

【PTA-C语言】编程练习4 - 数组Ⅰ

如果代码存在问题&#xff0c;麻烦大家指正 ~ ~有帮助麻烦点个赞 ~ ~ 编程练习4 - 数组Ⅰ&#xff08;1~7&#xff09; 7-1 评委打分&#xff08;分数 10&#xff09;7-2 组合数的和&#xff08;分数 10&#xff09;7-3 找不同&#xff08;分数 15&#xff09;7-4 利用二分查找…

在前端开发中,什么是SEO(Search Engine Optimization)?如何优化网站的SEO?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

聚观早报 |华为畅享 70正式开售;梦饷科技双12玩法

【聚观365】12月8日消息 华为畅享 70正式开售 梦饷科技双12玩法 华为Mate X5应对火海挑战 谷歌发布AI模型Gemini 字节跳动开启新一轮回购 华为畅享 70正式开售 精致外观与创新科技兼具的华为畅享 70正式开售&#xff0c;1199元起搭载6000mAh超大电池&#xff0c;带来超强…