D+67::SHOP_스크롤사용/상품재고수량변경 및 상태(매진/판매중)변경 기능구현

Am.Vinch·2022년 10월 4일
0

20220930_FRI

실습내용

  • 회원목록 테이블 제목줄 고정,내용테이블만 스크롤 만들기
  • 주의할점은 테이블을 두개를 만들어 제목줄과 내용줄을 나눠서 스크롤바는 내용줄만 사용하도록하면 된다.

실습내용

  • 홈페이지 첫 화면 상품목록페이지 구현
  • 디비에 저장한 등록한 상품을 상품목록 띄우기
  • (상품 이미지 뛰우고 첨부하기)
  • 상품관리 페이지 에서 상품목록 조회
    : (content/admin/item_manage.html)
  • 조회 컬럼 : No,상품카테고리(단,name값으로 나오도록 구현),상품명,가격.재고,등록일
    : 상태는 판매중 / 매진 두개로 라디오버튼으로 구현
    예시) no / 카테고리 / 상품명 / 가격 / 재고 / 등록일 / 상태
    1 / 상의 / 자켓 / 100/ 10 / 22.8.9 / 판매중
  • 재고버튼 수정,판매상태 여부 수정하기 -> ajax사용하여 변경하기

CSS 크기의 단위

reg,em, px , %

<참고>https://esef.tistory.com/117

  • 1rem : rem단위는 문서의 기본값의 배수
  • 1em : em단위는 em단위가 있는 곳의 폰트사이즈의 배수
  • px: px은 우리가 알고 있는 픽셀값
  • % : %는 사용자가 보이는 화면에서 차지하는 비중

SHOP 프로젝트

상품관리페이지

  • 재고변경 기능 구현
  • 상태변경 기능 구현
    • -> 재고나 상태를 변경시 이 후 처리할 다른 일이 없기때문에 ajax에서 success구문에 할일이 없기 때문에 ajax를 사용하는 것이 편이하다.
  • 위 기능을 구현하는데, 페이지이동하는 방법과 페이지이동없는 ajax를 이용하는 방법 총 두가지의 방법이 있다.
    • 단, ajax를 실행시 자바스크립트에서 코드실행이 된다. 필요한 컨트롤러 쿼리를 위해 매퍼와 서비스에서 기능을 구현한다. 서세스구문에서 무엇을 해야할지를 생각해야한다. 서세스구문에서 할일이 많다면 그냥 페이지이동하여 기능을 구현하는 것이 후처리가 더 쉬울 수 있다.
    • 그래서 후 처리를 먼저 생각하고 페이지이동을 어떻게 할 지를 생각해야한다.

재고수량 변경 및 상품 판매상태 변경 기능 구현

기능 구현 순서

    1. 상품관리 html에서 onclick으로 js에서 실행할 함수로 이동하기 만들기
    1. html과 유사한 파일명으로 js만들어서 함수만들기
    1. 컨트롤러가서 쿼리실행하도록 데이터 던져주기
    1. 쿼리실행할 매퍼,서비스(임플) 만들기
  • 5.js에서 매개변수 받아서 사용하기.

html

  • item_manage.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout" 
	layout:decorate="~{layout/admin_layout}"
	xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
	
<div layout:fragment="content">
	 <div class="row">
	 	<div class="col">
			<h1>상품관리페이지</h1>
	 		<div class="row"><!-- 좌우측화면 담고있는 화면 -->
				<!-- 검색 -->
				<div class="col-12 mb-5">
				<div>
				<table class="table table-striped table-hover">
				  <tr>
				  	<td> 카테고리 </td>
				  	<td colspan="2"> 
					  	<select >
					  		<option th:each="cateList : ${cateListAll}" th:text="${cateList.cateName}"></option>
					  	</select> 
				  	</td>
				  	<td>상품명</td>
				  	<td><input type="text"> </td>
				  	<td>재고</td>
				  	<td><input type="number" > </td>
				  </tr>
				  
				  <tr>
				  	<td>등록일</td>
				  	<td><input type="date"></td> 
				  	<td><input type="date"></td>
				  	<td>상태</td>
				  	<td colspan="3">
					  	<input type="radio" value="전체" th:text="전체" checked="checked"> 
					  	<input type="radio" value="판매중" th:text="판매중"> 
					  	<input type="radio" value="매진" th:text="매진"> 
				  	</td>
				  </tr>
				  
				</table>
				</div>
				<div align="center">
				  	<button class="btn btn-outline-secondary" type="submit">검색</button> 
				</div>
				</div>	 		
				
				<!-- 목록테이블 제목줄 -->
				<div class="col-12 mb-5">
				<table class="table table-striped table-hover">
				  <colgroup>
				  	<col width="*%">
				  	<col width="10%">
				  	<col width="20%">
				  	<col width="10%">
				  	<col width="20%">
				  	<col width="20%">
				  	<col width="20%">
				  </colgroup>
				  <thead>
					  <tr>
					  	<th>No.</th>
					  	<th>카테고리</th>
					  	<th>상품명</th>
					  	<th>가격</th>
					  	<th>재고</th>
					  	<th>등록일</th>
					  	<th>상태</th>
					  </tr>
				  </thead>
				  <tbody>
					<th:block th:if="${#lists.size(regItemList) == 0}">
						<tr>
							<td colspan="7"> 조회된 목록이 없습니다.</td>
						</tr>
					</th:block>
					<th:block th:unless="${#lists.size(regItemList) == 0}">
						<th:block th:each="regItemInfo , status : ${regItemList}" >
							<tr>
								<td th:text="${status.count}"></td>
								<td th:text="${regItemInfo.cateName}"></td>
								<td th:text="${regItemInfo.itemName}"></td>
								<td th:text="${regItemInfo.itemPrice}"></td>
								<td> 
									<div class="input-group mb-3">
  										<input id="itemStocks" type="number" th:value="${regItemInfo.itemStock}"  class="form-control stockInput" 
  												aria-label="Recipient's username" aria-describedby="button-addon2">
									 	 <!-- 타임리프로 데이터 던질때 주의할 점: 대괄호 두개 달러 중괄호 사용하기 -->
									 	 <!--  단, 데이터를 던질때 value값 itemstock을 던지면 변경한 숫자가 넘어가는것이아니라 
									 	       원래 있던 변경 재고값이 넘어가는 것이기때문에 itemcode값을 던져야한다.-->
									 	 <!--  this 를 넘기면 내가 뭔가 액션을 취한 (변경버튼클릭했을 때의 그 값)이 넘어간다.-->
									 	 <button class="btn btn-outline-secondary" type="submit" id="button-addon2" 
									 	 			th:onclick="changeStock([[${regItemInfo.itemCode}]] ,this);" >변경</button>
									</div>
								</td>
								<td th:text="${regItemInfo.regDate}"></td>
								<!-- 라디오값-->
								<!-- 변수와 문자열을 동시에 사용하기위해서 -->
								<!-- 네임값에 버티컬바 두개를 넣으면 편히 넣어 데이터넘길수있다. -->
								
								<td id="itemStatusId">
									<div class="form-check form-check-inline">
				                       <input class="form-check-input " type="radio" th:name="|itemStatus_${status.count}|" 
				                       	id="onSale" value="ON_SALE" th:checked="${regItemInfo.itemStatus eq '판매중'}"
				                       	th:onclick="changeItemStatus([[${regItemInfo.itemCode}]], 'ON_SALE');" >       
				                       <label class="form-check-label" for="inlineRadio1">판매중</label>
				                     </div>
				                     <div class="form-check form-check-inline">
				                      <input class="form-check-input " type="radio" th:name="|itemStatus_${status.count}|" 
				                      	 id="soldOut" value="SOLD_OUT" th:checked="${regItemInfo.itemStatus eq '매진'}"
        	 				             th:onclick="changeItemStatus([[${regItemInfo.itemCode}]],'SOLD_OUT');" >       
				                       <label class="form-check-label" for="inlineRadio2">매진</label>
				                     </div>
								</td>
							</tr>
						</th:block>
					</th:block>
					</tbody>
				</table>
				</div>	 	
	 		</div>

	 	</div>
	 </div>
	 
	 <!-- 수량 변경 후 실행되는 modal창 -->
	<!-- Modal -->
	<div class="modal fade" id="updateStockModal" 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 class="row">
	       		<!-- text-end: 글자 우측정렬 btn-sm: 작은 버튼-->
	       		<div class="col text-end">
			         <button type="button" class="btn btn-primary  btn-sm" data-bs-dismiss="modal" 
			         		style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: 1.5rem; --bs-btn-font-size: 1rem;" >확인</button>
	       		</div>
	       </div>
	      </div>
	    </div>
	  </div>
	</div>
	<!-- modal -->
	
	 <!-- 상품상태 변경 후 실행되는 modal창 -->
	<!-- Modal -->
	<div class="modal fade" id="updateStatusModal" 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 class="row">
	       		<div class="col text-end">
			         <button type="button" class="btn btn-primary  btn-sm" data-bs-dismiss="modal" 
			         		style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: 1.5rem; --bs-btn-font-size: 1rem;" >확인</button>
	       		</div>
	       </div>
	      </div>
	    </div>
	  </div>
	</div>
	<!-- modal -->
	 
<!-- 반드시 해당되는 div 태그 안에 있어야 실행이 된다. -->	
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript" th:src="@{/js/layout/item_manage.js}"></script>	 
</div>

</html>

js

  • itme_manage.js
    • 태그선택 요소
  • this라는 액션을 취할 때 = 변경버튼클릭할 때 태그가 어딨는지 찾아가야한다.
    - 형제의 자식태그 밸류값을 빼온다
    -부모태그 찾아갈때 : parentElemnet
    - 이전형제태그 찾아갈때 : previousElemnetSibling
    - 다음형제 노드 : nextElemnetSibling
    - 감싸고 있는 태그 중 가장 가까운 상위태그 : closest()
    - 자식태그(모든) : children
    - 자식이 여러명있을 때 배열로 접근하여 자식태그를 선택해준다
  • 재고 수량 value값 선택하여 갖고오기
    : 태그를 찾아가서 변경될 수량 input태그 밸류값을 가져온다.

  • 데이터 하나만 들고올 때
const itemStock = selectedTag.parentElemnet.previousElemnetSibling.children[0].vlaue;
  • 데이터 여러개 들고올때
    : 단, 아래처럼 클래스로 불러오게되면 for문을 도는 반복문이기때문에 중복이되어 데이터를 들고오지않는다.
    왜냐면 내가 실제로 변경할 인풋태그 값 하나만 불러와야하기때문이다.
  • 잘못된 방법
    const itemStock = document.querySelectorAll('.stockInput');
  • 올바른 방법
    : 선택한 태그를 둘러싸고 있는! 감싸고있는!(감싸지않으면 x) 가장 가까운 td태그 선택
    <방법 1>
    const itemStock1 = selectedTag.parentElemnet.children[0].vlaue;
    <방법 2>
    const itemStock = selectedTag.closest('td').querySelector('.stockInput').vlaue;

//-------------------상품(판매)여부 라디오 버튼 클릭시 진행되는 함수---------------------------------//
function changeItemStatus(itemCode, status){
	
      $.ajax({
         url: '/admin/changeItemStatus', //요청경로
         type: 'post',
         data: {'itemStatus':status,'itemCode':itemCode}, //필요한 데이터
         success: function(result) {
			const modal = new bootstrap.Modal('#updateStatusModal');
			modal.show();  
         },
         error: function() {
            alert('실패');
         }
      });
}



//--------------------------재고 수량변경 버튼 클릭시 진행되는 함수---------------------------------//
function changeStock(itemCode, selectedTag){
	 const itemStock = selectedTag.closest('td').querySelector('#itemStocks').value;
    
      //1) ajax start
      $.ajax({
         url: '/admin/changeStock', //요청경로
         type: 'post',
         data: {'itemCode':itemCode ,'itemStock':itemStock}, //필요한 데이터
         success: function(result) {
		// -- alert창 띄우는 ver.
        // alert('수량을 변경했습니다');
        // -- modal창 띄우는 ver.
        // 버튼을 클릭했을 때가 아니라 원했을 때 띄우기
	        const modal = new bootstrap.Modal('#updateStockModal');
			modal.show();        
        
         },
         error: function() {
            alert('실패');
         }
      });
      //1) ajax end
}

Controller

  • adminController
package Kh.study.shop.admin.controller;
@Controller
@RequestMapping("/admin")
public class AdminController {
////////////////////////////////////////////////////
	@Resource(name = "adminService")
	private AdminService adminService;
	@Resource(name = "memberService")
	private MemberService memberService;
	@Resource(name = "itemService")
	private ItemService itemService;
/////////////////////////////////////////////////////
	
	//관리자_첫화면 :상품등록 및 상세페이지 동시
	@GetMapping("/regCate")
	public String admin(Model model, ItemVO itemVO) {
		//전체 카테고리  목록조회(좌측화면에만 데이터 뿌리기)
		model.addAttribute("cateListAll",adminService.cateListAll());
		
		//'사용' 중 카테고리 목록 조회(우측화면 상품등록시 적용되도록)
		model.addAttribute("cateListUse",adminService.cateListUse());

		return "content/admin/reg_item";
	}
	
	//카테고리 등록
	@PostMapping("/regCate")
	public String regCate(CategoryVO categoryVO) {
		adminService.regCate(categoryVO);
		// 카테고리 등록 후, 다시 첫화면 페이지로 이동
		return "redirect:/admin/regCate";
	}
	
	//카테고리상태 변경(ajax실행)
	//ajax사용하는 이유! 페이지이동없이하려고!!
	// -> 그래서 페이지이동 리턴값이 없다!
	@ResponseBody
	@PostMapping("/changeStatus")
	public void changeStatus(CategoryVO categoryVO) {
		//상태변경하기
		adminService.changeStatus(categoryVO);
	}
	
	// 사용중 카테고리 목록 조회(ajax사용)
	@ResponseBody
	@PostMapping("/selectCategoryListInUseAjax")
	public List<CategoryVO>  selectCategoryListInUseAjax() {
		//사용중 카테고리 목록 재조회
		List<CategoryVO> cateList = adminService.cateListUse();
		return cateList;
	}
	
	//실제 상품등록
	@PostMapping("/regItem")
	public String reg(ItemVO itemVO) {
		adminService.regItem(itemVO);
		return "redirect:/admin/regCate";//관리자 첫화면
	}
	
	
	//회원권한설정클릭시 첫화면
	@GetMapping("/setMemberRole")
	public String setMemberRoleProccess(Model model,MemberVO memberVO) {
		model.addAttribute("memberList",memberService.selectMemberList());
		return "content/admin/set_member_role" ;
	}
	
	
	 // (ajax)회원 상세정보조회
	@ResponseBody
	@PostMapping("/showDetailmemberInfo") 
	public MemberVO showDetailmemberInfo(String memberId) { 
		MemberVO memberInfo = memberService.selectMemberDetail(memberId);
		return memberInfo;
	}
	
	//상세조회 후 수정하기
	@PostMapping("/updateMemberRole")
	public String updateMemberRole(MemberVO memberVO) {
		// 수정쿼리문
		return "redirect:/admin/setMemberRole";
	}
	
	//상품관리 클릭시
	@GetMapping("/itemManage")
	public String itemManage(Model model, ItemVO itemVO) {
		// 등록된 상품목록조회
		model.addAttribute("regItemList", itemService.selectRegItemList());
		// 카테고리 목록조회
		model.addAttribute("cateListAll",adminService.cateListAll());
		return "content/admin/item_manage";
	}
	 
	//(ajax) 수량 변경
	@ResponseBody
	@PostMapping("/changeStock")
	public void changeStock(ItemVO itemVO) {
		itemService.updateStock(itemVO);
	}
	//(ajax) 상품판매여부 변경
	@ResponseBody
	@PostMapping("/changeItemStatus")
	public void changeItemStatus(ItemVO itemVO) {
		itemService.updateStautus(itemVO);
	}
	
	
}

mapper

  • 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="itemMapper">
	<!-- 패키지명 클래스명 -->
 	<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"/>

		<result column="CATE_NAME" property="cateName"/>
		<result column="CATE_STATUS" property="cateStatus"/>
	</resultMap>
	
<!-- 상품목록조회 -->
<select id="selectItemList" resultMap="item">
   SELECT 
  	 ITEM_CODE
	,ITEM_NAME
	,ITEM_PRICE
	,ITEM_COMMENT
	,REG_DATE
	,ITEM_STOCK
	,CATE_CODE
   FROM SHOP_ITEM
   ORDER by ITEM_CODE
</select> 

<!-- 상품관리 페이지 이동시  -->
<!-- 상품+카테고리 함께 목록조회 -->

<!-- 조건검색기능 -->
<!-- 조건절 AND로 계속 연결된다.-->
<!--  대소문자 구분없이   -->
<!-- <= 사용시, 부등호를 태그로 인식하기때문에 -->
<!-- less than: $lt; greater than : $gt; -->

<!-- 날짜비교 중요한사항! -->
<!-- ! 그대로 컬러명사용하는 것이아니라 형변환(TO_CHAR)하여 진행해야 비교가 온전히 가능하다-->
<!-- 모든 값은 if문으로 넣어 사용해야한다.  -->
<select id="selectRegItemList" resultMap="item">
	SELECT 
	ITEM_CODE
		,ITEM_NAME
		,ITEM_PRICE
		,to_char(REG_DATE, 'YYYY-MM-DD') REG_DATE
		,ITEM_STOCK
		,SHOP_ITEM.CATE_CODE
	    ,CATE_NAME
	    ,DECODE(ITEM_STATUS,'ON_SALE','판매중','매진')  ITEM_STATUS
	FROM  SHOP_ITEM,ITEM_CATECGORY
	WHERE SHOP_ITEM.CATE_CODE = ITEM_CATECGORY.CATE_CODE
	<!-- <if test="">
	AND SHOP_ITEM.CATE_CODE = #{cateCode} 
	</if>
	<if test="">
	AND UPPER(ITEM_NAME) LIKE UPPER('%#{itemName}%')
	</if>
	<if test="">
	AND ITEM_STOCK &lt;= #{itemStock}
	</if>
	<if test="">
	AND TO_CHAR(REG_DATE,'YYYY-DD-MM') &gt;= '#{regDate}' 
	</if>
	<if test="">
	AND TO_CHAR(REG_DATE,'YYYY-DD-MM') &lt;= '#{regDate}' 
	</if>
	<if test="">
	AND ITEM_STATUS = 'ON_SALE'
	</if> -->
	
</select>
<!-- 재고 수량 변경 -->
  <update id="updateStock" >
	UPDATE SHOP_ITEM 
	SET 
	ITEM_STOCK = #{itemStock}
	WHERE ITEM_CODE = #{itemCode}
</update>  

<!-- 상품상태 여부 변경 -->
<update id="updateStautus" >
	UPDATE SHOP_ITEM 
	SET
	ITEM_STATUS = #{itemStatus}	
	WHERE ITEM_CODE = #{itemCode}
</update>
</mapper> 

Service

  • Service
package Kh.study.shop.item.service;
public interface ItemService {
	List<ItemVO> selectItemList();
	List<ItemVO> selectRegItemList();
	void updateStock(ItemVO itemVO);
	void updateStautus(ItemVO itemVO);
}

ServiceImpl

  • ServiceImpl
package Kh.study.shop.item.service;

@Service("itemService")
public class ItemServiceImpl implements ItemService {
	@Autowired
	private SqlSessionTemplate sqlSession;
	
	@Override
	public List<ItemVO> selectItemList() {
		return sqlSession.selectList("itemMapper.selectItemList");
	}
	@Override
	public List<ItemVO> selectRegItemList() {
		return sqlSession.selectList("itemMapper.selectRegItemList");
	}
	@Override
	public void updateStock(ItemVO itemVO) {
		sqlSession.update("itemMapper.updateStock",itemVO);
	}
	@Override
	public void updateStautus(ItemVO itemVO) {
		sqlSession.update("itemMapper.updateStautus",itemVO);
		
	}
}

결과

수량변경 기능 구현

  • 재고수량 새로 입력 후, 변경 버튼 클릭시 modal창으로 변경완료 문구 뜨게 한뒤, 확인 버튼클릭시 데이터베이스까지 완전히 변경된 것을 확인 할 수 있다.

  • 관리자로 로그인 후, 상품관리 클릭 후 페이지 이동

  • 수량 새로 입력

  • 변경 버튼 클릭

  • 변경 확인 버튼 클릭 시 결과

  • 새로 입력한 값 그대로 나타남 (새로고침해도 그대로 유지)

  • 디비 결과 확인 (조회하기)
    : 1번 달라구트 꿈의 백화점 상품 재고 값이 '10'으로 변경됨을 확인 가능하다


상태변경 기능 구현

  • 변경 전 화면

  • 변경 후 화면

  • 새로고침 후 변경된 값 유지된 것을 확인 가능하다

  • 마지막으로 데이터베이스에서도 확인
    : 2번 나이키 상품의 상태값이 '매진' 에서 '판매중'으로 변경된 것을 확인할 수 있다.

profile
Dev.Vinch

0개의 댓글