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 { ArrowDown, ArrowUp, MoreVertical } from "lucide-vue-next";
|
||||
import { codigosResposta } from "p-respostas";
|
||||
import EliTabelaPaginacao from "./EliTabelaPaginacao.vue";
|
||||
import type { EliTabelaConsulta } from "./types-eli-tabela";
|
||||
|
||||
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) {
|
||||
const alvo = Math.min(Math.max(1, pagina), totalPaginas.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) {
|
||||
if (menuAberto.value === null) {
|
||||
return;
|
||||
|
|
@ -475,10 +408,6 @@ export default defineComponent({
|
|||
);
|
||||
}
|
||||
|
||||
const podeVoltar = paginaAtual.value > 1;
|
||||
const podeAvancar = paginaAtual.value < totalPaginas.value;
|
||||
const botoesPaginacao = rangePaginacao();
|
||||
|
||||
const conteudoTabela = [
|
||||
h("table", { class: "eli-tabela__table" }, [
|
||||
h(
|
||||
|
|
@ -659,72 +588,14 @@ export default defineComponent({
|
|||
|
||||
if (totalPaginas.value > 1 && quantidade.value > 0) {
|
||||
conteudoTabela.push(
|
||||
h(
|
||||
"nav",
|
||||
{
|
||||
class: "eli-tabela__paginacao",
|
||||
role: "navigation",
|
||||
"aria-label": "Paginação de resultados",
|
||||
h(EliTabelaPaginacao, {
|
||||
pagina: paginaAtual.value,
|
||||
totalPaginas: totalPaginas.value,
|
||||
maximoBotoes: props.tabela.maximo_botoes_paginacao,
|
||||
onAlterar: (pagina: number) => {
|
||||
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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
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;
|
||||
limit?: number;
|
||||
}) => Promise<tipoResposta<EliConsultaPaginada<T>>>;
|
||||
maximo_botoes_paginacao?: number;
|
||||
/** Mensagem exibida quando a consulta retorna ok porém sem dados. */
|
||||
mensagemVazio?: string;
|
||||
acoes?: EliTabelaAcao<T>[];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue