162abdc97c
- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports) - Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier - Add root scripts: format, format:check, lint, lint:fix - Reformat 257 files across admin-web, ops-web, desktop-client, packages - Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters - Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex - Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
157 lines
3.1 KiB
Vue
157 lines
3.1 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
current: number
|
|
pageSize: number
|
|
total: number
|
|
maxVisiblePages?: number
|
|
}>(),
|
|
{
|
|
maxVisiblePages: 5,
|
|
},
|
|
)
|
|
|
|
const emit = defineEmits<{
|
|
(event: 'change', page: number): void
|
|
}>()
|
|
|
|
type PageToken = number | 'ellipsis'
|
|
|
|
const totalPages = computed(() => Math.max(1, Math.ceil(props.total / props.pageSize)))
|
|
|
|
const tokens = computed<PageToken[]>(() => {
|
|
if (totalPages.value <= props.maxVisiblePages) {
|
|
return Array.from({ length: totalPages.value }, (_value, index) => index + 1)
|
|
}
|
|
|
|
const pages: PageToken[] = [1]
|
|
let start = Math.max(2, props.current - 1)
|
|
let end = Math.min(totalPages.value - 1, props.current + 1)
|
|
|
|
while (end - start < 2 && start > 2) {
|
|
start -= 1
|
|
}
|
|
while (end - start < 2 && end < totalPages.value - 1) {
|
|
end += 1
|
|
}
|
|
|
|
if (start > 2) {
|
|
pages.push('ellipsis')
|
|
}
|
|
|
|
for (let page = start; page <= end; page += 1) {
|
|
pages.push(page)
|
|
}
|
|
|
|
if (end < totalPages.value - 1) {
|
|
pages.push('ellipsis')
|
|
}
|
|
|
|
pages.push(totalPages.value)
|
|
return pages
|
|
})
|
|
|
|
function goTo(page: number) {
|
|
if (page === props.current || page < 1 || page > totalPages.value) {
|
|
return
|
|
}
|
|
emit('change', page)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<nav v-if="totalPages > 1" class="pagination" aria-label="Pagination">
|
|
<button class="pagination__nav" :disabled="props.current <= 1" @click="goTo(props.current - 1)">
|
|
上一页
|
|
</button>
|
|
|
|
<div class="pagination__pages">
|
|
<template
|
|
v-for="token in tokens"
|
|
:key="`${token}-${typeof token === 'number' ? token : Math.random()}`"
|
|
>
|
|
<span v-if="token === 'ellipsis'" class="pagination__ellipsis">…</span>
|
|
<button
|
|
v-else
|
|
class="pagination__page"
|
|
:class="{ active: token === props.current }"
|
|
@click="goTo(token)"
|
|
>
|
|
{{ token }}
|
|
</button>
|
|
</template>
|
|
</div>
|
|
|
|
<button
|
|
class="pagination__nav"
|
|
:disabled="props.current >= totalPages"
|
|
@click="goTo(props.current + 1)"
|
|
>
|
|
下一页
|
|
</button>
|
|
</nav>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.pagination {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.pagination__pages {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.pagination__nav,
|
|
.pagination__page {
|
|
appearance: none;
|
|
min-width: 34px;
|
|
height: 34px;
|
|
padding: 0 12px;
|
|
border-radius: 10px;
|
|
border: 1px solid #dbe5f0;
|
|
background: #ffffff;
|
|
color: #334155;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
transition:
|
|
background-color 0.2s ease,
|
|
border-color 0.2s ease,
|
|
color 0.2s ease,
|
|
opacity 0.2s ease;
|
|
}
|
|
|
|
.pagination__nav:hover:not(:disabled),
|
|
.pagination__page:hover:not(:disabled) {
|
|
background: #f8fafc;
|
|
border-color: #bfdbfe;
|
|
}
|
|
|
|
.pagination__nav:disabled,
|
|
.pagination__page:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.pagination__page.active {
|
|
background: #1677ff;
|
|
border-color: #1677ff;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.pagination__ellipsis {
|
|
width: 18px;
|
|
text-align: center;
|
|
color: #94a3b8;
|
|
font-size: 14px;
|
|
}
|
|
</style>
|