pagination —— ترقيم الصفحات
ترقيم الصفحات هو التنقل القياسي عندما «تتجاوز البيانات سعة الشاشة». ولم تكمن صعوبته الأساسية في تقليب الصفحات، بل في عرض مجال أرقام الصفحات: فمع 100 صفحة لا يمكن رسم 100 زر جميعها، ولكل مكتبة خوارزميتها في كيفية التعبير عن الموضع وتوفير القفز السريع باستخدام الصفحتين الأولى والأخيرة + النافذة الحالية + علامات الحذف. ويُنفِّذ core/pagination خوارزمية «تقلص النافذة» الشائعة، ويُطبِّع مختلف المدخلات غير الصالحة.
متى يُستخدم
Section titled “متى يُستخدم”- عندما يُطبّق كل طرف
LhPagination: ترقيم أسفل الجداول، وترقيم نتائج البحث، وترقيم الألبومات. - المشاهد التي يمكن فيها تقسيم البيانات إلى صفحات ويحتاج المستخدم فيها إلى التحكم الصريح برقم الصفحة.
- لا يجب استخدامه: التحميل بالتمرير اللانهائي (استخدم
virtual-listأو الاستماع للتمرير)؛ وعندما تكون البيانات قليلة إلى حد تتسع له شاشة واحدة (عدم الترقيم أبسط).
طريقة الاستيراد
Section titled “طريقة الاستيراد”import { getPageCount, clampPage, getPaginationRange, getPaginationItemAria, formatMessage,} from '@lh-ui/share/core/pagination'import type { LhPaginationRangeItem, LhPaginationRangeState, LhPaginationMessageParams,} from '@lh-ui/share/core/pagination'نية التصميم
Section titled “نية التصميم”تطهير المعاملين في getPageCount: يمر total وpageSize كلاهما عبر shared/sanitize.sanitizeNumber — فلا ينفذ NaN / Infinity ليتحولا إلى عدد صفحات NaN. وهذا الدفاع ليس زائدًا: فـ total يأتي عادةً من واجهة غير متزامنة، وقد يكون أثناء التحميل undefined، فيكون Math.ceil(undefined / 10) هو NaN، ويُبطل NaN عمل clampPage وخوارزمية المجال بالكامل.
معاملة clampPage تفرّق بين NaN وInfinity: يُعتبر NaN قيمة غير صالحة فيُتراجع إلى الصفحة الأولى؛ أما ±Infinity فيُترك للتقييد — فـ Infinity يقع في الصفحة الأخيرة، و-Infinity في الأولى. ولهذا التمييز معنى عملي: القيم غير الصالحة التي يمررها الخارج في الوضع المضبوط (NaN) يجب أن تعود إلى موضع آمن، بينما التعبير عن «اقفز إلى الصفحة الأخيرة» بـ Infinity استخدام قانوني.
منطق الالتصاق في خوارزمية المجال: يمر current في getPaginationRange أولًا عبر clampPage للتطبيع — وتسجّل التعليقات سيناريو حقيقيًا: قد يتجاوز innerCurrent المُمرَّر من الطرف البصري الحدود briefly (في الوضع المضبوط عندما تخرج القيمة الخارجية عن المزامنة مع القيمة الداخلية، أو عندما يصير total أصغر دون أن يتزامن current)، وعدم تقييده يجعل الصفحة الحالية تقع خارج نافذة العرض، وينقلب حكم علامة الحذف.
علامة الحذف لا تُخرج إلا عند نشوء فجوة فعلية: showLeftEllipsis = leftSibling > 2 لا > 1 — فعندما تلتصق النافذة بالصفحة الأولى (مثل current=3، siblingCount=1)، لا يُحذف سوى صفحة واحدة هي «2»، وحينها يكون عرض 1،2،3،4،5 متتابعة أوضح من إدراج علامة حذف.
جدول الدوال
Section titled “جدول الدوال”| الدالة | التوقيع | القيمة المُعادة | الدور |
|---|---|---|---|
getPageCount |
(total, pageSize) => number |
إجمالي الصفحات (الأدنى 1) | تطهير المعاملين |
clampPage |
(page, pageCount) => number |
رقم صفحة مُقيَّد | NaN←1، Infinity←الصفحة الأخيرة |
getPaginationRange |
(state: LhPaginationRangeState) => LhPaginationRangeItem[] |
مصفوفة عناصر المجال | خوارزمية تقلص النافذة |
getPaginationItemAria |
(options: { page, current, labelTemplate? }) => LhAriaAttrs |
حزمة سمات aria | aria-current / aria-label |
formatMessage |
إعادة تصدير من shared/format |
نصوص مع ترجمة متغيرة | الحفاظ على مسار الاستيراد الأصلي |
تعريف الأنواع
Section titled “تعريف الأنواع”export type LhPaginationRangeItemType = 'page' | 'ellipsis'
export interface LhPaginationRangeItem { type: LhPaginationRangeItemType page: number // عنصر ellipsis دائمًا 0، وموضعه هو دلالته}
export interface LhPaginationRangeState { current: number // الصفحة الحالية (تبدأ من 1) pageCount: number siblingCount?: number // عدد صفحات الجانبين للصفحة الحالية، الافتراضي 1}
export type LhPaginationMessageParams = Record<string, string | number>إمكانية الوصول
Section titled “إمكانية الوصول”getPaginationItemAria({ page: 3, current: 3, labelTemplate: 'الصفحة {page}' })// { 'aria-current': 'page', 'aria-label': 'الصفحة 3' }
getPaginationItemAria({ page: 5, current: 3, labelTemplate: 'الصفحة {page}' })// { 'aria-label': 'الصفحة 5' }
getPaginationItemAria({ page: 5, current: 3 })// {} (دون قالب لا يُخرج aria-label؛ فالرقم داخل الزر مقروء بذاته)- تُخرج الصفحة الحالية
aria-current="page"— وهي اصطلاح APG القياسي لترقيم الصفحات، وبناءً عليه ينطق قارئ الشاشة «الصفحة الحالية» بدلًا من معاملة الرقم كرابط عادي. - زر علامة الحذف يجب أن يحمل
aria-label(مثل «تقديم 5 صفحات») أوdisabled— فالنص «…» الصرف يحمل صفر معلومة لقارئ الشاشة، وهو أكثر عيوب إمكانية الوصول شيوعًا في الترقيم. - يجب أن يُغلَّف الترقيم كله بـ
<nav aria-label="ترقيم الصفحات">، لتمييزه عن بقية تنقل الصفحة. - يأتي
labelTemplateمن حزمة اللغة (pagination.pageوغيرها)، ويستبدل هذا المتغيّر{page}.
أمثلة الاستخدام
Section titled “أمثلة الاستخدام”import { getPageCount, clampPage, getPaginationRange } from '@lh-ui/share/core/pagination'
getPageCount(100, 10) // 10getPageCount(105, 10) // 11getPageCount(0, 10) // 1 (الأدنى صفحة واحدة)getPageCount(NaN, 10) // 1 (يُطهَّر total إلى 0، ثم يرفعه Math.max(1, …) إلى 1)getPageCount(100, NaN) // 100 (يُطهَّر pageSize إلى القيمة الافتراضية 1، أي عنصر واحد لكل صفحة)
clampPage(5, 10) // 5clampPage(0, 10) // 1clampPage(15, 10) // 10clampPage(NaN, 10) // 1clampPage(Infinity, 10) // 10 (يقع Infinity في الصفحة الأخيرة)
// siblingCount الافتراضي 1، إجمالي الخانات = 1*2 + 5 = 7getPaginationRange({ current: 1, pageCount: 10 })// [1, 2, 3, …, 10]getPaginationRange({ current: 5, pageCount: 10 })// [1, …, 4, 5, 6, …, 10]getPaginationRange({ current: 10, pageCount: 10 })// [1, …, 8, 9, 10]getPaginationRange({ current: 5, pageCount: 5 })// [1, 2, 3, 4, 5] (الصفحات أقل من الخانات، فتُعرض كلها بلا علامات حذف)getPaginationRange({ current: 99, pageCount: 10 })// [1, …, 8, 9, 10] (current المتجاوز يُقيَّد)<!-- Vue3 --><nav class="lh-pagination" aria-label="ترقيم الصفحات"> <button type="button" class="lh-pagination__prev" :disabled="current <= 1" @click="go(current - 1)"> السابقة </button> <template v-for="(item, i) in range" :key="i"> <span v-if="item.type === 'ellipsis'" class="lh-pagination__ellipsis">…</span> <button v-else type="button" class="lh-pagination__item" :class="{ 'is-active': item.page === current }" v-bind="getPaginationItemAria({ page: item.page, current, labelTemplate: t('pagination.page') })" @click="go(item.page)" > {{ item.page }} </button> </template> <button type="button" class="lh-pagination__next" :disabled="current >= pageCount" @click="go(current + 1)"> التالية </button> <select v-model="pageSize" class="lh-pagination__sizes"> <option :value="10">10 عنصرًا/صفحة</option> <option :value="20">20 عنصرًا/صفحة</option> <option :value="50">50 عنصرًا/صفحة</option> </select> <span class="lh-pagination__total">إجمالي {{ total }} عنصرًا</span></nav>const pageCount = computed(() => getPageCount(props.total, pageSize.value))const range = computed(() => getPaginationRange({ current: current.value, pageCount: pageCount.value }),)function go(page: number) { const next = clampPage(page, pageCount.value) if (next !== current.value) emit('update:current', next)}// React: ترقيم مضبوط، يمتلك المكوّن الأب current؛ وفي go يُستدعى clampPage قبل رد النداء// uniapp: <view> + تكرار <view> لعناصر المجال؛ والقفز إلى صفحة عبر <input type="number"> + زر قفزالأنماط والرموز
Section titled “الأنماط والرموز”| اسم الصنف | الدلالة |
|---|---|
.lh-pagination |
الصنف الأساسي (flex موسَّط) |
.lh-pagination__prev / __next |
زرا الصفحتين السابقة/التالية |
.lh-pagination__item |
زر رقم الصفحة |
.lh-pagination__item.is-active |
الصفحة الحالية (خلفية باللون الأساسي) |
.lh-pagination__ellipsis |
علامة الحذف |
.lh-pagination__sizes |
محدد عدد العناصر لكل صفحة |
.lh-pagination__total / __jump |
نص الإجمالي / إدخال القفز |
الصفحة الحالية بخلفية --lh-primary-color + نص --lh-color-on-primary؛ والأزرار المعطّلة --lh-text-disabled؛ ويتحكم --lh-gap-small في التباعد بين الأزرار.
ملاحظات
Section titled “ملاحظات”- أرقام الصفحات تبدأ من 1: تبدأ
currentوpageوpageCountجميعها من 1، خلافًا لفهارس صفوفtable(التي تبدأ من 0)، والخلط بينهما خطأ سهل. - علاقة
pageCountبـtotal: يحسبها الطرف البصري عادةً بـgetPageCount(total, pageSize)، لكن يمكن تمريرpageCountمباشرةً (عند معرفة الخادم لإجمالي الصفحات). ولا تستخدم المصدرين معًا، تفاديًا للتعارض. - تقارب
currentعند تغيُّرpageSize: عند التبديل من 50 عنصرًا/صفحة إلى 10، قد تتجاوز الصفحة الحاليةpageCountالجديد — فاستدعِclampPageمرة عندwatch(pageSize)، وإلا ستُعرض صفحة فارغة. - تفاعل علامة الحذف: ينقل النقر على علامة الحذف عادةً 5 صفحات (
current ± 5)؛ ولا يبني core هذا السلوك، بل يُنفّذه الطرف البصري ويعطي زر علامة الحذفaria-label. siblingCountوالخانات:totalSlots = siblingCount * 2 + 5(الصفحتان الأولى والأخيرة + الصفحة الحالية + خانتا علامتي الحذف + شقيقان على الجانبين). وعندpageCount <= totalSlotsتُعرض كلها بلا علامات حذف — ففيsiblingCount: 2يكون الحد 9 صفحات.- التجاوز العابر في الوضع المضبوط: لقد قيَّد
getPaginationRangeداخليًاcurrent، فلن يخطئ العرض؛ لكنaria-currentسيقع على الصفحة المُقيَّدة، وبعد مزامنة الحالة الخارجية يتسق الأمر طبيعيًا. - إعادة تصدير
formatMessage: يُعيد هذا النمط تصديرformatMessageمنshared/formatبالربطة نفسها، فقط للحفاظ على مسار الاستيراد القائم في المصب (تعبئة نصوص مثلpagination.totalالتي تحمل متغيّر{total})، فليستخدم الكود الجديدcore/shared/formatمباشرة.
@lh-ui/share/core/pagination → getPaginationRange( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue