- Fix cart overlay blocking header in mobile (z-index: header 900 < cart 1000) - Add cart-is-collapsed class with translateY(calc(100% - 60px)) for bottom sheet behavior - Ensure cart header remains visible and clickable when collapsed in mobile - Add deep Vuetify styles for search field integration (:deep(.v-field)) - Preserve desktop sticky sidebar behavior (position: sticky, overflow-y: auto) - Make entire cart header clickable in mobile (@click on v-card-title) - Add visual feedback with chevron icons (mdi-chevron-up/down) - Clean CSS organization with section comments
603 lines
13 KiB
Vue
603 lines
13 KiB
Vue
<template>
|
|
<v-card :class="{
|
|
'cart-mobile-expanded': isMobile && !isCollapsed,
|
|
'cart-collapsed-mobile': isMobile && isCollapsed,
|
|
'cart-desktop-collapsed': !isMobile && isCollapsed
|
|
}">
|
|
<v-card-title
|
|
class="d-flex align-center cart-title"
|
|
:class="{ 'cart-header-mobile': isMobile, 'cart-header-desktop': !isMobile }"
|
|
@click="isMobile && $emit('toggle-collapse')">
|
|
<!-- Icono del carrito - SIEMPRE VISIBLE -->
|
|
<v-icon class="mr-2">mdi-cart</v-icon>
|
|
|
|
<!-- Texto "Carrito" - SIEMPRE VISIBLE -->
|
|
<span class="cart-title-text">Carrito</span>
|
|
|
|
<!-- Cantidad de productos - SIEMPRE VISIBLE -->
|
|
<v-chip
|
|
v-if="cartCount > 0"
|
|
color="primary"
|
|
class="ml-2"
|
|
size="small"
|
|
>
|
|
{{ cartCount }}
|
|
</v-chip>
|
|
<v-chip
|
|
v-else
|
|
color="grey"
|
|
class="ml-2"
|
|
size="small"
|
|
variant="outlined"
|
|
>
|
|
0
|
|
</v-chip>
|
|
|
|
<!-- Total visible cuando está colapsado (mobile o desktop) -->
|
|
<span
|
|
v-if="isCollapsed && cartItems.length > 0"
|
|
class="ml-auto text-subtitle-1 font-weight-bold mr-2"
|
|
>
|
|
{{ currency(cartTotal) }}
|
|
</span>
|
|
<v-spacer v-else></v-spacer>
|
|
|
|
<!-- Botón toggle SIEMPRE visible (mobile y desktop) -->
|
|
<v-btn
|
|
icon
|
|
size="small"
|
|
variant="text"
|
|
@click="$emit('toggle-collapse')"
|
|
:title="isCollapsed ? 'Expandir carrito' : 'Contraer carrito'"
|
|
>
|
|
<v-icon>{{ isCollapsed ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
|
</v-btn>
|
|
</v-card-title>
|
|
|
|
<div v-show="!isCollapsed">
|
|
<v-divider></v-divider>
|
|
|
|
<v-card-text v-if="cartItems.length === 0" class="text-center grey--text">
|
|
El carrito está vacío
|
|
</v-card-text>
|
|
|
|
<v-list v-else density="compact" :max-height="listMaxHeight" class="overflow-y-auto cart-list">
|
|
<v-list-item v-for="(item, index) in cartItems" :key="item.id" class="cart-list-item">
|
|
<template v-slot:prepend>
|
|
<div class="prepend-wrapper">
|
|
<!-- Mostrar imagen solo si NO es extra small -->
|
|
<v-avatar v-if="!isExtraSmall" size="40" rounded>
|
|
<v-img :src="item.img" cover></v-img>
|
|
</v-avatar>
|
|
<!-- Mostrar número cuando es extra small -->
|
|
<div v-else class="item-number">
|
|
{{ index + 1 }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Contenido reorganizado -->
|
|
<div class="cart-item-content">
|
|
<!-- Línea 1: Nombre del producto -->
|
|
<div class="product-name">{{ item.name }}</div>
|
|
|
|
<!-- Línea 2: Controles + Precio unitario -->
|
|
<div class="controls-row">
|
|
<div class="quantity-controls">
|
|
<v-btn small text class="qty-btn" @click="decreaseQuantity(item.id)">
|
|
<v-icon small>mdi-minus</v-icon>
|
|
</v-btn>
|
|
<v-text-field
|
|
:model-value="item.quantity"
|
|
@update:model-value="updateQuantity(item.id, $event)"
|
|
type="number"
|
|
min="1"
|
|
density="compact"
|
|
variant="outlined"
|
|
hide-details
|
|
class="qty-input"
|
|
/>
|
|
<v-btn small text class="qty-btn" @click="increaseQuantity(item.id)">
|
|
<v-icon small>mdi-plus</v-icon>
|
|
</v-btn>
|
|
</div>
|
|
<span class="unit-price">x {{ currency(item.price) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Append: Total + Delete -->
|
|
<template v-slot:append>
|
|
<div class="item-actions">
|
|
<strong class="total-price">{{ currency(item.price * item.quantity) }}</strong>
|
|
<v-btn
|
|
icon="mdi-delete"
|
|
size="small"
|
|
variant="text"
|
|
color="error"
|
|
class="delete-btn"
|
|
@click="$emit('remove', item.id)"
|
|
></v-btn>
|
|
</div>
|
|
</template>
|
|
</v-list-item>
|
|
</v-list>
|
|
|
|
<v-divider v-if="cartItems.length > 0"></v-divider>
|
|
|
|
<v-card-text v-if="cartItems.length > 0" class="cart-total-section">
|
|
<div class="d-flex justify-space-between align-center">
|
|
<strong>Total:</strong>
|
|
<strong class="text-h6">{{ currency(cartTotal) }}</strong>
|
|
</div>
|
|
</v-card-text>
|
|
|
|
<v-card-actions v-if="cartItems.length > 0" class="cart-checkout-section">
|
|
<v-btn color="primary" block @click="$emit('checkout')">
|
|
Finalizar Compra
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</div>
|
|
</v-card>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'Cart',
|
|
props: {
|
|
cartItems: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
currency: {
|
|
type: Function,
|
|
required: true
|
|
},
|
|
isCollapsed: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
isMobile: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
windowWidth: {
|
|
type: Number,
|
|
default: 0
|
|
}
|
|
},
|
|
emits: ['remove', 'checkout', 'update-quantity', 'toggle-collapse'],
|
|
computed: {
|
|
cartCount() {
|
|
return this.cartItems.reduce((sum, item) => sum + item.quantity, 0);
|
|
},
|
|
cartTotal() {
|
|
return this.cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
|
},
|
|
listMaxHeight() {
|
|
// En desktop, permitir más altura para scroll
|
|
if (!this.isMobile) {
|
|
return '500px';
|
|
}
|
|
// En mobile, altura limitada
|
|
return '300px';
|
|
},
|
|
isExtraSmall() {
|
|
// Detectar si la resolución es menor a 560px
|
|
return this.windowWidth < 560;
|
|
}
|
|
},
|
|
methods: {
|
|
updateQuantity(itemId, newQuantity) {
|
|
const qty = parseInt(newQuantity) || 1;
|
|
this.$emit('update-quantity', { itemId, quantity: qty });
|
|
},
|
|
increaseQuantity(itemId) {
|
|
const item = this.cartItems.find(i => i.id === itemId);
|
|
if (item) {
|
|
this.$emit('update-quantity', { itemId, quantity: item.quantity + 1 });
|
|
}
|
|
},
|
|
decreaseQuantity(itemId) {
|
|
const item = this.cartItems.find(i => i.id === itemId);
|
|
if (item && item.quantity > 1) {
|
|
this.$emit('update-quantity', { itemId, quantity: item.quantity - 1 });
|
|
} else if (item) {
|
|
this.$emit('remove', itemId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* === ESTILOS BASE PARA CART ITEMS (Mobile First) === */
|
|
|
|
/* Contenedor principal del item */
|
|
.cart-item-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
flex: 1;
|
|
min-width: 0; /* Permite que el flex funcione con overflow */
|
|
}
|
|
|
|
/* Nombre del producto */
|
|
.product-name {
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
line-height: 1.3;
|
|
color: rgba(0, 0, 0, 0.87);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2; /* Máximo 2 líneas */
|
|
-webkit-box-orient: vertical;
|
|
word-break: break-word;
|
|
}
|
|
|
|
/* Fila de controles + precio unitario */
|
|
.controls-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
/* Controles de cantidad */
|
|
.quantity-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Input de cantidad */
|
|
.qty-input {
|
|
width: 55px;
|
|
min-width: 55px;
|
|
}
|
|
|
|
.qty-input input {
|
|
text-align: center;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Botones de cantidad */
|
|
.qty-btn {
|
|
min-width: 24px !important;
|
|
width: 24px !important;
|
|
height: 24px !important;
|
|
border-radius: 12px !important;
|
|
padding: 0 !important;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.qty-btn .v-icon {
|
|
font-size: 14px !important;
|
|
}
|
|
|
|
/* Precio unitario */
|
|
.unit-price {
|
|
font-size: 0.8rem;
|
|
color: rgba(0, 0, 0, 0.6);
|
|
font-weight: 400;
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Contenedor de acciones (total + delete) */
|
|
.item-actions {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
justify-content: flex-start;
|
|
gap: 4px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Precio total */
|
|
.total-price {
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
color: #2e7d32;
|
|
white-space: nowrap;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
/* Botón de eliminar */
|
|
.delete-btn {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Footer: Total y checkout */
|
|
.cart-total-section {
|
|
padding: 12px 16px !important;
|
|
}
|
|
|
|
.cart-checkout-section {
|
|
padding: 4px 16px 12px !important;
|
|
}
|
|
|
|
/* Ajustes al list-item */
|
|
.cart-list-item {
|
|
padding: 10px 8px !important;
|
|
min-height: auto !important;
|
|
align-items: center !important;
|
|
}
|
|
|
|
/* Wrapper del prepend para controlar gap con content */
|
|
.prepend-wrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-right: 10px;
|
|
}
|
|
|
|
/* Estilos para mobile expandido */
|
|
.cart-mobile-expanded {
|
|
max-height: 70vh;
|
|
overflow-y: auto;
|
|
border-radius: 16px 16px 0 0 !important;
|
|
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15) !important;
|
|
}
|
|
|
|
/* Estilos para mobile colapsado */
|
|
.cart-collapsed-mobile {
|
|
height: 60px;
|
|
overflow: hidden;
|
|
border-radius: 16px 16px 0 0 !important;
|
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1) !important;
|
|
}
|
|
|
|
/* Estilos para desktop colapsado */
|
|
.cart-desktop-collapsed {
|
|
height: 60px;
|
|
overflow: hidden;
|
|
border-radius: 12px !important;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
|
|
transition: all 0.3s ease-in-out;
|
|
}
|
|
|
|
/* Header del carrito */
|
|
.cart-title {
|
|
padding: 8px 12px !important;
|
|
min-height: 48px;
|
|
}
|
|
|
|
.cart-title-text {
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Header interactivo en mobile */
|
|
.cart-header-mobile {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
/* Header interactivo en desktop */
|
|
.cart-header-desktop {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
background-color: rgba(0, 0, 0, 0.02);
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.cart-header-desktop:hover {
|
|
background-color: rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
/* Lista de items con scroll */
|
|
.cart-list {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
|
}
|
|
|
|
.cart-list::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
|
|
.cart-list::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
.cart-list::-webkit-scrollbar-thumb {
|
|
background-color: rgba(0, 0, 0, 0.2);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.cart-list::-webkit-scrollbar-thumb:hover {
|
|
background-color: rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
/* Transiciones suaves */
|
|
.v-card {
|
|
transition: max-height 0.35s ease-in-out, box-shadow 0.3s ease-in-out, border-radius 0.3s ease-in-out;
|
|
}
|
|
|
|
/* === MEDIA QUERIES RESPONSIVE === */
|
|
|
|
/* Resolución 560-959px (Mobile/Tablet con imagen) */
|
|
@media (min-width: 560px) and (max-width: 959px) {
|
|
.cart-title {
|
|
padding: 10px 16px !important;
|
|
min-height: 52px;
|
|
}
|
|
|
|
.cart-title-text {
|
|
font-size: 1.05rem;
|
|
}
|
|
|
|
.cart-list-item {
|
|
padding: 12px !important;
|
|
}
|
|
|
|
.prepend-wrapper {
|
|
margin-right: 12px;
|
|
}
|
|
|
|
.cart-item-content {
|
|
gap: 8px;
|
|
}
|
|
|
|
.product-name {
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.controls-row {
|
|
gap: 12px;
|
|
}
|
|
|
|
.quantity-controls {
|
|
gap: 6px;
|
|
}
|
|
|
|
.qty-input {
|
|
width: 65px !important;
|
|
min-width: 65px !important;
|
|
}
|
|
|
|
.qty-btn {
|
|
min-width: 28px !important;
|
|
width: 28px !important;
|
|
height: 28px !important;
|
|
border-radius: 14px !important;
|
|
}
|
|
|
|
.qty-btn .v-icon {
|
|
font-size: 16px !important;
|
|
}
|
|
|
|
.unit-price {
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.total-price {
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.cart-total-section {
|
|
padding: 12px 16px !important;
|
|
}
|
|
|
|
.cart-checkout-section {
|
|
padding: 4px 16px 12px !important;
|
|
}
|
|
}
|
|
|
|
/* Resolución ≥960px (Desktop) */
|
|
@media (min-width: 960px) {
|
|
.v-card {
|
|
border-radius: 12px !important;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08) !important;
|
|
}
|
|
|
|
.cart-title {
|
|
padding: 12px 16px !important;
|
|
min-height: 56px;
|
|
}
|
|
|
|
.cart-title-text {
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.cart-list-item {
|
|
padding: 12px 16px !important;
|
|
}
|
|
|
|
.prepend-wrapper {
|
|
margin-right: 14px;
|
|
}
|
|
|
|
.cart-item-content {
|
|
gap: 10px;
|
|
}
|
|
|
|
.product-name {
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.controls-row {
|
|
gap: 14px;
|
|
}
|
|
|
|
.quantity-controls {
|
|
gap: 6px;
|
|
}
|
|
|
|
.qty-input {
|
|
width: 70px !important;
|
|
min-width: 70px !important;
|
|
}
|
|
|
|
.qty-btn {
|
|
min-width: 32px !important;
|
|
width: 32px !important;
|
|
height: 32px !important;
|
|
border-radius: 16px !important;
|
|
}
|
|
|
|
.qty-btn .v-icon {
|
|
font-size: 18px !important;
|
|
}
|
|
|
|
.unit-price {
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.total-price {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
/* Item-actions en fila en desktop */
|
|
.item-actions {
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.cart-desktop-collapsed {
|
|
width: 100%;
|
|
}
|
|
|
|
.cart-total-section {
|
|
padding: 14px 16px !important;
|
|
}
|
|
|
|
.cart-checkout-section {
|
|
padding: 4px 16px 14px !important;
|
|
}
|
|
}
|
|
|
|
|
|
/* Ajustes para la lista de items en mobile */
|
|
@media (max-width: 680px) {
|
|
.v-list {
|
|
max-height: calc(70vh - 200px) !important;
|
|
}
|
|
}
|
|
|
|
/* Ajustes para resoluciones extra pequeñas (<560px) */
|
|
@media (max-width: 559px) {
|
|
/* Número del item cuando no hay imagen */
|
|
.item-number {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 32px;
|
|
height: 32px;
|
|
min-width: 32px;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
border-radius: 50%;
|
|
font-weight: 700;
|
|
font-size: 0.875rem;
|
|
color: white;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.quantity-controls {
|
|
gap: 3px !important;
|
|
}
|
|
}
|
|
</style>
|