什么?封装一个组织架构组件(树状图组件)

主要用于展示公司组织架构各个节点图

传递对应的配置数据即可

基础默认样式,可以自行配置颜色跟展示效果

index.vue

<template>
  <div>
    <div class="drag-container">
      <div class="drag-div">
        <!-- <div class="drag-div" v-candrag> -->
        <div class="tree_container">
          <div>
            <div
              class="tree_title"
              :style="{ color: titleColor, fontSize: titleFontSize }"
            >
              <span>{
  
  { title }}</span>
            </div>
            <template>
              <div class="tree_slot">
                <slot name="head"></slot>
              </div>
            </template>
          </div>
        </div>
        <!-- 顶级节点结构 对象结构 -->
        <div
          v-if="!Array.isArray(treeData) && Object.keys(treeData).length > 0"
        >
          <div
            class="tree_topLevel_lable"
            :style="{ '--dynamic-color': lineColor }"
          >
            <div
              class="tree_topLevel"
              :class="{
                isRound_item: isRound
              }"
              :style="{
                borderColor: borderColor,
                background: topNodeColor
              }"
            >
              <div>
                {
  
  { treeData.name }}
              </div>
              <!-- 展开 -->
              <div
                style="display: flex;width: 100%;justify-content: space-around;"
              >
                <div
                  class="tree_icon"
                  :style="{ color: lineColor }"
                  @click.stop="treeCLick(treeData)"
                  v-show="treeData.children && treeData.children.length > 0"
                >
                  <i
                    class="el-icon-circle-plus-outline"
                    v-show="!treeData.checked"
                  ></i>
                  <i
                    class="el-icon-remove-outline"
                    v-show="treeData.checked"
                  ></i>
                </div>
              </div>
            </div>
          </div>
          <!-- 常规节点 -->
          <!-- 组织 -->
          <div class="tree_organization" v-show="treeData.checked">
            <!-- 对象 -->
            <div v-if="treeData && isObject(treeData) && treeData.children">
              <tree
                :data="treeData.children"
                :cutSubscript="cutSubscript"
                :canItBeExpanded="canItBeExpanded"
                @treeItemClick="treeItemClick"
                :isRound="isRound"
                :parentNodeBgColor="parentNodeBgColor"
                :parentNodeTitleColor="parentNodeTitleColor"
                :childNodeBgColor="childNodeBgColor"
                :childNodeTitleColor="childNodeTitleColor"
                :lineColor="lineColor"
                :borderColor="borderColor"
                :isHorizontal="isHorizontal"
              ></tree>
            </div>
          </div>
        </div>
        <div v-else>
          <!-- 数组 -->
          <div v-if="treeData && Array.isArray(treeData)">
            <tree
              :data="treeData"
              :cutSubscript="cutSubscript"
              @treeItemClick="treeItemClick"
              :canItBeExpanded="canItBeExpanded"
              :isRound="isRound"
              :parentNodeBgColor="parentNodeBgColor"
              :parentNodeTitleColor="parentNodeTitleColor"
              :childNodeBgColor="childNodeBgColor"
              :childNodeTitleColor="childNodeTitleColor"
              :lineColor="lineColor"
              :borderColor="borderColor"
              :isHorizontal="isHorizontal"
            ></tree>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import tree from "./tree.vue";
export default {
  components: {
    tree
  },
  props: {
    // 标题
    title: {
      type: String,
      default: ""
    },
    // 标题颜色
    titleColor: {
      type: String,
      default: "#151517"
    },
    // 标题字体大小
    titleFontSize: {
      type: String,
      default: "26px"
    },
    // 标题字体粗细
    titleFontWeight: {
      type: String,
      default: "bold"
    },
    // 是否父节点横向展示
    isHorizontal: {
      type: Boolean,
      default: false
    },
    // 是否圆角
    isRound: {
      type: Boolean,
      default: false
    },
    // 顶级节点颜色
    topNodeColor: {
      type: String,
      default: "#0094de"
    },
    // 顶级节点标题颜色
    topNodeTitleColor: {
      type: String,
      default: "#fff"
    },
    // 父节点背景颜色
    parentNodeBgColor: {
      type: String,
      default: "#0094de"
    },
    // 父节点标题颜色
    parentNodeTitleColor: {
      type: String,
      default: "#fff"
    },
    // 子节点背景颜色
    childNodeBgColor: {
      type: String,
      default: "#0094de"
    },
    // 子节点标题颜色
    childNodeTitleColor: {
      type: String,
      default: "#fff"
    },
    // 连接线颜色
    lineColor: {
      type: String,
      default: "#151517"
    },
    // 边框颜色
    borderColor: {
      type: String,
      default: "#151517"
    },
    // 是否可以点击+号展开
    canItBeExpanded: {
      type: Boolean,
      default: true
    },
    title: {
      type: String,
      default: ""
    },
    treeData: {
      type: [Array, Object],
      // Array 没有顶级节点
      // Object 有顶级节点
      default: () => {
        return [];
      }
    },
    cutSubscript: {
      type: Number,
      default: 11
    },
    allopen: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {};
  },
  computed: {
    userInfo: {
      get() {
        return this.store.getters.userInfo;
      }
    }
  },
  watch: {},
  created() {},
  mounted() {},
  beforeCreate() {},
  beforeMount() {},
  beforeUpdate() {},
  updated() {},
  beforeDestroy() {},
  destroyed() {},
  activated() {},
  methods: {
    //递归展开
    openAll(data) {
      if (data && data.length > 0) {
        data.forEach(item => {
          this.$set(item, "checked", true);
          if (item.children && item.children.length > 0) {
            this.openAll(item.children);
          }
        });
      }
    },
    openAllObj(data) {
      if (data && data.children && data.children.length > 0) {
        this.$set(data, "checked", true);
        data.children.forEach(item => {
          this.$set(item, "checked", true);
          if (item.children && item.children.length > 0) {
            this.openAll(item.children);
          }
        });
      }
    },
    treeItemClick(item) {
      this.$emit("treeItemClick", item);
    },
    treeCLick(item) {
      this.$nextTick(() => {
        if (item.children && item.children.length > 0) {
          if (!this.canItBeExpanded) return;
          this.$set(item, "checked", !item.checked);
        }
      });
    },
    isObject(value) {
      return (
        value !== null && typeof value === "object" && !(value instanceof Array)
      );
    }
  }
};
</script>
<style scoped lang="scss">
.tree_container {
  padding: 20px;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
}

.tree_title {
  font-size: 26px;
  font-weight: bold;
  font-family: fangsong;
}
.tree_topLevel {
  border: 1px solid black;
  border-radius: 4px;
  min-width: 75px;
  background-color: #fff;
  padding: 12px 16px;
  position: relative;
  cursor: pointer;
  text-align: center;
  background-color: #0094de;
  color: #fff;
  font-size: 16px;
  font-weight: 600;
}
.tree_topLevel_lable {
  display: flex;
  margin-top: 30px;
  justify-content: center;
  position: relative;
}
.tree_topLevel_lable::after {
  position: absolute;
  content: "";
  display: block;
  width: 1px;
  height: 30px;
  bottom: -30px;
  border-left: 1px solid var(--dynamic-color);
}
.tree_organization {
  display: flex;
  justify-content: center;
  margin-top: 10px;
}
.tree_icon {
  padding: 4px;
  z-index: 3;
  position: absolute;
  bottom: -42px;
  background-color: #fff;
  font-size: 20px;
  color: #000;
  // left: 44.3%;
}

.drag-container {
  width: 100%;
  height: 100%;
  position: relative;
  -webkit-user-select: none; /* Safari */
  -moz-user-select: none; /* Firefox */
  -ms-user-select: none; /* IE10+/Edge */
  user-select: none; /* Standard syntax */
}
// .drag-div {
// position: absolute; /* 把拖拽元素设置为绝对定位,非常重要!!! */
// width: 300%;
// height: 300%;
// }

.draggable_str {
  cursor: grab;
}

.isRound_item {
  border-radius: 25px !important;
}
</style>

tree.vue 

<template>
  <div class="org-chart">
    <div
      v-for="(item, index) in data"
      :key="index"
      class="org-item"
      :title="item.name"
    >
      <div
        class="org-item-content"
        :class="{
          'org-first-item': index == 0 && data.length != 1,
          'org-lase-item': index == data.length - 1 && data.length != 1,
          'org-single-item': data.length == 1
        }"
        :style="{ '--dynamic-color': lineColor }"
      >
        <div
          class="org-link"
          :class="{
            link_children:((item.children && item.children.length > 0) || item.isChild) && isHorizontal
          }"
          :style="{ '--dynamic-color': lineColor }"
        >
          <div
            class="org_name"
            :class="{
              'org-is-child':((item.children && item.children.length > 0) || item.isChild) && isHorizontal,
              isRound_item: isRound 
            }"
            :style="{
              borderColor: borderColor,
              '--dynamic-bgcolor': parentNodeBgColor,
              '--dynamic-bgTitlecolor': parentNodeTitleColor,
              '--dynamic-childColor': childNodeBgColor,
              '--dynamic-childTitleColor': childNodeTitleColor
              // 边框阴影
              // boxShadow: `1px 0 0px ${borderColor}`
            }"
          >
            <div class="org_name_flex" @click.stop="orgItemClick(item, data)">
              <div
                v-for="(text, keys) in cutString(item.name)"
                :key="keys"
                :class="{
                  or_childFrom:((item.children && item.children.length > 0) || item.isChild) && isHorizontal
                }"
              >
                <div
                  class="org-title"
                  v-for="(character, textkey) in text"
                  :key="textkey"
                  :class="{
                    fh_transform:
                      text[textkey] == '(' ||
                      text[textkey] == ')' ||
                      text[textkey] == '(' ||
                      text[textkey] == ')',
                    sz_transform: isNumberString(text[textkey]),
                    jg_transform: item.children && item.children.length > 0
                  }"
                >
                  {
  
  { text[textkey] }}
                </div>
              </div>
            </div>
            <div
              class="org-icon"
              :style="{ color: lineColor }"
              :class="{
                icon_childFrom: item.children && item.children.length > 0 && isHorizontal
              }"
              @click.stop="orgCLick(item)"
              v-show="item.children && item.children.length > 0"
            >
              <i class="el-icon-circle-plus-outline" v-show="!item.checked"></i>
              <i class="el-icon-remove-outline" v-show="item.checked"></i>
            </div>
          </div>
        </div>
      </div>
      <div
        v-if="item.children && item.children.length > 0 && item.checked"
        class="org-children"
      >
        <org-chart
          :data="item.children"
          @orgItemClick="orgItemClick"
          :canItBeExpanded="canItBeExpanded"
          :isRound="isRound"
          :parentNodeBgColor="parentNodeBgColor"
          :parentNodeTitleColor="parentNodeTitleColor"
          :childNodeBgColor="childNodeBgColor"
          :childNodeTitleColor="childNodeTitleColor"
          :lineColor="lineColor"
          :borderColor="borderColor"
          :cutSubscript="cutSubscript"
          :isHorizontal="isHorizontal"
        ></org-chart>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "OrgChart",
  components: {},
  props: {
    // 是否父节点横向展示
    isHorizontal: {
      type: Boolean,
      default: false
    },
    // 是否可以点击+号展开
    canItBeExpanded: {
      type: Boolean,
      default: true
    },
    data: {
      type: Array,
      required: true
    },
    // 是否圆角
    isRound: {
      type: Boolean,
      default: false
    },
    // 父节点背景颜色
    parentNodeBgColor: {
      type: String,
      default: "#0094de"
    },
    // 父节点标题颜色
    parentNodeTitleColor: {
      type: String,
      default: "#fff"
    },
    // 子节点背景颜色
    childNodeBgColor: {
      type: String,
      default: "#0094de"
    },
    // 子节点标题颜色
    childNodeTitleColor: {
      type: String,
      default: "#fff"
    },
    // 连接线颜色
    lineColor: {
      type: String,
      default: "#151517"
    },
    // 边框颜色
    borderColor: {
      type: String,
      default: "#151517"
    },
    // 字符切割
    cutSubscript: {
      type: Number,
      default: 11
    }
  },
  data() {
    return {};
  },
  computed: {
    userInfo: {
      get() {
        return this.store.getters.userInfo;
      }
    }
  },
  watch: {},
  created() {},
  mounted() {},
  beforeCreate() {},
  beforeMount() {},
  beforeUpdate() {},
  updated() {},
  beforeDestroy() {},
  destroyed() {},
  activated() {},
  methods: {
    // 字符切割
    cutString(str) {
      if (!str) return [];
      let num = this.cutSubscript;
      if (str.length <= num) return [str];
      if (str.length % 2 == 0) {
        num = str.length / 2;
      } else {
        num = Math.floor(str.length / 2) + 1;
      }
      let result = [];
      for (let i = 0; i < str.length; i += num) {
        result.push(str.slice(i, i + num));
      }
      return result;
    },
    // 判断是否数字
    isNumberString(str) {
      const num = Number(str);
      return !isNaN(num) && str == num;
    },
    orgCLick(item) {
      this.$nextTick(() => {
        if (item.children && item.children.length > 0) {
          if (!this.canItBeExpanded) return;
          this.$set(item, "checked", !item.checked);
          console.log(123, "???");
        }
      });
    },
    orgItemClick(item, data) {
      this.$emit("treeItemClick", item, data);
    }
  }
};
</script>
<style scoped lang="scss">
.org-chart {
  display: flex;
  justify-content: center;
  margin-top: 25px;
  padding: 20px 0;
}
.org-item {
  // padding: 0 5px;
  position: relative;
}

.org_name {
  cursor: pointer;
  position: relative;
  // text-align: center;
  padding: 8px;
  border: 1px solid black;
  border-radius: 4px;
  // writing-mode: vertical-lr;
  width: 53px;
  height: 240px;
  background-color: var(--dynamic-childColor);
  // display: flex;
  // align-items: center;
}
.org_name_flex {
  width: 100%;
  height: 100%;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
  letter-spacing: 1px;
}
.org-title {
  text-align: center;
  margin: auto;
  color: var(--dynamic-childTitleColor);
  font-size: 16px;
  font-weight: 600;
}
.org-icon {
  z-index: 1;
  position: absolute;
  bottom: -32px;
  background-color: #fff;
  font-size: 20px;
  left: 12px;
  padding: 4px;
}
.icon_childFrom {
  left: 43.5% !important;
}
.org-item-content {
  padding: 0 5px;
  display: flex;
  justify-content: center;
}
.org-link {
  z-index: 1;
  position: relative;
}
.org-chart {
  position: relative;
}

.org-item-content::before {
  position: absolute;
  content: "";
  display: block;
  width: 100%;
  height: 0px;
  border-top: 1px solid var(--dynamic-color);
  top: -20px;
  left: 0;
}
.org-link::before {
  position: absolute;
  content: "";
  display: block;
  width: 1px;
  height: 8%;
  border-left: 1px solid var(--dynamic-color);
  top: -8%;
  left: 50%;
}
.link_children::before {
  top: -46% !important;
  height: 46% !important;
}
.org-is-child {
  width: 220px !important;
  height: 43px !important;
  background-color: var(--dynamic-bgcolor) !important;
  .org-title {
    color: var(--dynamic-bgTitlecolor) !important;
  }
}

.org-first-item::before {
  width: 50% !important;
  left: 50% !important;
}
.org-lase-item::before {
  width: 50% !important;
}
.org-single-item::before {
  width: 0% !important;
  left: 50% !important;
  top: -10% !important;
  // border-top: 10px solid #151517 !important;
}

.fh_transform {
  transform: rotate(90deg);
}
.sz_transform {
  transform: rotate(90deg);
}
.jg_transform {
  transform: rotate(0deg) !important;
}
.or_childFrom {
  display: flex;
  align-items: center;
}

.isRound_item {
  border-radius: 25px !important;
}
</style>

 使用方法

html部分

事件按需求自行绑定

    <treeDiagram :treeData="orgData"/>

js部分

    import treeDiagram from "@/components/treeDiagram/index";
    export default {
        components: {
            treeDiagram
        },
        data(){
            return{
                orgData: [
                    {
                    name: "综合办公司",
                    children: [
                        { name: "党委巡察办公室" },
                        {
                        name: "党委巡察办公室2",
                        children: [
                            { name: "科技和信息化部1" },
                            { name: "科技和信息化部2" },
                            {
                            name: "科技和信息化部3",
                            children: [
                                {
                                name: "科技和信息化部1",
                                children: [{ name: "科技和信息化部3" }]
                                },
                                { name: "科技和信息化部2" },
                                { name: "科技和信息化部3" }
                            ]
                            }
                        ]
                        },
                        { name: "党委巡察办公室2" },
                        { name: "党委巡察办公室2" }
                    ]
                    },
                    {
                    name: "党建工作部1",
                    children: [
                        { name: "科技和信息化部1" },
                        { name: "科技和信息化部2" },
                        { name: "科技和信息化部3" }
                    ]
                    },
                    {
                    name: "党建工作部2",
                    children: [{ name: "运营事业部1" }, { name: "运营事业部2" }]
                    }
                ],

            }
        },
        methods:{

        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值