257 lines
4.6 KiB
Plaintext
257 lines
4.6 KiB
Plaintext
<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>
|