Merge pull request #20 from Development-team-1/owner-vue

점주용 페이지 vue.js로 변경
This commit is contained in:
백창훈
2022-02-23 18:41:03 +09:00
committed by GitHub
35 changed files with 1527 additions and 10 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/owner-vue/node_modules/

8
config-service/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# 디폴트 무시된 파일
/shelf/
/workspace.xml
# 에디터 기반 HTTP 클라이언트 요청
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -14,6 +14,16 @@ spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "http://localhost:8080"
allowedMethods:
- POST
- GET
- PUT
- OPTIONS
- DELETE
routes:
- id: owner-frontend-service
uri: lb://OWNER-FRONTEND-SERVICE
@@ -67,4 +77,4 @@ token:
refresh-expired-time: 604800000
secret: my-secret
refresh-token-name: refresh-token
access-token-name: access-token
access-token-name: access-token

8
owner-vue/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# 디폴트 무시된 파일
/shelf/
/workspace.xml
# 에디터 기반 HTTP 클라이언트 요청
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

54
owner-vue/package.json Normal file
View File

@@ -0,0 +1,54 @@
{
"name": "owner-admin",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@mdi/js": "^6.5.95",
"axios": "^0.26.0",
"core-js": "^3.6.5",
"moment": "^2.29.1",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuedraggable": "^2.24.3",
"vuetify": "^2.4.0"
},
"devDependencies": {
"@mdi/font": "^6.1.95",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"sass": "~1.32.0",
"sass-loader": "^10.0.0",
"vue-cli-plugin-vuetify": "~2.4.2",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.7.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

28
owner-vue/src/App.vue Normal file
View File

@@ -0,0 +1,28 @@
<template>
<v-app id="inspire">
<Sidebar :drawer="drawer" />
<Topbar @drawerEvent="drawer = !drawer" />
<v-main style="background: #f5f5f540">
<v-container class="py-8 px-6" fluid>
<router-view></router-view>
</v-container>
</v-main>
</v-app>
</template>
<script>
import Sidebar from "./components/Sidebar";
import Topbar from "./components/Topbar";
export default {
name: "App",
components: { Topbar, Sidebar},
data: () => ({
cards: ["Today", "Yesterday"],
drawer: null,
}),
mounted() {
}
};
</script>

View File

@@ -0,0 +1,23 @@
import axios from "axios";
export default {
requestPrevOrder(startDate, endDate, page) {
const options = {
params: {
startDate: startDate,
endDate: endDate,
page: page
}
}
return axios.get("http://localhost:8001/order-service/prevOrder", options);
},
requestOrder(orderDate, lastOrderId) {
const options = {
params: {
orderDate: orderDate,
lastOrderId: lastOrderId
}
}
return axios.get("http://localhost:8001/order-service/orderMain", options);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>

After

Width:  |  Height:  |  Size: 539 B

View File

@@ -0,0 +1,55 @@
<template>
<v-menu
v-model="isPop"
:close-on-content-click="false"
:nudge-right="40"
transition="scale-transition"
offset-y
min-width="auto"
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-model="value"
:label="label"
prepend-icon="mdi-calendar"
readonly
v-bind="attrs"
v-on="on"
></v-text-field>
</template>
<v-date-picker
v-model="value"
@input="input">
</v-date-picker>
</v-menu>
</template>
<script>
const moment = require('moment');
export default {
name: "DatePicker",
props: {
'label': String
},
data: function() {
return {
isPop: false,
value: moment().format('YYYY-MM-DD')
}
},
created: function() {
this.$emit('inputDate', this.value);
},
methods: {
input: function() {
this.isPop = false;
this.$emit('inputDate', this.value);
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,44 @@
<template>
<v-card>
<v-toolbar elevation="1" dense>
<v-toolbar-title>{{ userName }}</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn outlined color="grey grey lighten-1" small>상세보기</v-btn>
</v-toolbar>
<v-card-title>{{ itemNames.join(", ") }}</v-card-title>
<v-card-subtitle></v-card-subtitle>
<v-card-text>{{ orderTime }}</v-card-text>
<v-card-actions>
<v-btn v-if="orderStatus === 'PENDING'"
block depressed color="primary">
주문 대기
</v-btn>
<v-btn v-else-if="orderStatus === 'PLACED'"
block depressed color="primary">
주문 수령
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
name: "OrderCard",
data: function() {
return {
};
},
props: {
orderId: Number,
userName: Number,
itemNames: [],
orderTime: String,
orderStatus: String
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,52 @@
<template>
<!-- <v-navigation-drawer v-model="drawer" app> -->
<v-navigation-drawer app>
<v-img
height="140"
class="pa-4"
src="https://preview.pixlr.com/images/800wm/1439/2/1439104804.jpg"
>
<div class="text-center">
<v-avatar class="mb-4" color="grey darken-1" size="64">
<v-img
aspect-ratio="30"
src="https://yt3.ggpht.com/esazPAO03T0f0vKdByJvkDy6MSwjyG5f-c_2S2CJapszQ3KPQyZarpoqvgv0Us0atUbILytj=s88-c-k-c0x00ffffff-no-rj"
/>
</v-avatar>
<h2 class="white--text">Web Burden</h2>
</div>
</v-img>
<v-divider></v-divider>
<v-list>
<v-list-item v-for="[icon, text] in links" :key="icon" link>
<v-list-item-icon>
<v-icon>{{ icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ text }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<script>
export default {
name: "Sidebar",
props: ["drawer"],
data() {
return {
links: [
["mdi-microsoft-windows", "Dashboard"],
["mdi-account", "Profile"],
["mdi-clipboard-list-outline", "Products"],
["mdi-card-account-details-outline", "Orders"],
["mdi-cog", "System Setting"],
],
};
},
};
</script>
<style scoped></style>

View File

@@ -0,0 +1,149 @@
<template>
<v-app-bar app elevate-on-scroll elevation="3" color="white">
<v-app-bar-nav-icon @click="$emit('drawerEvent')"></v-app-bar-nav-icon>
<v-spacer />
<v-col lg="6" cols="12">
<v-form>
<v-text-field
class="p-0 m-0 mt-6"
full-width
dense
append-icon="mdi-magnify"
outlined
rounded
placeholder="Search"
/>
</v-form>
</v-col>
<v-spacer />
<v-menu offset-y>
<template v-slot:activator="{ attrs, on }">
<span
class="mx-5 mr-10"
style="cursor: pointer"
v-bind="attrs"
v-on="on"
>
<v-badge content="3" color="red" offset-y="10" offset-x="10">
<v-icon>mdi-bell</v-icon>
</v-badge>
</span>
</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 offset-y>
<template v-slot:activator="{ attrs, on }">
<span style="cursor: pointer" v-bind="attrs" v-on="on">
<v-chip link>
<v-badge dot bottom color="green" offset-y="10" offset-x="10">
<v-avatar size="40">
<v-img src="https://randomuser.me/api/portraits/women/81.jpg" />
</v-avatar>
</v-badge>
<span class="ml-3">Jane Smith</span>
</v-chip>
</span>
</template>
<v-list width="250" class="py-0">
<v-list-item two-line>
<v-list-item-avatar>
<img src="https://randomuser.me/api/portraits/women/81.jpg" />
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>Jane Smith</v-list-item-title>
<v-list-item-subtitle>Logged In</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-divider />
<v-list-item link v-for="(menu, i) in menus" :key="i">
<v-list-item-icon>
<v-icon>{{ menu.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>
{{ menu.title }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-app-bar>
</template>
<script>
export default {
name: "Topbar",
data() {
return {
menus: [
{ title: "Profile", icon: "mdi-account" },
{ title: "Change Password", icon: "mdi-key" },
{ title: "Setting", icon: "mdi-cog" },
{ title: "Logout", icon: "mdi-logout" },
],
items: [
{
avatar: "https://cdn.vuetifyjs.com/images/lists/1.jpg",
title: "Brunch this weekend?",
subtitle: `<span class="text--primary">Ali Connors</span> &mdash; 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> &mdash; 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> &mdash; 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> &mdash; 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> &mdash; We should eat this: Grate, Squash, Corn, and tomatillo Tacos.',
},
],
};
},
};
</script>
<style scoped></style>

16
owner-vue/src/main.js Normal file
View File

@@ -0,0 +1,16 @@
import 'font-awesome/css/font-awesome.min.css' // Ensure you are using css-loader
import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify'
import router from './router'
import axios from "axios";
Vue.config.productionTip = false
new Vue({
vuetify,
router,
render: h => h(App)
}).$mount('#app')
Vue.component('axios',axios)

View File

@@ -0,0 +1,11 @@
import '@mdi/font/css/materialdesignicons.css'
import Vue from 'vue';
import Vuetify from 'vuetify/lib/framework';
Vue.use(Vuetify);
export default new Vuetify({
icons: {
iconfont: 'mdi', // default - only for display purposes
},
});

View File

@@ -0,0 +1,40 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'dashboard',
component: () => import('./../views/Dashboard')
},
{
path: '/category',
name: 'category',
component: () => import('./../views/Category')
},
{
path: '/menu',
name: 'menu',
component: () => import('./../views/Menu')
},
{
path: '/prev-order',
name: 'prev-order',
component: () => import('./../views/PrevOrder')
},
{
path: '/order',
name: 'order',
component: () => import('./../views/Order.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router

View File

@@ -0,0 +1,5 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@@ -0,0 +1,150 @@
<template>
<div>
<v-subheader class="py-0 d-flex justify-space-between rounded-lg">
<h3>Category List</h3>
<div style="text-align: right">
<span class="group pa-2">
<v-btn
elevation="2"
icon
color="success"
@click="clickAdd"
><v-icon>{{ icons.mdiPlus }}</v-icon></v-btn>
</span>
<v-btn
elevation="2"
icon
color="primary"
@click="clickSave"
><v-icon>{{ icons.mdiContentSave }}</v-icon></v-btn>
</div>
</v-subheader>
<v-expansion-panels style="display: block">
<draggable v-model="categoryList" id="categoryEl" >
<v-expansion-panel
v-for="item in categoryList" :key="item.categoryId" class="category-item" :data-id="item.categoryId"
>
<v-expansion-panel-header >
<span contenteditable="true" >{{ item.name }}</span>
<template v-slot:actions>
<v-btn
elevation="2"
icon
@click.stop="clickDelete($event)"
><v-icon>{{ icons.mdiDelete }}</v-icon></v-btn>
</template>
</v-expansion-panel-header>
<v-expansion-panel-content>
item-list
</v-expansion-panel-content>
</v-expansion-panel>
</draggable>
</v-expansion-panels>
</div>
</template>
<script>
import draggable from 'vuedraggable'
import {
mdiContentSave, mdiDelete,
mdiPlus,
} from '@mdi/js'
import axios from "axios";
export default {
name: "Category",
components:{
draggable
},
data() {
return {
icons: {
mdiContentSave,
mdiPlus,
mdiDelete,
},
categoryList: [
],
deletedList: []
}
},
methods:{
clickAdd:function (){
var count = this.categoryList.length+1
var item = {
name: 'new Category',
order:count
}
this.categoryList.push(item)
},
clickDelete:function (event){
var category = event.currentTarget.parentNode.parentNode.parentNode;
let delCategory = {
categoryId : category.dataset.id,
name : category.children[0].children[0].innerHTML,
}
this.deletedList.push(delCategory)
category.remove();
},
clickSave:function (){
var vm =this;
let data = {
storeId : "1",
categoryList: [],
deletedList: vm.deletedList
}
var categoryEl = document.querySelector("#categoryEl");
categoryEl.childNodes.forEach(function(item ,index){
let category = {
categoryId : item.dataset.id,
name : item.children[0].children[0].innerHTML,
order : index+1
}
data.categoryList.push(category)
})
axios({
method:'put',
url:'/store-service/category',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
data: data,
responseType:'json'
})
.then(function (response) {
console.log(response)
vm.deletedList=[]
vm.getCategoryList()
});
},
getCategoryList:function(){
var vm =this;
axios({
method:'get',
url:'/store-service/category',
responseType:'json'
})
.then(function (response) {
console.log(response.data.data)
vm.categoryList = response.data.data;
});
}
},
mounted() {
this.getCategoryList();
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,212 @@
<template>
<div class="dashboard">
<v-subheader class="py-0 d-flex justify-space-between rounded-lg">
<h3>Dashboard</h3>
<v-btn color="success">
View Orders
</v-btn>
</v-subheader>
<br>
<v-row>
<v-col lg="7" cols="12">
<v-alert dense text type="success">
Login Successfully! Welcome to <strong>Web Burden</strong>
</v-alert>
<v-row>
<v-col lg="6" cols="12" v-for="(item,index) in activityLog" :key="index">
<v-card elevation="2" class="rounded-lg">
<v-card-text class="d-flex justify-space-between align-center">
<div>
<strong>{{ item.title }}</strong> <br>
<span>Last 3 weeks</span>
</div>
<v-avatar size="60" :color="item.color" style="border: 3px solid #444">
<span style="color: white">{{item.amount}} +</span>
</v-avatar>
</v-card-text>
<v-card-actions class="d-flex justify-space-between">
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-col>
<v-col cols="12" lg="5">
<v-card>
<v-card-title>Activities</v-card-title>
<v-card-text class="py-0">
<v-timeline align-top dense>
<v-timeline-item color="indigo" small>
<strong>5 Minuts ago</strong>
<div class="text-caption">
You have new order please check this out
</div>
</v-timeline-item>
<v-timeline-item color="green" small>
<strong>35 Minuts ago</strong>
<div class="text-caption mb-2">
A Product has delivered!
</div>
</v-timeline-item>
<v-timeline-item color="indigo" small>
<strong>44 Minuts ago</strong>
<div class="text-caption">
You have new order please check this out
</div>
</v-timeline-item>
</v-timeline>
</v-card-text>
</v-card>
</v-col>
<v-col>
<v-card>
<v-data-table
caption="Recent Order list"
:headers="headers"
:items="desserts"
:items-per-page="5"
class="elevation-1"
>
<template v-slot:item.action="">
<v-btn color="success" outlined small shaped >View</v-btn>
</template>
</v-data-table>
</v-card>
</v-col>
</v-row>
</div>
</template>
<script>
export default {
name: "Dashboard",
data() {
return {
activityLog: [
{title: 'Total Products', amount: 50, icon: 'mdi-account', color: 'cyan lighten-3'},
{title: 'Total Customer', amount: 3433, icon: 'mdi-account-group-outline', color: 'green darken-2'},
{title: 'Total Sale', amount: 3433, icon: 'mdi-account-group-outline', color: 'blue-grey darken-1'},
{
title: 'Pending Orders',
amount: 3433,
icon: 'mdi-account-group-outline',
color: 'deep-orange darken-1'
},
],
headers: [
{
text: 'Dessert (100g serving)',
align: 'start',
sortable: false,
value: 'name',
},
{text: 'Calories', value: 'calories'},
{text: 'Fat (g)', value: 'fat'},
{text: 'Carbs (g)', value: 'carbs'},
{text: 'Protein (g)', value: 'protein'},
{text: 'Iron (%)', value: 'iron'},
{text: 'Actions', value: 'action'},
],
desserts: [
{
name: 'Frozen Yogurt',
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
iron: '1%',
},
{
name: 'Ice cream sandwich',
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
iron: '1%',
},
{
name: 'Eclair',
calories: 262,
fat: 16.0,
carbs: 23,
protein: 6.0,
iron: '7%',
},
{
name: 'Cupcake',
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
iron: '8%',
},
{
name: 'Gingerbread',
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
iron: '16%',
},
{
name: 'Jelly bean',
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
iron: '0%',
},
{
name: 'Lollipop',
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0,
iron: '2%',
},
{
name: 'Honeycomb',
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
iron: '45%',
},
{
name: 'Donut',
calories: 452,
fat: 25.0,
carbs: 51,
protein: 4.9,
iron: '22%',
},
{
name: 'KitKat',
calories: 518,
fat: 26.0,
carbs: 65,
protein: 7,
iron: '6%',
},
],
}
},
methods: {
onButtonClick(item) {
console.log('click on ' + item.no)
}
}
}
</script>
<style scoped>
.overlap-icon {
position: absolute;
top: -33px;
text-align: center;
padding-top: 12px;
}
</style>

View File

@@ -0,0 +1,15 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
</div>
</template>
<script>
// @ is an alias to /src
export default {
name: 'Home',
components: {
}
}
</script>

View File

@@ -0,0 +1,122 @@
<template>
<div>
<v-subheader class="py-0 d-flex justify-space-between rounded-lg">
<h3>메뉴 관리</h3>
</v-subheader>
<v-form @submit.prevent>
<v-container>
<v-row>
<v-col
cols="12"
md="4"
>
<v-text-field
v-model="word"
label="검색"
required
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-form>
<v-col>
<v-data-table
:headers="headers"
:items="menus"
:items-per-page="itemsPerPage"
hide-default-footer
class="elevation-1"
>
</v-data-table>
<div class="text-center pt-2">
<v-pagination
v-model="page"
:length="pageCount"
:total-visible="totalVisible"
@click="getMenu"
></v-pagination>
</div>
</v-col>
</div>
</template>
<script>
import {
mdiContentSave, mdiDelete,
mdiPlus,
} from '@mdi/js'
import axios from "axios";
export default {
name: "Category",
components:{
},
data() {
return {
icons: {
mdiContentSave,
mdiPlus,
mdiDelete,
},
page: 1,
pageCount: 1,
itemsPerPage: 10,
totalVisible: 10,
headers: [
{
text: '메뉴번호',
align: 'start',
sortable: false,
value: 'id',
},
{ text: '이름', value: 'name' },
{ text: '카테고리', value: 'categoryName' },
{ text: '가격', value: 'price' },
],
word:"",
menus: [],
}
},
methods:{
getMenu(){
var vm = this;
const searchParam= {
word: vm.word,
page: vm.page-1
}
axios({
method:'get',
url:'/store-service/item',
params : searchParam,
responseType:'json'
})
.then(function (response) {
const page = response.data.data.page;
vm.menus = response.data.data.itemList;
vm.page = page.startPage+1;
vm.pageCount = page.totalPage;
});
}
},
watch:{
page:function () {
this.getMenu();
},
word:function () {
if(this.page == 1)
this.getMenu()
else
this.page =1;
},
},
mounted() {
this.getMenu()
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,119 @@
<template>
<div class="dashboard">
<v-subheader class="py-0 d-flex justify-space-between rounded-lg">
<h3>주문</h3>
<v-btn color="success" @click="search">
검색
</v-btn>
</v-subheader>
<br>
<v-row>
<date-picker :label=" '주문일' " @inputDate="inputDate"></date-picker>
</v-row>
<v-row>
<v-col v-for="card in cards" cols="4" :key="card.orderId">
<order-card
:order-id="card.orderId"
:userName="card.userName"
:itemNames="card.itemNames"
:orderTime="card.orderTime"
:orderStatus="card.orderStatus"
>
</order-card>
</v-col>
</v-row>
<br><br><br>
<v-row justify="center" v-if="showButton">
<v-btn rounded outlined color="primary"
@click="more">더보기</v-btn>
</v-row>
</div>
</template>
<script>
import OrderApi from '../api/order.js';
import OrderCard from '../components/OrderCard.vue'
import DatePicker from "@/components/DatePicker";
export default {
name: "Order",
components: {
OrderCard,
DatePicker
},
mounted() {
this.search();
},
data: () => {
return {
date: '',
cards: [],
lastOrderId: null,
showButton: false
}
},
methods: {
search: function() {
this.cards = [];
this.lastOrderId = null;
OrderApi.requestOrder(this.date, this.lastOrderId)
.then( (response) => {
this.renderCard(response.data);
})
.catch( (error) => {
console.log(error);
})
},
renderCard: function (json) {
const orders = json.data;
const size = orders.length;
if (size === 0) {
alert("검색 데이터가 없습니다.");
this.showButton = false;
} else {
this.showButton = true;
}
orders.forEach( (order, index) => {
if (index === (size - 1)) {
this.lastOrderId = order.orderId;
}
let orderItemNames = []
order.orderItemResponses.forEach( (orderItem) => {
orderItemNames.push(orderItem.itemId);
})
this.cards.push({
orderId: order.orderId,
userName: order.orderId,
itemNames: orderItemNames,
orderTime: order.orderTime,
orderStatus: order.orderStatus
})
});
},
inputDate: function(value) {
this.date = value;
},
more: function() {
OrderApi.requestOrder(this.date, this.lastOrderId)
.then( (response) => {
this.renderCard(response.data);
})
.catch( (error) => {
console.log(error);
})
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,163 @@
<template>
<div class="dashboard">
<v-subheader class="py-0 d-flex justify-space-between rounded-lg">
<h3>지난 주문</h3>
<v-btn color="success" v-on:click="search">
검색
</v-btn>
</v-subheader>
<br>
<v-row>
<date-picker :label=" '시작일' " @inputDate="inputStartDate"></date-picker>
<date-picker :label=" '종료일' " @inputDate="inputEndDate"></date-picker>
</v-row>
<v-row>
<div class="subtitle-1">{{startDate}} ~ {{endDate}} 내역</div>
</v-row>
<br>
<v-data-table
:headers="headers"
:items="orders"
:items-per-page="itemsPerPage"
hide-default-footer
class="elevation-1"
>
</v-data-table>
<div class="text-center pt-2">
<v-pagination
v-model="page"
:length="pageCount"
:total-visible="totalVisible"
></v-pagination>
</div>
</div>
</template>
<script>
import orderApi from '../api/order.js';
import DatePicker from "@/components/DatePicker";
const moment = require('moment');
export default {
name: "PrevOrder",
components: {
'date-picker': DatePicker
},
mounted: function() {
orderApi.requestPrevOrder(this.startDate, this.endDate, this.page - 1)
.then( (response) => {
this.renderList(response.data);
})
.catch( (error) => {
console.log(error);
});
},
watch: {
"page": function (newPage) {
orderApi.requestPrevOrder(this.startDate, this.endDate, newPage - 1)
.then( (response) => {
this.renderList(response.data);
})
.catch( (error) => {
console.log(error);
console.log(error.response.data);
});
}
},
data: function() {
return {
// date
startDate: '',
endDate: '',
// pagination
page: 1,
pageCount: 1,
itemsPerPage: 10,
totalVisible: 10,
// data table
headers: [
{
text: '주문번호',
align: 'start',
sortable: false,
value: 'orderId',
},
{ text: '주문상태', value: 'orderStatus' },
{ text: '주문시간', value: 'orderTime' },
{ text: '주문상품', value: 'orderItemNames' },
{ text: '결제금액', value: 'orderPrice' },
{ text: '닉네임', value: 'userName' },
],
orders: [],
}
},
methods: {
search: function() {
if(!this.checkDate()) return;
orderApi.requestPrevOrder(this.startDate, this.endDate, this.page - 1)
.then( (response) => {
this.renderList(response.data);
})
.catch( (error) => {
console.log(error);
});
},
renderList: function(json) {
const orders = json.data.orders;
this.orders = [];
orders.forEach(order => {
let orderItemNames = [];
order.orderItems.forEach(orderItem => {
orderItemNames.push(orderItem.orderItemId);
})
this.orders.push({
orderId: order.orderId,
orderStatus: order.orderStatus,
orderTime: order.orderTime,
orderItemNames: orderItemNames.join(", "),
orderPrice: order.orderPrice,
userName: order.userName
});
})
// pagination setting
const page = json.data.page;
this.page = page.startPage + 1;
this.pageCount = page.totalPage;
},
checkDate: function() {
if (!this.startDate || !this.endDate) {
alert("시작일과 종료일을 입력해주세요.");
return false;
}
if (moment(this.startDate).isAfter(this.endDate)) {
alert("시작일은 종료일보다 클 수 없습니다.");
return false;
}
return true;
},
inputStartDate: function(value) {
this.startDate = value;
},
inputEndDate: function(value) {
this.endDate = value;
}
}
}
</script>
<style scoped>
</style>

17
owner-vue/vue.config.js Normal file
View File

@@ -0,0 +1,17 @@
module.exports = {
transpileDependencies: [
'vuetify'
],
devServer:{
proxy:{
'store-service/' :{
target: 'http://localhost:8001',
ws:true,
},
'order-service/' :{
target: 'http://localhost:8001',
ws:true,
}
}
}
}

View File

@@ -1,11 +1,17 @@
package com.justpickup.storeservice.domain.item.dto;
import com.justpickup.storeservice.domain.category.dto.CategoryDto;
import com.justpickup.storeservice.domain.item.entity.Item;
import com.justpickup.storeservice.global.entity.Yn;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ItemDto {
private Long id;
@@ -16,15 +22,15 @@ public class ItemDto {
private Long price;
private CategoryDto categoryDto;
/*
private PhotoDto photoDto;
private CategoryDto categoryDto;
private StoreDto storeDto;
private List<ItemOptionDto> itemOptionDtoList;
*/
// == 생성 메소드 == //
@Builder
public ItemDto(Long id, String name, Yn salesYn, Long price) {
this.id = id;
this.name = name;
@@ -41,6 +47,16 @@ public class ItemDto {
.build();
}
public static ItemDto createWithCategoryItemDto(Item item) {
return ItemDto.builder()
.id(item.getId())
.name(item.getName())
.categoryDto(new CategoryDto(item.getCategory()))
.price(item.getPrice())
.salesYn(item.getSalesYn())
.build();
}
// TODO: 2022/02/03 queryDsl 쿼리 생성 시 구현 필요
// public static ItemDto createFullItemDto(Item item) {
// return null

View File

@@ -0,0 +1,20 @@
package com.justpickup.storeservice.domain.item.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ItemSearch {
@NotNull
private String type;
private String word;
}

View File

@@ -1,7 +1,12 @@
package com.justpickup.storeservice.domain.item.repository;
import com.justpickup.storeservice.domain.item.entity.Item;
import com.justpickup.storeservice.domain.store.entity.Store;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ItemRepository extends JpaRepository<Item, Long> {
List<Item> findByStore(Store store);
}

View File

@@ -0,0 +1,53 @@
package com.justpickup.storeservice.domain.item.repository;
import com.justpickup.storeservice.domain.category.entity.QCategory;
import com.justpickup.storeservice.domain.item.entity.Item;
import com.justpickup.storeservice.domain.item.entity.QItem;
import com.justpickup.storeservice.domain.store.entity.QStore;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class ItemRepositoryCustom {
private final JPAQueryFactory queryFactory;
public Page<Item> findItem(Long storeId,String word, Pageable pageable){
//count 가져오기
Long count = queryFactory.select(QItem.item.count())
.from(QItem.item)
.join(QItem.item.category)
.leftJoin(QItem.item.store)
.on(QItem.item.store.id.eq(storeId))
.where(
QItem.item.name.contains(word)
.or(QItem.item.category.name.contains(word))
)
.limit(pageable.getPageSize())
.offset(pageable.getOffset())
.fetchOne();
//List 가져오기
List<Item> itemList = queryFactory.selectFrom(QItem.item)
.join(QItem.item.category).fetchJoin()
.leftJoin(QItem.item.store)
.on(QItem.item.store.id.eq(storeId))
.where(
QItem.item.name.contains(word)
.or(QItem.item.category.name.contains(word))
)
.limit(pageable.getPageSize())
.offset(pageable.getOffset())
.fetch();
return PageableExecutionUtils.getPage(itemList,pageable,() -> count);
}
}

View File

@@ -1,8 +1,14 @@
package com.justpickup.storeservice.domain.item.service;
import com.justpickup.storeservice.domain.item.dto.ItemDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
public interface ItemService {
ItemDto findItemByItemId(Long itemId);
Page<ItemDto> findItemList(Long storeId,String word, Pageable pageable);
}

View File

@@ -1,14 +1,26 @@
package com.justpickup.storeservice.domain.item.service;
import com.justpickup.storeservice.domain.category.exception.NotFoundStoreException;
import com.justpickup.storeservice.domain.item.dto.ItemDto;
import com.justpickup.storeservice.domain.item.dto.ItemSearch;
import com.justpickup.storeservice.domain.item.entity.Item;
import com.justpickup.storeservice.domain.item.exception.NotExistItemException;
import com.justpickup.storeservice.domain.item.repository.ItemRepository;
import com.justpickup.storeservice.domain.item.repository.ItemRepositoryCustom;
import com.justpickup.storeservice.domain.store.entity.Store;
import com.justpickup.storeservice.domain.store.repository.StoreRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@@ -16,6 +28,8 @@ import org.springframework.transaction.annotation.Transactional;
public class ItemServiceImpl implements ItemService {
private final ItemRepository itemRepository;
private final ItemRepositoryCustom itemRepositoryCustom;
private final StoreRepository storeRepository;
@Override
@@ -25,4 +39,15 @@ public class ItemServiceImpl implements ItemService {
return ItemDto.createItemDto(findItem);
}
@Override
public Page<ItemDto> findItemList( Long storeId,String word, Pageable pageable) {
Page<Item> itemList = itemRepositoryCustom.findItem(storeId,word,pageable);
return PageableExecutionUtils.getPage(itemList.stream()
.map(ItemDto::createWithCategoryItemDto)
.collect(Collectors.toList()),pageable,itemList::getTotalElements);
}
}

View File

@@ -1,5 +1,7 @@
package com.justpickup.storeservice.domain.item.web;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.justpickup.storeservice.domain.item.dto.ItemDto;
import com.justpickup.storeservice.domain.item.service.ItemService;
import com.justpickup.storeservice.global.dto.Result;
@@ -8,21 +10,85 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.hibernate.annotations.Parameter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequiredArgsConstructor
@RequestMapping("/item")
public class ItemController {
private final ItemService itemService;
@GetMapping("/{itemId}")
@GetMapping("/item")
public ResponseEntity<Result<GetItemResponse>> getItemList( @RequestParam String word,
@PageableDefault(page = 0, size = 10) Pageable pageable){
Long storeId = 1L;
Page<ItemDto> itemDtoList = itemService.findItemList(storeId,word,pageable);
List<GetItemListResponse.Item> itemList = itemDtoList.stream()
.map(GetItemListResponse.Item::new)
.collect(Collectors.toList());
GetItemListResponse getItemResponse = new GetItemListResponse(
itemList,
itemDtoList.getNumber(),
itemDtoList.getTotalPages()
);
return ResponseEntity.status(HttpStatus.OK)
.body((Result<GetItemResponse>)Result.createSuccessResult(getItemResponse));
}
@Data @NoArgsConstructor @AllArgsConstructor
static class GetItemListResponse {
private List<Item> itemList;
private Page page;
public GetItemListResponse(List<Item> itemList, int startPage,int totalPage) {
this.itemList = itemList;
this.page = new Page(startPage,totalPage);
}
@Data
static class Item{
private Long id;
private String name;
private Yn salesYn;
private Long price;
private String categoryName;
public Item(ItemDto itemDto) {
this.id = itemDto.getId();
this.name = itemDto.getName();
this.salesYn = itemDto.getSalesYn();
this.price = itemDto.getPrice();
this.categoryName = itemDto.getCategoryDto().getName();
}
}
@Data @AllArgsConstructor
static class Page {
int startPage;
int totalPage;
}
}
@GetMapping("/item/{itemId}")
public ResponseEntity getItem(@PathVariable("itemId") Long itemId) {
ItemDto itemByItemId = itemService.findItemByItemId(itemId);

View File

@@ -1,5 +1,5 @@
server:
port: 0
port: 12343
spring:
application: