상세페이지, 등록, 목록, 이미지 업로드, 글 수정, 이전글, 다음글

keep_going·2022년 12월 21일
0

vue

목록 보기
3/13
고객용 물품목록 => http://1.234.5.158:23000/item101/selectlistpage.json?page=1
물품등록 => http://1.234.5.158:23000/item101/insert.json  => key 정보: name, price, content, quantity, image
물품1개조회 => http://1.234.5.158:23000/item101/selectone.json?no=1
판매자용 전체물품목록 => http://1.234.5.158:23000/item101/selectlist.json
물품삭제 => http://1.234.5.158:23000/item101/delete.json?no=1
물품수정 => http://1.234.5.158:23000/item101/update.json?no=113 => 물품명, 내용, 가격, 수량만 가능

-------------------------------------


await axios.get(url, {headers});
await axios.post(url, body, {headers});
await axios.put(url, body, {headers});
await axios.delete(url, {headers:headers, data:body});

src 밑에 assets이 resource 보관하는 곳. imgs, css.. 같은 폴더 만들어서 관리하면 된다


// 파일명 : vue.config.js

// npm run serve => 읽어서 수행함.
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,

  devServer :{
    proxy : {

      '/board101' : {
        target : 'http://1.234.5.158:23000',
        changeOrigin : true,
        logLevel:'debug'
      },
	// /item101로 시작하는 url용 추가
      '/item101' : {
        target : 'http://1.234.5.158:23000',
        changeOrigin : true,
        logLevel:'debug'
      },
      // 보통은 서버:8080/api/board101 이런식으로 api로 통일시키지만 연습하기 위해 다르게 사용
      
    }
  }
})


// 파일명 : ImagePage.vue

<template>
    <div>
        <h3>image 실습</h3>

        <div v-for="tmp of imgs" :key="tmp" style="display:inline-block">
            <img :src="tmp.url" style="width:50px; height:50px;" />
        </div>

        <div>
            {{ state }} <br />
            이름 : <input type="text" v-model="state.username" /><br />
            파일 : <input type="file" @change="handleImage($event)" /><br />
            <img :src="state.imageurl" style="width:50px;height:50px" />
            // 이미지: <input type="file" v-model="state.userfile" /><br /> 가 안됨!
            // file에는 v-model을 못건다. 웹에서 파일은 숫자, 문자처럼 생각하면 안되고 다르게 처리한다 생각
            // 파일을 선택했을때 밑에 수동으로 state.userfile에 넣어주는 작업을 해야한다.
            // 첨부한 상태인지 취소한 상태인지 알기 위해서 change event사용
            // this의 역할을 하는것이 $event
            // :src="state.imageurl"를 찍으면 밑에 있는 정보를 찍는것
            // 그냥 src="state.imageurl"는 문자 그대로 들어가는것
            // 밑에 있는 변수를 쓰고 싶으면 : 를 빼먹으면 안된다!
        </div>

    </div>
</template>

<script>
import { reactive } from 'vue';

export default {
    setup () {
        
        //[{},{},{}] => read로 사용
        const imgs = [ // 바뀌는 변수가 아니므로 reactive 안쓴다!
            { url : require('../assets/imgs/apple.jpg') },
            // 파일 가져 올때는 require을 써야 한다.
            // ../는 현재 위치 기준으로 한단계 위로 올라가는것
            { url : require('../assets/imgs/orange.jpg') },
            { url : 'https://picsum.photos/500/300?image=1'},
          	// 웹에 있는 이미지는 주소 그대로 넣는다.
        ];

        // read writer로 사용. 사용자가 입력하면 바뀐다!
        const state = reactive({
            username : '',
            userfile : null, 
            // 보여지는거는 imageurl이지만 실제 백엔드로 보내주는 역할은 usefile이 하는것!!
            imageurl : require('../assets/imgs/logo.png'),
          	// 아무것도 안했을때 기본으로 뜨는 이미지
        });

        const handleImage = (e) => { // $event를 e로 받은것
            // {0: File, length: 1}
            console.log('handleImage', e.target.files);
			// target 밑에 files 안에서 첨부 했을때, 안했을때 확인할 수 있다.
          
            if(e.target.files.length > 0){ // 첨부 선택
              	state.userfile = e.target.files[0];
                // URL.createObjectURL => 크롬내부에 가상의 이미지 url을 만드는것
                // blob:가상의 주소생성
                state.imageurl = URL.createObjectURL( e.target.files[0] );
            }
            else { //첨부취소
                state.userfile = null;
                state.imageurl = require('../assets/imgs/logo.png');
            }
        };

        return { 
            imgs,
            state, 
            handleImage,
        };
    }
}
</script>

<style lang="css" scoped>

</style>

// 파일명 : ItemPage.vue

<template>
    <div>
        <h3>물품목록</h3>

        <router-link :to="{ path :'/iteminsert' }">
            <button>물품등록</button>
        </router-link>

        <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" 
                    style="cursor:pointer;" @click="handleContent( tmp._id  )">
                    <td>{{ tmp._id }}</td>
                    <td>{{ tmp.name }}</td>
                    <td>{{ tmp.content }}</td>
                    <td>{{ tmp.price }}</td>
                    <td>{{ tmp.quantity }}</td>
                    <td>{{ tmp.regdate }}</td>
                </tr>
            </tbody>
        </table>
    </div>
</template>

<script>
import axios from 'axios';
import { reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';

export default {
    setup () {
        // 페이지 이동 라우트 
        const router = useRouter();

        // 벡엔드 데이터를 받아서 보관, template에 적용되서 출력
        const state = reactive({
            rows : null, // 물품목록 받을 변수
        });

        // 함수 호출되어야 함.
        const handleData = async() => {
            const url = `/item101/selectlist.json`;
            const headers = {"Content-Type":"application/json"};
            const { data } = await axios.get(url, {headers});
            console.log(data);
            if(data.status === 200) {
                // [{},{},{}]
                state.rows = data.result;
            }
        };

        const handleContent = ( code ) => { // 물품 번호 받음
            console.log('handleContent', code);
            router.push({
                path    : '/itemcontent', 
                query   : { no : code }
            });
          	// 쿼리를 줬다는건 뒤쪽에 ?를 붙여서 데이터 전송을 했다는 뜻. ?code=code(위에서 받은 물품 번호) 라는 뜻이여!
        };


        // 화면이 로딩될때 함수 호출, 생명주기 
        onMounted(() => {
            handleData() // () 붙이는거 까먹지 마라...
        });

        return {
            state,
            handleContent
        };
    }
}
</script>

<style lang="css" scoped>

</style>

// 파일명 : ItemInsertPage.vue

<template>
    <div class="container">
    {{ state }}
        <div class="item">
            <label class="lbl">물품명</label>
            <input type="text" v-model="state.name"/>
        </div>

        <div class="item">
            <label class="lbl">물품설명</label>
            <textarea rows="6" v-model="state.content"></textarea>
        </div>

        <div class="item">
            <label class="lbl">가격</label>
            <input type="text" v-model="state.price"/>
        </div>

        <div class="item">
            <label class="lbl">수량</label>
            <input type="number" v-model="state.quantity" />
        </div>

        <div class="item">
            <label class="lbl">이미지</label>
            <img :src="state.imageurl" style="width: 50px; height: 50px;" />
            <input type="file" @change="handleImage($event)"/>
        </div>

        <div class="item">
            <label class="lbl"></label>
            <button @click="handleInsert()">등록하기</button>
        </div>
    </div>
</template>

<script>
import axios from 'axios';
import { reactive } from 'vue';
import { useRouter } from 'vue-router';

export default {
    setup () {

        const router = useRouter();
        
        const state = reactive({
            name        : 'a',
            content     : 'b',
            price       : '123', // 문자 취급
            quantity    : 456, // 숫자로 하면 비울수가 없음. 0이라도 써야함
            imagedata   : null,
            imageurl    : require('../assets/imgs/logo.png'),
        });

        const handleImage = (e) => {
            if(e.target.files.length > 0) {
                state.imagedata = e.target.files[0];
                state.imageurl = URL.createObjectURL(e.target.files[0] );
            }
            else {
                state.imagedata = null;
                state.imageurl = require('../assets/imgs/logo.png');
            }
        };

        const handleInsert = async() => {
            if(state.name.length <=0){
                alert('물품명을 입력하세요.');
                return false; //함수종료
            }

            if( state.imagedata === null) {
                alert('이미지를 첨부하세요.');
                return false; // 함수 종료
            }

            const url = `/item101/insert.json`;
            const headers = {"Content-Type":"multipart/form-data"}; // 이미지 존재할때!
            let body = new FormData(); // 계속 바뀌므로..
            body.append('name', state.name );
            body.append('price', Number(state.price) ); // 전송할때 문자인 값을 숫자로 변환시켜서 보냄
            body.append('content', state.content );
            body.append('quantity', Number(state.quantity) );
            body.append('image', state.imagedata );
            
            const { data } = await axios.post(url, body, {headers});
            console.log(data); // 콘솔이라도 안찍으면 data 안썼다고 오류뜸..
            if(data.status === 200) {
                alert('등록되었습니다.');
                router.push({path:'/item'});
            }
        };

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

<style lang="css" scoped>
    .container {
        width: 800px;
        margin: 0px auto;
    }
    .item {
        margin: 10px;
    }
    .lbl {
        display: inline-block;
        width: 100px;
    }
</style>

// 파일명 : ItemContentPage.vue

<template>
    <div>
        <h3>물품상세</h3>
        {{ state }}
        <hr />
        // row값 하나 밖에 없음. 배열 꼴이 아니므로 반복문 돌릴수 없다. 
         
        // 백엔드로 갔다가 row로 받아오는데 시간 좀 걸림.
        // template을 그리는건 바로 수행한다. 이걸 먼저 안하면 성질급한 사용자가 제대로 작동하고 있는건지.. 오류난건지.. 알수 없잖아!
        // 데이터 받는 타이밍과 화면에 출력하는 타이밍이 맞지 않아서 row가 null일때 template을 수행해서 오류 발생
        // row에 데이터를 먼저 담아서 template을 그리고 싶다면..? 
        // 뭘 먼저 읽을지는 바꿀수 없다. 정해져있음.
        // row값이 null일때는 수행 안되게 하는게 최선! if문 처리

        // show는 숨기는 개념! 기본 상태일때 (div값이 1일때)
        <div v-show="state.div===1" v-if="state.row">
        // v-if="state.row" !== null 의 뜻  
            <p>{{ state.row._id }}</p>
            <p>{{ state.row.name }}</p>
            <p>{{ state.row.content }}</p>
            <p>{{ state.row.price }}</p>
            <p>{{ state.row.quantity }}</p>
            <p>{{ state.row.regdate }}</p>
            <img :src="state.row.img" style="width:50px; height:50px" />
            <hr />
            <button @click="state.div = 2">수정</button>
			// 클릭하면 div값 2로 변경
            <button @click="handleDelete()">삭제</button>
            <button @click="handlePrevPage()">이전페이지</button>
        </div>
		
		// 수정할때 보이는 화면. row를 div 1과 동일하게 사용하면 수정 완료하지 않고 취소해도 수정됨 
		//받아오는 데이터는 동일하지만 div 1과는 구분 될 수 있도록 다르게 변수 설정
        <div v-show="state.div===2" v-if="state.row1">
            <div>
                <input type="text" v-model="state.row1.name" />
            </div>

            <div>
                <input type="text" v-model="state.row1.content" />
            </div>
            
            <div>
                <input type="text" v-model="state.row1.price" />
            </div>

            <div>
                <input type="text" v-model="state.row1.quantity" />
            </div>

            <button @click="handleUpdate()">수정완료</button>
            <button @click="state.div = 1">취소</button>
			// 취소 하면 다시 이전 값이 보이도록!
        </div>

    </div>
</template>

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

export default {
    setup () {
        const route  = useRoute();
        const router = useRouter();

        const state = reactive({
            no      : Number(route.query.no), // ?no=511
            row     : null,
            row1    : null, // 수정 시 사용
            div     : 1,  // ex)1, 2 div 태그를 전환하기 용도. 기본값을 1으로 준것.
        });
      		// '' 는 null이 아니라 빈 값인것! null은 할당 자체가 안됨. 다른 개념.
      		// 그래서 if문 처리 안했을때 '' 처리하면 오류 안나고 null 처리 하면 오류 난다. 정확한 개념은 null을 써야함.
      		// 어쨌든 중요한건 if문 처리를 해야된다는것!
      		// 수행 순서는 뒤죽박죽인데 if문을 처리함으로써 원하는 순서대로 수행되는 것 처럼 보이게 하는것.

        const handleData = async() => {
            const url = `/item101/selectone.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.row1 = { ...data.result }; //내용만 복사하기
            }
        };

        onMounted(()=>{
            handleData();
        });

        const handlePrevPage = () => {
            router.go(-1);
        };

        const handleDelete = async() => {
            if(confirm('삭제할까요?')){
                const url = `/item101/delete.json?no=${state.no}`;
                const headers = {"Content-Type":"application/json"};
                const body = {};
                const { data } = await axios.delete(url, {headers:headers, data:body});
                console.log(data);
                if(data.status === 200){
                    router.push({path:'/item'});
                }
            }
        };

        const handleUpdate = async() =>{
            const url = `/item101/update.json?no=${state.no}`;
            const headers = {"Content-Type":"application/json"};
            const body = {
                name : state.row1.name,
                content : state.row1.content,
                price : state.row1.price,
                quantity : state.row1.quantity,
            }

            const { data } = await axios.put(url, body, {headers});
            console.log(data);
            if(data.status === 200) {
                // 현재 페이지로 전환
                // 같은페이지는 url은 변경되지만 컴포넌트 갱신하지 않음
                handleData(); // 수동으로 내용변경
                state.div = 1; //첫번째 div로 보이게...
            }
        };

        return {
            state,
            handlePrevPage,
            handleDelete,
            handleUpdate
        };
    }
}
</script>

<style lang="css" scoped>

</style>

// 파일명 : BoardContentPage.vue
이전글, 다음글 기능 추가

<template>
    <div class="container">
        <h3>게시판 상세 내용</h3>
        {{ state }}
        <hr />
        <div class="item">
            <p>번호 : {{ state.row._id }}</p>
            <p>제목 : {{ state.row.title}}</p>
            <p>내용 : {{ state.row.content }}</p>
            <p>작성자 : {{ state.row.writer }}</p>
            <p>조회수 : {{ state.row.hit }}</p>
            <p>등록일 : {{ state.row.regdate }}</p>
            <button @click="handleBack()">이전</button>
            <button @click="handlePrev()">이전글</button>
            <button @click="handleNext()">다음글</button>
        </div>
    </div>
</template>

<script>
import axios from 'axios';
import { reactive, onMounted } from 'vue';
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: '',
            next: ''
        })
        
      	const handleBack = () => {
            router.go(-1);
        };

        const handlePrev = () => {
            console.log(state.prev);
            router.push({path:"/boardcontent", query:{no:state.prev}});
            state.no = state.prev // 이전글 주소로 바뀐 번호(prev)를 주소창의 no에 넣어줌
            handleData() ; // 같은 페이지는 재로딩이 안되서 수동으로 함수를 호출해서 재로딩 시켜야함.
        }

        // 다음글은 보드컨텐트1로 보내고 보드 컨텐트 1에서 바로 다음글 주소 넘어가게 라우터 이용해서 짜보자.
        // 확실히 이전글 방법보다는 느림... 
        const handleNext = () => {
            router.push({path:"/boardcontent1", query:{no1:state.next}});
        }

        const handleData = async() => {
            const url = `/board101/selectone.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;
            }
        };

        onMounted(()=>{
            handleData();
        });

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

<style lang="css" scoped>
    .container {
        width               : 600px;
        margin              : 0px auto;
        background-color    : rgb(255, 252, 239);
        padding             : 10px;
    }
</style>

// 파일명 : BoardContent1Page.vue
라우터 페이지

<template>
    <div>
        다음글 이동용 페이지
    </div>
</template>

<script>
import { reactive } from 'vue';
import { useRoute, useRouter } from 'vue-router';

export default {
    setup () {
        
        const route = useRoute() ;
        const router = useRouter() ;

        const state = reactive({
            no : Number(route.query.no1),
        })
        
        if(state.no === 0) {
            alert('다음글이 없습니다.');
            router.push({path:"/board"});
        }
        else {
            router.push({path:"/boardcontent", query:{no:state.no}});
        }
        
        return {
            state
        }
    }
}
</script>

<style lang="css" scoped>

</style>
profile
keep going

0개의 댓글