tab, component, popup 변경

This commit is contained in:
2025-08-22 14:01:30 +09:00
parent fd6fe43498
commit f801e876d2
22 changed files with 625 additions and 145 deletions

View File

@@ -0,0 +1,173 @@
<template>
<div class="codes-page">
<div class="page-header">
<h1>공통코드</h1>
<div class="page-actions">
<button class="action-btn">코드 추가</button>
<button class="action-btn">그룹코드 추가</button>
<button class="action-btn primary">저장</button>
</div>
</div>
<div class="aggrid-section">
<div class="aggrid-title">그룹코드</div>
<div class="aggrid-container">
<ag-grid-vue
class="ag-theme-material"
style="width: 100%; height: 180px"
:column-defs="groupColDefs"
:row-data="groupRowData"
:default-col-def="defaultColDef"
/>
</div>
</div>
<div class="aggrid-section">
<div class="aggrid-title">코드</div>
<div class="aggrid-container">
<ag-grid-vue
class="ag-theme-material"
style="width: 100%; height: 320px"
:column-defs="codeColDefs"
:row-data="codeRowData"
:default-col-def="defaultColDef"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { AgGridVue } from "ag-grid-vue3";
import { ModuleRegistry, AllCommunityModule } from "ag-grid-community";
import type { GroupCode, Code, ColDef, DefaultColDef } from "~/types/ag-grid";
// AG Grid 모듈 등록
onMounted(() => {
ModuleRegistry.registerModules([AllCommunityModule]);
});
const groupColDefs = ref<ColDef[]>([
{ headerName: "그룹 코드", field: "groupCode", width: 140 },
{ headerName: "그룹 코드명", field: "groupName", width: 180 },
{ headerName: "사용 여부", field: "useYn", width: 100 },
{ headerName: "정렬 순서", field: "order", width: 100 },
]);
const groupRowData = ref<GroupCode[]>([
{
groupCode: "MONITORING_001",
groupName: "모니터링 기본정보",
useYn: "Y",
order: 1,
},
{ groupCode: "RESOURCE_001", groupName: "리소스 유형", useYn: "Y", order: 6 },
]);
const codeColDefs = ref<ColDef[]>([
{ headerName: "코드", field: "code", width: 160 },
{ headerName: "코드명", field: "codeName", width: 120 },
{ headerName: "코드 상세", field: "codeDetail", width: 200 },
{ headerName: "부모 코드", field: "parentCode", width: 120 },
{ headerName: "사용 여부", field: "useYn", width: 100 },
{ headerName: "정렬 순서", field: "order", width: 100 },
]);
const codeRowData = ref<Code[]>([
{
code: "RESOURCE_001_001",
codeName: "facility",
codeDetail: "주요 시설을 나타냄",
parentCode: "",
useYn: "Y",
order: 1,
},
{
code: "RESOURCE_001_002",
codeName: "equipment",
codeDetail: "사업에 설치된 장비",
parentCode: "",
useYn: "Y",
order: 2,
},
{
code: "RESOURCE_001_003",
codeName: "device",
codeDetail: "IoT 디바이스 또는 센서",
parentCode: "",
useYn: "Y",
order: 3,
},
{
code: "RESOURCE_001_004",
codeName: "station",
codeDetail: "데이터 수집 또는 처리 스테이션",
parentCode: "",
useYn: "Y",
order: 4,
},
]);
const defaultColDef = ref<DefaultColDef>({
resizable: true,
sortable: true,
filter: true,
minWidth: 80,
});
</script>
<style scoped>
.codes-page {
padding: 32px 32px 0 32px;
}
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 18px;
}
.page-header h1 {
font-size: 1.35rem;
font-weight: 700;
color: #222;
margin: 0;
}
.page-actions {
display: flex;
gap: 10px;
}
.action-btn {
background: #f4f6fa;
border: 1px solid #e0e7ef;
color: #1976d2;
font-weight: 500;
border-radius: 5px;
padding: 7px 18px;
font-size: 1rem;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.action-btn.primary {
background: #1976d2;
color: #fff;
border: none;
}
.action-btn:hover {
background: #e6f0fa;
}
.aggrid-section {
margin-bottom: 32px;
}
.aggrid-title {
font-size: 1.08rem;
font-weight: 600;
color: #1976d2;
margin-bottom: 8px;
margin-left: 2px;
}
.aggrid-container {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.04);
padding: 18px 18px 0 18px;
}
</style>

View File

@@ -0,0 +1,118 @@
<template>
<div class="logs-page">
<div class="page-header">
<div class="breadcrumb">관리자 메뉴 / 접속기록</div>
</div>
<div class="filter-section">
<label for="date-input">날짜 설정</label>
<input id="date-input" v-model="selectedDate" type="date" />
</div>
<div class="aggrid-section">
<div class="aggrid-container">
<ag-grid-vue
class="ag-theme-material"
style="width: 100%; height: 220px"
:column-defs="colDefs"
:row-data="filteredRows"
:default-col-def="defaultColDef"
/>
</div>
</div>
<div class="copyright">
© 2024. Ocean Monitoring System. All Rights Reserved.
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import { AgGridVue } from "ag-grid-vue3";
import { ModuleRegistry, AllCommunityModule } from "ag-grid-community";
import type { LogEntry, ColDef, DefaultColDef } from "~/types/ag-grid";
onMounted(() => {
ModuleRegistry.registerModules([AllCommunityModule]);
});
const selectedDate = ref<string>("2025-06-27");
const rows = ref<LogEntry[]>([
{ account: "admin3", datetime: "Jun 27, 2025, 9:51:04 AM", ip: "172.17.0.1" },
{ account: "admin9", datetime: "Jun 27, 2025, 8:51:41 AM", ip: "172.17.0.1" },
]);
const colDefs = ref<ColDef[]>([
{ headerName: "계정", field: "account", width: 160 },
{ headerName: "접속 일시", field: "datetime", width: 200 },
{ headerName: "IP", field: "ip", width: 160 },
]);
const defaultColDef = ref<DefaultColDef>({
resizable: true,
sortable: true,
filter: true,
minWidth: 80,
});
const filteredRows = computed<LogEntry[]>(() => {
// 실제 구현시 날짜 필터링 적용
return rows.value;
});
</script>
<style scoped>
.logs-page {
padding: 32px 32px 0 32px;
}
.page-header {
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: 18px;
}
.breadcrumb {
font-size: 1.08rem;
color: #1976d2;
font-weight: 500;
}
.filter-section {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 18px;
margin-left: 2px;
}
.filter-section label {
font-size: 1rem;
color: #222;
font-weight: 500;
}
.filter-section input[type="date"] {
border: 1px solid #e0e7ef;
border-radius: 5px;
padding: 6px 12px;
font-size: 1rem;
color: #222;
background: #f4f6fa;
}
.aggrid-section {
margin-bottom: 32px;
}
.aggrid-container {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.04);
padding: 18px 18px 0 18px;
}
.table-info {
font-size: 0.98rem;
color: #666;
margin-bottom: 12px;
}
.copyright {
text-align: right;
color: #888;
font-size: 0.98rem;
margin: 32px 8px 0 0;
padding-bottom: 18px;
}
</style>

View File

@@ -0,0 +1,148 @@
<template>
<div class="codes-page">
<div class="page-header">
<h1>프로그램 관리</h1>
<div class="page-actions">
<button class="action-btn">프로그램 추가</button>
<button class="action-btn primary">저장</button>
</div>
</div>
<div class="aggrid-section">
<div class="aggrid-title">프로그램 목록</div>
<div class="aggrid-container">
<ag-grid-vue
class="ag-theme-material"
style="width: 100%; height: 600px"
:column-defs="programColDefs"
:row-data="programRowData"
:default-col-def="defaultColDef"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { AgGridVue } from "ag-grid-vue3";
import { ModuleRegistry, AllCommunityModule } from "ag-grid-community";
import type { Program, ColDef, DefaultColDef } from "~/types/ag-grid";
onMounted(() => {
ModuleRegistry.registerModules([AllCommunityModule]);
});
const programColDefs = ref<ColDef[]>([
{ headerName: "부모 코드", field: "parentCode", width: 100 },
{ headerName: "레벨", field: "level", width: 60 },
{ headerName: "코드", field: "code", width: 120 },
{ headerName: "이름", field: "name", width: 160 },
{ headerName: "사용 여부", field: "useYn", width: 80 },
{ headerName: "메뉴 여부", field: "menuYn", width: 80 },
{ headerName: "API 여부", field: "apiYn", width: 80 },
{ headerName: "예외 허용 여부", field: "exceptionYn", width: 110 },
{ headerName: "표시 순서", field: "order", width: 80 },
{ headerName: "uri", field: "uri", width: 180 },
{ headerName: "필드1", field: "field1", width: 100 },
{ headerName: "필드2", field: "field2", width: 100 },
{ headerName: "필드3", field: "field3", width: 100 },
]);
const programRowData = ref<Program[]>([
{
parentCode: "OMS01",
level: 1,
code: "OMS01_01",
name: "DASHBOARD",
useYn: true,
menuYn: true,
apiYn: false,
exceptionYn: false,
order: 1,
uri: "/dashboard/overview",
field1: "ri-pie-chart-2-line",
field2: "",
field3: "",
},
{
parentCode: "OMS01_02",
level: 2,
code: "OMS01_02_01",
name: "IoT 센서이력 조회",
useYn: true,
menuYn: true,
apiYn: false,
exceptionYn: false,
order: 5,
uri: "/environmental/iot-sensor-history",
field1: "ri-line-chart-line",
field2: "",
field3: "",
},
// ... (샘플 데이터 추가)
]);
const defaultColDef = ref<DefaultColDef>({
resizable: true,
sortable: true,
filter: true,
minWidth: 80,
});
</script>
<style scoped>
.codes-page {
padding: 32px 32px 0 32px;
}
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 18px;
}
.page-header h1 {
font-size: 1.35rem;
font-weight: 700;
color: #222;
margin: 0;
}
.page-actions {
display: flex;
gap: 10px;
}
.action-btn {
background: #f4f6fa;
border: 1px solid #e0e7ef;
color: #1976d2;
font-weight: 500;
border-radius: 5px;
padding: 7px 18px;
font-size: 1rem;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.action-btn.primary {
background: #1976d2;
color: #fff;
border: none;
}
.action-btn:hover {
background: #e6f0fa;
}
.aggrid-section {
margin-bottom: 32px;
}
.aggrid-title {
font-size: 1.08rem;
font-weight: 600;
color: #1976d2;
margin-bottom: 8px;
margin-left: 2px;
}
.aggrid-container {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.04);
padding: 18px 18px 0 18px;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<ContentsWrapper>
<template #actions>
<button @click="onAddClick">추가</button>
<button @click="onUpdateClick">저장</button>
</template>
<input type="text" >
<ToastGrid
ref="grid1Ref"
:data="data"
:columns="colDefs"
/>
</ContentsWrapper>
</template>
<script setup lang="ts">
import {colDefs} from '../../../composables/grids/resourceGrid'
definePageMeta({
title: '리소스 관리'
})
const data = [{}]
const grid1Ref = ref();
onMounted(async () => {
await nextTick() // DOM 및 컴포넌트 렌더링 완료 대기
grid1Ref.value?.api()?.setBodyHeight('700')
})
function onAddClick() {
grid1Ref.value?.api()?.appendRow({});
}
function onUpdateClick() {
//grid1Ref.value?.clearGrid();
console.log(grid1Ref.value?.api()?.getModifiedRows());
}
</script>

123
pages/[tabId]/index.vue Normal file
View File

@@ -0,0 +1,123 @@
<template>
<div
class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 py-12 px-4"
>
<div class="max-w-4xl mx-auto">
<div class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-900 mb-4">
Integrated Bio Foundry Platform
</h1>
<p class="text-xl text-gray-600">
통합 바이오 파운드리 플랫폼에 오신 것을 환영합니다
</p>
<!-- 사용자 환영 메시지 -->
<div
v-if="userStore.isLoggedIn"
class="mt-6 p-4 bg-white rounded-lg shadow-md inline-block"
>
<p class="text-lg text-gray-800 mb-2">
안녕하세요,
<span class="font-semibold text-blue-600">{{
userStore.userName
}}</span
>!
</p>
<p class="text-sm text-gray-600">
{{ userStore.isAdmin ? "관리자" : "사용자" }} 권한으로
로그인되었습니다.
</p>
</div>
<div
v-else
class="mt-6 p-4 bg-yellow-50 rounded-lg shadow-md inline-block"
>
<p class="text-lg text-gray-800 mb-2">로그인이 필요합니다</p>
<NuxtLink
to="/login"
class="inline-block mt-2 bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg transition-colors"
>
로그인하기
</NuxtLink>
</div>
</div>
<!-- Tailwind CSS 테스트 섹션 -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div
class="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow"
>
<div
class="w-12 h-12 bg-blue-500 rounded-full flex items-center justify-center mb-4 mx-auto"
>
<span class="text-white font-bold">1</span>
</div>
<h3 class="text-lg font-semibold text-gray-900 mb-2">Feature 1</h3>
<p class="text-gray-600">Tailwind CSS가 정상 작동하고 있습니다!</p>
</div>
<div
class="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow"
>
<div
class="w-12 h-12 bg-green-500 rounded-full flex items-center justify-center mb-4 mx-auto"
>
<span class="text-white font-bold">2</span>
</div>
<h3 class="text-lg font-semibold text-gray-900 mb-2">Feature 2</h3>
<p class="text-gray-600">반응형 디자인이 적용되었습니다.</p>
</div>
<div
class="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow"
>
<div
class="w-12 h-12 bg-purple-500 rounded-full flex items-center justify-center mb-4 mx-auto"
>
<span class="text-white font-bold">3</span>
</div>
<h3 class="text-lg font-semibold text-gray-900 mb-2">Feature 3</h3>
<p class="text-gray-600">모던한 UI 컴포넌트를 사용할 있습니다.</p>
</div>
</div>
<!-- 버튼 테스트 -->
<div class="text-center space-x-4">
<button
class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg transition-colors"
>
Primary Button
</button>
<button
class="bg-gray-500 hover:bg-gray-600 text-white font-medium py-2 px-4 rounded-lg transition-colors"
>
Secondary Button
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from "~/stores/user";
// 페이지 메타데이터 설정
definePageMeta({
title: "Home",
description: "Welcome to our Nuxt.js application",
});
const userStore = useUserStore();
</script>
<style scoped>
.home {
padding: 2rem;
text-align: center;
}
h1 {
color: #00dc82;
margin-bottom: 1rem;
}
</style>

View File

@@ -0,0 +1,20 @@
<template>
<ContentsWrapper> <!-- wrapper(title) 추가 -->
<template #actions> <!--title 우측 버튼 설정-->
<button>스케쥴 확인</button>
<button @click="addSamplePopupShow = true">샘플 등록</button>
</template>
<!--메인 콘텐츠 영역-->
<input type="text" >
<addSamplePopup v-model:show="addSamplePopupShow" />
</ContentsWrapper>
</template>
<script setup lang="ts">
import addSamplePopup from '../popup/addSamplePopup.vue';
// title(wrapper) 설정
definePageMeta({
title: '조회 결과'
})
const addSamplePopupShow = ref(false);
</script>

323
pages/[tabId]/tui.vue Normal file
View File

@@ -0,0 +1,323 @@
<script setup lang="ts">
import ToastGrid from '@/components/base/ToastGrid.vue';
const data = [
{
id: 549731,
name: 'Beautiful Lies',
artist: 'Birdy',
release: '2016.03.26',
type: 'Deluxe',
typeCode: '1',
genre: 'Pop',
genreCode: '1',
grade: '4',
price: 10000,
downloadCount: 1000,
listenCount: 5000,
_attributes: {
expanded: true,
},
_children: [
{
id: 491379,
name: 'Chaos And The Calm',
artist: 'James Bay',
release: '2015.03.23',
type: 'EP',
typeCode: '2',
genre: 'Pop,Rock',
genreCode: '1,2',
grade: '5',
price: 12000,
downloadCount: 1000,
listenCount: 5000,
_children: [],
},
{
id: 498896,
name: 'The Magic Whip',
artist: 'Blur',
release: '2015.04.27',
type: 'EP',
typeCode: '2',
genre: 'Rock',
genreCode: '2',
grade: '3',
price: 15000,
downloadCount: 1000,
listenCount: 5000,
_attributes: {
expanded: false,
},
},
{
id: 450720,
name: "I'm Not The Only One",
artist: 'Sam Smith',
release: '2014.09.15',
type: 'Single',
typeCode: '3',
genre: 'Pop,R&B',
genreCode: '1,3',
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',
genreCode: '1',
grade: '3',
price: 20000,
downloadCount: 1000,
listenCount: 5000,
_attributes: {
expanded: true,
},
_children: [
{
id: 490500,
name: 'Blue Skies',
release: '2015.03.18',
artist: 'Lenka',
type: 'Single',
typeCode: '3',
genre: 'Pop,Rock',
genreCode: '1,2',
grade: '5',
price: 6000,
downloadCount: 1000,
listenCount: 5000,
},
{
id: 317659,
name: "I Won't Give Up",
artist: 'Jason Mraz',
release: '2012.01.03',
type: 'Single',
typeCode: '3',
genre: 'Pop',
genreCode: '1',
grade: '2',
price: 7000,
downloadCount: 1000,
listenCount: 5000,
},
{
id: 583551,
name: 'Following My Intuition',
artist: 'Craig David',
release: '2016.10.01',
type: 'Deluxe',
typeCode: '1',
genre: 'R&B,Electronic',
genreCode: '3,4',
grade: '5',
price: 15000,
downloadCount: 1000,
listenCount: 5000,
},
],
},
],
},
],
},
{
id: 436461,
name: 'X',
artist: 'Ed Sheeran',
release: '2014.06.24',
type: 'Deluxe',
typeCode: '1',
genre: 'Pop',
genreCode: '1',
grade: '5',
price: 20000,
downloadCount: 1000,
listenCount: 5000,
},
{
id: 295651,
name: 'Moves Like Jagger',
release: '2011.08.08',
artist: 'Maroon5',
type: 'Single',
typeCode: '3',
genre: 'Pop,Rock',
genreCode: '1,2',
grade: '2',
price: 7000,
downloadCount: 1000,
listenCount: 5000,
},
{
id: 541713,
name: 'A Head Full Of Dreams',
artist: 'Coldplay',
release: '2015.12.04',
type: 'Deluxe',
typeCode: '1',
genre: 'Rock',
genreCode: '2',
grade: '3',
price: 25000,
downloadCount: 1000,
listenCount: 5000,
_attributes: {
expanded: false,
},
_children: [
{
id: 294574,
name: '4',
artist: 'Beyoncé',
release: '2011.07.26',
type: 'Deluxe',
typeCode: '1',
genre: 'Pop',
genreCode: '1',
grade: '3',
price: 12000,
downloadCount: 1000,
listenCount: 5000,
},
{
id: 265289,
name: '21',
artist: 'Adele',
release: '2011.01.21',
type: 'Deluxe',
typeCode: '1',
genre: 'Pop,R&B',
genreCode: '1,3',
grade: '5',
price: 15000,
downloadCount: 1000,
listenCount: 5000,
},
],
},
{
id: 555871,
name: 'Warm On A Cold Night',
artist: 'HONNE',
release: '2016.07.22',
type: 'EP',
typeCode: '1',
genre: 'R&B,Electronic',
genreCode: '3,4',
grade: '4',
price: 11000,
downloadCount: 1000,
listenCount: 5000,
},
{
id: 550571,
name: 'Take Me To The Alley',
artist: 'Gregory Porter',
release: '2016.09.02',
type: 'Deluxe',
typeCode: '1',
genre: 'Jazz',
genreCode: '5',
grade: '3',
price: 30000,
downloadCount: 1000,
listenCount: 5000,
},
{
id: 544128,
name: 'Make Out',
artist: 'LANY',
release: '2015.12.11',
type: 'EP',
typeCode: '2',
genre: 'Electronic',
genreCode: '4',
grade: '2',
price: 12000,
downloadCount: 1000,
listenCount: 5000,
},
{
id: 366374,
name: 'Get Lucky',
artist: 'Daft Punk',
release: '2013.04.23',
type: 'Single',
typeCode: '3',
genre: 'Pop,Funk',
genreCode: '1,5',
grade: '3',
price: 9000,
downloadCount: 1000,
listenCount: 5000,
},
{
id: 8012747,
name: 'Valtari',
artist: 'Sigur Rós',
release: '2012.05.31',
type: 'EP',
typeCode: '3',
genre: 'Rock',
genreCode: '2',
grade: '5',
price: 10000,
downloadCount: 1000,
listenCount: 5000,
},
];
const columns = [
{ header: 'Name', name: 'name', width: 300 },
{ header: 'Artist', name: 'artist' },
{ header: 'Type', name: 'type' },
{ header: 'Release', name: 'release' },
{ header: 'Genre', name: 'genre' },
{ header: 'checkbox', name: 'checkbox', editor:{ type: 'checkbox', options: {
listItems: [
{ text: 'true', value: true },
]
}}}
];
const treeColumnOptions = { name: 'name', useCascadingCheckbox: true };
const grid1Ref = ref();
function onClearClick() {
//grid1Ref.value?.clearGrid();
grid1Ref.value?.api()?.clear();
}
function onUpdateClick() {
//grid1Ref.value?.clearGrid();
console.log(grid1Ref.value?.api()?.getModifiedRows());
}
</script>
<template>
<div>
<button @click="onClearClick">clear api</button>
<br>
<button @click="onUpdateClick">update list</button>
<ToastGrid
ref="grid1Ref"
:data="data"
:columns="columns"
:treeColumnOptions="treeColumnOptions"
/>
</div>
</template>