나의 수업일지

인천 일보 아카데미 58~67일 차 개인 프로젝트 - NAVER 지식in 클론 코딩 (7)

GUPING 2023. 6. 11. 04:22
  • 네이버 지식인 QnA글 목록 화면

012
1번 사진은 실제 네이버 지식인 QnA게시판

카테고리는 애초에 만들 생각이 없었기 때문에 지웠고

당연히 관심질문 등 유저의 정보를 나타내는 공간이 필요 없다고 생각하여 뺏습니다

또한 질문 게시판과 QnA게시판의 차이를 모르겠어서 그냥 QnA게시판만 만들기로 했습니다.

 

  • boardQnA.jsp - script

main화면의 QnA 리스트와 크게 다른점이 없지만

제목으로만 보기 ,  내용까지 보기 버튼을 추가했다는 약간의 차이점이 있습니다.

어떻게 만들어야할지 고민하다가 그냥 무식하고 편한 방법으로

2가지 상황의 Container를 2개 만들고,

버튼 클릭에 따라 display를 none / block으로 바뀌게 만들었습니다

<script>
    const previewTypeQna = () => {
    제목 / 내용 미리보기 List의 display를 변경해주는 함수
    
        const previewTypeQna = document.getElementById('previewTypeQna');
        내용까지 보기 display 변경을 위한 document 객체
        const titleTypeQna = document.getElementById('titleTypeQna');
        제목만 보기 display 변경을 위한 document 객체
        const numResult = document.getElementById('numResult');
        const ajaxNumResult = document.getElementById('ajax-numResult');
        내용까지 보기 상태에서 다음페이지로 넘어가는 경우
        인덱스의 qnaList와 마찬가지로 화면의 맨위로 올라가는 상황이였기 떄문에
        페이징을 ajax와 일반 페이징으로 2가지 사용하였습니다.
        
        $('#type_title').removeClass('type_title1');
        $('#type_preview').removeClass('type_preview1');
        $('#type_title').addClass('type_title');
        $('#type_preview').addClass('type_preview');
        previewTypeQna.style.display = "block";
        numResult.style.display = "block";
        titleTypeQna.style.display = "none";
        ajaxNumResult.style.display = "none";
        List를 보고 있는 메뉴의 상태를 생상에 따라 알려주고 있기 떄문에
        class이름으로 스타일을 주고 제이쿼리를 사용하여
        클릭에 따라 클래스의 이름을 변경 스타일이 변경될 수 있게 만들었습니다
        
    }
    const titleTypeQna = () => {
        const titleTypeQna = document.getElementById('titleTypeQna');
        제목만 보기 display 변경을 위한 document 객체
        const previewTypeQna = document.getElementById('previewTypeQna');
        내용까지 보기 display 변경을 위한 document 객체
        const numResult = document.getElementById('numResult');
        const ajaxNumResult = document.getElementById('ajax-numResult');
        위의 설명과 마찬가지로 페이징을 보여주는 document 객체
        
        $('#type_title').removeClass('type_title');
        $('#type_preview').removeClass('type_preview');
        $('#type_title').addClass('type_title1');
        $('#type_preview').addClass('type_preview1');
        previewTypeQna.style.display = "none";
        numResult.style.display = "none";
        titleTypeQna.style.display = "block";
        ajaxNumResult.style.display = "block";
        List를 보고 있는 메뉴의 상태를 생상에 따라 알려주고 있기 떄문에
        class이름으로 스타일을 주고 제이쿼리를 사용하여
        클릭에 따라 클래스의 이름을 변경 스타일이 변경될 수 있게 만들었습니다
        
    }
    const qnaListNBBtn = (qnaPagingPage, qnaSearch) => {
    제목 / 내용 미리보기 상태의 페이징
    맨위로 올라가는 상황을 방지하고자 ajax를 사용하였음
    
        const qnaPage = qnaPagingPage;
        사용자가 원하는 page의 nuber를 받아왔습니다
        const q = qnaSearch;
        사용자가 검색을 하는 경우를 대비해
        검색어를 받아와줍니다 비어있는 경우,
        controller에서 설정한 기본값으로 적용됩니다
        const numResult = document.getElementById('ajax-numResult-numBtn');
        페이징에 사용되는 number를 담고있는 태그의 객체
        const backBtnResult = document.getElementById('backBtnResult');
        페이징에 사용되는 "이전"버튼을 담고있는 태그의 객체
        const nextBtnResult = document.getElementById('nextBtnResult');
        페이징에 사용되는 "다음"버튼을 담고있는 태그의 객체
        const previewTypeQna = document.getElementById('previewTypeQna');
        제목/내용 미리보기 List를 담고있는 태그의 객체
        
        $.ajax({
            type: "post",
            url: "/boardQna/UDPage",
            data: {
                "qnaPage": qnaPage,
                "q": q
                페이징에 사용되는 ajax이기 때문에
                해당 페이지의 리스트와 검색어를 통한 리스트를
                가져오기 위한 page , q 를 매개변수로 보내줍니다.
            },
            success: function (res) {
                let outPut = "";
                List정보를 문자열로 담아줄 output
                let numPut = "";
                하단의 페이징의 문자열을 number를 담아줄 numPut
                let downPut = "";
                "이전"버튼의 문자열을 담아줄 downPut
                let upPut = "";
                "다음"버튼의 문자열을 담아줄 upPut
                downPut += '<a class="QnA-back-bnt-on" style="cursor: pointer;" onclick="qnaListNBBtn(' + (res.qnaBoardPage.page - 1) + ',' + res.qnaBoardPage.q + ')">이전</a>';
                "이전"페이지로 넘어가는 버튼을 문자열로 표현하여 downPut에 담아줍니다
                upPut += '<a class="QnA-next-bnt-on" style="cursor: pointer;" onclick="qnaListNBBtn(' + (res.qnaBoardPage.page + 1) + ',' + res.qnaBoardPage.q + ')">다음</a>';
                "다음"페이지로 넘어가는 버턴을 문자열로 표현하여 upPut에 담아줍니다
                for (let i in res.qnaBoardDTOList) {
                List의 정보는 for문을 사용하여 뽑아줍니다
                +=를 사용하여 이전에 안에 들어있던 무자열에 덮어씌어지는것을 방지하고 이어 붙여 담아줍니다.
                    outPut += '<div class="answer_box" style="border-top: 1px solid #cacccc;position: relative;">';
                    outPut += '<div class="tit_wrap">'
                    outPut += '<a href="/board/detail?BoardId=' + res.qnaBoardDTOList[i].id + '" target="_blank" class="tit_wrap_link_a">';
                    if(res.qnaBoardDTOList[i].boardPoint != 0){
                    글목록을 가져오며 해당 글에 걸려있는 POINT있다면 출력
                    걸려있는 POINT가 없다면 굳이 출력하지 않기위함
                        outPut += '<span class="power_grade" style="margin-right: 10px;" title="내공 전시장">'+ res.qnaBoardDTOList[i].boardPoint +'</span>';
                    }
                    outPut += '<span class=tit_txt">' + res.qnaBoardDTOList[i].boardTitle + '</span>';
                    outPut += '</a>';
                    outPut += '</div>';
                    outPut += '<div class="update_info">';
                    outPut += '<span class="num_answer">';
                    outPut += '답변 <em>' + res.qnaBoardDTOList[i].boardAnswer +'</em>';
                    outPut += '</span>';
                    outPut += '<span class="info">';
                    let nowTime = new Date().getTime(); // 현재 시간을 밀리초로 가져옴
                    let commentDate = new Date(res.qnaBoardDTOList[i].boardCreatedDate); // DTO의 boardCreatedDate를 JavaScript Date 객체로 변환
                    let timeDifference = (nowTime - commentDate) / (1000 * 60); // 분 단위로 시간 차이 계산
                    if (timeDifference <= 10) {
                        outPut += '방금 전';
                    } else if (timeDifference > 10 && timeDifference <= 60) {
                        outPut += moment(timeDifference) + '분 전';
                    } else if (timeDifference > 60 && timeDifference <= 60 * 24) {
                        timeDifference = (timeDifference / 60);
                        outPut += moment(timeDifference) + '시간 전';
                    } else if (timeDifference > 60 * 24 && timeDifference <= 60 * 24 * 30) {
                        timeDifference = (timeDifference / (60 * 24));
                        outPut += moment(timeDifference) + '일 전';
                    } else if (timeDifference > 60*24 && timeDifference <= 60*24*30){
                        outPut += moment(timeDifference) + '일 전';
                    } else {
                        outPut += moment(res.qnaBoardDTOList[i].boardCreatedDate).format("YYYY-MM-DD HH:mm");
                    }
                    글이 게시된 시간을 분 전 , 시간 전 , 일 전 으로 출력하기 위해
                    계산식을 사용하여 IF문을 통해 맞는 정보만 문자열에 추가합니다.
                    outPut += '</span>';
                    outPut += '</div>';
                    outPut += '</div>';
                }
                for (let i = res.qnaBoardPage.startPage; i <= res.qnaBoardPage.endPage; i++) {
                하단에 보여질 number 숫자 버튼을 for문을 통해 출력하며
                numPut에 저장합니다 .
                    if (res.qnaBoardPage.page == i) {
                        numPut += '<a class="QnA-paging-bnt-off">' + i + '</a>';
                    } else {
                        numPut += '<a class="QnA-paging-bnt-on" style="cursor: pointer;" onclick="qnaListNBBtn(' + i + ',' + res.qnaBoardPage.q + ')">' + i + '</a>';
                    }
                    for문을 통해 출력하며 사용자가 보아야하는 page와 같은 수라면
                    비활성화한 버튼태그를 담아줍니다.
                }
                if (res.qnaBoardPage.page <= 1) {
                    backBtnResult.innerHTML = "";
                    맨 마지막페이지라면 "다음" 버튼을 지웁니다.
                } else {
                    backBtnResult.innerHTML = downPut;
                    맨 마지막 페이지가 아니라면 "다음"버튼의 문자열을 담은
                    변수를 해당 태그의 객체 innerHTML에 담아 출력합니다
                }
                if (res.qnaBoardPage.page == res.qnaBoardPage.maxPage) {
                    nextBtnResult.innerHTML = "";
                    처음 페이지라면 "이전" 버튼을 지웁니다.
                } else if (res.qnaBoardPage.page != res.qnaBoardPage.maxPage) {
                    nextBtnResult.innerHTML = upPut;
                    처음 페이지가 아니라면 "이전" 버튼의 문자열을 담은
                    변수를 해당 태그의 객체 innerHTML에 담아 출력합니다
                }
                numResult.innerHTML = numPut;
                ListResult.innerHTML = outPut;
                위에서 담은 문자열의 변수를 객체로 만든 Result 객체의
                innerHTML에 담아 출력합니다.
            },
            error: function () {
                console.log("실패");
            }
        })
        previewTypeQna.scrollIntoView({behavior: 'auto'});
        페이징 버튼이 너무 아래있으면 다음페이지로 넘어가는 경우
        List의 최상단으로 스크롤을 이동시킵니다.
    }
</script>

 

  • Controller
    QnA페이지로 이동시 사용되는 GetMapping
    @GetMapping("/board/Qna")
    public String boardQna(@RequestParam(value = "qnaPage", required = false,defaultValue = "1")int qnaPage,
                           @RequestParam(value = "q",required = false,defaultValue = "") String q,
                           Model model,HttpSession session){
        PageDTO qnaPageDTO = new PageDTO();
        qnaPageDTO.setPage(qnaPage);
        페이징에 사용되는 PageDTO
        model.addAttribute("qnaBoardDTOList",boardService.qnaBoardList(qnaPageDTO,q));
        페이징을통해 가져온 List를 담아주는 Model
        model.addAttribute("qnaPaging",boardService.qnaPagingParam(qnaPageDTO,q));
        페이징을통해 가져온 하단의 버튼정보를 담아주는 model
        model.addAttribute("memberDTO", boardService.findById(session.getAttribute("memberId")));
        QnA페이지 에서도 Header에 있는 Layout을 사용해야하기 때문에 사용자의 정보를 담아주는 model
        return "/boardPage/boardQna";
    }
    
    QnA페이지의 ajax를 통한 페이징에 사용되는
    PostMapping
    @PostMapping("/boardQna/UDPage")
    public ResponseEntity boardQnaUD(@RequestParam(value = "qnaPage", required = false,defaultValue = "1")int qnaPage,
                                     @RequestParam(value = "q",required = false,defaultValue = "") String q) {
        PageDTO qnaPageDTO = new PageDTO();
        qnaPageDTO.setPage(qnaPage);

        Map<String,Object> boardQnaResponse = new HashMap<>();
        boardQnaResponse.put("qnaBoardDTOList",boardService.qnaBoardList(qnaPageDTO,q));
        boardQnaResponse.put("qnaBoardPage",boardService.qnaPagingParam(qnaPageDTO,q));
        Ajax가 사용되었기 떄문에 List와 paging 정보 2가지를 넘겨야 하는 상황으로
        Map을 사용하여 하나로 압축하여 넘겨줍니다.
        
        return new ResponseEntity<>(boardQnaResponse,HttpStatus.OK);
    }

 

  • Service

Main페이지인 index에서 사용된 QnA리스트와 페이징 메소드를 공유합니다

Main 페이지의 경우 전날 해설을 마쳤기 때문에 굳이 해설하지 않겠습니다

    public Object qnaBoardList(PageDTO qnaPageDTO, String q) {
        qnaPageDTO.setPageLimit(10);
        qnaPageDTO.setQ(q);
        List<BoardDTO> qnaBoardDTOList = boardRepository.qnaBoardDTOList(qnaPageDTO);
        return qnaBoardDTOList;
}

    public Object qnaPagingParam(PageDTO qnaPageDTO, String q) {
        qnaPageDTO.setBlockLimit(10);
        qnaPageDTO.setQ(q);
        qnaPageDTO.setBoardCount(boardRepository.BoardCount(qnaPageDTO));
        if (qnaPageDTO.getEndPage() > qnaPageDTO.getMaxPage()) {
            qnaPageDTO.setEndPage(qnaPageDTO.getMaxPage());
        }
        return qnaPageDTO;

    }

 

  • Repository

Main페이지인 index에서 사용된 QnA리스트와 페이징 메소드를 공유합니다

Main페이지의 경우 전날 해설을 마쳤기 때문에 굳이 해설하지 않겠습니다.

    public int BoardCount(PageDTO qnaPageDTO) {
        if (!(qnaPageDTO.getQ().equals(""))) {
            return sql.selectOne("naverBoard.BoardSearchCount", qnaPageDTO);
        } else {
            return sql.selectOne("naverBoard.BoardCount");
        }
    }

    public List<BoardDTO> qnaBoardDTOList(PageDTO qnaPageDTO) {
        if (!(qnaPageDTO.getQ().equals(""))) {
            return sql.selectList("naverBoard.qnaSearchBoardList", qnaPageDTO);
        } else {
            return sql.selectList("naverBoard.qnaBoardList", qnaPageDTO);
        }
    }

 

  • Mapper

Main페이지인 index에서 사용된 QnA리스트와 페이징 메소드를 공유합니다

Main페이지의 경우 전날 해설을 마쳤기 때문에 굳이 해설을하지 않겠습니다.

    <select id="qnaBoardList" parameterType="page" resultType="board">
        select * from board_table order by boardCreatedDate desc limit #{pageStart},#{pageLimit}
    </select>
    
    <select id="qnaSearchBoardList" parameterType="page" resultType="board">
        select *
        from board_table
        where boardContents like concat('%', #{q}, '%') or boardTitle like concat('%', #{q}, '%')
        order by id desc limit #{pageStart}, #{pageLimit}
    </select>
    
    <select id="BoardCount" resultType="Integer">
        select count(id) from board_table
    </select>
    
    <select id="BoardSearchCount" parameterType="page" resultType="Integer">
        select count(id)
        from board_table
        where boardContents like concat('%', #{q}, '%')
           or boardTitle like concat('%', #{q}, '%')
    </select>