Skip to content
API 风格:

جدول Table ​

جدول بتهيئة الأعمدة. يدعم الفرز، وتثبيت الأعمدة، وخطوط الحمار الوحشي، وفتحات الخلايا، إضافة إلى التمرير الافتراضي للبيانات الضخمة (دوال نقية في core تحسب النافذة المرئية، مطوَّرة داخليًا دون أي مكتبة).

الاستخدام الأساسي ​

الاستخدام الأساسي
الاسم
المسؤولية
المدينة
شين يان
هيكلة
هانغتشو
سو وان
واجهة أمامية
سوتشو
لو لي
تصميم
بيجينغ
vue
<script setup>
const cols = [
  { prop: 'name', label: 'الاسم', width: 140 },
  { prop: 'role', label: 'المسؤولية' },
  { prop: 'city', label: 'المدينة', width: 120 },
]
const rows = [
  { id: 1, name: 'شين يان', role: 'هيكلة', city: 'هانغتشو' },
  { id: 2, name: 'سو وان', role: 'واجهة أمامية', city: 'سوتشو' },
  { id: 3, name: 'لو لي', role: 'تصميم', city: 'بيجينغ' },
]
</script>

<template>
  <LhTable :data="rows" :columns="cols" />
</template>
vue
<script>
export default {
  data() {
    return {
      cols: [
        { prop: 'name', label: 'الاسم', width: 140 },
        { prop: 'role', label: 'المسؤولية' },
        { prop: 'city', label: 'المدينة', width: 120 },
      ],
      rows: [
        { id: 1, name: 'شين يان', role: 'هيكلة', city: 'هانغتشو' },
        { id: 2, name: 'سو وان', role: 'واجهة أمامية', city: 'سوتشو' },
        { id: 3, name: 'لو لي', role: 'تصميم', city: 'بيجينغ' },
      ],
    }
  },
}
</script>

<template>
  <LhTable :data="rows" :columns="cols" />
</template>

الفرز وتثبيت الأعمدة ​

فرز + تثبيت أعمدة + خطوط الحمار الوحشي
الاسم
المسؤولية
المدينة
شين يان
34
هيكلة
هانغتشو
سو وان
28
واجهة أمامية
سوتشو
لو لي
31
تصميم
بيجينغ
vue
<script setup>
const sortCols = [
  { prop: 'name', label: 'الاسم', width: 140, fixed: 'left' },
  { prop: 'age', label: 'العمر', width: 100, sortable: true, align: 'center' },
  { prop: 'role', label: 'المسؤولية' },
  { prop: 'city', label: 'المدينة', width: 120 },
]
</script>

<template>
  <LhTable :data="rows" :columns="sortCols" stripe border size="small" />
</template>
vue
<script>
export default {
  data() {
    return {
      sortCols: [
        { prop: 'name', label: 'الاسم', width: 140, fixed: 'left' },
        { prop: 'age', label: 'العمر', width: 100, sortable: true, align: 'center' },
        { prop: 'role', label: 'المسؤولية' },
        { prop: 'city', label: 'المدينة', width: 120 },
      ],
    }
  },
}
</script>

<template>
  <LhTable :data="rows" :columns="sortCols" stripe border size="small" />
</template>

التمرير الافتراضي (بيانات ضخمة) ​

يُفعَّل بضبط height: لا يُعرض سوى الصفوف داخل النافذة المرئية، ويجب أن يتطابق rowHeight مع ارتفاع الصف الفعلي.

تمرير افتراضي لـ 1000 صف
الاسم
المسؤولية
المدينة
الصف 1
هيكلة
هانغتشو
الصف 2
واجهة أمامية
هانغتشو
الصف 3
هيكلة
هانغتشو
الصف 4
واجهة أمامية
هانغتشو
الصف 5
هيكلة
هانغتشو
الصف 6
واجهة أمامية
هانغتشو
vue
<script setup lang="ts">
const cols = [
  { prop: 'name', label: 'الاسم', width: 140 },
  { prop: 'role', label: 'المسؤولية' },
  { prop: 'city', label: 'المدينة', width: 120 },
]

const bigRows = Array.from({ length: 1000 }, (_, i) => ({
  id: i + 1,
  name: `الصف ${i + 1}`,
  role: i % 2 === 0 ? 'هيكلة' : 'واجهة أمامية',
  city: 'هانغتشو',
}))
</script>

<template>
  <LhTable :data="bigRows" :columns="cols" :height="240" :row-height="48" />
</template>
vue
<script>
export default {
  data() {
    return {
      cols: [
        { prop: 'name', label: 'الاسم', width: 140 },
        { prop: 'role', label: 'المسؤولية' },
        { prop: 'city', label: 'المدينة', width: 120 },
      ],
      bigRows: Array.from({ length: 1000 }, (_, i) => ({
        id: i + 1,
        name: `الصف ${i + 1}`,
        role: i % 2 === 0 ? 'هيكلة' : 'واجهة أمامية',
        city: 'هانغتشو',
      })),
    }
  },
}
</script>

<template>
  <LhTable :data="bigRows" :columns="cols" :height="240" :row-height="48" />
</template>

خلية مخصَّصة ​

تخصيص عبر الفتحة
الاسم
المسؤولية
شين يان
هيكلة
سو وان
واجهة أمامية
لو لي
تصميم
vue
<script setup lang="ts">
const rows = [
  { id: 1, name: 'شين يان', age: 34, role: 'هيكلة', city: 'هانغتشو' },
  { id: 2, name: 'سو وان', age: 28, role: 'واجهة أمامية', city: 'سوتشو' },
  { id: 3, name: 'لو لي', age: 31, role: 'تصميم', city: 'بيجينغ' },
]
const slotCols = [
  { prop: 'name', label: 'الاسم', width: 140 },
  { prop: 'role', label: 'المسؤولية', slot: 'role' },
]
</script>

<template>
  <!-- تخصيص عمود المسؤولية عبر الفتحة -->
  <LhTable :data="rows" :columns="slotCols">
    <template #role="{ value }">
      <LhTag :type="value === 'هيكلة' ? 'primary' : 'info'">{{ value }}</LhTag>
    </template>
  </LhTable>
</template>
vue
<script>
export default {
  data() {
    return {
      rows: [
        { id: 1, name: 'شين يان', age: 34, role: 'هيكلة', city: 'هانغتشو' },
        { id: 2, name: 'سو وان', age: 28, role: 'واجهة أمامية', city: 'سوتشو' },
        { id: 3, name: 'لو لي', age: 31, role: 'تصميم', city: 'بيجينغ' },
      ],
      slotCols: [
        { prop: 'name', label: 'الاسم', width: 140 },
        { prop: 'role', label: 'المسؤولية', slot: 'role' },
      ],
    }
  },
}
</script>

<template>
  <!-- تخصيص عمود المسؤولية عبر الفتحة -->
  <LhTable :data="rows" :columns="slotCols">
    <template #role="{ value }">
      <LhTag :type="value === 'هيكلة' ? 'primary' : 'info'">{{ value }}</LhTag>
    </template>
  </LhTable>
</template>

رؤوس متعددة المستويات ​

بتداخل children في تهيئة الأعمدة تحصل على رؤوس متعددة المستويات، بشكل غير محدود؛ ويُحاذا الجدول وفق الأعمدة الورقية.

رأس من مستويين
الاسم
معلومات المشروع
المسؤولية
المدينة
شين يان
هيكلة
هانغتشو
34
سو وان
واجهة أمامية
سوتشو
28
لو لي
تصميم
بيجينغ
31
vue
<script setup>
const nestedCols = [
  { prop: 'name', label: 'الاسم', width: 140, fixed: 'left' },
  {
    label: 'معلومات المشروع',
    children: [
      { prop: 'role', label: 'المسؤولية' },
      { prop: 'city', label: 'المدينة', width: 120 },
    ],
  },
  { prop: 'age', label: 'العمر', sortable: true, align: 'center' },
]
</script>

<template>
  <LhTable :data="rows" :columns="nestedCols" border />
</template>
vue
<script>
export default {
  data() {
    return {
      nestedCols: [
        { prop: 'name', label: 'الاسم', width: 140, fixed: 'left' },
        {
          label: 'معلومات المشروع',
          children: [
            { prop: 'role', label: 'المسؤولية' },
            { prop: 'city', label: 'المدينة', width: 120 },
          ],
        },
        { prop: 'age', label: 'العمر', sortable: true, align: 'center' },
      ],
    }
  },
}
</script>

<template>
  <LhTable :data="rows" :columns="nestedCols" border />
</template>

التصفية ​

عند ضبط filters في تهيئة العمود يظهر مُطلِّق التصفية في الرأس؛ وبعد تحديد الخيارات يُطبَّق عند النقر على «تأكيد» ويُفرَّغ عند النقر على «إعادة ضبط». يجعل filterMultiple: false الاختيار أحاديًا، ويُخصِّص filterMethod منطق الحكم؛ والاستخدام الموجَّه في المثال أدناه.

تصفية العمود (موجَّهة)
الاسم
المسؤولية
شين يان
هيكلة
سو وان
واجهة أمامية
لو لي
تصميم

التصفية الحالية: {}

vue
<script setup>
import { ref } from 'vue'

const filteredValues = ref({})

const filterCols = [
  { prop: 'name', label: 'الاسم', width: 140 },
  {
    prop: 'role', label: 'المسؤولية', filters: [
      { text: 'هيكلة', value: 'هيكلة' },
      { text: 'واجهة أمامية', value: 'واجهة أمامية' },
      { text: 'تصميم', value: 'تصميم' },
    ],
  },
]

function onFilterChange({ prop, values }) {
  console.log('filterChange', prop, values)
}
</script>

<template>
  <!-- تصفية موجَّهة -->
  <LhTable
    :data="rows"
    :columns="filterCols"
    v-model:filtered-values="filteredValues"
    @filter-change="onFilterChange"
  />
</template>
vue
<script>
export default {
  data() {
    return {
      filteredValues: {},
      filterCols: [
        { prop: 'name', label: 'الاسم', width: 140 },
        {
          prop: 'role', label: 'المسؤولية', filters: [
            { text: 'هيكلة', value: 'هيكلة' },
            { text: 'واجهة أمامية', value: 'واجهة أمامية' },
            { text: 'تصميم', value: 'تصميم' },
          ],
        },
      ],
    }
  },
  methods: {
    onFilterChange({ prop, values }) {
      console.log('filterChange', prop, values)
    },
  },
}
</script>

<template>
  <!-- تصفية موجَّهة -->
  <LhTable
    :data="rows"
    :columns="filterCols"
    v-model:filtered-values="filteredValues"
    @filter-change="onFilterChange"
  />
</template>

سحب عرض العمود ​

فعِّل resizable (المفتاح الرئيسي) وعلِّم العمود بـ resizable: true (عمود ورقة وعرضه رقمي). يظهر مقبض السحب على حافة الرأس، وبعد التركيز يضيف ←/→ في كل مرة ±10px، بحد أدنى للحجم 40px.

سحب لضبط عرض العمود
الاسم
المسؤولية
المدينة
شين يان
هيكلة
هانغتشو
سو وان
واجهة أمامية
سوتشو
لو لي
تصميم
بيجينغ

آخر تعديل: (بلا تعديل)

vue
<script setup>
import { ref } from 'vue'

const resizeLog = ref('(بلا تعديل)')

const resizeCols = [
  { prop: 'name', label: 'الاسم', width: 140, resizable: true, fixed: 'left' },
  { prop: 'role', label: 'المسؤولية' },
  { prop: 'city', label: 'المدينة', width: 120, resizable: true },
]

function onColumnResize({ prop, width }) {
  resizeLog.value = `${prop} → ${width}px`
}
</script>

<template>
  <!-- سحب عرض العمود -->
  <LhTable :data="rows" :columns="resizeCols" resizable border @column-resize="onColumnResize" />
</template>
vue
<script>
export default {
  data() {
    return {
      resizeLog: '(بلا تعديل)',
      resizeCols: [
        { prop: 'name', label: 'الاسم', width: 140, resizable: true, fixed: 'left' },
        { prop: 'role', label: 'المسؤولية' },
        { prop: 'city', label: 'المدينة', width: 120, resizable: true },
      ],
    }
  },
  methods: {
    onColumnResize({ prop, width }) {
      this.resizeLog = `${prop} → ${width}px`
    },
  },
}
</script>

<template>
  <!-- سحب عرض العمود -->
  <LhTable :data="rows" :columns="resizeCols" resizable border @column-resize="onColumnResize" />
</template>

مفتاح الصف، والارتفاع الأقصى، والفرز الافتراضي ​

يُحدِّد row-key مفتاحًا فريدًا للصف (مُوصى به)؛ وبعد تحديد max-height يلتصق الرأس بالأعلى ويتمرر جسم الجدول؛ ويُخصِّص sorter دالة الفرز؛ ويضبط default-sort-prop / default-sort-order الفرز الابتدائي؛ ويضبط default-filtered-values التصفية الابتدائية.

rowKey / maxHeight / فرز افتراضي
الاسم
المسؤولية
شين يان
34
هيكلة
لو لي
31
تصميم
سو وان
28
واجهة أمامية
vue
<script setup>
const sorterCols = [
  { prop: 'name', label: 'الاسم', width: 140 },
  { prop: 'age', label: 'العمر', sortable: true, sorter: (a, b) => a.age - b.age },
  { prop: 'role', label: 'المسؤولية' },
]
</script>

<template>
  <LhTable
    :data="rows"
    :columns="sorterCols"
    row-key="name"
    max-height="200"
    default-sort-prop="age"
    default-sort-order="desc"
  />
</template>
vue
<script>
export default {
  data() {
    return {
      sorterCols: [
        { prop: 'name', label: 'الاسم', width: 140 },
        { prop: 'age', label: 'العمر', sortable: true, sorter: (a, b) => a.age - b.age },
        { prop: 'role', label: 'المسؤولية' },
      ],
    }
  },
}
</script>

<template>
  <LhTable
    :data="rows"
    :columns="sorterCols"
    row-key="name"
    max-height="200"
    default-sort-prop="age"
    default-sort-order="desc"
  />
</template>

API ​

LhTable Props ​

اسم الخاصيةالوصفالنوعالافتراضي
dataبيانات الصفوف[LhTableRow](#type-LhTableRow)[]—
columnsتهيئة الأعمدة[LhTableColumn](#type-LhTableColumn)[]—
heightارتفاع الجدول (يُفعِّل التمرير الافتراضي عند ضبطه؛ والرقم يعني px)string | number—
maxHeightأقصى ارتفاع في الوضع غير الافتراضيstring | number—
rowHeightارتفاع صف التمرير الافتراضي (يجب أن يتطابق مع ارتفاع الصف الفعلي)number[LH_TABLE_ROW_HEIGHT](#const-LH_TABLE_ROW_HEIGHT)
stripeخطوط الحمار الوحشيbooleanfalse
borderحدود الخلاياbooleanfalse
sizeالحجم"small" | "default" | "large"'default'
rowKeyاسم حقل مفتاح الصفstring'id'
defaultSortPropعمود الفرز الافتراضيstring—
defaultSortOrderاتجاه الفرز الافتراضي"asc" | "desc"—
sorterدالة فرز مخصَّصة(a: LhTableRow, b: LhTableRow) => number—
resizableتفعيل سحب عرض العمود (يتطلَّب أيضًا resizable على مستوى العمود)booleanfalse
defaultFilteredValuesحالة التصفية الافتراضية (غير موجَّهة، المفاتيح هي prop الأعمدة){ [x: string]: (string | number)[]; }() => ({})
filteredValuesحالة التصفية الموجَّهة (المفاتيح هي prop الأعمدة){ [x: string]: (string | number)[]; }—
sxخصائص التخطيط السريع sxLhSxProps—

Events ​

اسم الحدثالوصفوسائط الاستدعاء
sortChangeيُطلق عند تغيّر الفرز (عند تغيير العمود يبدأ من asc من جديد، وعلى نفس العمود يدور asc → desc → إلغاء)(payload: { prop: string, order: 'asc' | 'desc' | undefined })
rowClickيُطلق عند النقر على أحد الصفوف(row: LhTableRow, index: number)
filterChangeيُطلق بعد إرسال «تأكيد» أو «إعادة ضبط» التصفية(payload: { prop: string, values: (string | number)[] })
update:filteredValuesيُطلق عند تغيّر حالة التصفية (v-model:filtered-values)(values: Record<string, (string | number)[]>)
columnResizeيُطلق بعد اكتمال ضبط عرض العمود (انتهاء السحب أو ←/→ من لوحة المفاتيح)(payload: { prop: string, width: number })

الفتحات: empty، و<column.slot> (مع البارامترات { row, column, index, value }).

LhTableColumn ​

الحقلالوصفالنوع
propاسم حقل البياناتstring
labelعنوان العمودstring
widthعرض العمود (الرقم يعني px، ويُقسَّم بالتساوي إذا لم يُضبط)number | string
alignالمحاذاة'left' | 'center' | 'right'
fixedتثبيت العمود (يتطلَّب width)'left' | 'right'
sortableقابل للفرزboolean
slotاسم فتحة الخلية المخصَّصةstring
formatterتنسيق القيمة(row, value) => string
childrenالأعمدة الفرعية (ضبطها يُنشئ رؤوس متعددة المستويات)LhTableColumn[]
filtersخيارات تصفية العمود (فعّالة للأعمدة الورقية فقط)LhTableFilterOption[]
filterMethodمنطق تصفية مخصَّص (الافتراضي مقارنة prop بالمساواة التامة)(value, row) => boolean
filterMultipleهل لوحة التصفية متعددة الاختيارboolean
resizableيمكن سحب عرض هذا العمود (يجب أن يكون عمودًا ورقيًا بـ width رقمي)boolean

إمكانية الوصول ​

  • role="grid" + aria-rowcount / aria-colcount؛ والصف row، ورأس العمود columnheader، والخلية gridcell
  • يكشف رأس العمود القابل للفرز عن aria-sort (none / ascending / descending)، ومُطلِّق الفرز <button> حقيقي
  • الأعمدة المثبَّتة تستخدم position: sticky في CSS (دون حسابات التصاق مخصَّصة)
  • مُطلِّق التصفية aria-haspopup + aria-expanded، واللوحة role="group"
  • مقبض عرض العمود role="separator"، قابل للتركيز ويُضبَط بـ ←/→ بمقدار ±10px

تعريفات الأنواع ​

بنى TypeScript التي يصدّرها هذا المكوّن للخارج (مستخرجة تلقائيًا من packages/components/table/src/types.ts و@lh-ui/share/core/*؛ انقر كل عنصر لتوسيع تعريفه الكامل؛ لأوصاف الخصائص انظر جدول Props أعلاه):

LhTableProps
ts
export interface LhTableProps {
  /** بيانات الصفوف */
  data: LhTableRow[]
  /** تهيئة الأعمدة */
  columns: LhTableColumn[]
  /** ارتفاع الجدول (يُفعِّل التمرير الافتراضي عند ضبطه؛ والرقم يعني px) */
  height?: number | string
  /** أقصى ارتفاع في الوضع غير الافتراضي */
  maxHeight?: number | string
  /** ارتفاع صف التمرير الافتراضي (يجب أن يتطابق مع ارتفاع الصف الفعلي) */
  rowHeight?: number
  /** خطوط الحمار الوحشي */
  stripe?: boolean
  /** حدود الخلايا */
  border?: boolean
  /** الحجم */
  size?: 'small' | 'default' | 'large'
  /** اسم حقل مفتاح الصف */
  rowKey?: string
  /** عمود الفرز الافتراضي */
  defaultSortProp?: string
  /** اتجاه الفرز الافتراضي */
  defaultSortOrder?: LhTableSortOrder
  /** دالة فرز مخصَّصة */
  sorter?: (a: LhTableRow, b: LhTableRow) => number
  /** تفعيل سحب عرض العمود (يتطلَّب أيضًا resizable على مستوى العمود، ويجب أن يكون width العمود رقميًا) */
  resizable?: boolean
  /** حالة التصفية الافتراضية (غير موجَّهة، المفاتيح هي prop الأعمدة) */
  defaultFilteredValues?: Record<string, (string | number)[]>
  /** حالة التصفية الموجَّهة (المفاتيح هي prop الأعمدة) */
  filteredValues?: Record<string, (string | number)[]>
  /** خصائص التخطيط السريع sx */
  sx?: LhSxProps
}
LhTableSortOrder (مأخوذ من @lh-ui/share/core/table)
ts
/** اتجاه الفرز */
export type LhTableSortOrder = 'asc' | 'desc'
LhTableRow (مأخوذ من @lh-ui/share/core/table)
ts
/** نوع بيانات الصف */
export type LhTableRow = Record<string, any>
LhTableColumn (مأخوذ من @lh-ui/share/core/table)
ts
/** تهيئة العمود */
export interface LhTableColumn {
  /** اسم حقل البيانات */
  prop: string
  /** عنوان العمود */
  label: string
  /** عرض العمود (الرقم يعني px) */
  width?: number | string
  /** المحاذاة */
  align?: LhTableAlign
  /** تثبيت العمود (يتطلَّب width) */
  fixed?: 'left' | 'right'
  /** قابل للفرز */
  sortable?: boolean
  /** اسم فتحة الخلية المخصَّصة */
  slot?: string
  /** تنسيق القيمة */
  formatter?: (row: LhTableRow, value: any) => string
  /** الأعمدة الفرعية (ضبطها يُنشئ رؤوس متعددة المستويات، بشكل غير محدود) */
  children?: LhTableColumn[]
  /** خيارات تصفية العمود (فعّالة للأعمدة الورقية فقط) */
  filters?: LhTableFilterOption[]
  /** منطق تصفية مخصَّص (الافتراضي مقارنة prop بالمساواة التامة) */
  filterMethod?: (value: string | number, row: LhTableRow) => boolean
  /** هل لوحة التصفية متعددة الاختيار (الافتراضي true) */
  filterMultiple?: boolean
  /** يمكن سحب عرض هذا العمود (يجب أن يكون عمودًا ورقيًا بـ width رقمي) */
  resizable?: boolean
}
LhTableFilterOption (مأخوذ من @lh-ui/share/core/table)
ts
/** خيار التصفية */
export interface LhTableFilterOption {
  /** نص الخيار */
  text: string
  /** قيمة الخيار (تُستخدم في مقارنة filterMethod) */
  value: string | number
}
LhTableAlign (مأخوذ من @lh-ui/share/core/table)
ts
/** محاذاة الخلية */
export type LhTableAlign = 'left' | 'center' | 'right'

الثوابت ​

الثوابت العامة التي يستخدمها هذا المكوّن كالتالي (يمكن النقر على الاسم نفسه في عمود «النوع / الافتراضي» في جدول Props للقفز إلى هنا):

LH_TABLE_ROW_HEIGHT (مأخوذ من @lh-ui/share/core/table)
ts
/** ارتفاع الصف الافتراضي للتمرير الافتراضي */
export const LH_TABLE_ROW_HEIGHT = 48
LH_TABLE_OVERSCAN (مأخوذ من @lh-ui/share/core/table)
ts
/** عدد الصفوف الإضافية المعروضة أعلى النافذة وأسفلها (احتياطي شاشة بيضاء عند التمرير) */
export const LH_TABLE_OVERSCAN = 3
LH_TABLE_MIN_COL_WIDTH (مأخوذ من @lh-ui/share/core/table)
ts
// ============================================================================
// سحب عرض العمود
// ============================================================================

/** الحد الأدنى للسحب (px)، وأضيق من ذلك لا تتسع الخلية للمحتوى */
export const LH_TABLE_MIN_COL_WIDTH = 40

جمال التقاليد، أساس الكود