20220930_FRI
실습내용
- 회원목록 테이블 제목줄 고정,내용테이블만 스크롤 만들기
- 주의할점은 테이블을 두개를 만들어 제목줄과 내용줄을 나눠서 스크롤바는 내용줄만 사용하도록하면 된다.
실습내용
- 홈페이지 첫 화면 상품목록페이지 구현
- 디비에 저장한 등록한 상품을 상품목록 띄우기
- (상품 이미지 뛰우고 첨부하기)
- 상품관리 페이지 에서 상품목록 조회
: (content/admin/item_manage.html)- 조회 컬럼 : No,상품카테고리(단,name값으로 나오도록 구현),상품명,가격.재고,등록일
: 상태는 판매중 / 매진 두개로 라디오버튼으로 구현
예시) no / 카테고리 / 상품명 / 가격 / 재고 / 등록일 / 상태
1 / 상의 / 자켓 / 100/ 10 / 22.8.9 / 판매중- 재고버튼 수정,판매상태 여부 수정하기 -> ajax사용하여 변경하기
<참고>https://esef.tistory.com/117
- 1rem : rem단위는 문서의 기본값의 배수
- 1em : em단위는 em단위가 있는 곳의 폰트사이즈의 배수
- px: px은 우리가 알고 있는 픽셀값
- % : %는 사용자가 보이는 화면에서 차지하는 비중
- 재고변경 기능 구현
- 상태변경 기능 구현
- -> 재고나 상태를 변경시 이 후 처리할 다른 일이 없기때문에 ajax에서 success구문에 할일이 없기 때문에 ajax를 사용하는 것이 편이하다.
- 위 기능을 구현하는데, 페이지이동하는 방법과 페이지이동없는 ajax를 이용하는 방법 총 두가지의 방법이 있다.
- 단, ajax를 실행시 자바스크립트에서 코드실행이 된다. 필요한 컨트롤러 쿼리를 위해 매퍼와 서비스에서 기능을 구현한다. 서세스구문에서 무엇을 해야할지를 생각해야한다. 서세스구문에서 할일이 많다면 그냥 페이지이동하여 기능을 구현하는 것이 후처리가 더 쉬울 수 있다.
- 그래서 후 처리를 먼저 생각하고 페이지이동을 어떻게 할 지를 생각해야한다.
기능 구현 순서
- 상품관리 html에서 onclick으로 js에서 실행할 함수로 이동하기 만들기
- html과 유사한 파일명으로 js만들어서 함수만들기
- 컨트롤러가서 쿼리실행하도록 데이터 던져주기
- 쿼리실행할 매퍼,서비스(임플) 만들기
- 5.js에서 매개변수 받아서 사용하기.
<!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>
- 태그선택 요소
const itemStock = selectedTag.parentElemnet.previousElemnetSibling.children[0].vlaue;
const itemStock = document.querySelectorAll('.stockInput');
const itemStock1 = selectedTag.parentElemnet.children[0].vlaue;
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
}
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);
}
}
<?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 <= #{itemStock}
</if>
<if test="">
AND TO_CHAR(REG_DATE,'YYYY-DD-MM') >= '#{regDate}'
</if>
<if test="">
AND TO_CHAR(REG_DATE,'YYYY-DD-MM') <= '#{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>
package Kh.study.shop.item.service;
public interface ItemService {
List<ItemVO> selectItemList();
List<ItemVO> selectRegItemList();
void updateStock(ItemVO itemVO);
void updateStautus(ItemVO itemVO);
}
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번 나이키 상품의 상태값이 '매진' 에서 '판매중'으로 변경된 것을 확인할 수 있다.