20220928_tue
어제 내용 정리
1. login_result.html 에서 권한여부(ROLE)에 따라 관리자(MANAGER),일반회원(MEMBER)으로 로그인시 어떻게 이동할지 sec문을 이용하여 alert창을 띄운 후 이동하도록 한다.
2. AdminController 를 만들어서 관리자컨트롤러로 페이지 이동하도록 만든다.
3. 관리자페이지의 가장 첫 화면은 '상품등록화면' 페이지이다.그래서 reg_item.html 파일을 만들어 준다.
4. 위 페이지는 layout으로 총 3단화면(top,side,content)으로 구성되어야한다.
: admin_layout으로 위 구성대로 구현한다.
단, th:replace를 사용할때 지정한 파일이름을 불러올때 일치하여 사용해야한다.
- side.html파일에서 사이드메뉴를 구현한다. (부트스트랩 이용)
오늘 실습 내용
- 관리자 페이지 구현
- 상품등록 화면구성기능
- 카테고리목록조회
- 카테고리 등록기능
- ajax 적용하여 사용여부 변경
DROP TABLE ITEM_CATECGORY;
DROP TABLE SHOP_ITEM
-- 상품 카테고리 테이블
CREATE TABLE ITEM_CATECGORY (
CATE_CODE VARCHAR2(50) CONSTRAINT ITEM_CATECGORY_PK PRIMARY KEY
, CATE_NAME VARCHAR2(100) NOT NULL
, CATE_STATUS VARCHAR2(20) DEFAULT 'USE' NOT NULL
);
-- 상품 카테고리 테이블 조회
SELECT * FROM ITEM_CATECGORY;
-- 상품정보 테이블
CREATE TABLE SHOP_ITEM (
ITEM_CODE VARCHAR2(50) CONSTRAINT SHOP_ITEM_PK PRIMARY KEY
, ITEM_NAME VARCHAR2(100) NOT NULL
, ITEM_PRICE NUMBER NOT NULL
, ITEM_COMMENT VARCHAR2(200)
, REG_DATE DATE DEFAULT SYSDATE
, ITEM_STOCK NUMBER NOT NULL
, CATE_CODE VARCHAR2(50) NOT NULL CONSTRAINT SHOP_ITEM_FK REFERENCES ITEM_CATECGORY (CATE_CODE) ON DELETE CASCADE
-- ON DELETE CASCADE
--부모 테이블에서 row 를 삭제할 경우 연결된 자식 테이블의 row 가 함께 삭제
--연결된 데이터를 한 번에 지울 수 있어 데이터의 관리가 편리해지고 일관성을 유지
);
-- 상품정보 테이블 조회
SELECT * FROM SHOP_ITEM;
- Kh.study.shop
- admin
- Controller
- AdminController.java
package Kh.study.shop.admin.controller;
@Controller
@RequestMapping("/admin")
public class AdminController {
@Resource(name = "adminService")
private AdminService adminService;
///////////////////////////////////////////////////////////////////
//관리자_첫화면 :상품등록 및 상세페이지 동시
@GetMapping("/regItem")
public String admin(Model model) {
//상품목록조회
model.addAttribute("cateList",adminService.cateList());
return "content/admin/reg_item";
}
//카테고리 등록
@PostMapping("/regCate")
public String regCate(CategoryVO categoryVO) {
adminService.regCate(categoryVO);
// 카테고리 등록 후, 다시 첫화면 페이지로 이동
return "redirect:/admin/regItem";
}
//카테고리상태 변경(ajax실행)
//ajax사용하는 이유! 페이지이동없이하려고!!
// -> 그래서 페이지이동 리턴값이 없다!
@ResponseBody
@PostMapping("/changeStatus")
public void changeStatus(CategoryVO categoryVO) {
//상태변경하기
adminService.changeStatus(categoryVO);
}
}
package Kh.study.shop.admin.service;
public interface AdminService {
//카테고리 목록조회
List<CategoryVO> cateList();
//카테고리 등록
void regCate(CategoryVO categoryVO);
//카테 상태 변경
void changeStatus(CategoryVO categoryVO);
}
package Kh.study.shop.admin.service;
@Service("adminService")
public class AdminServiceImpl implements AdminService {
@Autowired//어노테이션으로 객체생성
private SqlSessionTemplate sqlSession;
//목록조회
@Override
public List<CategoryVO> cateList() {
return sqlSession.selectList("adminMapper.cateList");
}
//카테고리 등록
@Override
public void regCate(CategoryVO categoryVO) {
sqlSession.insert("adminMapper.regCate",categoryVO);
}
//카테 상태 변경
@Override
public void changeStatus(CategoryVO categoryVO) {
sqlSession.update("adminMapper.changeStatus",categoryVO);
}
}
- item
- vo
- ItemVO
- CategoryVO
package Kh.study.shop.item.vo;
@Setter
@Getter
@ToString
public class ItemVO {
private String itemCode;
private String itemName;
private int itemPrice;
private String itemComment;
private String regDate;
private int itemStock;
private String cateCode;
}
package Kh.study.shop.item.vo;
@Setter
@Getter
@ToString
public class CategoryVO {
private String cateCode;
private String cateName;
private String cateStatus;
}
- src/main/resources
- mappers
- admin-mapper.xml
<?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="adminMapper">
<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>
<!-- 카테고리 목록조회 -->
<select id="cateList" resultMap="category">
SELECT
CATE_CODE
,CATE_NAME
,CATE_STATUS
FROM ITEM_CATECGORY
ORDER BY CATE_CODE
</select>
<!-- 카테고리 등록 -->
<insert id="regCate">
<selectKey resultType="String" keyProperty="cateCode" order="BEFORE">
SELECT 'CATE_' ||LPAD(NVL(MAX(TO_NUMBER(SUBSTR(CATE_CODE,6))),0)+1,3,0)
FROM ITEM_CATECGORY
</selectKey>
INSERT INTO ITEM_CATECGORY
(CATE_CODE,CATE_NAME)
VALUES
(#{cateCode},#{cateName})
</insert>
<!-- 카테고리 상태 변경 -->
<update id="changeStatus">
UPDATE ITEM_CATECGORY
SET CATE_STATUS = #{cateStatus}
WHERE CATE_CODE = #{cateCode}
</update>
</mapper>
- src/main/resources
- static
- css
- admin.css
- js
- reg_item.js
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<div th:fragment="top">
<!-- 아이템컨트롤러에서 던져진 로그인여부값을 히든으로 가져와서 받는다. -->
<input type="hidden" id="isLoginFail" th:value="${isLoginFail}">
<div class="row">
<div class="col text-end" sec:authorize="isAnonymous()">
<span data-bs-toggle="modal" data-bs-target="#join_modal" style="color: #3D8361;">JOIN</span>
<span data-bs-toggle="modal" data-bs-target="#login_modal" style="color: #3D8361;">LOGIN</span>
</div>
<div class="col text-end" sec:authorize="isAuthenticated()" >
<!-- 아래 sec 데이터값은 아이디값을 추출되기때문에 span태그 사이에 다른 문자열을 넣을 수 없다. -->
<!-- 그래서 이어서 문자열을 작성하려면 span태그 밖에서 작성하도록 한다. -->
<span sec:authentication="name"></span> 님 반갑습니다😊
<!-- 로그아웃 컨트롤러는 시큐리티에서 자동으로 호출되기때문에 컨트롤러나 form태그 작성 불필요 -->
<!-- 단순히 a태그나 버튼으로 경로이동만 시켜주면 된다! -->
<button th:onclick="location.href='@{/logout}';" sec:authorize="isAuthenticated()" type="submit" class="btn btn-light">LOGOUT</button>
</div>
</div>
<div class="row">
<div class="col text-center" >
<span style=" color: #A1C298; font-weight: bold; font-size: 55px;">S H O P</span>
</div>
</div>
<!--top MENU -->
<div class="row">
<div class="col">
<nav class="navbar navbar-expand-lg bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#" style="color: #3D8361;">MENU</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Item</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Order</a>
</li>
<li class="nav-item">
<a class="nav-link " href="#">Cart</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
</div>
<!-- login 클릭시 실행 Modal -->
<div class="modal fade" id="login_modal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel" style="font-weight: bold;">L O G I N</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- 로그인창 modal -->
<div class="modal-body">
<!-- 로그인 - 세션이용 데이터 form태그로 보내주기 -->
<!-- 부트스트랩에서 가져왔기때문에 함부러 수정x 형식에 맞게 해야사용가능하다 -->
<form class="row g-3" name="formLogin" method="post" action="/member/login" >
<div class="mb-3">
<!-- label은 input태그의 라벨(스티커)이다. 웬만하면, for값과 id값이 같아야한다 -->
<!-- html 파일 내에 memberId 라는 id값이 여러개 존재한다.(중복발생) -->
<label for="memberId" class="form-label" >ID</label>
<input id="memberId" type="text" class="form-control" name="memberId" aria-describedby="emailHelp" placeholder="Input your ID" >
</div>
<div class="mb-3">
<label for="memberPw" class="form-label">Password</label>
<input type="password" class="form-control" name="memberPw" id="memberPw" placeholder="Input your password">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Login</button> <!--submit은 ajax에서 사용하는 것이 아니다!!! onclick이용해서 ajax? -->
<button type="button" class="btn btn-primary" onclick="goLogin();">AjaxLogin</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- join 클릭시 실행 Modal -->
<div class="modal fade" id="join_modal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel" style="font-weight: bold;">J O I N</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- 너비 조정-->
<!-- col-12 / col-6 : 한 줄 너비 총 크기 12이기때문에 12분등 중 어느만큼 사용하는건지 -->
<form class="row g-3" action="/member/join" method="post">
<!-- class값은 고정 건드리면 안됨. 부트스트랩사용하기위한 의미 -->
<!-- for태그와 id태그 이름은 같게 하도록 -->
<div class="col-12">
<label for="memberId" class="form-label">ID</label>
<input type="text" class="form-control" id="memberId" placeholder="put your ID" name="memberId">
</div>
<div class="col-12">
<label for="memberPw" class="form-label">PASSWORD</label>
<input type="password" class="form-control" id="memberPw" name="memberPw" placeholder="put your Password">
</div>
<div class="col-12">
<label for="memberName" class="form-label">NAME</label>
<input type="text" class="form-control" id="memberName" name="memberName" placeholder="put your Name">
</div>
<!-- 주소(상세주소 + 검색버튼추가) -->
<!-- 줄을 맞추기위해 실제내용은 화면에 안보이도록 공백문자사용/ class에 form-control 붙여넣기 -->
<div class="col-9">
<label for="memberAddr" class="form-label"> ADDRESS </label>
<input type="text" class="form-control" id="memberAddr" name="memberAddr" readonly onclick="searchAddr();"><!-- 값변경못하도록 읽기전용속성값부여하기 ( 데이터넘기기 가능) -->
</div>
<div class="col-3" >
<label for="" class="form-label"> </label>
<input type="button" class="btn btn-secondary form-control" onclick="searchAddr();" value="Search">
</div>
<div class="col-12" >
<input type="text" class="form-control" id="addrDetail" name="addrDetail">
</div>
<div class="col-12">
<label for="memberEmail" class="form-label">EMAIL</label>
<input type="text" class="form-control" id="memberEmail" placeholder="put your Email" name="memberEmail">
</div>
<div class="d-grid gap-2 col-12" >
<!-- 버튼클릭하나로 모든 데이터 가져가야하므로 무조건 submit! -->
<button type="submit" class="btn btn-primary">JOIN</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- 카카오API 사용위해 자바스크립트 사용 전, 미리 로드하기 -->
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<!-- js불러오는 방법_기본 (1) -->
<!-- 주의)html 파일 이외의 파일들은 static 폴더 기준이면서 맨앞은 '/'넣어줘야한다!!!(templates X) -->
<!-- src속성값을 넣어주면 해당 파일에 js찾아서 불러온다는 기능 -->
<!-- <script type="text/javascript" src="/js/layout/top.js"></script> -->
<!-- js불러오는 방법_타임리프 (2) -->
<!-- 기본방법과 비슷하지만 @{}사용해야한다! -->
<script type="text/javascript" th:src="@{/js/layout/top.js}"></script>
<!-- 위 방법의 차이점은? -->
<!-- 디자이너와 협업시 용이하다.
이클립스를 사용하지 않는 디자이너분들은 타임리프를 사용해서 타임리프로 된 html파일은 데이터 던져주면,
데이터값을 확인할 수 있다. 타임리프를 사용하지않으면 디자이너분들이 사용하기 불편하기때문에 사용하는 것이다. -->
</div>
</html>
function changeStatus(cateCode, status){
const result = confirm('상품의 상태를 변경할까요?');
if(result){
//ajax start
$.ajax({
url: '/admin/changeStatus', //요청경로
type: 'post',
data: {'cateStatus':status,'cateCode':cateCode}, //필요한 데이터
success: function(result) {
alert('상태를 변경했습니다');
},
error: function() {
alert('실패');
}
});
//ajax end
}
}
/* 모든파일에 적용되는 공통 css */
@charset "UTF-8";
@font-face {
font-family: 'RIDIBatang';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_twelve@1.0/RIDIBatang.woff') format('woff');
font-weight: normal;
font-style: normal;
}
body {
font-family: 'RIDIBatang';
}
a{
text-decoration:none;
color: #0F3D3E;
font-size: 18px;
font-weight: bold;
}
a:hover{
color: #1C6758;
}
.layoutTable{
width: 1000px;
margin: 0 auto;
border: 1px solid black;
border-collapse: collapse;
margin-top: 40px;
text-align: center;
}
.layoutTable > tbody >tr, .layoutTable > tbody > tr >td{
border: 1px solid black;
}
.layoutTable > tbody> tr{/* 자식 */
height: 500px;
}
.layoutTable > tbody> tr > td{/* 자식 */
vertical-align: top;
padding: 20px;
}
.stuListTable, .stuDetailTable{
width: 400px;
border: 1px solid gray;
border-collapse: collapse;
margin: 0 auto;
text-align: center;
}
.stuListTable tr,.stuListTable td{/* 자손 */
border: 1px solid gray;
}
.stuDetailTable tr,.stuDetailTable td{/* 자손 */
border: 1px solid gray;
}
span:hover {
cursor: pointer;/* 손가락모양으로 커서변경 */
}
- src/main/resources
- templates
- content
- admin
- reg_item.html
- fragment
- side.html
- layout
- admin_layout.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div th:fragment="side">
<div style="color: #367E18;" >
<ul><a th:href="@{/admin/setItem}">상품등록</a> </ul>
<ul><a th:href="@{/admin/setItem}">상품관리</a></ul>
<ul><a th:href="@{/admin/setRole}">회원권한설정</a></ul>
<ul><a th:href="@{/admin/setMenu}">메뉴관리</a></ul>
</div>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
layout:decorate="~{layout/admin_layout}"><!-- 물결표시와 중괄호 문법은 선택적으로 사용가능하다. -->
<link href="/css/reg_item.css" rel="stylesheet">
<div layout:fragment="content">
<!-- 전체화면 -->
<div class="row">
<!-- 좌측화면 -->
<div class="col-5">
<div class="row">
<div class="col-12 mb-3"><!-- mb : margin-bottom / ms : margin-side -->
<h6>CATEGORY MANAGE</h6>
</div>
<div class="col-12 mb-3">
<form class="row g-3" th:action="@{/admin/regCate}" method="post">
<div class="col-8">
<input name="cateName" type="text" class="form-control" id="" placeholder="카테고리를 입력하세요">
</div>
<div class="col-4">
<button type="submit" class="btn btn-primary"><!-- 등록버튼 안에 이미지 들고오기 단, 버튼태그만 가능하며 인풋태그는 불가능 -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cart-check" viewBox="0 0 16 16">
<path d="M11.354 6.354a.5.5 0 0 0-.708-.708L8 8.293 6.854 7.146a.5.5 0 1 0-.708.708l1.5 1.5a.5.5 0 0 0 .708 0l3-3z"/>
<path d="M.5 1a.5.5 0 0 0 0 1h1.11l.401 1.607 1.498 7.985A.5.5 0 0 0 4 12h1a2 2 0 1 0 0 4 2 2 0 0 0 0-4h7a2 2 0 1 0 0 4 2 2 0 0 0 0-4h1a.5.5 0 0 0 .491-.408l1.5-8A.5.5 0 0 0 14.5 3H2.89l-.405-1.621A.5.5 0 0 0 2 1H.5zm3.915 10L3.102 4h10.796l-1.313 7h-8.17zM6 14a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm7 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
</svg>
등록</button>
</div>
</form>
</div>
<div class="col-12 mb-3">
<table class="table table-striped table-hover text-center">
<thead>
<tr>
<th scope="col">No.</th>
<th scope="col">카테코드</th>
<th scope="col">상품명</th>
<th scope="col">사용여부</th>
</tr>
</thead>
<!-- 목록조회 테이블 -->
<tbody>
<th:block th:if="${#lists.size(cateList) == 0}">
<tr>
<td colspan="4">게시글 목록이 없습니다.</td>
</tr>
</th:block>
<!-- if문을 그대로 사용하면서 문자열 부정문은 맨앞에 느낌표를 사용한다! -->
<!-- <th:block th:if="${!(#lists.size(cateList) eq 0)}"> -->
<th:block th:unless="${#lists.size(cateList) == 0}"> <!-- 등호대신 eq 도 가능하다 -->
<th:block th:each="cateList, status : ${cateList}"><!-- 행번호추출하는 방법 status추가하기 -->
<tr>
<td th:text="${status.count}"></td><!-- 행번호추출 -->
<td th:text="${cateList.cateCode}" ></td>
<td th:text="${cateList.cateName}" ></td>
<td>
<!-- 라디오값을 타임리프로 데이터 넘기는 방법?? -->
<!-- 1. 라디오값을 중복이아닌 단 하나만 선택되도록하려면 name값을 동일하게 부여한다 -->
<!-- 2. 각 선택한 라디오값마다 다른 값을 부여해서 다음 페이지에 데이터를 갖고가려한다. -->
<!-- 3. 타임리프로 라디오값 넘기는 방법은 status를 사용해서 행번호를 각각 매겨준다.그래야 각각 다른 값을 갖기때문-->
<!-- 4. 각각의 사용여부의 값이 check된 값인지 확인하기위해서, 새로고침해도 진짜 본인의값인지(사용/미사용)알려면
카테리스트의 status값이 use인지 unuse인지에 따라 체크되도록 만들어 확인해볼수있다. -->
<!-- ajax 함수로 클릭시 이동할 때 변수명 가져가는 방법 -->
<!-- 함수명 괄호안에 변수명은 항상 대괄호 두개 안에 기존방식대로 넣어준다!! -->
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" th:name="|cateStatus_${status.count}|"
id="use" value="USE" th:checked="${cateList.cateStatus eq 'USE'}"
th:onclick="changeStatus([[${cateList.cateCode}]], 'USE');" >
<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="|cateStatus_${status.count}|"
id="unuse" value="UNUSE" th:checked="${cateList.cateStatus eq 'UNUSE'}"
th:onclick="changeStatus([[${cateList.cateCode}]], 'UNUSE');" >
<label class="form-check-label" for="inlineRadio2">미사용</label>
</div>
</td>
</tr>
</th:block>
</th:block>
</tbody>
</table>
</div>
</div>
</div>
<!-- 우측화면 -->
<div class="col-7">상품등록화면</div>
</div>
<!-- 반드시 해당되는 div 태그 안에 있어야 실행이 된다. -->
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript" th:src="@{/js/layout/reg_item.js}"></script>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
<link href="/css/admin.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-u1OknCvxWvY5kfmNBILK2hRnQC3Pr17a+RTT6rIHI7NnikvbZlHgTPOOmMi466C8" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
</head>
<body>
<div class="container">
<!-- 고정된 top.html 화면 -->
<div style="margin-bottom: 50px;" class="row">
<div class="col">
<div th:replace="fragment/top::top"></div>
</div>
</div>
<!-- 고정된 side.html 화면 -->
<!-- col의 12등분을 이용하여 너비조정이 쉽게 가능하다. -->
<div style="position: fixed; width: 200px;" class="row">
<div class="col">
<div th:replace="fragment/side::side"></div>
</div>
</div>
<!-- 계속바뀌는 베이스화면 -->
<div style="position: relative; left: 250px;" class="row">
<div class="col">
<div layout:fragment="content"></div>
</div>
</div>
</div>
</body>
</html>