Frond-end/Vue
[Vue3, SpringBoot, MariaDB] 이전글/ 다음글 구현하기
Nellie Kim
2024. 3. 21. 14:21
728x90
notice.xml
이전/다음글의 idx 와 글 제목을 map으로 받았다.
<!-- 이전/다음글 조회 -->
<select id="getDetailPrevAndNextIdx" parameterType="kr.co.medical.api.notice.dto.NoticeDto"
resultType="Map">
SELECT
idx,
title
FROM tb_notice
WHERE idx IN (
(SELECT idx FROM tb_notice WHERE idx <![CDATA[<]]> #{idx} AND del_yn = 'N' ORDER BY idx DESC LIMIT 1 ),
(SELECT idx FROM tb_notice WHERE idx <![CDATA[>]]> #{idx} AND del_yn = 'N' ORDER BY idx LIMIT 1 )
)
</select>
NoticeDto.java
@Data
public class NoticeDto extends BaseDto {
private Integer idx;
private String type;
private String title;
private String contents;
private LocalDateTime crteDt;
private String delYn;
private Integer viewCount;
private List<FileMngDto> images;
private List<Integer> fileIdList;
private Map<String,Object> prevAndNextIdx; // 이전글, 다음글 idx, title map
}
NoticeSvcImpl.java
noticeDao.getDetailPrevAndNextIdx(idx)로 가져온 맵을 아래와 같이 세팅해주었다.
public NoticeDto getDetail(Integer idx) {
NoticeDto noticeDto = new NoticeDto();
noticeDto.setIdx(idx);
List<NoticeDto> noticeList = getList(noticeDto);
if(noticeList.isEmpty()){
throw new BizException("param_wrong");
}else if(noticeList.size() > 1){
throw new BizException("org.springframework.dao.DuplicateKeyException");
}
noticeDto = noticeList.get(0);
List<FileMngDto> files = noticeDao.getNoticeFileList(idx);
noticeDto.setViewCount(noticeDto.getViewCount()+1);
// 이전/다음글의 idx, 글제목을 map에 세팅
List<Map<String, Object>> detailPrevAndNextIdx = noticeDao.getDetailPrevAndNextIdx(idx);
HashMap<String, Object> resultMap = new HashMap<>();
for (Map<String, Object> map : detailPrevAndNextIdx) {
if ((Integer) map.get("idx") < idx){
resultMap.put("prevIdx", map.get("idx"));
resultMap.put("prevTitle", map.get("title"));
}else{
resultMap.put("nextIdx", map.get("idx"));
resultMap.put("nextTitle", map.get("title"));
}
}
noticeDto.setPrevAndNextIdx(resultMap);
noticeDao.updateViewCount(noticeDto);
noticeDto.setImages(files.stream()
.map(fileMngSvc::setUrlAndBytes)
.collect(Collectors.toList()));
return noticeDto;
}
요청 결과
아래와 같이 Map으로 잘 출력이 되었다.
이렇게 받은 응답으로 프론트 개발을 할 것이다.
[idx].vue
vue로 공지사항 상세화면을 개발했다.
<script setup lang="ts">
import { onMounted } from 'vue'
import { getNoticeDetail, NoticeModel } from '@/apis/notice'
import { useRouter } from 'vue-router'
import { ImagePreview } from '@varlet/ui'
import { RETURN_CODE } from '@/apis/common'
/**
|--------------------------------------------------
| 😃 1. 변수
|--------------------------------------------------
*/
const isRefresh = ref(false)
const router = useRouter()
const props = defineProps<{ idx: number }>()
const noticeDetailData = ref<NoticeModel>()
/**
|--------------------------------------------------
| 🎁 2. 일반 함수
|--------------------------------------------------
*/
/** 1. url을 받아서 미리보기를 띄워주는 함수 */
function preview(url: string) {
ImagePreview(url)
}
/** 2. 토큰 확인 함수 */
function tokenError(code: string) {
if (/^30002\d{3}$/.test(code)) {
Snackbar.error('로그인 후 이용 부탁드립니다')
router.replace('/login')
}
}
/** 3. 새로고침 함수 */
function handleRefresh() {
isRefresh.value = false
}
/** 4. 이전글로 이동 */
function movePrevPage() {
const prevIdx = noticeDetailData.value?.prevAndNextIdx.prevIdx
console.log('prevIdx : ', prevIdx)
router.replace(`/hospital/news/${prevIdx}`)
fetchData2(prevIdx)
}
/** 5. 다음글로 이동 */
function moveNextPage() {
const nextIdx = noticeDetailData.value?.prevAndNextIdx.nextIdx
console.log('nextIdx : ', nextIdx)
router.replace(`/hospital/news/${nextIdx}`)
fetchData2(nextIdx)
}
/**
|--------------------------------------------------
| 🎁 3. fetch 함수
|--------------------------------------------------
*/
/**------------------------
| 🎈 fetch 함수 1
|------------------------*/
/** 1. 상세보기 호출 */
async function fetchData() {
const idx = props.idx
console.log('idx,,,,,,,,??', idx)
if (idx == 0) return
const request = {
idx: idx
}
const response = await getNoticeDetail(request)
if (response.code !== RETURN_CODE.SUCCESS) {
console.log('정보를 가져오는 중 오류가 발생했습니다.')
return
}
tokenError(response.code)
noticeDetailData.value = response.data
console.log('data.value,,,,,,,: ', noticeDetailData.value)
}
/**------------------------
| 🎈 fetch 함수 2
|------------------------*/
/** 1. 이전/다음글 클릭 시 상세보기 호출 */
async function fetchData2(idx: number) {
console.log('idx,,,,,,,,??', idx)
if (idx == 0) return
const request = {
idx: idx
}
const response = await getNoticeDetail(request)
if (response.code !== RETURN_CODE.SUCCESS) {
console.log('정보를 가져오는 중 오류가 발생했습니다.')
return
}
tokenError(response.code)
noticeDetailData.value = response.data
}
/**
|--------------------------------------------------
| 🎁 4. LifeCycle 함수
|--------------------------------------------------
*/
onMounted(() => {
fetchData()
})
</script>
<!-- /**
|--------------------------------------------------
| 🎁 화면
|--------------------------------------------------
*/ -->
<template>
<div class="message">
<var-pull-refresh v-model="isRefresh" @refresh="handleRefresh">
<app-header title="병원 소식">
<template #left>
<app-back />
</template>
<template #right>
<app-side-menu />
</template>
</app-header>
<div class="block"></div>
<div class="content">
<div class="message-item">
<div class="message-list">
<div class="message-item-detail-header-centered-box">
-제목 : {{ noticeDetailData?.title }}<br />
-안내 내용 : {{ noticeDetailData?.contents }}<br />
<div v-for="(img, i) in noticeDetailData?.images" :key="i">
<br />-이미지url(임시) {{ i + 1 }} : <br />
<!-- <img :src="img.url" @click="preview(img.url)" /> -->
<div>
{{ img.url }}
</div>
<img
src="/src/assets/images/avatar.jpg"
@click="preview('/src/assets/images/avatar.jpg')"
width="100%"
/><br />
</div>
<br />
</div>
</div>
</div>
</div>
</var-pull-refresh>
</div>
<br />
<br />
<div class="footer">
<hr />
<var-bottom-navigation active="">
<var-bottom-navigation-item
v-if="noticeDetailData?.prevAndNextIdx.prevIdx"
label="이전글"
icon="chevron-left"
style="color: blueviolet; white-space: pre-line"
@click="movePrevPage"
/>
<var-bottom-navigation-item v-else label="이전글" icon="chevron-left" style="color: darkgrey" />
<var-bottom-navigation-item
v-if="noticeDetailData?.prevAndNextIdx.nextIdx"
label="다음글"
icon="chevron-right"
style="color: blueviolet"
@click="moveNextPage"
/>
<var-bottom-navigation-item v-else label="다음글" icon="chevron-right" style="color: darkgrey" />
</var-bottom-navigation>
<div style="color: blueviolet; font: 0.6em sans-serif; margin: 10px">
<span v-if="noticeDetailData?.prevAndNextIdx.prevIdx" style="margin-left: 15%; float: left">
{{ noticeDetailData?.prevAndNextIdx.prevTitle }}
</span>
<span v-if="noticeDetailData?.prevAndNextIdx.nextIdx" style="margin-right: 15%; float: right">
{{ noticeDetailData?.prevAndNextIdx.nextTitle }}
</span>
</div>
</div>
</template>
<style lang="less" scoped>
.container {
display: flex;
flex-direction: column;
}
.content {
padding: 20px;
}
.centered-submit {
display: flex;
justify-content: center;
}
.request-title {
font-weight: bold;
margin-bottom: 10px;
}
.nurse-textarea {
width: 100%;
min-height: 100px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
margin-bottom: 20px;
}
.submit-btn {
padding: 10px 20px;
background-color: var(--color-primary);
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
width: 90%;
}
.request-title {
font-weight: bold;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.request-date {
font-weight: normal;
font-size: 0.9em;
}
.block {
height: 50px;
}
.message-list {
padding: 10px 0;
overflow-y: auto; /* 세로 스크롤 활성화 */
max-height: calc(100vh - var(--app-bar-height)); /* 스크롤 높이 조정 */
}
.footer {
margin-top: 500px;
}
</style>
최종 결과물
css가 안되어있으니 너무 허접하다.
어쨌든 원했던대로, 이전글/다음글 구현도 완료하였고 아래에 해당 글의 제목이 나타나도록 구현을 완료했다.