feat(owner-vue): 알림 서비스 구현
- 알림 페이지 추가 - 알림 개수에 맞게 상단바에 표시
This commit is contained in:
19
owner-vue/src/api/notification.js
Normal file
19
owner-vue/src/api/notification.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const url = process.env.VUE_APP_OWNER_SERVICE_BASEURL + "/notification-service";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
requestNotification() {
|
||||||
|
return axios.get(url + "/notifications");
|
||||||
|
},
|
||||||
|
patchNotification(id, isRead) {
|
||||||
|
const body = {
|
||||||
|
read: isRead
|
||||||
|
}
|
||||||
|
|
||||||
|
return axios.patch(url + "/notification/" + id, body)
|
||||||
|
},
|
||||||
|
countsNotification() {
|
||||||
|
return axios.get(url + "/api/notification/counts");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,6 @@ export default {
|
|||||||
},
|
},
|
||||||
saveItem(method, itemData){
|
saveItem(method, itemData){
|
||||||
const _url = process.env.VUE_APP_OWNER_SERVICE_BASEURL+'/store-service/api/owner/item'+(method==='put'?+"/"+itemData.itemId:'')
|
const _url = process.env.VUE_APP_OWNER_SERVICE_BASEURL+'/store-service/api/owner/item'+(method==='put'?+"/"+itemData.itemId:'')
|
||||||
console.log(_url)
|
|
||||||
return axios({
|
return axios({
|
||||||
method:method,
|
method:method,
|
||||||
url: _url,
|
url: _url,
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ const moment = require('moment');
|
|||||||
const ACCESS_TOKEN_NAME = "accessToken";
|
const ACCESS_TOKEN_NAME = "accessToken";
|
||||||
const EXPIRED_TIME_NAME = "expiredTime";
|
const EXPIRED_TIME_NAME = "expiredTime";
|
||||||
|
|
||||||
const tag = "[jwt]";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getToken() {
|
getToken() {
|
||||||
return localStorage.getItem(ACCESS_TOKEN_NAME);
|
return localStorage.getItem(ACCESS_TOKEN_NAME);
|
||||||
@@ -33,7 +31,7 @@ export default {
|
|||||||
|
|
||||||
const difference = moment.duration(expiredMoment.diff(currentMoment)).asSeconds();
|
const difference = moment.duration(expiredMoment.diff(currentMoment)).asSeconds();
|
||||||
|
|
||||||
console.log(tag, "expireMoment = ", expiredMoment, "currentMoment = ", currentMoment, "diff = ", difference);
|
// console.log(tag, "expireMoment = ", expiredMoment, "currentMoment = ", currentMoment, "diff = ", difference);
|
||||||
|
|
||||||
// 만료 30초 전일 경우 만료로 판단
|
// 만료 30초 전일 경우 만료로 판단
|
||||||
return difference <= 30;
|
return difference <= 30;
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ export default {
|
|||||||
addItemOption : function () {
|
addItemOption : function () {
|
||||||
if(!this.data) return;
|
if(!this.data) return;
|
||||||
|
|
||||||
console.log(this.optionType)
|
|
||||||
this.dialog = false
|
this.dialog = false
|
||||||
this.$emit('addItemOption',this.data,this.optionType)
|
this.$emit('addItemOption',this.data,this.optionType)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,7 +166,6 @@ export default {
|
|||||||
this.$emit('save')
|
this.$emit('save')
|
||||||
},
|
},
|
||||||
addItemOption : function (itemOptionValue,optionType){
|
addItemOption : function (itemOptionValue,optionType){
|
||||||
console.log("saveOption",itemOptionValue,optionType)
|
|
||||||
this.$emit("addItemOption",itemOptionValue,optionType)
|
this.$emit("addItemOption",itemOptionValue,optionType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ axios.interceptors.response.use(
|
|||||||
if (error.response.status === 401) {
|
if (error.response.status === 401) {
|
||||||
let code = error.response.data.code;
|
let code = error.response.data.code;
|
||||||
if (code === "EXPIRED") {
|
if (code === "EXPIRED") {
|
||||||
console.log("## expired");
|
|
||||||
try {
|
try {
|
||||||
const accessToken = await auth.requestReissue();
|
const accessToken = await auth.requestReissue();
|
||||||
originalRequest.headers.Authorization = "Bearer " + accessToken;
|
originalRequest.headers.Authorization = "Bearer " + accessToken;
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ const authCheck = async function (to, from, next) {
|
|||||||
};
|
};
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/dashboard',
|
path: '/order',
|
||||||
redirect: 'dashboard',
|
redirect: 'order',
|
||||||
component: DashboardLayout,
|
component: DashboardLayout,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -51,7 +51,13 @@ const routes = [
|
|||||||
name: 'order',
|
name: 'order',
|
||||||
beforeEnter: authCheck,
|
beforeEnter: authCheck,
|
||||||
component: () => import('./../views/Order.vue')
|
component: () => import('./../views/Order.vue')
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
path: '/notification',
|
||||||
|
name: 'notificationr',
|
||||||
|
beforeEnter: authCheck,
|
||||||
|
component: () => import('./../views/NotificationView.vue')
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-app id="inspire">
|
<v-app id="inspire">
|
||||||
<side-bar v-bind:drawer="drawer"></side-bar>
|
<side-bar v-bind:drawer="drawer"></side-bar>
|
||||||
<top-bar v-on:drawEvent="drawer = !drawer"></top-bar>
|
<top-bar v-on:drawEvent="drawer = !drawer"
|
||||||
|
:notificationCounts="notificationCounts"
|
||||||
|
></top-bar>
|
||||||
<v-main style="background: #f5f5f540">
|
<v-main style="background: #f5f5f540">
|
||||||
<v-container class="py-8, px-6" fluid>
|
<v-container class="py-8, px-6" fluid>
|
||||||
<router-view></router-view>
|
<router-view
|
||||||
|
v-on:plusCount="notificationCounts++"
|
||||||
|
v-on:minusCount="notificationCounts--"
|
||||||
|
></router-view>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-main>
|
</v-main>
|
||||||
</v-app>
|
</v-app>
|
||||||
@@ -13,6 +18,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import Sidebar from './Sidebar.vue'
|
import Sidebar from './Sidebar.vue'
|
||||||
import Topbar from "./Topbar.vue";
|
import Topbar from "./Topbar.vue";
|
||||||
|
import notificationApi from "@/api/notification";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "DashboardLayout",
|
name: "DashboardLayout",
|
||||||
@@ -20,9 +26,19 @@ export default {
|
|||||||
'side-bar': Sidebar,
|
'side-bar': Sidebar,
|
||||||
'top-bar': Topbar
|
'top-bar': Topbar
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.searchNotificationCounts();
|
||||||
|
},
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
drawer: null
|
drawer: null,
|
||||||
|
notificationCounts: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
searchNotificationCounts: async function() {
|
||||||
|
const response = await notificationApi.countsNotification();
|
||||||
|
this.notificationCounts = response.data.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export default {
|
|||||||
links: [
|
links: [
|
||||||
{name: "지난 주문", url: "/prev-order", icon: "mdi-home-outline"},
|
{name: "지난 주문", url: "/prev-order", icon: "mdi-home-outline"},
|
||||||
{name: "카테고리", url: "/category", icon: "mdi-magnify"},
|
{name: "카테고리", url: "/category", icon: "mdi-magnify"},
|
||||||
{name: "주문", url: "/order", icon: "mdi-cards-heart-outline"},
|
{name: "주문", url: "/order", icon: "mdi-cards-heart"},
|
||||||
{name: "메뉴 관리", url: "/menu", icon: "mdi-cards-heart-outline"},
|
{name: "메뉴 관리", url: "/menu", icon: "mdi-cards-heart-outline"},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,39 +11,18 @@
|
|||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
>
|
>
|
||||||
<v-badge content="3" color="red" offset-y="10" offset-x="10">
|
<v-btn @click="goNotification"
|
||||||
<v-icon>mdi-bell</v-icon>
|
elevation="0"
|
||||||
</v-badge>
|
color="white"
|
||||||
|
>
|
||||||
|
<v-badge :content="notificationCounts"
|
||||||
|
:value="notificationCounts"
|
||||||
|
color="red" offset-y="10" offset-x="10">
|
||||||
|
<v-icon>mdi-bell</v-icon>
|
||||||
|
</v-badge>
|
||||||
|
</v-btn>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<v-list three-line width="250">
|
|
||||||
<template v-for="(item, index) in items">
|
|
||||||
<v-subheader
|
|
||||||
v-if="item.header"
|
|
||||||
:key="item.header"
|
|
||||||
v-text="item.header"
|
|
||||||
></v-subheader>
|
|
||||||
|
|
||||||
<v-divider
|
|
||||||
v-else-if="item.divider"
|
|
||||||
:key="index"
|
|
||||||
:inset="item.inset"
|
|
||||||
></v-divider>
|
|
||||||
|
|
||||||
<v-list-item v-else :key="item.title">
|
|
||||||
<v-list-item-avatar>
|
|
||||||
<v-img :src="item.avatar"></v-img>
|
|
||||||
</v-list-item-avatar>
|
|
||||||
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title v-html="item.title"></v-list-item-title>
|
|
||||||
<v-list-item-subtitle
|
|
||||||
v-html="item.subtitle"
|
|
||||||
></v-list-item-subtitle>
|
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
</template>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
</v-menu>
|
||||||
<v-menu offset-y>
|
<v-menu offset-y>
|
||||||
<template v-slot:activator="{ attrs, on }">
|
<template v-slot:activator="{ attrs, on }">
|
||||||
@@ -89,46 +68,10 @@ import authApi from "../../api/auth";
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Topbar",
|
name: "Topbar",
|
||||||
|
props: ["notificationCounts"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
userName: '',
|
userName: '',
|
||||||
menus: [
|
|
||||||
{ title: "Logout", icon: "mdi-logout", methods: "logout" },
|
|
||||||
],
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
avatar: "https://cdn.vuetifyjs.com/images/lists/1.jpg",
|
|
||||||
title: "Brunch this weekend?",
|
|
||||||
subtitle: `<span class="text--primary">Ali Connors</span> — I'll be in your neighborhood doing errands this weekend. Do you want to hang out?`,
|
|
||||||
},
|
|
||||||
{ divider: true, inset: true },
|
|
||||||
{
|
|
||||||
avatar: "https://cdn.vuetifyjs.com/images/lists/2.jpg",
|
|
||||||
title: 'Summer BBQ <span class="grey--text text--lighten-1">4</span>',
|
|
||||||
subtitle: `<span class="text--primary">to Alex, Scott, Jennifer</span> — Wish I could come, but I'm out of town this weekend.`,
|
|
||||||
},
|
|
||||||
{ divider: true, inset: true },
|
|
||||||
{
|
|
||||||
avatar: "https://cdn.vuetifyjs.com/images/lists/3.jpg",
|
|
||||||
title: "Oui oui",
|
|
||||||
subtitle:
|
|
||||||
'<span class="text--primary">Sandra Adams</span> — Do you have Paris recommendations? Have you ever been?',
|
|
||||||
},
|
|
||||||
{ divider: true, inset: true },
|
|
||||||
{
|
|
||||||
avatar: "https://cdn.vuetifyjs.com/images/lists/4.jpg",
|
|
||||||
title: "Birthday gift",
|
|
||||||
subtitle:
|
|
||||||
'<span class="text--primary">Trevor Hansen</span> — Have any ideas about what we should get Heidi for her birthday?',
|
|
||||||
},
|
|
||||||
{ divider: true, inset: true },
|
|
||||||
{
|
|
||||||
avatar: "https://cdn.vuetifyjs.com/images/lists/5.jpg",
|
|
||||||
title: "Recipe to try",
|
|
||||||
subtitle:
|
|
||||||
'<span class="text--primary">Britta Holt</span> — We should eat this: Grate, Squash, Corn, and tomatillo Tacos.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -141,9 +84,13 @@ export default {
|
|||||||
const response = await userApi.requestUserInfo();
|
const response = await userApi.requestUserInfo();
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
},
|
},
|
||||||
logout: async function () {
|
logout: async function() {
|
||||||
await authApi.logout();
|
await authApi.logout();
|
||||||
},
|
},
|
||||||
|
goNotification: function() {
|
||||||
|
this.$router.push("/notification");
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -198,10 +198,7 @@ export default {
|
|||||||
method='put'
|
method='put'
|
||||||
else
|
else
|
||||||
method='post'
|
method='post'
|
||||||
store.saveItem(method,itemData)
|
store.saveItem(method,itemData);
|
||||||
.then(response => console.log(response))
|
|
||||||
.catch(reason => console.log(reason))
|
|
||||||
|
|
||||||
},
|
},
|
||||||
addItemOption:function (itemOptionValue,type){
|
addItemOption:function (itemOptionValue,type){
|
||||||
var item = {
|
var item = {
|
||||||
|
|||||||
88
owner-vue/src/views/NotificationView.vue
Normal file
88
owner-vue/src/views/NotificationView.vue
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dashboard">
|
||||||
|
<v-subheader class="py-0 d-flex justify-space-between rounded-lg">
|
||||||
|
<h3>알림</h3>
|
||||||
|
</v-subheader>
|
||||||
|
<br>
|
||||||
|
<template
|
||||||
|
v-for="(item, index) in notifications">
|
||||||
|
<v-list-item three-line :key="item.id">
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>{{item.title}}</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>
|
||||||
|
{{item.message}}
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
<v-list-item-subtitle>
|
||||||
|
{{item.time}}
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
|
||||||
|
<v-list-item-action>
|
||||||
|
<v-checkbox
|
||||||
|
disabled
|
||||||
|
v-if="item.prevRead"
|
||||||
|
v-model="item.read"
|
||||||
|
hide-details></v-checkbox>
|
||||||
|
<v-checkbox
|
||||||
|
v-else
|
||||||
|
v-model="item.read"
|
||||||
|
@click="clickRead(item.id, item.read)"
|
||||||
|
hide-details></v-checkbox>
|
||||||
|
</v-list-item-action>
|
||||||
|
</v-list-item>
|
||||||
|
<v-divider
|
||||||
|
v-if="index < notifications.length - 1"
|
||||||
|
:key="'divider-' + index"
|
||||||
|
></v-divider>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import notificationApi from "../api/notification";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "NotificationView",
|
||||||
|
mounted() {
|
||||||
|
this.search();
|
||||||
|
},
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
notifications: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
search: async function() {
|
||||||
|
const response = await notificationApi.requestNotification();
|
||||||
|
this.render(response.data);
|
||||||
|
},
|
||||||
|
render: function(json) {
|
||||||
|
const notifications = json.data.notifications;
|
||||||
|
notifications.forEach(notification => {
|
||||||
|
this.notifications.push({
|
||||||
|
id: notification.id,
|
||||||
|
message: notification.message,
|
||||||
|
title: notification.title,
|
||||||
|
prevRead: notification.read,
|
||||||
|
read: notification.read,
|
||||||
|
time: notification.time
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clickRead: async function(id, isRead) {
|
||||||
|
await notificationApi.patchNotification(id, isRead);
|
||||||
|
if (isRead) {
|
||||||
|
alert("해당 알림은 읽음 처리되었습니다.");
|
||||||
|
this.$emit("minusCount");
|
||||||
|
} else {
|
||||||
|
alert("해당 알림은 읽음 해제 처리되었습니다.");
|
||||||
|
this.$emit("plusCount");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -71,7 +71,6 @@ export default {
|
|||||||
this.renderCard(response.data)
|
this.renderCard(response.data)
|
||||||
},
|
},
|
||||||
renderCard: function (json) {
|
renderCard: function (json) {
|
||||||
console.log(json);
|
|
||||||
const orders = json.data.orders;
|
const orders = json.data.orders;
|
||||||
const size = orders.length;
|
const size = orders.length;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user