#45 feat: clean catalog view on mobile devices
This commit is contained in:
@@ -1,39 +1,62 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-footer height="40" app>
|
<div>
|
||||||
<a
|
<!-- Footer completo: visible siempre en desktop, toggle en mobile -->
|
||||||
v-for="item in items"
|
<v-footer
|
||||||
:key="item.title"
|
v-if="expanded || !isMobile"
|
||||||
:href="item.href"
|
height="40"
|
||||||
:title="item.title"
|
app
|
||||||
class="d-inline-block mx-2 social-link"
|
class="app-footer"
|
||||||
rel="noopener noreferrer"
|
:class="{ 'footer-overlay': isMobile }"
|
||||||
target="_blank"
|
|
||||||
>
|
>
|
||||||
<v-icon
|
|
||||||
:icon="item.icon"
|
|
||||||
:size="item.icon === '$vuetify' ? 24 : 16"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="text-caption text-disabled"
|
|
||||||
style="position: absolute; right: 16px;"
|
|
||||||
>
|
|
||||||
© 2016-{{ (new Date()).getFullYear() }} <span class="d-none d-sm-inline-block">Vuetify, LLC</span>
|
|
||||||
—
|
|
||||||
<a
|
<a
|
||||||
class="text-decoration-none on-surface"
|
v-for="item in items"
|
||||||
href="https://vuetifyjs.com/about/licensing/"
|
:key="item.title"
|
||||||
|
:href="item.href"
|
||||||
|
:title="item.title"
|
||||||
|
class="d-inline-block mx-2 social-link"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
MIT License
|
<v-icon
|
||||||
|
:icon="item.icon"
|
||||||
|
:size="item.icon === '$vuetify' ? 24 : 16"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
</v-footer>
|
<div
|
||||||
|
class="text-caption text-disabled"
|
||||||
|
style="position: absolute; right: 16px;"
|
||||||
|
>
|
||||||
|
© 2016-{{ (new Date()).getFullYear() }} <span class="d-none d-sm-inline-block">Vuetify, LLC</span>
|
||||||
|
—
|
||||||
|
<a
|
||||||
|
class="text-decoration-none on-surface"
|
||||||
|
href="https://vuetifyjs.com/about/licensing/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
MIT License
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</v-footer>
|
||||||
|
|
||||||
|
<!-- Botón flotante: solo en mobile -->
|
||||||
|
<v-btn
|
||||||
|
v-if="isMobile"
|
||||||
|
icon
|
||||||
|
size="small"
|
||||||
|
class="footer-toggle-btn"
|
||||||
|
:color="expanded ? 'primary' : undefined"
|
||||||
|
@click="expanded = !expanded"
|
||||||
|
>
|
||||||
|
<v-icon :icon="expanded ? 'mdi-close' : '$vuetify'" :size="expanded ? 20 : 24" />
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
title: 'Vuetify Documentation',
|
title: 'Vuetify Documentation',
|
||||||
@@ -65,15 +88,48 @@
|
|||||||
icon: `mdi-reddit`,
|
icon: `mdi-reddit`,
|
||||||
href: 'https://reddit.com/r/vuetifyjs',
|
href: 'https://reddit.com/r/vuetifyjs',
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
|
const expanded = ref(false);
|
||||||
|
const windowWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1200);
|
||||||
|
|
||||||
|
const isMobile = computed(() => windowWidth.value < 960);
|
||||||
|
|
||||||
|
const onResize = () => {
|
||||||
|
windowWidth.value = window.innerWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', onResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', onResize);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="sass">
|
<style scoped lang="sass">
|
||||||
.social-link :deep(.v-icon)
|
.social-link :deep(.v-icon)
|
||||||
color: rgba(var(--v-theme-on-background), var(--v-disabled-opacity))
|
color: rgba(var(--v-theme-on-background), var(--v-disabled-opacity))
|
||||||
text-decoration: none
|
text-decoration: none
|
||||||
transition: .2s ease-in-out
|
transition: .2s ease-in-out
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
color: rgba(25, 118, 210, 1)
|
color: rgba(25, 118, 210, 1)
|
||||||
|
|
||||||
|
/* Botón flotante para mobile */
|
||||||
|
.footer-toggle-btn
|
||||||
|
position: fixed
|
||||||
|
bottom: 12px
|
||||||
|
right: 12px
|
||||||
|
z-index: 2000
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25)
|
||||||
|
|
||||||
|
/* Overlay del footer en mobile */
|
||||||
|
.footer-overlay
|
||||||
|
position: fixed !important
|
||||||
|
bottom: 0
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
z-index: 1999
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-dialog v-model="show" max-width="400">
|
<v-dialog v-model="show" max-width="400">
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title class="headline">Iniciar sesión</v-card-title>
|
<v-card-title class="headline d-flex align-center">
|
||||||
|
Iniciar sesión
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn icon size="small" variant="text" @click="show = false">
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-form ref="form" @submit.prevent="onSubmit">
|
<v-form ref="form" @submit.prevent="onSubmit">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
|
|||||||
@@ -63,52 +63,64 @@
|
|||||||
|
|
||||||
<!-- Footer con Controles de Cantidad -->
|
<!-- Footer con Controles de Cantidad -->
|
||||||
<v-card-actions class="product-actions pa-2 pb-3 justify-center">
|
<v-card-actions class="product-actions pa-2 pb-3 justify-center">
|
||||||
<div class="quantity-controls">
|
<template v-if="disabled">
|
||||||
<v-btn
|
<v-btn
|
||||||
icon
|
color="primary"
|
||||||
size="small"
|
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
color="error"
|
prepend-icon="mdi-cart-plus"
|
||||||
class="qty-btn"
|
class="login-add-btn"
|
||||||
@click="decrease(product)"
|
@click="$emit('request-login')"
|
||||||
:disabled="disabled || product.quantity === 0"
|
|
||||||
>
|
>
|
||||||
<v-icon size="20">mdi-minus</v-icon>
|
Agregar
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="quantity-controls">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
color="error"
|
||||||
|
class="qty-btn"
|
||||||
|
@click="decrease(product)"
|
||||||
|
:disabled="product.quantity === 0"
|
||||||
|
>
|
||||||
|
<v-icon size="20">mdi-minus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model.number="product.quantity"
|
v-model.number="product.quantity"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
class="quantity-input mx-1"
|
class="quantity-input mx-1"
|
||||||
variant="solo-filled"
|
variant="solo-filled"
|
||||||
density="compact"
|
density="compact"
|
||||||
hide-details
|
hide-details
|
||||||
single-line
|
single-line
|
||||||
flat
|
flat
|
||||||
aria-label="Cantidad"
|
aria-label="Cantidad"
|
||||||
:disabled="disabled"
|
@input="handleQuantityChange"
|
||||||
@input="handleQuantityChange"
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
icon
|
icon
|
||||||
size="small"
|
size="small"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
color="success"
|
color="success"
|
||||||
class="qty-btn"
|
class="qty-btn"
|
||||||
@click="handleIncrease"
|
@click="handleIncrease"
|
||||||
:disabled="disabled"
|
>
|
||||||
>
|
<v-icon size="20">mdi-plus</v-icon>
|
||||||
<v-icon size="20">mdi-plus</v-icon>
|
</v-btn>
|
||||||
</v-btn>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
emits: ['add-to-cart', 'request-login'],
|
||||||
props: {
|
props: {
|
||||||
product: {
|
product: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -312,6 +324,13 @@ export default {
|
|||||||
RESPONSIVE BREAKPOINTS
|
RESPONSIVE BREAKPOINTS
|
||||||
============================================ */
|
============================================ */
|
||||||
|
|
||||||
|
/* Botón de agregar (no autenticado) */
|
||||||
|
.login-add-btn {
|
||||||
|
width: 100%;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Móvil pequeño (< 375px) */
|
/* Móvil pequeño (< 375px) */
|
||||||
@media (max-width: 374px) {
|
@media (max-width: 374px) {
|
||||||
.product-content {
|
.product-content {
|
||||||
|
|||||||
@@ -49,20 +49,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
|
|
||||||
<!-- Controles de paginación superiores -->
|
|
||||||
<PaginationControls
|
|
||||||
v-if="filteredItems.length > 0"
|
|
||||||
:current-page="currentPage"
|
|
||||||
:total-pages="totalPages"
|
|
||||||
:items-per-page="itemsPerPage"
|
|
||||||
:items-per-page-options="itemsPerPageOptions"
|
|
||||||
:pagination-info="paginationInfo"
|
|
||||||
:total-visible-pages="totalVisiblePages"
|
|
||||||
@page-change="handlePageChange"
|
|
||||||
@items-per-page-change="handleItemsPerPageChange"
|
|
||||||
position="top"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Grid de productos paginados -->
|
<!-- Grid de productos paginados -->
|
||||||
<v-row class="product-grid" v-if="paginatedItems.length > 0">
|
<v-row class="product-grid" v-if="paginatedItems.length > 0">
|
||||||
<v-col
|
<v-col
|
||||||
@@ -82,6 +68,7 @@
|
|||||||
:updateQuantity="updateQuantity"
|
:updateQuantity="updateQuantity"
|
||||||
:disabled="!isAuthenticated"
|
:disabled="!isAuthenticated"
|
||||||
@add-to-cart="addToCart"
|
@add-to-cart="addToCart"
|
||||||
|
@request-login="showLogin"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -136,7 +123,16 @@
|
|||||||
@update-quantity="updateCartQuantity"
|
@update-quantity="updateCartQuantity"
|
||||||
@toggle-collapse="toggleCart"
|
@toggle-collapse="toggleCart"
|
||||||
/>
|
/>
|
||||||
<v-card v-else class="login-prompt-card pa-4 text-center">
|
<v-card v-else-if="!isMobile || showLoginPrompt" class="login-prompt-card pa-4 text-center">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
size="x-small"
|
||||||
|
variant="text"
|
||||||
|
class="login-prompt-close"
|
||||||
|
@click="showLoginPrompt = false"
|
||||||
|
>
|
||||||
|
<v-icon size="18">mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
<v-icon size="48" color="primary" class="mb-2">mdi-cart-lock</v-icon>
|
<v-icon size="48" color="primary" class="mb-2">mdi-cart-lock</v-icon>
|
||||||
<p class="text-body-2 text-medium-emphasis mb-3">
|
<p class="text-body-2 text-medium-emphasis mb-3">
|
||||||
Inicia sesión para agregar productos al carrito y finalizar tu compra
|
Inicia sesión para agregar productos al carrito y finalizar tu compra
|
||||||
@@ -274,7 +270,8 @@ export default {
|
|||||||
setup() {
|
setup() {
|
||||||
const cartStore = useCartStore();
|
const cartStore = useCartStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const cartCollapsed = ref(false); // Cambiado a false para que inicie expandido en desktop
|
const cartCollapsed = ref(false);
|
||||||
|
const showLoginPrompt = ref(false);
|
||||||
const windowWidth = ref(window.innerWidth);
|
const windowWidth = ref(window.innerWidth);
|
||||||
|
|
||||||
const isMobile = computed(() => windowWidth.value < 960); // Cambiado de 680 a 960
|
const isMobile = computed(() => windowWidth.value < 960); // Cambiado de 680 a 960
|
||||||
@@ -295,6 +292,7 @@ export default {
|
|||||||
cartStore,
|
cartStore,
|
||||||
authStore,
|
authStore,
|
||||||
cartCollapsed,
|
cartCollapsed,
|
||||||
|
showLoginPrompt,
|
||||||
isMobile,
|
isMobile,
|
||||||
windowWidth,
|
windowWidth,
|
||||||
};
|
};
|
||||||
@@ -553,7 +551,14 @@ export default {
|
|||||||
openLoginDialog() {
|
openLoginDialog() {
|
||||||
this.$refs.loginDialogRef.open();
|
this.$refs.loginDialogRef.open();
|
||||||
},
|
},
|
||||||
|
showLogin() {
|
||||||
|
this.showLoginPrompt = true;
|
||||||
|
if (this.isMobile) {
|
||||||
|
this.cartCollapsed = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
onLoginSuccess() {
|
onLoginSuccess() {
|
||||||
|
this.showLoginPrompt = false;
|
||||||
this.api.getCurrentUser().then((user) => {
|
this.api.getCurrentUser().then((user) => {
|
||||||
this.authStore.setUser(user);
|
this.authStore.setUser(user);
|
||||||
});
|
});
|
||||||
@@ -769,6 +774,13 @@ export default {
|
|||||||
.login-prompt-card {
|
.login-prompt-card {
|
||||||
border: 2px dashed rgba(0, 0, 0, 0.12);
|
border: 2px dashed rgba(0, 0, 0, 0.12);
|
||||||
background: #fafafa !important;
|
background: #fafafa !important;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-prompt-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
|
|||||||
Reference in New Issue
Block a user