본문 바로가기
▶ Front-End/Vue.js

Vue3 기초 예제 - CRUD 관리자 페이지

by 오늘도 코딩 2023. 11. 2.
728x90
반응형

공통 코드를 관리하는 관리자 페이지 기초 예제

*동적 데이터 테이블 생성, Axios API 통신, Modal open/close

*API 관련 설명 생략

*자세한 설명 생략

 

 

▷ 기초 예제 - CRUD

 

- Cmp.css

*dataTable, modal 관련 css 추가

@import url('https://fonts.googleapis.com/css2?family=Gugi&display=swap');

* {
  font-family: 'Gugi', sans-serif;
}

.HeaderCmp {
  margin-bottom: 50px;
  text-align: center;
  color: #2c3e50;
}

.ForTestCmp {
  text-align: left;
  font-size: small;
  font-weight: lighter;
  color: #41b883;
}

.IfTestCmp {
  text-align: left;
  font-size: small;
  font-weight: lighter;
  color: olive;
}

.CompositionTestCmp {
  text-align: left;
  font-size: small;
  font-weight: lighter;
  color: chocolate;
}

.APICallTestCmp {
  text-align: left;
  font-size: small;
  font-weight: lighter;
  color: purple;
}

.MenuCmp {
  text-align: left;
  margin-left: 100px;
}

.dataTable {
  border-collapse: collapse;
  text-align: left;
}

.dataTable th {
  width: 200px;
  background: #f3f6f7;
  font-weight: bold;
  color: #369;
  border-bottom: 3px solid #036;
}

.dataTable td {
  padding: 5px;
  border-bottom: 1px solid #ccc;
}

.modal{
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  position: fixed;

  width: 400px;
  text-align: center;
  background: white;
  border: 5px solid #036;
}

.modalContent {
  text-align: left; 
  margin-left: 20px;
}

a {
  text-decoration: none;
  color: black;
}

 

- ACM.vue

*공통 코드를 관리하는 Component

*주석 이외 설명 생략(관련 글 참고)

<template>
    <div>
        <h1>👤 공간 코드 관리</h1><br />
        <div>
            <h3>공간 코드 추가</h3>
            <input v-model="inAreaCd" placeholder="공간코드(A00)">
            <input v-model="inAreaNm" placeholder="공간이름">
            <button @click="areaMerge('INSERT', inAreaCd, inAreaNm);">추가</button>
        </div><br /><br />
        <div>
            <h3>공간코드</h3>
            <table class="dataTable">
                <th>NO</th>
                <th>공간 코드</th>
                <th>공간 이름</th>
                <th>관리</th>
                <tr v-for="(obj, i) in areaList" :key="obj">
                    <td> {{ i + 1 }} </td>
                    <td>{{ obj.areaCd }}</td>
                    <td>{{ obj.areaNm }}</td>
                    <td @click="openModal(obj.areaCd, obj.areaNm)">🔍 수정/삭제</td>
                </tr>
            </table>
        </div>
    </div>


    <!-- modal -->
    <div class="modal" v-if="modalState">
        <h2>공간 코드 수정/삭제</h2><br />
        <div class="modalContent">
            <h4>공간 코드 : </h4>
            <h1>{{ modalAreaCd }}</h1><br />
            <h4>공간 이름 : </h4>
            <p><input style="font-size:30px" v-model="modalAreaNm" placeholder="공간이름"></p>
            <br />
        </div><br /><br />
        <div>
            <button style="font-size: 30px;" @click="areaMerge('UPDATE', modalAreaCd, modalAreaNm)">👨‍🔧(수정)</button>
            <button style="font-size: 30px; margin-left: 10px;" @click="areaDelete(modalAreaCd)">🧺(삭제)</button>
        </div><br />
        <div>
            <h4 @click="colseModal()">❌(닫기)</h4>
        </div>
    </div>
    <!-- /modal -->
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useLoginStore } from '@/module/store/LoginStore.js'
import { API_LIST, sendPost } from '@/module/AxiosModule'

const areaList = ref([])

const inAreaCd = ref("")
const inAreaNm = ref("")

const modalState = ref(false)
const modalAreaCd = ref("")
const modalAreaNm = ref("")

onMounted(() => {
    /** 로그인 체크 */
    useLoginStore().checkUser()

    /** 공간 코드 테이블 초기화 */
    areaInit()
})

/** modal open */
function openModal(areaCd, areaNm) {
    modalState.value = true
    modalAreaCd.value = areaCd
    modalAreaNm.value = areaNm
}

/** modal close */
function colseModal() {
    modalState.value = false
}

/** 공간 코드 테이블 초기화 */
async function areaInit() {

    inAreaCd.value = ""
    inAreaNm.value = ""

    /* 공간 코드 전체 조회 요청 */
    const resVO = await sendPost(API_LIST['BHS-ACM-1001'], {})
    if (resVO.resultCode == "0000") {
        // 정렬
        areaList.value = resVO.result.sort((a, b) => a.areaCd.localeCompare(b.areaCd))
    }
}

/** 공간 코드 추가/수정 */
async function areaMerge(action, areaCd, areaNm) {

    /* 입력 값 확인 */
    if (areaCd == "" || areaNm == "") {
        alert("입력 값을 확인 해주세요.")
        return
    }

    if (action == "INSERT") {
        for (const i in areaList.value) {
            if (areaList.value[i].areaCd == areaCd) {
                alert("공간코드 중복입니다. 다시 입력해주세요.")
                return
            }
        }
    }

    /* 요청 값 설정 */
    const REQ_DATA = {
        "areaCd": areaCd,
        "areaNm": areaNm
    }

    /* 공간 코드 추가/수정 요청 */
    const resVO = await sendPost(API_LIST['BHS-ACM-1003'], REQ_DATA)
    if (resVO.resultCode == "0000") {
        action == "INSERT" ? alert("공간 코드 추가 완료 됐습니다.") : alert("공간 코드 수정 완료 됐습니다.")

        /** modal close */
        colseModal()

        /** 공간 코드 테이블 초기화 */
        areaInit()
    } else {
        alert(resVO.resultMsg)
        return
    }
}

/** 공간 코드 삭제 */
async function areaDelete(areaCd) {

    /* 요청 값  설정 */
    const REQ_DATA = {
        "areaCd": areaCd
    }

    /* 공간 코드 삭제 요청 */
    const resVO = await sendPost(API_LIST['BHS-ACM-1004'], REQ_DATA)
    if (resVO.resultCode == "0000") {
        alert("공간 코드 삭제 완료 됐습니다.")

        /** modal close */
        colseModal()

        /** 공간 코드 테이블 초기화 */
        areaInit()
    } else {
        alert(resVO.resultMsg)
        return
    }
}
</script>

 

*Axios post headers 값 추가

function sendPost(API_URI, REQ_DATA) {
    return Axios
        .post(API_SERVER + API_URI, REQ_DATA,
            {
                headers: {
                    "Authorization": useLoginStore().JWT
                }
            })
        .then((res) => {
            return res.data
        })
        .catch((res) => {
            return res.data
        })
}

 

- MenuNav.js

*router에 ACM Component 추가

import { createRouter, createWebHistory } from 'vue-router'

// 연결 컴포넌트 
import ForTestCmp from '@/components/test/ForTestCmp.vue'
import IfTestCmp from '@/components/test/IfTestCmp.vue'
import CompositionTestCmp from '@/components/test/CompositionTestCmp.vue'
import LoginCmp from '@/components/common/LoginCmp.vue'

import ACM from '@/components/service/ACM.vue'
import CCM from '@/components/service/CCM.vue'

// 라우터 설계
const routes = [
    { path: '/', component: LoginCmp },
    { path: '/1', component: IfTestCmp },
    { path: '/2', component: ForTestCmp },
    { path: '/3', component: CompositionTestCmp },

    { path: '/4', component: ACM }, 
    { path: '/5', component: CCM } 
]

// 라우터 생성
const router = createRouter({
    history: createWebHistory(),
    routes
});

// 라우터 추출 (main.js에서 import)
export { router }

 

- MenuCmp.vue

*메뉴 네비게이션에 router link 추가

<template>
    <div class="MenuCmp">
        <div>
            <h3><router-link to="/">🏠HOME</router-link></h3>
        </div><br />
        <div>
            <h3>💡TEST</h3>
            <h5><router-link to="/1">▷ IF</router-link></h5>
            <h5><router-link to="/2">▷ FOR</router-link></h5>
            <h5><router-link to="/3">▷ Composition</router-link></h5>
        </div><br />
        <div>
            <h3>👨‍💻 API</h3>
            <h5><router-link to="/4">▷ 공간 코드 관리</router-link></h5>
            <h5><router-link to="/5">▷ 컨텐츠 코드 관리</router-link></h5>
        </div>
    </div>
</template>

 

 

▷ 결과 확인

*로그인,  validation 제외

 

① 초기 화면

 

② 입력 후 추가 버튼 클릭

 

③ 추가 완료 팝업과 입력란 초기화, 정렬하여 재 생성된 데이터 테이블

 

④ 수정/삭제 클릭 시, Modal Open

 

⑤ 공간 이름 수정 후 , 수정 버튼 클릭

 

⑥ 수정 완료 팝업과 Modal Close, 재 생성된 데이터 테이블

 

⑦ 다시 수정/삭제 클릭 후 삭제 클릭

 

⑧ 삭제 완료 팝업과 재 생성된 데이터 테이블

 

 

▷ 관련 글

 

Vue3 기초 예제 프로젝트 정리

Vue3 기초 예제 프로젝트 구조 및 소스 중간 정리 *관련 글이 점점 늘어나 하나로 통합하기 위함 *이 글에서 만 싱크 맞춤 *주석 이외 설명 생략 *자세한 설명 생략 ▷ 프로젝트 전체 구조 < 파일 따

coding-today.tistory.com

 

 

728x90
728x90

댓글