bkp
This commit is contained in:
parent
c4a0d31686
commit
e1fec007b6
3 changed files with 253 additions and 197 deletions
|
|
@ -8,6 +8,7 @@ import { computed, defineComponent, h, onBeforeUnmount, onMounted, PropType, ref
|
||||||
import type { ComponentPublicInstance } from "vue";
|
import type { ComponentPublicInstance } from "vue";
|
||||||
import { ArrowDown, ArrowUp, MoreVertical } from "lucide-vue-next";
|
import { ArrowDown, ArrowUp, MoreVertical } from "lucide-vue-next";
|
||||||
import { codigosResposta } from "p-respostas";
|
import { codigosResposta } from "p-respostas";
|
||||||
|
import EliTabelaPaginacao from "./EliTabelaPaginacao.vue";
|
||||||
import type { EliTabelaConsulta } from "./types-eli-tabela";
|
import type { EliTabelaConsulta } from "./types-eli-tabela";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
|
@ -93,18 +94,6 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function paginaAnterior() {
|
|
||||||
if (paginaAtual.value > 1) {
|
|
||||||
paginaAtual.value -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function proximaPagina() {
|
|
||||||
if (paginaAtual.value < totalPaginas.value) {
|
|
||||||
paginaAtual.value += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function irParaPagina(pagina: number) {
|
function irParaPagina(pagina: number) {
|
||||||
const alvo = Math.min(Math.max(1, pagina), totalPaginas.value);
|
const alvo = Math.min(Math.max(1, pagina), totalPaginas.value);
|
||||||
if (alvo !== paginaAtual.value) {
|
if (alvo !== paginaAtual.value) {
|
||||||
|
|
@ -112,62 +101,6 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function rangePaginacao(maximoBotoes = 7) {
|
|
||||||
const total = totalPaginas.value;
|
|
||||||
const atual = paginaAtual.value;
|
|
||||||
const resultado: Array<{ label: string; pagina?: number; ativo?: boolean; disabled?: boolean }> = [];
|
|
||||||
|
|
||||||
if (total <= maximoBotoes) {
|
|
||||||
for (let pagina = 1; pagina <= total; pagina += 1) {
|
|
||||||
resultado.push({
|
|
||||||
label: String(pagina),
|
|
||||||
pagina,
|
|
||||||
ativo: pagina === atual,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return resultado;
|
|
||||||
}
|
|
||||||
|
|
||||||
const adicionarPagina = (pagina: number) => {
|
|
||||||
resultado.push({
|
|
||||||
label: String(pagina),
|
|
||||||
pagina,
|
|
||||||
ativo: pagina === atual,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const adicionarEllipsis = () => {
|
|
||||||
resultado.push({ label: "…", disabled: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
const visiveis = Math.max(3, maximoBotoes - 2);
|
|
||||||
let inicio = Math.max(2, atual - Math.floor(visiveis / 2));
|
|
||||||
let fim = inicio + visiveis - 1;
|
|
||||||
|
|
||||||
if (fim >= total) {
|
|
||||||
fim = total - 1;
|
|
||||||
inicio = fim - visiveis + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
adicionarPagina(1);
|
|
||||||
|
|
||||||
if (inicio > 2) {
|
|
||||||
adicionarEllipsis();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let pagina = inicio; pagina <= fim; pagina += 1) {
|
|
||||||
adicionarPagina(pagina);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fim < total - 1) {
|
|
||||||
adicionarEllipsis();
|
|
||||||
}
|
|
||||||
|
|
||||||
adicionarPagina(total);
|
|
||||||
|
|
||||||
return resultado;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClickFora(evento: MouseEvent) {
|
function handleClickFora(evento: MouseEvent) {
|
||||||
if (menuAberto.value === null) {
|
if (menuAberto.value === null) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -475,10 +408,6 @@ export default defineComponent({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const podeVoltar = paginaAtual.value > 1;
|
|
||||||
const podeAvancar = paginaAtual.value < totalPaginas.value;
|
|
||||||
const botoesPaginacao = rangePaginacao();
|
|
||||||
|
|
||||||
const conteudoTabela = [
|
const conteudoTabela = [
|
||||||
h("table", { class: "eli-tabela__table" }, [
|
h("table", { class: "eli-tabela__table" }, [
|
||||||
h(
|
h(
|
||||||
|
|
@ -659,72 +588,14 @@ export default defineComponent({
|
||||||
|
|
||||||
if (totalPaginas.value > 1 && quantidade.value > 0) {
|
if (totalPaginas.value > 1 && quantidade.value > 0) {
|
||||||
conteudoTabela.push(
|
conteudoTabela.push(
|
||||||
h(
|
h(EliTabelaPaginacao, {
|
||||||
"nav",
|
pagina: paginaAtual.value,
|
||||||
{
|
totalPaginas: totalPaginas.value,
|
||||||
class: "eli-tabela__paginacao",
|
maximoBotoes: props.tabela.maximo_botoes_paginacao,
|
||||||
role: "navigation",
|
onAlterar: (pagina: number) => {
|
||||||
"aria-label": "Paginação de resultados",
|
irParaPagina(pagina);
|
||||||
},
|
},
|
||||||
[
|
})
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
class: "eli-tabela__pagina-botao",
|
|
||||||
onClick: paginaAnterior,
|
|
||||||
disabled: !podeVoltar,
|
|
||||||
"aria-label": "Página anterior",
|
|
||||||
},
|
|
||||||
"<<"
|
|
||||||
),
|
|
||||||
...botoesPaginacao.map((item, indice) =>
|
|
||||||
item.disabled || !item.pagina
|
|
||||||
? h(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
key: `${item.label}-${indice}`,
|
|
||||||
class: "eli-tabela__pagina-ellipsis",
|
|
||||||
"aria-hidden": "true",
|
|
||||||
},
|
|
||||||
item.label
|
|
||||||
)
|
|
||||||
: h(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
key: `${item.label}-${indice}`,
|
|
||||||
type: "button",
|
|
||||||
class: [
|
|
||||||
"eli-tabela__pagina-botao",
|
|
||||||
item.ativo
|
|
||||||
? "eli-tabela__pagina-botao--ativo"
|
|
||||||
: undefined,
|
|
||||||
],
|
|
||||||
disabled: item.ativo,
|
|
||||||
"aria-current": item.ativo ? "page" : undefined,
|
|
||||||
"aria-label": `Ir para página ${item.label}`,
|
|
||||||
onClick: () => {
|
|
||||||
if (item.pagina) {
|
|
||||||
irParaPagina(item.pagina);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
item.label
|
|
||||||
)
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
class: "eli-tabela__pagina-botao",
|
|
||||||
onClick: proximaPagina,
|
|
||||||
disabled: !podeAvancar,
|
|
||||||
"aria-label": "Próxima página",
|
|
||||||
},
|
|
||||||
">>"
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -818,66 +689,6 @@ export default defineComponent({
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__paginacao {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
margin-top: 12px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__pagina-botao {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 6px 14px;
|
|
||||||
border-radius: 9999px;
|
|
||||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
||||||
background: #ffffff;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: rgba(15, 23, 42, 0.82);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__pagina-botao:hover,
|
|
||||||
.eli-tabela__pagina-botao:focus-visible {
|
|
||||||
background-color: rgba(37, 99, 235, 0.08);
|
|
||||||
border-color: rgba(37, 99, 235, 0.4);
|
|
||||||
color: rgba(37, 99, 235, 0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__pagina-botao:focus-visible {
|
|
||||||
outline: 2px solid rgba(37, 99, 235, 0.45);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__pagina-botao:disabled {
|
|
||||||
cursor: default;
|
|
||||||
opacity: 0.5;
|
|
||||||
background: rgba(148, 163, 184, 0.08);
|
|
||||||
border-color: rgba(148, 163, 184, 0.18);
|
|
||||||
color: rgba(71, 85, 105, 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__pagina-botao--ativo {
|
|
||||||
background: rgba(37, 99, 235, 0.12);
|
|
||||||
border-color: rgba(37, 99, 235, 0.4);
|
|
||||||
color: rgba(37, 99, 235, 0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__pagina-ellipsis {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 32px;
|
|
||||||
color: rgba(107, 114, 128, 0.85);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__tr:last-child .eli-tabela__td {
|
.eli-tabela__tr:last-child .eli-tabela__td {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
244
src/components/eli/EliTabela/EliTabelaPaginacao.vue
Normal file
244
src/components/eli/EliTabela/EliTabelaPaginacao.vue
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
<template>
|
||||||
|
<nav
|
||||||
|
v-if="totalPaginasExibidas > 1"
|
||||||
|
class="eli-tabela__paginacao"
|
||||||
|
role="navigation"
|
||||||
|
aria-label="Paginação de resultados"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="eli-tabela__pagina-botao"
|
||||||
|
:disabled="anteriorDesabilitado"
|
||||||
|
aria-label="Página anterior"
|
||||||
|
@click="irParaPagina(paginaAtual - 1)"
|
||||||
|
>
|
||||||
|
<<
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<template v-for="(item, index) in botoes" :key="`${item.label}-${index}`">
|
||||||
|
<span
|
||||||
|
v-if="item.ehEllipsis"
|
||||||
|
class="eli-tabela__pagina-ellipsis"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
type="button"
|
||||||
|
class="eli-tabela__pagina-botao"
|
||||||
|
:class="item.ativo ? 'eli-tabela__pagina-botao--ativo' : undefined"
|
||||||
|
:disabled="item.ativo"
|
||||||
|
:aria-current="item.ativo ? 'page' : undefined"
|
||||||
|
:aria-label="`Ir para página ${item.label}`"
|
||||||
|
@click="irParaPagina(item.pagina)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="eli-tabela__pagina-botao"
|
||||||
|
:disabled="proximaDesabilitada"
|
||||||
|
aria-label="Próxima página"
|
||||||
|
@click="irParaPagina(paginaAtual + 1)"
|
||||||
|
>
|
||||||
|
>>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent } from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "EliTabelaPaginacao",
|
||||||
|
props: {
|
||||||
|
pagina: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
totalPaginas: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
maximoBotoes: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: {
|
||||||
|
alterar(pagina: number) {
|
||||||
|
return Number.isFinite(pagina);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
/**
|
||||||
|
* Define o limite de botões visíveis. Mantemos um mínimo de 7 para garantir
|
||||||
|
* uma navegação confortável, mesmo quando o consumidor não informa o valor.
|
||||||
|
*/
|
||||||
|
const maximoBotoesVisiveis = computed(() => {
|
||||||
|
const valor = props.maximoBotoes;
|
||||||
|
if (typeof valor === "number" && valor >= 5) {
|
||||||
|
return Math.floor(valor);
|
||||||
|
}
|
||||||
|
return 7;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constrói a lista de botões/reticências que serão exibidos na paginação.
|
||||||
|
* Mantemos sempre a primeira e a última página visíveis, posicionando as
|
||||||
|
* demais de forma dinâmica ao redor da página atual.
|
||||||
|
*/
|
||||||
|
const botoes = computed(() => {
|
||||||
|
const total = props.totalPaginas;
|
||||||
|
const atual = props.pagina;
|
||||||
|
const limite = maximoBotoesVisiveis.value;
|
||||||
|
const resultado: Array<{
|
||||||
|
label: string;
|
||||||
|
pagina?: number;
|
||||||
|
ativo?: boolean;
|
||||||
|
ehEllipsis?: boolean;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
const adicionarPagina = (pagina: number) => {
|
||||||
|
resultado.push({
|
||||||
|
label: String(pagina),
|
||||||
|
pagina,
|
||||||
|
ativo: pagina === atual,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const adicionarReticencias = () => {
|
||||||
|
resultado.push({ label: "…", ehEllipsis: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
if (total <= limite) {
|
||||||
|
for (let pagina = 1; pagina <= total; pagina += 1) {
|
||||||
|
adicionarPagina(pagina);
|
||||||
|
}
|
||||||
|
return resultado;
|
||||||
|
}
|
||||||
|
|
||||||
|
const visiveisCentrais = Math.max(3, limite - 2);
|
||||||
|
let inicio = Math.max(2, atual - Math.floor(visiveisCentrais / 2));
|
||||||
|
let fim = inicio + visiveisCentrais - 1;
|
||||||
|
|
||||||
|
if (fim >= total) {
|
||||||
|
fim = total - 1;
|
||||||
|
inicio = fim - visiveisCentrais + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
adicionarPagina(1);
|
||||||
|
|
||||||
|
if (inicio > 2) {
|
||||||
|
adicionarReticencias();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let pagina = inicio; pagina <= fim; pagina += 1) {
|
||||||
|
adicionarPagina(pagina);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fim < total - 1) {
|
||||||
|
adicionarReticencias();
|
||||||
|
}
|
||||||
|
|
||||||
|
adicionarPagina(total);
|
||||||
|
|
||||||
|
return resultado;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emite a requisição de mudança de página garantindo que o valor esteja
|
||||||
|
* dentro dos limites válidos informados pelo componente pai.
|
||||||
|
*/
|
||||||
|
function irParaPagina(pagina: number | undefined) {
|
||||||
|
if (!pagina) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const alvo = Math.min(Math.max(1, pagina), props.totalPaginas);
|
||||||
|
if (alvo !== props.pagina) {
|
||||||
|
emit("alterar", alvo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const anteriorDesabilitado = computed(() => props.pagina <= 1);
|
||||||
|
const proximaDesabilitada = computed(() => props.pagina >= props.totalPaginas);
|
||||||
|
const paginaAtual = computed(() => props.pagina);
|
||||||
|
const totalPaginasExibidas = computed(() => props.totalPaginas);
|
||||||
|
|
||||||
|
return {
|
||||||
|
botoes,
|
||||||
|
irParaPagina,
|
||||||
|
anteriorDesabilitado,
|
||||||
|
proximaDesabilitada,
|
||||||
|
paginaAtual,
|
||||||
|
totalPaginasExibidas,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.eli-tabela__paginacao {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__pagina-botao {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||||
|
background: #ffffff;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(15, 23, 42, 0.82);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, border-color 0.2s ease,
|
||||||
|
color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__pagina-botao:hover,
|
||||||
|
.eli-tabela__pagina-botao:focus-visible {
|
||||||
|
background-color: rgba(37, 99, 235, 0.08);
|
||||||
|
border-color: rgba(37, 99, 235, 0.4);
|
||||||
|
color: rgba(37, 99, 235, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__pagina-botao:focus-visible {
|
||||||
|
outline: 2px solid rgba(37, 99, 235, 0.45);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__pagina-botao:disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.5;
|
||||||
|
background: rgba(148, 163, 184, 0.08);
|
||||||
|
border-color: rgba(148, 163, 184, 0.18);
|
||||||
|
color: rgba(71, 85, 105, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__pagina-botao--ativo {
|
||||||
|
background: rgba(37, 99, 235, 0.12);
|
||||||
|
border-color: rgba(37, 99, 235, 0.4);
|
||||||
|
color: rgba(37, 99, 235, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__pagina-ellipsis {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
color: rgba(107, 114, 128, 0.85);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -39,6 +39,7 @@ export type EliTabelaConsulta<T> = {
|
||||||
offSet?: number;
|
offSet?: number;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}) => Promise<tipoResposta<EliConsultaPaginada<T>>>;
|
}) => Promise<tipoResposta<EliConsultaPaginada<T>>>;
|
||||||
|
maximo_botoes_paginacao?: number;
|
||||||
/** Mensagem exibida quando a consulta retorna ok porém sem dados. */
|
/** Mensagem exibida quando a consulta retorna ok porém sem dados. */
|
||||||
mensagemVazio?: string;
|
mensagemVazio?: string;
|
||||||
acoes?: EliTabelaAcao<T>[];
|
acoes?: EliTabelaAcao<T>[];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue