판매자 페이지, 물품 등록, 물품 수정

keep_going·2022년 12월 26일
0

vue

목록 보기
6/13

전체적으로 components를 적용시킨 홈페이지를 만들어보자!


-- 1. nodejs 다운로드 (v8엔진)
https://nodejs.org/ko/


-- 2. node, npm 설치 버전 확인
CMD> node -v
    18.12.1
    
CMD> npm -v
    8.19.2

-- 2. vue, @vue/cli 설치하기
CMD> npm i vue -g
CMD> npm i @vue/cli -g


-- 3. 설치 확인
CMD> vue --version
    @vue/cli 5.0.8
    

CMD> vue create vue_20221226

CMD> cd vue_20221226


CMD> npm i vue-router@next --save
CMD> npm i axios --save
CMD> npm i vuex --save
CMD> npm i element-plus --save


환경설정파일 : vue.config.js

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  
  // 벡엔드 연동 proxy설정정
  devServer :{
    proxy : {

      '/board101' : {
        target : 'http://1.234.5.158:23000',
        changeOrigin : true,
        logLevel:'debug'
      },

      '/item101' : {
        target : 'http://1.234.5.158:23000',
        changeOrigin : true,
        logLevel:'debug'
      },

      '/member101' : {
        target : 'http://1.234.5.158:23000',
        changeOrigin : true,
        logLevel:'debug'
      },
      
    }
  }
  
})

CMD> npm run serve

  • 깃허브에 추가할때 ignore에 node 체크 하는것 잊지 마시오!
  • 다시 불러올때는 ...

// 파일명 : routes/index.js

// 1.라이브러리 가져오기
import { createWebHashHistory, createRouter } from 'vue-router';
// 이번엔 # 들어간 주소로 해보자

// 2. 라우트 설정
import Home from '@/components/HomePage.vue'; // 앞엔 변수명이라 짧아도됨. 뒤에는 파일명
import Seller from '@/components/SellerPage.vue';
import ItemInsert from '@/components/ItemInsertPage.vue' ;
import ItemUpdate from '@/components/ItemUpdatePage.vue' ;

const router = createRouter({
    history : createWebHashHistory(),
    routes : [
        {path:'/', component:Home },
        {path:'/seller', component:Seller },
      	{path :'/iteminsert', component:ItemInsert},
        {path :'/itemupdate', component:ItemUpdate},
    ]
});

// 이전페이지 이동페이지 정보 확인용
router.beforeEach((to, from, next)=>{
    console.log(to, from);
    next(); // next가 없으면 페이지 이동이 안됨.
});

// 3. 모듈 export
export default router;

// 파일명 : main.js

import { createApp } from 'vue';
import App from './App.vue';

// element-plus
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';

// router
import routes from './routes/index';
// index는 안넣어도 되지만 헷갈릴까봐

// createApp(App).설정라이브러리.mount('#app'); 
// 위와 아래는 같은 표현이지만 위와 같이 표현하면 중간이 계속 길어져서 보기싫어!
const app = createApp(App);
app.use(routes);
app.use(ElementPlus);
app.mount('#app');

// 파일명 : SellerPage.vue


<template>
    <div>
        <h3>판매자</h3>
        
        
        <el-button type="primary" @click="handleItemInsert()">물품등록</el-button>
		// 라우터 링크로 하니 밑줄 생겨서 함수로 했으나 style에서 text-decoration:none으로 하면 밑줄없어진다구요
    
        <el-table :data="rows" size="small">
         // 리턴에서 toRef했기 때문에 state 생략가능
            <el-table-column prop="_id" label="물품번호" width="70" />
            <el-table-column label="이미지">
                <template #default="scope">
                    <img :src="scope.row.img" style="width:50px;" />
                </template>    
            </el-table-column>
			// 이미지를 넣으려면 예제에서 버튼 들어가있는것 찾아보자. 기존의 것은 string만 가능
			// 최초에 한번만 데이터를 주고 나머지는 내부에서 데이터를 꺼내서 사용해야한다. 
			// 데이터를 rows로 통으로 줬기 때문에 하나의 정보만 꺼내는게 필요->scope 이용!
			// scope는 components 안의 데이터를 다시 꺼낼 때 사용하는것
			// 예제를 보니 scope.row.tag에서 가져왔구나! 그래서 row에 데이터를 받았다는걸 추측해서 사용..! components의 내부를 못보기때문에 예제를 최대한 활용하는수밖에
            
            <el-table-column prop="name" label="물품명" width="70" />
            <el-table-column prop="price" label="가격" width="70" />
            <el-table-column prop="quantity" label="수량" width="70" />
            <el-table-column prop="content" label="내용" />
            <el-table-column prop="regdate" label="날짜" width="170"/>
            <el-table-column label="버튼">
                <template #default="scope">
                    <el-button size="small" @click="handleUpdate(scope.row._id)">수정</el-button>
                    <el-button size="small" @click="handleDelete(scope.row._id)">삭제</el-button>
					// 삭제/수정하고자 하는 글의 번호 정보를 줘야 한다
					// 여기에 scope를 쓰는이유? 밑에서 준 data를 다운 받은 components에 넣어주고 싶어서? 다시 한번 찾아보자
                </template>    
            </el-table-column>    
        </el-table>

        <div>
            <el-pagination small background layout="prev, pager, next" :page-size=12 :total="total" @current-change="handlePage" />
            // 함수 뒤에 () 안붙이는 이유는 주는 파라미터가 몇갠지 모르기 때문에 비워두는것!
        </div>


        <el-dialog v-model="centerDialogVisible" title="Warning" width="30%" center>
            <span>
                삭제할까요?
            </span>
            <template #footer>
                <el-button type="primary" @click="handleDeleteAction()">
                    확인
                </el-button>
                <el-button @click="centerDialogVisible = false">
                    취소
                </el-button>
        		// 클릭했을때 값을 바로 바꿔주도록
            </template>
        </el-dialog>
    </div>
</template>

<script>
import axios from 'axios';
import { reactive, toRefs } from '@vue/reactivity';
import { onMounted } from '@vue/runtime-core';
import { useRouter } from 'vue-router';
export default {
    setup () {
        // 상태 변수
        const state = reactive({
            page    : 1,
            rows    : null, //[{},{},{}]
            total   : 0,
            centerDialogVisible : false,
            no : 0,
        });

        const router = useRouter();
		
      	// 함수
        const handleUpdate = (no) => {
            router.push({path:'/itemupdate', query:{no:no}});
        }
        
        const handleItemInsert = () => {
            router.push({path:'/iteminsert'});
        };

        const handleDeleteAction = async() => {
            // 벡엔드를 호출해서 삭제하기
            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){
                // 다이얼로그 닫기
                state.centerDialogVisible = false;
                handleData();
            }
        }

        const handleDelete = (no) => {
            console.log(no);
            state.no = no; 
            state.centerDialogVisible = true;
        };
      	// dialog는 화면상에 존재하지만 숨겨져있다가 삭제버튼 눌리면 보이게 하는것
      	// 보이게 하는 타이밍이 centerDialogVisible 값이 참이 될때 인것!
        // 취소 눌러서 창 사라지게 하려면 centerDialogVisible 값을 거짓으로 변경

        
        const handlePage = ( no ) => {
            console.log(no);
            state.page = no;
            handleData();
        };

        const handleData = async() =>{
            const url = `/item101/selectlistpage.json?page=${state.page}`;
            const headers = {"Content-Type":"application/json"};
            const { data } = await axios.get(url, {headers});
          	// axios가 import 안되서 수동으로 함... 오류생기면 import 확인
            console.log(data);
            if(data.status === 200){
                state.rows  = data.result;
                state.total = data.total;
              
                for(let tmp of data.result) {
                   if(tmp.content.length >20) {
                       tmp.content = tmp.content.substring(0, 20) + "...";
                        // 길이 20자 이상이면 이후는 ...으로 보이도록 substring 사용!
                   }
                }
            }
        };



        // 생명주기
        onMounted(()=>{
            handleData();
        });

        // 리턴
        return {
            state, 
            ...toRefs(state),
            handlePage,
            handleDelete,
            handleDeleteAction,
            handleItemInsert,
          	handleUpdate
        }
    }
}
</script>

<style lang="css" scoped>

</style>

// 파일명 : ItemUpdatePage.vue

<template>
    <div>
        {{ state }}
        <div v-if="row !== null">
            <div>
            <label>물품명</label>
            <el-input v-model="row.name" style="width: 400px;" />
            </div>
            
            <div>
                <label>물품 설명</label>
                <el-input type="textarea" v-model="row.content" :rows="6" style="width: 400px;" />
            </div>

            <div>
                <label>수량</label>
                <el-input-number v-model="row.quantity" :min="1" />
            </div>

            <div>
                <label>가격</label>
                <el-input-number v-model="row.price" :min="1" />
            </div>

            <div>
                <label></label>
                <el-button type="info" size="small" @click="handleUpdate()">수정하기</el-button>
            </div>
            <hr />

            <div v-for="tmp of state.imageurl" :key="tmp" style="display:inline-block;">
                <img :src="tmp.img" style="width: 100px;" />
            </div>
                  
            <div v-for="tmp of state.cnt" :key="tmp">
                <input type="file" @change="handleImage(tmp, $event)" />
                // tmp는 위치정보, $event는 이미지의 정보(첨부 또는 취소 정보)
                // input type="file"에는 v-model이 안걸린다..!
            </div>

            <button @click="FilePlus()">항목+</button>
            <button @click="FileMinus()">항목-</button>
            <button @click="handleSubImage()">서브이미지등록</button>
        </div>
    </div>
</template>

<script>
import { reactive, toRefs } from '@vue/reactivity';
import { useRoute } from 'vue-router'
import { onMounted } from '@vue/runtime-core';
import axios from 'axios';
export default {
    setup () {
        const route = useRoute();
		const router = useRouter();
        const state = reactive({
            no      : Number( route.query.no ), //주소창 ?no=557
            row     : null,
            cnt     : 2,
            images  : [null, null, null, null, null], // 최대 5개 가능, 그냥 []로 표현해도 됨
          	imageurl : [],
        });

        // 이미지가 변경될 시 정보를 state.images배열에 추가
        // v-model을 사용할 수 없음, 수동으로 처리해야 함.
        const handleImage = (tmp, img) => {
            // tmp는 위치정보
            // img는 이미지의 정보(첨부 또는 취소정보)
            console.log(tmp-1, img); // 배열은 0부터 시작이므로 tmp-1
            if(img.target.files.length>0){
                state.images[tmp-1] = img.target.files[0];
            }
            else {
                state.images[tmp-1] = null;
            }
        };
		
      	// 추가시 물품번호 필요
        const handleSubImage = async() => {
            console.log('handleSubImage');
            const url = `/item101/insertimages.json`;
            const headers = {"Content-Type":"multipart/form-data"};
            const body = new FormData();

            body.append("code", state.no); // code가 물품번호! 조회할때는 물품번호 필요
            for(let i=0; i<state.cnt; i++){// 배열은 시작하는 값이 0이므로 등호 안들어감!
                body.append("image", state.images[i]);
            }
            const { data }  = await axios.post(url, body, {headers});
            console.log(data);
        };

        const FilePlus = () => {
            state.cnt++;
            if(state.cnt > 5) {
                state.cnt = 5; // 파일 첨부 창 최대 5개
            }
        };

        const FileMinus = () => {
            state.cnt--;
            if(state.cnt < 2) {
                state.cnt = 2; // 파일 첨부 창 최소 2개
            }
        };

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

            const { data } = await axios.put(url, body, {headers});
            console.log(data);
            if(data.status === 200) {
                router.push({path:'/seller'});
            }
        };
      
        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;
            }
        };

      	// 서브 이미지 읽기
        const handleData1 = async() => {
            const url = `/item101/subimagecode.json?code=${state.no}`;
            const headers = {"Content-Type":"application/json"};
            const { data } = await axios.get(url, {headers});
            console.log(data);
            if(data.status === 200) {
                state.imageurl = data.result; 
            }
        };
      
        onMounted(() => {
            handleData();
            handleData1();
        });

        return {
            state,
            ...toRefs(state),
            FilePlus,
            FileMinus,
            handleSubImage,
            handleImage,
        };
    }
}
</script>

<style lang="css" scoped>
   label {
        display: inline-block;
        width: 100px;
    }
</style>

// 파일명 : ItemInsertPage.vue

<template>
    <div>
        <div>
            <label>물품명</label>
            <el-input v-model="state.name" style="width: 400px;" placeholder="Please input" />
        </div>

        <div>
            <label>물품 설명</label>
            <el-input type="textarea" v-model="state.content" :rows="6" style="width: 400px;" placeholder="Please input" />
        </div>

        <div>
            <label>수량</label>
            <el-input-number v-model="state.quantity" :min="1" />
        </div>

        <div>
            <label>가격</label>
            <el-input-number v-model="state.price" :min="1" />
        </div>

        <div>
            <label>이미지</label>
            <img :src="state.imageurl" style="width: 100px;"/>
            <input type="file" style="width: 200px;" @change="handleImage($event)" />
            // 여기에 el-input으로 하니 e.target.files에 보관 되있지 않네...! 어디에 들어가있는지 예를 봐도 모르면 어떻게 하나? 
            // input에는 change 안되게 되어있었음... upload 이용해야함
        </div>

        <div>
            <label></label>
            <el-button type="info" size="small" @click="handleInsert()">등록하기</el-button>
        </div>
        
    </div>
</template>

<script>
import { reactive } from '@vue/reactivity'
import axios from 'axios';
import { useRouter } from 'vue-router';
export default {
    setup () {
        const state = reactive({
            name : null,
            content : null,
            quantity : 0,
            price : 0,
            imagedata : null,
            imageurl : require('../assets/imgs/noimage.png')
        });

        const router = useRouter();

        const handleImage = (e) => {
            console.log(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/noimage.png')
            }
        }

        const handleInsert = async() => {
            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);
            if(data.status === 200) {
                router.push({path:'/seller'});
            }
        }


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

<style lang="css" scoped>
    label {
        display: inline-block;
        width: 100px;
    }

</style>

그밖에

  • scope... 굳이 데이터를 넣었다가 다시 꺼냈다가... ? 같은페이지에 있는 정보인것같은데 components로 보는 이유는 데이터를 components를 이용해서(table 같은곳에) 집어 넣었기 때문에..? 다시 생각해보고 찾아보자
    참조 ) https://ddol.tistory.com/53
profile
keep going

1개의 댓글

comment-user-thumbnail
2022년 12월 26일

참고) parameter에 대해서 -> https://axce.tistory.com/55

답글 달기