D+75::shop_장바구니 장바구니등록쿼리수정(Merge,selectKey),수량변경(Modal,Ajax,이중서브쿼리),수량변경//파이썬_리스트함수,for문(반복문)

Am.Vinch·2022년 10월 14일
0

20221014_fri

실습내용

    1. 장바구니 등록 쿼리 수정(merge into 사용)
    1. 변경버튼 클릭시 상품수량 변경 ->ajax 사용 (java 상관없음)
      : 정말 변경하시겠습니까?(modal창띄우기) -> 확인 클릭시, 변경했습니다.(modal)
    1. '선택삭제'
      : 체크된 상품이 있는지 검사 -> 삭제(modal)
      : 정말 삭제하시겠습니까?(modal창띄우기) -> 확인 클릭시, 삭제했습니다.(modal)
    1. 상품 상세페이지에서 수량변경시 총 가격도 자동계산 만들기 (자바스크립트 이용)

👀✔장바구니 등록 및 장바구니 상품수량 변경(totalPrice,finalPrice 자동변경)

controller

package Kh.study.shop.cart.controller;

@Controller
@RequestMapping("/cart")
public class CartController {
	@Resource(name = "cartService")
	private CartService cartService;
	
	
	@ResponseBody //ajax사용 하니까
	@PostMapping("/regCart")
	public void regCart(CartVO cartVO ,Authentication authentication) {
		User user = (User)authentication.getPrincipal();
		cartVO.setMemberId(user.getUsername());
		cartService.regCart(cartVO);
		
	}
	
	// ajax에서 if문으로 result값이 true일때 이동하는 페이지 경로
	@GetMapping("/cartList")
	public String cartList(Model model,Authentication authentication) {
		// memberId 값 가져와서 cartList에 같이 던져주기 
		User user = (User)authentication.getPrincipal();
		model.addAttribute("cartList",cartService.selectCartList(user.getUsername())) ;
		
		
		// 전체 총 가격 데이터 (리스트에서 뽑아서 누적합으로 데이터 던져주기)
		int finalPrice = 0;
		for(CartVO e : cartService.selectCartList(user.getUsername())) {
			finalPrice = finalPrice + e.getTotalPrice();
		}
		model.addAttribute("finalPrice",finalPrice);
		
		return "content/cart/cart_list";
	}
	
	//장바구니 상품 수량변경
	@ResponseBody
	@PostMapping("/updateAmount")
	public void updateAmount(CartVO cartVO) {
		cartService.updateAmount(cartVO);
		
	}
}

mapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--해당 파일에 모든 쿼리문을 작성 -->
<mapper namespace="cartMapper">
 		<resultMap type="Kh.study.shop.cart.vo.CartVO" id="cart">
		<id column="CART_CODE" 	property="cartCode"/>
		<result column="ITEM_CODE" property="itemCode"/>
		<result column="MEMBER_ID" property="memberId"/>
		<result column="CART_AMOUNT" property="cartAmount"/>
		<result column="REG_DATE" property="regDate"/>
		<result column="TOTAL_PRICE" property="totalPrice"/>
		<!-- 내가 만든 ver. 그냥 변수추가하기  -->
		<result column="CATE_NAME" property="cateName"/>
		<result column="ITEM_NAME" property="itemName"/>
		<result column="ITEM_PRICE" property="itemPrice"/>
		<result column="ATTACHED_NAME" property="attachedName"/>
		
		<!-- 선생님 ver. 단, cartVO에 itemVO 추가하기  -->
		<association property="itemVO" resultMap="itemMapper.item"></association>
		</resultMap>
	
	
	
	<resultMap type="Kh.study.shop.item.vo.CategoryVO" id="category">
		<id column="CATE_CODE"  	 property="cateCode"/>
		<result column="CATE_NAME"   property="cateName"/>
		<result column="CATE_STATUS" property="cateStatus"/>
	</resultMap>
	<resultMap type="Kh.study.shop.item.vo.ItemVO" id="item">
		<id column="ITEM_CODE" 	property="itemCode"/>
		<result column="ITEM_NAME" property="itemName"/>
		<result column="ITEM_PRICE" property="itemPrice"/>
		<result column="ITEM_COMMENT" property="itemComment"/>
		<result column="REG_DATE" property="regDate"/>
		<result column="ITEM_STOCK" property="itemStock"/>
		<result column="ITEM_STATUS" property="itemStatus"/>
		<result column="CATE_CODE" 	property="cateCode"/>
		 <association property="categoryVO" resultMap="adminMapper.category" /> 
		<collection property="imgList" resultMap="img"/>
	</resultMap>
	  <resultMap type="Kh.study.shop.item.vo.ImgVO" id="img">
		<id column="IMG_CODE" property="imgCode"/>
		<result column="ORIGIN_NAME" property="originName"/>
		<result column="ATTACHED_NAME" property="attachedName"/>
		<result column="IS_MAIN" property="isMain"/>
		<result column="ITEM_CODE" property="itemCode"/>
	</resultMap>  
	
<!--  장바구니 목록조회 -->
<!-- 장바구니를 reg_date를 조회할 때 주의할 점: 반드시 장바구니에 등록한 날짜를 기준으로 해야한다. -->
<!-- 장바구니등록된 날짜 : C.REG_DATE -->	
<select id="selectCartList" resultMap="cart">
	SELECT CART_CODE
            ,IT.CATE_CODE
            ,CATE_NAME
			,C.ITEM_CODE
			,ITEM_NAME
			,MEMBER_ID
			,CART_AMOUNT
			,TO_CHAR(C.REG_DATE,'YYYY-MM-DD') REG_DATE
			,ITEM_PRICE
			,TOTAL_PRICE
			,ATTACHED_NAME
	FROM SHOP_CART C,SHOP_ITEM IT,ITEM_IMG IM,ITEM_CATECGORY CT
	WHERE C.ITEM_CODE = IT.ITEM_CODE
    AND C.ITEM_CODE = IM.ITEM_CODE
    and IT.CATE_CODE = CT.CATE_CODE
	AND IS_MAIN = 'Y'
	AND MEMBER_ID = #{memberId}
  	ORDER BY CART_CODE DESC
</select>


<!-- 장바구니 등록 (같은 상품이어도 별도로 등록됨)-->
<!-- totalPrice : 단가 * 수량 = 단가 * #{cartAmount} -->
<!-- 단가 : 서브쿼리로 구한다. -->
<!-- <insert id="regCart">
	<selectKey resultType="String" keyProperty="cartCode" order="BEFORE">
		SELECT 'CART_' ||LPAD(NVL(MAX(TO_NUMBER(SUBSTR(CART_CODE,6))),0)+1,3,0)
 		FROM SHOP_CART
	</selectKey>
	INSERT INTO SHOP_CART
		(   CART_CODE
			,ITEM_CODE
			,MEMBER_ID
			,CART_AMOUNT
			,TOTAL_PRICE
		) VALUES (
			#{cartCode},#{itemCode},#{memberId},#{cartAmount}
			, (SELECT ITEM_PRICE FROM SHOP_ITEM WHERE ITEM_CODE = #{itemCode}) * #{cartAmount}
			)
</insert> -->


<!-- 장바구니 등록쿼리 Merge 사용(같은상품끼리 자동 업데이트 묶어서 등록) -->
<insert id="regCart">
	<selectKey resultType="String" keyProperty="cartCode" order="BEFORE">
		SELECT 'CART_'|| LPAD(NVL(MAX(TO_NUMBER(SUBSTR(CART_CODE, 6))), 0) +1, 3, 0) FROM SHOP_CART
	</selectKey>
	MERGE INTO SHOP_CART
	   USING DUAL
	   ON(ITEM_CODE = #{itemCode} AND MEMBER_ID = #{memberId})
	
	   WHEN MATCHED THEN
	   UPDATE 
	   SET
	      CART_AMOUNT = CART_AMOUNT + #{cartAmount}
	     , TOTAL_PRICE = (CART_AMOUNT + #{cartAmount}) * 
	        			 (SELECT ITEM_PRICE FROM SHOP_ITEM WHERE ITEM_CODE = #{itemCode})
	   WHERE MEMBER_ID = #{memberId}
	   AND ITEM_CODE = #{itemCode}    
	
	   WHEN NOT MATCHED THEN
	
	   INSERT(   CART_CODE
	         , ITEM_CODE
	         , MEMBER_ID
	         , CART_AMOUNT
	         , TOTAL_PRICE
	      )VALUES(
	           #{cartCode}
	         , #{itemCode}
	         , #{memberId}
	         , #{cartAmount}
	         , (SELECT ITEM_PRICE FROM SHOP_ITEM 
	            WHERE ITEM_CODE = #{itemCode}) * #{cartAmount}
	      )
</insert>


<!-- 재고 상품 수량 변경 -->
<!-- 수량이 바뀌면 총가격도 바뀌어야한다! 주의하기  -->
<!-- cat_code 값을 알면 item_Cdoe도 알수 있다! -> 이 점을 이용해서 이중서브쿼리문! -->
<update id="updateAmount">
	UPDATE SHOP_CART 
	SET 
	CART_AMOUNT = #{cartAmount}
	, TOTAL_PRICE = ( SELECT ITEM_PRICE 
					  FROM SHOP_ITEM 
	                  WHERE ITEM_CODE = 
						                 (
							                 SELECT ITEM_CODE
											 FROM SHOP_CART 
							                 WHERE CART_CODE = #{cartCode}
						                 )
	                 
	                 )
	                 * #{cartAmount}
	WHERE CART_CODE = #{cartCode}
</update>  

</mapper>   

service

  package Kh.study.shop.cart.service;
public interface CartService {
	//장바구니 조회
	List<CartVO> selectCartList(String memberId);
	//장바구니 등록
	void regCart(CartVO cartVO);
	//상품수량 변경
	void updateAmount(CartVO cartVO);
}

serviceImpl

  package Kh.study.shop.cart.service;

@Service("cartService")
public class CartServiceImpl implements CartService {
	@Autowired
	private SqlSessionTemplate sqlSession;

	@Override
	public List<CartVO> selectCartList(String memberId) {
		return sqlSession.selectList("cartMapper.selectCartList",memberId);
	}

	@Override
	public void regCart(CartVO cartVO) {
		sqlSession.insert("cartMapper.regCart",cartVO);
	}
	// 장바구니 상품 수량 변경
	@Override
	public void updateAmount(CartVO cartVO) {
		sqlSession.insert("cartMapper.updateAmount",cartVO);
	}
}

js

  // [변수 선언] ------------------------------------------------------------------------//
//제목 행의 체크박스의 체크여부를 확인
const allChk = document.querySelector('#allChk');
//제목줄 제외 모든 체크박스
const chks = document.querySelectorAll('.chk');
//모달창(수량변경)
const updateAmountModal = new bootstrap.Modal('#updateAmountModal');




////////////////////////////// 이벤트 정의 영역 ///////////////////////////////////////////
//[이벤트 1] 체크박스 전체 선택
allChk.addEventListener('click',function(){// allchk 값은 하나만 있기 때문 이벤트 가능!

	const isChecked = allChk.checked;//체크된 제목줄 체크박스를 isChecked라고 한다.

	const chk = document.querySelectorAll('.chk');// 테이블의 체크된 체크박스 
	
	//제목 줄의 체크박스가 체크 됐다면..
	if(isChecked){//체크해ㅏㄹ
		for(i = 0; i<chk.length; i++){
			chk[i].checked = true;//체크된 클래스가 chk의 i번째 값은 true로 준다.
			
			
		}
		
	}
	else{//체크해제해라
		for(i = 0; i<chk.length; i++){
			chk[i].checked = false;//체크된 클래스가 chk의 값은 true로 준다.
		}
	}
	
	//전체 체크된 가격 총가격 나타내기
	setFinalPrice();
	
});


// [이벤트 2] 체크박스 전체해제 
	// event for문 밖 변수선언
	const chk = document.querySelectorAll('.chk');
	
	
	
	for(const e of chk){
		e.addEventListener('click',e=>{
			const cnt = chk.length; //제목줄 밑의 전체 체크박스의 갯수는 cnt
			const checkedCnt = document.querySelectorAll('.chk:checked').length; //클래스가 chk 인 것들 중에서 체크된것만 들고오겠다.그것의 갯수.
		
		if(cnt == checkedCnt){// 체크해라
			document.querySelector('#allChk').checked = true;
		}
		
		else{//체크하지마라
			document.querySelector('#allChk').checked = false;
					
		}	
		
	//전체 체크된 가격 총가격 나타내기
	//setFinalPrice();	
	});	
}

// [이벤트 3 ]장바구니 목록에 있는 체크박스 클릭 시 진행
// !!! 값이 여러개에 이벤트 불가능! -> for문돌려서 하나씩 빼서 여러번 실시한다.
for(const chk of chks){
	chk.addEventListener('click',function(){//체크박스 클릭시 실행
			setFinalPrice();//체크된 가격 하나씩 빼서 총가격 누적합계산하겠다.
		});
}












/////////////////////////////////////   [함수 정의]  ///////////////////////////////////////////////////////////////////////////
//[함수 3] 총가격을 세팅하는 함수
function setFinalPrice(){
			//체크된 박스 태그 선택
			const checkedBoxes = document.querySelectorAll('.chk:checked');
			
			//체크된 체크박스의 totalPrice 값 선택
			let finalPrice = 0;
			for(const checkBox of checkedBoxes){ // parseInt : 자바스크립트_숫자를 문자로 변환
			    const totalPrice =  parseInt(checkBox.closest('tr').querySelector('.totalPriceDiv').dataset.totalPrice);
			   
			    // 만약 data-totalprice로 데이터를 가져온다면?(th: 타임리프 사용해야 변수값 들고온다)
			    finalPrice = finalPrice + totalPrice;
			}
			//선택된 전체 값 더하기 
			// 천단위 구분 기호 : .toLocaleString()
		    document.querySelector('#finalPriceTag').innerText = '₩'+ finalPrice.toLocaleString()  ;
		    
}

// [함수 4 ] 수량변경 버튼 클릭시 진행되는 함수
function goUpdateAmount(selectedTag){// selectedTag :this로 넘어오는 태그(수량값의 변경버튼 클릭한 태그 의미)
	updateAmountModal.show();//맨위에 선언한 변수 모달을 가져와 열어주도록 한다.        
	
	// 수량값과 카트코드값을 들고와야하기때문에 태그 선택해서 가져오기! 어떻게?
	// 클릭한 버튼의 data-cart-code, data-cart-amount 속성의 값을 수량을 변경하고자하는 데이터로 세팅
	// 내가 지금 클릭한 태그의 바로 옆에있는 input태그의 수량값 들고오자. -> this 사용하기

    // - 수량 (cartAmount)
    // --1) input태그 number값으로 수량 태그 선택시 :
	const cartAmount = selectedTag.closest('tr').querySelector('input[type="number"]').value;
	// --2) id값으로 수량 태그 선택시 : const cartAmountB = selectedTag.querySelector('#cartAmounts').value;

	// - 카트코드 (cartCode): 체크박스에다가 카트코드 부여하기 value값으로
	const cartCode = selectedTag.closest('tr').querySelector('input[type="checkbox"]').value;

	// - 변경된 수량 태그선택
	//const itemPrice = document.querySelector('#itemPriceTag').value;
	//alert(itemPrice);
	
	// data- 속성값에 위에서 선택한 태그값들을 바로 넣어준다. 
	document.querySelector('#updateAmountBtn').dataset.cartCode = cartCode;
	document.querySelector('#updateAmountBtn').dataset.cartAmount = cartAmount;
	//document.querySelector('#updateAmountBtn').dataset.itemPrice = cartAmount;
	
}

// [함수 5] 수량버튼 클릭 후, 나타나는 모달창의 확인버튼 클릭시, 진행되는 함수 
function updateAmount(){// 여기 괄호안 매개변수는 html연결된 데이터! 
	const cartCode   = document.querySelector('#updateAmountBtn').dataset.cartCode;
	const cartAmount = document.querySelector('#updateAmountBtn').dataset.cartAmount;
	//const itemPrice = document.querySelector('#updateAmountBtn').dataset.itemPrice;

	$.ajax({
         url: '/cart/updateAmount', //요청경로
         type: 'post',
		// 여기 데이터는 controller에서 던져받는 데이터!
         data: {'cartCode':cartCode 
         		,'cartAmount':cartAmount}, //필요한 데이터
         success: function(result) {
        	alert('성공');
        	 //주의!! 모달창은 알아서 닫히지않는다. 페이지이동을 하지않은 이상! 때문에 코드를 직접 써서 닫아줘야한다.
        	 updateAmountModal.hide();
        	 //모달창이 닫히자마자 input태그 입력한 상품수량값으로 옆에 수량도 바뀌어야 한다. 
        	 
         },
         error: function() {
            alert('실패');
         }
      });
}

html

  <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout" 
	layout:decorate="~{layout/base_layout}"
	xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
	
<div layout:fragment="content">
	<!-- 제목줄 -->
	<div class="row mb-5">
		<div class="col">
		 <h1>CART LIST</h1>
		 </div>
	</div>
	
	<!-- 장바구니 목록 테이블  -->
	<div class="row ">
		<div class="col ">
			 <table style="width: 100%;" class="table table-striped table-hover">
				 <colgroup>
					<col width="*%" >
					<col width="5%" >
					<col width="15%" >
					<col width="20%" >
					<col width="25%" >
					<col width="20%" >
					<col width="20%" >
				</colgroup> 
				<thead style="font-size: 20px; font-weight: bold; color: #425F57;">
					<tr >
						<td><input type="checkbox" id="allChk" checked="checked"></td>
						<td>No.</td>
						<td colspan="2" style="text-align: center;">상품 정보</td>
						<td>수량</td>
						<td>가격</td>
						<td>등록 일자</td>
					</tr>
				</thead>
				
				
				<tbody align="center" style="text-align: justify;">
	 			<th:block th:if="${#lists.size(cartList) == 0}">
			 		<div> 장바구니에 등록된 상품이 없습니다.</div>
			   	</th:block> 
			   	
			   	  <th:block th:unless="${#lists.size(cartList) == 0}">
						<th:block th:each="cart : ${cartList}" > 
							<tr> <!-- 만약, td태그 밖에서 데이터 화면에 나타나게한다면? -> [[${cart.cateName}]] -->
								<td><input  th:value="${cart.cartCode}"  type="checkbox" class="chk"  checked="checked"></td>
								<!-- 역순으로 순서 뽑는 방법 : 전체갯수 - index -->
								<!-- 순서(No) 뽑는방법 : [[${cartStat.index}]] 이용 -->
								<!-- 차례대로 순서 뽑는방법 : <th:block th:each="cart , status : ${cartList}" >  <td th:text="${status.count}">  -->
								<td>
									<span th:text="${#lists.size(cartList) - cartStat.index}"></span> 
								</td>
								<td ><img width="100px;" height="140px;" th:src="|@{/images/}${cart.attachedName}|">   </td>
								<td > 
									[<span style="color: #749F82;" th:text=" ${cart.cateName}"></span>]
									<span th:text=" ${cart.itemName}" ></span> 
								</td>
								<td >
									<input id="cartAmounts" type="number" min="0" th:value="${cart.cartAmount}">   
									<!-- 내가 클릭한 변경버튼의 해당하는 그 상품수량 숫자값을 들고오기위해 this 사용! -->
									<button type="button" class="btn btn-light" th:onclick="goUpdateAmount(this);">변경</button>
								</td>
								<td><!-- (다른방법) [[${cart.itemVO.itemPrice}]]*[[${cart.cartAmount}]]  -->
								 <div class="row">
								 	<div class="col-6">
										 <div><span>[[${#numbers.formatCurrency(cart.itemPrice)}]]</span>*<span>[[${cart.cartAmount}]]</span>&nbsp;=</div><!-- 공백 표시 문자 기호 한칸띄어쓰기 -->
								 	</div>
								 	<div class="col-6">
										 <div class="totalPriceDiv" style="font-style: italic; font-weight: bold;"th:text="${#numbers.formatCurrency(cart.totalPrice)}" th:data-total-price="${cart.totalPrice}"> </div>	
								 	</div>
								 </div>
								</td>
								<td th:text="${cart.regDate}"> </td>
							</tr>
					 	</th:block>  
			   	</th:block> 				
				</tbody>
				
			</table>
		 </div>
	</div>
	
	
	<!-- 하단 영역 -->
		
		<!-- 총가격 -->
		<div class="row mt-5 mb-1" >
			<div class="col-3"></div><!-- 줄맞춤용도 -->
			<div class="col-3"></div><!-- 줄맞춤용도 -->
			<div class="col-3"></div><!-- 줄맞춤용도 -->
			<div class="col-3">
				 <div  class="input-group mb-3 " >
				  <span class="input-group-text" id="inputGroup-sizing-default">
					  총가격 
				  </span>
				  <div id="finalPriceTag" style="text-align: right;" th:data-total-price="${finalPrice}"   th:text=" ${#numbers.formatCurrency(finalPrice)}"  class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default"></div>
				</div>
			</div>
		</div>

		<!-- 삭제 및 구매 버튼 -->
		<div class="row mb-10" >
			 <div class="col" align="right">
				<form action="insertBuys.buy" method="post">
					<input type="hidden" name="totalPrice" id="totalPrice" value="">
					<input type="hidden" name="itemCode" id="itemCode" value="">
					<input type="hidden" name="cartAmount" id="cartAmount" value="">
					<input type="hidden" name="cartCode" id="cartCode" value="">
			
					<button onclick="goDelete();" class="btn btn-outline-danger">선택 삭제</button>
					<button onclick="goBuy();" class="btn btn-outline-warning" style="color: #FF9F29;">선택 구매</button>
				</form>
			</div> 
		</div>
		
		
		
<!-- 수량변경 modal 창  -->		
<div class="modal fade" id=updateAmountModal tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
     <div class="modal-dialog">
       <div class="modal-content">
         <div class="modal-body">
            <div class="row mb-3">
               <div class="col">
                  <div>상품 수량 변경을 하시겠습니까?</div>
               </div>
            </div>
            <div class="row">
               <div class="col text-end">
              		   <!-- th:onclick="|location.href='@{컨트롤러경로}'|" -->
              		   <!-- js에서 데이터 보내려면, 태그를 잘 선택해서 받아서 사용하기위해 data- 속성이용해서 보내기. -->
	                <button id="updateAmountBtn" th:onclick="updateAmount();"  data-cart-code=""  data-cart-amount=""   type="button" class="btn  btn-sm"  style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: 3rem; --bs-btn-font-size: 1rem;">
	               	 	확인
	               	 </button>
	                <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: 3rem; --bs-btn-font-size: 1rem;">
		                취소
	                </button>
               </div>
            </div>
         </div>
       </div>
     </div>
   </div>

<script src="https://code.jquery.com/jquery-latest.min.js"></script>	
<script type="text/javascript" th:src="@{/js/layout/cart_list.js}"></script>	
</div>
</html>

Ajax 사용시, 문제점

단, Ajax사용시 다음과 같은 문제점이 발생한다.
1. 수량 변경버튼클릭 후, 변경은 하지만 가격영역에서 수량과 총가격은 페이지이동없이 변하지 않는다. 새로고침을 해야 변경된 값을 확인할 수 있다.
2. 마지막 finalPrice 값도 변하지 않기때문에 자동계산될 수 있도록 만들어야한다.


결과

  • 로그인 후, 해당 아이디 회원의 장바구니 페이지 이동시 첫 화면

  • 여기서 수량을 새로 input태그에 입력 후, 변경 버튼 누르면 Modal창이 뜬다.

  • 모달창의 확인 버튼을 누르면 '성공'이라는 alert창이 뜨면서 데이터베이스 update완료되는것을 알 수 있다.

  • 취소버튼 클릭시, modal창이 닫히면서 이전 화면 그대로 나타난다.

  • 하지만, Ajax를 사용해서 페이지이동(새로고침)을 하지않는다면, 수량영역 옆 가격영역에 표시되는 단가 * 수량 = 총가격의 데이터값과 오른쪽 하단의 총가격 데이터들이 자동으로 변경되지 않는다! 그래서 이 또한 Ajax로 넘겨받아 자동으로 계산되어 변경되도록 만들어줘야한다...(다음시간..)

profile
Dev.Vinch

0개의 댓글