Vue.js 기반 관리자 대시보드 구성 요소 분석

사이드바 메뉴 구현 (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();
    }
});

태그: Vue.js Element-UI vuex spa routing

6월 6일 02:41에 게시됨