基于vue+View UI的组织机构选择

发布于:2025-07-02 ⋅ 阅读:(16) ⋅ 点赞:(0)

1、效果
在这里插入图片描述
在这里插入图片描述
1、代码

<template>
  <Button type="primary" @click="modal = true">点击选择</Button>
  <div v-if="selectedArr.length > 0">
    <p>已选择项:</p>
    <div v-for="(item, index) in selectedArr" :key="index">{{ item.title }}</div>
  </div>
  <Modal width="600" v-model="modal" title="请选择" @on-ok="ok" @on-cancel="cancel">
    <div class="tr-modal">
      <div class="org-tree">
        <div class="title">
          <span>组织机构</span>
          <Input v-model="keyOrg" @on-change="onInput" placeholder="请输入"></Input>
        </div>
        <Tree
          ref="tree"
          class="tree-main"
          @on-check-change="onCheckChange"
          :data="data"
          show-checkbox
          check-directly
          multiple
        ></Tree>
      </div>
      <div class="to-right">
        <Icon type="ios-arrow-forward" size="30" color="#1890ff" />
      </div>
      <div class="select-con">
        <div class="title">
          <span>已选择项</span>
        </div>
        <div class="to-right">
          <ul class="select-ul">
            <li
              v-for="(item, index) in selectedArr"
              :key="index"
              @mouseenter="item.ishover = true"
              @mouseleave="item.ishover = false"
              @click="onDelete(index, item)"
            >
              <span>{{ item.title }}</span>
              <Icon v-if="item.ishover" type="md-close" size="20" color="#1890ff" />
            </li>
          </ul>
        </div>
      </div>
    </div>
  </Modal>
</template>
<script>
export default {
  data() {
    return {
      modal: false,
      selectedArr: [],
      data: [
        {
          industryName: '农业、林业',
          node: 1,
          children: [
            {
              industryName: '农产品基地项目(含药材基地)',
              children: [],
              id: '01--001',
              title: '01--001_农产品基地项目(含药材基地)',
              industryState: '1',
              parentId: '01',
              industryCode: '01--001',
            },
            {
              industryName: '经济林基地项目',
              children: [],
              id: '01--002',
              title: '01--002_经济林基地项目',
              industryState: '1',
              parentId: '01',
              industryCode: '01--002',
            },
          ],
          id: '01',
          title: '01_农业、林业',
          industryState: '1',
          parentId: null,
          industryCode: '01',
        },
        {
          industryName: '畜牧业',
          node: 1,
          children: [
            {
              industryName: '牲畜饲养;家禽饲养;其他畜牧业',
              children: [],
              id: '02--003',
              title: '02--003_牲畜饲养;家禽饲养;其他畜牧业',
              industryState: '1',
              parentId: '02',
              industryCode: '02--003',
            },
          ],
          id: '02',
          title: '02_畜牧业',
          industryState: '1',
          parentId: null,
          industryCode: '02',
        },
        {
          industryName: '渔业',
          node: 1,
          children: [
            {
              industryName: '海水养殖',
              children: [],
              id: '03--004',
              title: '03--004_海水养殖',
              industryState: '1',
              parentId: '03',
              industryCode: '03--004',
            },
            {
              industryName: '内陆养殖',
              children: [],
              id: '03--005',
              title: '03--005_内陆养殖',
              industryState: '1',
              parentId: '03',
              industryCode: '03--005',
            },
          ],
          id: '03',
          title: '03_渔业',
          industryState: '1',
          parentId: null,
          industryCode: '03',
        },
      ],
      keyOrg: '',
      dataCp: [],
    }
  },
  methods: {
    ok() {
      this.$Message.info('Clicked ok')
    },
    cancel() {
      this.$Message.info('Clicked cancel')
    },
    onInput() {
      if (this.keyOrg) {
        let flattenTree = this.flattenTree(this.dataCp)
        let filterChildren = flattenTree.filter(
          (el) => el.parentId && el.title.includes(this.keyOrg),
        )
        this.data = filterChildren
      } else {
        this.data = this.dataCp
      }
    },
    onDelete(index, item) {
      this.selectedArr.splice(index, 1)
      // 先扁平化
      let flattenTree = this.flattenTree(this.data)
      // 先根据parentId找到当前项的父节点
      let parent = flattenTree.find((el) => el.id == item.parentId)
      if (parent) {
        parent.checked = false
      }
      //   再取消勾选
      let checkItem = flattenTree.find((el) => el.id == item.id)
      checkItem.checked = false
      //   再还原成树形结构
      this.data = this.unflattenTree(flattenTree)
    },

    onCheckChange(prev, curr) {
      // 选中
      if (curr.checked) {
        // 选中的节点没有子节点(选中一个)
        if (curr.children.length === 0) {
          curr.ishover = false
          this.selectedArr.push(curr)
        } else {
          // 选中的节点有子节点(选中一个,然后勾选其子节点)
          curr.children.forEach((el) => {
            el.ishover = false
          })
          this.selectedArr.push(...curr.children)
        }
      } else {
        // 选中的节点没有子节点(选中一个)
        if (curr.children.length === 0) {
          // 取消
          let index = this.selectedArr.findIndex((item) => item.id == curr.id)
          // 删除已选项
          this.selectedArr.splice(index, 1)
        } else {
          curr.children.forEach((el) => {
            // 取消
            let index = this.selectedArr.findIndex((item) => item.id == el.id)
            // 删除已选项
            this.selectedArr.splice(index, 1)
          })
        }
      }
    },
    // 扁平化树形数据
    flattenTree(treeData) {
      const result = []
      function flatten(node) {
        // 创建新节点对象(浅拷贝,避免修改原对象)
        const newNode = { ...node }
        // 如果有子节点,先递归处理子节点
        if (Array.isArray(newNode.children) && newNode.children.length > 0) {
          // 临时保存子节点引用
          const children = newNode.children
          // 移除children属性(根据需求可选)
          delete newNode.children
          // 将当前节点加入结果
          result.push(newNode)
          // 递归处理子节点
          children.forEach((child) => flatten(child))
        } else {
          // 无子节点直接加入结果
          result.push(newNode)
        }
      }
      // 遍历所有根节点
      treeData.forEach((root) => flatten(root))
      return result
    },
    // 将扁平数据还原为树形结构
    unflattenTree(flatData) {
      // 创建ID映射字典和结果集
      const nodeMap = {}
      const roots = []
      // 第一遍遍历:创建所有节点的映射
      flatData.forEach((node) => {
        nodeMap[node.id] = { ...node, children: [] }
      })

      // 第二遍遍历:构建树形结构
      flatData.forEach((node) => {
        const currentNode = nodeMap[node.id]

        if (node.parentId) {
          // 找到父节点并添加到其children
          const parent = nodeMap[node.parentId]
          if (parent) {
            parent.children.push(currentNode)
          }
        } else {
          // 根节点
          roots.push(currentNode)
        }
      })

      return roots
    },
  },
  mounted() {
    this.dataCp = JSON.parse(JSON.stringify(this.data))
  },
}
</script>

<style lang="scss" scoped>
.tr-modal {
  display: flex;
  height: 400px;
  .org-tree {
    width: 260px;
    height: 100%;
    border: 1px solid #d9d9d9;
  }
  .to-right {
    display: flex;
    align-items: center;
    margin: 0 5px;
    cursor: pointer;
  }
  .select-con {
    width: 260px;
    border: 1px solid #d9d9d9;
  }
  .title {
    height: 32px;
    display: flex;
    align-items: center;
    border-bottom: 1px solid #d9d9d9;
    span {
      display: flex;
      font-size: 14px;
      color: #1890ff;
      justify-content: center;
      align-items: center;
      width: 70px;
      font-weight: 700;
    }
    :deep(.ivu-input-wrapper) {
      flex: 1;
      width: auto;
    }
  }
  .tree-main {
    height: calc(100% - 32px);
    overflow-y: auto;
  }
  .select-ul {
    width: 100%;
    li {
      padding: 0 5px;
      height: 24px;
      display: flex;
      align-items: center;
      font-size: 14px;
      justify-content: space-between;
      margin: 4px 0;
      background-color: #eee;
      span {
        width: 90%;
        overflow: hidden;
        white-space: nowrap;
      }
    }
  }
}
</style>