본문 바로가기
Programming

[Vue.js] 210706 학습일지

by 강한수달 2021. 7. 6.

SPA ( Single Page Application )

- 이전의 방식은 페이지에 어떤 Action 으로 인해 변경이 발생한 경우 Client 는 Server 에

  HTML 파일을 재요청하여 페이지 전체를 갱신하게 되는 비효율적인 부분이 있음

- 이러한 부분을 해결하기 위해 HTML 파일이 아닌 변경된 부분에 한하여 Server 에

  데이터를 요청하는 방식이 고안됨

- 하나의 페이지로 여러개의 페이지를 새로고침 없이 이용 가능하게 됨

- URL 공유나 검색 엔진에 노출되야하는 시스템으로는 부적합함

- SPA 프레임워크 중 대표적인 예는 Vue.js, Angular, React 가 있음

 

위) 전통적인 페이지 로드 방식,  아래) 단일 페이지 로드 방식

 

CSR ( Client Side Rendering )

- URL 이 바뀌어도 Server 에서 새로운 HTML 을 다시 내려받지 않고 필요한 데이터를

  Server 에게 요청하고 Server 로부터 받은 데이터로 Client 에서 자체적으로 렌더링을 함

  ( Vue의 경우 app.js 파일 내에 해당 기능들이 명시되어 있음 )

- 단점으로는 app.js 와 같은 파일을 먼저 다운받아야 화면을 구성할 수 있으므로

  웹페이지 첫 화면을 보는데 오래 걸릴 수 있음

- 페이지 캐싱이 잘 되지 않음

- SEO ( Search Engine Optimization ) 최적화가 잘 되지 않음

  ( HTML 파일 자체는 빈 페이지이며 화면을 채우고 구성하는 것은 javascript 파일이기 때문임 )

SPA, CSR 웹페이지 예시


문제. 레스토랑 주문 시스템 구현

<template>
    <div class="container">
        <div class="MENU_BOARD">
        <!-- 상품 아이템 리스트 -->
            <button v-for="(menuItem, menuIdx) in menus" v-bind:key=menuIdx
                @click="sendOrder(menuItem.menuId)"
                class="btn">
                <span style="color: rgb(60, 255, 0);">{{menuItem.menuName}}</span><br>
                <span style="color: white;">({{menuItem.price.toLocaleString()}} 원)</span>
            </button>
        </div>
    
        <!-- 주문 목록 -->
        <div>
            <table class="MN_ORDER_BOOK">
                <thead>
                    <tr>
                        <th class="th th-head01"> 메뉴명 </th>
                        <th class="th th-head02"> 가격 </th>
                        <th class="th th-head03"> 수량 </th>
                        <th class="th th-head02"> 총합 </th>
                        <th class="th th-head03"> </th>
                    </tr>
                </thead>
                <tbody>
                    <!-- qty값이 음수일 때 방지 필요 -->
                    <tr :key=orderIdx v-for="(orderItem, orderIdx) in currentOrderList">
                        <td class="item-font"> {{orderItem.menuName}} </td>
                        <td class="item-font"> {{orderItem.price.toLocaleString()}} 원</td>
                        <td><input type="number" style="width:30px;"
                            v-model.number="orderItem.qty" 
                            @change="changeOrder(orderIdx)">
                        </td>
                        <td class="item-font"> {{orderItem.total.toLocaleString()}} 원 </td>
                        <td> <button @click="deleteOrder(orderIdx)"> X </button> </td>
                    </tr>
                </tbody>
            </table>
        </div>
        <!-- 할인 전 총 금액 -->
        <div class="result">
            <p class="big-white">할인 전 금액 : {{totalAmount.toLocaleString()}} 원</p>
        </div>

        <!-- 할인 쿠폰, 카드 선택 -->
        <div class="MENU_BOARD">
            <p class="big-white">쿠폰 선택 및 카드 선택 </p>
            <div>
                <select v-model="selectedCouponId"
                        style="width:200px;"
                        @change="payment()">
                    <option v-bind:value=0>
                        {{ paddingBothSide("할인쿠폰",21,"=") }}
                    </option>
                    <!-- v-bind:value 에 함수를 집어넣어 호출마다 1, 2, 3 ... n 을 반환할 때 왜 coupons의 크기만큼 호출하지 않는가? 
                        너무 많은 횟수를 호출함 -->
                    <option v-bind:key=cpIdx+1
                            v-bind:value=cpIdx+1
                            v-for="(cpItem, cpIdx) in coupons">
                            
                            {{cpItem.title}}

                    </option>
                </select>
            </div>
        



        <div v-bind:key=ctIdx v-for="(ctItem, ctIdx) in cardTypes">
            <select v-model="selectedCardIdList[ctIdx]" 
                    @change="payment()" 
                    style="width:200px;">
                <option v-bind:value=0> {{paddingBothSide(ctItem.title,21,"=")}} </option>
                <option v-bind:key=fctIdx+1
                        v-bind:value=fctIdx+1
                        v-for="(fctItem, fctIdx) in getFilteredCards(ctItem.cardType)">
                            {{fctItem.cardName}}
                </option>
            </select>

            <!-- creditCards -> discount, discountType -->
        </div>
        </div>
        <!-- 최종 금액 -->
        <div class="result">
            <p class="big-white"> 최종 결제액 : {{discounted.toLocaleString()}} 원</p>
        </div>
    </div>
</template>

<script>
export default {
    data(){
        return {
            //상품 정보
            menus : [{
                menuId: 1,
                menuName: "무제한 샐러드바",
                price: 25000,
            },
            {
                menuId: 2,
                menuName: "안심 스테이크(150g)",
                price: 35500,
            },
            {
                menuId: 3,
                menuName: "립아이 스테이크(220g)",
                price: 22500,
            },
            {
                menuId: 4,
                menuName: "채끝 등심 스테이크(210g)",
                price: 30500,
            },
            {
                menuId: 5,
                menuName: "자몽에이드",
                price: 6500,
            },
            {
                menuId: 6,
                menuName: "애플망고에이드",
                price: 6500,
            },
            {
                menuId: 7,
                menuName: "생맥주",
                price: 400,
            },
            ],
            //할인 종류
            cardTypes : [{
                cardType: "CREDIT",
                title: "신용카드"
            },
            {
                cardType: "TELECOM",
                title: "통신사"
            },
            {
                cardType: "OKCASHBAG",
                title: "OK캐시백"
            },
            {
                cardType: "POINT",
                title: "포인트결제"
            }
            ],
            //할인 카드/통신사/포인트/OK캐시백
            creditCards : [{
                cardId: 1,
                cardType: "CREDIT",
                cardName: "CJ ONE 삼성카드",
                discount: 30,
                discountType: "%"
            },
            {
                cardId: 2,
                cardType: "CREDIT",
                cardName: "CJ ONE 신한카드",
                discount: 30,
                discountType: "%"
            },
            {
                cardId: 3,
                cardType: "CREDIT",
                cardName: "The CJ 카드",
                discount: 22,
                discountType: "%"
            },
            {
                cardId: 4,
                cardType: "CREDIT",
                cardName: "삼성 6 V4카드",
                discount: 20,
                discountType: "%"
            },
            {
                cardId: 5,
                cardType: "CREDIT",
                cardName: "신한 Lady카드",
                discount: 20,
                discountType: "%"
            },
            {
                cardId: 6,
                cardType: "CREDIT",
                cardName: "삼성 SFC",
                discount: 20,
                discountType: "%"
            },
            {
                cardId: 7,
                cardType: "CREDIT",
                cardName: "삼성 S클라스",
                discount: 20,
                discountType: "%"
            },
            {
                cardId: 8,
                cardType: "CREDIT",
                cardName: "하나 Yes OK Saver",
                discount: 20,
                discountType: "%"
            },
            {
                cardId: 9,
                cardType: "CREDIT",
                cardName: "홈플러스 하나줄리엣카드",
                discount: 20,
                discountType: "%"
            },
            {
                cardId: 10,
                cardType: "CREDIT",
                cardName: "하나 줄리엣카드 & Yes 4u shopping",
                discount: 20,
                discountType: "%"
            },
            {
                cardId: 11,
                cardType: "CREDIT",
                cardName: "KB Star",
                discount: 20,
                discountType: "%"
            },
            {
                cardId: 12,
                cardType: "CREDIT",
                cardName: "이마트 KB카드",
                discount: 15,
                discountType: "%"
            },
            {
                cardId: 13,
                cardType: "TELECOM",
                cardName: "KT 멤버십 일반 할인",
                discount: 5,
                discountType: "%"
            },
            {
                cardId: 14,
                cardType: "TELECOM",
                cardName: "KT 멤버십 VIP 할인",
                discount: 15,
                discountType: "%"
            },
            {
                cardId: 15,
                cardType: "TELECOM",
                cardName: "T 멤버십 실버 할인",
                discount: 5,
                discountType: "%"
            },
            {
                cardId: 16,
                cardType: "TELECOM",
                cardName: "T 멤버십 VIP/골드 할인",
                discount: 15,
                discountType: "%"
            },
            {
                cardId: 17,
                cardType: "OKCASHBAG",
                cardName: "OK캐시백",
                discount: 30,
                discountType: "%"
            },
            {
                cardId: 18,
                cardType: "POINT",
                cardName: "BC Top 포인트",
                discount: 100,
                discountType: "%"
            },
            {
                cardId: 19,
                cardType: "POINT",
                cardName: "기아멤버스 카드",
                discount: 20,
                discountType: "%"
            },
            {
                cardId: 20,
                cardType: "POINT",
                cardName: "삼성카드 포인트",
                discount: 100,
                discountType: "%"
            },
            {
                cardId: 21,
                cardType: "POINT",
                cardName: "현대카드 M",
                discount: 20,
                discountType: "%"
            },
            {
                cardId: 22,
                cardType: "POINT",
                cardName: "신한 Hi-Point 카드",
                discount: 20,
                discountType: "%"
            },
            {
                cardId: 23,
                cardType: "POINT",
                cardName: "블루멤버스 카드",
                discount: 20,
                discountType: "%"
            }
            ],
            //할인 쿠폰
            coupons : [{
                couponId: 1,
                title: "5% 할인쿠폰(중복할인 가능)",
                discount: 5,
                doubleDiscount: true,
                discountType: "%"
                },
                {
                couponId: 2,
                title: "10% 할인쿠폰(중복할인 가능)",
                discount: 10,
                doubleDiscount: true,
                discountType: "%"
                },
                {
                couponId: 3,
                title: "15% 할인쿠폰(중복할인 가능)",
                discount: 15,
                doubleDiscount: true,
                discountType: "%"
                },
                {
                couponId: 4,
                title: "5000 할인쿠폰(중복할인 가능)",
                discount: 5000,
                doubleDiscount: true,
                discountType: ""
                },
                {
                couponId: 5,
                title: "10,000 할인쿠폰(중복할인 가능)",
                discount: 10000,
                doubleDiscount: true,
                discountType: ""
                },
                {
                couponId: 6,
                title: "20,000 할인쿠폰(중복할인 가능)",
                discount: 20000,
                doubleDiscount: true,
                discountType: ""
                },
                {
                couponId: 7,
                title: "5% 할인쿠폰(중복할인 불가능)",
                discount: 5,
                doubleDiscount: false,
                discountType: "%"
                },
                {
                couponId: 8,
                title: "10% 할인쿠폰(중복할인 불가능)",
                discount: 10,
                doubleDiscount: false,
                discountType: "%"
                },
                {
                couponId: 9,
                title: "15% 할인쿠폰(중복할인 불가능)",
                discount: 15,
                doubleDiscount: false,
                discountType: "%"
                },
                {
                couponId: 10,
                title: "5000 할인쿠폰(중복할인 불가능)",
                discount: 5000,
                doubleDiscount: false,
                discountType: ""
                },
                {
                couponId: 11,
                title: "10,000 할인쿠폰(중복할인 불가능)",
                discount: 10000,
                doubleDiscount: false,
                discountType: ""
                },
                {
                couponId: 12,
                title: "20,000 할인쿠폰(중복할인 불가능)",
                discount: 20000,
                doubleDiscount: false,
                discountType: ""
                }
            ],


            // 현재 주문한 상품 정보 
            // menuid 를 인덱스로 값들을 가지는걸로
            currentOrderList : {},

            selectedCardIdList : Array.from({length: 4}, () => 0),
            selectedCouponId : 0,

            totalAmount : 0,
            discounted : 0,
        };
    },
    watch: {
        currentOrderList : {
            deep:true,
            
            handler(){
                let result = 0;
                const tempList = this.currentOrderList

                if (Object.keys(tempList).length > 0){
                    
                    for (var idx in tempList){
                        result += tempList[idx].total
                    }
                    this.totalAmount = result
                }
                else {
                    this.totalAmount = 0
                }
            }
        },

        totalAmount : {
            handler(){
                this.payment()
            }
        }
    },
    methods : {
        sendOrder(menuId){
            const selectedItem = this.menus.filter(item => item.menuId == menuId)[0];
            const name = selectedItem.menuName;
            const price = selectedItem.price;

            const isKeyExist = Object.keys(this.currentOrderList).includes(`${menuId}`)
            
            if(isKeyExist){
                this.currentOrderList[`${menuId}`].qty += 1;
                this.currentOrderList[`${menuId}`].total = price * this.currentOrderList[`${menuId}`].qty;
            }

            else{
                this.currentOrderList[menuId] = {
                    menuName : name,
                    price : price,
                    qty : 1,
                    total : price,
                };
            }
        },
        
        deleteOrder(orderId){
            delete this.currentOrderList[orderId];
        },

        changeOrder(orderId){
            // 딥카피 안되있는 변수
            var oldItem = this.currentOrderList[orderId]
            this.payment()

            if (oldItem.qty <= 0){
                this.deleteOrder(orderId)
            }else{
                this.currentOrderList[orderId].total = oldItem.price * oldItem.qty
            }
            
        },
        
        // 할인율, 할인 타입, 할인 전 금액을 받아 최종 할인된 금액을 반환함
        // getDiscountValue
        getDiscountPrice(discount, discountType, oldPrice){
            
            let result = 0;

            switch(discountType){
                case '%':
                    result = oldPrice * (1 - (discount * 0.01))
                    return (result) > 0 ? result : 0;
                case '':
                    result = oldPrice - discount;
                    return (result) > 0 ? result : 0;
                default:
                    console.log("discountType is not '%' or ''")
                    return -1
            }
        },

        payment(){
            
            var oldAmount = this.totalAmount;
            var Discount_Results = [];
                                    
            // 선 쿠폰 후 카드 계산
            // 쿠폰 정보 부터 가져온다.

            if(this.selectedCouponId > 0){
                var couponItem = this.coupons.filter(item => item.couponId == this.selectedCouponId)[0]
                var total_coupon_discount = this.getDiscountPrice(couponItem.discount, couponItem.discountType, oldAmount)
            
                // 쿠폰이 중복할인이 가능한지 확인한다.
                // 가능하면 전체금액 감소 이후 카드 계산
                if(couponItem.doubleDiscount){
                    oldAmount = total_coupon_discount;
                    console.log("double discounted", oldAmount, total_coupon_discount)
                }else{
                    // 중복할인 불가하면 카드별 최대 할인 비교해야하므로 배열에 푸쉬
                    // 쿠폰명, 할인금액(할인전금액 - 할인액) 푸쉬
                    Discount_Results.push(oldAmount - total_coupon_discount)
                }
            }


            // 카드 정보를 가져온다.
            // 이전 카드 할인율이 현재 카드 할인율보다 높으면 가장 좋은 카드 Value 갱신

            
            var g_idx = 0;
            var idx = 0;

            for(var credit of this.cardTypes){
                var cards = this.creditCards.filter(i => i.cardType == credit.cardType)
                var cardId = this.selectedCardIdList[idx]

                idx += 1;

                if(cardId != 0){
                    var items = cards.filter(i => i.cardId == (g_idx + cardId))[0]
                    var total_credit_discount = this.getDiscountPrice(items.discount, items.discountType, oldAmount)

                    console.table(items)
                    Discount_Results.push(oldAmount - total_credit_discount)     
                }

                g_idx += cards.length;
            }

            
            
            // 쿠폰 중복 할인 불가라면 가장 할인율이 높은 결과로 리턴, 어떻게 줄이지
            var max = 0;

            for(var item of Discount_Results){
                if(item > max){
                    max = item
                }
            }
            
            this.discounted = Math.floor((oldAmount - max) / 10) * 10
        },

        getFilteredCards(cardType){
            return this.creditCards.filter(item => item.cardType == cardType)
        },

        paddingBothSide(originText, maxLength=21, fillString=""){
            const padCount = (maxLength - originText.length) / 2
            const padString = `${fillString.repeat(padCount)}`

            return `${padString}${originText}${padString}`
        }
    },

}
</script>

<style scoped>

div{
    padding: 1px;
    margin:10px;
    border-radius: 15px;
}

.th{
    font-size: 17px;
    font-weight: bold;
    color: rgb(109, 71, 0);
    border-bottom: 2px dashed;
}

.th-head01{
    width: 150px;
}
.th-head02{
    width: 100px;
}
.th-head03{
    width: 40px;
}


.item-font{
    color:rgb(66, 27, 0);
    font-size: 12px;
    font-weight: bold;
}
.btn{
    margin:10px;
    padding: 5px;
    width: 160px;
    height:100px;
    background: rgb(29, 28, 28);
    font-size: 12px;
    font-weight: bold;
    border-radius: 10px;
    border-color: rgb(78, 105, 14);
}
.MN_ORDER_BOOK{
    background: rgb(255, 203, 107);
    width: 580px;
    border:2px solid;
    border-color: #dcff12;
    border-radius: 10px;
}

.big-white{
    font-size: 25px;
    font-weight: bold;
    color:rgb(255, 255, 255);
}
.result{
    border: 3px;
    border-color: azure;
    width: 580px;
    height: 80px;
    background-color: rgb(59, 59, 59);
    
}
.MENU_BOARD{
   background: rgb(194, 194, 194); 
}
.container{
    border: 2px solid;
    width: 600px;
    height:1040px;
    background: rgb(37, 25, 25);
}

</style>

Vue 프레임워크를 이용하여 구현하였으며, 아래는 실행화면임

 

 

 

본 자료는 https://www.youtube.com/watch?v=5W72UHb-9iI 의 영상자료를 참고하였습니다.

'Programming' 카테고리의 다른 글

[Back-End] REST, REST API, RESTful API 란?  (1) 2021.12.15
[Ethereum] 210726 학습일지  (0) 2021.07.26
[JS] 210629 학습일지  (0) 2021.06.29
[HTML, CSS, JS] 210623 학습일지  (0) 2021.06.23
[HTML, CSS] 210621 학습일지  (0) 2021.06.22

댓글