기본 콘텐츠로 건너뛰기

[2023-04-19] vscode로 typescript 개발 환경(Menu 테이블을 Tree형태로 구성)

안녕하세요. 클스 입니다.

typescript 가 이젠 많이 중요해 졌습니다. vuejs, react 등에서 빠질 수 가 없네요~


개요

- DB의 메뉴 테이블에서 데이터를 가져와서 웹에 tree 구조로 출력해보려고 하니 웹을 올리고 소스 수정하면 빌드하고 확인하는 시간들이 많이 들어요~

- 그래서 typescript만 개발하면 좋을 듯하여 셋팅 해봤습니다.


메뉴 테이블 쿼리

WITH RECURSIVE tree_view(menu_id, menu_nm, up_menu_id, depth, path) AS (
    SELECT 
        menu_id, 
        menu_nm, 
        up_menu_id, 
        1 as depth, 
        CAST(menu_id AS VARCHAR(100)) as path
    FROM tb_menu
    WHERE up_menu_id is NULL -- 최상위 노드를 선택합니다
UNION ALL    
    SELECT 
        child.menu_id, 
        child.menu_nm, 
        child.up_menu_id, 
        parent.depth + 1 as depth, 
        CONCAT(parent.path, '_', child.menu_id) as path
    FROM tb_menu child
    JOIN tree_view parent 
      ON child.up_menu_id = parent.menu_id
)
SELECT 
    menu_id, menu_nm, up_menu_id, depth, path
    , CONCAT( RIGHT('----------',depth*3), menu_nm, ' ') AS parent_child_tree
FROM tree_view
ORDER BY path;


tree_view 조회 결과

menu_idmenu_nmup_menu_iddepthpathparent_child_tree
M00192메뉴/권한 관리M000013ROOT_M00001_M00192---------메뉴/권한 관리
M00190권한 관리M001924ROOT_M00001_M00192_M00190----------권한 관리
M00191메뉴 관리M001924ROOT_M00001_M00192_M00191----------메뉴 관리
M00193회사/조직 관리M000013ROOT_M00001_M00193---------회사/조직 관리
M00187회사 관리M001934ROOT_M00001_M00193_M00187----------회사 관리
M00188조직 관리M001934ROOT_M00001_M00193_M00188----------조직 관리
M00189사용자 관리M001934ROOT_M00001_M00193_M00189----------사용자 관리
M00194시스템관리M000013ROOT_M00001_M00194---------시스템관리


데이터를 tree 형태로 만드는 tree_test.ts 

- 조금 범용적으로 만들기 위해 menu_id -> id로 menu_nm -> name으로 , up_menu_id -> up_id 로 설계 했습니다.


필수 패키지 설치 및 vscode extension 설치

* 패키지 설치
$ sudo npm install -g typescript
$ sudo npm install -g ts-node    <누락되서 나중에 설치했어요>
$ mkdir tsTest
$ cd tsTest
$ npm init -y   #초기 프로젝트 설정
$ code .   # vscode 실행

* vscode extension 설치
$ Code Runner 설치하고 sample.ts 파이를 만들고, CTRL + ALT + N 하면 실행된다.
  한가지 아쉬운점은 jupyter notebook 처럼 변수 등등 디버깅을 모르겠다. ㅠ.ㅠ
   

      해당 디렉토리에 package.json 이 생긴다. $ npm install 을하면 package.json에 정의된 모듈들이 설치됩니다. 지금은 아무것도 없어서 package-lock.json 만 생기네요~


       * Code Runner 확장 프그램 설치

       



vscode에서 tree_test.ts 파일을 만든다

소스에는 원본 데이터 -> Tree 자료 구조 -> 출력 하는 함수가 포함되어 있습니다.

interface item {
  id: string;
  name: string;
  up_id: string | null;
}

interface TreeNode {
  id: string;
  up_id: string | null;
  label: string;
  avatar?: string;
  icon?: string;
  disabled?: boolean;
  children?: TreeNode[];
}

function buildTree(items: item[]): TreeNode[] {
  const menuMap = new Map<string, item>();
  const treeMap = new Map<string, TreeNode>();

  // 메뉴 항목 맵 만들기
  for (const item of items) {
    menuMap.set(item.id, item);
  }

  // 트리 노드 맵 만들기
  for (const [id, item] of menuMap) {
    const treeNode: TreeNode = {
      id: item.id,
      up_id: item.up_id,
      label: item.name,
    };

    treeMap.set(id, treeNode);

    // 부모 노드가 있는 경우, 부모 노드에 자식 노드 추가하기
    const parentId = item.up_id;

    if (parentId !== null) {
      const parent = treeMap.get(parentId);

      if (parent !== undefined) {
        if (parent.children === undefined) {
          parent.children = [];
        }

        parent.children.push(treeNode);
        console.log(treeNode);
      }
    }
  }

  // 최상위 노드들 찾기
  const rootNodes: TreeNode[] = [];

  for (const [id, item] of menuMap) {
    if (item.up_id === null) {
      const rootNode = treeMap.get(id);

      if (rootNode !== undefined) {
        rootNodes.push(rootNode);
        console.log(rootNode);
      }
    }
  }

  return rootNodes;
}

function printTree(treeNodes: TreeNode[], depth = 0) {
  for (const treeNode of treeNodes) {
    const indent = " ".repeat(depth * 2);
    console.log(`${indent}${treeNode.label} (${treeNode.id})`);

    if (treeNode.children !== undefined) {
      printTree(treeNode.children, depth + 1);
    }
  }
}

// 임의의 데이터 배열
const menuData = [
  { id: "ROOT", up_id: null, name: "ROOT" },
  { id: "M1", up_id: "ROOT", name: "메뉴 1" },
  { id: "M2", up_id: "M1", name: "서브메뉴 1-1" },
  { id: "M3", up_id: "M1", name: "서브메뉴 1-2" },
  { id: "M4", up_id: "ROOT", name: "메뉴 2" },
  { id: "M5", up_id: "M4", name: "서브메뉴 2-1" },
  { id: "M6", up_id: "M5", name: "서브메뉴 2-1-1" },
  { id: "M7", up_id: "M5", name: "서브메뉴 2-1-2" },
  { id: "M8", up_id: "M4", name: "서브메뉴 2-2" },
];

const tree = buildTree(menuData);
console.log(tree);

console.log("---BEGIN----------------------------------------------");
printTree(tree);
console.log("----END---------------------------------------------");

const menuData2 = [
  { id: "ROOT", name: "ROOT", up_id: null },
  { id: "M00001", name: "관리자", up_id: "ROOT" },
  { id: "M00192", name: "메뉴/권한 관리", up_id: "M00001" },
  { id: "M00190", name: "권한 관리", up_id: "M00192" },
  { id: "M00191", name: "메뉴 관리", up_id: "M00192" },
  { id: "M00193", name: "회사/조직 관리", up_id: "M00001" },
  { id: "M00187", name: "회사 관리", up_id: "M00193" },
  { id: "M00188", name: "조직 관리", up_id: "M00193" },
  { id: "M00189", name: "사용자 관리", up_id: "M00193" },
  { id: "M00194", name: "시스템관리", up_id: "M00001" },
  { id: "M00182", name: "공통코드 관리", up_id: "M00194" },
  { id: "M00183", name: "용어사전 관리", up_id: "M00194" },
  { id: "M00184", name: "로그 이력", up_id: "M00194" },
  { id: "M00235", name: "휴일 관리", up_id: "M00194" },
  { id: "M00236", name: "게시판 관리", up_id: "M00194" },
  { id: "M00237", name: "알림 관리", up_id: "M00194" },
  { id: "M00243", name: "사용자별 사용 현황", up_id: "M00194" },
  { id: "M00195", name: "화면 마법사", up_id: "M00001" },
  { id: "M00178", name: "조회 조건 관리", up_id: "M00195" },
  { id: "M00179", name: "메타정보 정의", up_id: "M00195" },
  { id: "M00180", name: "포틀릿 관리", up_id: "M00195" },
  { id: "M00181", name: "화면 마법사", up_id: "M00195" },
];

const tree2 = buildTree(menuData2);
// console.log(tree2);

console.log("---BEGIN----------------------------------------------");
printTree(tree2);
console.log("----END---------------------------------------------");


실행하기



     마우스 우 클릭을 하면 Run Code 가 나온다. 핫키는 Ctrol+Option +N 입니다.

실행 결과


Done] exited with code=127 in 0.01 seconds

[Running] ts-node "/Users/keunsookim/quasar/typescript/tree_test.ts"
{ id: 'M1', up_id: 'ROOT', label: '메뉴 1' }
{ id: 'M2', up_id: 'M1', label: '서브메뉴 1-1' }
{ id: 'M3', up_id: 'M1', label: '서브메뉴 1-2' }
{ id: 'M4', up_id: 'ROOT', label: '메뉴 2' }
{ id: 'M5', up_id: 'M4', label: '서브메뉴 2-1' }
{ id: 'M6', up_id: 'M5', label: '서브메뉴 2-1-1' }
{ id: 'M7', up_id: 'M5', label: '서브메뉴 2-1-2' }
{ id: 'M8', up_id: 'M4', label: '서브메뉴 2-2' }
{
id: 'ROOT',
up_id: null,
label: 'ROOT',
children: [
{ id: 'M1', up_id: 'ROOT', label: '메뉴 1', children: [Array] },
{ id: 'M4', up_id: 'ROOT', label: '메뉴 2', children: [Array] }
]
}
[
{
id: 'ROOT',
up_id: null,
label: 'ROOT',
children: [ [Object], [Object] ]
}
]
---BEGIN----------------------------------------------
ROOT (ROOT)
메뉴 1 (M1)
서브메뉴 1-1 (M2)
서브메뉴 1-2 (M3)
메뉴 2 (M4)
서브메뉴 2-1 (M5)
서브메뉴 2-1-1 (M6)
서브메뉴 2-1-2 (M7)
서브메뉴 2-2 (M8)
----END---------------------------------------------
{ id: 'M00001', up_id: 'ROOT', label: '관리자' }
{ id: 'M00192', up_id: 'M00001', label: '메뉴/권한 관리' }
{ id: 'M00190', up_id: 'M00192', label: '권한 관리' }
{ id: 'M00191', up_id: 'M00192', label: '메뉴 관리' }
{ id: 'M00193', up_id: 'M00001', label: '회사/조직 관리' }
{ id: 'M00187', up_id: 'M00193', label: '회사 관리' }
{ id: 'M00188', up_id: 'M00193', label: '조직 관리' }
{ id: 'M00189', up_id: 'M00193', label: '사용자 관리' }
{ id: 'M00194', up_id: 'M00001', label: '시스템관리' }
{ id: 'M00182', up_id: 'M00194', label: '공통코드 관리' }
{ id: 'M00183', up_id: 'M00194', label: '용어사전 관리' }
{ id: 'M00184', up_id: 'M00194', label: '로그 이력' }
{ id: 'M00235', up_id: 'M00194', label: '휴일 관리' }
{ id: 'M00236', up_id: 'M00194', label: '게시판 관리' }
{ id: 'M00237', up_id: 'M00194', label: '알림 관리' }
{ id: 'M00243', up_id: 'M00194', label: '사용자별 사용 현황' }
{ id: 'M00195', up_id: 'M00001', label: '화면 마법사' }
{ id: 'M00178', up_id: 'M00195', label: '조회 조건 관리' }
{ id: 'M00179', up_id: 'M00195', label: '메타정보 정의' }
{ id: 'M00180', up_id: 'M00195', label: '포틀릿 관리' }
{ id: 'M00181', up_id: 'M00195', label: '화면 마법사' }
{
id: 'ROOT',
up_id: null,
label: 'ROOT',
children: [ { id: 'M00001', up_id: 'ROOT', label: '관리자', children: [Array] } ]
}
---BEGIN----------------------------------------------
ROOT (ROOT)
관리자 (M00001)
메뉴/권한 관리 (M00192)
권한 관리 (M00190)
메뉴 관리 (M00191)
회사/조직 관리 (M00193)
회사 관리 (M00187)
조직 관리 (M00188)
사용자 관리 (M00189)
시스템관리 (M00194)
공통코드 관리 (M00182)
용어사전 관리 (M00183)
로그 이력 (M00184)
휴일 관리 (M00235)
게시판 관리 (M00236)
알림 관리 (M00237)
사용자별 사용 현황 (M00243)
화면 마법사 (M00195)
조회 조건 관리 (M00178)
메타정보 정의 (M00179)
포틀릿 관리 (M00180)
화면 마법사 (M00181)
----END---------------------------------------------

[Done] exited with code=0 in 0.932 seconds

마무리


이 ts 소스를 vuejs 에 넣어서 웹에 tree 형태로 출력하시면 됩니다.

typescript는 처음이라 함수를 재귀호출로 못만들었습니다. 람다등 좀더 공부를 많이해서
해봐야 겠습니다.

이만 클스 였습니다.


추가로 하는김에 vue에 붙혀서 웹으로 표시 해봤습니다. 추가적으로 클릭시 이벤트 받기, 아이콘 출력하기 tree customize 하기, drag & drop 하기... 





댓글

이 블로그의 인기 게시물

[quaser.dev][2014-09-14] 윈도우즈(10, 64bit)에 개발환경 설정하기

[quaser.dev][2014-09-14] 윈도우즈(10, 64bit)에 개발환경 설정하기

[2024-10-19] iPhone, iPad에서 ChatGPT로 PDF 생성시 한글 깨짐 해결 방법

iPhone, iPad에서 ChatGPT로 PDF 생성 시 한글 깨짐 해결 방법

[2025-04-16(수)] OpenAI gpt-4.1 시리즈 발표, Anthropic Claude에 대한 생각

OpenAI gpt-4.1 시리즈 발표, Anthropic Claude에 대한 생각 안녕하세요. 클스 입니다. 4/15일자로 openai가 gpt-4.1 시리즈를 발표 했습니다. 현재는 api로만 사용가능합니다. 점차 웹/앱 사용자에게 오픈 될거라 생각 됩니다. 비용상 문제로 4.1-mini, nano를 사용해서 chatbot을 만들어 보고 있습니다. 4o 시리즈 보다는 확실히 빠르고, 답변의 정확도는 올라간 것 같습니다. 앤트로픽 클로드와 비교를 많이 하는데, 업무 시스템 혹은 AI 솔루션을 개발하는 입장에서는 어떤 생태계를 제공하는가가 주요한 결정 입니다. AI관련 인력을 충분히 보유한 회사의 경우는 어떤걸 사용해도 좋을 결과를 가지겠지만 일반적인 챗봇 개발 절차를 보면 다음과 같이 볼 수 있습니다. 1. 문서를 준비한다. 대부분 pdf, text, markdown 2. 문서를 파싱해서 vectordb에 올린다.     - 별도 벡터디비 구성 필요. 어떤 db를 선택할지 고민 필요     - 어떤 Parser를 사용할지, 텍스트 오버래핑은 얼마가 적당한지 고민 필요        (회사의 문서가 워낙 많고, 다양하면 하나하나 테스트 해서 좋은걸 선택하는 것이 어렵다)     - 유사도 측정은 어떤 알고리즘을 써야할지 고민 필요     - llamaindex도 고민해야 함. 3. RAG flow를 만든다.     - langchain을 쓸지, 각 AI 벤더에서 제공하는 sdk를 쓸지 고민 필요       (대부분 락인이 되지 않으려면 langchain을 사용하면 좋지만, 벤더에 특화면 기능 적용이 늦음) 4. 챗봇 UI 앱을 만든다.     - 답변이 text 로 구성되다 보니. 그래프, 이미지등 복합적인 컨텐츠를 재배치 하여 표현하기 상당히 어렵네요. (이건 제가 실력이 모자라서 .. 패스) ...