Store_Vuex init...
This commit is contained in:
parent
5ff12cab9d
commit
845c3eb97d
7
.env
7
.env
@ -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
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
104
src/components/common/TableComponent
Normal file
104
src/components/common/TableComponent
Normal 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>
|
||||
87
src/components/common/TableHeaderComponent
Normal file
87
src/components/common/TableHeaderComponent
Normal 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>
|
||||
20
src/components/common/TablePaginationComponent
Normal file
20
src/components/common/TablePaginationComponent
Normal 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>
|
||||
@ -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>
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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> </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>
|
||||
|
||||
@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
@ -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
97
src/store/modules/auth.js
Normal 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
28
src/store/modules/jwt.js
Normal 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
55
src/store/modules/todo.js
Normal 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 }
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user