로그인 후 이전 페이지, 이전글, 다음글,답글 목록, 메뉴 분리, 수동 pagination, 주소 api

keep_going·2022년 12월 28일
0

vue

목록 보기
8/13

// 파일명 : route/index.js_수정: 로그인 여부에 따라 이동하는 페이지 달라짐


import store from '../stores/index' ;
// 수동으로 import시킴

// 이전페이지 이동페이지 정보 확인용
router.beforeEach((to, from, next)=>{
    console.log(to, from); // 위에 찍힌게 to, 밑에 찍인게 from
 
	// 홈에 있다가 주문하기 누를때 로그인이 안되어 있다면 로그인 페이지로 가야하는데
  	// 로그인 이후 이전페이지로 보내면 로그인페이지로 이동
    // 로그인, 로그아웃은 되돌아가려는 페이지가 아니다! 기록하면 안됨
    if(to.path !== '/login' && to.path !== '/logout') {
      	// 서버 : 포트번호/itemcontent
        sessionStorage.setItem("CURRENT_PATH", to.path);
      	// 저장소에는 object 타입 저장 불가능->stringify로 문자로 바꿔줌 ex) ?no=580
        sessionStorage.setItem("QUERY", JSON.stringify(to.query));
      	// sessionStorage에 각각의 페이지 방문할때 마다 저장
        // url의 앞부분(/itemcontent)은 path에 ?뒷부분(no=580)은 query에 보관! 
		// 주문하기 눌렀을때 바로 로그인 페이지로 넘어가면 
        // 로그인 이후 이전페이지로 보내면 
      	// 다시 주문수량 선택하는 페이지(itemcontent)로 옴. 
        // 주문 정보가 기록이 안된것! 
        // 주문하는 정보 기록하기 위해 보여주지 않아도 주문페이지(order)로 갔다가 로그인 페이지로 가야함. 
        if(to.path === '/order' || to.path==="/test" ) { // ||는 or의 의미		// 로그인 되어야 표시되는 페이지들 ex)주문페이지... or로 다른 페이지 더 넣어도됨!
            const logged = store.getters.getLogged;
            // 로그인 여부를 store에서 받아옴
            console.log(logged);
            if(logged === false) { // 로그인 되어 있지 않으면
                next({path:'/login'}); //강제로 페이지 변경
                return; //함수종료, 아래쪽 next수행하지 않기 위해서
            }
        }
    }

    next(); // () 안에 페이지가 없으면 기존에 이동하고자하는 페이지로 이동
});

// 3. 모듈 export
export default router;

// 파일명 : LoginPage.vue_수정:로그인 후 홈이 아니라 이전페이지로 이동

// 로그인 후 홈으로 이동이 아니라 이전페이지로 이동 필요
// 이전페이지 정보를 보관해야함. 
// router에 페이지 이동 정보가 있다. 컴포넌트 호출될때마다(페이지 이동시 마다) 내용이 찍힌다.
const path = sessionStorage.getItem("CURRENT_PATH");
const query = JSON.parse(sessionStorage.getItem("QUERY"));
// parse를 이용해 문자를 object로 복원 

store.commit('setDefaultActive', path);
// 로그인 후 활성화되는 메뉴도 이전페이지껄로

// 이전페이지로 이동    
router.push({path:path, query:query});

// 파일명 : MenuPage.vue

  • App.vue에 있던 메뉴들이 너무 길어져서 따로 component로 빼냄. import나 함수들도 통째로 다 가져와야 한다!
<template>
    <div>
        <el-menu :mode="state.mode" :router="state.router" 
        :default-active="state.defaultActive"  @select="handleSelect">
            <el-menu-item index="/"></el-menu-item>
            <el-menu-item index="/seller">판매자</el-menu-item>
            <el-menu-item index="/login" v-if="state.logged === false">로그인</el-menu-item>
            <el-menu-item index="/join"  v-if="state.logged === false">회원가입</el-menu-item>
            <el-menu-item index="/logout" v-if="state.logged === true">{{state.userid}}님 로그아웃</el-menu-item>
        </el-menu>
    </div>
</template>

<script>
import { reactive } from '@vue/reactivity'
import { useStore } from 'vuex';
import { computed } from '@vue/runtime-core';

export default {
    setup () {
        
        const store = useStore();
        
        const state = reactive({
            mode: 'horizontal',
            router : true,
            logged : computed(() => store.getters.getLogged),
            userid : computed(() => store.getters.getUserid),
            defaultActive : computed(() => store.getters.getDefaultActive),
        });

        const handleSelect = (e) =>{
            console.log(e);
            store.commit('setDefaultActive', e);
        }

        return {state, handleSelect}
    }
}
</script>

<style lang="scss" scoped>

</style>

// 파일명: App.vue_수정: 메뉴 빠지고 footer 추가한 형태


<template>
  <div>
    <!-- 여기서 그리드를 잘 써야 한다. 여기는 간단하게 배치만 하는것! -->
    <menu-page></menu-page> 
    <!-- // 메뉴 -->
    <router-view></router-view> 
    <!-- // 라우터 설정된 컴포넌트들 교체 되는 위치 -->
    <footer-page></footer-page> 
    <!-- // footer -->
  </div>
</template>

<script>

import FooterPage from '@/components/FooterPage.vue' ; // footer페이지 추가
import MenuPage from '@/components/MenuPage.vue' // menu페이지 추가

export default {
  components: {
    FooterPage, // <footer-page> 
    MenuPage // <menu-page>
  },
  setup () {
    
    return {}
  }
}
</script>

<style lang="css" scoped>

</style>

// 파일명: JoinPage.vue_수정: 지도 api 추가

<template>
    <div>
        <h3>회원가입</h3>
        {{ state }}

        <input type="text" id="sample6_postcode" 
            v-model="state.postcode" placeholder="우편번호">
        <input type="button" @click="sample6_execDaumPostcode()" value="우편번호 찾기"><br>
        <input type="text" id="sample6_address" v-model="state.address" placeholder="주소"><br>
        <input type="text" id="sample6_detailAddress" v-model="state.detail" placeholder="상세주소">
        <input type="text" id="sample6_extraAddress" v-model="state.extra" placeholder="참고항목">
        <hr />
    </div>
</template>

<script>
// api 가져와서 쓸때 vue나 react 용은 거의 없다. 
// 대부분이 자바스크립트용으로 script src 형태로 라이브러리를 주는데
// vue는 npm i axios --save같은 방식으로 라이브러리를 설치한뒤 import해서 사용한다.
// 결국 vue 안에 javascript를 넣어서 적절히 사용할 줄 알아야함.
          
import { onMounted } from '@vue/runtime-core';

export default {
    setup () {
        const state = reactive({
            postcode : '',
            address : '',
            detail : '',
            extra :'',
        });

        // 페이지가 로딩될 때
        onMounted(() => {
            // console.log(window);
          	// 자바스크립트에서 라이브러리를 추가한다는 것은 window에 추가한다는것
          	// 이 작업을 수동으로 해줘야한다
            let script = document.createElement('script');
            script.src ="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js";
            document.head.appendChild(script); // window에 스크립트 추가
            console.log(window); // window에 없던 daum이 생김
        });

      	// 추가된 라이브러리를 이용한 함수
        const sample6_execDaumPostcode = () => {
            console.log('sample6_execDaumPostcode');
            new window.daum.Postcode({ // daum을 window안에 넣었으니 window.을 붙여줘야한다. 속에 내용은 그대로 가져와서 사용
                oncomplete: function(data) {
                    // 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.

                    // 각 주소의 노출 규칙에 따라 주소를 조합한다.
                    // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
                    var addr = ''; // 주소 변수
                    var extraAddr = ''; // 참고항목 변수

                    //사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
                    if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
                        addr = data.roadAddress;
                    } else { // 사용자가 지번 주소를 선택했을 경우(J)
                        addr = data.jibunAddress;
                    }

                    // 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
                    if(data.userSelectedType === 'R'){
                        // 법정동명이 있을 경우 추가한다. (법정리는 제외)
                        // 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
                        if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
                            extraAddr += data.bname;
                        }
                        // 건물명이 있고, 공동주택일 경우 추가한다.
                        if(data.buildingName !== '' && data.apartment === 'Y'){
                            extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
                        }
                        // 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
                        if(extraAddr !== ''){
                            extraAddr = ' (' + extraAddr + ')';
                        }
                        // 조합된 참고항목을 해당 필드에 넣는다.
                        document.getElementById("sample6_extraAddress").value = extraAddr;
                    
                    } else {
                        document.getElementById("sample6_extraAddress").value = '';
                    }

                    // 우편번호와 주소 정보를 해당 필드에 넣는다.
                    document.getElementById('sample6_postcode').value = data.zonecode;
                    state.postcode = data.zonecode; // v-model과 연동시키기 위함!!!! 이거 없으면 손으로 입력한 값만 연동된다.
                    
                    document.getElementById("sample6_address").value = addr;
                    state.address = addr;
                    // 커서를 상세주소 필드로 이동한다.
                    document.getElementById("sample6_detailAddress").focus();
                }
            }).open();
        };
    
        return {
            sample6_execDaumPostcode,
        };
    }
}
</script>

// 파일명: BoardInsertPage.vue

  • 게시판 관련 컴포넌트들은 따로 폴더 만들어서 관리
<template>
    <div class="container">
        <h3>게시판 글쓰기</h3>
        {{ state }}
        <div class="item">
            <label class="lbl">제목</label>
            <input type="text" v-model="state.title" placeholder="제목" autofocus />
        </div>

        <div class="item">
            <label class="lbl">내용</label>
            <textarea rows="6" cols="22" v-model="state.content" placeholder="내용"></textarea>
        </div>

        <div class="item">
            <label class="lbl">작성자</label>
            <input type="text" v-model="state.writer" placeholder="작성자" />
        </div>

        <div class="item">
            <label class="lbl">이미지</label>
            <input type="file" @change="handleImage($event)" />
        </div>

        <div class="item">
            <label class="lbl"></label>
            <button>글쓰기</button>
        </div>
    </div>
</template>

<script>
import { reactive } from '@vue/reactivity'
import axios from 'axios';
import { useRouter } from 'vue-router';
export default {
    setup () {
        const router = useRouter();

        const state = reactive({
            title   : '',
            content : '',
            writer  : '',
            file    : null,
        })

        const handleImage = (e) => {
            console.log(e);
            if(e.target.files.length > 0) {
                state.file = e.target.files[0];
            }
            else {
                state.file = null;
            }
        };

        const handleInsert = async() => {
            // 유효성 검사
            const url = `/board101/insertimage.json`;
            const headers = {"Content-Type":"multipart/form-data"};
            const body = new FormData();
          	// 백엔드에서 요구하는 데이터값들
            body.append("title", state.title);
            body.append("content", state.content);
            body.append("writer", state.writer);
            // 파일명, 크기, 내용, 종류
            body.append("image", state.file); 

            const { data } = await axios.post(url, body, {headers});
            console.log(data);
            if(data.status === 200) {
                router.push({path:'/boardselect'});
            }

        };

        return {
            state,
            handleImage,
            handleInsert,
        }
    }
}
</script>

<style lang="css" scoped>
    .container {
        width   : 600px;
        padding : 10px;
        margin  : 5px;
        border  : 1px solid #cccccc;
    }

    .lbl {
        display : inline-block;
        width   : 100px;
    }

    .item {
        margin-bottom   : 5px;
    }

</style>

게시판 글쓰기 => http://1.234.5.158:23000/board101/insertimage.json => {title:'', content:'b', writer:'c', image:'이미지'}
게시판 목록 => http://1.234.5.158:23000/board101/selectlistimage.json?page=1&text=

게시판 상세 => http://1.234.5.158:23000/board101/selectoneimage.json?no=글번호
답글 조회 => http://1.234.5.158:23000/board101/selectreply.json?brdno=게시글번호

** 답글 작성 => http://1.234.5.158:23000/board101/insertreply.json => { brdno:게시글번호, content:답글내용, writer:'답글작성자' }


// 파일명: BoardSelectPage.vue

<template>
    <div>
        <h3>게시판 목록</h3>
        <div>
            <table border="1">
                <thead>
                    <tr>
                        <th>글번호</th>
                        <th>제목</th>
                        <th>이미지</th>
                        <th>작성자</th>
                        <th>조회수</th>
                        <th>등록일</th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="tmp of state.rows" :key="tmp">
                        <td>{{ tmp._id }}</td>
                        <td @click="handleContent(tmp._id)" style="cursor:pointer;">{{ tmp.title }}</td>
                        <td><img :src= "tmp.img" style="width: 100px;" /></td>
                        <td>{{ tmp.writer }}</td>
                        <td>{{ tmp.hit }}</td>
                        <td>{{ tmp.regdate }}</td>
                    </tr>
                </tbody>
            </table>

            // component 이용안하고 수동으로 pagination 만든것
            <div v-for="tmp of state.pages" :key="tmp" style="display: inline-block;">
                <button @click="handlePage(tmp)">{{ tmp }}</button>
            </div>
            // 새로고침해도 페이지 유지하고싶으면 파라미터 이용해야한다
        </div>        
            </div>
</template>

<script>
import { onMounted, reactive } from '@vue/runtime-core';
import axios from 'axios';
import { useRoute, useRouter } from 'vue-router';
export default {
    setup () {
        // 변수 설정----------------------------------------
        const route = useRoute();
        const router = useRouter();
        //-------------------------------------------------

        // 상태 변수----------------------------------------
        const state = reactive({
            page  : 1,
            text  : '',
            rows  : [],
            total : 0,
            pages : 0,
        });
        //--------------------------------------------------

        // 게시물 목록 데이터 읽기(목록, 전체 게시물 수)-------
        const handleData = async() => {
            const url = `/board101/selectlistimage.json?page=${state.page}&text=${state.text}`;
            const headers = {"Content-Type":"application/json"};
            const { data } = await axios.get(url, {headers});
            console.log(data);
            if(data.status === 200) {
                state.rows = data.rows;
                state.total = data.total;
                state.pages = Math.floor((data.total-1)/10) + 1 ; // pagination 직접 계산한것
            }
        };
        //--------------------------------------------------

        // 게시물 상세 페이지로 이동----------------------------
        // boardcontent?no=게시물번호
        const handleContent = (id) => {
            console.log(id); // 글 번호 정보
            router.push({path:'/boardcontent', query:{no:id}});
        };
      	//--------------------------------------------------

        // 페이지네이션---------------------------------------
        const handlePage = (tmp) => {
            state.page = tmp;
            router.push({path:'/boardselect', query:{page:tmp}});
            // 새로 고침해도 pagination 유지 위해서 주소에 query 넣어주는것 (주소 바꿔줌)
            handleData();
        };
      	//--------------------------------------------------

        // f5누를때 호출됨, page 유지 위해서 query(page)가져오기-
        onMounted(() => {
            // 주소창의 ?page=1 정보를 읽음!!!!!!!!!!!!!!!!!!!
            const page = route.query.page;
            // page 정보가 없으면 초기값 1, 있으면 읽은 정보로
            if(typeof(page) !=='undefined') {
                state.page = Number(page);
            }
            handleData();
        });
      	//--------------------------------------------------

        return {
            state,
            handlePage,
            handleContent
        };
    }
    // 정보를 보관하기 위해서는 저장소에 보관하거나 query에 보관해야함 
    // 노출되도 되는 정보를 주로 query에 보관. 아이디나 암호는 노출되면 안되니 storage에 보관
}
</script>

<style lang="css" scoped>

</style>

// 파일명: BoardContentPage.vue

<template>
    <div>
        {{ state }}
        <p>글번호: {{ state.row._id }}</p>
        <p>제목: {{ state.row.title }}</p>
        <p>내용: {{ state.row.content }}</p>
        <p>이미지: <img :src="state.row.img" style="height: 400px;"/></p>
        <p>작성자: {{ state.row.writer }}</p>
        <p>조회수: {{ state.row.hit }}</p>
        <p>등록일: {{ state.row.regdate }}</p>
        <button>수정</button>
        <button>삭제</button>
        <button @click="handlePrev()">이전글</button>
        <button @click="handleNext()">다음글</button>
        <hr />

        <div v-for="tmp of state.rows" :key="tmp">
            <p>답글 내용: {{ tmp.content }}</p>
            <p>작성자: {{ tmp.writer }}</p>
            <hr />
        </div>
        <input type="text" v-model="state.recontent" placeholder="답글쓰기" />
        <input type="text" v-model="state.rewriter" placeholder="답글작성자" />
        <button @click="handleRe()">답글 쓰기</button>
    </div>
</template>

<script>
import { onMounted, reactive } from '@vue/runtime-core';
import axios from 'axios';
import { useRoute, useRouter } from 'vue-router';

export default {

    setup () {
        const route = useRoute();     
        const router = useRouter();
        const state = reactive({
            no      : Number(route.query.no),
            row     : '',
            prev    : 0,
            next    : 0,
            rows    : '',
            recontent : '',
            rewriter: '',
        });   

        
        const handleRe = async() => {
            const url = `/board101/insertreply.json`;
            const headers = {"Content-Type":"application/json"};
            const body = {
                brdno: state.no,
                content : state.recontent,
                writer: state.rewriter,
            }
            const { data } = await axios.post(url, body, {headers});
            console.log('handleRe =>', data);
            if(data.status === 200) {
                alert('답글이 작성되었습니다.');
                handleData1();
                state.recontent = '';
                state.rewriter = '';
            }

        };

        const handlePrev = () => {
            if(state.prev === 0) {
                alert('이전글이 없습니다.')
                router.push({path:"/boardcontent", query:{no:state.no}});
            }
            else {
                router.push({path:"/boardcontent", query:{no:state.prev}});
                state.no = state.prev;
                handleData();
                handleData1();
            } // else 없이 짜니 이전글로 안넘어 가긴 하는데 no가 null이라고 오류가 나네...?
        };

        const handleNext = () => {
            if(state.next !== 0) {
                router.push({path:"/boardcontent", query:{no:state.next}});
                state.no = state.next;
                handleData();
                handleData1();
            }
            else {
                alert('다음글이 없습니다.')
                router.push({path:"/boardcontent", query:{no:state.no}});
            } // else 없이 짜니까 다음글이 있을때 다음글 없습니다 뜨고 다음글로 넘어감...? 

         	// if를 값이 0일때로 잡냐 0이 아닐때로 잡냐 일부러 다르게 해봤는데 실행에 있어서 차이는 아직 잘 모르겠다.. 백엔드 배우면 메모리 잡아먹는 정도가 다른게 보일지도..?
          
            // else가 없으면 if 조건 만족하면 if 문 돌리고 if문 바깥을 실행하고, if 조건 만족하지 않으면 if 문 안돌리고 바깥을 실행하네?
            // if문 돌아가는 조건에서 if문 안에서 return하면 함수가 거기에서 종료되므로 else를 생략해도 바깥이 실행안됨
            // 당연하지! else 안쓴건 if문이랑 붙어있다 하더라도 if문과 별개의 것이니까 if가 돌든 안돌든 실행됨
           // 단지 if 내에서 return하면 함수가 종료되어서 그 이후가 안돌아가는것뿐!
        };

        // 게시물 상세 내역 가져오기
        const handleData = async() => {
            const url =`/board101/selectoneimage.json?no=${state.no}`;
            const headers = {"Content-Type":"application/json"};
            const { data } = await axios.get(url, {headers});
            console.log(data);
            if(data.status === 200) {
                state.row = data.result;
                state.prev = data.prevNo;
                state.next = data.nextNo;
            }    
        };
        
        // 해당 게시물의 답글 목록 가져오기
        const handleData1 = async() => {
            const url = `/board101/selectreply.json?brdno=${state.no}`;
            const headers = {"Content-Type":"application/json"};
            const { data } = await axios.get(url, {headers});
            console.log(data);
            if(data.status === 200) {
                state.rows = data.rows;
            } 
        }

        // 화면이 로딩될떄 즉시 출력해야 하는 것들..
        onMounted(()=> {
            handleData();
            handleData1();
        })

        return {
            state,
            handlePrev,
            handleNext,
            handleRe,
        }
    }
}
</script>

<style lang="css" scoped>

</style>
profile
keep going

4개의 댓글

comment-user-thumbnail
2022년 12월 28일
답글 달기
comment-user-thumbnail
2022년 12월 28일

필기 )
다음주 금욜 평가 게시판 만들기

백엔드 데이터에서 key가 image..buffer는 실제 내용, 현재날짜는 자동으로 ㄷ르어가게 해놓음.
catch 아래는 오류나면 실행. 0은 데이터는 저장 된것..? -1은 서버자체가 이상
요청되어 오는것 중에 바디 안에 있는 타이틀만 넣으라는 뜻
백엔드에서 다 정해졍있다... 뭘 달라는거가

nosqlbooster 검색 몽고db 접속해서 볼수있는 프로그램. 원격으로 작업 가능하게하는 프로그램
포트는 서버가 있으면 데이터왔다갔다 하는 통로
커내ㅐㄱ션 서버에 1.234.5.158에 포트번호 37017
authentication에서auth db db203 유저 아이디는 id203 pw는 pw203
200으로 접속해서 보드 101확인 테이블로 보면 편하다run 눌러주면 새로고침됨

답글 달기