All'alba vincerò

At dawn, I will win!

Toy Projects

[Javascript] 장바구니 기능

나디아 Nadia 2024. 3. 12. 13:34

 


장바구니 기능

: 상품 목록을 보여주고 담기 버튼 클릭 시 장바구니에 담기며, 담은 상품의 합계와 영수증을 보여주는 페이지

 

 

 

 

 

사용 언어

  • HTML
  • CSS
  • Javascript

 


구조

 

  • index.html - 전체 구조
  • index.js - 기능 구현
  • main.css - 전체 디자인
  • store.json - 데이터 파일

 

 

 


 


코드

 

구현 계획

1. 레이아웃 구성하기 (html, css)
- 상품 카드 만들기

2. 상품 목록 데이터 끌어오기
- 데이터 갯수만큼 html 생성하기

3. 검색 기능
(1) 검색어 필터링
(2) 검색어가 없는 카드엔 hide 클래스 추가 / 있으면 제거

4. 상품 드래그 기능

5. 장바구니에 있는 상품 카드의 html 수정

- 수량 입력 칸 추가 (<input>)

 

7. 최종 가격 출력

8. 개인 정보 입력창
- 유효성 검사

9. 영수증 출력

- canvas 태그 사용

 

 

 

주요 기능

JSON 파일의 데이터 가져오기

- 데이터 개수만큼 html 카드 생성하기 

$.get('store.json')
.then((data)=>{

  products = data.products; // 원본 데이터 변수에 보관

  // json 데이터 개수만큼 상품 카드 html 생성하기
  data.products.forEach((a) => {

    let html = `
        <div class="container" id="dragMe" draggable="true" ondragstart="drag(event)">
            <div class="item" data-id="${a.id}">
            <div class="image">
                <img src="${a.photo}" alt="" style="width: 200px; height: 150px;">
            </div>
            <div class="top">
                <div class="title">${a.title}</div>
                <div class="company">${a.brand}</div>
            </div>
            <div class="price">가격: ${a.price}</div>
                <button data-id="${a.id}" class="cartBtn">Add to Cart</button>
            </div>
        </div> `;

        $('.card').append(html)
    });

 

 

 

검색어 기능

- include( ) 사용

document.getElementById('search').addEventListener('input', function() {

    let search = document.getElementById('search').value;

    for (let i = 0; i < products.length; i++) {
        if (products[i]['title'].includes(search) || products[i]['brand'].includes(search)) {
            document.getElementsByClassName('container')[i].classList.remove('hide')

        } else {
            document.getElementsByClassName('container')[i].classList.add('hide');
        }
    }
});

 

 

 

담기 버튼 누르면 장바구니에 담기

$(document).on('click', '.cartBtn', function(e) {
    // 현재 클릭된 버튼이 'cartBtn' 클래스를 가지고 있는지 확인
    if ($(e.target).hasClass('cartBtn')) {

        // 지금 누른 버튼의 번호 
        let productId = e.target.dataset.id;

        // 장바구니 'cart'에 지금 누른 버튼의 번호 == 현재 클릭된 상품의 ID가 같은 상품을 찾기
        let productFind = cart.findIndex((a)=>{ return a.id == productId })

        // 없으면 cart에 {새 상품}추가, 그 수량을 1로 설정
        if (productFind == -1) {
            let now = products.find((a)=> { return a.id == productId });
            now.count = 1;
            cart.push(now);
        } else { // 있으면 수량만 증가
            cart[productFind].count++;
        }

        // 담기 버튼 누를 때 마다 장바구니에 html 생성
        $('.dropBox').html('');
        cart.forEach((a, i)=>{
            $('.dropBox').append(`
                    <div class="container" id="dragMe" draggable="true" ondragstart="drag(event)">
                        <div class="item" data-id="${a.id}">
                        <div class="image">
                            <img src="${a.photo}" alt="" style="width: 200px; height: 150px;">
                        </div>
                        <div class="top">
                            <div class="title">${a.title}</div>
                            <div class="company">${a.brand}</div>
                        </div>
                        <div class="price">가격: ${a.price}</div>
                            <button data-id="${a.id}" class="cartBtn">Add to Cart</button>
                        </div>
                        <input type="number" value="${a.count}" class="item-count">
                    </div>
            `);
     });
});

 

 

 

정렬 버튼

- 가격순 정렬, 2만원 이하 정렬

// -----------------가격순 정렬-----------------
    document.getElementById('priceDown').addEventListener('click', function() {
    
        // 1. 가격순 정렬
        products.sort(function(a, b) {
            return b.price - a.price
        });
    
        // 2. 카드 div의 내용을 삭제하기
        document.querySelector('.card').innerHTML = '';
        
        // 3. 새로 정렬한 데이터 갯수만큼 카드 생성하기
        products.forEach((a, i) => {
            var a = `
            <div class="container" id="dragMe" draggable="true" ondragstart="drag(event)">
                <div class="item" data-id="${a.id}">
                <div class="image">
                    <img src="${a.photo}" alt="" style="width: 200px; height: 150px;">
                </div>
                <div class="top">
                    <div class="title">${a.title}</div>
                    <div class="company">${a.brand}</div>
                </div>
                <div class="price">가격: ${a.price}</div>
                    <button data-id="${a.id}" class="cartBtn">Add to Cart</button>
                </div>
            </div> `;
        
            document.querySelector('.card').insertAdjacentHTML('beforeend', a);
        });
    });

    // -----------------2만원 이하 정렬-----------------
    document.getElementById('down-2').addEventListener('click', function() {

        // 1. 가격이 2만원 이하 필터링
        var newProduct = products.filter(function(a) {
            return a.price <= 20000;
        })
    
        // 2. 카드 div의 내용을 삭제하기
        document.querySelector('.card').innerHTML = '';
    
        // 3. 새로 정렬한 데이터 갯수만큼 카드 생성하기
        newProduct.forEach((a, i) => {
            var a = `
            <div class="container" id="dragMe" draggable="true" ondragstart="drag(event)">
                <div class="item" data-id="${a.id}">
                <div class="image">
                    <img src="${a.photo}" alt="" style="width: 200px; height: 150px;">
                </div>
                <div class="top">
                    <div class="title">${a.title}</div>
                    <div class="company">${a.brand}</div>
                </div>
                <div class="price">가격: ${a.price}</div>
                    <button data-id="${a.id}" class="cartBtn">Add to Cart</button>
                </div>
            </div> `;
        
            document.querySelector('.card').insertAdjacentHTML('beforeend', a);
        });
    });

 

 

 

최종 가격

function priceCalc(){
    
    let finalPrice = 0; // 가격을 저장할 변수
    
    // 각 상품(수량 입력 필드)에 대해 가격과 수량을 곱하여 합산
    $('.item-count').each(function() {

        // 입력 필드의 부모 요소(container)를 찾은 후, 그 하위에 있는 "price" 클래스 요소의 텍스트를 가져옴(= 각 상품의 가격)
        var priceText = $(this).parent().find('.price').text();
        
        // "가격: " 뒤의 숫자만 추출해서 가격으로 사용
        // 문자열을 공백으로 분할, 두 번째 요소(인덱스 1)를 실수로 변환
        var price = parseFloat(priceText.split(' ')[1]);
        
        // 수량 입력 필드 값(개수)을 정수로 변환
        var count = parseInt($(this).val());

        // 최종 가격 계산 (가격 * 개수)
        finalPrice += price * count;
    });

    // 가격 3자리 마다 콤마(,) 찍는 함수
    let formattedPrice = finalPrice.toLocaleString();

    $('.final-price').html('합계: ' + formattedPrice + '원');
}

 

 

 

영수증 모달창

document.getElementById('send').addEventListener('click', function () {

    // 기존 모달 닫기
    document.querySelector('.modal1').classList.remove('show-modal');

    let name = document.querySelectorAll('.m-input')[0].value;
    let phone = document.querySelectorAll('.m-input')[1].value;
    
    // 개인 정보 유효성 검사
    // 이름
    if (name == '') {
        alert('이름을 입력하세요.');
        e.preventDefault()
    } else if (/[a-zA-Zㄱ-ㅎ0-9]/.test(name)) {
        alert('이름은 한글 입력만 가능합니다.');
        e.preventDefault()
    } 

    if (name.length < 2 || name.length > 5) {
        alert('이름 제한 길이를 넘었습니다.');
        e.preventDefault()
    }

    // 전화번호
    if (phone == '') {
        alert('전화번호를 입력하세요.');
        e.preventDefault()
    } else if ((!/^(01[016789]{1})-[0-9]{4}-[0-9]{4}$/.test(phone)) && (!/^(01[016789]{1})[0-9]{4}[0-9]{4}$/.test(phone))) {
        alert('전화번호 형식이 틀렸습니다.');
        e.preventDefault()
    }


    // 영수증 모달 열기
    document.getElementById('receiptModal').style.display = 'block';
    
    // 영수증 캔버스 생성 및 내용 그리기
    var canvas = document.createElement('canvas');
    canvas.id = 'receiptCanvas';
    canvas.width = 500;
    canvas.height = 800;
    var c = canvas.getContext('2d');

    c.font = '18px dotum'; 
    c.fillText('이름: ' + name, 170, 30);
    c.fillText('전화번호: ' + phone, 170, 60);

    // 구매 시간!!!!!!!!!!!!!!!!!!!!!!!

    // 장바구니에 저장된 각 상품 정보 출력
    let yPosition = 120; // 출력할 상품의 시작 위치

    cart.forEach((product) => {
        c.fillText('상품명: ' + product.title, 170, yPosition);
        c.fillText('가격: ' + product.price, 170, yPosition + 30);
        c.fillText('수량: ' + product.count, 170, yPosition + 60);
        c.fillText('합계: ' + (product.price * product.count), 170, yPosition + 90);
        yPosition += 135; // 다음 상품 정보의 출력 위치 조정
    });

    // 총 합계 계산
    let totalPrice = cart.reduce((total, product) => total + (product.price * product.count), 0);
    c.fillText('총 합계: ' + totalPrice, 170, yPosition + 10);

    // 생성된 캔버스를 영수증 모달에 추가
    var modalContent = document.querySelector('#receiptModal .modal-content');
    modalContent.appendChild(canvas);
});

// 영수증 닫기 버튼
document.getElementById('closeReceipt').addEventListener('click', function () {
    document.getElementById('receiptModal').style.display = 'none';
});

 

 

 

 


잘한 점

 

끝까지 포기하지 않고 완성한 것..............

 

 

 



아쉬운 점

 

지금의 내게는 너무 어려운 과제였어서 아쉬운 점이 많다.

 

vanilla js로만 구현하고 싶었는데 jQuery를 사용하지 않으면 도저히 다음으로 넘어갈 수 없는 부분들이 있었고,

어쩔 수 없이 둘을 혼용해서 사용하였다.

 

드래그 이벤트를 제대로 구현하지 못했다.

상품을 드래그하면 무조건 1번 상품으로만 생성되는 치명적인 오류가 있다.

최대한 수정해보려 했지만 내 지식의 한계인지 도무지 원인을 알아낼 수 없어서 상품을 드래그해서 장바구니에 추가하는 기능은 차후에 수정하려고 한다.

 

 

 


 

깃허브

https://github.com/kwonboryong/Toy_Projects/tree/main/Cart

 




출처

코딩애플