Catalog view #87
|
@ -28,13 +28,9 @@ const props = defineProps({
|
|||
type: Number,
|
||||
required: true
|
||||
},
|
||||
rounded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
fullRounded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
roundedBorders: {
|
||||
type: String,
|
||||
default: 'none'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
|
@ -70,6 +66,17 @@ const showEditForm = ref(false);
|
|||
const url = computed(() => {
|
||||
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>
|
||||
<template>
|
||||
<div class="relative-position main-image-container">
|
||||
|
@ -85,11 +92,7 @@ const url = computed(() => {
|
|||
<QTooltip>{{ t('addOrEditImage') }}</QTooltip>
|
||||
</QBtn>
|
||||
<QImg
|
||||
:class="{
|
||||
zoomIn: props.zoomSize,
|
||||
rounded: props.rounded,
|
||||
'full-rounded': props.fullRounded
|
||||
}"
|
||||
:class="[rounded, { zoomIn: props.zoomSize }]"
|
||||
class="main-image"
|
||||
:src="url"
|
||||
v-bind="$attrs"
|
||||
|
@ -161,9 +164,19 @@ const url = computed(() => {
|
|||
.rounded {
|
||||
border-radius: 0.6em;
|
||||
}
|
||||
|
||||
.full-rounded {
|
||||
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 {
|
||||
border-radius: 0%;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ const props = defineProps({
|
|||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'Search'
|
||||
default: ''
|
||||
},
|
||||
sqlQuery: {
|
||||
type: String,
|
||||
|
|
|
@ -103,6 +103,9 @@ export default {
|
|||
user: 'Usuari',
|
||||
password: 'Contrasenya',
|
||||
modify: 'Modificar',
|
||||
shoppingCart: 'Cistella de la compra',
|
||||
available: 'Disponible',
|
||||
minQuantity: 'Quantitat mínima',
|
||||
// Image related translations
|
||||
'Cant lock cache': 'No es pot bloquejar la memòria cau',
|
||||
'Bad file format': 'Format de fitxer no reconegut',
|
||||
|
|
|
@ -136,6 +136,9 @@ export default {
|
|||
remindMe: 'Remember me',
|
||||
password: 'Password',
|
||||
modify: 'Modify',
|
||||
shoppingCart: 'Shopping cart',
|
||||
available: 'Available',
|
||||
minQuantity: 'Minimum quantity',
|
||||
// Image related translations
|
||||
'Cant lock cache': 'The cache could not be blocked',
|
||||
'Bad file format': 'Unrecognized file format',
|
||||
|
|
|
@ -135,6 +135,9 @@ export default {
|
|||
cancel: 'Cancelar',
|
||||
of: 'de',
|
||||
modify: 'Modificar',
|
||||
shoppingCart: 'Cesta de la compra',
|
||||
available: 'Disponible',
|
||||
minQuantity: 'Cantidad mínima',
|
||||
// Image related translations
|
||||
'Cant lock cache': 'La caché no pudo ser bloqueada',
|
||||
'Bad file format': 'Formato de archivo no reconocido',
|
||||
|
|
|
@ -103,6 +103,9 @@ export default {
|
|||
user: 'Utilisateur',
|
||||
password: 'Mot de passe',
|
||||
modify: 'Modifier',
|
||||
shoppingCart: 'Panier',
|
||||
available: 'Disponible',
|
||||
minQuantity: 'Quantité minimum',
|
||||
// Image related translations
|
||||
'Cant lock cache': "Le cache n'a pas pu être verrouillé",
|
||||
'Bad file format': 'Format de fichier non reconnu',
|
||||
|
|
|
@ -101,6 +101,9 @@ export default {
|
|||
user: 'Utilizador',
|
||||
password: 'Senha',
|
||||
modify: 'Modificar',
|
||||
shoppingCart: 'Cesta da compra',
|
||||
available: 'Disponível',
|
||||
minQuantity: 'Quantidade mínima',
|
||||
// Image related translations
|
||||
'Cant lock cache': 'O cache não pôde ser bloqueado',
|
||||
'Bad file format': 'Formato de arquivo inválido',
|
||||
|
|
|
@ -104,10 +104,7 @@ onMounted(async () => {
|
|||
</QBtn>
|
||||
</Teleport>
|
||||
<QPage class="vn-w-sm">
|
||||
<QList
|
||||
class="rounded-borders shadow-1 shadow-transition"
|
||||
separator
|
||||
>
|
||||
<QList class="rounded-borders shadow-1 shadow-transition" separator>
|
||||
<CardList
|
||||
v-for="(address, index) in addresses"
|
||||
:key="index"
|
||||
|
|
|
@ -33,9 +33,9 @@ const onSearch = data => (items.value = data || []);
|
|||
<template>
|
||||
<Teleport v-if="isHeaderMounted" to="#actions">
|
||||
<VnSearchBar
|
||||
:sqlQuery="query"
|
||||
@onSearch="onSearch"
|
||||
@onSearchError="items = []"
|
||||
:sql-query="query"
|
||||
@on-search="onSearch"
|
||||
@on-search-error="items = []"
|
||||
/>
|
||||
</Teleport>
|
||||
<QPage class="vn-w-xs">
|
||||
|
@ -66,8 +66,8 @@ const onSearch = data => (items.value = data || []);
|
|||
class="q-mr-md"
|
||||
rounded
|
||||
editable
|
||||
editSchema="catalog"
|
||||
:editImageName="item.image"
|
||||
edit-schema="catalog"
|
||||
:edit-image-name="item.image"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
|
|
|
@ -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>
|
|
@ -0,0 +1,207 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
import CardList from 'src/components/ui/CardList.vue';
|
||||
|
||||
import { currency } from 'src/lib/filters.js';
|
||||
|
||||
defineProps({
|
||||
item: { type: Object, default: () => {} },
|
||||
viewMode: { type: String, default: 'grid' }
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QCard v-if="viewMode === 'grid'" v-ripple class="catalog-card">
|
||||
<VnImg
|
||||
storage="catalog"
|
||||
size="200x200"
|
||||
:id="item.image"
|
||||
height="210px"
|
||||
rounded="bottom"
|
||||
/>
|
||||
<div class="column" style="height: 205px; padding: 10px">
|
||||
<div class="column" style="margin-bottom: auto">
|
||||
<div class="text-subtitle2 ellipsis-2-lines">
|
||||
{{ item.item }}
|
||||
</div>
|
||||
<div
|
||||
class="row justify-between text-uppercase text-subtitle1 text-grey-7"
|
||||
>
|
||||
<span class="text-subtitle2">
|
||||
{{ item.subName }}
|
||||
</span>
|
||||
<span> #{{ item.id }}</span>
|
||||
</div>
|
||||
<div class="tags q-pt-xs text-caption">
|
||||
<div
|
||||
v-for="(tag, index) in item.previewTags"
|
||||
|
||||
:key="index"
|
||||
class="full-width row"
|
||||
>
|
||||
<span class="text-grey-7 col">
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
<span class="col ellipsis">{{ tag.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.minQuantity" class="row justify-end">
|
||||
<QIcon
|
||||
name="production_quantity_limits"
|
||||
size="xs"
|
||||
color="negative"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('minQuantity') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<span class="text-negative text-caption">{{
|
||||
item.minQuantity
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-between items-cente q-gutter-x-xs">
|
||||
<QBadge
|
||||
:label="`x${item.grouping}`"
|
||||
color="grey"
|
||||
class="col-2 justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('minGrouping') }}
|
||||
</QTooltip>
|
||||
</QBadge>
|
||||
<QBadge
|
||||
outline
|
||||
:label="item.available"
|
||||
color="accent"
|
||||
text-color="black"
|
||||
class="col justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('available') }}
|
||||
</QTooltip>
|
||||
</QBadge>
|
||||
<QBadge
|
||||
outline
|
||||
:label="currency(item.price)"
|
||||
color="accent"
|
||||
text-color="black"
|
||||
class="col justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('groupingPrice') }}
|
||||
</QTooltip>
|
||||
</QBadge>
|
||||
</div>
|
||||
</div>
|
||||
</QCard>
|
||||
<CardList v-else class="vn-w-sm">
|
||||
<template #prepend>
|
||||
<VnImg
|
||||
storage="catalog"
|
||||
size="200x200"
|
||||
:id="item.image"
|
||||
width="105px"
|
||||
height="105px"
|
||||
rounded-borders="full"
|
||||
class="q-mr-md"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<span class="ellipsis-2-lines">
|
||||
{{ item.item }}
|
||||
</span>
|
||||
<div class="row justify-between text-uppercase text-grey-7">
|
||||
<span>{{ item.subName }}</span>
|
||||
<span>#{{ item.id }}</span>
|
||||
</div>
|
||||
<div class="full-width row">
|
||||
<span
|
||||
v-for="(tag, index) in item.previewTags"
|
||||
:key="index"
|
||||
class="text-grey-7 text-caption q-mr-sm"
|
||||
>
|
||||
{{ tag.value }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="item.minQuantity" class="row justify-end">
|
||||
<QIcon
|
||||
name="production_quantity_limits"
|
||||
size="xs"
|
||||
color="negative"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('minQuantity') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<span class="text-negative text-caption">{{
|
||||
item.minQuantity
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="row justify-end items-center q-gutter-x-xs q-mt-sm">
|
||||
<QBadge
|
||||
:label="`x${item.grouping}`"
|
||||
color="grey"
|
||||
class="col-2 justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('minGrouping') }}
|
||||
</QTooltip>
|
||||
</QBadge>
|
||||
<QBadge
|
||||
outline
|
||||
:label="item.available"
|
||||
color="accent"
|
||||
text-color="black"
|
||||
class="col-3 justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('available') }}
|
||||
</QTooltip>
|
||||
</QBadge>
|
||||
<QBadge
|
||||
outline
|
||||
:label="currency(item.price)"
|
||||
color="accent"
|
||||
text-color="black"
|
||||
class="col-3 justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('groupingPrice') }}
|
||||
</QTooltip>
|
||||
</QBadge>
|
||||
</div>
|
||||
</template>
|
||||
</CardList>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.catalog-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 210px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n lang="yaml">
|
||||
en-US:
|
||||
groupingPrice: Price per group
|
||||
minGrouping: Minimum packing
|
||||
es-ES:
|
||||
groupingPrice: Precio por grupo
|
||||
minGrouping: Cantidad mínima
|
||||
ca-ES:
|
||||
groupingPrice: Preu per grup
|
||||
minGrouping: Empaquetament mínim
|
||||
fr-FR:
|
||||
groupingPrice: Prix par groupe
|
||||
minGrouping: Emballage minimum
|
||||
pt-PT:
|
||||
groupingPrice: Preço por grupo
|
||||
minGrouping: Embalagem mínima
|
||||
</i18n>
|
File diff suppressed because it is too large
Load Diff
|
@ -174,7 +174,6 @@ en-US:
|
|||
startOrder: Start order
|
||||
noOrdersFound: No orders found
|
||||
makePayment: Make payment
|
||||
shoppingCart: Shopping cart
|
||||
balance: 'Balance:'
|
||||
paymentInfo: >-
|
||||
The amount shown is your slope (negative) or favorable balance today, it
|
||||
|
@ -187,7 +186,6 @@ es-ES:
|
|||
startOrder: Empezar pedido
|
||||
noOrdersFound: No se encontrado pedidos
|
||||
makePayment: Realizar pago
|
||||
shoppingCart: Cesta de la compra
|
||||
balance: 'Saldo:'
|
||||
paymentInfo: >-
|
||||
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
|
||||
noOrdersFound: No s'han trobat comandes
|
||||
makePayment: Realitzar pagament
|
||||
shoppingCart: Cistella de la compra
|
||||
balance: 'Saldo:'
|
||||
paymentInfo: >-
|
||||
La quantitat mostrada és el teu saldo pendent (negatiu) o favorable a dia
|
||||
|
@ -215,7 +212,6 @@ fr-FR:
|
|||
startOrder: Acheter
|
||||
noOrdersFound: Aucune commande trouvée
|
||||
makePayment: Effectuer un paiement
|
||||
shoppingCart: Panier
|
||||
balance: 'Balance:'
|
||||
paymentInfo: >-
|
||||
Le montant indiqué est votre pente (négative) ou balance favorable
|
||||
|
@ -229,7 +225,6 @@ pt-PT:
|
|||
startOrder: Iniciar encomenda
|
||||
noOrdersFound: Nenhum pedido encontrado
|
||||
makePayment: Realizar pagamento
|
||||
shoppingCart: Cesta da compra
|
||||
balance: 'Saldo:'
|
||||
paymentInfo: >-
|
||||
A quantidade mostrada é seu saldo pendente (negativo) ou favorável a dia de
|
||||
|
|
|
@ -66,7 +66,7 @@ const deleteRow = id => {
|
|||
<template>
|
||||
<QCard class="vn-w-sm" style="padding: 32px">
|
||||
<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 class="no-padding q-mb-md q-gutter-y-xs">
|
||||
<div class="text-subtitle1 text-bold">
|
||||
|
|
|
@ -41,12 +41,23 @@ onMounted(() => {
|
|||
password.value.focus();
|
||||
}
|
||||
});
|
||||
async function onLogin() {
|
||||
await userStore.login(email.value, password.value, remember.value);
|
||||
|
||||
const onLogin = async () => {
|
||||
await userStore.fetchUser();
|
||||
await userStore.updateUserLang(selectedLocaleValue.value);
|
||||
await router.push('/');
|
||||
}
|
||||
};
|
||||
|
||||
const login = async () => {
|
||||
await userStore.login(email.value, password.value, remember.value);
|
||||
await onLogin();
|
||||
};
|
||||
|
||||
const loginAsGuest = async () => {
|
||||
userStore.isGuest = true;
|
||||
localStorage.setItem('hederaGuest', true);
|
||||
await onLogin();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -56,7 +67,7 @@ async function onLogin() {
|
|||
<img src="statics/logo.svg" alt="Verdnatura" class="block" />
|
||||
</router-link>
|
||||
</div>
|
||||
<QForm @submit="onLogin" class="q-gutter-y-md">
|
||||
<QForm @submit="login()" class="q-gutter-y-md">
|
||||
<div class="q-gutter-y-sm">
|
||||
<QInput v-model="email" :label="$t('user')" autofocus />
|
||||
<QInput
|
||||
|
@ -106,7 +117,7 @@ async function onLogin() {
|
|||
</div>
|
||||
<div class="justify-center">
|
||||
<QBtn
|
||||
to="/"
|
||||
@click="loginAsGuest()"
|
||||
:label="$t('logInAsGuest')"
|
||||
class="full-width"
|
||||
color="primary"
|
||||
|
|
|
@ -75,7 +75,7 @@ const routes = [
|
|||
meta: {
|
||||
title: 'Catalog'
|
||||
},
|
||||
component: () => import('pages/Ecomerce/Catalog.vue')
|
||||
component: () => import('pages/Ecomerce/CatalogView.vue')
|
||||
},
|
||||
{
|
||||
name: 'basket',
|
||||
|
|
|
@ -139,6 +139,11 @@ export const useAppStore = defineStore('hedera', {
|
|||
unloadOrder() {
|
||||
localStorage.removeItem(storageOrderName);
|
||||
this.basketOrderId = null;
|
||||
},
|
||||
|
||||
onLogout() {
|
||||
this.unloadOrder();
|
||||
this.menuEssentialLinks = [];
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { defineStore } from 'pinia';
|
|||
import { api, jApi } from 'boot/axios';
|
||||
import { i18n } from 'src/boot/i18n';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import { useAppStore } from 'src/stores/app.js';
|
||||
|
||||
const { t } = i18n.global;
|
||||
const { notify } = useNotify();
|
||||
|
@ -35,6 +36,7 @@ export const useUserStore = defineStore('user', {
|
|||
|
||||
actions: {
|
||||
async init() {
|
||||
this.isGuest = localStorage.getItem('hederaGuest') || false;
|
||||
await this.fetchUser();
|
||||
await this.supplantInit();
|
||||
this.updateSiteLocale();
|
||||
|
@ -71,6 +73,8 @@ export const useUserStore = defineStore('user', {
|
|||
sessionStorage.removeItem('vnToken');
|
||||
}
|
||||
this.$reset();
|
||||
localStorage.removeItem('hederaGuest');
|
||||
useAppStore().onLogout();
|
||||
},
|
||||
|
||||
async fetchUser(userType = 'user') {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
export default function debounce(callback, delay) {
|
||||
let timeoutId;
|
||||
return (...args) => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
// eslint-disable-next-line
|
||||
callback(...args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Echo en falta el carro en rojo que representa la cantidad mínima
x1, representa el empaquetado mínimo. Esta traducción no está presente en el componente vue
minQuantity handleado.
Commit:
0f5014088d