创建TS文件 UrlWindow.ts
export class Paginator {
currentPage: number
lastPage: number
query: Object = {}
path: string = '/'
fragment: string = ''
onEachSide: number = 3
pageName: string = 'page'
perPage: number = 15
options: object = {}
total: number = 0
items = []
constructor(paginate: object, options: object = {}) {
console.log(paginate)
let total = paginate['total']
let perPage = paginate['per_page']
let items = paginate['data']
this.query = this.getUrlParams(window.location.href)
this.options = options;
Object.keys(options).forEach(k => {
this[k] = options[k]
})
this.total = total;
this.perPage = perPage;
this.lastPage = paginate['last_page'];//Math.max(Math.ceil(total / perPage), 1);
this.path = paginate['path'];//this.path !== '/' ? this.path.trim().replace(/^\/|\/$/g, '') : this.path;
this.currentPage = paginate['current_page']
this.items = items;
}
getUrlParams(url: string) {
console.log(url)
// 通过 ? 分割获取后面的参数字符串
const urlStr = url.split('?')[1]
if (urlStr) {
// 创建空对象存储参数
let obj = {};
// 再通过 & 将每一个参数单独分割出来
let paramsArr = urlStr.split('&')
for (let i = 0, len = paramsArr.length; i < len; i++) {
// 再通过 = 将每一个参数分割为 key:value 的形式
let arr = paramsArr[i].split('=')
obj[arr[0]] = arr[1];
}
return obj
}
return {}
}
hasPages() {
return this.currentPage != 1 || this.hasMorePages();
}
hasMorePages() {
return this.currentPage < this.lastPage
}
getUrlRange(start:number, end:number) {
const ret = {}
for (var i = start; i < end + 1; i++) {
ret[i] = this.url(i)
}
return ret
}
firstItem() {
return this.items.length > 0 ? (this.currentPage - 1) * this.perPage + 1 : null;
}
arrQuery(parameters: Object) {
let strs = []
Object.keys(parameters).forEach(k => {
strs.push(k + '=' + parameters[k])
})
console.log('page ', strs)
return strs.join('&')
}
url(page: any) {
if (page <= 0) {
page = 1;
}
let parameters = this.query
parameters['page'] = page;
return this.path + (this.path.indexOf('?') > -1 ? '&' : '?')
+ this.arrQuery(parameters) + this.buildFragment()
}
buildFragment() {
return this.fragment ? ('#' + this.fragment) : '';
}
previousPageUrl() {
if (this.currentPage > 1) {
return this.url(this.currentPage - 1);
}
}
nextPageUrl() {
if (this.hasMorePages()) {
let url= this.url(this.currentPage + 1);
return url
}
}
count() {
return this.items.length;
}
onFirstPage()
{
return this.currentPage <= 1;
}
lastItem()
{
return this.items.length > 0 ? this.firstItem() + this.count() - 1 : null;
}
}
export class UrlWindow {
paginator: Paginator
constructor(paginator: Paginator) {
this.paginator = paginator
}
objectAssign(obj1, obj2) {
const obj3 = {};
Object.keys(obj1).forEach(key => {
obj3[key] = obj1[key];
});
Object.keys(obj2).forEach(key => {
obj3[key] = obj2[key];
});
return obj3
}
hasPages() {
return this.paginator.lastPage > 1
}
currentPage() {
return this.paginator.currentPage
}
lastPage() {
return this.paginator.lastPage
}
getSliderTooCloseToBeginning($window, $onEachSide) {
return {
'first': this.paginator.getUrlRange(1, $window + $onEachSide),
'slider': null,
'last': this.getFinish(),
};
}
getFinish() {
return this.paginator.getUrlRange(
this.lastPage() - 1,
this.lastPage()
);
}
getSliderTooCloseToEnding($window, $onEachSide) {
let $last = this.paginator.getUrlRange(
this.lastPage() - ($window + ($onEachSide - 1)),
this.lastPage()
);
return {
'first': this.getStart(),
'slider': null,
'last': $last,
};
}
getStart() {
return this.paginator.getUrlRange(1, 2);
}
getFullSlider($onEachSide) {
return {
'first': this.getStart(),
'slider': this.getAdjacentUrlRange($onEachSide),
'last': this.getFinish(),
};
}
getAdjacentUrlRange($onEachSide) {
return this.paginator.getUrlRange(
this.currentPage() - $onEachSide,
this.currentPage() + $onEachSide
);
}
getUrlSlider($onEachSide: number) {
let $window = $onEachSide + 4;
if (!this.hasPages()) {
return { 'first': null, 'slider': null, 'last': null };
}
// If the current page is very close to the beginning of the page range, we will
// just render the beginning of the page range, followed by the last 2 of the
// links in this list, since we will not have room to create a full slider.
if (this.currentPage() <= $window) {
return this.getSliderTooCloseToBeginning($window, $onEachSide);
}
// If the current page is close to the ending of the page range we will just get
// this first couple pages, followed by a larger window of these ending pages
// since we're too close to the end of the list to create a full on slider.
else if (this.currentPage() > (this.lastPage() - $window)) {
return this.getSliderTooCloseToEnding($window, $onEachSide);
}
// If we have enough room on both sides of the current page to build a slider we
// will surround it with both the beginning and ending caps, with this window
// of pages in the middle providing a Google style sliding paginator setup.
return this.getFullSlider($onEachSide);
}
getSmallSlider() {
return {
'first': this.paginator.getUrlRange(1, this.lastPage()),
'slider': null,
'last': null,
};
}
elements() {
let $window = this.get();
let ret = [
$window['first'],
Array.isArray($window['slider']) ? '...' : null,
$window['slider'],
Array.isArray($window['last']) ? '...' : null,
$window['last'],
];
let ret2 = [];
console.log('ret ', ret)
ret.forEach(v => {
if (v !== null) {
ret2.push(v)
}
})
return ret2
}
get() {
let $onEachSide = this.paginator.onEachSide;
if (this.paginator.lastPage < ($onEachSide * 2) + 8) {
console.log('get small slider')
return this.getSmallSlider();
}
return this.getUrlSlider($onEachSide);
}
}
vue分页组件Paginate.vue
<script setup>
import { computed } from 'vue';
import { Link } from '@inertiajs/vue3';
import { stringify } from 'qs';
import { UrlWindow, Paginator } from '@/Components/UrlWindow'
import { isObject, isString } from '@vue/shared';
const props = defineProps({
paginate: Object
});
const paginator = new Paginator(props.paginate)
const UW = new UrlWindow(paginator)
const elements = UW.elements()
console.log('elements:', elements)
</script>
<template>
<nav v-if="paginator.hasPages()" role="navigation" aria-label="分页导航" class="flex items-center justify-between">
<div class="flex justify-between flex-1 sm:hidden">
<span v-if="paginator.onFirstPage()"
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md">
上一页
</span>
<a v-else :href="paginator.previousPageUrl()"
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
上一页
</a>
<a v-if="paginator.hasMorePages()" :href="paginator.nextPageUrl()"
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
下一页
</a>
<span v-else
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md">
下一页
</span>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p class="text-sm text-gray-700 leading-5">
显示
<span v-if="paginator.firstItem()">
<span class="font-medium">{{ paginator.firstItem() }}</span>
到
<span class="font-medium">{{ paginator.lastItem() }}</span>
</span>
<span v-else>
{{ paginator.count() }}
</span>
of
<span class="font-medium">{{ paginator.total }}</span>
结果
</p>
</div>
<div>
<span class="relative z-0 inline-flex shadow-sm rounded-md">
<span v-if="paginator.onFirstPage()" aria-disabled="true" aria-label="上一页">
<span
class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-l-md leading-5"
aria-hidden="true">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
</span>
</span>
<a v-else :href="paginator.previousPageUrl()" rel="prev"
class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-l-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150"
aria-label="{{ __('pagination.previous') }}">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
</a>
<template v-for="element in elements">
<template v-if="isString(element)">
<span aria-disabled="true">
<span
class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 cursor-default leading-5">
{{ element }}
</span>
</span>
</template>
<template v-if="isObject(element)">
<template v-for="(url, page) in element">
<span v-if="paginator.currentPage == page" aria-current="page">
<span
class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5">
{{ page }}</span>
</span>
<a v-else :href="url"
class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 hover:text-gray-500 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
aria-label="">
{{ page }}
</a>
</template>
</template>
</template>
<a v-if="paginator.hasMorePages" :href="paginator.nextPageUrl()" rel="next"
class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-r-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150"
aria-label="下一页">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd" />
</svg>
</a>
<span v-else aria-disabled="true" aria-label="下一页">
<span
class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-r-md leading-5"
aria-hidden="true">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd" />
</svg>
</span>
</span>
</span>
</div>
</div>
</nav>
</template>
控制器ArticleController.php
<?php
namespace App\Http\Controllers;
use Inertia\Inertia;
use App\Models\Article;
use Illuminate\Http\Request;
use Inertia\Response;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Redirect;
class ArticleController extends Controller
{
/**
* Display the user's profile form.
*/
public function index(): Response
{
$paginate = Article::orderBy('updated_at', 'desc')->paginate(1)->toArray();
return Inertia::render('Article/Index', [
'paginate' => $paginate,
]);
}
}
引用
<script setup>
import Paginate from '@/Components/Paginate.vue';
const props = defineProps(['paginate']);
</script>
<template>
......
<paginate :paginate="paginate"></paginate>
......
</template>
效果
------------------------------------------------------
已经封装成组件vue-laravel-ui发布到npm,使用更方便
npm i vue-laravel-ui
// 页面代码
<script setup>
import {Pagination} from 'vue-laravel-ui'
const props = defineProps(['paginate']);
</script>
<template>
// .....
<Pagination :paginate="paginate"></Pagination>
//.....
</template>
// 控制器传分页参数
public function index(): Response
{
$paginate = Article::orderBy('updated_at', 'desc')->paginate(1)->toArray();
return Inertia::render('Article/Index', [
'paginate' => $paginate, // 这里传参
]);
}