This commit is contained in:
이정혁 2024-05-10 15:47:31 +09:00
parent f0645957fe
commit aedf8f5824
11 changed files with 576 additions and 1 deletions

View File

@ -6,7 +6,7 @@ import { RouterLink, RouterView } from 'vue-router';
<header>
<div class="wrapper">
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/article">article</RouterLink>
</nav>
</div>
</header>

31
src/api/article.js Normal file
View File

@ -0,0 +1,31 @@
import { localAxios } from '@/utils/http-commons';
const local = localAxios();
function getArticles(success, fail) {
//list
local.get('/article/list').then(success).catch(fail);
}
function searchArticle(keyword, success, fail) {
local.get(`/article/search?keyword=${keyword}`).then(success).catch(fail);
}
function getArticle(id, success, fail) {
//detail
local.get(`/article/${id}`).then(success).catch(fail);
}
function updateArticle(article, id, success, fail) {
local.put(`/article/${id}`, JSON.stringify(article)).then(success).catch(fail);
}
function deleteArticle(id, success, fail) {
local.delete(`/article/${id}`).then(success).catch(fail);
}
function addArticle(article, success, fail) {
local.post('/article', JSON.stringify(article)).then(success).catch(fail);
}
export { getArticles, searchArticle, getArticle, updateArticle, deleteArticle, addArticle };

View File

@ -0,0 +1,139 @@
<script setup>
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { getArticle, updateArticle, deleteArticle } from '@/api/article';
const route = useRoute();
const router = useRouter();
const id = ref(route.query.id);
/*
DOM이 mounted된 상태에서 아직 서버에서 데이터가 경우
Book의 데이터를 화면에 표시 없어서 error 발생하므로 초기 설정을 한다.
*/
const article = ref({
id: '',
title: '',
text: '',
name: '',
authorId: '',
date: '',
});
//setup(init) detailBook()
getArticle(
id.value,
({ data }) => {
article.value = data;
},
(error) => {
alert(error);
}
);
// Detail readonly='readonly'
// readonly='' isReadonly=false
const isReadonly = ref(true);
function removeHandler() {
// console.log('BookDetail.remove.......................')
// books.value = books.value.filter((item) => isbn.value != item.isbn)
// console.log('BookDetail.remove.......................books:', books.value)
deleteArticle(
id.value,
({ data }) => {
console.log('deleteArticle................result:', data);
alert('삭제 성공');
moveHandler();
},
(error) => {
alert('삭제 실패');
}
);
// moveHandler()
}
function moveHandler() {
router.push({
name: 'article-list',
});
}
function updateHandler() {
if (!isReadonly.value) {
//isReadonly false update
updateArticle(
article.value,
id,
({ data }) => {
console.log('updateArticl................result:', data);
alert('수정 성공');
},
(error) => {
alert('수정 실패');
}
);
}
isReadonly.value = !isReadonly.value;
}
</script>
<template>
<div>
<table class="table table-bordered">
<tbody>
<tr>
<th>게시판 번호</th>
<td><input type="text" v-model.lazy="book.isbn" readonly="readonly" /></td>
</tr>
<tr>
<th>제목</th>
<td><input type="text" v-model.lazy="book.title" :readonly="isReadonly" /></td>
</tr>
<tr>
<th>내용</th>
<td><input type="text" v-model.lazy="book.author" :readonly="isReadonly" /></td>
</tr>
<tr>
<th>작성자</th>
<td><input type="text" v-model.lazy="book.price" :readonly="isReadonly" /></td>
</tr>
<tr>
<th>아이디</th>
<td><input type="text" v-model.lazy="book.price" :readonly="isReadonly" /></td>
</tr>
<tr>
<th>날짜</th>
<td><input type="text" v-model.lazy="book.price" :readonly="isReadonly" /></td>
</tr>
<!-- <tr>
<th colspan="2"> 정보</th>
</tr>
<tr>
<td colspan="2">
<textarea
cols="45"
rows="10"
v-model.lazy="book.describ"
:readonly="isReadonly"
></textarea>
</td>
</tr> -->
<tr>
<td colspan="2" class="text-center">
<button class="btn btn-outline-primary m-1" @click="updateHandler">수정</button>
<button class="btn btn-outline-primary m-1" @click="moveHandler">목록</button>
<button class="btn btn-outline-primary m-1" @click="removeHandler">삭제</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<style scoped>
input:read-only {
background-color: lightgray;
}
</style>

View File

@ -0,0 +1,126 @@
<script setup>
import { getArticles } from '@/api/article';
import { ref, reactive } from 'vue';
import ArticleListItem from './item/ArticleListItem.vue';
import VSelect from '@/components/common/VSelect.vue';
import PageNavigation from '@/components/common/PageNavigation.vue';
// books
const articles = ref([]);
// axios .
// axios params json ref()
// reactive() .
const params = reactive({
pageNo: 1,
key: 'all',
keyword: '',
});
const currentPage = ref(1);
const totalpage = ref(1);
//VSelect props
const selectOptions = ref([
{ text: '---선택하세요---', value: 'all' },
{ text: '제목+내용', value: 'title' },
// { text: '', value: 'author' },
]);
function searchList() {
console.log('searchList....params:', params);
searchArticle(
params,
({ data }) => {
console.log(data);
articles.value = data.articles;
// currentPage.value = data.page.pageNo;
// totalpage.value = data.page.total;
},
(error) => {
console.log(error);
}
);
}
// searchList(); //setup(init) searchList()
getArticles(
({ data }) => {
console.log(data);
articles.value = data;
// currentPage.value = data.page.pageNo;
// totalpage.value = data.page.total;
},
(error) => {
console.log(error);
}
);
const changeKey = (val) => {
console.log('search.....................changeKey:', val);
params.key = val;
console.log('search.....................key:', params.key);
};
//PageNavigation emit
function pageChange(value) {
params.pageNo = value;
searchList();
}
</script>
<template>
<h3 class="text-center">등록된 도서 목록</h3>
<div class="row">
<div class="col-6">
<router-link :to="{ name: 'article-regist' }">
<button class="btn btn-outline-primary">등록</button>
</router-link>
</div>
<div class="col-6">
<div class="input-group">
<span class="input-group-text">검색조건</span>
<v-select :selectOptions="selectOptions" @on-key-select="changeKey" />
<input
type="text"
class="input-control"
placeholder="검색어를 입력하세요"
v-model="params.word"
@keyup.enter="searchList"
/>
<button class="btn btn-dark" @click="searchList">검색</button>
</div>
</div>
</div>
<div v-if="articles.length > 0">
<table class="table table-boardered table-hover">
<thead>
<colgroup>
<col width="25%" />
<col width="40%" />
<col width="15%" />
<col width="20%" />
</colgroup>
<tr class="text-center">
<th>게시글 번호</th>
<th>제목</th>
<th>작성자</th>
<th>아이디</th>
<th>날짜</th>
</tr>
</thead>
<tbody>
<ArticleListItem
v-for="article in articles"
:key="article.id"
:article="article"
></ArticleListItem>
</tbody>
</table>
</div>
<div v-else>
<h3>등록된 정보가 없습니다.</h3>
</div>
<PageNavigation :currentPage="currentPage" :totalPage="totalpage" @page-change="pageChange" />
</template>
<style scoped></style>

View File

@ -0,0 +1,129 @@
<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { addArticle } from '@/api/article';
const router = useRouter();
//v-model
const title = ref('');
const text = ref('');
const name = ref('');
const authorId = ref('');
/*
Dom과 연결할 반응형 변수 선언, setup에서는 DOM tree가 구성되지 않았기 때문에
mounted 이후에 접근이 가능하다.
*/
const titleDiv = ref(null);
const textDiv = ref(null);
const nameDiv = ref(null);
const authorIdDiv = ref(null);
// const books = inject('books')
// console.log('BookRegist....books:', books.value)
function moveHandler() {
router.push({ name: 'article-list' });
}
function createHandler() {
//
let err = false;
let msg = '';
// if (!isbn.value) {
// msg = ' '
// err = false;
// isbnDiv.value.focus()
// } else if (err && !title.value) { }
!title.value && ((msg = '제목을 입력해 주세요'), (err = true), titleDiv.value.focus());
!err && !text.value && ((msg = '내용을 입력해 주세요'), (err = true), textDiv.value.focus());
!err && !name.value && ((msg = '이름을 입력해 주세요'), (err = true), nameDiv.value.focus());
!err &&
!authorId.value &&
((msg = '아이디를 입력해 주세요'), (err = true), authorIdDiv.value.focus());
const article = ref({
title: '',
text: '',
name: '',
authorId: '',
});
if (err) {
alert(msg);
} else {
article.value.title = title.value;
article.value.text = text.value;
article.value.name = name.value;
article.value.authorId = authorId.value;
}
// console.log('bookregist.......', books.value)
// title.value = ''
// isbn.value = ''
// author.value = ''
// price.value = ''
// describ.value = ''
// alert('')
// moveHandler()
// }
addArticle(
article.value,
({ data }) => {
console.log('update................result:', data);
alert('등록 성공');
},
(error) => {
alert('수정 실패');
}
);
moveHandler();
}
</script>
<template>
<div class="me-4">
<h1 class="underline text-center">도서 등록</h1>
<table class="table table-boardered">
<thead>
<tr>
<td>제목</td>
<td><input type="text" ref="titleDiv" v-model="title" /></td>
</tr>
<tr>
<td>내용</td>
<td><input type="text" ref="textDiv" v-model="text" /></td>
</tr>
<tr>
<td>이름</td>
<td><input type="text" ref="nameDiv" v-model="name" /></td>
</tr>
<tr>
<td>아이디</td>
<td><input type="text" ref="authorIdDiv" v-model="authorId" /></td>
</tr>
</thead>
<tbody>
<!-- <tr>
<td colspan="2"> 정보</td>
</tr>
<tr>
<td colspan="2" class="text-center">
<textarea v-model="describ" cols="45" rows="10" />
</td>
</tr> -->
<tr>
<td colspan="2" class="text-center">
<button class="btn btn-primary m-1" @click="createHandler">등록</button>
<button class="btn btn-primary m-1" @click="moveHandler">목록</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<style scoped></style>

View File

@ -0,0 +1,16 @@
<script setup>
defineProps({ article: Object });
</script>
<template>
<tr class="text-center">
<th>{{ article.isbn }}</th>
<td>
<router-link :to="{ name: 'article-detail', query: { id: article.id } }">
{{ article.title }}
</router-link>
</td>
<td>{{ article.author }}</td>
<td>{{ article.price }}</td>
</tr>
</template>
<style scoped></style>

View File

@ -0,0 +1,65 @@
<script setup>
import { computed } from 'vue'
const props = defineProps({ currentPage: Number, totalPage: Number })
const emit = defineEmits(['pageChange'])
// const navigationSize = parseInt(import.meta.env.VITE_ARTICLE_NAVIGATION_SIZE)
const navigationSize = 10
const startPage = computed(() => {
return parseInt((props.currentPage - 1) / navigationSize) * navigationSize + 1
})
const endPage = computed(() => {
let lastPage =
parseInt((props.currentPage - 1) / navigationSize) * navigationSize + navigationSize
return props.totalPage < lastPage ? props.totalPage : lastPage
})
const endRange = computed(() => {
return parseInt((props.totalPage - 1) / navigationSize) * navigationSize < props.currentPage
})
function range(start, end) {
const list = []
for (let i = start; i <= end; i++) list.push(i)
return list
// return Array(end - start + 1)
// .fill()
// .map((val, i) => start + i);
}
function onPageChange(pg) {
console.log(pg + '로 이동!!!')
emit('pageChange', pg)
}
</script>
<template>
<div class="row">
<ul class="pagination justify-content-center">
<li class="page-item">
<a class="page-link" @click="onPageChange(1)">최신</a>
</li>
<li class="page-item">
<a class="page-link" @click="onPageChange(startPage == 1 ? 1 : startPage - 1)">이전</a>
</li>
<template v-for="pg in range(startPage, endPage)" :key="pg">
<li :class="currentPage === pg ? 'page-item active' : 'page-item'">
<a class="page-link" @click="onPageChange(pg)">{{ pg }}</a>
</li>
</template>
<li class="page-item">
<a class="page-link" @click="onPageChange(endRange ? totalPage : endPage + 1)">다음</a>
</li>
<li class="page-item"><a class="page-link" @click="onPageChange(totalPage)">마지막</a></li>
</ul>
</div>
</template>
<style scoped>
a {
cursor: pointer;
}
</style>

View File

@ -0,0 +1,20 @@
<script setup>
import { ref} from 'vue'
defineProps({ selectOptions: Array })
const emit = defineEmits(["onKeySelect"])
const key = ref('all')
const onSelect = () => {
console.log('vselect................... key:', key.value)
emit('onKeySelect', key.value)
}
</script>
<template>
<select class="form-select" v-model="key" @change="onSelect">
<option v-for="(k, index) in selectOptions" :key="index" :value="k.value">
{{k.text}}
</option>
</select>
</template>
<style scoped>
</style>

View File

@ -9,6 +9,30 @@ const router = createRouter({
name: 'home',
component: HomeView,
},
{
path: '/article',
name: 'article',
//index.html이 화면에 표시될때 미리 다운 받지 않고 필요시 동적으로 페이지를 다운 받는다.
component: () => import('@/views/ArticleView.vue'),
redirect: { name: 'article-list' },
children: [
{
path: 'list', //children은 /를 자동으로 붙여 주므로 빼야 한다.
name: 'article-list',
component: () => import('@/components/article/ArticleList.vue'),
},
{
path: 'regist',
name: 'article-regist',
component: () => import('@/components/article/ArticleRegist.vue'),
},
{
path: 'detail',
name: 'article-detail',
component: () => import('@/components/article/ArticleDetail.vue'),
},
],
},
],
});

15
src/utils/http-commons.js Normal file
View File

@ -0,0 +1,15 @@
import axios from 'axios';
// const { VITE_API_BASE_URL } ="http://localhost/";
function localAxios() {
const instance = axios.create({
baseURL: 'http://localhost',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
});
return instance;
}
export { localAxios };

10
src/views/ArticleView.vue Normal file
View File

@ -0,0 +1,10 @@
<script setup>
import { ref, provide, watch } from 'vue';
</script>
<template>
<div class="text-center m-4">
<div class="alert alert-info" role="alert">게시판</div>
<router-view></router-view>
</div>
</template>
<style scoped></style>