게시판 분류 관리 페이지, 기능 완료.

왼쪽 사이드바 작업중.
master
강석 최 2021-12-07 19:08:49 +09:00
parent 3942e7ba0e
commit a0765ec49a
14 changed files with 424 additions and 60 deletions

View File

@ -1,6 +1,7 @@
package com.dbnt.kcgfilemanager;
import com.dbnt.kcgfilemanager.model.UserInfo;
import com.dbnt.kcgfilemanager.service.BoardCategoryService;
import com.dbnt.kcgfilemanager.service.CommonCodeService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@ -17,6 +18,7 @@ import java.security.Principal;
public class BaseController {
private final CommonCodeService commonCodeService;
private final BoardCategoryService boardCategoryService;
@GetMapping("/")
public ModelAndView loginCheck(Principal principal, HttpSession session) {
@ -26,6 +28,7 @@ public class BaseController {
}else{
session.setAttribute("positionList", commonCodeService.selectCommonCodeValue("POSITION"));
session.setAttribute("departmentList", commonCodeService.selectCommonCodeValue("DEPARTMENT"));
session.setAttribute("categoryList", boardCategoryService.selectBoardCategoryAll(null, 1));
if(((UserInfo)((UsernamePasswordAuthenticationToken) principal).getPrincipal()).getUserRole().indexOf("ADMIN")>0){
mav = new ModelAndView("redirect:/admin/main");
}else{

View File

@ -1,13 +1,18 @@
package com.dbnt.kcgfilemanager.controller;
import com.dbnt.kcgfilemanager.model.BoardCategory;
import com.dbnt.kcgfilemanager.model.CommonCode;
import com.dbnt.kcgfilemanager.model.UserInfo;
import com.dbnt.kcgfilemanager.service.BoardCategoryService;
import com.dbnt.kcgfilemanager.service.CommonCodeService;
import com.dbnt.kcgfilemanager.service.UserInfoService;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@ -16,6 +21,7 @@ import java.util.List;
public class adminController {
private final CommonCodeService commonCodeService;
private final BoardCategoryService boardCategoryService;
private final UserInfoService userInfoService;
@GetMapping("/main")
@ -26,9 +32,30 @@ public class adminController {
@GetMapping("/categoryMgt")
public ModelAndView categoryMgt(){
ModelAndView mav = new ModelAndView("admin/categoryMgt");
mav.addObject("categoryList", boardCategoryService.selectBoardCategory(null,1));
return mav;
}
@GetMapping("/getChildCategoryTable")
public ModelAndView getChildCategoryTable(Integer parentCategory, Integer depth){
ModelAndView mav = new ModelAndView("admin/boardCategoryTable");
mav.addObject("categoryList", boardCategoryService.selectBoardCategory(parentCategory, depth));
mav.addObject("depth", depth);
return mav;
}
@PostMapping("/insertCategory")
@ResponseBody
public int insertBoardCategory(@RequestBody List<BoardCategory> categoryList) {
boardCategoryService.insertBoardCategory(categoryList);
return categoryList.get(0).getDepth();
}
@DeleteMapping("/deleteCategory")
@ResponseBody
public int deleteBoardCategory(Integer categorySeq, Integer depth) {
boardCategoryService.deleteBoardCategory(categorySeq, depth);
return categorySeq;
}
@GetMapping("/userMgt")
public ModelAndView userMgt(UserInfo userInfo) {
userInfo.setQueryInfo();

View File

@ -0,0 +1,8 @@
package com.dbnt.kcgfilemanager.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BoardCategoryMapper {
}

View File

@ -5,6 +5,7 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.web.bind.annotation.RequestBody;
import javax.persistence.*;
import java.util.List;

View File

@ -0,0 +1,12 @@
package com.dbnt.kcgfilemanager.repository;
import com.dbnt.kcgfilemanager.model.BoardCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface BoardCategoryRepository extends JpaRepository<BoardCategory, Integer> {
List<BoardCategory> findByParentCategoryAndDepth(Integer parentCategory, Integer depth);
List<BoardCategory> findByParentCategory(Integer parentCategory);
}

View File

@ -0,0 +1,46 @@
package com.dbnt.kcgfilemanager.service;
import com.dbnt.kcgfilemanager.mapper.BoardCategoryMapper;
import com.dbnt.kcgfilemanager.model.BoardCategory;
import com.dbnt.kcgfilemanager.repository.BoardCategoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class BoardCategoryService {
private final BoardCategoryMapper boardCategoryMapper;
private final BoardCategoryRepository boardCategoryRepository;
public List<BoardCategory> selectBoardCategory(Integer parentCategory, Integer depth) {
return boardCategoryRepository.findByParentCategoryAndDepth(parentCategory, depth);
}
public List<BoardCategory> selectBoardCategoryAll(Integer parentCategory, Integer depth){
List<BoardCategory> categoryList = boardCategoryRepository.findByParentCategoryAndDepth(parentCategory, depth);
for(BoardCategory category: categoryList){
category.setChildCategoryList(selectBoardCategoryAll(category.getCategorySeq(), depth+1));
}
return categoryList;
}
public void insertBoardCategory(List<BoardCategory> categoryList){
boardCategoryRepository.saveAll(categoryList);
}
public void deleteBoardCategory(Integer categorySeq, Integer depth) {
deleteChildCategory(selectBoardCategoryAll(categorySeq, depth+1));
BoardCategory boardCategory = new BoardCategory();
boardCategory.setCategorySeq(categorySeq);
boardCategoryRepository.delete(boardCategory);
}
private void deleteChildCategory(List<BoardCategory> childList){
for(BoardCategory category: childList){
deleteChildCategory(category.getChildCategoryList());
}
boardCategoryRepository.deleteAll(childList);
}
}

View File

@ -9,13 +9,14 @@ spring.thymeleaf.cache=false
#mariaDB
spring.datasource.driverClassName=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.url=jdbc:log4jdbc:mariadb://106.247.244.146:57306/kcg_fm?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.url=jdbc:log4jdbc:mariadb://cks0504.iptime.org:3306/kcg_fm?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=dbnt0928
spring.datasource.password=mariadb#0524
#jpa
spring.jpa.show-sql=true
spring.jpa.generate-ddl=false
spring.jpa.hibernate.naming.physical-strategy = org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
# MyBatis
mybatis.mapper-locations: mybatisMapper/**/*.xml

View File

@ -1,9 +1,12 @@
#loginPage{
background-image: url("/img/img01.jpg");
}
.form-signin{
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
position: absolute;
z-index: 200;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

View File

@ -1,4 +1,140 @@
$(document).on('click', '.addCategoryBtn', function (){
const depth = $(this).attr("data-depth")
$("#depth"+depth+"Category").append($("#appendTr").children().clone())
$(function(){
})
$(document).on('click', '.categoryTr', function (){
const target = $(this).find(".trCheckBox");
const depth = Number(target.val());
const parentCategory = checkBoxAction(target);
getCategoryTable(parentCategory, depth+1);
})
$(document).on('click', '.insertTr', function (){
const target = $(this).find(".trCheckBox");
checkBoxAction(target);
})
$(document).on('click', '.addCategoryBtn', function (){
const depth = Number($(this).attr("data-depth"));
const appendTr = $("#appendTr").children().clone();
if(Number(depth)>1){
const parentSeq = $(".trCheckBox:checked[value='"+(depth-1)+"']").parent().find(".categorySeq").val()
if(parentSeq === undefined){
alert("선택된 상위 메뉴가 없습니다.")
return false;
}
appendTr.find(".parentCategory").val(parentSeq);
}
appendTr[0].className = "insertTr";
appendTr.find(".trCheckBox").val(depth);
appendTr.find(".depth").val(depth);
$("#depth"+depth+"Category").append(appendTr);
})
$(document).on('click', '.deleteCategoryBtn', function (){
let depth = Number($(this).attr("data-depth"));
const target = $(".trCheckBox:checked[value='"+depth+"']");
if(target.length>0){
const category = depth===1?"대분류":(depth===2?"연도":(depth===3?"중분류":(depth===4?"소분류":'')));
const categorySeq = target.parent().find(".categorySeq").val()
if(categorySeq===undefined || categorySeq===''){
// DB에 등록되지 않은 분류
deleteTr(target.parent().parent());
}else{
let msg = "";
if(category!=="소분류"){
msg = "\n하위 모든 분류가 삭제됩니다."
}
if(confirm(category+"의 선택된 분류를 삭제하시겠습니까?"+msg)){
$.ajax({
type : 'DELETE',
url : "/admin/deleteCategory",
data : {categorySeq: categorySeq, depth: depth},
beforeSend: function (xhr){
xhr.setRequestHeader($("[name='_csrf_header']").val(), $("[name='_csrf']").val());
},
success : function(data) {
alert("저장되었습니다.");
const target = $(".categorySeq[value='"+data+"']")
deleteTr(target.parent().parent());
},
error : function(xhr, status) {
}
})
}
}
}
})
$(document).on('click', '#saveCategoryBtn', function (){
const categoryList = [];
$(".insertTr").each(function (idx, el){
categoryList.push({});
const target = $(el);
categoryList[idx].depth = target.find(".depth").val();
categoryList[idx].parentCategory = target.find(".parentCategory").val();
categoryList[idx].categoryName = target.find(".categoryName").val();
})
if(categoryList.length !== 0) {
if (confirm(categoryList.length + "건을 저장하시겠습니까?")) {
$.ajax({
type : 'POST',
url : "/admin/insertCategory",
data : JSON.stringify(categoryList),
contentType: 'application/json',
beforeSend: function (xhr){
xhr.setRequestHeader($("[name='_csrf_header']").val(), $("[name='_csrf']").val());
},
success : function(data) {
alert("저장되었습니다.");
const depth = data;
const parentCategory = $(".trCheckBox:checked[value='"+(depth-1)+"']").parent().find(".categorySeq").val()
getCategoryTable(parentCategory, depth);
},
error : function(xhr, status) {
}
})
}
}
})
function checkBoxAction(target){
const depth = Number(target.val());
const parentCategory = Number($(target).parent().find(".categorySeq").val());
$(".trCheckBox[value='"+depth+"']").prop("checked", false);
target[0].checked = true;
return parentCategory
}
function getCategoryTable(parentCategory, depth){
$.ajax({
url: '/admin/getChildCategoryTable',
type: 'GET',
data : {parentCategory: parentCategory, depth: depth},
dataType:"html",
success: function(html){
let depth = Number($(html).find("thead").attr("data-depth"));
$("#depth"+depth+"Div").empty().append(html);
childTableReset(depth);
},
error:function(){
}
});
}
function deleteTr(target){
childTableReset(Number(target.find(".trCheckBox").val()))
target.remove();
}
function childTableReset(depth){
let childTbody
do{
depth++;
childTbody = $("#depth"+depth+"Category")
childTbody.empty();
}while (childTbody.length>0)
}

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<table class="table table-striped">
<thead th:data-depth="${depth}">
<tr>
<th></th>
<th th:text="${depth==1?'대분류':(depth==2?'연도':(depth==3?'중분류':'소분류'))}"></th>
</tr>
</thead>
<tbody th:id="|depth${depth}Category|">
<tr class="categoryTr" th:each="boardCategory:${categoryList}">
<td>
<input type="hidden" class="categorySeq" th:value="${boardCategory.categorySeq}">
<input type="checkbox" class="trCheckBox" th:value="${boardCategory.depth}">
</td>
<td>
<input type="hidden" class="depth" th:value="${boardCategory.depth}">
<input type="hidden" class="parentCategory" th:value="${boardCategory.parentCategory}">
<input type="text" class="form-control categoryName border-0 text-center bg-transparent" th:value="${boardCategory.categoryName}" readonly>
</td>
</tr>
</tbody>
</table>
</html>

View File

@ -9,32 +9,51 @@
</th:block>
<div layout:fragment="content">
<main class="pt-3">
<input type="hidden" name="_csrf_header" th:value="${_csrf.headerName}"/>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<h4>게시판 분류 관리</h4>
<div class="row mx-0">
<div class="col-auto card text-center">
<div class="card-body">
<div class="row justify-content-end">
<input type="button" class="col-auto btn btn-primary mx-3" value="저장">
<input type="button" class="col-auto btn btn-primary mx-3" id="saveCategoryBtn" value="저장">
</div>
<div class="row justify-content-start">
<div class="col-auto m-3 p-3 border">
<div class="row-cols-6" id="depth1Div">
<table class="table table-striped">
<thead>
<thead data-depth="1">
<tr>
<th></th>
<th>대분류</th>
</tr>
</thead>
<tbody id="depth1Category">
<tr class="categoryTr" th:each="boardCategory:${categoryList}">
<td>
<input type="hidden" class="categorySeq" th:value="${boardCategory.categorySeq}">
<input type="checkbox" class="trCheckBox" th:value="${boardCategory.depth}">
</td>
<td>
<input type="hidden" class="depth" th:value="${boardCategory.depth}">
<input type="hidden" class="parentCategory" th:value="${boardCategory.parentCategory}">
<input type="text" class="form-control categoryName border-0 text-center bg-transparent" th:value="${boardCategory.categoryName}" readonly>
</td>
</tr>
</tbody>
</table>
</div>
<div class="row justify-content-end">
<div class="col-auto">
<input type="button" class="btn btn-success addCategoryBtn" value="추가" data-depth="1">
</div>
<div class="col-auto">
<input type="button" class="btn btn-danger deleteCategoryBtn" value="삭제" data-depth="1">
</div>
</div>
</div>
<div class="col-auto m-3 p-3 border">
<div class="row-cols-6" id="depth2Div">
<table class="table table-striped">
<thead>
<tr>
@ -45,13 +64,18 @@
<tbody id="depth2Category">
</tbody>
</table>
</div>
<div class="row justify-content-end">
<div class="col-auto">
<input type="button" class="btn btn-success addCategoryBtn" value="추가" data-depth="2">
</div>
<div class="col-auto">
<input type="button" class="btn btn-danger deleteCategoryBtn" value="삭제" data-depth="2">
</div>
</div>
</div>
<div class="col-auto m-3 p-3 border">
<div class="row-cols-6" id="depth3Div">
<table class="table table-striped">
<thead>
<tr>
@ -62,13 +86,18 @@
<tbody id="depth3Category">
</tbody>
</table>
</div>
<div class="row justify-content-end">
<div class="col-auto">
<input type="button" class="btn btn-success addCategoryBtn" value="추가" data-depth="3">
</div>
<div class="col-auto">
<input type="button" class="btn btn-danger deleteCategoryBtn" value="삭제" data-depth="3">
</div>
</div>
</div>
<div class="col-auto m-3 p-3 border">
<div class="row-cols-6" id="depth4Div">
<table class="table table-striped">
<thead>
<tr>
@ -79,10 +108,14 @@
<tbody id="depth4Category">
</tbody>
</table>
</div>
<div class="row justify-content-end">
<div class="col-auto">
<input type="button" class="btn btn-success addCategoryBtn" value="추가" data-depth="4">
</div>
<div class="col-auto">
<input type="button" class="btn btn-danger deleteCategoryBtn" value="삭제" data-depth="4">
</div>
</div>
</div>
</div>
@ -94,8 +127,14 @@
<table>
<tbody id="appendTr">
<tr>
<td colspan="2">
<input type="text" class="form-control">
<td>
<input type="hidden" class="categorySeq">
<input type="checkbox" class="trCheckBox">
</td>
<td>
<input type="hidden" class="parentCategory">
<input type="hidden" class="depth">
<input type="text" class="form-control categoryName">
</td>
</tr>
</tbody>

View File

@ -24,5 +24,4 @@
</tr>
</tbody>
</table>
</html>

View File

@ -19,7 +19,51 @@
<input type="button" class="btn btn-outline-success" value="자료 등록"/>
</div>
<div sec:authorize="isAuthenticated()">
카테고리 메뉴 영역
<div class="flex-shrink-0 p-3 bg-white">
<ul class="list-unstyled ps-0">
<li class="mb-1">
<button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#home-collapse" aria-expanded="false">
Home
</button>
<div class="collapse" id="home-collapse" style="">
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
<li><a href="#" class="link-dark rounded">Overview</a></li>
<li><a href="#" class="link-dark rounded">Updates</a></li>
<li><a href="#" class="link-dark rounded">Reports</a></li>
</ul>
</div>
</li>
<li class="mb-1">
<button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#dashboard-collapse" aria-expanded="false">
Dashboard
</button>
<div class="collapse" id="dashboard-collapse" style="">
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
<li><a href="#" class="link-dark rounded">Overview</a></li>
<li><a href="#" class="link-dark rounded">Weekly</a></li>
<li><a href="#" class="link-dark rounded">Monthly</a></li>
<li><a href="#" class="link-dark rounded">Annually</a></li>
</ul>
</div>
</li>
<li class="mb-1">
<button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#orders-collapse" aria-expanded="false">
Orders
</button>
<div class="collapse" id="orders-collapse" style="">
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
<li><a href="#" class="link-dark rounded">New</a></li>
<li><a href="#" class="link-dark rounded">Processed</a></li>
<li><a href="#" class="link-dark rounded">Shipped</a></li>
<li><a href="#" class="link-dark rounded">Returned</a></li>
</ul>
</div>
</li>
</ul>
</div>
<th:block th:each="category:${session.categoryList}">
</th:block>
</div>
</div>
</html>

View File

@ -4,7 +4,28 @@
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout}">
<div layout:fragment="content" class="h-100" id="loginPage">
<div layout:fragment="content" class="h-100">
<div id="carouselExampleFade" class="carousel slide carousel-fade" data-bs-ride="carousel" style="z-index: 100">
<div class="carousel-inner">
<div class="carousel-item active">
<img th:src="@{/img/img01.jpg}" class="w-100" alt="배경1">
</div>
<div class="carousel-item">
<img th:src="@{/img/img02.jpg}" class="w-100" alt="배경2">
</div>
<!--<div class="carousel-item">
<img th:src="@{/img/img03.jpg}"class="d-block w-100" alt="...">
</div>-->
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleFade" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#carouselExampleFade" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
<div class="form-signin text-center mt-5 rounded bg-white">
<!-- Security config의 loginPage("url")와 action url과 동일하게 작성-->
<form action="/user/login" method="post">