고객용 물품목록 => 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>