import {
computed,
defineComponent,
nextTick,
onMounted,
ref,
SlotsType,
VNode,
watch
} from 'vue';
import { clamp, isNil } from 'lodash-es';
import { DefaultProps, ElementLocator, Font } from '@motajs/render';
import { SetupComponentOptions } from '@motajs/system-ui';
/** 圆角矩形页码距离容器的边框大小,与 pageSize 相乘 */
const RECT_PAD = 0.1;
export interface PageProps extends DefaultProps {
/** 共有多少页 */
pages: number;
/** 页码组件的定位 */
loc: ElementLocator;
/** 当前页码 */
page?: number;
/** 页码的字体 */
font?: Font;
/** 只有一页的时候,是否隐藏页码 */
hideIfSingle?: boolean;
}
export type PageEmits = {
pageChange: (page: number) => void;
'update:page': (page: number) => void;
};
export interface PageExpose {
/**
* 切换页码
* @param page 要切换至的页码数,0 表示第一页
*/
changePage(page: number): void;
/**
* 切换到传入的页码数加上当前页码数的页码
* @param delta 页码数增量
*/
movePage(delta: number): void;
/**
* 获取当前在第几页
*/
now(): number;
}
type PageSlots = SlotsType<{
default: (page: number) => VNode | VNode[];
}>;
const pageProps = {
props: ['pages', 'loc', 'page', 'font', 'hideIfSingle'],
emits: ['pageChange', 'update:page']
} satisfies SetupComponentOptions<
PageProps,
PageEmits,
keyof PageEmits,
PageSlots
>;
/**
* 分页组件,用于多页切换,例如存档界面等。参数参考 {@link PageProps},函数接口参考 {@link PageExpose}
*
* ---
*
* 用例如下,是一个在每页显示文字的用例,其中 page 表示页码索引,第一页就是 0,第二页就是 1,以此类推:
* ```tsx
*
* {
* (page: number) => {
* return items[page].map(v => )
* }
* }
*
* ```
*/
export const Page = defineComponent<
PageProps,
PageEmits,
keyof PageEmits,
PageSlots
>((props, { slots, expose, emit }) => {
const nowPage = ref(props.page ?? 0);
// 五个元素的位置
const leftLoc = ref([]);
const leftPageLoc = ref([]);
const nowPageLoc = ref([]);
const rightPageLoc = ref([]);
const rightLoc = ref([]);
/** 内容的位置 */
const contentLoc = ref([]);
/** 页码容器的位置 */
const pageLoc = ref([]);
/** 页码的矩形框的位置 */
const rectLoc = ref([0, 0, 0, 0]);
/** 页面文字的位置 */
const textLoc = ref([0, 0, 0, 0]);
// 两个监听的参数
const leftArrow = ref();
const rightArrow = ref();
const hide = computed(() => props.hideIfSingle && props.pages === 1);
const font = computed(() => props.font ?? new Font());
const isFirst = computed(() => nowPage.value === 0);
const isLast = computed(() => nowPage.value === props.pages - 1);
const width = computed(() => props.loc[2] ?? 200);
const height = computed(() => props.loc[3] ?? 200);
const round = computed(() => font.value.size / 4);
const nowPageFont = computed(() => Font.clone(font.value, { weight: 700 }));
/** 页码的横向间距 */
const interval = computed(() => {
const size = font.value.size * 1.5;
const max = size * 9;
if (width.value > max) {
return size;
} else {
return (width.value - size * 5) / 4;
}
});
// 左右箭头的颜色
const leftColor = computed(() => (isFirst.value ? '#666' : '#ddd'));
const rightColor = computed(() => (isLast.value ? '#666' : '#ddd'));
let updating = false;
const updatePagePos = () => {
if (updating) return;
updating = true;
nextTick(() => {
updating = false;
});
const pageH = hide.value ? 0 : font.value.size + 8;
contentLoc.value = [0, 0, width.value, height.value - pageH];
pageLoc.value = [0, height.value - pageH, width.value, pageH];
const center = width.value / 2;
const size = font.value.size * 1.5;
const int = size + interval.value;
nowPageLoc.value = [center, 0, size, size, 0.5, 0];
leftPageLoc.value = [center - int, 0, size, size, 0.5, 0];
leftLoc.value = [center - int * 2, 0, size, size, 0.5, 0];
rightPageLoc.value = [center + int, 0, size, size, 0.5, 0];
rightLoc.value = [center + int * 2, 0, size, size, 0.5, 0];
};
const updateArrowPath = () => {
const rectSize = font.value.size * 1.5;
const size = font.value.size;
const pad = rectSize - size;
const left = new Path2D();
left.moveTo(size, pad);
left.lineTo(pad, rectSize / 2);
left.lineTo(size, rectSize - pad);
const right = new Path2D();
right.moveTo(pad, pad);
right.lineTo(size, rectSize / 2);
right.lineTo(pad, rectSize - pad);
leftArrow.value = left;
rightArrow.value = right;
};
const updateRectAndText = () => {
const size = font.value.size * 1.5;
const pad = RECT_PAD * size;
rectLoc.value = [pad, pad, size - pad * 2, size - pad * 2];
textLoc.value = [size / 2, size / 2, void 0, void 0, 0.5, 0.5];
};
watch(font, () => {
updatePagePos();
updateArrowPath();
updateRectAndText();
});
watch(
() => props.loc,
() => {
updatePagePos();
updateRectAndText();
}
);
watch(
() => props.page,
page => {
if (!isNil(page)) {
const target = clamp(page, 0, props.pages - 1);
if (nowPage.value !== target) {
nowPage.value = target;
emit('pageChange', target);
}
}
}
);
/**
* 切换页码
*/
const changePage = (page: number) => {
const target = clamp(page, 0, props.pages - 1);
if (nowPage.value !== target) {
nowPage.value = target;
emit('pageChange', target);
emit('update:page', target);
}
};
const movePage = (delta: number) => {
changePage(nowPage.value + delta);
};
const now = () => nowPage.value;
const lastPage = () => {
changePage(nowPage.value - 1);
};
const nextPage = () => {
changePage(nowPage.value + 1);
};
onMounted(() => {
updatePagePos();
updateArrowPath();
updateRectAndText();
});
expose({ changePage, movePage, now });
return () => {
return (
{slots.default?.(nowPage.value)}
{!isFirst.value && (
)}
{!isLast.value && (
)}
);
};
}, pageProps);