0
1
Fork 0
This commit is contained in:
William Buezas 2024-09-22 17:50:24 -03:00
parent c3e3b4d7b6
commit bbaa6d87ff
12 changed files with 1170 additions and 730 deletions

View File

@ -28,13 +28,9 @@ const props = defineProps({
type: Number, type: Number,
required: true required: true
}, },
rounded: { roundedBorders: {
type: Boolean, type: String,
default: false default: 'none'
},
fullRounded: {
type: Boolean,
default: false
}, },
width: { width: {
type: String, type: String,
@ -70,6 +66,17 @@ const showEditForm = ref(false);
const url = computed(() => { const url = computed(() => {
return `${props.baseURL ?? app.imageUrl}/${props.storage}/${props.size}/${props.id}`; return `${props.baseURL ?? app.imageUrl}/${props.storage}/${props.size}/${props.id}`;
}); });
const rounded = computed(() => {
const roundedMap = {
none: '',
default: 'rounded',
full: 'full-rounded',
top: 'top-rounded',
bottom: 'bottom-rounded'
};
return roundedMap[props.roundedBorders];
});
</script> </script>
<template> <template>
<div class="relative-position main-image-container"> <div class="relative-position main-image-container">
@ -85,11 +92,7 @@ const url = computed(() => {
<QTooltip>{{ t('addOrEditImage') }}</QTooltip> <QTooltip>{{ t('addOrEditImage') }}</QTooltip>
</QBtn> </QBtn>
<QImg <QImg
:class="{ :class="[rounded, { zoomIn: props.zoomSize }]"
zoomIn: props.zoomSize,
rounded: props.rounded,
'full-rounded': props.fullRounded
}"
class="main-image" class="main-image"
:src="url" :src="url"
v-bind="$attrs" v-bind="$attrs"
@ -161,9 +164,19 @@ const url = computed(() => {
.rounded { .rounded {
border-radius: 0.6em; border-radius: 0.6em;
} }
.full-rounded { .full-rounded {
border-radius: 50px; border-radius: 50px;
} }
.rounded-bottom {
border-radius: 0.6em 0.6em 0 0;
}
.rounded-top {
border-radius: 0 0 0.6em 0.6em;
}
.img_zoom { .img_zoom {
border-radius: 0%; border-radius: 0%;
} }

View File

@ -103,6 +103,7 @@ export default {
user: 'Usuari', user: 'Usuari',
password: 'Contrasenya', password: 'Contrasenya',
modify: 'Modificar', modify: 'Modificar',
shoppingCart: 'Cistella de la compra',
// Image related translations // Image related translations
'Cant lock cache': 'No es pot bloquejar la memòria cau', 'Cant lock cache': 'No es pot bloquejar la memòria cau',
'Bad file format': 'Format de fitxer no reconegut', 'Bad file format': 'Format de fitxer no reconegut',

View File

@ -136,6 +136,7 @@ export default {
remindMe: 'Remember me', remindMe: 'Remember me',
password: 'Password', password: 'Password',
modify: 'Modify', modify: 'Modify',
shoppingCart: 'Shopping cart',
// Image related translations // Image related translations
'Cant lock cache': 'The cache could not be blocked', 'Cant lock cache': 'The cache could not be blocked',
'Bad file format': 'Unrecognized file format', 'Bad file format': 'Unrecognized file format',

View File

@ -135,6 +135,7 @@ export default {
cancel: 'Cancelar', cancel: 'Cancelar',
of: 'de', of: 'de',
modify: 'Modificar', modify: 'Modificar',
shoppingCart: 'Cesta de la compra',
// Image related translations // Image related translations
'Cant lock cache': 'La caché no pudo ser bloqueada', 'Cant lock cache': 'La caché no pudo ser bloqueada',
'Bad file format': 'Formato de archivo no reconocido', 'Bad file format': 'Formato de archivo no reconocido',

View File

@ -103,6 +103,7 @@ export default {
user: 'Utilisateur', user: 'Utilisateur',
password: 'Mot de passe', password: 'Mot de passe',
modify: 'Modifier', modify: 'Modifier',
shoppingCart: 'Panier',
// Image related translations // Image related translations
'Cant lock cache': "Le cache n'a pas pu être verrouillé", 'Cant lock cache': "Le cache n'a pas pu être verrouillé",
'Bad file format': 'Format de fichier non reconnu', 'Bad file format': 'Format de fichier non reconnu',

View File

@ -101,6 +101,7 @@ export default {
user: 'Utilizador', user: 'Utilizador',
password: 'Senha', password: 'Senha',
modify: 'Modificar', modify: 'Modificar',
shoppingCart: 'Cesta da compra',
// Image related translations // Image related translations
'Cant lock cache': 'O cache não pôde ser bloqueado', 'Cant lock cache': 'O cache não pôde ser bloqueado',
'Bad file format': 'Formato de arquivo inválido', 'Bad file format': 'Formato de arquivo inválido',

View File

@ -33,9 +33,9 @@ const onSearch = data => (items.value = data || []);
<template> <template>
<Teleport v-if="isHeaderMounted" to="#actions"> <Teleport v-if="isHeaderMounted" to="#actions">
<VnSearchBar <VnSearchBar
:sqlQuery="query" :sql-query="query"
@onSearch="onSearch" @on-search="onSearch"
@onSearchError="items = []" @on-search-error="items = []"
/> />
</Teleport> </Teleport>
<QPage class="vn-w-xs"> <QPage class="vn-w-xs">
@ -66,8 +66,8 @@ const onSearch = data => (items.value = data || []);
class="q-mr-md" class="q-mr-md"
rounded rounded
editable editable
editSchema="catalog" edit-schema="catalog"
:editImageName="item.image" :edit-image-name="item.image"
/> />
</template> </template>
<template #content> <template #content>

View File

@ -1,706 +0,0 @@
<template>
<Teleport v-if="isHeaderMounted" to="#actions">
<QInput
:placeholder="$t('search')"
v-model="search"
debounce="500"
class="search q-mr-sm"
rounded
dark
dense
standout
>
<template #prepend>
<QIcon v-if="search === ''" name="search" />
<QIcon
v-else
name="clear"
class="cursor-pointer"
@click="search = ''"
/>
</template>
</QInput>
<QBtn
:icon="$t(viewMode == 'list' ? 'view_list' : 'grid_on')"
:label="$t(viewMode == 'list' ? 'listView' : 'gridView')"
@click="onViewModeClick()"
rounded
no-caps
/>
</Teleport>
<div style="padding-bottom: 5em">
<QDrawer v-model="$app.rightDrawerOpen" side="right" :width="250">
<div class="q-pa-md">
<div class="basket-info">
<p>{{ date(new Date()) }}</p>
<p>
{{ $t('warehouse') }}
{{ 'Algemesi' }}
</p>
<QBtn
flat
rounded
no-caps
:to="{
name: 'checkout',
params: { id: appStore.basketOrderId },
query: { continue: 'catalog' }
}"
>
{{ $t('modify') }}
</QBtn>
</div>
<div class="q-mt-md">
<div class="q-mb-xs text-grey-7">
{{ $t('category') }}
<QIcon
v-if="category"
style="font-size: 1.3em"
name="cancel"
class="cursor-pointer"
:title="$t('deleteFilter')"
@click="
$router.push({ params: { category: null } })
"
/>
</div>
<div class="categories">
<QBtn
flat
round
class="category q-pa-sm"
v-for="cat in categories"
:class="{ active: category == cat.id }"
:key="cat.id"
:title="cat.name"
:to="{ params: { category: cat.id, type: null } }"
>
<img :src="`statics/category/${cat.code}.svg`" />
</QBtn>
</div>
</div>
<div class="q-mt-md" v-if="category || search">
<div class="q-mb-xs text-grey-7">
{{ $t('filterBy') }}
</div>
<QSelect
v-model="type"
option-value="id"
option-label="name"
:options="types"
:disable="!category"
clearable
:label="$t('family')"
@filter="filterType"
@input="
$router.push({ params: { type: type && type.id } })
"
/>
<QSelect
v-model="order"
input-debounce="0"
:options="orderOptions"
:label="$t('orderBy')"
/>
</div>
</div>
<div class="q-pa-md" 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 }}
<QIcon
v-if="tag.hasFilter"
style="font-size: 1.3em"
name="cancel"
:title="$t('deleteFilter')"
class="cursor-pointer"
@click="onResetTagFilterClick(tag)"
/>
</div>
<div v-if="!tag.useRange">
<div
v-for="value in tag.values.slice(0, tag.showCount)"
:key="value"
>
<QCheckbox
v-model="tag.filter"
:dense="true"
:val="value"
:label="value"
@input="onCheck(tag)"
/>
</div>
<div v-if="tag.values.length > tag.showCount">
<span
class="cursor-pointer text-blue"
@click="tag.showCount = Infinity"
>
<QIcon name="keyboard_arrow_down" />
{{ $t('viewMore') }}
</span>
</div>
<div v-if="tag.showCount == Infinity">
<span
class="cursor-pointer text-blue"
@click="tag.showCount = tag.initialCount"
>
<QIcon name="keyboard_arrow_up" />
{{ $t('viewLess') }}
</span>
</div>
</div>
<div class="q-mx-md">
<QRange
class="q-mt-lg"
v-if="tag.useRange"
v-model="tag.filter"
:min="tag.min"
:max="tag.max"
:step="tag.step"
:color="tag.hasFilter ? 'primary' : 'grey-6'"
@input="onRangeChange(tag, true)"
@change="onRangeChange(tag)"
label-always
markers
snap
/>
</div>
</div>
</div>
</QDrawer>
<QInfiniteScroll
@load="onLoad"
scroll-taget="html"
:offset="800"
:disable="disableScroll"
>
<div class="q-pa-md row justify-center q-gutter-md">
<QSpinner v-if="isLoading" color="primary" size="50px" />
<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>
<QCard class="my-card" v-for="_item in items" :key="_item.id">
<img
:src="`${$imageBase}/catalog/200x200/${_item.image}`"
/>
<QCardSection>
<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>
<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>
</QCardSection>
<QCardActions class="actions justify-between">
<div class="q-pl-sm">
<span class="available bg-green text-white">{{
_item.available
}}</span>
{{ $t('from') }}
<span class="price">{{
currency(_item.buy?.price3)
}}</span>
</div>
<QBtn
icon="add_shopping_cart"
:title="$t('buy')"
@click="showItem(_item)"
flat
/>
</QCardActions>
</QCard>
</div>
<template #loading>
<div class="row justify-center q-my-md">
<QSpinner color="primary" name="dots" size="40px" />
</div>
</template>
</QInfiniteScroll>
<QDialog v-model="showItemDialog">
<QCard style="width: 25em">
<QImg
:src="`${$imageBase}/catalog/200x200/${item.image}`"
:ratio="5 / 3"
>
<div class="absolute-bottom text-center q-pa-xs">
<div class="text-subtitle1">
{{ item.longName }}
</div>
</div>
</QImg>
<QCardSection>
<div
class="text-uppercase text-subtitle1 text-grey-7 ellipsize"
>
{{ item.subName }}
</div>
<div class="text-grey-7">#{{ item.id }}</div>
</QCardSection>
<QCardSection>
<div v-for="tag in item.tags" :key="tag.tagFk">
<span class="text-grey-7">{{ tag.tag.name }}</span>
{{ tag.value }}
</div>
</QCardSection>
<QCardActions align="right">
<QBtn @click="showItemDialog = false" flat>
{{ $t('cancel') }}
</QBtn>
<QBtn @click="showItemDialog = false" flat>
{{ $t('accept') }}
</QBtn>
</QCardActions>
</QCard>
</QDialog>
<QPageSticky>
<QBtn
fab
to="/ecomerce/basket"
icon="shopping_cart"
color="accent"
:title="$t('shoppingCart')"
/>
</QPageSticky>
</div>
</template>
<style lang="scss" scoped>
.search {
max-width: 250px;
}
.basket-info {
background-color: #8cc63f;
color: white;
padding: 17px 28px;
border-radius: 7px;
text-align: center;
& > p {
margin: 0;
padding: 0.4em 0;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
.categories {
margin: 0 auto;
width: 220px;
.category {
width: 55px;
&.active {
background: rgba(0, 0, 0, 0.08);
}
& > img {
height: 40px;
width: 40px;
}
}
}
.tags {
max-height: 4.6em;
overflow: hidden;
}
.available {
padding: 0.15em;
border-radius: 0.2em;
font-size: 1.3em;
}
.price {
font-size: 1.3em;
}
.my-card {
width: 100%;
max-width: 17.5em;
height: 32.5em;
overflow: hidden;
.name,
.sub-name {
line-height: 1.3em;
}
.ellipsize {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.description {
height: 40px;
overflow: hidden;
}
}
</style>
<script>
import { date, currency, formatDate } from 'src/lib/filters.js';
import axios from 'axios';
import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia';
const CancelToken = axios.CancelToken;
export default {
name: 'HederaCatalog',
setup() {
const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore);
return { isHeaderMounted, appStore };
},
data() {
return {
uid: 0,
search: '',
orderDate: formatDate(new Date(), 'YYYY/MM/DD'),
category: null,
categories: [],
type: null,
typeId: null,
types: [],
orgTypes: [],
item: {},
showItemDialog: false,
tags: [],
isLoading: false,
items: null,
limit: null,
pageSize: 30,
maxTags: 5,
disableScroll: true,
viewMode: 'list',
order: {
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'
},
{
label: this.$t('priceDesc'),
value: 'price DESC'
},
{
label: this.$t('available'),
value: 'available'
}
]
};
},
created() {
this.$app.useRightDrawer = true;
},
async beforeMount() {
const isGuest = false; // TODO: Integrate isGuest logic
if (!isGuest) {
this.appStore.check('catalog');
}
},
async mounted() {
this.categories = await this.$jApi.query(
`SELECT c.id, l.name, c.color, c.code
FROM vn.itemCategory c
JOIN vn.itemCategoryL10n l ON l.id = c.id
WHERE c.display
ORDER BY display`
);
this.onRouteChange(this.$route);
},
beforeUnmount() {
this.clearTimeoutAndRequest();
},
beforeRouteUpdate(to, from, next) {
this.onRouteChange(to);
next();
},
watch: {
categories() {
this.refreshTitle();
},
orgTypes() {
this.refreshTitle();
},
order() {
this.loadItems();
},
date() {
this.loadItems();
},
async category(value) {
this.orgTypes = [];
if (!value) return;
const res = await this.$jApi.execQuery(
`CALL myOrder_getAvailable(${this.appStore.basketOrderId});
SELECT DISTINCT t.id, l.name
FROM vn.item i
JOIN vn.itemType t ON t.id = i.typeFk
JOIN tmp.itemAvailable a ON a.id = i.id
JOIN vn.itemTypeL10n l ON l.id = t.id
WHERE t.\`order\` >= 0
AND t.categoryFk = #category
ORDER BY t.\`order\`, l.name;
DROP TEMPORARY TABLE tmp.itemAvailable;`,
{ category: value }
);
res.fetch();
this.orgTypes = res.fetchData();
},
search(value) {
const location = { params: this.$route.params };
if (value) location.query = { search: value };
this.$router.push(location);
}
},
methods: {
date,
currency,
onViewModeClick() {
this.viewMode = this.viewMode === 'list' ? 'grid' : 'list';
},
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.tags = [];
this.refreshTitle();
this.loadItems();
},
refreshTitle() {
let title = this.$t(this.$router.currentRoute.value.name);
let subtitle;
if (this.category) {
const category =
this.categories.find(i => i.id === this.category) || {};
title = category.name;
}
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;
}
this.$app.$patch({ title, subtitle });
},
clearTimeoutAndRequest() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
if (this.source) {
this.source.cancel();
this.source = null;
}
},
loadItemsDelayed() {
this.clearTimeoutAndRequest();
this.timeout = setTimeout(() => this.loadItems(), 500);
},
loadItems() {
this.items = null;
this.isLoading = true;
this.limit = this.pageSize;
this.disableScroll = false;
this.isLoading = false;
// this.loadItemsBase().finally(() => (this.isLoading = false))
},
onLoad(index, done) {
if (this.isLoading) return done();
this.limit += this.pageSize;
done();
// this.loadItemsBase().finally(done)
},
loadItemsBase() {
this.clearTimeoutAndRequest();
if (!(this.category || this.typeId || this.search)) {
this.tags = [];
return Promise.resolve(true);
}
const tagFilter = [];
for (const tag of this.tags) {
if (tag.hasFilter) {
tagFilter.push({
tagFk: tag.id,
values: tag.filter
});
}
}
this.source = CancelToken.source();
const params = {
dated: this.orderDate,
typeFk: this.typeId,
categoryFk: this.category,
search: this.search,
order: this.order.value,
limit: this.limit,
tagFilter
};
const config = {
params,
cancelToken: this.source.token
};
return this.$axios
.get('Items/catalog', config)
.then(res => this.onItemsGet(res))
.catch(err => this.onItemsError(err))
.finally(() => (this.cancel = null));
},
onItemsError(err) {
if (err.__CANCEL__) return;
this.disableScroll = true;
throw err;
},
onItemsGet(res) {
for (const tag of res.data.tags) {
tag.uid = this.uid++;
if (tag.filter) {
tag.hasFilter = true;
tag.useRange = tag.filter.max || tag.filter.min;
} else {
tag.useRange =
tag.isQuantitative && tag.values.length > this.maxTags;
this.resetTagFilter(tag);
}
if (tag.values) {
tag.initialCount = this.maxTags;
if (Array.isArray(tag.filter)) {
tag.initialCount = Math.max(
tag.initialCount,
tag.filter.length
);
}
tag.showCount = tag.initialCount;
}
}
this.items = res.data.items;
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: tag.min,
max: tag.max
};
} else {
tag.filter = [];
}
},
onResetTagFilterClick(tag) {
this.resetTagFilter(tag);
this.loadItems();
},
filterType(val, update) {
if (val === '') {
update(() => {
this.types = this.orgTypes;
});
} else {
update(() => {
const needle = val.toLowerCase();
this.types = this.orgTypes.filter(
type => type.name.toLowerCase().indexOf(needle) > -1
);
});
}
},
showItem(item) {
this.item = item;
this.showItemDialog = true;
const conf = this.$state.catalogConfig;
const params = {
dated: this.orderDate,
addressFk: conf.addressFk,
agencyModeFk: conf.agencyModeFk
};
this.$axios
.get(`Items/${item.id}/calcCatalog`, { params })
.then(res => (this.lots = res.data));
}
}
};
</script>
<i18n lang="yaml">
es-ES:
gridView: Vista de rejilla
listView: Vista de lista
shoppingCart: Cesta de la compra
warehouse: Almacén
agency: Agencia
modify: Modificar
category: Categoría
deleteFilter: Quitar filtro
filterBy: Filtrar por
family: Familia
orderBy: Ordernar por
pleaseSetFilter: Elige un filtro en el menú de la derecha
search: Buscar
</i18n>

File diff suppressed because it is too large Load Diff

View File

@ -174,7 +174,6 @@ en-US:
startOrder: Start order startOrder: Start order
noOrdersFound: No orders found noOrdersFound: No orders found
makePayment: Make payment makePayment: Make payment
shoppingCart: Shopping cart
balance: 'Balance:' balance: 'Balance:'
paymentInfo: >- paymentInfo: >-
The amount shown is your slope (negative) or favorable balance today, it The amount shown is your slope (negative) or favorable balance today, it
@ -187,7 +186,6 @@ es-ES:
startOrder: Empezar pedido startOrder: Empezar pedido
noOrdersFound: No se encontrado pedidos noOrdersFound: No se encontrado pedidos
makePayment: Realizar pago makePayment: Realizar pago
shoppingCart: Cesta de la compra
balance: 'Saldo:' balance: 'Saldo:'
paymentInfo: >- paymentInfo: >-
La cantidad mostrada es tu saldo pendiente (negativa) o favorable a día de La cantidad mostrada es tu saldo pendiente (negativa) o favorable a día de
@ -201,7 +199,6 @@ ca-ES:
startOrder: Començar encàrrec startOrder: Començar encàrrec
noOrdersFound: No s'han trobat comandes noOrdersFound: No s'han trobat comandes
makePayment: Realitzar pagament makePayment: Realitzar pagament
shoppingCart: Cistella de la compra
balance: 'Saldo:' balance: 'Saldo:'
paymentInfo: >- paymentInfo: >-
La quantitat mostrada és el teu saldo pendent (negatiu) o favorable a dia La quantitat mostrada és el teu saldo pendent (negatiu) o favorable a dia
@ -215,7 +212,6 @@ fr-FR:
startOrder: Acheter startOrder: Acheter
noOrdersFound: Aucune commande trouvée noOrdersFound: Aucune commande trouvée
makePayment: Effectuer un paiement makePayment: Effectuer un paiement
shoppingCart: Panier
balance: 'Balance:' balance: 'Balance:'
paymentInfo: >- paymentInfo: >-
Le montant indiqué est votre pente (négative) ou balance favorable Le montant indiqué est votre pente (négative) ou balance favorable
@ -229,7 +225,6 @@ pt-PT:
startOrder: Iniciar encomenda startOrder: Iniciar encomenda
noOrdersFound: Nenhum pedido encontrado noOrdersFound: Nenhum pedido encontrado
makePayment: Realizar pagamento makePayment: Realizar pagamento
shoppingCart: Cesta da compra
balance: 'Saldo:' balance: 'Saldo:'
paymentInfo: >- paymentInfo: >-
A quantidade mostrada é seu saldo pendente (negativo) ou favorável a dia de A quantidade mostrada é seu saldo pendente (negativo) ou favorável a dia de

View File

@ -66,7 +66,7 @@ const deleteRow = id => {
<template> <template>
<QCard class="vn-w-sm" style="padding: 32px"> <QCard class="vn-w-sm" style="padding: 32px">
<QCardSection class="no-padding q-mb-md"> <QCardSection class="no-padding q-mb-md">
<div class="text-h6">#{{ ticket.id }}</div> <div class="text-h6 text-bold">#{{ ticket.id }}</div>
</QCardSection> </QCardSection>
<QCardSection class="no-padding q-mb-md q-gutter-y-xs"> <QCardSection class="no-padding q-mb-md q-gutter-y-xs">
<div class="text-subtitle1 text-bold"> <div class="text-subtitle1 text-bold">

View File

@ -75,7 +75,7 @@ const routes = [
meta: { meta: {
title: 'Catalog' title: 'Catalog'
}, },
component: () => import('pages/Ecomerce/Catalog.vue') component: () => import('pages/Ecomerce/CatalogView.vue')
}, },
{ {
name: 'basket', name: 'basket',