# bio_frontend

## NUXT 3 (VUE 3) 환경

- data-list 와 dataList는 동일 변수명으로 인식
- compnenets 아래의 .vue는 자동 인식(template 내부에서만, 별도 script에서 필요시 선언 필요)

# 구성 요소

## components 구성

### 폴더 구조

```
components/
├── base/                    // 기본 UI 요소 (접두사 없이 사용)
│   ├── button/
│   │   ├── CommonButton.vue
│   │   └── PermissionButton.vue
│   ├── grid/
│   │   └── ToastGrid.vue
│   ├── info/
│   │   └── PageDescription.vue
│   ├── loading/
│   │   └── GlobalLoading.vue
│   └── popup/
│       └── CommonPopup.vue
├── layout/                  // 레이아웃 요소 (접두사 없이 사용)
│   ├── navigation/
│   │   ├── AppHeader.vue
│   │   ├── SubMenuBar.vue
│   │   └── TabBar.vue
│   └── wrapper/
│       ├── ContentsWrapper.vue
│       └── PopupWrapper.vue
└── domain/                  // 도메인별 기능 컴포넌트 (@domain/ 접두사 사용)
    └── culture-graph/
        ├── BatchGraph.vue
        ├── BatchTabs.vue
        └── CustomContextMenu.vue
```

### 자동 임포트 설정 (nuxt.config.ts)

```typescript
components: [
  { path: "~/components/base", pathPrefix: false },    // @base/ 접두사 제거
  { path: "~/components/layout", pathPrefix: false },  // @layout/ 접두사 제거
  { path: "~/components/domain", pathPrefix: true },   // @domain/ 접두사 유지
],
```

### 컴포넌트 사용법

- **base/**, **layout/** 폴더의 컴포넌트\*\*: 접두사 없이 직접 사용

  ```vue
  <template>
    <CommonButton />
    <ToastGrid />
    <ContentsWrapper />
  </template>
  ```

- **domain/** 폴더의 컴포넌트: `@domain/` 접두사 사용
  ```vue
  <template>
    <DomainCultureGraphBatchGraph />
  </template>
  ```

## page 구성

```
pages         // 단일 화면(비 탭 요소)
|- popup      // 팝업 요소
|- [tabId]    // 탭 요소
    |- admin  // 관리자 페이지

```

# page(페이지) 생성 요소

## 공통 페이지 구성

```
<template>
    <ContentsWrapper> <!-- wrapper(title) 추가 -->
        <template #actions> <!--title 우측 버튼 설정-->
            <button>추가</button>
            <button>저장</button>
        </template>
        <!--메인 콘텐츠 영역-->
        <input type="text" >
        <!--메인 콘텐츠 영역-->
    </ContentsWrapper>
</template>
<script setup lang="ts">
    // title(wrapper) 설정
    definePageMeta({
        title: '리소스 관리'
    })
</script>
```

## Toast(Tui) Grid 사용법

한글 설명: https://github.com/nhn/tui.grid/blob/master/packages/toast-ui.grid/docs/v4.0-migration-guide-kor.md

### 기본 설정

```
<template>
    <button @click="onAddClick">추가</button>
    <button @click="onUpdateClick">저장</button>

    <!-- toast Grid 필수값 ref, data, columns(header) -->
    <ToastGrid
        ref="grid1Ref"
        :data="data"
        :columns="colDefs"
    />
</template>

<script setup lang="ts">
// 컬럼 항목 리스트
// composables폴더 아래에 생성
// 위 한글 성명 참조하여 생성
import {colDefs} from '../../composables/grids/resourceGrid'

// 데이터 항목
const data = [{}]

// ref 설정
// api : https://nhn.github.io/tui.grid/latest/Grid
const grid1Ref = ref();

onMounted(async () => {
  await nextTick() // DOM 및 컴포넌트 렌더링 완료 대기
  grid1Ref.value?.api()?.setBodyHeight('700') // ref api를 통해서 높이 지정
})

let no = 1;

// 항목 추가 버튼
function onAddClick() {
  grid1Ref.value?.api()?.appendRow({'no': no}); // ref api를 통해서 항목 추가
  ++no;
}

// 저장 버튼
function onUpdateClick() {
   //grid1Ref.value?.clearGrid();
  const chageList = grid1Ref.value?.api()?.getModifiedRows(); // ref api를 통해서 변경점 읽어오기
  console.log(changeList);
}
</script>
```

## tree data

```
<template>
  <ToastGrid
    ref="grid1Ref"
    :data="data"
    :columns="columns"
    :treeColumnOptions="treeColumnOptions"
  />
</template>

<script setup lang="ts">
// data 설정시 _children: 항목으로 구성
const data = [
  {
    id: 549731,
    name: 'Beautiful Lies',
    price: 10000,
    downloadCount: 1000,
    listenCount: 5000,
    _attributes: {
      expanded: true,
    },
    _children: [
      {
        id: 491379,
        name: 'Chaos And The Calm',
        artist: 'James Bay',
        grade: '5',
        price: 12000,
        downloadCount: 1000,
        listenCount: 5000
      },
      {
        id: 450720,
        name: "I'm Not The Only One",
        artist: 'Sam Smith',
        release: '2014.09.15',
        type: 'Single',
        grade: '4',
        price: 8000,
        downloadCount: 1000,
        listenCount: 5000,
        _attributes: {
          expanded: true,
        },
        _children: [
          {
            id: 587871,
            name: 'This Is Acting',
            artist: 'Sia',
            release: '2016.10.22',
            type: 'EP',
            typeCode: '2',
            genre: 'Pop',
            _attributes: {
              expanded: true,
            },
            _children: [
              {
                id: 490500,
                name: 'Blue Skies',
                release: '2015.03.18',
                artist: 'Lenka',
                type: 'Single',
                typeCode: '3',
              },
              {
                id: 317659,
                name: "I Won't Give Up",
                artist: 'Jason Mraz',
                release: '2012.01.03',
                type: 'Single',
                typeCode: '3',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    id: 436461,
    name: 'X',
    artist: 'Ed Sheeran',
    release: '2014.06.24',
    type: 'Deluxe',
    typeCode: '1',
  },
];

const columns = [
  { header: 'Name', name: 'name', width: 300 },
  { header: 'Artist', name: 'artist' },
  { header: 'Type', name: 'type' },
  { header: 'Release', name: 'release' },
  { header: 'Genre', name: 'genre' },
];

// tree column 설정
const treeColumnOptions = { name: 'name', useCascadingCheckbox: true };

const grid1Ref = ref();
</script>
```

## 팝업 구성

```
┌─────────────────────────────┐
│      customPopup.vue        │
│ ┌─────────────────────────┐ │
│ │      poupWrapper.vue    │ │
│ │ ┌─────────────────────┐ │ │
│ │ │        <slot>       │ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────┘ │
└─────────────────────────────┘


customPopup.vue - 기본 빈 팝업
poupWrapper.vue - top, middle, bottom으로 구성된 팝업 구조

1. 구조에 따라 customPopup, poupWrapper 기반으로 팝업 생성(/pages/popup/*.vue)
2. 사용할 페이지에서 생성한 팝업 추가
```

### 팝업 생성

```
// examplePopup.vue
<template>
  <div>
    <!--PopupWrapper 구성, 크기 지정, 숨김 여부 지정 -->
    <PopupWrapper width="1000px" height="600px" v-model:show="show">
      <template #top>
        <h2>팝업 제목</h2>
      </template>

      <template #middle>
        <!-- 팝업 본문 -->
      </template>

      <template #bottom>
        <button>추가 버튼</button>
        <!-- 닫기 버튼은 자동 생성 -->
      </template>
    </PopupWrapper>
  </div>
</template>

<script setup lang="ts">
  // 숨김 여부 지정
  const show = defineModel('show', {type: Boolean, default:false});
</script>
```

### 팝업 사용

```
<template>
  <button @click="popupShow = true">팝업 실행</button>
  <examplePopup v-model:show="popupShow" />
</template>
<script setup lang="ts">
  import addSamplePopup from '../popup/examplePopup.vue';
  const popupShow = ref(false);
</script>
```

## 탭 구성

```
// layouts/default.vue
// stores/tab.ts

import { useTabsStore } from "../stores/tab";
const tabsStore = useTabsStore();

// 탭추가 (최대 10개)
tabsStore.addTab();

// 탭 갱신
tabsStore.updateActiveTab({ label, to, componentName});

// 탭 변경
tabsStore.setActiveTab(key);

// 탭 생성
<div
  v-for="tab in tabsStore.tabs"
  :key="tab.key"
  class="tab-item"
  :class="{ active: tabsStore.activeTab === tab.key }"
  @click="tabsStore.setActiveTab(tab.key);"
>
  {{ tab.label }}
  <span v-show="tabsStore.activeTab !== tab.key" class="close-btn" @click.stop="tabsStore.removeTab(tab.key)"> × </span>
</div>
```
