2025-08-08 10:16:20 +09:00
|
|
|
|
# bio_frontend
|
|
|
|
|
|
2025-08-14 09:46:54 +09:00
|
|
|
|
## NUXT 3 (VUE 3) 환경
|
|
|
|
|
- data-list 와 dataList는 동일 변수명으로 인식
|
|
|
|
|
- compnenets 아래의 .vue는 자동 인식(template 내부에서만, 별도 script에서 필요시 선언 필요)
|
|
|
|
|
|
|
|
|
|
|
2025-08-22 14:01:30 +09:00
|
|
|
|
# 구성 요소
|
|
|
|
|
## components 구성
|
|
|
|
|
```
|
|
|
|
|
components
|
|
|
|
|
|- base // 기본 요소(button, input, grid, popup)
|
|
|
|
|
|- layout // 레이아웃 요소(header, footer, sidebar, wrapper)
|
|
|
|
|
|- module // 특정 기능 단위(card, form, list)
|
|
|
|
|
|- pages // 특정 페이지 전용
|
|
|
|
|
```
|
2025-08-14 09:46:54 +09:00
|
|
|
|
|
2025-08-22 14:01:30 +09:00
|
|
|
|
## page 구성
|
|
|
|
|
```
|
|
|
|
|
pages // 단일 화면(비 탭 요소)
|
2025-08-22 14:04:50 +09:00
|
|
|
|
|- popup // 팝업 요소
|
2025-08-22 14:01:30 +09:00
|
|
|
|
|- [tabId] // 탭 요소
|
|
|
|
|
|- admin // 관리자 페이지
|
2025-08-22 14:04:50 +09:00
|
|
|
|
|
2025-08-22 14:01:30 +09:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# page(페이지) 생성 요소
|
|
|
|
|
## 공통 페이지 구성
|
2025-08-14 09:46:54 +09:00
|
|
|
|
```
|
|
|
|
|
<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>
|
|
|
|
|
```
|
|
|
|
|
|
2025-08-22 14:01:30 +09:00
|
|
|
|
|
2025-08-14 09:46:54 +09:00
|
|
|
|
## 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>
|
|
|
|
|
```
|
2025-08-22 14:01:30 +09:00
|
|
|
|
|
|
|
|
|
## 팝업 구성
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌─────────────────────────────┐
|
|
|
|
|
│ 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>
|
|
|
|
|
```
|