사이드바 메뉴 구현 (ElMenu 활용)
Element UI의 el-menu 컴포넌트를 사용하여 반응형 사이드 내비게이션을 구성합니다. 주요 속성은 다음과 같습니다:
- collapse: 수직 모드에서 메뉴 접기/펼치기 제어
- backgroundColor, textColor: 시각적 스타일링을 위한 색상 설정
- active-text-color: 활성화된 메뉴 항목 강조 색상
<el-menu
:collapse="isSidebarCollapsed"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409EFF"
@open="onSubmenuOpen"
@close="onSubmenuClose">
<!-- 동적 메뉴 항목 렌더링 -->
<template v-for="menu in menuItems">
<el-menu-item
v-if="!menu.children"
:index="menu.path"
:key="menu.path"
@click="navigateTo(menu)">
<i :class="'icon-' + menu.icon"></i>
<span slot="title">{{ menu.title }}</span>
</el-menu-item>
<el-submenu v-else :index="menu.path" :key="menu.path">
<template slot="title">
<i :class="'icon-' + menu.icon"></i>
<span>{{ menu.title }}</span>
</template>
<el-menu-item
v-for="child in menu.children"
:key="child.path"
:index="child.path"
@click="navigateTo(child)">
{{ child.title }}
</el-menu-item>
</el-submenu>
</template>
</el-menu>
헤더 영역 및 사용자 인터랙션
상단 헤더는 브레드크럼 내비게이션과 드롭다운 프로필 메뉴로 구성됩니다.
<header class="main-header">
<div class="left-section">
<el-button
icon="el-icon-s-fold"
@click="toggleSidebar"
circle size="mini"/>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item
v-for="route in breadcrumbRoutes"
:key="route.path"
:to="{ path: route.path }">
{{ route.name }}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="right-section">
<el-dropdown @command="handleCommand">
<img :src="currentUser.avatar" class="avatar"/>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="profile">내 정보</el-dropdown-item>
<el-dropdown-item command="logout" divided>로그아웃</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</header>
Vuex 상태 관리 패턴 적용
전역 상태를 중앙에서 관리하기 위해 Vuex 스토어를 설계합니다. 핵심 개념은 다음과 같습니다:
// store/modules/layout.js
const state = {
sidebarCollapsed: false,
openTabs: [
{ path: '/', name: 'dashboard', title: '대시보드' }
],
currentRoute: null,
menuStructure: []
};
const mutations = {
TOGGLE_SIDEBAR(state) {
state.sidebarCollapsed = !state.sidebarCollapsed;
},
ADD_TAB(state, route) {
if (!state.openTabs.find(tab => tab.path === route.path)) {
state.openTabs.push(route);
}
},
REMOVE_TAB(state, target) {
const tabs = state.openTabs;
let index = 0;
for (let item of tabs) {
if (item.path === target.path) {
break;
}
index++;
}
tabs.splice(index, 1);
}
};
const actions = {
toggleSidebar({ commit }) {
commit('TOGGLE_SIDEBAR');
}
};
컴포넌트에서는 맵핑 헬퍼를 통해 간결하게 접근할 수 있습니다:
import { mapState, mapMutations } from 'vuex';
export default {
computed: {
...mapState('layout', ['sidebarCollapsed', 'openTabs'])
},
methods: {
...mapMutations('layout', ['TOGGLE_SIDEBAR', 'ADD_TAB']),
toggle() {
this.TOGGLE_SIDEBAR();
}
}
}
재사용 가능한 폼 컴포넌트 개발
동적 폼 생성을 위한 일반화된 폼 컴포넌트입니다.
<template>
<el-form :model="formData" :inline="inlineMode" label-width="90px">
<el-form-item
v-for="field in fields"
:key="field.key"
:label="field.label">
<el-input
v-if="field.type === 'text'"
v-model="formData[field.key]"
:placeholder="'입력 ' + field.label"/>
<el-select
v-else-if="field.type === 'select'"
v-model="formData[field.key]">
<el-option
v-for="opt in field.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"/>
</el-select>
<el-switch
v-else-if="field.type === 'boolean'"
v-model="formData[field.key]"/>
<el-date-picker
v-else-if="field.type === 'date'"
v-model="formData[field.key]"
type="date"
value-format="yyyy-MM-dd"/>
</el-form-item>
<slot name="actions"/>
</el-form>
</template>
데이터 테이블 및 페이징 처리
Element UI 테이블을 기반으로 한 표준 데이터 그리드 컴포넌트입니다.
<el-table
:data="tableData"
style="width: 100%"
height="calc(100vh - 200px)"
border
stripe>
<el-table-column
v-for="column in columns"
:key="column.prop"
:prop="column.prop"
:label="column.label"
:width="column.width || 150"
show-overflow-tooltip/>
<el-table-column label="관리" fixed="right" width="180">
<template slot-scope="scope">
<el-button size="mini" @click="onEdit(scope.row)">수정</el-button>
<el-button size="mini" type="danger" @click="onDelete(scope.row)">삭제</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@current-change="onPageChange"
:current-page.sync="pagination.currentPage"
:page-size="20"
:total="pagination.total"
layout="total, prev, pager, next"/>
탭 네비게이션 (태그 기반)
여러 페이지를 탭 형태로 관리하는 인터페이스입니다.
<div class="tab-container">
<el-tag
v-for="(tab, idx) in openedTabs"
:key="tab.path"
:closable="tab.name !== 'dashboard'"
:type="getCurrentTabType(tab)"
@click="switchToTab(tab)"
@close="closeTab(tab, idx)">
{{ tab.title }}
</el-tag>
</div>
라우터 가드를 통한 보안 제어
인증 상태에 따라 페이지 접근을 제어합니다.
router.beforeEach((to, from, next) => {
const isAuthenticated = !!localStorage.getItem('authToken');
if (!isAuthenticated && to.name !== 'Login') {
next({ name: 'Login' });
} else if (isAuthenticated && to.name === 'Login') {
next({ name: 'Dashboard' });
} else {
next();
}
});