feat: #8655 added button for scrolling up #1523

Merged
provira merged 34 commits from 8655-scrollUpButton into dev 2025-04-15 11:26:07 +00:00
8 changed files with 139 additions and 18 deletions

View File

@ -2,6 +2,7 @@
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import { useQuasar, Dark } from 'quasar'; import { useQuasar, Dark } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnScroll from './components/common/VnScroll.vue';
const quasar = useQuasar(); const quasar = useQuasar();
const { availableLocales, locale, fallbackLocale } = useI18n(); const { availableLocales, locale, fallbackLocale } = useI18n();
@ -38,6 +39,7 @@ quasar.iconMapFn = (iconName) => {
<template> <template>
<RouterView /> <RouterView />
<VnScroll/>
</template> </template>
<style lang="scss"> <style lang="scss">

View File

@ -406,6 +406,7 @@ defineExpose({
</QBtnDropdown> </QBtnDropdown>
<QBtn <QBtn
v-else v-else
data-cy="saveDefaultBtn"
:label="tMobile('globals.save')" :label="tMobile('globals.save')"
color="primary" color="primary"
icon="save" icon="save"

View File

@ -33,6 +33,7 @@ import VnTableOrder from 'src/components/VnTable/VnOrder.vue';
import VnTableFilter from './VnTableFilter.vue'; import VnTableFilter from './VnTableFilter.vue';
import { getColAlign } from 'src/composables/getColAlign'; import { getColAlign } from 'src/composables/getColAlign';
import RightMenu from '../common/RightMenu.vue'; import RightMenu from '../common/RightMenu.vue';
import VnScroll from '../common/VnScroll.vue'
const arrayData = useArrayData(useAttrs()['data-key']); const arrayData = useArrayData(useAttrs()['data-key']);
const $props = defineProps({ const $props = defineProps({
@ -168,6 +169,7 @@ const params = ref(useFilterParams($attrs['data-key']).params);
const orders = ref(useFilterParams($attrs['data-key']).orders); const orders = ref(useFilterParams($attrs['data-key']).orders);
const app = inject('app'); const app = inject('app');
const tableHeight = useTableHeight(); const tableHeight = useTableHeight();
const vnScrollRef = ref(null);
const editingRow = ref(null); const editingRow = ref(null);
const editingField = ref(null); const editingField = ref(null);
@ -189,6 +191,17 @@ const tableModes = [
}, },
]; ];
const onVirtualScroll = ({ to }) => {
Outdated
Review

Esta parte es necesaria? Con el codigo que hay dentro de VnScroll no funciona?

Esta parte es necesaria? Con el codigo que hay dentro de VnScroll no funciona?
handleScroll();
const virtualScrollContainer = tableRef.value?.$el?.querySelector('.q-table__middle');
if (virtualScrollContainer) {
virtualScrollContainer.dispatchEvent(new CustomEvent('scroll'));
if (vnScrollRef.value) {
vnScrollRef.value.updateScrollContainer(virtualScrollContainer);
}
}
};
onBeforeMount(() => { onBeforeMount(() => {
const urlParams = route.query[$props.searchUrl]; const urlParams = route.query[$props.searchUrl];
hasParams.value = urlParams && Object.keys(urlParams).length !== 0; hasParams.value = urlParams && Object.keys(urlParams).length !== 0;
@ -327,16 +340,13 @@ function handleOnDataSaved(_) {
if (_.onDataSaved) _.onDataSaved({ CrudModelRef: CrudModelRef.value }); if (_.onDataSaved) _.onDataSaved({ CrudModelRef: CrudModelRef.value });
else $props.create.onDataSaved(_); else $props.create.onDataSaved(_);
provira marked this conversation as resolved Outdated
Outdated
Review

Esta funcionalidad no se debe quitar

Esta funcionalidad no se debe quitar
} }
function handleScroll() { function handleScroll() {
if ($props.crudModel.disableInfiniteScroll) return; if ($props.crudModel.disableInfiniteScroll) return;
const tMiddle = tableRef.value.$el.querySelector('.q-table__middle');
const tMiddle = tableRef.value.$el.querySelector('.q-table__middle'); const { scrollHeight, scrollTop, clientHeight } = tMiddle;
const { scrollHeight, scrollTop, clientHeight } = tMiddle; const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) <= 40;
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) <= 40; if (isAtBottom) CrudModelRef.value.vnPaginateRef.paginate();
if (isAtBottom) CrudModelRef.value.vnPaginateRef.paginate();
} }
function handleSelection({ evt, added, rows: selectedRows }, rows) { function handleSelection({ evt, added, rows: selectedRows }, rows) {
if (evt?.shiftKey && added) { if (evt?.shiftKey && added) {
const rowIndex = selectedRows[0].$index; const rowIndex = selectedRows[0].$index;
@ -669,9 +679,9 @@ const rowCtrlClickFunction = computed(() => {
ref="tableRef" ref="tableRef"
v-bind="table" v-bind="table"
:class="[ :class="[
'vnTable', 'vnTable',
table ? 'selection-cell' : '', table ? 'selection-cell' : '',
$props.footer ? 'last-row-sticky' : '', $props.footer ? 'last-row-sticky' : '',
]" ]"
wrap-cells wrap-cells
:columns="splittedColumns.columns" :columns="splittedColumns.columns"
@ -683,7 +693,7 @@ const rowCtrlClickFunction = computed(() => {
flat flat
:style="isTableMode && `max-height: ${$props.tableHeight || tableHeight}`" :style="isTableMode && `max-height: ${$props.tableHeight || tableHeight}`"
:virtual-scroll="isTableMode" :virtual-scroll="isTableMode"
@virtual-scroll="handleScroll" @virtual-scroll="onVirtualScroll"
@row-click="(event, row) => handleRowClick(event, row)" @row-click="(event, row) => handleRowClick(event, row)"
@update:selected="emit('update:selected', $event)" @update:selected="emit('update:selected', $event)"
@selection="(details) => handleSelection(details, rows)" @selection="(details) => handleSelection(details, rows)"
@ -1087,6 +1097,11 @@ const rowCtrlClickFunction = computed(() => {
</template> </template>
</FormModelPopup> </FormModelPopup>
</QDialog> </QDialog>
<VnScroll
ref="vnScrollRef"
v-if="isTableMode"
:scroll-target="tableRef?.$el?.querySelector('.q-table__middle')"
/>
</template> </template>
<i18n> <i18n>
en: en:

View File

@ -0,0 +1,100 @@
<script setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
const props = defineProps({
scrollTarget: { type: [String, Object], default: 'window' }
});
const scrollPosition = ref(0);
const showButton = ref(false);
let scrollContainer = null;
const onScroll = () => {
if (!scrollContainer) return;
scrollPosition.value =
typeof props.scrollTarget === 'object'
? scrollContainer.scrollTop
: window.scrollY;
};
watch(scrollPosition, (newValue) => {
showButton.value = newValue > 0;
});
const scrollToTop = () => {
if (scrollContainer) {
scrollContainer.scrollTo({ top: 0, behavior: 'smooth' });
}
};
const updateScrollContainer = (container) => {
if (container) {
if (scrollContainer) {
scrollContainer.removeEventListener('scroll', onScroll);
}
scrollContainer = container;
scrollContainer.addEventListener('scroll', onScroll);
onScroll();
}
};
defineExpose({
updateScrollContainer
});
const initScrollContainer = async () => {
await nextTick();
Review

Yo esto lo simplificaria a:

const initScrollContainer = async () => {
await nextTick();
let scrollContainer  = window;
    if (props.target) {
        if (typeof props.scrollTarget === 'object') {
            scrollContainer = props.scrollTarget;
        } else {
            scrollContainer = document.querySelector(props.scrollTarget);
        }
    }

    if (!scrollContainer) return
        scrollContainer.addEventListener('scroll', onScroll);    
};
Yo esto lo simplificaria a: ``` const initScrollContainer = async () => { await nextTick(); let scrollContainer = window; if (props.target) { if (typeof props.scrollTarget === 'object') { scrollContainer = props.scrollTarget; } else { scrollContainer = document.querySelector(props.scrollTarget); } } if (!scrollContainer) return scrollContainer.addEventListener('scroll', onScroll); }; ```
Review

Se carga el window scroll. Esta solución funciona y reduce el codigo innecesario, además se puede prescindir de la prop mode (ya no es necesaria):

const initScrollContainer = async () => {
    await nextTick();

    if (typeof props.scrollTarget === 'object') {
        scrollContainer = props.scrollTarget;
    } else {
        scrollContainer = window;
    }

    if (!scrollContainer) return
        scrollContainer.addEventListener('scroll', onScroll);    
};

Se carga el window scroll. Esta solución funciona y reduce el codigo innecesario, además se puede prescindir de la prop mode (ya no es necesaria): ``` const initScrollContainer = async () => { await nextTick(); if (typeof props.scrollTarget === 'object') { scrollContainer = props.scrollTarget; } else { scrollContainer = window; } if (!scrollContainer) return scrollContainer.addEventListener('scroll', onScroll); }; ```
if (typeof props.scrollTarget === 'object') {
scrollContainer = props.scrollTarget;
} else {
scrollContainer = window;
}
if (!scrollContainer) return
scrollContainer.addEventListener('scroll', onScroll);
};
onMounted(() => {
initScrollContainer();
});
onUnmounted(() => {
if (scrollContainer) {
scrollContainer.removeEventListener('scroll', onScroll);
scrollContainer = null;
}
});
</script>
<template>
<QIcon
v-if="showButton"
color="primary"
name="keyboard_arrow_up"
class="scroll-to-top"
@click="scrollToTop"
>
<QTooltip>{{ $t('globals.scrollToTop') }}</QTooltip>
</QIcon>
</template>
provira marked this conversation as resolved
Review

Si lo visualizas desde el movil no se queda en el centro
image

Si lo visualizas desde el movil no se queda en el centro ![image](/attachments/4194e3b9-925f-4dd5-93fe-d2c20f4e402d)
<style scoped>
pablone marked this conversation as resolved Outdated

Te falta el efecto de hover al poner el ratón sobre el icono (poner el cursor como pointer y un pequeño degrado en el color). valora poner un QBtn y lo pondria un poco más grande size="md"

Te falta el efecto de hover al poner el ratón sobre el icono (poner el cursor como pointer y un pequeño degrado en el color). valora poner un `QBtn` y lo pondria un poco más grande `size="md"`

Con un QBtn se ve muy pequeño el icono con respecto al tamaño del botón

Con un QBtn se ve muy pequeño el icono con respecto al tamaño del botón
.scroll-to-top {
position: fixed;
top: 70px;
font-size: 65px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
transition: transform 0.2s ease-in-out;
}
.scroll-to-top:hover {
transform: translateX(-50%) scale(1.2);
cursor: pointer;
filter: brightness(0.8);
}
</style>

View File

@ -6,6 +6,7 @@ globals:
quantity: Quantity quantity: Quantity
entity: Entity entity: Entity
preview: Preview preview: Preview
scrollToTop: Go up
user: User user: User
details: Details details: Details
collapseMenu: Collapse lateral menu collapseMenu: Collapse lateral menu

View File

@ -6,6 +6,7 @@ globals:
quantity: Cantidad quantity: Cantidad
entity: Entidad entity: Entidad
preview: Vista previa preview: Vista previa
scrollToTop: Ir arriba
user: Usuario user: Usuario
details: Detalles details: Detalles
collapseMenu: Contraer menú lateral collapseMenu: Contraer menú lateral

View File

@ -11,14 +11,15 @@ describe('OrderList', () => {
it('create order', () => { it('create order', () => {
cy.get('[data-cy="vnTableCreateBtn"]').click(); cy.get('[data-cy="vnTableCreateBtn"]').click();
cy.selectOption(clientCreateSelect, 1101); cy.selectOption('[data-cy="Client_select"]', 1101);
cy.get(addressCreateSelect).click(); cy.dataCy('landedDate').find('input').type('06/01/2001');
cy.get('[data-cy="Address_select"]').click();
cy.get( cy.get(
'.q-menu > div> div.q-item:nth-child(1) >div.q-item__section--avatar > i', '.q-menu > div> div.q-item:nth-child(1) >div.q-item__section--avatar > i',
).should('have.text', 'star'); ).should('have.text', 'star');
cy.dataCy('landedDate').find('input').type('06/01/2001'); cy.get('.q-menu > div> .q-item:nth-child(1)').click();
cy.selectOption(agencyCreateSelect, 1); cy.get('.q-card [data-cy="Agency_select"]').click();
provira marked this conversation as resolved
Review

Esto no suele ir bien, tampoco deberia estar en esta tarea

Esto no suele ir bien, tampoco deberia estar en esta tarea
cy.get('.q-menu > div> .q-item:nth-child(1)').click();
cy.intercept('GET', /\/api\/Orders\/\d/).as('orderSale'); cy.intercept('GET', /\/api\/Orders\/\d/).as('orderSale');
cy.get('[data-cy="FormModelPopup_save"] > .q-btn__content > .block').click(); cy.get('[data-cy="FormModelPopup_save"] > .q-btn__content > .block').click();
cy.wait('@orderSale'); cy.wait('@orderSale');

View File

@ -10,7 +10,7 @@ describe('WagonTypeEdit', () => {
cy.get('.q-card'); cy.get('.q-card');
cy.get('input').first().type(' changed'); cy.get('input').first().type(' changed');
cy.get('div.q-checkbox__bg').first().click(); cy.get('div.q-checkbox__bg').first().click();
cy.get('.q-btn--standard').click(); cy.dataCy('saveDefaultBtn').click();
}); });
it('should delete a tray', () => { it('should delete a tray', () => {