Files
jindengchen-ai-report/cool-unix/uni_modules/cool-ui/components/cl-pagination/cl-pagination.uvue

257 lines
4.6 KiB
Plaintext
Raw Normal View History

2025-11-13 10:36:23 +08:00
<template>
<view class="cl-pagination">
<view
class="cl-pagination__prev"
:class="[
{
'is-disabled': value == 1,
'is-dark': isDark
},
pt.item?.className,
pt.prev?.className
]"
@tap="prev"
>
<slot name="prev" :disabled="value == 1">
<cl-icon
:name="pt.prevIcon?.name ?? 'arrow-left-s-line'"
:size="pt.prevIcon?.size"
:color="pt.prevIcon?.color"
:pt="{
className: pt.prevIcon?.className
}"
></cl-icon>
</slot>
</view>
<view
v-for="(item, index) in list"
:key="index"
class="cl-pagination__item"
:class="[
{
'is-active': item == value,
'is-dark': isDark
},
pt.item?.className
]"
@tap="toPage(item)"
>
<cl-text
:pt="{
className: parseClass([
'cl-pagination__item-text',
{
'text-white': item == value
},
pt.itemText?.className
])
}"
>{{ item }}</cl-text
>
</view>
<view
class="cl-pagination__next"
:class="[
{
'is-disabled': value == totalPage,
'is-dark': isDark
},
pt.item?.className,
pt.next?.className
]"
@tap="next"
>
<slot name="next" :disabled="value == totalPage">
<cl-icon
:name="pt.nextIcon?.name ?? 'arrow-right-s-line'"
:size="pt.nextIcon?.size"
:color="pt.nextIcon?.color"
:pt="{
className: pt.nextIcon?.className
}"
></cl-icon>
</slot>
</view>
</view>
</template>
<script setup lang="ts">
import type { PassThroughProps } from "../../types";
import { isDark, parseClass, parsePt } from "@/cool";
import { computed, ref, watch } from "vue";
import type { ClIconProps } from "../cl-icon/props";
defineOptions({
name: "cl-pagination"
});
const props = defineProps({
pt: {
type: Object,
default: () => ({})
},
modelValue: {
type: Number,
default: 1
},
total: {
type: Number,
default: 0
},
size: {
type: Number,
default: 10
}
});
const emit = defineEmits(["update:modelValue", "change"]);
// 透传样式类型定义
type PassThrough = {
className?: string;
item?: PassThroughProps;
itemText?: PassThroughProps;
prev?: PassThroughProps;
prevIcon?: ClIconProps;
next?: PassThroughProps;
nextIcon?: ClIconProps;
};
// 解析透传样式配置
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 计算总页数,根据总数和每页大小向上取整
const totalPage = computed(() => {
if (props.total == 0) return 1;
return Math.ceil(props.total / props.size);
});
// 绑定值
const value = ref(props.modelValue);
// 计算分页列表,根据当前页码和总页数生成显示的分页按钮
const list = computed(() => {
const total = totalPage.value;
const list: (number | string)[] = [];
if (total <= 7) {
// 总页数小于等于7显示所有页码
for (let i = 1; i <= total; i++) {
list.push(i);
}
} else {
// 总是显示第一页
list.push(1);
if (value.value <= 4) {
// 当前页在前面: 1 2 3 4 5 ... 100
for (let i = 2; i <= 5; i++) {
list.push(i);
}
list.push("...");
list.push(total);
} else if (value.value >= total - 3) {
// 当前页在后面: 1 ... 96 97 98 99 100
list.push("...");
for (let i = total - 4; i <= total; i++) {
list.push(i);
}
} else {
// 当前页在中间: 1 ... 4 5 6 ... 100
list.push("...");
for (let i = value.value - 1; i <= value.value + 1; i++) {
list.push(i);
}
list.push("...");
list.push(total);
}
}
return list;
});
// 跳转到指定页面,处理页码点击事件
function toPage(item: number | string) {
// 忽略省略号点击
if (item == "..." || typeof item !== "number") return;
// 边界检查,确保页码在有效范围内
if (typeof item == "number") {
if (item > totalPage.value) return;
if ((item as number) < 1) return;
}
value.value = item;
// 触发双向绑定更新和变化事件
emit("update:modelValue", item);
emit("change", item);
}
// 跳转到上一页
function prev() {
toPage(value.value - 1);
}
// 跳转到下一页
function next() {
toPage(value.value + 1);
}
watch(
computed(() => props.modelValue),
(val: number) => {
value.value = val;
},
{
immediate: true
}
);
defineExpose({
prev,
next
});
</script>
<style lang="scss" scoped>
.cl-pagination {
@apply flex flex-row justify-center w-full;
&__prev,
&__next,
&__item {
@apply flex flex-row items-center justify-center bg-surface-100 rounded-lg;
height: 60rpx;
width: 60rpx;
&.is-disabled {
@apply opacity-50;
}
&.is-dark {
@apply bg-surface-700;
}
}
&__item {
margin: 0 5rpx;
&.is-active {
@apply bg-primary-500;
}
}
&__prev {
margin-right: 5rpx;
}
&__next {
margin-left: 5rpx;
}
}
</style>