Address, Config, Orders
This commit is contained in:
parent
03a7734cf3
commit
73a8d045a6
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "Account",
|
||||
"base": "PersistedModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "account.user"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"required": true
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"roleFk": {
|
||||
"type": "number",
|
||||
"mysql": {
|
||||
"columnName": "role"
|
||||
}
|
||||
},
|
||||
"nickname": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"lang": {
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"type": "date"
|
||||
},
|
||||
"updated": {
|
||||
"type": "date"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "Country",
|
||||
"description": "Worldwide countries",
|
||||
"base": "PersistedModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "country"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "Number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"country": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"code": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"acls": [
|
||||
{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -13,6 +13,10 @@ module.exports = Self => {
|
|||
arg: 'typeFk',
|
||||
type: 'Number',
|
||||
description: 'The item type id'
|
||||
}, {
|
||||
arg: 'categoryFk',
|
||||
type: 'Number',
|
||||
description: 'The item category id'
|
||||
}, {
|
||||
arg: 'search',
|
||||
type: 'String',
|
||||
|
@ -42,7 +46,7 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.catalog = async (dated, typeFk, search, order, limit, tagFilter) => {
|
||||
Self.catalog = async (dated, typeFk, categoryFk, search, order, limit, tagFilter) => {
|
||||
let $ = Self.app.models;
|
||||
let itemIds;
|
||||
|
||||
|
@ -63,11 +67,27 @@ module.exports = Self => {
|
|||
if (/^[0-9]+$/.test(search)) {
|
||||
itemIds = [parseInt(search)];
|
||||
} else {
|
||||
if (typeFk || search) {
|
||||
let where = {};
|
||||
let inbounds = await $.Inbound.find({
|
||||
fields: ['itemFk'],
|
||||
where: inboundWhere
|
||||
});
|
||||
itemIds = toValues(inbounds, 'itemFk');
|
||||
|
||||
if (typeFk)
|
||||
if (categoryFk || typeFk || search) {
|
||||
let where = {
|
||||
id: {inq: itemIds}
|
||||
};
|
||||
|
||||
if (typeFk) {
|
||||
where.typeFk = typeFk;
|
||||
} else if (categoryFk) {
|
||||
let types = await $.ItemType.find({
|
||||
fields: ['id'],
|
||||
where: {categoryFk}
|
||||
});
|
||||
where.typeFk = {inq: toValues(types, 'id')};
|
||||
}
|
||||
|
||||
if (search)
|
||||
where.longName = {like: `%${search}%`};
|
||||
|
||||
|
@ -79,15 +99,6 @@ module.exports = Self => {
|
|||
let items = await Self.find(filter);
|
||||
itemIds = items.map(i => i.id);
|
||||
}
|
||||
|
||||
let where = Object.assign({}, inboundWhere);
|
||||
if (itemIds) where.itemFk = {inq: itemIds};
|
||||
|
||||
let inbounds = await $.Inbound.find({
|
||||
fields: ['itemFk'],
|
||||
where
|
||||
});
|
||||
itemIds = toValues(inbounds, 'itemFk');
|
||||
}
|
||||
|
||||
// Applies tag filters
|
||||
|
@ -129,6 +140,9 @@ module.exports = Self => {
|
|||
|
||||
// Obtains distinct tags and it's distinct values
|
||||
|
||||
let tags = [];
|
||||
|
||||
if (typeFk || search) {
|
||||
let tagValues = await $.ItemTag.find({
|
||||
fields: ['tagFk', 'value', 'intValue', 'priority'],
|
||||
where: {
|
||||
|
@ -162,7 +176,7 @@ module.exports = Self => {
|
|||
}
|
||||
|
||||
let tagIds = [...tagValueMap.keys()];
|
||||
let tags = await $.Tag.find({
|
||||
tags = await $.Tag.find({
|
||||
fields: ['id', 'name', 'isQuantitative', 'unit'],
|
||||
where: {
|
||||
id: {inq: tagIds}
|
||||
|
@ -206,12 +220,15 @@ module.exports = Self => {
|
|||
filter
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Obtains items data
|
||||
|
||||
let items = await Self.find({
|
||||
fields: ['id', 'longName', 'subname', 'image'],
|
||||
fields: ['id', 'longName', 'subName', 'image'],
|
||||
where: {id: {inq: itemIds}},
|
||||
limit: limit,
|
||||
order: order,
|
||||
include: [
|
||||
{
|
||||
relation: 'tags',
|
||||
|
@ -236,13 +253,14 @@ module.exports = Self => {
|
|||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
limit: limit,
|
||||
order: order
|
||||
]
|
||||
});
|
||||
|
||||
for (let item of items)
|
||||
for (let item of items) {
|
||||
item.inbound = item.inbounds()[0];
|
||||
item.buy = item.inbound && item.inbound.buy();
|
||||
item.available = sum(item.inbounds(), 'available');
|
||||
}
|
||||
|
||||
return {items, tags};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "Language",
|
||||
"base": "PersistedModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "hedera.language"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"id": true
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"orgName": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"isActive": {
|
||||
"type": "boolean",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "UserPassword",
|
||||
"description": "User password requirements",
|
||||
"base": "PersistedModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "account.userPassword"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "Number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"length": {
|
||||
"type": "Number",
|
||||
"required": true
|
||||
},
|
||||
"nAlpha": {
|
||||
"type": "Number",
|
||||
"required": true
|
||||
},
|
||||
"nUpper": {
|
||||
"type": "Number",
|
||||
"required": true
|
||||
},
|
||||
"nDigits": {
|
||||
"type": "Number",
|
||||
"required": true
|
||||
},
|
||||
"nPunct": {
|
||||
"type": "Number",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,6 +53,9 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Account": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Address": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
@ -68,6 +71,9 @@
|
|||
"Client": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Country": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"DeliveryMethod": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
@ -86,6 +92,9 @@
|
|||
"ItemType": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Language": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"New": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
@ -119,6 +128,9 @@
|
|||
"TicketTracking": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserPassword": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Warehouse": {
|
||||
"dataSource": "vn"
|
||||
}
|
||||
|
|
|
@ -27,42 +27,51 @@ module.exports = function (ctx) {
|
|||
framework: {
|
||||
components: [
|
||||
'QBadge',
|
||||
'QLayout',
|
||||
'QHeader',
|
||||
'QDate',
|
||||
'QDrawer',
|
||||
'QPageContainer',
|
||||
'QPage',
|
||||
'QToolbar',
|
||||
'QToolbarTitle',
|
||||
'QBtn',
|
||||
'QIcon',
|
||||
'QList',
|
||||
'QInfiniteScroll',
|
||||
'QItem',
|
||||
'QItemSection',
|
||||
'QItemLabel',
|
||||
'QCarousel',
|
||||
'QCarouselControl',
|
||||
'QCarouselSlide',
|
||||
'QCard',
|
||||
'QCardSection',
|
||||
'QCardActions',
|
||||
'QRating',
|
||||
'QCheckbox',
|
||||
'QDate',
|
||||
'QDialog',
|
||||
'QDrawer',
|
||||
'QExpansionItem',
|
||||
'QHeader',
|
||||
'QIcon',
|
||||
'QImg',
|
||||
'QInfiniteScroll',
|
||||
'QInput',
|
||||
'QItem',
|
||||
'QItemSection',
|
||||
'QItemLabel',
|
||||
'QList',
|
||||
'QLayout',
|
||||
'QPageContainer',
|
||||
'QPage',
|
||||
'QPageSticky',
|
||||
'QPopupProxy',
|
||||
'QRadio',
|
||||
'QRating',
|
||||
'QRange',
|
||||
'QSelect',
|
||||
'QSeparator',
|
||||
'QSpinner',
|
||||
'QTooltip',
|
||||
'QImg',
|
||||
'QPageSticky',
|
||||
'QPopupProxy'
|
||||
'QTab',
|
||||
'QTabs',
|
||||
'QTabPanel',
|
||||
'QTabPanels',
|
||||
'QRouteTab',
|
||||
'QToolbar',
|
||||
'QToolbarTitle',
|
||||
'QTooltip'
|
||||
],
|
||||
|
||||
directives: [
|
||||
'Ripple'
|
||||
'Ripple',
|
||||
'ClosePopup'
|
||||
],
|
||||
|
||||
// Quasar plugins
|
||||
|
|
|
@ -7,7 +7,8 @@ export default async ({ app, Vue }) => {
|
|||
userName: null,
|
||||
title: null,
|
||||
subtitle: null,
|
||||
search: null
|
||||
search: null,
|
||||
rightDrawerOpen: true
|
||||
})
|
||||
Vue.prototype.$state = state
|
||||
}
|
||||
|
|
|
@ -1 +1,10 @@
|
|||
// app global css
|
||||
|
||||
.vn-w-md
|
||||
width 30em
|
||||
.vn-pp
|
||||
padding 1em
|
||||
|
||||
@media (max-width: 960px)
|
||||
.vn-pp
|
||||
padding 1em 0
|
||||
|
|
|
@ -5,6 +5,16 @@ export default {
|
|||
password: 'Password',
|
||||
remember: 'Don not close session',
|
||||
search: 'Search',
|
||||
accept: 'Accept',
|
||||
cancel: 'Cancel',
|
||||
save: 'Save',
|
||||
delete: 'Delete',
|
||||
add: 'Add',
|
||||
create: 'create',
|
||||
dataSaved: 'Data saved!',
|
||||
noDataFound: 'No data found',
|
||||
areYouSureDelete: 'Are you sure you want to delete?',
|
||||
fieldIsRequired: 'Field is required',
|
||||
date: {
|
||||
days: [
|
||||
'Sunday',
|
||||
|
@ -64,9 +74,14 @@ export default {
|
|||
orders: 'Orders',
|
||||
conditions: 'Conditions',
|
||||
about: 'About us',
|
||||
config: 'Configuration',
|
||||
user: 'User',
|
||||
addresses: 'Addresses',
|
||||
addressEdit: 'Edit address',
|
||||
register: 'Register',
|
||||
|
||||
// Home
|
||||
recentNews: 'Recent news',
|
||||
startOrder: 'Start order',
|
||||
|
||||
weThinkForYou: 'We think for you',
|
||||
|
@ -86,6 +101,8 @@ export default {
|
|||
|
||||
// Catalog
|
||||
more: 'More',
|
||||
noItemsFound: 'No items found',
|
||||
pleaseSetFilter: 'Please, set a filter using the right menu',
|
||||
buy: 'Buy',
|
||||
deleteFilter: 'Delete filter',
|
||||
viewMore: 'View more',
|
||||
|
@ -101,6 +118,7 @@ export default {
|
|||
relevancy: 'Relevancy',
|
||||
priceAsc: 'Acending price',
|
||||
priceDesc: 'Descending price',
|
||||
novelty: 'Novelty',
|
||||
available: 'Available',
|
||||
siceAsc: 'Ascending size',
|
||||
sizeDesc: 'Descencing size',
|
||||
|
@ -109,6 +127,7 @@ export default {
|
|||
ordersMadeAt: 'Orders made at',
|
||||
pendingConfirmtion: 'Pending confirmation',
|
||||
noOrdersFound: 'No orders found',
|
||||
pending: 'Pending',
|
||||
confirmed: 'Confirmed',
|
||||
packages: '{n} packages',
|
||||
|
||||
|
@ -118,10 +137,45 @@ export default {
|
|||
// About
|
||||
aboutDesc: 'Verdnatura offers all services for your florist.',
|
||||
|
||||
// Config
|
||||
userName: 'Username',
|
||||
nickname: 'Nickname',
|
||||
language: 'Language',
|
||||
receiveInvoiceByEmail: 'Receive invoices by email',
|
||||
passwordRequirements: 'Password requirements',
|
||||
charsLong: '{0} characters long',
|
||||
alphabeticChars: '{0} alphabetic chars',
|
||||
upperLetters: '{0} uppercase letters',
|
||||
digits: '{0} digits',
|
||||
simbols: '{0} symbols. Ex: $%&.',
|
||||
oldPassword: 'Old password',
|
||||
newPassword: 'New password',
|
||||
repeatPassword: 'Repeat password',
|
||||
changePassword: 'Change password',
|
||||
passwordCannotBeEmpty: 'Password cannot be empty',
|
||||
passwordsDontMatch: 'Passwords do not match',
|
||||
passwordChanged: 'Password changed successfully!',
|
||||
|
||||
// Addresses
|
||||
setAsDefault: 'Set as default',
|
||||
addressSetAsDefault: 'Address set as default',
|
||||
addressRemoved: 'Address removed',
|
||||
areYouSureDeleteAddress: 'Are you sure you want to delete the address?',
|
||||
addressCreated: 'Address created successfully!',
|
||||
|
||||
// Address
|
||||
consignatary: 'Consignatary',
|
||||
street: 'Street',
|
||||
city: 'City',
|
||||
postalCode: 'Postal code',
|
||||
province: 'Province',
|
||||
country: 'Country',
|
||||
phone: 'Phone',
|
||||
mobile: 'Mobile',
|
||||
|
||||
// Register
|
||||
registerAsNew: 'Registrarse como nuevo usuario',
|
||||
notYetUser: 'You are not yet a user, register now and start enjoying everything that Verdnatura offers you.',
|
||||
repeatPassword: 'Repeat password',
|
||||
receiveOffers: 'Receive offers and promotions by e-mail',
|
||||
userRegistered: 'User registered successfully'
|
||||
}
|
||||
|
|
|
@ -5,6 +5,16 @@ export default {
|
|||
password: 'Contraseña',
|
||||
remember: 'No cerrar sesión',
|
||||
search: 'Buscar',
|
||||
accept: 'Aceptar',
|
||||
cancel: 'Cancelar',
|
||||
save: 'Guardar',
|
||||
delete: 'Borrar',
|
||||
add: 'Añadir',
|
||||
create: 'Crear',
|
||||
dataSaved: '¡Datos guardados!',
|
||||
noDataFound: 'No se han encontrado datos',
|
||||
areYouSureDelete: '¿Seguro que quieres eliminar?',
|
||||
fieldIsRequired: 'Campo requerido',
|
||||
date: {
|
||||
days: [
|
||||
'Domingo',
|
||||
|
@ -65,9 +75,14 @@ export default {
|
|||
orders: 'Pedidos',
|
||||
conditions: 'Condiciones',
|
||||
about: 'Sobre nosotros',
|
||||
config: 'Configuración',
|
||||
user: 'Usuario',
|
||||
addresses: 'Direcciones',
|
||||
addressEdit: 'Editar dirección',
|
||||
register: 'Registrarse',
|
||||
|
||||
// Home
|
||||
recentNews: 'Noticias recientes',
|
||||
startOrder: 'Empezar pedido',
|
||||
|
||||
weThinkForYou: 'Pensamos para tí',
|
||||
|
@ -87,6 +102,8 @@ export default {
|
|||
|
||||
// Catalog
|
||||
more: 'Más',
|
||||
noItemsFound: 'No se han encontrado artículos',
|
||||
pleaseSetFilter: 'Por favor, establece un filtro usando el menú de la derecha',
|
||||
buy: 'Comprar',
|
||||
deleteFilter: 'Eliminar filtro',
|
||||
viewMore: 'Ver más',
|
||||
|
@ -102,6 +119,7 @@ export default {
|
|||
relevancy: 'Relevancia',
|
||||
priceAsc: 'Precio ascendente',
|
||||
priceDesc: 'Precio descendente',
|
||||
novelty: 'Novedad',
|
||||
available: 'Disponible',
|
||||
siceAsc: 'Medida ascendente',
|
||||
sizeDesc: 'Medida descendente',
|
||||
|
@ -110,6 +128,7 @@ export default {
|
|||
ordersMadeAt: 'Pedidos realizados en',
|
||||
pendingConfirmtion: 'Pendientes de confirmar',
|
||||
noOrdersFound: 'No se han encontrado pedidos',
|
||||
pending: 'Pendientes',
|
||||
confirmed: 'Confirmados',
|
||||
packages: '{n} bultos',
|
||||
|
||||
|
@ -119,10 +138,45 @@ export default {
|
|||
// About
|
||||
aboutDesc: 'Verdnatura te ofrece todos los servicios que necesita tu floristería.',
|
||||
|
||||
// Config
|
||||
userName: 'Nombre de usuario',
|
||||
nickname: 'Nombre a mostrar',
|
||||
language: 'Idioma',
|
||||
receiveInvoiceByEmail: 'Recibir facturas por correo eletrónico',
|
||||
passwordRequirements: 'Requisitos de contraseña',
|
||||
charsLong: '{0} carácteres de longitud',
|
||||
alphabeticChars: '{0} carácteres alfabéticos',
|
||||
upperLetters: '{0} letras mayúsculas',
|
||||
digits: '{0} dígitos',
|
||||
simbols: '{0} símbolos. Ej: $%&.',
|
||||
oldPassword: 'Antigua contraseña',
|
||||
newPassword: 'Nueva contraseña',
|
||||
repeatPassword: 'Repetir contraseña',
|
||||
changePassword: 'Cambiar contraseña',
|
||||
passwordCannotBeEmpty: 'La contraseña no puede estar vacía',
|
||||
passwordsDontMatch: 'Las contraseñas no coinciden',
|
||||
passwordChanged: '¡Contraseña modificada correctamente!',
|
||||
|
||||
// Addresses
|
||||
setAsDefault: 'Establecer como predeterminada',
|
||||
addressSetAsDefault: 'Dirección establecida como predeterminada',
|
||||
addressRemoved: 'Dirección eliminada',
|
||||
areYouSureDeleteAddress: '¿Seguro que quieres eliminar la dirección?',
|
||||
addressCreated: '¡Dirección creada correctamente!',
|
||||
|
||||
// Address
|
||||
consignatary: 'Consignatario',
|
||||
street: 'Dirección',
|
||||
city: 'City',
|
||||
postalCode: 'Código postal',
|
||||
province: 'Provincia',
|
||||
country: 'País',
|
||||
phone: 'Teléfono',
|
||||
mobile: 'Móvil',
|
||||
|
||||
// Register
|
||||
registerAsNew: 'Registrarse como nuevo usuario',
|
||||
notYetUser: '¿Todavía no eres usuari@?, registrate y empieza a disfrutar de todo lo que Verdnatura puede ofrecerte.',
|
||||
repeatPassword: 'Repetir contraseña',
|
||||
receiveOffers: 'Recibir ofertas y promociones por correo electrónico',
|
||||
userRegistered: 'Usuario registrado correctamente'
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
<q-checkbox v-model="remember" :label="$t('remember')" />
|
||||
</q-card-section>
|
||||
<q-card-actions class="justify-center">
|
||||
<q-btn flat :label="$t('register')" to="/register" />
|
||||
<q-btn flat :label="$t('enter')" @click="login()" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
|
|
|
@ -19,36 +19,50 @@
|
|||
{{$state.subtitle}}
|
||||
</div>
|
||||
</q-toolbar-title>
|
||||
<q-input
|
||||
v-if="$router.currentRoute.name == 'catalog'"
|
||||
dark
|
||||
dense
|
||||
standout
|
||||
v-model="$state.search">
|
||||
<template v-slot:append>
|
||||
<q-icon v-if="$state.search === ''" name="search" />
|
||||
<q-icon v-else name="clear" class="cursor-pointer" @click="$state.search = ''" />
|
||||
</template>
|
||||
</q-input>
|
||||
<q-btn flat
|
||||
v-if="$router.currentRoute.name == 'catalog'"
|
||||
class="q-ml-md"
|
||||
:label="$t('configureOrder')"
|
||||
:to="{name: 'orders'}"
|
||||
/>
|
||||
<q-btn flat
|
||||
v-if="!$state.userId"
|
||||
class="q-ml-md"
|
||||
:label="$t('login')"
|
||||
:to="{name: 'login'}"
|
||||
to="/login"
|
||||
/>
|
||||
<q-input
|
||||
v-if="$router.currentRoute.name == 'catalog'"
|
||||
v-model="$state.search"
|
||||
debounce="500"
|
||||
class="q-mr-sm"
|
||||
dark
|
||||
dense
|
||||
standout>
|
||||
<template v-slot:append>
|
||||
<q-icon
|
||||
v-if="$state.search === ''"
|
||||
name="search"
|
||||
/>
|
||||
<q-icon
|
||||
v-else
|
||||
name="clear"
|
||||
class="cursor-pointer"
|
||||
@click="$state.search = ''"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-btn
|
||||
v-if="$router.currentRoute.name == 'catalog'"
|
||||
@click="$state.rightDrawerOpen = !$state.rightDrawerOpen"
|
||||
aria-label="Menu"
|
||||
flat
|
||||
dense
|
||||
round>
|
||||
<q-icon name="menu" />
|
||||
</q-btn>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
<q-drawer
|
||||
v-model="leftDrawerOpen"
|
||||
content-class="bg-grey-2"
|
||||
behavior="mobile"
|
||||
elevated
|
||||
>
|
||||
overlay>
|
||||
<div class="q-pa-md shadow-1 q-mb-md bg-grey-10" style="color: white;">
|
||||
<img src="statics/logo-dark.svg" alt="Verdnatura" class="logo q-mb-md"/>
|
||||
<div class="row items-center full-width justify-between">
|
||||
|
@ -82,11 +96,21 @@
|
|||
<q-item-label>{{$t('about')}}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable :to="{name: 'register'}">
|
||||
<q-expansion-item
|
||||
expand-icon-toggle
|
||||
expand-separator
|
||||
:label="$t('config')">
|
||||
<q-item clickable :to="{name: 'config'}">
|
||||
<q-item-section>
|
||||
<q-item-label>{{$t('register')}}</q-item-label>
|
||||
<q-item-label>{{$t('user')}}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable :to="{name: 'addresses'}">
|
||||
<q-item-section>
|
||||
<q-item-label>{{$t('addresses')}}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-expansion-item>
|
||||
</q-list>
|
||||
</q-drawer>
|
||||
<q-page-container>
|
||||
|
@ -123,7 +147,7 @@ export default {
|
|||
openURL,
|
||||
logout () {
|
||||
localStorage.removeItem('token')
|
||||
this.$router.push('login')
|
||||
this.$router.push('/login')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
<template>
|
||||
<div class="vn-pp row justify-center">
|
||||
<q-card class="vn-w-md">
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-input v-model="address.nickname" :label="$t('consignatary')" />
|
||||
</q-card-section>
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-input v-model="address.street" :label="$t('street')" />
|
||||
</q-card-section>
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-input v-model="address.city" :label="$t('city')" />
|
||||
</q-card-section>
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-input v-model="address.postalCode" :label="$t('postalCode')" />
|
||||
</q-card-section>
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-select
|
||||
v-model="country"
|
||||
:label="$t('country')"
|
||||
:options="countries"
|
||||
option-value="id"
|
||||
option-label="country"/>
|
||||
</q-card-section>
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-select
|
||||
v-model="province"
|
||||
:label="$t('province')"
|
||||
:options="provinces"
|
||||
option-value="id"
|
||||
option-label="name"/>
|
||||
</q-card-section>
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-input v-model="address.phone" :label="$t('phone')" />
|
||||
</q-card-section>
|
||||
<q-card-actions class="justify-center">
|
||||
<q-btn
|
||||
:label="$t('cancel')"
|
||||
to="/addresses"
|
||||
flat/>
|
||||
<q-btn
|
||||
v-if="addressId"
|
||||
:label="$t('save')"
|
||||
@click="onSave"
|
||||
flat/>
|
||||
<q-btn
|
||||
v-if="!addressId"
|
||||
:label="$t('create')"
|
||||
@click="onSave"
|
||||
flat/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Address',
|
||||
data () {
|
||||
return {
|
||||
address: {},
|
||||
addressId: null,
|
||||
provinces: null,
|
||||
countries: null,
|
||||
province: null,
|
||||
country: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let filter = {
|
||||
fields: ['id', 'country']
|
||||
}
|
||||
this.$axios.get('Countries', { params: { filter } })
|
||||
.then(res => (this.countries = res.data))
|
||||
|
||||
this.loadAddress()
|
||||
},
|
||||
watch: {
|
||||
address () {
|
||||
this.updateCountry()
|
||||
},
|
||||
countries () {
|
||||
this.updateCountry()
|
||||
},
|
||||
provinces () {
|
||||
this.updateProvince()
|
||||
},
|
||||
country () {
|
||||
this.loadProvinces()
|
||||
},
|
||||
province (value) {
|
||||
if (!this.address.provinceFk) return
|
||||
this.address.provinceFk = value && value.id
|
||||
},
|
||||
'this.$route.params.address': function () {
|
||||
this.loadAddress()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadAddress () {
|
||||
this.addressId = this.$route.params.address
|
||||
this.address = {}
|
||||
|
||||
if (this.addressId) {
|
||||
let filter = {
|
||||
fields: [
|
||||
'id',
|
||||
'nickname',
|
||||
'street',
|
||||
'city',
|
||||
'postalCode',
|
||||
'provinceFk',
|
||||
'phone'
|
||||
],
|
||||
include: {
|
||||
relation: 'province',
|
||||
scope: {
|
||||
fields: ['countryFk']
|
||||
}
|
||||
}
|
||||
}
|
||||
this.$axios.get(`Addresses/${this.addressId}`, { params: { filter } })
|
||||
.then(res => (this.address = res.data))
|
||||
} else {
|
||||
this.address = {
|
||||
clientFk: this.$state.userId
|
||||
}
|
||||
}
|
||||
},
|
||||
loadProvinces () {
|
||||
this.provinces = null
|
||||
if (!this.country) return
|
||||
|
||||
let filter = {
|
||||
fields: ['id', 'name'],
|
||||
where: { countryFk: this.country.id }
|
||||
}
|
||||
this.$axios.get('Provinces', { params: { filter } })
|
||||
.then(res => (this.provinces = res.data))
|
||||
},
|
||||
updateCountry () {
|
||||
if (!this.countries || !this.address.province) return
|
||||
this.country = this.countries.find(
|
||||
i => i.id === this.address.province.countryFk)
|
||||
},
|
||||
updateProvince () {
|
||||
if (!this.provinces) return
|
||||
let province = this.provinces.find(
|
||||
i => i.id === this.address.provinceFk)
|
||||
if (province) this.province = province
|
||||
},
|
||||
onSave () {
|
||||
if (this.addressId) {
|
||||
this.$axios.patch(`Addresses/${this.addressId}`, this.address)
|
||||
.then(res => {
|
||||
this.$q.notify(this.$t('dataSaved'))
|
||||
this.$router.push('/addresses')
|
||||
})
|
||||
} else {
|
||||
this.$axios.post(`Addresses`, this.address)
|
||||
.then(res => {
|
||||
this.$q.notify(this.$t('addressCreated'))
|
||||
this.$router.push('/addresses')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,141 @@
|
|||
<template>
|
||||
<div class="vn-pp row justify-center">
|
||||
<q-card class="vn-w-md">
|
||||
<q-list bordered separator>
|
||||
<q-item v-if="addresses && !addresses.length">
|
||||
{{$t('noDataFound')}}
|
||||
</q-item>
|
||||
<q-item
|
||||
v-for="(address, index) in addresses"
|
||||
:key="address.id"
|
||||
:to="`/address/${address.id}`"
|
||||
clickable
|
||||
v-ripple>
|
||||
<q-item-section side top>
|
||||
<q-radio
|
||||
v-model="defaultAddress"
|
||||
:val="address.id"
|
||||
:title="$t('setAsDefault')"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label class="ellipsis">
|
||||
{{address.nickname}}
|
||||
</q-item-label>
|
||||
<q-item-label class="ellipsis" caption>
|
||||
{{address.street}}
|
||||
</q-item-label>
|
||||
<q-item-label class="ellipsis" caption>
|
||||
{{address.postalCode}} {{address.city}}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side top>
|
||||
<q-btn
|
||||
icon="delete"
|
||||
@click="deleteAddress(index, $event)"
|
||||
:title="$t('delete')"
|
||||
size="12px"
|
||||
flat
|
||||
dense
|
||||
round/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card>
|
||||
<q-dialog v-model="confirm" persistent>
|
||||
<q-card>
|
||||
<q-card-section class="row items-center">
|
||||
{{$t('areYouSureDeleteAddress')}}
|
||||
</q-card-section>
|
||||
<q-card-actions align="right">
|
||||
<q-btn
|
||||
:label="$t('cancel')"
|
||||
v-close-popup
|
||||
flat
|
||||
color="primary"/>
|
||||
<q-btn
|
||||
:label="$t('accept')"
|
||||
@click="confirmDeletion(deleteIndex)"
|
||||
v-close-popup
|
||||
flat
|
||||
color="primary"/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
||||
<q-btn
|
||||
fab
|
||||
icon="add"
|
||||
color="accent"
|
||||
to="/address"
|
||||
:title="$t('add')"
|
||||
/>
|
||||
</q-page-sticky>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Addresses',
|
||||
data () {
|
||||
return {
|
||||
addresses: null,
|
||||
defaultAddress: null,
|
||||
confirm: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let filter = {
|
||||
fields: [
|
||||
'id',
|
||||
'nickname',
|
||||
'street',
|
||||
'city',
|
||||
'postalCode'
|
||||
],
|
||||
where: {
|
||||
clientFk: this.$state.userId,
|
||||
isActive: true
|
||||
},
|
||||
order: 'nickname'
|
||||
}
|
||||
this.$axios.get('Addresses', { params: { filter } })
|
||||
.then(res => (this.addresses = res.data))
|
||||
|
||||
filter = {
|
||||
fields: ['defaultAddressFk']
|
||||
}
|
||||
this.$axios.get(`Clients/${this.$state.userId}`, { params: { filter } })
|
||||
.then(res => (this.defaultAddress = res.data.defaultAddressFk))
|
||||
},
|
||||
watch: {
|
||||
defaultAddress (value, oldValue) {
|
||||
if (!oldValue) return
|
||||
let data = { defaultAddressFk: value }
|
||||
this.$axios.patch(`Clients/${this.$state.userId}`, data)
|
||||
.then(res => {
|
||||
this.$q.notify(this.$t('addressSetAsDefault'))
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteAddress (index, event) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.confirm = true
|
||||
this.deleteIndex = index
|
||||
},
|
||||
confirmDeletion (index) {
|
||||
let address = this.addresses[index]
|
||||
let data = { isActive: false }
|
||||
this.$axios.patch(`Addresses/${address.id}`, data)
|
||||
.then(res => {
|
||||
this.addresses.splice(index, 1)
|
||||
this.$q.notify(this.$t('addressRemoved'))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div style="padding-bottom: 5em;">
|
||||
<q-drawer
|
||||
v-model="rightDrawerOpen"
|
||||
v-model="$state.rightDrawerOpen"
|
||||
content-class="bg-grey-2"
|
||||
side="right"
|
||||
elevated>
|
||||
|
@ -14,7 +14,7 @@
|
|||
name="cancel"
|
||||
class="cursor-pointer"
|
||||
:title="$t('deleteFilter')"
|
||||
:to="{params: {category: null}}"
|
||||
@click="$router.push({params: {category: null}})"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -25,7 +25,7 @@
|
|||
:class="{active: category == cat.id}"
|
||||
:key="cat.id"
|
||||
:title="cat.name"
|
||||
:to="{params: {category: cat.id}}">
|
||||
:to="{params: {category: cat.id, type: null}}">
|
||||
<img
|
||||
class="category-img"
|
||||
:src="`statics/category/${cat.id}.svg`">
|
||||
|
@ -40,6 +40,7 @@
|
|||
clearable
|
||||
:label="$t('family')"
|
||||
@filter="filterType"
|
||||
@input="$router.push({params: {type: type && type.id}})"
|
||||
/>
|
||||
</div>
|
||||
<q-separator />
|
||||
|
@ -66,14 +67,14 @@
|
|||
</div>
|
||||
<q-separator />
|
||||
<div class="q-pa-md"
|
||||
v-if="type || search">
|
||||
v-if="typeId || search">
|
||||
<div class="q-mb-md"
|
||||
v-for="tag in tags"
|
||||
:key="tag.uid">
|
||||
<div class="q-mb-xs text-caption text-grey-7">
|
||||
{{tag.name}}
|
||||
<q-icon
|
||||
v-if="tag.filter.length || tag.filter.min || tag.filter.max"
|
||||
v-if="tag.hasFilter"
|
||||
style="font-size: 1.3em;"
|
||||
name="cancel"
|
||||
:title="$t('deleteFilter')"
|
||||
|
@ -90,7 +91,7 @@
|
|||
:dense="true"
|
||||
:val="value"
|
||||
:label="value"
|
||||
@input="loadItems"
|
||||
@input="onCheck(tag)"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="tag.values.length > tag.showCount">
|
||||
|
@ -118,8 +119,9 @@
|
|||
:min="tag.min"
|
||||
:max="tag.max"
|
||||
:step="tag.step"
|
||||
@input="loadItemsDelayed"
|
||||
@change="loadItems"
|
||||
:color="tag.hasFilter ? 'primary' : 'grey-6'"
|
||||
@input="onRangeChange(tag, true)"
|
||||
@change="onRangeChange(tag)"
|
||||
label-always
|
||||
markers
|
||||
snap
|
||||
|
@ -139,29 +141,45 @@
|
|||
color="primary"
|
||||
size="50px">
|
||||
</q-spinner>
|
||||
<div
|
||||
v-if="items && !items.length"
|
||||
class="text-subtitle1 text-grey-7 q-pa-md">
|
||||
{{$t('noItemsFound')}}
|
||||
</div>
|
||||
<div
|
||||
v-if="!items && !isLoading"
|
||||
class="text-subtitle1 text-grey-7 q-pa-md">
|
||||
{{$t('pleaseSetFilter')}}
|
||||
</div>
|
||||
<q-card
|
||||
class="my-card"
|
||||
v-for="item in items"
|
||||
:key="item.id">
|
||||
<img :src="`${$imageBase}/catalog/200x200/${item.image}`" />
|
||||
<q-card-section>
|
||||
<div class="name text-subtitle1">{{item.longName}}</div>
|
||||
<div class="text-uppercase text-subtitle1 text-grey-7">
|
||||
<div class="name text-subtitle1">
|
||||
{{item.longName}}
|
||||
</div>
|
||||
<div class="sub-name text-uppercase text-subtitle1 text-grey-7 ellipsize q-pt-xs">
|
||||
{{item.subName}}
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section class="tags">
|
||||
<div class="tags q-pt-xs">
|
||||
<div v-for="tag in item.tags" :key="tag.tagFk">
|
||||
<span class="text-grey-7">{{tag.tag.name}}</span> {{tag.value}}
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-actions class="actions justify-between">
|
||||
<div class="q-pl-sm">
|
||||
<span class="available bg-green text-white">{{item.available}}</span>
|
||||
{{$t('from')}}
|
||||
<span class="price">{{item.inbounds[0].buy && item.inbounds[0].buy.price3 | currency}}</span>
|
||||
<span class="price">{{item.buy && item.buy.price3 | currency}}</span>
|
||||
</div>
|
||||
<q-btn flat>{{$t('buy')}}</q-btn>
|
||||
<q-btn
|
||||
icon="add_shopping_cart"
|
||||
:title="$t('buy')"
|
||||
flat>
|
||||
</q-btn>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</div>
|
||||
|
@ -171,16 +189,27 @@
|
|||
</div>
|
||||
</template>
|
||||
</q-infinite-scroll>
|
||||
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
||||
<q-btn
|
||||
fab
|
||||
icon="shopping_basket"
|
||||
color="accent"
|
||||
:to="{name: 'orders'}"
|
||||
:title="$t('orders')"
|
||||
/>
|
||||
</q-page-sticky>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.my-card
|
||||
width 100%
|
||||
max-width 21em
|
||||
height 38em
|
||||
max-width 17.5em
|
||||
height 32.5em
|
||||
overflow hidden
|
||||
.name
|
||||
.name, .sub-name
|
||||
line-height 1.3em
|
||||
.ellipsize
|
||||
white-space nowrap
|
||||
overflow hidden
|
||||
text-overflow ellipsis
|
||||
|
@ -194,7 +223,7 @@
|
|||
.category-img
|
||||
height 3.5em
|
||||
.tags
|
||||
max-height 6.2em
|
||||
max-height 4.6em
|
||||
overflow hidden
|
||||
.available
|
||||
padding .15em
|
||||
|
@ -223,26 +252,35 @@ export default {
|
|||
date: date.formatDate(new Date(), 'YYYY/MM/DD'),
|
||||
category: null,
|
||||
categories: [],
|
||||
tags: [],
|
||||
type: null,
|
||||
typeId: null,
|
||||
types: [],
|
||||
orgTypes: [],
|
||||
tags: [],
|
||||
isLoading: false,
|
||||
items: null,
|
||||
rightDrawerOpen: this.$q.platform.is.desktop,
|
||||
pageSize: 24,
|
||||
limit: null,
|
||||
pageSize: 30,
|
||||
maxTags: 5,
|
||||
disableScroll: true,
|
||||
order: {
|
||||
label: this.$t('name'),
|
||||
value: 'name'
|
||||
label: this.$t('relevancy'),
|
||||
value: 'relevancy DESC, longName'
|
||||
},
|
||||
orderOptions: [
|
||||
{
|
||||
label: this.$t('relevancy'),
|
||||
value: 'relevancy DESC, longName'
|
||||
}, {
|
||||
label: this.$t('name'),
|
||||
value: 'longName'
|
||||
}, /* {
|
||||
}, {
|
||||
label: this.$t('siceAsc'),
|
||||
value: 'size ASC'
|
||||
}, {
|
||||
label: this.$t('sizeDesc'),
|
||||
value: 'size DESC'
|
||||
} /* {
|
||||
label: this.$t('priceAsc'),
|
||||
value: 'price ASC'
|
||||
}, {
|
||||
|
@ -251,71 +289,92 @@ export default {
|
|||
}, {
|
||||
label: this.$t('available'),
|
||||
value: 'available'
|
||||
}, */ {
|
||||
label: this.$t('siceAsc'),
|
||||
value: 'size ASC'
|
||||
}, {
|
||||
label: this.$t('sizeDesc'),
|
||||
value: 'size DESC'
|
||||
}
|
||||
label: this.$t('novelty'),
|
||||
value: 'dated DESC'
|
||||
} */
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$axios.get('ItemCategories')
|
||||
.then(res => (this.categories = res.data))
|
||||
this.onRouteChange(this.$route)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.clearTimeoutAndRequest()
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.onRouteChange(to)
|
||||
next()
|
||||
},
|
||||
watch: {
|
||||
categories () {
|
||||
this.refreshTitle()
|
||||
},
|
||||
orgTypes () {
|
||||
this.refreshTitle()
|
||||
},
|
||||
order () {
|
||||
this.loadItems()
|
||||
},
|
||||
date () {
|
||||
this.loadItems()
|
||||
},
|
||||
type (type) {
|
||||
this.$router.push({
|
||||
params: {
|
||||
category: this.category,
|
||||
type: type && type.id
|
||||
category (value) {
|
||||
this.orgTypes = []
|
||||
|
||||
if (value) {
|
||||
let filter = {
|
||||
where: { categoryFk: value },
|
||||
order: 'order DESC, name'
|
||||
}
|
||||
this.$axios.get('ItemTypes', { params: { filter } })
|
||||
.then(res => (this.orgTypes = res.data))
|
||||
}
|
||||
})
|
||||
this.$state.subtitle = type && type.name
|
||||
this.$state.search = ''
|
||||
this.loadItems()
|
||||
},
|
||||
'$state.search': function (value) {
|
||||
if (!value) this.loadItems()
|
||||
else this.loadItemsDelayed()
|
||||
},
|
||||
'$route.params.category': function (categoryId) {
|
||||
let category = this.categories.find(i => i.id === categoryId)
|
||||
|
||||
if (category) {
|
||||
this.$state.title = category.name
|
||||
this.$state.titleColor = `#${category.color}`
|
||||
} else {
|
||||
this.$state.title = this.$t(this.$router.currentRoute.name)
|
||||
this.$state.titleColor = null
|
||||
}
|
||||
|
||||
this.category = categoryId
|
||||
this.type = null
|
||||
|
||||
let params = { filter: { where: { categoryFk: categoryId } } }
|
||||
this.$axios.get('ItemTypes', { params })
|
||||
.then(res => (this.orgTypes = res.data))
|
||||
},
|
||||
'$route.params.type': function (type) {
|
||||
if (!this.type) this.type = { id: type }
|
||||
let location = { params: this.$route.params }
|
||||
if (value) location.query = { search: value }
|
||||
this.$router.push(location)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadItemsDelayed () {
|
||||
this.clearTimeoutAndRequest()
|
||||
this.timeout = setTimeout(() => this.loadItems(), 500)
|
||||
onRouteChange (route) {
|
||||
let { category, type } = route.params
|
||||
|
||||
category = parseInt(category) || null
|
||||
type = parseInt(type) || null
|
||||
|
||||
this.category = category
|
||||
this.typeId = category ? type : null
|
||||
this.search = route.query.search || ''
|
||||
this.$state.search = this.search
|
||||
this.tags = []
|
||||
|
||||
this.refreshTitle()
|
||||
this.loadItems()
|
||||
},
|
||||
refreshTitle () {
|
||||
let title = this.$t(this.$router.currentRoute.name)
|
||||
let titleColor
|
||||
let subtitle
|
||||
|
||||
if (this.category) {
|
||||
let category = this.categories.find(i => i.id === this.category) || {}
|
||||
title = category.name
|
||||
titleColor = `#${category.color}`
|
||||
}
|
||||
|
||||
if (this.typeId) {
|
||||
this.type = this.orgTypes.find(i => i.id === this.typeId)
|
||||
subtitle = title
|
||||
title = this.type && this.type.name
|
||||
} else {
|
||||
this.type = null
|
||||
}
|
||||
|
||||
Object.assign(this.$state, { title, titleColor, subtitle })
|
||||
},
|
||||
clearTimeoutAndRequest () {
|
||||
if (this.timeout) {
|
||||
|
@ -327,34 +386,36 @@ export default {
|
|||
this.source = null
|
||||
}
|
||||
},
|
||||
loadItemsDelayed () {
|
||||
this.clearTimeoutAndRequest()
|
||||
this.timeout = setTimeout(() => this.loadItems(), 500)
|
||||
},
|
||||
loadItems () {
|
||||
this.items = []
|
||||
this.items = null
|
||||
this.isLoading = true
|
||||
this.limit = this.pageSize
|
||||
this.disableScroll = false
|
||||
this.loadItemsBase()
|
||||
.finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
.finally(() => (this.isLoading = false))
|
||||
},
|
||||
onLoad (index, done) {
|
||||
if (this.isLoading) return done()
|
||||
this.limit += this.pageSize
|
||||
this.loadItemsBase()
|
||||
.finally(() => done())
|
||||
.finally(done)
|
||||
},
|
||||
loadItemsBase () {
|
||||
this.clearTimeoutAndRequest()
|
||||
|
||||
if (!this.type) {
|
||||
if (!(this.category || this.typeId || this.search)) {
|
||||
this.tags = []
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
let typeFk
|
||||
let tagFilter = []
|
||||
|
||||
for (let tag of this.tags) {
|
||||
if (this.hasFilters(tag)) {
|
||||
if (tag.hasFilter) {
|
||||
tagFilter.push({
|
||||
tagFk: tag.id,
|
||||
values: tag.filter
|
||||
|
@ -362,19 +423,16 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.type) {
|
||||
typeFk = this.type.id
|
||||
}
|
||||
|
||||
this.source = CancelToken.source()
|
||||
|
||||
let params = {
|
||||
dated: this.date,
|
||||
typeFk: typeFk,
|
||||
search: this.$state.search,
|
||||
typeFk: this.typeId,
|
||||
categoryFk: this.category,
|
||||
search: this.search,
|
||||
order: this.order.value,
|
||||
tagFilter: tagFilter,
|
||||
limit: this.limit
|
||||
limit: this.limit,
|
||||
tagFilter
|
||||
}
|
||||
let config = {
|
||||
params,
|
||||
|
@ -382,14 +440,20 @@ export default {
|
|||
}
|
||||
return this.$axios.get('Items/catalog', config)
|
||||
.then(res => this.onItemsGet(res))
|
||||
.catch(err => { if (!err.__CANCEL__) throw err })
|
||||
.catch(err => this.onItemsError(err))
|
||||
.finally(() => (this.cancel = null))
|
||||
},
|
||||
onItemsError (err) {
|
||||
if (err.__CANCEL__) return
|
||||
this.disableScroll = true
|
||||
throw err
|
||||
},
|
||||
onItemsGet (res) {
|
||||
for (let tag of res.data.tags) {
|
||||
tag.uid = this.uid++
|
||||
|
||||
if (tag.filter) {
|
||||
tag.hasFilter = true
|
||||
tag.useRange =
|
||||
tag.filter.max ||
|
||||
tag.filter.min
|
||||
|
@ -412,11 +476,23 @@ export default {
|
|||
this.tags = res.data.tags
|
||||
this.disableScroll = this.items.length < this.limit
|
||||
},
|
||||
onRangeChange (tag, delay) {
|
||||
tag.hasFilter = true
|
||||
|
||||
if (!delay) this.loadItems()
|
||||
else this.loadItemsDelayed()
|
||||
},
|
||||
onCheck (tag) {
|
||||
tag.hasFilter = tag.filter.length > 0
|
||||
this.loadItems()
|
||||
},
|
||||
resetTagFilter (tag) {
|
||||
tag.hasFilter = false
|
||||
|
||||
if (tag.useRange) {
|
||||
tag.filter = {
|
||||
min: null,
|
||||
max: null
|
||||
min: tag.min,
|
||||
max: tag.max
|
||||
}
|
||||
} else {
|
||||
tag.filter = []
|
||||
|
@ -426,10 +502,6 @@ export default {
|
|||
this.resetTagFilter(tag)
|
||||
this.loadItems()
|
||||
},
|
||||
hasFilters (tag) {
|
||||
let filter = tag.filter
|
||||
return filter.length || filter.min || filter.max
|
||||
},
|
||||
filterType (val, update) {
|
||||
if (val === '') {
|
||||
update(() => { this.types = this.orgTypes })
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
<template>
|
||||
<div class="vn-pp row justify-center">
|
||||
<q-card class="vn-w-md">
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-input
|
||||
v-model="user.name"
|
||||
:label="$t('userName')"
|
||||
readonly/>
|
||||
</q-card-section>
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-input v-model="user.email" :label="$t('email')" />
|
||||
</q-card-section>
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-input v-model="user.nickname" :label="$t('nickname')" />
|
||||
</q-card-section>
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-select
|
||||
v-model="user.lang"
|
||||
:label="$t('language')"
|
||||
:options="languages"
|
||||
option-value="id"
|
||||
option-label="name" />
|
||||
</q-card-section>
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-checkbox v-model="receiveInvoices" :label="$t('receiveInvoiceByEmail')" />
|
||||
</q-card-section>
|
||||
<q-card-actions class="justify-center">
|
||||
<q-btn flat :label="$t('changePassword')" :to="{ query: { changePassword: true } }"/>
|
||||
<q-btn flat :label="$t('save')" @click="onSave"/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
<q-dialog v-model="changePassword" persistent>
|
||||
<q-card style="width: 25em;">
|
||||
<q-card-section>
|
||||
<q-input
|
||||
v-model="oldPassword"
|
||||
:label="$t('oldPassword')"
|
||||
type="password"
|
||||
:rules="[val => !!val || $t('fieldIsRequired')]"
|
||||
autofocus
|
||||
dense/>
|
||||
<q-input
|
||||
v-model="newPassword"
|
||||
:label="$t('newPassword')"
|
||||
type="password"
|
||||
:rules="[val => !!val || $t('passwordCannotBeEmpty')]"
|
||||
dense>
|
||||
<template v-slot:append>
|
||||
<q-icon name="warning">
|
||||
<q-tooltip>
|
||||
<div>
|
||||
{{$t('passwordRequirements')}}
|
||||
</div>
|
||||
<ul class="q-pl-md">
|
||||
<li>{{$t('charsLong', [requirements.length])}}</li>
|
||||
<li>{{$t('alphabeticChars', [requirements.nAlpha])}}</li>
|
||||
<li>{{$t('upperLetters', [requirements.nUpper])}}</li>
|
||||
<li>{{$t('digits', [requirements.nDigits])}}</li>
|
||||
<li>{{$t('simbols', [requirements.nPunct])}}</li>
|
||||
</ul>
|
||||
</q-tooltip>
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input
|
||||
v-model="repeatPassword"
|
||||
:label="$t('repeatPassword')"
|
||||
type="password"
|
||||
:rules="[val => val === newPassword || $t('passwordsDontMatch')]"
|
||||
dense/>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right">
|
||||
<q-btn
|
||||
:label="$t('cancel')"
|
||||
to="/config"
|
||||
flat
|
||||
color="primary"/>
|
||||
<q-btn
|
||||
:label="$t('accept')"
|
||||
@click="onChangePassword()"
|
||||
flat
|
||||
color="primary"/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Config',
|
||||
props: {
|
||||
changePassword: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
user: {},
|
||||
receiveInvoices: false,
|
||||
languages: null,
|
||||
requirements: {},
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
repeatPassword: ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let filter = {
|
||||
fields: [
|
||||
'id',
|
||||
'name',
|
||||
'nickname',
|
||||
'lang',
|
||||
'email'
|
||||
]
|
||||
}
|
||||
this.$axios.get(`Accounts/${this.$state.userId}`, { params: { filter } })
|
||||
.then(res => (this.user = res.data))
|
||||
|
||||
filter = {
|
||||
fields: [
|
||||
'code',
|
||||
'name',
|
||||
'orgName'
|
||||
],
|
||||
where: { isActive: true }
|
||||
}
|
||||
this.$axios.get(`Languages`, { params: { filter } })
|
||||
.then(res => (this.languages = res.data))
|
||||
|
||||
filter = {
|
||||
fields: [
|
||||
'id',
|
||||
'length',
|
||||
'nAlpha',
|
||||
'nUpper',
|
||||
'nDigits',
|
||||
'nPunct'
|
||||
]
|
||||
}
|
||||
this.$axios.get(`UserPasswords`, { params: { filter } })
|
||||
.then(res => (this.requirements = res.data[0]))
|
||||
},
|
||||
methods: {
|
||||
onChangePassword: function () {
|
||||
this.$q.notify(this.$t('passwordChanged'))
|
||||
this.$router.push('/config')
|
||||
|
||||
this.oldPassword = ''
|
||||
this.newPassword = ''
|
||||
this.repeatPassword = ''
|
||||
},
|
||||
onSave () {
|
||||
this.$axios.patch(`Accounts/${this.$state.userId}`, this.user)
|
||||
.then(res => (this.$q.notify(this.$t('dataSaved'))))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<div class="my-list q-pa-md">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<q-select v-model="yearFilter" :options="years" :label="$t('ordersMadeAt')" />
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card class="q-mt-md">
|
||||
<q-list bordered separator>
|
||||
<q-item v-if="!tickets.length">
|
||||
{{$t('noOrdersFound')}}
|
||||
</q-item>
|
||||
<q-item
|
||||
v-for="ticket in tickets"
|
||||
:key="ticket.id"
|
||||
clickable
|
||||
v-ripple>
|
||||
<q-item-section>
|
||||
<q-item-label>{{formatDate(ticket.landed)}}</q-item-label>
|
||||
<q-item-label caption>#{{ticket.id}}</q-item-label>
|
||||
<q-item-label caption>{{ticket.address.nickname}}</q-item-label>
|
||||
<q-item-label caption>{{ticket.address.city}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side top>
|
||||
<q-item-label>
|
||||
485.50€
|
||||
</q-item-label>
|
||||
<q-badge
|
||||
v-if="ticket.state"
|
||||
color="teal"
|
||||
:label="ticket.state.state.name"/>
|
||||
<q-item-label v-if="ticket.packages" caption>
|
||||
{{$t('packages', {n: ticket.packages})}}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.my-list
|
||||
max-width 30em
|
||||
margin auto
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import { date } from 'quasar'
|
||||
|
||||
export default {
|
||||
name: 'Orders',
|
||||
data () {
|
||||
return {
|
||||
yearFilter: null,
|
||||
years: [],
|
||||
tickets: []
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let now = new Date()
|
||||
let curYear = now.getFullYear()
|
||||
|
||||
for (let i = 0; i <= 5; i++) {
|
||||
this.years.push(curYear - i)
|
||||
}
|
||||
|
||||
this.yearFilter = curYear
|
||||
},
|
||||
watch: {
|
||||
yearFilter (year) {
|
||||
let start = new Date(year, 0)
|
||||
let end = new Date(year + 1, 0)
|
||||
|
||||
let params = { filter: {
|
||||
where: {
|
||||
clientFk: this.$state.userId,
|
||||
landed: { between: [start, end] }
|
||||
},
|
||||
include: [
|
||||
'address',
|
||||
{ state: 'state' }
|
||||
],
|
||||
order: 'landed DESC'
|
||||
} }
|
||||
this.$axios.get('Tickets', { params })
|
||||
.then(res => (this.tickets = res.data))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDate (value) {
|
||||
return date.formatDate(value, 'ddd, MMMM Do', this.$t('date'))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -29,6 +29,7 @@
|
|||
</div>
|
||||
</q-carousel-slide>
|
||||
</q-carousel>
|
||||
<h2 class="text-center text-grey-7">{{$t('recentNews')}}</h2>
|
||||
<div class="q-pa-sm row items-start">
|
||||
<div
|
||||
class="new-card q-pa-sm"
|
||||
|
@ -48,7 +49,7 @@
|
|||
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
||||
<q-btn
|
||||
fab
|
||||
icon="shopping_basket"
|
||||
icon="add_shopping_cart"
|
||||
color="accent"
|
||||
:to="{name: 'catalog'}"
|
||||
:title="$t('startOrder')"
|
||||
|
@ -113,7 +114,6 @@ export default {
|
|||
name: 'PageIndex',
|
||||
data () {
|
||||
return {
|
||||
slideIndex: 0,
|
||||
slide: null,
|
||||
slides: [
|
||||
{
|
||||
|
@ -154,13 +154,14 @@ export default {
|
|||
this.$axios.get('News', { params })
|
||||
.then(res => (this.news = res.data))
|
||||
|
||||
this.slide = this.slides[this.slideIndex].image
|
||||
this.slide = this.slides[0].image
|
||||
this.interval = setInterval(() => {
|
||||
this.slideIndex = (this.slideIndex + 1) % this.slides.length
|
||||
this.slide = this.slides[this.slideIndex].image
|
||||
let index = this.slides.findIndex(i => i.image === this.slide)
|
||||
let nextIndex = (index + 1) % this.slides.length
|
||||
this.slide = this.slides[nextIndex].image
|
||||
}, 8000)
|
||||
},
|
||||
destroyed () {
|
||||
beforeDestroy () {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,69 +1,16 @@
|
|||
<template>
|
||||
<div class="my-list q-pa-md">
|
||||
<q-card>
|
||||
<q-list bordered separator>
|
||||
<q-item-label header>{{$t('pendingConfirmtion')}}</q-item-label>
|
||||
<q-item v-if="!orders.length">
|
||||
{{$t('noOrdersFound')}}
|
||||
</q-item>
|
||||
<q-item
|
||||
v-for="order in orders"
|
||||
:key="order.id"
|
||||
clickable
|
||||
v-ripple>
|
||||
<q-item-section>
|
||||
<q-item-label>{{formatDate(order.landed)}}</q-item-label>
|
||||
<q-item-label caption>#{{order.id}}</q-item-label>
|
||||
<q-item-label caption>{{order.address.nickname}}</q-item-label>
|
||||
<q-item-label caption>{{order.address.city}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side top>
|
||||
485.50€
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card>
|
||||
<q-card class="q-mt-md">
|
||||
<q-card-section>
|
||||
<q-select v-model="yearFilter" :options="years" :label="$t('ordersMadeAt')" />
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card class="q-mt-md">
|
||||
<q-list bordered separator>
|
||||
<q-item-label header>{{$t('confirmed')}}</q-item-label>
|
||||
<q-item v-if="!tickets.length">
|
||||
{{$t('noOrdersFound')}}
|
||||
</q-item>
|
||||
<q-item
|
||||
v-for="ticket in tickets"
|
||||
:key="ticket.id"
|
||||
clickable
|
||||
v-ripple>
|
||||
<q-item-section>
|
||||
<q-item-label>{{formatDate(ticket.landed)}}</q-item-label>
|
||||
<q-item-label caption>#{{ticket.id}}</q-item-label>
|
||||
<q-item-label caption>{{ticket.address.nickname}}</q-item-label>
|
||||
<q-item-label caption>{{ticket.address.city}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side top>
|
||||
<q-item-label>
|
||||
485.50€
|
||||
</q-item-label>
|
||||
<q-badge
|
||||
v-if="ticket.state"
|
||||
color="teal"
|
||||
:label="ticket.state.state.name"/>
|
||||
<q-item-label v-if="ticket.packages" caption>
|
||||
{{$t('packages', {n: ticket.packages})}}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card>
|
||||
<div>
|
||||
<q-tabs
|
||||
inline-label
|
||||
class="bg-secondary text-white shadow-2">
|
||||
<q-route-tab to="/orders/pending" icon="av_timer" :label="$t('pending')" />
|
||||
<q-route-tab to="/orders/confirmed" icon="check" :label="$t('confirmed')" />
|
||||
</q-tabs>
|
||||
<router-view></router-view>
|
||||
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
||||
<q-btn
|
||||
fab
|
||||
icon="shopping_basket"
|
||||
icon="add_shopping_cart"
|
||||
color="accent"
|
||||
:to="{name: 'catalog'}"
|
||||
:title="$t('startOrder')"
|
||||
|
@ -72,72 +19,12 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.my-list
|
||||
max-width 30em
|
||||
margin auto
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import { date } from 'quasar'
|
||||
|
||||
export default {
|
||||
name: 'Orders',
|
||||
data () {
|
||||
return {
|
||||
yearFilter: null,
|
||||
years: [],
|
||||
orders: [],
|
||||
tickets: []
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let params = { filter: {
|
||||
where: {
|
||||
clientFk: this.$state.userId,
|
||||
isConfirmed: false
|
||||
},
|
||||
include: 'address',
|
||||
order: 'created DESC',
|
||||
limit: 20
|
||||
} }
|
||||
this.$axios.get('Orders', { params })
|
||||
.then(res => (this.orders = res.data))
|
||||
|
||||
let now = new Date()
|
||||
let curYear = now.getFullYear()
|
||||
|
||||
for (let i = 0; i <= 5; i++) {
|
||||
this.years.push(curYear - i)
|
||||
}
|
||||
|
||||
this.yearFilter = curYear
|
||||
},
|
||||
watch: {
|
||||
yearFilter (year) {
|
||||
let start = new Date(year, 0)
|
||||
let end = new Date(year + 1, 0)
|
||||
|
||||
let params = { filter: {
|
||||
where: {
|
||||
clientFk: this.userId,
|
||||
landed: { between: [start, end] }
|
||||
},
|
||||
include: [
|
||||
'address',
|
||||
{ state: 'state' }
|
||||
],
|
||||
order: 'landed DESC'
|
||||
} }
|
||||
this.$axios.get('Tickets', { params })
|
||||
.then(res => (this.tickets = res.data))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDate (value) {
|
||||
return date.formatDate(value, 'ddd, MMMM Do', this.$t('date'))
|
||||
}
|
||||
this.$router.replace('/orders/pending')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<div class="my-list q-pa-md">
|
||||
<q-card>
|
||||
<q-list bordered separator>
|
||||
<q-item v-if="!orders.length">
|
||||
{{$t('noOrdersFound')}}
|
||||
</q-item>
|
||||
<q-item
|
||||
v-for="order in orders"
|
||||
:key="order.id"
|
||||
clickable
|
||||
v-ripple>
|
||||
<q-item-section>
|
||||
<q-item-label>{{formatDate(order.landed)}}</q-item-label>
|
||||
<q-item-label caption>#{{order.id}}</q-item-label>
|
||||
<q-item-label caption>{{order.address.nickname}}</q-item-label>
|
||||
<q-item-label caption>{{order.address.city}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side top>
|
||||
485.50€
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.my-list
|
||||
max-width 30em
|
||||
margin auto
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import { date } from 'quasar'
|
||||
|
||||
export default {
|
||||
name: 'Orders',
|
||||
data () {
|
||||
return {
|
||||
orders: []
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let params = { filter: {
|
||||
where: {
|
||||
clientFk: this.$state.userId,
|
||||
isConfirmed: false
|
||||
},
|
||||
include: 'address',
|
||||
order: 'created DESC',
|
||||
limit: 20
|
||||
} }
|
||||
this.$axios.get('Orders', { params })
|
||||
.then(res => (this.orders = res.data))
|
||||
},
|
||||
methods: {
|
||||
formatDate (value) {
|
||||
return date.formatDate(value, 'ddd, MMMM Do', this.$t('date'))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -23,14 +23,13 @@ export default function (/* { store, ssrContext } */) {
|
|||
})
|
||||
|
||||
Router.afterEach((to, from) => {
|
||||
if (from.name === to.name) return
|
||||
let app = Router.app
|
||||
let $state = app.$state
|
||||
|
||||
if (from.name !== to.name) {
|
||||
$state.title = app.$t(to.name)
|
||||
$state.titleColor = null
|
||||
$state.subtitle = null
|
||||
}
|
||||
Object.assign(app.$state, {
|
||||
title: app.$t(to.name),
|
||||
titleColor: null,
|
||||
subtitle: null
|
||||
})
|
||||
})
|
||||
|
||||
return Router
|
||||
|
|
|
@ -19,7 +19,16 @@ const routes = [
|
|||
}, {
|
||||
name: 'orders',
|
||||
path: '/orders',
|
||||
component: () => import('pages/Orders.vue')
|
||||
component: () => import('pages/Orders.vue'),
|
||||
children: [
|
||||
{
|
||||
path: 'pending',
|
||||
component: () => import('pages/Pending.vue')
|
||||
}, {
|
||||
path: 'confirmed',
|
||||
component: () => import('pages/Confirmed.vue')
|
||||
}
|
||||
]
|
||||
}, {
|
||||
name: 'conditions',
|
||||
path: '/conditions',
|
||||
|
@ -32,6 +41,21 @@ const routes = [
|
|||
name: 'register',
|
||||
path: '/register',
|
||||
component: () => import('pages/Register.vue')
|
||||
}, {
|
||||
name: 'config',
|
||||
path: '/config',
|
||||
component: () => import('pages/Config.vue'),
|
||||
props: route => ({
|
||||
changePassword: String(route.query.changePassword) === 'true'
|
||||
})
|
||||
}, {
|
||||
name: 'addresses',
|
||||
path: '/addresses',
|
||||
component: () => import('pages/Addresses.vue')
|
||||
}, {
|
||||
name: 'addressEdit',
|
||||
path: '/address/:address?',
|
||||
component: () => import('pages/Address.vue')
|
||||
}
|
||||
]
|
||||
}, {
|
||||
|
|
Loading…
Reference in New Issue