125 lines
2.9 KiB
Vue
125 lines
2.9 KiB
Vue
<script setup lang="ts">
|
|
import { useVModel } from '@vueuse/core'
|
|
import { range, clamp } from 'lodash-es'
|
|
import { computed } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
const RANGE = 2
|
|
|
|
interface Events {
|
|
(e: 'update:current', page: number): void
|
|
}
|
|
|
|
interface Props {
|
|
current?: number
|
|
paginateBy?: number
|
|
total: number,
|
|
compact?: boolean
|
|
}
|
|
|
|
const emit = defineEmits<Events>()
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
current: 1,
|
|
paginateBy: 25,
|
|
compact: false
|
|
})
|
|
|
|
const current = useVModel(props, 'current', emit)
|
|
|
|
const pages = computed(() => {
|
|
const start = range(1, 1 + RANGE)
|
|
const end = range(maxPage.value - RANGE + 1, maxPage.value + 1)
|
|
const middle = range(
|
|
clamp(props.current - RANGE + 1, 1, maxPage.value),
|
|
clamp(props.current + RANGE, 1, maxPage.value)
|
|
).filter(i => !start.includes(i) && !end.includes(i))
|
|
|
|
if (end[0] - 1 <= start[RANGE - 1]) {
|
|
return [
|
|
...start,
|
|
...end.filter(i => i > start[RANGE - 1])
|
|
]
|
|
}
|
|
|
|
return [
|
|
...start,
|
|
middle.length === 0 && 'skip',
|
|
middle.length !== 0 && start[start.length - 1] + 1 !== middle[0] && 'skip',
|
|
...middle,
|
|
middle.length !== 0 && middle[middle.length - 1] + 1 !== end[0] && 'skip',
|
|
...end
|
|
].filter(i => i !== false) as Array<'skip' | number>
|
|
})
|
|
|
|
const maxPage = computed(() => Math.ceil(props.total / props.paginateBy))
|
|
|
|
const setPage = (page: number) => {
|
|
if (page > maxPage.value || page < 1) {
|
|
return
|
|
}
|
|
|
|
current.value = page
|
|
}
|
|
|
|
const { t } = useI18n()
|
|
const labels = computed(() => ({
|
|
pagination: t('components.vui.Pagination.pagination'),
|
|
previousPage: t('components.vui.Pagination.previousPage'),
|
|
nextPage: t('components.vui.Pagination.nextPage')
|
|
}))
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
v-if="maxPage > 1"
|
|
class="ui pagination menu component-pagination"
|
|
role="navigation"
|
|
:aria-label="labels.pagination"
|
|
>
|
|
<a
|
|
href="#"
|
|
:disabled="current - 1 < 1 || null"
|
|
role="button"
|
|
:aria-label="labels.previousPage"
|
|
:class="[{ 'disabled': current - 1 < 1 }, 'item']"
|
|
@click.prevent.stop="setPage(current - 1)"
|
|
>
|
|
<i class="angle left icon" />
|
|
</a>
|
|
|
|
<template v-if="!compact">
|
|
<template
|
|
v-for="page in pages"
|
|
:key="page"
|
|
>
|
|
<a
|
|
v-if="page === 'skip'"
|
|
href="#"
|
|
class="item disabled"
|
|
>
|
|
<span>…</span>
|
|
</a>
|
|
<a
|
|
v-else
|
|
href="#"
|
|
:class="[{ active: page === current }, 'item']"
|
|
@click.prevent.stop="setPage(page as number)"
|
|
>
|
|
<span>{{ page }}</span>
|
|
</a>
|
|
</template>
|
|
</template>
|
|
|
|
<a
|
|
href="#"
|
|
:disabled="current + 1 > maxPage || null"
|
|
role="button"
|
|
:aria-label="labels.nextPage"
|
|
:class="[{ disabled: current + 1 > maxPage }, 'item']"
|
|
@click.prevent.stop="setPage(current + 1)"
|
|
>
|
|
<i class="angle right icon" />
|
|
</a>
|
|
</div>
|
|
</template>
|