Store_Vuex init...

This commit is contained in:
최준흠 2022-09-07 17:53:32 +09:00
parent 5ff12cab9d
commit 845c3eb97d
15 changed files with 596 additions and 287 deletions

7
.env
View File

@ -8,9 +8,4 @@ VUE_APP_AUTH_HOST="http://localhost:2000"
VUE_APP_BACKEND_HOST="http://localhost:3000"
#LocalStorage는 Edge 브라우저의 개발툴 > 응용프로그램 > 로컬 저장소에서 확인가능
VUE_APP_SESSIONSTORAGE_JWT_NAME="access_token"
VUE_APP_SESSIONSTORAGE_REDIRECT_NAME = "RedirectPATH"
VUE_APP_TABLE_DEFAULT_PERPAGE = 10
VUE_APP_TABLE_DEFAULT_TABLE_PAGE = 1
VUE_APP_TABLE_DEFAULT_PERPAGE = 10

View File

@ -42,18 +42,16 @@
</b-input-group>
</validation-provider>
<b-button type="submit" variant="primary">Login</b-button>
<b-button type="reset" variant="danger" @click="onReset()"
>Reset</b-button
>
</b-form>
</validation-observer>
</div>
</template>
<script>
import axios from 'axios'
// : https://kdydesign.github.io/2019/04/06/vuejs-vuex-helper/
export default {
data() {
//console.log(this.$route)
return {
form: {
email: null,
@ -81,66 +79,21 @@ export default {
getValidationState({ dirty, validated, valid = null }) {
return dirty || validated ? valid : null
},
async onSubmit() {
const result = await this.callAPI('/auth/login', {
email: this.form.email,
password: this.form.password
})
console.log(result)
if (!result) {
console.log(result)
sessionStorage.removeItem(process.env.VUE_APP_SESSIONSTORAGE_JWT_NAME)
alert('로그인 실패...')
//this.$router.back()
} else {
// localStorage , key-value Storage
sessionStorage.setItem(
process.env.VUE_APP_SESSIONSTORAGE_JWT_NAME,
result
)
alert('로그인 성공...')
// redirect path
this.$router.replace(
sessionStorage.getItem(
process.env.VUE_APP_SESSIONSTORAGE_REDIRECT_NAME
) || '/'
)
// CLear
this.onReset()
}
},
onReset() {
// Reset our form values
this.form.email = ''
this.form.pasword = ''
// redirect path Clear
sessionStorage.removeItem(
process.env.VUE_APP_SESSIONSTORAGE_REDIRECT_NAME
)
// Trick to reset/clear native browser form validation state
// this.$nextTick(() => {
// this.$refs.observer.reset()
// })
},
async callAPI(url, params) {
console.log('CallAPI..', [url, params])
axios.defaults.baseURL = process.env.VUE_APP_AUTH_HOST
// Header
const headers = {
Authorization:
'Bearer ' +
sessionStorage.getItem(process.env.VUE_APP_SESSIONSTORAGE_JWT_NAME)
}
return await axios
.post(url, params, headers)
onSubmit() {
this.$store
.dispatch('AuthStore/login', {
email: this.form.email,
password: this.form.password
})
.then((response) => {
// console.log(response)
return response.status === 201 ? response.data : null
})
.catch((err) => {
console.log(err)
return null
console.log(response)
const return_url = this.$route.params.return_url || 'home'
alert('로그인 성공... -> ' + return_url)
this.$router.push({ name: return_url }).catch((e) => {
console.log(e)
})
})
.catch(({ message }) => alert('로그인 실패...:' + message))
}
}
}

View File

@ -0,0 +1,104 @@
<template>
<!-- 참조: https://codesandbox.io/s/3v0m1?file=/src/components/board/BoardList.vue -->
<b-table
ref="BTable"
:page="page"
:items="items"
:fields="fields"
:per-page="perPage"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
@sort-changed="sortClick"
selectable
select-mode="multi"
@row-selected="selectedRowToggle"
:sticky-header="commonTableAttributes.stickyHeader"
:no-border-collapse="commonTableAttributes.noBorderCollapse"
:striped="commonTableAttributes.striped"
:hover="commonTableAttributes.hover"
:no-local-sorting="commonTableAttributes.noLocalSorting"
:bordered="commonTableAttributes.bordered"
:dark="commonTableAttributes.dark"
:head-variant="commonTableAttributes.headVariant"
>
<!-- Field별 Filter용 -->
<template #head(is_done)="fields">
<b-form-select
:value="filterIsDone"
:options="fields.field.Options"
@change="isDoneClick"
></b-form-select>
</template>
<!-- 비/선택 Row 토글용 -->
<template #cell(rowSelect)="{ rowSelected }">
<template v-if="rowSelected">✓</template>
</template>
<!-- Row의 Index 이용한 번호 출력용 -->
<template #cell(id)="row">
<b-link ref="edit/{{row.item.id}}">
{{ total - (page * perPage + row.index) + parseInt(perPage) }}
</b-link>
</template>
<!-- 제목부분 출력용 -->
<template #cell(title)="row">
{{ row.item.title }}
</template>
<!-- 상세 내용 출력용 -->
<template #cell(content)="row">
<b-button size="sm" @click="row.toggleDetails" class="mr-2">
[{{ row.item.id }}] {{ row.detailsShowing ? '숨기기' : '보기' }}
</b-button>
</template>
<template #row-details="row">
<b-card>
<b-row>
<b-col v-html="row.item.content"></b-col>
</b-row>
<b-button size="sm" @click="row.toggleDetails">숨기기</b-button>
</b-card>
</template>
<!-- 사용여부 출력용 -->
<template #cell(is_done)="row">
{{ row.value }}
</template>
</b-table>
</template>
<script>
export default {
name: 'TableComponent',
props: [
'fields',
'items',
'total',
'page',
'perPage',
'selectedRows',
'filterIsDone'
],
data: function () {
return {
perPageOptions: [
{ text: '5줄', value: 5 },
{ text: '10줄', value: 10 },
{ text: '30줄', value: 30 },
{ text: '60줄', value: 60 },
{ text: '100줄', value: 100 }
],
// 속성옵션
commonTableAttributes: {
stickyHeader: '50%',
//noBorderCollapse: false,
striped: true,
hover: true,
noLocalSorting: true,
bordered: true,
dark: false,
headVariant: 'light'
},
sortBy: 'id',
sortDesc: true
}
}
}
</script>
<style scoped></style>

View File

@ -0,0 +1,87 @@
<template>
<b-container fluid>
<b-row>
<b-col class="border">
<b-input-group>
<label class="input-group-text">
<b-form-checkbox @change="selectedRowAllToggle"
>All</b-form-checkbox
>
</label>
</b-input-group>
</b-col>
<b-col cols="4" class="border">
<b-input-group>
<label class="input-group-text">
<b-form-checkbox-group
class="d-flex flex-nowrap"
v-model="searchFields"
:options="searchFieldOptions"
></b-form-checkbox-group>
</label>
<b-form-input
v-model="search"
type="text"
placeholder="검색어"
@keydown.enter="searchClick"
></b-form-input>
<b-button @click="searchClick">검색</b-button>
</b-input-group>
</b-col>
<b-col cols="4" class="border">
<b-input-group>
<b-form-select
class="d-flex flex-nowrap"
:value="filterDateField"
:options="filterDateFieldOptions"
></b-form-select>
<b-form-input :value="filterDateStart" type="date"></b-form-input>
~
<b-form-input :value="filterDateEnd" type="date"></b-form-input>
<b-button @click="dateClick">선택</b-button>
</b-input-group>
</b-col>
<b-col class="border">
<b-input-group class="justify-content-end">
<label class="input-group-text">줄수</label>
<b-form-select
:value="perPage"
:options="perPageOptions"
@change="perPageClick"
></b-form-select>
</b-input-group>
</b-col>
</b-row>
</b-container>
</template>
<script>
export default {
name: 'TableHeaderComponent',
props: [
'searchFields',
'searchFieldOptions',
'filterDateField',
'filterDateFieldOptions',
'filterDateStart',
'filterDateEnd'
],
data: function () {
return {
perPageOptions: [
{ text: '5줄', value: 5 },
{ text: '10줄', value: 10 },
{ text: '30줄', value: 30 },
{ text: '60줄', value: 60 },
{ text: '100줄', value: 100 }
],
search: null,
filter: null,
filterField: null,
filterDateStart: null,
filterDateEnd: null,
selectedRowAll: false
}
}
}
</script>
<style scoped></style>

View File

@ -0,0 +1,20 @@
<template>
<div>
<b-pagination
:total-rows="total"
:page="page"
:per-page="perPage"
@page-click="pageClick"
last-number
class="justify-content-center"
>
</b-pagination>
</div>
</template>
<script>
export default {
name: 'TablePaginationComponent',
props: ['total', 'page', 'perPage', 'pageClick']
}
</script>
<style scoped></style>

View File

@ -31,20 +31,51 @@
<b-dropdown-item href="#">FA</b-dropdown-item>
</b-nav-item-dropdown> -->
<b-nav-item-dropdown right>
<!-- Using 'button-content' slot -->
<template #button-content>
<em>User</em>
</template>
<b-dropdown-item href="#">Profile</b-dropdown-item>
<b-dropdown-item href="#">Sign Out</b-dropdown-item>
</b-nav-item-dropdown>
<template v-if="isAuthenticated">
<b-nav-item-dropdown right>
<!-- Using 'button-content' slot -->
<template #button-content>
<em>User</em>
</template>
<b-dropdown-item href="/profile">Profile</b-dropdown-item>
<b-dropdown-item @click="logout()">Sign Out</b-dropdown-item>
</b-nav-item-dropdown>
</template>
<template v-else
><b-dropdown-item href="/login"
>Login {{ isAuthenticated }}</b-dropdown-item
>
</template>
</b-navbar-nav>
</b-collapse>
</b-navbar>
</template>
<script>
import { createNamespacedHelpers } from 'vuex'
const authStore = createNamespacedHelpers('AuthStore')
export default {
name: 'HeaderLayout'
name: 'HeaderLayout',
computed: {
...authStore.mapGetters(['isAuthenticated'])
},
methods: {
redirect() {
if (!this.isAuthenticated && this.$router.name != 'home') {
this.$router
.push({
name: 'home'
})
.catch((e) => {
console.log(e + ':' + this.$router)
})
}
},
logout() {
this.$store
.dispatch('AuthStore/logout', {})
.then(() => this.redirect())
.catch(({ message }) => alert(message))
}
}
}
</script>

View File

@ -1,74 +1,58 @@
<template>
<div>
<b-modal
id="inputForm"
centered
title="입력폼"
size="lg"
:header-bg-variant="headerBgVariant"
:header-text-variant="headerTextVariant"
:body-bg-variant="bodyBgVariant"
:body-text-variant="bodyTextVariant"
:footer-bg-variant="footerBgVariant"
:footer-text-variant="footerTextVariant"
>
<validation-observer ref="observer" v-slot="{ handleSubmit }">
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
<validation-provider
:name="field.title.label"
:rules="field.title.rules"
v-slot="validationContext"
>
<b-input-group>
<label class="input-group-text">{{ field.title.label }}</label>
<b-form-input
:name="field.title.key"
:type="field.title.type"
v-model="form.title"
:state="getValidationState(validationContext)"
:style="field.title.style"
></b-form-input>
<b-form-invalid-feedback>{{
validationContext.errors[0]
}}</b-form-invalid-feedback>
</b-input-group>
</validation-provider>
<validation-provider
:name="field.content.label"
:rules="field.content.rules"
v-slot="validationContext"
>
<label class="input-group-text">{{ field.content.label }}</label>
<b-input-group>
<b-form-textarea
:config="field.content.config"
:name="field.content.key"
v-model="form.content"
:state="getValidationState(validationContext)"
:style="field.content.style"
rows="8"
></b-form-textarea>
<b-form-invalid-feedback>{{
validationContext.errors[0]
}}</b-form-invalid-feedback>
</b-input-group>
</validation-provider>
<div class="mt-300 border">
<b-button type="submit" variant="primary">입력</b-button>
<b-button type="reset" variant="danger" @click="onReset()"
>Reset</b-button
>
</div>
</b-form>
</validation-observer>
</b-modal>
</div>
<validation-observer ref="observer" v-slot="{ handleSubmit }">
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
<validation-provider
:name="field.title.label"
:rules="field.title.rules"
v-slot="validationContext"
>
<b-input-group>
<label class="input-group-text">{{ field.title.label }}</label>
<b-form-input
:name="field.title.key"
:type="field.title.type"
v-model="form.title"
:state="getValidationState(validationContext)"
:style="field.title.style"
></b-form-input>
<b-form-invalid-feedback>{{
validationContext.errors[0]
}}</b-form-invalid-feedback>
</b-input-group>
</validation-provider>
<validation-provider
:name="field.content.label"
:rules="field.content.rules"
v-slot="validationContext"
>
<label class="input-group-text">{{ field.content.label }}</label>
<b-input-group>
<b-form-textarea
:config="field.content.config"
:name="field.content.key"
v-model="form.content"
:state="getValidationState(validationContext)"
:style="field.content.style"
rows="8"
></b-form-textarea>
<b-form-invalid-feedback>{{
validationContext.errors[0]
}}</b-form-invalid-feedback>
</b-input-group>
</validation-provider>
<div class="mt-300 border">
<b-button type="submit" variant="primary">입력</b-button>
<b-button type="reset" variant="danger" @click="onReset()"
>Reset</b-button
>
</div>
</b-form>
</validation-observer>
</template>
<script>
import axios from 'axios'
//import { VueEditor } from 'vue2-editor/dist/vue2-editor.core.js'
export default {
components: {},
data() {
@ -93,23 +77,7 @@ export default {
rules: { required: true, min: 10 },
style: 'height: 200px'
}
},
variants: [
'primary',
'secondary',
'success',
'warning',
'danger',
'info',
'light',
'dark'
],
headerBgVariant: 'dark',
headerTextVariant: 'light',
bodyBgVariant: 'light',
bodyTextVariant: 'dark',
footerBgVariant: 'warning',
footerTextVariant: 'dark'
}
}
},
methods: {

View File

@ -60,10 +60,9 @@
</b-row>
</b-container>
<b-table
id="todoTable"
ref="todoTable"
ref="BTable"
:page="page"
:items="rows"
:items="getRows"
:fields="fields"
:per-page="perPage"
:sort-by.sync="sortBy"
@ -75,7 +74,6 @@
:select-mode="selectedRowMode"
selectable
@row-selected="rowSelectedToggle"
:busy="isBusy"
:sticky-header="commonTableAttributes.stickyHeader"
:no-border-collapse="commonTableAttributes.noBorderCollapse"
:striped="commonTableAttributes.striped"
@ -95,13 +93,12 @@
</template>
<!-- /선택 Row 토글용 -->
<template #cell(rowSelect)="{ rowSelected }">
<template v-if="rowSelected"> </template>
<template v-else> &nbsp; </template>
<template v-if="rowSelected"></template>
</template>
<!-- Row의 Index 이용한 번호 출력용 -->
<template #cell(id)="row">
<b-link ref="edit/{{row.item.id}}">
{{ total - (page * perPage + row.index) + perPage }}
{{ getTotal - (page * perPage + row.index) + Number(perPage) }}
</b-link>
</template>
<!-- 제목부분 출력용 -->
@ -126,13 +123,6 @@
<template #cell(is_done)="row">
{{ row.value }}
</template>
<!-- 데이터 로딩중 출력용 -->
<template #table-busy>
<div class="text-center text-danger my-2">
<b-spinner class="align-middle"></b-spinner>
<strong>Loading...</strong>
</div>
</template>
</b-table>
<b-container fluid>
<b-row>
@ -142,32 +132,33 @@
<b-col>
<b-pagination
:page="page"
:total-rows="total"
:total-rows="getTotal"
:per-page="perPage"
@page-click="pageClick"
last-number
aria-controls="todoTable"
class="justify-content-center"
>
</b-pagination>
</b-col>
<b-col>
<b-input-group class="justify-content-end">
<b-button @click="busyToggle">Toggle Busy State</b-button>
</b-input-group>
</b-col>
<b-col> </b-col>
</b-row>
</b-container>
</div>
</template>
<script>
import { createNamespacedHelpers } from 'vuex'
const todoStore = createNamespacedHelpers('TodoStore')
// : https://vuejsexamples.com/vuejs-tables-and-select-all-checkbox/
import axios from 'axios'
export default {
components: {},
computed: {
...todoStore.mapGetters(['getTotal', 'getRows'])
},
created: function () {
this.setDatas()
},
data: function () {
return {
rows: [],
fields: [
{
key: 'rowSelect',
@ -221,8 +212,7 @@ export default {
sortByFormatted: true //fomatter Sort true
}
],
total: 0,
page: process.env.VUE_APP_TABLE_DEFAULT_PAGE, //
page: 1, //
perPage: process.env.VUE_APP_TABLE_DEFAULT_PERPAGE, //
perPageOptions: [
{ text: '5줄', value: 5 },
@ -242,7 +232,6 @@ export default {
dark: false,
headVariant: 'light'
},
isBusy: false,
sortBy: 'id',
sortDesc: true,
search: null,
@ -267,17 +256,10 @@ export default {
selectedRowModes: ['multi', 'single', 'range']
}
},
setup: function () {},
created: function () {},
mounted: function () {
this.getDatas()
},
unmounted() {},
methods: {
async getDatas(page = 1) {
this.isBusy = true
const results = await this.callAPI('/todo', {
params: {
setDatas(page = 1) {
this.$store
.dispatch('TodoStore/setData', {
page: page,
perPage: this.perPage,
sortBy: this.sortBy,
@ -288,90 +270,60 @@ export default {
filterField: this.filterField,
filterDateStart: this.filterDateStart,
filterDateEnd: this.filterDateEnd
}
})
//console.log(JSON.stringify(results))
console.log(results)
this.total = results.total
this.perPage = results.perPage
this.page = results.page
this.sortBy = results.sortBy
this.sortDesc = results.sortDesc === 'true' ? true : false
this.rows = results.rows
this.isBusy = false
})
.catch((error) => {
alert(error)
})
},
searchClick() {
this.getDatas()
this.setDatas()
},
pageClick(bvEvent, page) {
console.log(bvEvent)
this.getDatas(page)
// bvEvent.preventDefault()
pageClick(event, page) {
//console.log(event)
this.setDatas(page)
// event.preventDefault()
},
perPageClick(perPage) {
this.perPage = perPage
console.log(this.perPage)
this.getDatas()
//console.log(this.perPage)
this.setDatas()
},
sortClick(event) {
console.log(event)
//console.log(event)
this.sortBy = event.sortBy
this.sortDesc = event.sortDesc
this.getDatas()
this.setDatas()
},
dateClick() {
this.filterField = this.filterDateField
this.getDatas()
this.setDatas()
},
isDoneClick() {
this.filter = this.filterIsDone
this.filterField = 'is_done'
this.getDatas()
},
busyToggle() {
this.isBusy = !this.isBusy
this.setDatas()
},
rowSelectedToggle(selectedRow) {
this.selectedRows = selectedRow
},
rowSelectAllToggle() {
if (this.selectedRowAllToggle) {
this.$refs.todoTable.clearSelected()
this.$refs.BTable.clearSelected()
this.selectedRowAllToggle = false
} else {
this.$refs.todoTable.selectAllRows()
this.$refs.BTable.selectAllRows()
this.selectedRowAllToggle = true
}
},
}
// rowSelectSet(id) {
// // Rows are indexed from 0, so the third row is index 2
// this.$refs.todoTable.selectRow(id)
// this.$refs.BTable.selectRow(id)
// },
// rowSelectUnSet(id) {
// // Rows are indexed from 0, so the third row is index 2
// this.$refs.todoTable.unselectRow(id)
// this.$refs.BTable.unselectRow(id)
// }
async callAPI(url, params) {
console.log('CallAPI..', [url, params])
axios.defaults.baseURL = process.env.VUE_APP_BACKEND_HOST
// Header
const headers = {
Authorization:
'Bearer ' +
sessionStorage.getItem(process.env.VUE_APP_LOCALSTORAGE_NAME)
}
return await axios
.get(url, params, headers)
.then((response) => {
return response.status === 200 ? response.data : []
})
.catch((err) => {
console.log(err)
return []
})
}
},
computed: {}
}
}
</script>
<style scoped></style>

View File

@ -1,7 +1,7 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import store from '../store'
Vue.use(VueRouter)
const routes = [
@ -33,8 +33,8 @@ const routes = [
component: () =>
import(
/* webpackChunkName: "api", webpackPrefetch:true */ '@/views/todoView.vue'
)
//meta: { requiredAuth: true }
),
meta: { requiredAuth: true }
}
]
@ -52,21 +52,6 @@ const router = new VueRouter({
// 1. 전역가드 : router.beforeEach((to, from, next) 이런식으로 전체에 대해 특정조건 (meta: { requiredAuth: true })으로 사용
// 2. 라우터 가드: 특정 Route Path아래에 직접선언-> beforeEach: function(to, from, next){}
// 3. 컴포넌트 가드: 특정 vue 파일내에서 바로 선언해서 사용 :// view/Mypage.vue ( Mypage Components )
// export default {
// name: "Mypage",
// beforeRouteEnter(to, from, next) {
// // Login 컴포넌트가 화면에 표시되기 전에 수행될 로직
// // Login 컴포넌트는 아직 생성되지 않은 시점
// },
// beforeRouteUpdate(to, from, next) {
// // 화면에 표시된 컴포넌트가 변경될 때 수행될 로직
// // `this`로 Login 컴포넌트를 접근할 수 있음
// },
// beforeRouteLeave(to, from, next) {
// // Login 컴포넌트를 화면에 표시한 url 값이 변경되기 직전의 로직
// // `this`로 Login 컴포넌트를 접근할 수 있음
// },
// }
// 로그인 처리 과정용
// to : 이동할 url 정보가 담긴 라우터 객체
// from : 현재 url 정보가 담긴 라우터 객체
@ -74,26 +59,21 @@ const router = new VueRouter({
//여기에서 login은 path가 아니라 name에서 찾는다
//router.beforeEach()를 호출하고 나면 모든 라우팅이 대기 상태가 된다
router.beforeEach((to, from, next) => {
console.log('라우딩 대기')
//1. routes 설정에서 meta: { requiredAuth: true } 가 선언된 경우
if (to.matched.some((routeRecord) => routeRecord.meta.requiredAuth)) {
if (to.matched.some((route) => route.meta.requiredAuth)) {
//2. 로그인 인증 않된 경우
//sessionStorage Access-Token이 없으면 Login페이지로 전송
if (!sessionStorage.getItem(process.env.VUE_APP_SESSIONSTORAGE_JWT_NAME)) {
console.log(from.path + ' => 3. Login 페이지 이동 => 로그인 페이지')
//로그인 성공 후 이동할 URL 저장
sessionStorage.setItem(
process.env.VUE_APP_SESSIONSTORAGE_REDIRECT_NAME,
to.path
)
next({ name: 'login' })
//console.log(store.getters)
//console.log(store.getters['AuthStore/isAuthenticated'])
if (!store.getters['AuthStore/isAuthenticated']) {
console.log(from.name + ' => 3. To[' + to.name + '] => 로그인')
next({ name: 'login', params: { return_url: to.name } })
} else {
//3. 로그인 인증완료
console.log(from.path + ' => 2. 이미인증완료 => ' + to.path)
console.log(from.name + ' => 2. 이미인증완료 => ' + to.name)
next()
}
} else {
console.log(from.path + ' => 1. 인증요구 없음 => ' + to.path)
console.log(from.name + ' => 1. 인증요구 없음 => ' + to.name)
next()
}
})

View File

@ -1,12 +1,13 @@
import Vue from 'vue'
import Vuex from 'vuex'
import AuthStore from './modules/auth'
import TodoStore from './modules/todo'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {}
modules: {
AuthStore,
TodoStore
}
})

97
src/store/modules/auth.js Normal file
View File

@ -0,0 +1,97 @@
import axios from 'axios'
import jwt from './jwt'
//참조: https://yamoo9.github.io/axios/guide/interceptors.html
const authAxios = axios.create({
baseURL: process.env.VUE_APP_AUTH_HOST,
headers: { 'content-type': 'application/json' }
})
authAxios.interceptors.request.use(
(config) => jwt.getAuthorizationHeader(config),
(error) => {
// Do something with request error
Promise.reject(error)
}
)
authAxios.defaults.headers.post['Content-Type'] =
'application/x-www-form-urlencoded'
// count state 속성 추가
const state = {
token: {
accessToken: jwt.getToken()
}, // 토큰정보
isAuthenticated: jwt.isAuthenticated()
}
const getters = {
getAccessToken: function (state) {
return state.token.accessToken
},
isAuthenticated: function (state) {
return state.isAuthenticated
}
}
const mutations = {
login: function (state, payload = {}) {
state.token.accessToken = payload.accessToken
state.isAuthenticated = true
jwt.saveToken(payload.accessToken)
},
logout: function (state) {
state.token.accessToken = ''
state.isAuthenticated = false
jwt.destroyToken()
}
}
const actions = {
login: function (context, payload) {
let params = {
email: payload.email,
password: payload.password
}
return new Promise((resolve, reject) => {
authAxios
.post('/auth/login', params)
.then((response) => {
const { data } = response
context.commit('login', {
accessToken: data.accessToken
})
resolve(response)
})
.catch((error) => {
reject(error)
})
})
},
logout: function (context, payload) {
return new Promise((resolve) => {
setTimeout(function () {
context.commit('logout', payload)
resolve({})
}, 1000)
})
},
register: function (context, payload) {
let params = {
email: payload.email,
password: payload.password,
name: payload.name
}
return new Promise((resolve, reject) => {
authAxios
.post('/user/register', params)
.then((response) => {
const { data } = response
context.commit('login', {
accessToken: data.accessToken
})
resolve(response)
})
.catch((error) => {
reject(error)
})
})
}
}
export default { namespaced: true, state, getters, mutations, actions }

28
src/store/modules/jwt.js Normal file
View File

@ -0,0 +1,28 @@
const Payload_KEY_NAME = 'access_token'
const getToken = () => {
return window.localStorage.getItem(Payload_KEY_NAME)
}
const saveToken = (token) => {
window.localStorage.setItem(Payload_KEY_NAME, token)
}
const destroyToken = () => {
window.localStorage.removeItem(Payload_KEY_NAME)
}
const isAuthenticated = () => {
return !!getToken()
}
const getAuthorizationHeader = (config) => {
//로그인이 되었는지 확인후 Request시 헤더에 로그인정보 추가하기 위함
if (isAuthenticated) {
config.headers.common['Authorization'] = getToken()
}
return config
}
export default {
getToken,
saveToken,
destroyToken,
isAuthenticated,
getAuthorizationHeader
}

55
src/store/modules/todo.js Normal file
View File

@ -0,0 +1,55 @@
import axios from 'axios'
import jwt from './jwt'
//참조: https://yamoo9.github.io/axios/guide/interceptors.html
const todoAxios = axios.create({
baseURL: process.env.VUE_APP_BACKEND_HOST,
headers: { 'content-type': 'application/json' }
})
todoAxios.interceptors.request.use(
(config) => jwt.getAuthorizationHeader(config),
(error) => {
// Do something with request error
Promise.reject(error)
}
)
todoAxios.defaults.headers.post['Content-Type'] =
'application/x-www-form-urlencoded'
// count state 속성 추가
const state = {
total: 0,
rows: []
}
const getters = {
getTotal: function (state) {
return state.total
},
getRows: function (state) {
return state.rows
}
}
const mutations = {
setData: function (state, data) {
state.total = data.total
state.rows = data.rows
}
}
const actions = {
setData: function (context, params) {
console.log(params)
return new Promise((resolve, reject) => {
todoAxios
.get('/todo', params)
.then((response) => {
const { data } = response
context.commit('setData', data)
resolve(response)
})
.catch((error) => {
reject(error)
})
})
}
}
export default { namespaced: true, state, getters, mutations, actions }

View File

@ -1,15 +1,23 @@
<template>
<div>
<div class="wrapper">
<!-- 참조: https://codesandbox.io/s/3v0m1?file=/src/components/board/BoardList.vue -->
<LoginComponent />
</div>
</template>
<script>
import LoginComponent from '@/components/common/LoginComponent.vue'
import LoginComponent from '@/components/LoginComponent.vue'
export default {
components: {
LoginComponent
}
}
</script>
<style scoped></style>
<style scoped>
.wrapper {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: white;
}
</style>

View File

@ -3,18 +3,48 @@
<!-- 참조: https://codesandbox.io/s/3v0m1?file=/src/components/board/BoardList.vue -->
<ListComponent />
<div class="justify-content-end border">
<b-button v-b-modal.inputForm variant="outline-primary">입력</b-button>
<InputComponent />
<template v-if="isAuthenticated">
<b-button v-b-modal.inputForm variant="outline-primary">입력</b-button>
<b-modal
id="inputForm"
centered
title="입력폼"
size="lg"
:header-bg-variant="headerBgVariant"
:header-text-variant="headerTextVariant"
:body-bg-variant="bodyBgVariant"
:body-text-variant="bodyTextVariant"
:footer-bg-variant="footerBgVariant"
:footer-text-variant="footerTextVariant"
>
<InputComponent />
</b-modal>
</template>
</div>
</div>
</template>
<script>
import ListComponent from '@/components/todo/ListComponent.vue'
import InputComponent from '@/components/todo/InputComponent.vue'
import { createNamespacedHelpers } from 'vuex'
const authStore = createNamespacedHelpers('AuthStore')
export default {
components: {
InputComponent,
ListComponent
},
computed: {
...authStore.mapGetters(['isAuthenticated'])
},
data() {
return {
headerBgVariant: 'dark',
headerTextVariant: 'light',
bodyBgVariant: 'light',
bodyTextVariant: 'dark',
footerBgVariant: 'warning',
footerTextVariant: 'dark'
}
}
}
</script>