이진탐색 트리구조

developer.do·2023년 5월 16일
0

1


<script setup lang="ts">
import MenuEditForm from "../components/menu-management/MenuEditForm.vue";
import MenuAddForm from "../components/menu-management/MenuAddForm.vue";
import axios from "axios";
import { IData } from "~/admin/types";

export interface IData {
  menuId: string;
  menuName: string;
  parentId: string | null;
  link: string;
  description: string;
  order: number;
  use: "Y" | "N";
  createDate: string;
  updateDate: string;
}

interface Tree {
  label: string;
  children?: Tree[];
}

const menusData = ref<IData[]>([]);

// 메뉴 추가 버튼
const openMenuAddPopupSignal = ref(false);
const openMenuAddPopup = () => {
  openMenuAddPopupSignal.value = true;
};

// 본문

// const handleNodeClick = (menuData: Tree) => {
//   console.log(menuData);
// };

const data: Tree[] = [
  {
    label: "게시판",
    children: [
      {
        label: "일반게시판",
      },
      {
        label: "특수게시판",
      },
    ],
  },
  {
    label: "관리자",
    children: [
      {
        label: "메뉴 관리",
        children: [
          {
            label: "Level three 2-1-1",
          },
        ],
      },
      {
        label: "공통코드 관리",
        children: [
          {
            label: "Level three 2-2-1",
          },
        ],
      },
    ],
  },
];

const defaultProps = {
  children: "children",
  label: "label",
};

// get 불러오기
const inquiryMenu = async () => {
  try {
    const res = await axios.get("/api/v1/menus");
    menusData.value = res.data.body.data;
    // console.log(menusData.value);
  } catch (error) {
    console.error("MenuList.vue error / inquiryMenus error" + error);
  }
};

// 노드 추가하기

class TreeNode {
  data: IData;
  label: string;
  parent: TreeNode | null;
  children: TreeNode[];

  constructor(data: IData) {
    this.data = data;
    this.parent = null;
    this.children = [];
    this.label = data.menuName;
  }

  insertChild(node: TreeNode) {
    this.children.push(node);
  }

  static(parent: TreeNode, child: TreeNode): boolean {
    return parent.data.menuId === child.data.parentId;
  }

  addTreeToParent(target: TreeNode): TreeNode | null {
    if (TreeNode.isParentNode(this, target)) {
      this.insertChild(target);
      return this;
    }

    for (let i = 0; i < this.children.length; i++) {
      const existParent: any = this.children[i].addTreeToParent(target);
      if (existParent) {
        return existParent;
      }
    }
    return null;
  }
}

let newTreeArrData = reactive<TreeNode[]>([]);
watch(
  () => menusData.value,
  (newVal) => {
    // console.log("newVal menus data: " + newVal);
    apiToTree();
    // console.log("newTreeArrData: ", newTreeArrData);
    treeToData();
    // console.log("treeToData: ", result.value);
  },
  { deep: true }
);

function apiToTree() {
  newTreeArrData = [];
  for (let i = 0; i < menusData.value.length; i++) {
    const data = menusData.value[i];
    const currNode = new TreeNode(data);

    if (!data.parentId) {
      newTreeArrData.push(currNode);
      console.info("currNode is top", currNode.data.menuId);
      continue;
    }

    let added = false;
    for (let i = 0; i < newTreeArrData.length; i++) {
      const exist = newTreeArrData[i];
      const parent = exist.addTreeToParent(currNode);
      if (parent) break;
    }
    if (!added) {
      // console.log("not added any nodes", currNode.data);
    }
  }
  // console.log(newTreeArrData);
}

interface TreeData {
  label: string;
  children: TreeData[];
}
const toTreeData = (tree: TreeNode): TreeData => {
  return {
    label: tree.label,
    children: tree.children.map((x) => toTreeData(x)).flat(), 
    //재귀적으로 자식들을 만들어준다. chiildren이 없으면 빈 []가 들어간다.
    // 라벨링을 달아준다고 보면 됨
  };
};
let result = ref<TreeData[]>([]);
function treeToData() {
  result.value = newTreeArrData.map(toTreeData); 
  // result 트리들이 담겨져 있는 곳
}

const handleNodeClick = (data: IData) => {
  // console.log(data);
};

onBeforeMount(inquiryMenu);
</script>

<template>
  <h1 class="text-left text-24px">메뉴 관리</h1>
  <div class="flex justify-center">
    <div class="flex flex-col w-40%">
      <el-button
        class="w-20 self-end mr-5 my-6"
        type="primary"
        @click="openMenuAddPopup()"
        >메뉴 추가</el-button
      >
      <el-tree
        class="p-7"
        :data="result"
        :props="defaultProps"
        @node-click="handleNodeClick"
      />
    </div>
    <div class="w-60% p-3 b-l-1 b-gray">
      <menu-add-form
        v-model:open-menu-add-popup-signal="openMenuAddPopupSignal"
        @inquiry-menu="inquiryMenu"
      />
      <menu-edit-form />
    </div>
  </div>
</template>

<style lang="scss" scoped>
.el-button {
  margin-left: 0px;
}
</style>

2

<template>
  <div class="w-30% h-70vh flex flex-col">
    <menu-add-popup
      v-model:menu-add-popup-is-open="menuAddPopupIsOpen"
    ></menu-add-popup>
    <el-button
      class="mt-5 w-25 self-end mr-1"
      type="primary"
      @click="menuAddPopupOpen"
      plain
    >
      메뉴 추가
    </el-button>
    <div class="b-1 mt-3 ml-6 h-40vh rounded-xl">
      <el-tree
        class="p-7"
        :data="result"
        :props="defaultProps"
        @node-click="handleNodeClick"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import axios from "axios";
import MenuAddPopup from "./MenuAddPopup.vue";
import { IData } from "~/admin/types";

const menuAddPopupIsOpen = ref<boolean>(false);
const menusData = ref<IData[]>([]);

const inquiryMenus = async () => {
  try {
    const res = await axios.get("/api/v1/menus");
    menusData.value = res.data.body.data;
    console.log(menusData.value);
  } catch (error) {
    console.error("MenuList.vue error / inquiryMenus error" + error);
  }
};

class TreeNode {
  data: IData;
  label: string;
  parent: TreeNode | null;
  children: TreeNode[];

  constructor(data: IData) {
    this.data = data;
    this.parent = null;
    this.children = [];
    this.label = data.menuName;
  }

  insertChild(node: TreeNode) {  // 만약 내 parentId가 부모 노드의 id와 같다면(meuuId) 나는 그 부모 노트의 children으로 들어감
    // 만약 내 parentId가 null 이라면 result 다음 index로 추가가 된다. 
    // 만약 내 parentId가 menu_01이라면 
    // 만약 내 parentId가 
    this.children.push(node);
  }

  static isParentNode(parent: TreeNode, child: TreeNode): boolean {
    return parent.data.menuId === child.data.parentId;
  }

  addTreeToParent(target: TreeNode): TreeNode | null {  
    // 내가 부모라면 자식목록에 넣고 나를 리턴한다. 내가 부모가아니면 
    //내 자식들한테 니가 부모해라 물어보겠지, 그래서 계속 자식한테 물어봄
    if (TreeNode.isParentNode(this, target)) {
      this.insertChild(target);
      return this;
    }

    for (let i = 0; i < this.children.length; i++) {  
      // 자기 자식도 없고 더이상 물어볼 애도 없다. 그러면 null을 반환한다.
      const existParent: any = this.children[i].addTreeToParent(target); 
      // 부모 노드에서 일치하는 id가 없다면 그러면 그 밑의 children의 id와 일치하고 
      //맞으면 거기에 밑으로 들어간다.
      if (existParent) {
        return existParent;
      }
    }
    return null;
  }
}

let newTreeArrData = reactive<TreeNode[]>([]);
watch(
  () => menusData.value,
  (newVal) => {
    console.log("newVal menus data: " + newVal);
    apiToTree();
    console.log("newTreeArrData: ", newTreeArrData);
    treeToData();
    console.log("treeToData: ", result.value);
  },
  { deep: true }
);

function apiToTree() { // 1. 여기가 진입점임
  newTreeArrData = [];
  for (let i = 0; i < menusData.value.length; i++) {  // 부모 id가 없으면 최상위 노드니깐 바로 잡아버린다.
    const data = menusData.value[i];
    const currNode = new TreeNode(data);

    if (!data.parentId) {
      newTreeArrData.push(currNode);
      console.info("currNode is top", currNode.data.menuId);
      continue;
    }

    let added = false;  // 모든 루트 노드를 돌았으면 에러가 나야함
    for (let i = 0; i < newTreeArrData.length; i++) {
      const exist = newTreeArrData[i];
      const parent = exist.addTreeToParent(currNode); 
      // 루트 노드들에서 재귀함수를 실행시킨다.(각각의 트리 함수들을 실행시킨다.)
      if (parent) break; // 모든 루트 노드들을 돌면서 자식들이 있는지 확인하고 없으면 
      //애러를 준다. 
    }
    if (!added) {
      console.log("not added any nodes", currNode.data);
    }
  }
  console.log(newTreeArrData);
}

interface TreeData {
  label: string;
  children: TreeData[];
}
const toTreeData = (tree: TreeNode): TreeData => {
  return {
    label: tree.label,
    children: tree.children.map((x) => toTreeData(x)).flat(),
  };
};
let result = ref<TreeData[]>([]); // 
function treeToData() {
  result.value = newTreeArrData.map(toTreeData);
}

const handleNodeClick = (data: IData) => {
  console.log(data);
};

const defaultProps = {
  children: "children",
  label: "label",
};

const menuAddPopupOpen = () => {
  menuAddPopupIsOpen.value = true;
};

onMounted(() => {
  inquiryMenus();
});
</script>

0개의 댓글