서버 띄울 땐 백엔드 먼저, 프론트는 그 다음!
프론트 파일
index : 리스트 등
inform : 모달
검색 기능 구현
C:\Workspace\metacamp-frontend-main\src\store\models\department.js
/* RestAPI 호출 */
api
.get('/serverApi/departments', { params: payload })
.then(response => {
const departmentList = response && response.data && response.data.rows
context.commit('setDepartmentList', departmentList)
})
'song' 검색
C:\Workspace\metacamp-frontend-main\src\views\department\index.vue
methods: {
searchDepartmentList() {
this.$store.dispatch('actDepartmentList', { name: 'song' })
},
사실 이렇게 백엔드에서 안해도 되는 게 필터링 지원하는 테이블이 많다. 그거 쓰면 된다.
오브젝트랑 JSON의 차이점
{
code: "일이삼"
}
오브젝트는 위처럼 키에 쌍따옴표가 없다.(JSON은 있음)
폼에선 오브젝트로 넘어가지만 axios가 알아서 JSON으로 바꿔준다.(원래 바꿔줘야함)
백엔드 연동
부서 관리, 사용자 관리, 장비 관리 모두 백엔드랑 연동
어제 배운대로 아래 부분들을 수정했다.
api
.post('/serverApi/users', payload)
.then(response => {
const insertedResult = response && response.data && response.data.id
context.commit('setInsertedResult', insertedResult)
})
검색기능 추가
무난하게 검색 기능까지 추가했다. 참고
- 검색 후 신규등록을 하면 검색 조건대로 리스트가 뜨는데 초기화 여부는 정책 문제
- 프론트에서 보고 백엔드에서 찾는 능력
model 파일 수정
C:\Workspace\metacamp-frontend-main\src\store\models\department.js
// ...
actions: {
// 부서 리스트 조회
actDepartmentList(context, payload) {
/* 테스트 데이터 세팅 */
/*
const departmentList = [
{ id: 1, name: '개발팀', code: 'dev', createdAt: '2021-12-01T00:00:00.000Z' },
{ id: 2, name: '영업팀', code: 'sales', createdAt: '2021-12-01T00:00:00.000Z' }
]
context.commit('setDepartmentList', departmentList)
*/
/* RestAPI 호출 */
api
.get('/serverApi/departments', { params: payload })
.then(response => {
const departmentList = response && response.data && response.data.rows
context.commit('setDepartmentList', departmentList)
})
// ...
인덱스 파일 수정
C:\Workspace\metacamp-frontend-main\src\views\department\index.vue
//...
<b-col style="text-align: left">
<b-input-group style="width: 250px">
<b-form-input
v-model="searchParams"
placeholder="부서명을 입력하세요"
size="sm"
@keyup.ctrl.enter="searchDepartmentList"
></b-form-input>
<b-input-group-append>
<b-button variant="primary" size="sm" @click="searchDepartmentList">검색</b-button>
</b-input-group-append>
</b-input-group>
</b-col>
//...
data() {
return {
searchParams: null,
fields: [
//...
methods: {
searchDepartmentList() {
// 검색 기능 설정
this.$store.dispatch('actDepartmentList', { name: this.searchParams })
// console.log('actDepartmentList', this.searchParams)
},
//...
근데 사용자 관리에서 문제가 생겼다.
백엔드에서 params를 name과 userid 둘 다 넘겨줘서 둘 다 검색할 수 있도록 만들고 싶었다.
처음엔 이렇게 짰다.
methods: {
searchUserList() {
this.$store.dispatch('actUserList', { name: this.searchParams.name, userid: this.searchParams.userid })
},
안됐다.(name만 검색됨)
그 다음엔 이렇게 짰다.
data() {
return {
searchParams: {
name: null,
userid: null
},
// ...
methods: {
searchUserList() {
this.$store.dispatch('actUserList', { name: this.searchParams.name || userid: this.searchParams.userid })
},
다음과 같은 오류가 떴다.
검색해보니 stackoverflow에서 타입을 설정해주길래 나도 String으로 타입을 설정해봤다.
data() {
return {
searchParams: {
type: String,
name: null,
userid: null
},
// ...
methods: {
searchUserList() {
this.$store.dispatch('actUserList', {
name: String(this.searchParams.name),
userid: String(this.searchParams.userid)
})
},
data에서 searchParams를 type: String으로 바꿔줬을 때 에러가 하나 줄어드는 것 같길래 밑에도 적용해봤는데 똑같았다.
이건 강사님께 여쭤보니 백엔드에서 or로 처리해야하는 것이었다...!
강사님은 form input을 두개 만들어서 해결하셨다.
이렇게 간단한 해결법이...
그래서 이런 식으로 짜봤다.
// ...
<b-col style="text-align: left">
<b-input-group style="width: 500px">
<b-form-input v-model="searchParams.name" placeholder="이름을 입력하세요" size="sm"></b-form-input>
<b-form-input v-model="searchParams.userid" placeholder="아이디를 입력하세요" size="sm"></b-form-input>
<b-input-group-append>
<b-button variant="primary" size="sm" @click="searchUserList">검색</b-button>
</b-input-group-append>
</b-input-group>
</b-col>
// ...
methods: {
searchUserList() {
this.$store.dispatch('actUserList', { name: this.searchParams.name, userid: this.searchParams.userid })
},
근데 강사님은 mehods의 searchUserList에 그냥 아래처럼 this.searchParams 그대로 넣으셨다. 그래도 잘 작동했다.
methods: {
searchUserList() {
this.$store.dispatch('actUserList', this.searchParams)
},
궁금해서 로그를 보니 그냥 this.searchParams로 적어도 백엔드에 {"name":"b"} 이렇게 잘 들어갔다.
2022-01-28 13:45:36.236[info] (device.list.params) {"name":"b"}
공백 검사 방지
Trim 함수 : 빈공백 없애기로 띄어쓰기 검색을 방지할 수 있다.
로그인 / 로그아웃
jwt를 발행/폐기하는 방식으로 처리
발급된 토큰 정보는 웹브라우저의 LocalStorage에 저장한다.
백엔드 서버에 전송할때 토큰은 request.headers.token에 전송 한다. (백엔드의 API를 확인할 것)
응답 받을때 토큰은 response.headers.token에 전송 받는다. (백엔드의 API를 확인할 것)
참고 우리는 별도의 헤더 정보를 사용하며 Bearer를 사용하지 않는다.
LocalStorage 확인
도메인/포트가 맞으면 저렇게 Storage(공간)을 준다.
키:밸류로 값을 저장할 수 있다.
차이점
local Storage : 그냥 브라우저에 담김
=> 재부팅해도 안날아감, 브라우저를 새로 띄워도 안날아감(내가 날리기 전까진) ex. 구글 로그인 유지(보안의 책임은 사용자에게...)
Session Storage : 해당 세션에만 담김
=> 브라우저를 새로 띄울 때마다 별도의 공간이 할당됨(브라우저를 닫으면 프론트에서 로그아웃됨 / 백엔드에서도 로그아웃 날리려면 코드를 따로 짜야됨)
==> 둘중 무엇을 선택하냐 : 정책의 차이
Bearer
Bearer에 담겠다 = 여기다 담겠다는 뜻인데 우리는 Headers에 따로 담을 거라 쓰지 않을 것(request.headers.token/response.headers.token)
Bearer 쓰는 이유 : auth처럼 토큰 정보를 공용으로 넣고 싶을 때 / 우리처럼 백엔드 프론트를 따로 할 땐 쓸 필요 없다.
로그인 화면
헤더 빼는 법
v-if로 라우터 파일의 meta.header이 false가 아니면(즉 true면) header가 보이도록 설정해준다.
라우터 파일에서 meta.header에 false 조건만 적어놨기에 !== false라고 적는 게 좋다.
true/false의 경우에도 null 값이 있을 수 있다.
C:\Workspace\metacamp-frontend-main\src\App.vue
<template>
<div>
<app-header v-if="this.$route.meta.header !== false" />
<router-view />
</div>
</template>
/auth의 /login, /logout에 meta에서 header를 false로 설정해준다.
meta는 내 마음대로 쓸 수 있다.
C:\Workspace\metacamp-frontend-main\src\router\index.js
{
path: '/auth',
component: () => import('../views/auth'), // /index가 자동으로 설정됨
children: [
{
path: '/auth/login',
component: () => import('../views/auth/login'),
meta: { header: false, noLogin: true }
},
{
path: '/auth/logout',
component: () => import('../views/auth/logout'),
meta: { header: false, noLogin: true }
}
]
},
위의 코드 내용 부가 설명 : /auth/index 파일의 <router-view /> Childeren의 컴포넌트로 교체된다
C:\Workspace\metacamp-frontend-main\src\views\auth\index.vue
<template>
<router-view />
</template>
토큰 발행
로그인
jwt-decode 설치(토큰 정보 확인용)
npm install jwt-decode
RestApi 호출
C:\Workspace\metacamp-frontend-main\src\store\models\auth.js
// ...
const stateInit = {
TokenUser: {
id: null,
userid: null,
name: null,
role: null,
iat: null,
exp: null
}
}
export default {
state: {
TokenUser: { ...stateInit.TokenUser }, // token에서 추출한 사용자 정보
Loading: false,
Error: null
},
// ...
/* RestApi 호출 */
api
.post('/serverApi/auths/token', payload) // token이 담긴다
// serverApi = http://localhost:3000/
.then(response => {
console.log(response)
const token = response.headers.token
// 백엔드 auth에서 설정했던 headers에 set한 token을 불러온다
const decodedToken = jwtDecode(token)
// 정상인 경우 처리
context.commit('setLoading', false)
context.commit('setTokenUser', decodedToken)
})
.catch(error => {
// 에러인 경우 처리
context.commit('setLoading', false)
context.commit('setError', error)
})
// ...
watch에서 tokenUser가 값이 0보다 크면(3번 걸쳐서 확인) 로그인이 완료되어 /home으로 이동한다.
C:\Workspace\metacamp-frontend-main\src\views\auth\login.vue
watch: {
tokenUser(value) {
if (value && value.id && value.id > 0) {
// 로그인이 완료된 상황
this.$router.push('/home') // 메인 페이지 이동
}
},
에러가 난 경우
error(errValue) {
if (errValue !== null) {
// 메세지 출력
this.$bvToast.toast('아이디/비밀번호를 확인해 주세요.', {
title: '로그인 에러',
variant: 'danger',
solid: true
})
}
}
LocalStorage에 쌓인 토큰 확인
axios의 Interceptor 확인
axios에서 제공하는 interceptors를 이용하여 request와 response할때 headers에 token값을 전송/수신 할 수 있다.
주의 토큰 정보를 헤더를 통해서 백엔드와 주고 받을 때에는 백엔드의 API와 맞춰야 한다.
C:\Workspace\metacamp-frontend-main\src\store\apiUtil.js
// request(요청)시 아래의 로직이 인터셉트 된다.
api.interceptors.request.use(
async request => {
// header.token 전송
const token = window.localStorage.getItem('token')
request.headers.token = token
return request
},
async error => {
return Promise.reject(error)
}
)
// response(응답)시 아래의 로직이 인터셉트 된다.
api.interceptors.response.use(
async response => {
// header.token 자동 갱신
const token = response.headers.token // token을 header에서 받은 경우
if (token) {
window.localStorage.setItem('token', token)
}
return response
},
async error => {
return Promise.reject(error)
}
)
요청의 경우 아래 부분에서 Headers의 token에 토큰 값을 넣어준다.(갱신 된다)
request.headers.token = token
그리고 응답의 경우 아래 부분에서 headrs에 token이 있는 경우(없을 수도 있으니 if문) localStorage에 토큰을 담아준다.
const token = response.headers.token // token을 header에서 받은 경우
if (token) {
window.localStorage.setItem('token', token)
}
확인해보면
백엔드를 호출할 때마다(페이지를 이동할 때마다) 토큰이 갱신 된다.(두 줄이 다름)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6OSwidXNlcmlkIjoiaWQxIiwibmFtZSI6ImJhbTciLCJyb2xlIjoibWVtYmVyIiwiaWF0IjoxNjQzMzUwOTkyLCJleHAiOjE2NDMzNTgxOTJ9.qNoZLJm-B7VflAXTbbILgJsNi0coSaBc-Flz1IV4kzY
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6OSwidXNlcmlkIjoiaWQxIiwibmFtZSI6ImJhbTciLCJyb2xlIjoibWVtYmVyIiwiaWF0IjoxNjQzMzUxMDAxLCJleHAiOjE2NDMzNTgyMDF9.ObxBWVvzDk1ik5eLC0300w6GEK9PFu4cjblrZk_7TUA
백엔드에서 isLoggedIn(middleware)가 토큰 검증이 성공할 때마다 새로운 토큰을 준다.
C:\Workspace\nodeproj\lib\middleware.js
if (token) {
// 토큰이 있는 경우 토큰 검증 수행
const decoded = tokenUtil.verifyToken(token);
logger.debug(`isLoggedIn decoded ${JSON.stringify(decoded)}`); // 로그 찍어보기
if (decoded) {
// 1. 토큰 검증이 성공한 경우 새로 갱신해준다.
const newToken = tokenUtil.makeToken(decoded); // 새로운 토큰 만들어줌
res.set('token', newToken); // header에 token에 새로운 토큰 세팅
next(); // 미들웨어 통과(계속 진행)
로그인 상태에서 재로그인 방지
로그인을 이미 한 상태에서 로그인 페이지 방문을 막아야한다.
created() {
// 이미 토큰을 가지고 있는 경우 처리를 위한 로직
const token = window.localStorage.getItem('token')
if (token) { // 토큰이 있다면
const decodedToken = jwtDecode(token)
const today = new Date() // 오늘 날짜
const expDate = new Date(decodedToken.exp * 1000)
/ 만료일이 초단위까지만 나와있어 today랑 비교하려면 ms(1000) 단위를 맞춰준다.
if (expDate && expDate >= today) { // 만료일이 있고 만료일이 오늘보다 크거나 같으면
// 토큰이 유효한 경우
this.$router.push('/home') // 메인 페이지 이동
} else {
// 토큰이 만료된 경우
window.localStorage.removeItem('token') // 토큰 삭제
}
}
},
프론트는 콘솔 로그로만 확인 가능하고 백엔드처럼 로그 파일(logger)로 확인하는게 불가능(프론트는 클라이언트에서만 돌고 서버가 아니기 때문)
만료일 확인하는 법
메인 페이지로의 이동을 막아주고 콘솔 로그를 찍는다.
자동으로 GMT로 뜨기 때문에 UTC로 별도의 변환이 필요하다.(console.log('expDate', expDate.toUTCString()) 이건 안먹힘)
created() {
// 이미 토큰을 가지고 있는 경우 처리를 위한 로직
const token = window.localStorage.getItem('token')
if (token) {
const decodedToken = jwtDecode(token)
const today = new Date()
const expDate = new Date(decodedToken.exp * 1000)
console.log('decodedToken', decodedToken)
console.log('today', today)
console.log('expDate', expDate)
if (expDate && expDate >= today) {
// 토큰이 유효한 경우
// this.$router.push('/home') // 메인 페이지 이동
} else {
// 토큰이 만료된 경우
window.localStorage.removeItem('token') // 토큰 삭제
}
}
},
로그아웃 페이지
강제로 로그아웃 할 수 있게 하기 위해 별도의 페이지로 구현
보통 함수로 구현도 많이 한다.(비추천)
export default {
computed: {
loading() {
return this.$store.getters.TokenLoading
}
},
watch: {
loading(value) {
if (value === false) {
// 로그아웃 처리 후 이동
this.$router.push('/auth/login')
}
}
},
created() {
this.$store.dispatch('authLogout')
}
}
로그아웃 상태에서 일반 페이지 접속 시 로그인 페이지로 튕기기
-> 라우터에서 세팅
프론트엔드 토큰 만료 테스트
만료시간을 1분으로 변경
C:\Workspace\nodeproj\lib\tokenUtil.js
const options = {
// expiresIn: '2h', // 만료시간 : 2시간
expiresIn: '1m', // 만료시간 : 1분
};
로그인하여 토큰 빼오기(복사)
1분 차이 확인(jwt.io)
1분 후에 새로고침하면 다시 로그인 페이지로 튕기는 걸 알 수 있다.
토큰도 사라졌다.
프론트엔드 토큰 조작 테스트
어제 만든 1년짜리 토큰 복붙하면 로그인된걸로 인식하여 새로고침하면 로그인된 페이지로 들어간다.
네트워크에서 Response Headers / Request Headers 확인
Response Headers
Request Headers
둘의 토큰 값이 다름을 알 수 있다.
(appUtil.js 코드 참고)
토큰 검사
SPA의 특징 : 페이지를 새로고침하면 모든 Store 정보가 날아감
=> 로그인한 사용자 정보처럼 중요한 Store 정보는 유지할 수 있도록 해야함
router의 beforeEach를 이용해서 구현 : 라우팅 시 페이지 이동 전 체크하는 로직 구현 가능
router.beforeEach는 반드시 const router = new Router의 하단에 위치해야 함 공식 문서
네비게이션 가드 | Vue Router
네비게이션 가드 이름에서 알 수 있듯이 vue-router가 제공하는 네비게이션 가드는 주로 리디렉션하거나 취소하여 네비게이션을 보호하는 데 사용됩니다. 라우트 탐색 프로세스에 연결하는 방법
router.vuejs.org
C:\Workspace\metacamp-frontend-main\src\router\index.js
router.beforeEach(async (to, from, next) => {
// console.log('router.beforeEach', to, from)
const noLogin = to.meta.noLogin // 이동할 페이지에서 로그인 허용여부 확인
if (noLogin === true) {
// 로그인이 필요없는 페이지는 그냥 이동
next()
} else {
// 로그인이 필요한 페이지는 토큰 체크 후 통과 여부 결정
// 1. localStorage에서 토큰 추출
const token = window.localStorage.getItem('token')
// TODO: 리다이렉트 페이지 처리(이동하려던 페이지로 이동시킬 수 있다.)
try {
const decodedToken = jwtDecode(token) // 토큰디코딩
const today = new Date() // 오늘날짜
const expDate = new Date(decodedToken.exp * 1000) // 토큰에서 만료일추출
if (expDate && expDate >= today) {
// 토큰이 유효한 경우
// 1. tokenUser정보가 없어진 경우 다시 갱신한다.
const tokenUser = store.getters['TokenUser']
if (!tokenUser || !tokenUser.id > 0) {
store.dispatch('authTokenUser', token)
}
// 처리를 다 했으면 가던길 가자
next()
} else {
// 토큰이 만료된 경우
next('/auth/login') // 로그인 페이지로 이동(login.vue의 created 부분을 통해 토큰을 삭제해준다.)
}
} catch (err) {
// 토큰 추출이 실패한 경우에 대한 처리
next('/auth/login') // 로그인 페이지로 이동
}
}
}
beforeEach()
router.beforeEach(async (to, from, next) => {
console.log로 to, from 확인
console.log('router.beforeEach', to, from)
noLogin
{
path: '/auth',
component: () => import('../views/auth'), // /index가 자동으로 설정됨
children: [
{
path: '/auth/login',
component: () => import('../views/auth/login'),
meta: { header: false, noLogin: true }
},
{
path: '/auth/logout',
component: () => import('../views/auth/logout'),
meta: { header: false, noLogin: true }
}
]
},
위의 routes에서 설정해줬다.
토큰 복구
store에 담긴 사용자 정보가 새로고침하면 날아가기 때문에 LocalStorage에 남아있는 토큰을 이용해 복구해준다.
if (expDate && expDate >= today) {
// 토큰이 유효한 경우
// 1. tokenUser정보가 없어진 경우 다시 갱신한다.
const tokenUser = store.getters['TokenUser']
if (!tokenUser || !tokenUser.id > 0) {
store.dispatch('authTokenUser', token)
}
// 처리를 다 했으면 가던길 가자
next()
}
authTokenUser
C:\Workspace\metacamp-frontend-main\src\store\models\auth.js
authTokenUser(context, payload) {
// 토큰사용자 설정
const decodedToken = jwtDecode(payload)
context.commit('setTokenUser', decodedToken)
}
직접 연동해보기
백엔드 연동, 로그인, beforeEach(), 인터셉트까지 무난하게 성공했다.
근데 검색창이 말썽이었다.
원래 잘 되던 프론트의 부서 관리, 장비 관리의 검색창에 값을 넣고 겁색버튼을 눌러도 백엔드로 넘어가질 않았다.
코드도 건드리지 않았는데...사용자 관리는 또 잘 됐다. 그래서 더 억울했다.
먼저 검색창이 잘 작동하는지 콘솔 로그를 찍어봤다.
methods: {
searchDeviceList() {
console.log('actDepartmentList', this.searchParams)
// 검색 기능 설정
this.$store.dispatch('actDeviceList', this.searchParams)
},
여긴 잘 넘어가는 것 같았다.
근데 백엔드 로그 파일에 params엔 아무것도 안넘어갔다.
잘 되다가 갑자기 안되는게 도저히 이유를 모르겠어서 강사님께 sos 쳤다.
강사님은 저기 찍어보는 것 뿐만 아니라 store에도 찍어보도록 알려주셨다.
actions: {
// 리스트 조회
actDeviceList(context, payload) {
// /* 테스트 데이터 세팅 */
// const DeviceList = [
// {
// id: 1,
// name: 'Edge1',
// model: 'E001',
// createdAt: '2021-12-01T00:00:00.000Z'
// },
// {
// id: 2,
// name: 'Edge2',
// model: 'E002',
// createdAt: '2021-12-01T00:00:00.000Z'
// }
// ]
// context.commit('setDeviceList', DeviceList)
console.log(payload)
/* RestAPI 호출 */
api
.get('/serverApi/devices', { params: payload })
.then(response => {
const deviceList = response && response.data && response.data.rows
context.commit('setDeviceList', deviceList)
})
.catch(error => {
// 에러인 경우 처리
console.error('DeviceList.error', error)
context.commit('setDeviceList', [])
})
},
여기도 잘 넘어갔다.
백엔드의 로그 파일도 확인했다.
2022-01-28 18:27:53.183[info] (device.list.params) {}
여전히 검색어가 안넘어가는 것으로 보였다.
그러다 강사님이 여기 보는 걸 알려주셨다.
이렇게 request URL을 통해 현재 검색어가 백엔드에서 설정한 name이 아닌 0으로 넘어가는 걸 확인할 수 있었다.
해결 방법은 통째로 searchParams로 넘겨주는 게 아닌 name으로 지정을 해서 넘겨주면 되는 것이었다.
<template>
<div>
<h1>장비 관리</h1>
<div style="margin-bottom: 5px">
<b-row style="margin-bottom: 5px">
<b-col style="text-align: left">
<b-input-group style="width: 250px">
<b-form-input
v-model="search.name"
placeholder="장비명을 입력하세요"
size="sm"
@keyup.ctrl.enter="searchDeviceList"
></b-form-input>
<b-input-group-append>
<b-button variant="primary" size="sm" @click="searchDeviceList">검색</b-button>
</b-input-group-append>
</b-input-group>
</b-col>
<b-col style="text-align: right">
<b-button variant="success" size="sm" @click="onClickAddNew">신규등록</b-button>
</b-col>
</b-row>
</div>
<div>
<b-table small hover striped :items="deviceList" :fields="fields">
<template #cell(createdAt)="row">
{{ row.item.createdAt.substring(0, 10) }}
</template>
<template #cell(updateBtn)="row">
<b-button size="sm" variant="success" @click="onClickEdit(row.item.id)">수정</b-button>
</template>
<template #cell(deleteBtn)="row">
<b-button size="sm" variant="danger" @click="onClickDelete(row.item.id)">삭제</b-button>
</template>
</b-table>
</div>
<!-- inform 영역 -->
<inform />
</div>
</template>
<script>
import inform from './inform.vue'
export default {
components: {
inform: inform
},
data() {
return {
search: {
name: null
},
fields: [
{ key: 'id', label: 'id' },
{ key: 'name', label: '장비이름' },
{ key: 'deviceModelName', label: '장비모델명' },
{ key: 'manufacturer', label: '모델명' },
{ key: 'location', label: '설치위치' },
{ key: 'edgeSerialNumber', label: '엣지 시리얼 넘버' },
{ key: 'networkInterface', label: '통신인터페이스' },
{ key: 'createdAt', label: '등록일' },
{ key: 'updateBtn', label: '수정' },
{ key: 'deleteBtn', label: '삭제' }
]
}
},
computed: {
deviceList() {
return this.$store.getters.DeviceList
},
insertedResult() {
return this.$store.getters.DeviceInsertedResult
},
updatedResult() {
return this.$store.getters.DeviceUpdatedResult
},
deletedResult() {
return this.$store.getters.DeviceDeletedResult
}
},
watch: {
insertedResult(value) {
// 등록 후 처리
if (value !== null) {
if (value > 0) {
// 등록이 성공한 경우
// 1. 메세지 출력
this.$bvToast.toast('등록 되었습니다.', {
title: 'SUCCESS',
variant: 'success',
solid: true
})
// 2. 리스트 재 검색
this.searchDeviceList()
} else {
// 등록이 실패한 경우
this.$bvToast.toast('등록이 실패하였습니다.', {
title: 'ERROR',
variant: 'danger',
solid: true
})
}
}
},
updatedResult(value) {
// 수정 후 처리
if (value !== null) {
if (value > 0) {
// 수정이 성공한 경우
// 1. 메세지 출력
this.$bvToast.toast('수정 되었습니다.', {
title: 'SUCCESS',
variant: 'success',
solid: true
})
// 2. 리스트 재 검색
this.searchDeviceList()
} else {
// 수정이 실패한 경우
this.$bvToast.toast('수정이 실패하였습니다.', {
title: 'ERROR',
variant: 'danger',
solid: true
})
}
}
},
deletedResult(value) {
// 삭제 후 처리
if (value !== null) {
if (value > 0) {
// 삭제가 성공한 경우
// 1. 메세지 출력
this.$bvToast.toast('삭제 되었습니다.', {
title: 'SUCCESS',
variant: 'success',
solid: true
})
// 2. 리스트 재 검색
this.searchDeviceList()
} else {
// 삭제가 실패한 경우
this.$bvToast.toast('삭제가 실패하였습니다.', {
title: 'ERROR',
variant: 'danger',
solid: true
})
}
}
}
},
created() {
this.searchDeviceList()
},
methods: {
searchDeviceList() {
// console.log('actDepartmentList', this.searchParams)
// 검색 기능 설정
this.$store.dispatch('actDeviceList', this.search)
},
이렇게 하니 잘 검색 됐다.
느낀 점
그동안 개발자 도구에서 콘솔 로그 빼고 나머지는 어떤 때 쓰는 건지 잘 몰랐는데 오늘 좀더 배운 것 같아서 좋았다.
특히 마지막에 에러난 덕분에 Network의 Headers에서 Request URL 보는 건 오래 기억에 남을 것 같다.
'공부 > [TIL] Digital Twin Bootcamp' 카테고리의 다른 글
TIL_220207_DevOps/Git/Github (0) | 2022.02.07 |
---|---|
TIL_220204_HMI (0) | 2022.02.04 |
TIL_220127_토큰 관리 (0) | 2022.01.27 |
TIL_220126_Backend_CRUD (0) | 2022.01.26 |
TIL_220125_Backend_CRUD (0) | 2022.01.25 |