feat: add vue use and pagination
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"@types/node": "^24.1.0",
|
"@types/node": "^24.1.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.0.1",
|
"@vitejs/plugin-vue-jsx": "^5.0.1",
|
||||||
|
"@vueuse/core": "^13.9.0",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"daisyui": "^5.0.50",
|
"daisyui": "^5.0.50",
|
||||||
"motion-v": "^1.6.1",
|
"motion-v": "^1.6.1",
|
||||||
|
|||||||
0
src/apis/.gitkeep
Normal file
0
src/apis/.gitkeep
Normal file
@@ -1,13 +0,0 @@
|
|||||||
import http from "@/utils/http";
|
|
||||||
|
|
||||||
const url = "";
|
|
||||||
|
|
||||||
const data = {};
|
|
||||||
|
|
||||||
export const exampleApi = () => {
|
|
||||||
return http({
|
|
||||||
url,
|
|
||||||
method: "POST",
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
0
src/components/.gitkeep
Normal file
0
src/components/.gitkeep
Normal file
@@ -1,10 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
86
src/pages/HelpPage.vue
Normal file
86
src/pages/HelpPage.vue
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
import { range } from "radash";
|
||||||
|
import { usePagination } from "@/utils/pagination";
|
||||||
|
|
||||||
|
const merchandise = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "商品1",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
currentPage,
|
||||||
|
pageSize,
|
||||||
|
nextPage,
|
||||||
|
prevPage,
|
||||||
|
changePage,
|
||||||
|
pageNumbers,
|
||||||
|
totalPages,
|
||||||
|
getPaginatedData,
|
||||||
|
} = usePagination(() => merchandise.value);
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
changePage(page);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await new Promise(async (resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
merchandise.value = Array.from(range(1, 50)).map((item) => ({
|
||||||
|
id: item,
|
||||||
|
name: `商品${item}`,
|
||||||
|
}));
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="hero min-h-screen bg-base-200">
|
||||||
|
<div class="hero-content text-center">
|
||||||
|
<div class="max-w-md">
|
||||||
|
<h1 class="text-5xl font-bold">帮助</h1>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li v-for="item in getPaginatedData" :key="item.id">
|
||||||
|
{{ item.name }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:disabled="currentPage === 1"
|
||||||
|
@click="handlePageChange(currentPage - 1)"
|
||||||
|
>
|
||||||
|
«
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-for="page in pageNumbers"
|
||||||
|
:key="page"
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:class="{
|
||||||
|
'btn-active': page === currentPage,
|
||||||
|
'btn-disabled': page === '...',
|
||||||
|
}"
|
||||||
|
@click="page !== '...' && handlePageChange(Number(page))"
|
||||||
|
>
|
||||||
|
{{ page }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:disabled="currentPage === totalPages"
|
||||||
|
@click="handlePageChange(currentPage + 1)"
|
||||||
|
>
|
||||||
|
»
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Component styles */
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
createRouter,
|
createRouter,
|
||||||
createWebHistory,
|
createWebHashHistory,
|
||||||
type RouteRecordRaw,
|
type RouteRecordRaw,
|
||||||
} from "vue-router";
|
} from "vue-router";
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ function generateRoutesFromPages(): RouteRecordRaw[] {
|
|||||||
const routes: RouteRecordRaw[] = generateRoutesFromPages();
|
const routes: RouteRecordRaw[] = generateRoutesFromPages();
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHashHistory(),
|
||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
158
src/utils/pagination.ts
Normal file
158
src/utils/pagination.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import { ref, computed, ComputedRef, Ref } from "vue";
|
||||||
|
|
||||||
|
export interface PaginationOptions {
|
||||||
|
pageSize?: number;
|
||||||
|
initialPage?: number;
|
||||||
|
maxVisiblePages?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginationResult<T> {
|
||||||
|
// Pagination state
|
||||||
|
currentPage: Ref<number>;
|
||||||
|
pageSize: Ref<number>;
|
||||||
|
totalPages: ComputedRef<number>;
|
||||||
|
pageNumbers: ComputedRef<(number | string)[]>;
|
||||||
|
|
||||||
|
// Pagination methods
|
||||||
|
changePage: (page: number) => void;
|
||||||
|
nextPage: () => void;
|
||||||
|
prevPage: () => void;
|
||||||
|
|
||||||
|
// Data methods
|
||||||
|
getPaginatedData: ComputedRef<T[]>;
|
||||||
|
setTotalItems: (count: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create pagination functionality for a list of items
|
||||||
|
*
|
||||||
|
* @param data Ref to the full data array or a function that returns the full data
|
||||||
|
* @param options Pagination options
|
||||||
|
* @returns Pagination state and methods
|
||||||
|
*/
|
||||||
|
export function usePagination<T>(
|
||||||
|
data: Ref<T[]> | (() => T[]),
|
||||||
|
options: PaginationOptions = {},
|
||||||
|
): PaginationResult<T> {
|
||||||
|
// Initialize pagination state
|
||||||
|
const currentPage = ref(options.initialPage || 1);
|
||||||
|
const pageSize = ref(options.pageSize || 10);
|
||||||
|
const maxVisiblePages = options.maxVisiblePages || 5;
|
||||||
|
|
||||||
|
// Calculate if data is a ref or a function
|
||||||
|
const isDataRef = "value" in data;
|
||||||
|
|
||||||
|
// Calculate total items
|
||||||
|
const totalItems = computed(() => {
|
||||||
|
if (isDataRef) {
|
||||||
|
return (data as Ref<T[]>).value.length;
|
||||||
|
} else {
|
||||||
|
return (data as () => T[])().length;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate total pages
|
||||||
|
const totalPages = computed(() => {
|
||||||
|
return Math.ceil(totalItems.value / pageSize.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get paginated data
|
||||||
|
const getPaginatedData = computed(() => {
|
||||||
|
const start = (currentPage.value - 1) * pageSize.value;
|
||||||
|
const end = start + pageSize.value;
|
||||||
|
|
||||||
|
if (isDataRef) {
|
||||||
|
return (data as Ref<T[]>).value.slice(start, end);
|
||||||
|
} else {
|
||||||
|
const fullData = (data as () => T[])();
|
||||||
|
return fullData.slice(start, end);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate array of page numbers for pagination
|
||||||
|
const pageNumbers = computed(() => {
|
||||||
|
const pages: (number | string)[] = [];
|
||||||
|
|
||||||
|
if (totalPages.value <= maxVisiblePages) {
|
||||||
|
// Show all pages if total pages are less than max visible
|
||||||
|
for (let i = 1; i <= totalPages.value; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Always show first page
|
||||||
|
pages.push(1);
|
||||||
|
|
||||||
|
// Calculate start and end of visible page range
|
||||||
|
let startPage = Math.max(2, currentPage.value - 1);
|
||||||
|
let endPage = Math.min(totalPages.value - 1, currentPage.value + 1);
|
||||||
|
|
||||||
|
// Adjust if we're near the beginning
|
||||||
|
if (currentPage.value <= 3) {
|
||||||
|
endPage = Math.min(totalPages.value - 1, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust if we're near the end
|
||||||
|
if (currentPage.value >= totalPages.value - 2) {
|
||||||
|
startPage = Math.max(2, totalPages.value - 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ellipsis if needed before visible pages
|
||||||
|
if (startPage > 2) {
|
||||||
|
pages.push("...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add visible page numbers
|
||||||
|
for (let i = startPage; i <= endPage; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ellipsis if needed after visible pages
|
||||||
|
if (endPage < totalPages.value - 1) {
|
||||||
|
pages.push("...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always show last page
|
||||||
|
pages.push(totalPages.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Change page
|
||||||
|
const changePage = (page: number) => {
|
||||||
|
if (page >= 1 && page <= totalPages.value) {
|
||||||
|
currentPage.value = page;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Next page
|
||||||
|
const nextPage = () => {
|
||||||
|
if (currentPage.value < totalPages.value) {
|
||||||
|
currentPage.value++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Previous page
|
||||||
|
const prevPage = () => {
|
||||||
|
if (currentPage.value > 1) {
|
||||||
|
currentPage.value--;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set total items (useful for API pagination)
|
||||||
|
const setTotalItems = (count: number) => {
|
||||||
|
totalItems.value = count;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentPage,
|
||||||
|
pageSize,
|
||||||
|
totalPages,
|
||||||
|
pageNumbers,
|
||||||
|
changePage,
|
||||||
|
nextPage,
|
||||||
|
prevPage,
|
||||||
|
getPaginatedData,
|
||||||
|
setTotalItems,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user