+
diff --git a/packages/varlet-ui/types/dataTable.d.ts b/packages/varlet-ui/types/dataTable.d.ts
index ac25113b741..39fb3f27f4c 100644
--- a/packages/varlet-ui/types/dataTable.d.ts
+++ b/packages/varlet-ui/types/dataTable.d.ts
@@ -6,6 +6,7 @@ export declare const dataTableProps: Record
export type DataTableKey = string | number
export type DataTableAlign = 'left' | 'center' | 'right'
+export type DataTableFixed = 'left' | 'right'
export type DataTableRowKey = keyof Row | string | ((row: Row, rowIndex: number) => DataTableKey)
@@ -26,6 +27,12 @@ export interface DataTableCellPropsContext extends DataTableRowPropsC
column: DataTableColumn
}
+export type DataTableCellSpan = number | ((context: DataTableCellPropsContext) => number)
+export type DataTableSelectionDisabled = boolean | ((context: DataTableRowPropsContext) => boolean)
+export interface DataTableExpandColumnContext extends DataTableRowPropsContext {
+ expanded: boolean
+}
+
export type DataTableRowProps =
| HTMLAttributes
| ((context: DataTableRowPropsContext) => HTMLAttributes | undefined)
@@ -34,17 +41,48 @@ export type DataTableCellProps =
| HTMLAttributes
| ((context: DataTableCellPropsContext) => HTMLAttributes | undefined)
-export interface DataTableColumn {
- key: string
- title: string
+export interface DataTableBaseColumn {
+ fixed?: DataTableFixed
width?: string | number
minWidth?: string | number
align?: DataTableAlign
titleAlign?: DataTableAlign
+ titleColSpan?: number
+ colSpan?: DataTableCellSpan
+ rowSpan?: DataTableCellSpan
cellProps?: DataTableCellProps
+}
+
+export interface DataTableFieldColumn extends DataTableBaseColumn {
+ type?: undefined
+ key: string
+ title: string
render?: (context: DataTableColumnRenderContext) => VNodeChild
}
+export interface DataTableSelectionColumn extends DataTableBaseColumn {
+ type: 'selection'
+ key?: string
+ title?: string
+ multiple?: boolean
+ disabled?: DataTableSelectionDisabled
+ render?: never
+}
+
+export interface DataTableExpandColumn extends DataTableBaseColumn {
+ type: 'expand'
+ key?: string
+ title?: string
+ render?: never
+ expandable?: (context: DataTableRowPropsContext) => boolean
+ renderExpand: (context: DataTableExpandColumnContext) => VNodeChild
+}
+
+export type DataTableColumn =
+ | DataTableFieldColumn
+ | DataTableSelectionColumn
+ | DataTableExpandColumn
+
export interface DataTablePagination {
simple?: boolean
disabled?: boolean
@@ -66,11 +104,13 @@ export interface DataTableProps extends BasicAttributes {
page?: number | string
pageSize?: number | string
total?: number | string
+ maxHeight?: number | string
elevation?: boolean | string | number
striped?: boolean
cellBordered?: boolean
size?: 'small' | 'normal' | 'large'
emptyText?: string
+ 'onUpdate:checkedRowKeys'?: ListenerProp<(checkedRowKeys: DataTableKey[]) => void>
'onUpdate:page'?: ListenerProp<(page: number) => void>
'onUpdate:pageSize'?: ListenerProp<(pageSize: number) => void>
}
From 14182d86cb4719b176cabde2f653900f16476f7c Mon Sep 17 00:00:00 2001
From: haoziqaq <357229046@qq.com>
Date: Thu, 21 May 2026 18:52:15 +0800
Subject: [PATCH 05/37] feat(data-table): add tree data support with cascading
selection and custom children key
- Implemented tree data rendering in DataTable component.
- Added props for tree configuration: `tree`, `cascade`, and `childrenKey`.
- Enhanced selection logic to support cascading selection for tree nodes.
- Updated styles for tree cells and triggers.
- Added tests for tree data functionality and selection behavior.
- Updated documentation to include examples for tree data and single selection.
---
.../varlet-ui/src/data-table/DataTable.vue | 437 +++++++++++++++---
.../src/data-table/__tests__/index.spec.js | 115 +++++
.../varlet-ui/src/data-table/dataTable.less | 30 ++
.../varlet-ui/src/data-table/docs/en-US.md | 81 +++-
.../varlet-ui/src/data-table/docs/zh-CN.md | 81 +++-
.../src/data-table/example/index.vue | 48 +-
.../src/data-table/example/locale/en-US.ts | 2 +
.../src/data-table/example/locale/zh-CN.ts | 2 +
packages/varlet-ui/src/data-table/props.ts | 16 +
.../varlet-ui/src/menu-select/menuSelect.less | 1 +
packages/varlet-ui/src/menu/menu.less | 1 +
packages/varlet-ui/src/select/select.less | 1 +
packages/varlet-ui/src/styles/common.less | 10 +-
packages/varlet-ui/src/table/props.ts | 5 +
packages/varlet-ui/src/table/table.less | 1 +
packages/varlet-ui/types/dataTable.d.ts | 7 +
packages/varlet-ui/varlet.config.mjs | 16 +-
17 files changed, 772 insertions(+), 82 deletions(-)
diff --git a/packages/varlet-ui/src/data-table/DataTable.vue b/packages/varlet-ui/src/data-table/DataTable.vue
index cb16bb02b87..f330a335c2d 100644
--- a/packages/varlet-ui/src/data-table/DataTable.vue
+++ b/packages/varlet-ui/src/data-table/DataTable.vue
@@ -79,6 +79,7 @@
+
+
+
+
+
+
+
@@ -169,6 +186,7 @@ import {
type DataTableKey,
type DataTablePagination,
type DataTableSelectionColumn,
+ type DataTableTreeOption,
} from './props'
const { name, n, classes } = createNamespace('data-table')
@@ -192,15 +210,32 @@ interface DataTableBodyCell {
key: string
columnIndex: number
column: DataTableColumn
+ treeLevel?: number
+ treeExpandable?: boolean
+ treeExpanded?: boolean
colSpan?: number
rowSpan?: number
}
+interface DataTableFlatRow {
+ key: DataTableKey
+ row: Record
+ rowIndex: number
+ pageRowIndex: number
+ level: number
+ parentKey?: DataTableKey
+ expandable: boolean
+ expanded: boolean
+}
+
interface DataTableBodyRow {
key: DataTableKey
row: Record
pageRowIndex: number
expanded: boolean
+ treeLevel: number
+ treeExpandable: boolean
+ treeExpanded: boolean
cells: DataTableBodyCell[]
}
@@ -218,6 +253,7 @@ export default defineComponent({
const { t: pt } = injectLocaleProvider()
const checkedRowKeys = ref([...props.checkedRowKeys])
const expandedRowKeys = ref(new Set())
+ const treeExpandedRowKeys = ref(new Set())
const defaultPaginationOptions: NormalizedPaginationOptions = {
simple: false,
disabled: false,
@@ -311,7 +347,7 @@ export default defineComponent({
hasPagination.value ? Math.min(Math.max(currentPage.value, 1), pageCount.value) : 1,
)
- const currentData = computed(() => {
+ const pagedData = computed(() => {
if (!hasPagination.value || props.remote) {
return props.data
}
@@ -321,27 +357,147 @@ export default defineComponent({
return props.data.slice(start, end)
})
+ const normalizedTree = computed(() => {
+ if (props.tree === false) {
+ return undefined
+ }
+
+ return props.tree === true ? {} : props.tree
+ })
+ const treeEnabled = computed(() => !!normalizedTree.value && !normalizedTree.value.disabled)
+ const cascadeSelectionEnabled = computed(() => treeEnabled.value && props.cascade)
+ const firstTreeColumnIndex = computed(() =>
+ props.columns.findIndex((column) => !isSelectionColumn(column) && !isExpandColumn(column)),
+ )
+ const flattenTreeRows = (sourceRows: Record[], includeCollapsedChildren: boolean) => {
+ const rows: DataTableFlatRow[] = []
+ let rowIndex = 0
+
+ const visit = (sourceRows: Record[], level: number, visible: boolean, parentKey?: DataTableKey) => {
+ sourceRows.forEach((row) => {
+ const currentRowIndex = rowIndex
+ const key = getRowKey(row, currentRowIndex)
+ const children = getTreeChildren(row)
+ const expandable = treeEnabled.value && children.length > 0
+ const expanded = expandable ? !treeExpandedRowKeys.value.has(key) : false
+
+ if (visible || includeCollapsedChildren) {
+ rows.push({
+ key,
+ row,
+ rowIndex: currentRowIndex,
+ pageRowIndex: currentRowIndex,
+ level,
+ parentKey,
+ expandable,
+ expanded,
+ })
+ }
+
+ rowIndex += 1
+ visit(children, level + 1, visible && expanded, key)
+ })
+ }
+
+ visit(sourceRows, 0, true)
+
+ return rows
+ }
+ const allCurrentRows = computed(() => flattenTreeRows(pagedData.value, true))
+ const currentData = computed(() => flattenTreeRows(pagedData.value, false))
+ const treeRowMeta = computed(() => {
+ const rowsByKey = new Map()
+ const rowToFlatRow = new Map, DataTableFlatRow>()
+ const parentKeyByChild = new Map()
+
+ allCurrentRows.value.forEach((flatRow) => {
+ rowsByKey.set(flatRow.key, flatRow)
+ rowToFlatRow.set(flatRow.row, flatRow)
+
+ if (flatRow.parentKey != null) {
+ parentKeyByChild.set(flatRow.key, flatRow.parentKey)
+ }
+ })
+
+ return {
+ rowsByKey,
+ rowToFlatRow,
+ parentKeyByChild,
+ }
+ })
const resolvedEmptyText = computed(() => (pt ? pt : t)('selectEmptyText'))
const selectionColumn = computed(() => props.columns.find(isSelectionColumn))
const expandColumn = computed(() => props.columns.find(isExpandColumn))
const checkedRowKeySet = computed(() => new Set(checkedRowKeys.value))
- const currentSelectableRows = computed(() => {
+ const treeSelectionState = computed(() => {
+ const states = new Map()
+
if (!selectionColumn.value) {
- return []
+ return states
}
- return currentData.value
- .map((row, pageRowIndex) => {
- const rowIndex = getAbsoluteRowIndex(pageRowIndex)
+ const visit = (row: Record): { checked: boolean; indeterminate: boolean; selectable: boolean } => {
+ const flatRow = treeRowMeta.value.rowToFlatRow.get(row)
+
+ if (!flatRow) {
+ return { checked: false, indeterminate: false, selectable: false }
+ }
+ const childStates = getTreeChildren(row).map(visit)
+ const selectable = !isRowSelectionDisabled(row, flatRow.pageRowIndex, selectionColumn.value!, flatRow.rowIndex)
+ const selfChecked = checkedRowKeySet.value.has(flatRow.key)
+
+ if (!cascadeSelectionEnabled.value || childStates.length === 0) {
+ const state = {
+ checked: selfChecked,
+ indeterminate: false,
+ }
+
+ states.set(flatRow.key, state)
return {
- row,
- pageRowIndex,
- rowIndex,
- key: getRowKey(row, rowIndex),
+ ...state,
+ selectable,
}
- })
+ }
+
+ const selectableChildren = childStates.filter((childState) => childState.selectable)
+ const allChildrenChecked =
+ selectableChildren.length > 0 && selectableChildren.every((childState) => childState.checked)
+ const someChildrenSelected = selectableChildren.some(
+ (childState) => childState.checked || childState.indeterminate,
+ )
+ const state = {
+ checked: selectable ? allChildrenChecked : false,
+ indeterminate: !allChildrenChecked && someChildrenSelected,
+ }
+
+ states.set(flatRow.key, state)
+
+ return {
+ ...state,
+ selectable: selectable || selectableChildren.length > 0,
+ }
+ }
+
+ pagedData.value.forEach(visit)
+
+ return states
+ })
+ const currentSelectableRows = computed(() => {
+ if (!selectionColumn.value) {
+ return []
+ }
+
+ const selectableRows = treeEnabled.value ? allCurrentRows.value : currentData.value
+
+ return selectableRows
+ .map(({ row, pageRowIndex, rowIndex, key }) => ({
+ row,
+ pageRowIndex,
+ rowIndex,
+ key,
+ }))
.filter(
({ row, rowIndex, pageRowIndex }) =>
!isRowSelectionDisabled(row, pageRowIndex, selectionColumn.value!, rowIndex),
@@ -349,12 +505,12 @@ export default defineComponent({
})
const allCurrentRowsSelected = computed(
() =>
- currentSelectableRows.value.length > 0 &&
- currentSelectableRows.value.every(({ key }) => checkedRowKeySet.value.has(key)),
+ currentSelectableRows.value.length > 0 && currentSelectableRows.value.every(({ key }) => isRowKeySelected(key)),
)
const someCurrentRowsSelected = computed(
() =>
- currentSelectableRows.value.some(({ key }) => checkedRowKeySet.value.has(key)) && !allCurrentRowsSelected.value,
+ currentSelectableRows.value.some(({ key }) => isRowKeySelected(key) || isRowKeyIndeterminate(key)) &&
+ !allCurrentRowsSelected.value,
)
watch(
@@ -365,6 +521,14 @@ export default defineComponent({
{ deep: true },
)
+ watch(
+ () => props.data,
+ () => {
+ syncTreeExpandedRowKeys()
+ },
+ { deep: true, immediate: true },
+ )
+
watch(
[() => props.pagination, () => props.remote, currentPage, normalizedPage],
([pagination, remote, page, normalizedPage]) => {
@@ -377,15 +541,11 @@ export default defineComponent({
{ immediate: true },
)
- const getAbsoluteRowIndex = (pageRowIndex: number) => {
- if (!hasPagination.value || props.remote) {
- return pageRowIndex
- }
-
- return (normalizedPage.value - 1) * currentPageSize.value + pageRowIndex
+ function getAbsoluteRowIndex(pageRowIndex: number) {
+ return pageRowIndex
}
- const getRowKey = (row: Record, rowIndex: number) => {
+ function getRowKey(row: Record, rowIndex: number) {
if (isFunction(props.rowKey)) {
return props.rowKey(row, rowIndex)
}
@@ -393,6 +553,37 @@ export default defineComponent({
return row[props.rowKey] ?? rowIndex
}
+ function getTreeChildren(row: Record) {
+ const children = row[props.childrenKey]
+
+ return Array.isArray(children) ? children : []
+ }
+
+ function syncTreeExpandedRowKeys() {
+ if (!treeEnabled.value) {
+ treeExpandedRowKeys.value = new Set()
+ return
+ }
+
+ const collapsedKeys = new Set(treeExpandedRowKeys.value)
+
+ const collect = (rows: Record[], level: number) => {
+ rows.forEach((row, index) => {
+ const rowIndex = level + index
+ const key = getRowKey(row, rowIndex)
+
+ if (getTreeChildren(row).length > 0 && !collapsedKeys.has(key)) {
+ collapsedKeys.delete(key)
+ }
+
+ collect(getTreeChildren(row), rowIndex + 1)
+ })
+ }
+
+ collect(props.data, 0)
+ treeExpandedRowKeys.value = collapsedKeys
+ }
+
function isSelectionColumn(column: DataTableColumn): column is DataTableSelectionColumn {
return column.type === 'selection'
}
@@ -440,8 +631,20 @@ export default defineComponent({
call(props['onUpdate:checkedRowKeys'], value)
}
+ function isRowKeySelected(key: DataTableKey) {
+ return treeSelectionState.value.get(key)?.checked ?? checkedRowKeySet.value.has(key)
+ }
+
+ function isRowKeyIndeterminate(key: DataTableKey) {
+ return treeSelectionState.value.get(key)?.indeterminate ?? false
+ }
+
function isRowSelected(row: Record, pageRowIndex: number) {
- return checkedRowKeySet.value.has(getRowKey(row, getAbsoluteRowIndex(pageRowIndex)))
+ return isRowKeySelected(getRowKey(row, getAbsoluteRowIndex(pageRowIndex)))
+ }
+
+ function isRowIndeterminate(row: Record, pageRowIndex: number) {
+ return isRowKeyIndeterminate(getRowKey(row, getAbsoluteRowIndex(pageRowIndex)))
}
function isRowExpanded(row: Record, pageRowIndex: number) {
@@ -465,8 +668,14 @@ export default defineComponent({
}
const nextKeys = new Set(checkedRowKeys.value)
-
- selected ? nextKeys.add(key) : nextKeys.delete(key)
+ if (cascadeSelectionEnabled.value) {
+ collectSelectableBranchKeys(row).forEach((branchKey) => {
+ selected ? nextKeys.add(branchKey) : nextKeys.delete(branchKey)
+ })
+ syncAncestorSelection(nextKeys, key)
+ } else {
+ selected ? nextKeys.add(key) : nextKeys.delete(key)
+ }
updateCheckedRowKeys([...nextKeys])
}
@@ -489,6 +698,65 @@ export default defineComponent({
updateCheckedRowKeys([...nextKeys])
}
+ function collectSelectableBranchKeys(row: Record) {
+ const keys: DataTableKey[] = []
+
+ const visit = (currentRow: Record) => {
+ const flatRow = treeRowMeta.value.rowToFlatRow.get(currentRow)
+
+ if (!flatRow || !selectionColumn.value) {
+ return
+ }
+
+ if (!isRowSelectionDisabled(currentRow, flatRow.pageRowIndex, selectionColumn.value, flatRow.rowIndex)) {
+ keys.push(flatRow.key)
+ }
+
+ getTreeChildren(currentRow).forEach(visit)
+ }
+
+ visit(row)
+
+ return keys
+ }
+
+ function shouldCascadeAncestorBeChecked(row: Record, nextKeys: Set): boolean {
+ const flatRow = treeRowMeta.value.rowToFlatRow.get(row)
+
+ if (!flatRow || !selectionColumn.value) {
+ return false
+ }
+
+ const selectable = !isRowSelectionDisabled(row, flatRow.pageRowIndex, selectionColumn.value, flatRow.rowIndex)
+ const children = getTreeChildren(row)
+
+ if (!children.length) {
+ return selectable ? nextKeys.has(flatRow.key) : true
+ }
+
+ return children.every((child) => shouldCascadeAncestorBeChecked(child, nextKeys)) && selectable
+ }
+
+ function syncAncestorSelection(nextKeys: Set, key: DataTableKey) {
+ let parentKey = treeRowMeta.value.parentKeyByChild.get(key)
+
+ while (parentKey != null) {
+ const parentRow = treeRowMeta.value.rowsByKey.get(parentKey)
+
+ if (!parentRow) {
+ break
+ }
+
+ if (shouldCascadeAncestorBeChecked(parentRow.row, nextKeys)) {
+ nextKeys.add(parentKey)
+ } else {
+ nextKeys.delete(parentKey)
+ }
+
+ parentKey = treeRowMeta.value.parentKeyByChild.get(parentKey)
+ }
+ }
+
function toggleRowExpanded(row: Record, pageRowIndex: number) {
if (!expandColumn.value || !isRowExpandable(row, pageRowIndex, expandColumn.value)) {
return
@@ -506,6 +774,31 @@ export default defineComponent({
expandedRowKeys.value = nextExpandedRowKeys
}
+ function isTreeRowExpanded(row: Record, pageRowIndex: number) {
+ if (!treeEnabled.value) {
+ return false
+ }
+
+ return !treeExpandedRowKeys.value.has(getRowKey(row, getAbsoluteRowIndex(pageRowIndex)))
+ }
+
+ function toggleTreeRowExpanded(row: Record, pageRowIndex: number) {
+ if (!treeEnabled.value || getTreeChildren(row).length === 0) {
+ return
+ }
+
+ const key = getRowKey(row, getAbsoluteRowIndex(pageRowIndex))
+ const nextCollapsedRowKeys = new Set(treeExpandedRowKeys.value)
+
+ if (nextCollapsedRowKeys.has(key)) {
+ nextCollapsedRowKeys.delete(key)
+ } else {
+ nextCollapsedRowKeys.add(key)
+ }
+
+ treeExpandedRowKeys.value = nextCollapsedRowKeys
+ }
+
const renderCell = (row: Record, column: DataTableColumn, pageRowIndex: number) => {
const rowIndex = getAbsoluteRowIndex(pageRowIndex)
@@ -587,58 +880,64 @@ export default defineComponent({
const rowCount = currentData.value.length
const covered = Array.from({ length: rowCount }, () => Array(columnCount.value).fill(false))
- return currentData.value.map((row, pageRowIndex) => {
- const cells: DataTableBodyCell[] = []
- const rowIndex = getAbsoluteRowIndex(pageRowIndex)
- const key = getRowKey(row, rowIndex)
+ return currentData.value.map(
+ ({ row, key, rowIndex, pageRowIndex, level, expandable, expanded }, visibleRowIndex) => {
+ const cells: DataTableBodyCell[] = []
- props.columns.forEach((column, columnIndex) => {
- if (covered[pageRowIndex][columnIndex]) {
- return
- }
+ props.columns.forEach((column, columnIndex) => {
+ if (covered[visibleRowIndex][columnIndex]) {
+ return
+ }
- const context = {
- row,
- rowIndex,
- pageRowIndex,
- column,
- }
- const maxColSpan = columnCount.value - columnIndex
- const maxRowSpan = rowCount - pageRowIndex
- const colSpan = resolveSpan(column.colSpan, context, maxColSpan)
- const rowSpan = resolveSpan(column.rowSpan, context, maxRowSpan)
+ const context = {
+ row,
+ rowIndex,
+ pageRowIndex: visibleRowIndex,
+ column,
+ }
+ const maxColSpan = columnCount.value - columnIndex
+ const maxRowSpan = rowCount - visibleRowIndex
+ const colSpan = resolveSpan(column.colSpan, context, maxColSpan)
+ const rowSpan = resolveSpan(column.rowSpan, context, maxRowSpan)
- if (colSpan === 0 || rowSpan === 0) {
- return
- }
+ if (colSpan === 0 || rowSpan === 0) {
+ return
+ }
- for (let rowOffset = 0; rowOffset < rowSpan; rowOffset += 1) {
- for (let colOffset = 0; colOffset < colSpan; colOffset += 1) {
- if (rowOffset === 0 && colOffset === 0) {
- continue
- }
+ for (let rowOffset = 0; rowOffset < rowSpan; rowOffset += 1) {
+ for (let colOffset = 0; colOffset < colSpan; colOffset += 1) {
+ if (rowOffset === 0 && colOffset === 0) {
+ continue
+ }
- covered[pageRowIndex + rowOffset][columnIndex + colOffset] = true
+ covered[visibleRowIndex + rowOffset][columnIndex + colOffset] = true
+ }
}
- }
- cells.push({
- key: `${column.key ?? column.type ?? columnIndex}-${pageRowIndex}-${columnIndex}`,
- columnIndex,
- column,
- colSpan: colSpan > 1 ? colSpan : undefined,
- rowSpan: rowSpan > 1 ? rowSpan : undefined,
+ cells.push({
+ key: `${column.key ?? column.type ?? columnIndex}-${visibleRowIndex}-${columnIndex}`,
+ columnIndex,
+ column,
+ treeLevel: columnIndex === firstTreeColumnIndex.value ? level : undefined,
+ treeExpandable: columnIndex === firstTreeColumnIndex.value ? expandable : undefined,
+ treeExpanded: columnIndex === firstTreeColumnIndex.value ? expanded : undefined,
+ colSpan: colSpan > 1 ? colSpan : undefined,
+ rowSpan: rowSpan > 1 ? rowSpan : undefined,
+ })
})
- })
- return {
- key,
- row,
- pageRowIndex,
- expanded: expandedRowKeys.value.has(key),
- cells,
- }
- })
+ return {
+ key,
+ row,
+ pageRowIndex: visibleRowIndex,
+ expanded: expandedRowKeys.value.has(key),
+ treeLevel: level,
+ treeExpandable: expandable,
+ treeExpanded: expanded,
+ cells,
+ }
+ },
+ )
})
const getRowProps = (row: Record, pageRowIndex: number) => {
@@ -748,10 +1047,14 @@ export default defineComponent({
isRowSelectionDisabled,
isRowExpandable,
isRowExpanded,
+ isRowIndeterminate,
+ isTreeRowExpanded,
isRowSelected,
toggleAllCurrentRows,
toggleRowExpanded,
+ toggleTreeRowExpanded,
toggleRowSelection,
+ treeEnabled,
currentSelectableRows,
renderCell,
renderExpandedRow,
diff --git a/packages/varlet-ui/src/data-table/__tests__/index.spec.js b/packages/varlet-ui/src/data-table/__tests__/index.spec.js
index 786b8f6933a..bac2b94b1c4 100644
--- a/packages/varlet-ui/src/data-table/__tests__/index.spec.js
+++ b/packages/varlet-ui/src/data-table/__tests__/index.spec.js
@@ -20,6 +20,24 @@ const data = [
{ id: 3, name: 'Taylor', role: 'Designer' },
]
+const treeData = [
+ {
+ id: 1,
+ name: 'Frontend',
+ role: 'Team',
+ nodes: [
+ { id: 11, name: 'Ada', role: 'Lead' },
+ { id: 12, name: 'Taylor', role: 'Engineer' },
+ ],
+ },
+ {
+ id: 2,
+ name: 'Design',
+ role: 'Team',
+ nodes: [{ id: 21, name: 'Linus', role: 'Designer' }],
+ },
+]
+
describe('test data-table component props', () => {
test('should render basic table content', () => {
const wrapper = mount(VarDataTable, {
@@ -255,6 +273,103 @@ describe('test data-table component props', () => {
wrapper.unmount()
})
+ test('should support tree data with custom children key', async () => {
+ const wrapper = mount(VarDataTable, {
+ props: {
+ columns,
+ data: treeData,
+ pagination: false,
+ tree: true,
+ childrenKey: 'nodes',
+ },
+ })
+
+ expect(wrapper.text()).toContain('Frontend')
+ expect(wrapper.text()).toContain('Ada')
+ expect(wrapper.text()).toContain('Taylor')
+ expect(wrapper.text()).toContain('Design')
+ expect(wrapper.text()).toContain('Linus')
+
+ const triggers = wrapper.findAll('.var-data-table__tree-trigger')
+ expect(triggers).toHaveLength(2)
+
+ await triggers[0].trigger('click')
+
+ expect(wrapper.text()).not.toContain('Ada')
+ expect(wrapper.text()).not.toContain('Taylor')
+ expect(wrapper.text()).toContain('Linus')
+
+ wrapper.unmount()
+ })
+
+ test('should cascade tree selection in multiple mode by default', async () => {
+ const onUpdateCheckedRowKeys = vi.fn()
+ const wrapper = mount(VarDataTable, {
+ props: {
+ columns: [{ type: 'selection' }, ...columns],
+ data: treeData,
+ pagination: false,
+ tree: true,
+ childrenKey: 'nodes',
+ checkedRowKeys: [],
+ 'onUpdate:checkedRowKeys': onUpdateCheckedRowKeys,
+ },
+ })
+
+ const checkboxes = wrapper.findAllComponents({ name: 'var-checkbox' })
+
+ checkboxes[1].vm.$emit('update:modelValue', true)
+ await wrapper.vm.$nextTick()
+ expect(onUpdateCheckedRowKeys).toHaveBeenLastCalledWith([1, 11, 12])
+
+ wrapper.unmount()
+ })
+
+ test('should support non-cascading tree selection when cascade is false', async () => {
+ const onUpdateCheckedRowKeys = vi.fn()
+ const wrapper = mount(VarDataTable, {
+ props: {
+ columns: [{ type: 'selection' }, ...columns],
+ data: treeData,
+ pagination: false,
+ tree: true,
+ cascade: false,
+ childrenKey: 'nodes',
+ checkedRowKeys: [],
+ 'onUpdate:checkedRowKeys': onUpdateCheckedRowKeys,
+ },
+ })
+
+ const checkboxes = wrapper.findAllComponents({ name: 'var-checkbox' })
+
+ checkboxes[1].vm.$emit('update:modelValue', true)
+ await wrapper.vm.$nextTick()
+ expect(onUpdateCheckedRowKeys).toHaveBeenLastCalledWith([1])
+
+ wrapper.unmount()
+ })
+
+ test('should count collapsed selected tree rows in header checkbox state', async () => {
+ const wrapper = mount(VarDataTable, {
+ props: {
+ columns: [{ type: 'selection' }, ...columns],
+ data: treeData,
+ pagination: false,
+ tree: true,
+ childrenKey: 'nodes',
+ checkedRowKeys: [11],
+ },
+ })
+
+ await wrapper.find('.var-data-table__tree-trigger').trigger('click')
+
+ const checkboxes = wrapper.findAllComponents({ name: 'var-checkbox' })
+ expect(checkboxes[0].vm.modelValue).toBe(false)
+ expect(checkboxes[0].vm.indeterminate).toBe(true)
+
+ wrapper.unmount()
+ })
+
test('should support maxHeight with sticky header', () => {
const wrapper = mount(VarDataTable, {
props: {
diff --git a/packages/varlet-ui/src/data-table/dataTable.less b/packages/varlet-ui/src/data-table/dataTable.less
index a2ab36fe4b0..a75a091845d 100644
--- a/packages/varlet-ui/src/data-table/dataTable.less
+++ b/packages/varlet-ui/src/data-table/dataTable.less
@@ -20,6 +20,7 @@
}
.var-data-table {
+ --scrollbar-track-background: var(--data-table-background);
width: 100%;
border-radius: var(--data-table-border-radius);
background: var(--data-table-background);
@@ -94,6 +95,10 @@
padding: var(--data-table-selection-cell-padding);
min-width: var(--data-table-selection-column-width);
+ .var-checkbox {
+ transform: none;
+ }
+
.var-checkbox__wrap {
display: flex;
align-items: center;
@@ -125,6 +130,31 @@
}
}
+ &__tree-cell {
+ display: flex;
+ align-items: center;
+ min-width: 0;
+ }
+
+ &__tree-trigger,
+ &__tree-indent {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ flex: none;
+ margin-right: 4px;
+ }
+
+ &__tree-trigger {
+ padding: 0;
+ border: 0;
+ background: transparent;
+ color: inherit;
+ cursor: pointer;
+ }
+
&__expanded-row {
background: var(--data-table-background);
border-bottom: 1px solid var(--data-table-border-color);
diff --git a/packages/varlet-ui/src/data-table/docs/en-US.md b/packages/varlet-ui/src/data-table/docs/en-US.md
index b715b9cbca0..c6f4515118b 100644
--- a/packages/varlet-ui/src/data-table/docs/en-US.md
+++ b/packages/varlet-ui/src/data-table/docs/en-US.md
@@ -159,7 +159,7 @@ const data = [
-
+
```
@@ -185,6 +185,76 @@ const columns = [
```
+#### Single Selection
+
+```html
+
+
+
+
+
+```
+
+### Tree Data
+
+Set `tree` explicitly to flatten and render hierarchical rows by `children-key`. Tree selection cascades by default: selecting a parent row also affects its children. Pass `:cascade="false"` if you want to keep tree selection non-cascading.
+
+```html
+
+
+
+
+
+```
+
### Expand
Use `type: 'expand'` to render an expand column. Use `renderExpand` to customize expanded content, and `expandable` to control whether a row can be expanded.
@@ -331,6 +401,9 @@ Set `max-height` to make the table body scroll internally while keeping the head
| `v-model:checked-row-keys` | Selected row keys | _Array_ | `[]` |
| `total` | Total item count in remote mode | _number \| string_ | `-` |
| `max-height` | Max height of the table body. When set, the header stays fixed and the body scrolls internally | _number \| string_ | `-` |
+| `tree` | Whether to explicitly enable tree data mode. Also supports a config object | _boolean \| DataTableTreeOption_ | `false` |
+| `cascade` | Whether tree selection should cascade | _boolean_ | `true` |
+| `children-key` | Child node field name for tree rows | _string_ | `'children'` |
| `elevation` | Elevation level | _boolean \| number \| string_ | `true` |
| `cell-bordered` | Whether to show cell dividers | _boolean_ | `false` |
| `size` | Table size | _'small' \| 'normal' \| 'large'_ | `'normal'` |
@@ -368,6 +441,12 @@ Set `max-height` to make the table body scroll internally while keeping the head
| `sizeOption` | Page size options | _number[]_ | `[10, 20, 50, 100]` |
| `showTotal` | Total text renderer | _`(total: number, range: [number, number]) => string`_ | `-` |
+### DataTableTreeOption
+
+| Prop | Description | Type | Default |
+| --- | --- | --- | --- |
+| `disabled` | Whether to disable tree rendering | _boolean_ | `false` |
+
### Slots
| Name | Description | Parameters |
diff --git a/packages/varlet-ui/src/data-table/docs/zh-CN.md b/packages/varlet-ui/src/data-table/docs/zh-CN.md
index 51486b9cc30..7f4a1048fbe 100644
--- a/packages/varlet-ui/src/data-table/docs/zh-CN.md
+++ b/packages/varlet-ui/src/data-table/docs/zh-CN.md
@@ -159,7 +159,7 @@ const data = [
-
+
```
@@ -185,6 +185,76 @@ const columns = [
```
+#### 单选
+
+```html
+
+
+
+
+
+```
+
+### 树形数据
+
+显式设置 `tree` 后,表格会按照 `children-key` 扁平化渲染树形结构。树形多选默认开启级联,勾选父节点会联动子节点;如果你想改成非级联模式,可以传 `:cascade="false"`。
+
+```html
+
+
+
+
+
+```
+
### 展开列
通过 `type: 'expand'` 渲染展开列,使用 `renderExpand` 自定义展开内容,`expandable` 可按行控制是否允许展开。
@@ -331,6 +401,9 @@ const columns = [{ key: 'name', title: '姓名' }]
| `v-model:checked-row-keys` | 选中行的 key 集合 | _Array_ | `[]` |
| `total` | 远程分页总条数 | _number \| string_ | `-` |
| `max-height` | 表格主体最大高度。设置后表头固定,内容区域内部滚动 | _number \| string_ | `-` |
+| `tree` | 是否显式开启树形数据,也支持传入配置对象 | _boolean \| DataTableTreeOption_ | `false` |
+| `cascade` | 树形选择是否开启级联 | _boolean_ | `true` |
+| `children-key` | 树形子节点字段名 | _string_ | `'children'` |
| `elevation` | 海拔层级 | _boolean \| number \| string_ | `true` |
| `cell-bordered` | 是否显示单元格分割线 | _boolean_ | `false` |
| `size` | 表格尺寸 | _'small' \| 'normal' \| 'large'_ | `'normal'` |
@@ -368,6 +441,12 @@ const columns = [{ key: 'name', title: '姓名' }]
| `sizeOption` | 每页条数选项 | _number[]_ | `[10, 20, 50, 100]` |
| `showTotal` | 总数文案渲染函数 | _`(total: number, range: [number, number]) => string`_ | `-` |
+### DataTableTreeOption
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| `disabled` | 是否禁用树形渲染 | _boolean_ | `false` |
+
### Slots
| 名称 | 说明 | 参数 |
diff --git a/packages/varlet-ui/src/data-table/example/index.vue b/packages/varlet-ui/src/data-table/example/index.vue
index db382c7049e..4716e4beb46 100644
--- a/packages/varlet-ui/src/data-table/example/index.vue
+++ b/packages/varlet-ui/src/data-table/example/index.vue
@@ -56,12 +56,20 @@ const spanData = [
]
const checkedRowKeys = ref([1, 3])
+const singleCheckedRowKeys = ref([2])
+const treeCheckedRowKeys = ref([12])
const selectionColumns = [
{ type: 'selection' },
{ key: 'name', title: 'Name' },
{ key: 'role', title: 'Role' },
{ key: 'status', title: 'Status' },
]
+const singleSelectionColumns = [
+ { type: 'selection', multiple: false },
+ { key: 'name', title: 'Name' },
+ { key: 'role', title: 'Role' },
+ { key: 'status', title: 'Status' },
+]
const expandColumns = [
{
@@ -83,6 +91,31 @@ const expandColumns = [
{ key: 'role', title: 'Role' },
{ key: 'status', title: 'Status' },
]
+const treeColumns = [
+ { type: 'selection' },
+ { key: 'name', title: 'Name' },
+ { key: 'role', title: 'Role' },
+ { key: 'status', title: 'Status' },
+]
+const treeData = [
+ {
+ id: 1,
+ name: 'Frontend',
+ role: 'Team',
+ status: 'Online',
+ nodes: [
+ { id: 11, name: 'Ada', role: 'Lead', status: 'Online' },
+ { id: 12, name: 'Taylor', role: 'Engineer', status: 'Busy' },
+ ],
+ },
+ {
+ id: 2,
+ name: 'Design',
+ role: 'Team',
+ status: 'Offline',
+ nodes: [{ id: 21, name: 'Linus', role: 'Designer', status: 'Offline' }],
+ },
+]
const selectedRowNames = computed(() =>
data
@@ -174,7 +207,7 @@ const remoteData = computed(() => {
{{ t('spans') }}
-
+
{{ t('selection') }}
@@ -182,6 +215,19 @@ const remoteData = computed(() => {
{{ t('selectedRows') }}: {{ selectedRowNames || '-' }}
+
{{ t('singleSelection') }}
+
+
+
{{ t('tree') }}
+
+
{{ t('expand') }}
diff --git a/packages/varlet-ui/src/data-table/example/locale/en-US.ts b/packages/varlet-ui/src/data-table/example/locale/en-US.ts
index a8fab3e289d..fefb6c94cd5 100644
--- a/packages/varlet-ui/src/data-table/example/locale/en-US.ts
+++ b/packages/varlet-ui/src/data-table/example/locale/en-US.ts
@@ -7,6 +7,8 @@ export default {
customRender: 'Custom Render',
spans: 'Cell Spans',
selection: 'Selection',
+ singleSelection: 'Single Selection',
+ tree: 'Tree Data',
expand: 'Expand',
selectedRows: 'Selected Rows',
pagerPagination: 'Pager Pagination',
diff --git a/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts b/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
index 25779689afd..71b0659aa0f 100644
--- a/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
+++ b/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
@@ -7,6 +7,8 @@ export default {
customRender: '自定义渲染',
spans: '单元格合并',
selection: '选择列',
+ singleSelection: '单选',
+ tree: '树形数据',
expand: '展开列',
selectedRows: '已选行',
pagerPagination: '页码分页',
diff --git a/packages/varlet-ui/src/data-table/props.ts b/packages/varlet-ui/src/data-table/props.ts
index d6c4ff2bbeb..a56cba96bb5 100644
--- a/packages/varlet-ui/src/data-table/props.ts
+++ b/packages/varlet-ui/src/data-table/props.ts
@@ -99,6 +99,10 @@ export interface DataTablePagination {
showTotal?: (total: number, range: [number, number]) => string
}
+export interface DataTableTreeOption {
+ disabled?: boolean
+}
+
export const props = {
data: {
type: Array as PropType
,
@@ -131,6 +135,18 @@ export const props = {
},
total: [Number, String],
maxHeight: [Number, String],
+ tree: {
+ type: [Boolean, Object] as PropType,
+ default: false,
+ },
+ cascade: {
+ type: Boolean,
+ default: true,
+ },
+ childrenKey: {
+ type: String,
+ default: 'children',
+ },
checkedRowKeys: {
type: Array as PropType,
default: () => [],
diff --git a/packages/varlet-ui/src/menu-select/menuSelect.less b/packages/varlet-ui/src/menu-select/menuSelect.less
index d5fc065c22e..5dc68636d95 100644
--- a/packages/varlet-ui/src/menu-select/menuSelect.less
+++ b/packages/varlet-ui/src/menu-select/menuSelect.less
@@ -7,6 +7,7 @@
.var-menu-select {
&__menu {
+ --scrollbar-track-background: var(--menu-select-menu-background-color);
padding: var(--menu-select-menu-padding);
border-radius: var(--menu-select-menu-border-radius);
background-color: var(--menu-select-menu-background-color);
diff --git a/packages/varlet-ui/src/menu/menu.less b/packages/varlet-ui/src/menu/menu.less
index 459965ece1f..3e9291e8f35 100644
--- a/packages/varlet-ui/src/menu/menu.less
+++ b/packages/varlet-ui/src/menu/menu.less
@@ -8,6 +8,7 @@
outline: none;
&__menu {
+ --scrollbar-track-background: var(--menu-background-color);
border-radius: var(--menu-border-radius);
}
diff --git a/packages/varlet-ui/src/select/select.less b/packages/varlet-ui/src/select/select.less
index f2d1ab2396d..f91a63c1dee 100644
--- a/packages/varlet-ui/src/select/select.less
+++ b/packages/varlet-ui/src/select/select.less
@@ -36,6 +36,7 @@
}
&__scroller {
+ --scrollbar-track-background: var(--select-scroller-background);
max-height: var(--select-scroller-max-height);
background: var(--select-scroller-background);
padding: var(--select-scroller-padding);
diff --git a/packages/varlet-ui/src/styles/common.less b/packages/varlet-ui/src/styles/common.less
index 71392f8ad3a..2b2fdae16c3 100644
--- a/packages/varlet-ui/src/styles/common.less
+++ b/packages/varlet-ui/src/styles/common.less
@@ -31,26 +31,28 @@
&--scrollbar {
scrollbar-width: thin;
- scrollbar-color: hsla(var(--hsl-text), 0.28) transparent;
+ scrollbar-color: hsla(var(--hsl-text), 0.28) var(--scrollbar-track-background, transparent);
&::-webkit-scrollbar {
width: 4px;
height: 4px;
- background: transparent;
+ background: var(--scrollbar-track-background, transparent);
}
&::-webkit-scrollbar-track {
- background: transparent;
+ background: var(--scrollbar-track-background, transparent);
}
&::-webkit-scrollbar-thumb {
border-radius: 999px;
background: hsla(var(--hsl-text), 0.28);
+ border: 1px solid var(--scrollbar-track-background, transparent);
+ background-clip: padding-box;
cursor: pointer;
}
&::-webkit-scrollbar-corner {
- background: transparent;
+ background: var(--scrollbar-track-background, transparent);
}
}
}
diff --git a/packages/varlet-ui/src/table/props.ts b/packages/varlet-ui/src/table/props.ts
index 324692950d6..fa260316249 100644
--- a/packages/varlet-ui/src/table/props.ts
+++ b/packages/varlet-ui/src/table/props.ts
@@ -1,3 +1,7 @@
+import type { PropType } from 'vue'
+
+export type TableSurface = 'low'
+
export const props = {
fullWidth: {
type: [Number, String],
@@ -10,4 +14,5 @@ export const props = {
type: [Boolean, Number, String],
default: true,
},
+ surface: String as PropType,
}
diff --git a/packages/varlet-ui/src/table/table.less b/packages/varlet-ui/src/table/table.less
index a8ba067940e..7d07923812a 100644
--- a/packages/varlet-ui/src/table/table.less
+++ b/packages/varlet-ui/src/table/table.less
@@ -17,6 +17,7 @@
}
.var-table {
+ --scrollbar-track-background: var(--table-background);
width: 100%;
border-radius: var(--table-border-radius);
diff --git a/packages/varlet-ui/types/dataTable.d.ts b/packages/varlet-ui/types/dataTable.d.ts
index 39fb3f27f4c..ad89dbac040 100644
--- a/packages/varlet-ui/types/dataTable.d.ts
+++ b/packages/varlet-ui/types/dataTable.d.ts
@@ -93,6 +93,10 @@ export interface DataTablePagination {
showTotal?: (total: number, range: [number, number]) => string
}
+export interface DataTableTreeOption {
+ disabled?: boolean
+}
+
export interface DataTableProps extends BasicAttributes {
data?: any[]
columns?: DataTableColumn[]
@@ -105,6 +109,9 @@ export interface DataTableProps extends BasicAttributes {
pageSize?: number | string
total?: number | string
maxHeight?: number | string
+ tree?: boolean | DataTableTreeOption
+ cascade?: boolean
+ childrenKey?: string
elevation?: boolean | string | number
striped?: boolean
cellBordered?: boolean
diff --git a/packages/varlet-ui/varlet.config.mjs b/packages/varlet-ui/varlet.config.mjs
index 4c02684bddb..5b049470242 100644
--- a/packages/varlet-ui/varlet.config.mjs
+++ b/packages/varlet-ui/varlet.config.mjs
@@ -392,6 +392,14 @@ export default defineConfig({
doc: 'data-table',
type: 2,
},
+ {
+ text: {
+ 'zh-CN': 'Pagination 分页',
+ 'en-US': 'Pagination',
+ },
+ doc: 'pagination',
+ type: 2,
+ },
{
text: {
'zh-CN': 'Watermark 水印',
@@ -557,14 +565,6 @@ export default defineConfig({
doc: 'popup',
type: 2,
},
- {
- text: {
- 'zh-CN': 'Pagination 分页',
- 'en-US': 'Pagination',
- },
- doc: 'pagination',
- type: 2,
- },
{
text: {
'zh-CN': 'Menu 菜单',
From 4d47c2e90a219a2098d63b6dac69a1eccca3170f Mon Sep 17 00:00:00 2001
From: haoziqaq <357229046@qq.com>
Date: Fri, 22 May 2026 02:40:04 +0800
Subject: [PATCH 06/37] feat(data-table): add subtle background support and
enhance selection features
- Introduced `surface="low"` for a subtle background style in the data table.
- Enhanced selection column to support non-cascading tree selection and single selection mode.
- Updated rendering of expanded content for better visual structure.
- Improved pagination handling with local and remote data management.
- Added new localization keys for surface background and tree selection features.
- Refactored props and types for better clarity and consistency.
- Updated styles to accommodate new surface background and text color variables.
---
.../varlet-ui/src/data-table/DataTable.vue | 1034 +++++++++--------
.../src/data-table/__tests__/index.spec.js | 26 +-
.../varlet-ui/src/data-table/dataTable.less | 37 +-
.../varlet-ui/src/data-table/docs/en-US.md | 123 +-
.../varlet-ui/src/data-table/docs/zh-CN.md | 123 +-
.../src/data-table/example/index.vue | 108 +-
.../src/data-table/example/locale/en-US.ts | 3 +
.../src/data-table/example/locale/zh-CN.ts | 3 +
packages/varlet-ui/src/data-table/props.ts | 59 +-
packages/varlet-ui/src/locale/en-US.ts | 2 +
packages/varlet-ui/src/locale/fa-IR.ts | 2 +
packages/varlet-ui/src/locale/index.ts | 2 +
packages/varlet-ui/src/locale/ja-JP.ts | 2 +
packages/varlet-ui/src/locale/zh-CN.ts | 2 +
packages/varlet-ui/src/locale/zh-TW.ts | 2 +
packages/varlet-ui/src/table/Table.vue | 21 +-
packages/varlet-ui/src/table/table.less | 5 +
.../__snapshots__/index.spec.js.snap | 35 +-
.../varlet-ui/src/themes/dark/dataTable.ts | 10 +-
.../src/themes/md3-dark/dataTable.ts | 11 +-
.../src/themes/md3-light/dataTable.ts | 11 +-
packages/varlet-ui/types/dataTable.d.ts | 56 +-
22 files changed, 1034 insertions(+), 643 deletions(-)
diff --git a/packages/varlet-ui/src/data-table/DataTable.vue b/packages/varlet-ui/src/data-table/DataTable.vue
index f330a335c2d..31d08df6ff7 100644
--- a/packages/varlet-ui/src/data-table/DataTable.vue
+++ b/packages/varlet-ui/src/data-table/DataTable.vue
@@ -5,15 +5,19 @@
n(),
formatElevation(elevation, 1),
n('$--box'),
+ [surface === 'low', n('--surface-low')],
[cellBordered, n('--cell-bordered')],
- [hasPagination, n('--with-footer')],
+ [showFooter, n('--with-footer')],
n(`--${size}`),
)
"
>
-
-
+
+
-
+
-
+
+
-
+
-
+
-
+
-
-
+
+
+
+
-
+
{{ resolvedEmptyText }}
-
+
+
+
+
+```
+
### 单元格合并
通过 `rowSpan`、`colSpan` 和 `titleColSpan` 控制表体与表头合并。返回 `0` 时当前单元格不渲染,通常配合前一个单元格的跨行或跨列使用。
@@ -165,7 +175,7 @@ const data = [
### 选择列
-通过 `type: 'selection'` 渲染选择列,并使用 `v-model:checked-row-keys` 控制选中行。设置 `multiple: false` 可切换为单选,设置 `disabled` 可禁用整列选择或按行禁用。
+通过 `type: 'selection'` 渲染选择列,并使用 `v-model:checked-row-keys` 控制选中行。设置 `multiple: false` 可切换为单选,设置 `disabled` 可禁用整列选择或按行禁用。单选模式下会使用 `radio`,而不是 `checkbox`。
```html
+
+ {{ t('surfaceLow') }}
+
+
{{ t('spans') }}
@@ -228,6 +268,27 @@ const remoteData = computed(() => {
children-key="nodes"
/>
+ {{ t('treeNonCascade') }}
+
+
+ {{ t('treeSingleSelection') }}
+
+
{{ t('expand') }}
@@ -235,7 +296,13 @@ const remoteData = computed(() => {
{{ t('localPagination') }}
-
+
{{ t('remotePagination') }}
{
v-model:page-size="remotePageSize"
:columns="columns"
:data="remoteData"
+ :loading="remoteLoading"
:total="manyRows.length"
:pagination="defaultPagination"
remote
diff --git a/packages/varlet-ui/src/data-table/example/locale/en-US.ts b/packages/varlet-ui/src/data-table/example/locale/en-US.ts
index fefb6c94cd5..781cdd0bc4f 100644
--- a/packages/varlet-ui/src/data-table/example/locale/en-US.ts
+++ b/packages/varlet-ui/src/data-table/example/locale/en-US.ts
@@ -5,10 +5,13 @@ export default {
columnOptions: 'Column Options',
customProps: 'Custom Props',
customRender: 'Custom Render',
+ surfaceLow: 'Subtle Background',
spans: 'Cell Spans',
selection: 'Selection',
singleSelection: 'Single Selection',
tree: 'Tree Data',
+ treeNonCascade: 'Tree Non-Cascade',
+ treeSingleSelection: 'Tree Single Selection',
expand: 'Expand',
selectedRows: 'Selected Rows',
pagerPagination: 'Pager Pagination',
diff --git a/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts b/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
index 71b0659aa0f..92d8f73757c 100644
--- a/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
+++ b/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
@@ -5,10 +5,13 @@ export default {
columnOptions: '列配置',
customProps: '属性透传',
customRender: '自定义渲染',
+ surfaceLow: '弱背景色',
spans: '单元格合并',
selection: '选择列',
singleSelection: '单选',
tree: '树形数据',
+ treeNonCascade: '树形非级联',
+ treeSingleSelection: '树形单选',
expand: '展开列',
selectedRows: '已选行',
pagerPagination: '页码分页',
diff --git a/packages/varlet-ui/src/data-table/props.ts b/packages/varlet-ui/src/data-table/props.ts
index a56cba96bb5..0bc7f6b1578 100644
--- a/packages/varlet-ui/src/data-table/props.ts
+++ b/packages/varlet-ui/src/data-table/props.ts
@@ -1,41 +1,43 @@
import { type HTMLAttributes, type PropType, type VNodeChild } from 'vue'
import { defineListenerProp } from '../utils/components'
+export type DataTableSurface = 'low'
+
+export type DataTableTableLayout = 'auto' | 'fixed'
+
export type DataTableKey = string | number
-export type DataTableAlign = 'left' | 'center' | 'right'
-export type DataTableFixed = 'left' | 'right'
+export type DataTableColumnAlign = 'left' | 'center' | 'right'
+
+export type DataTableColumnFixed = 'left' | 'right'
export type DataTableRowKey =
| Extract
| string
| ((row: Row, rowIndex: number) => DataTableKey)
-export interface DataTableColumnRenderContext {
+export interface DataTableRowBaseContext {
row: Row
rowIndex: number
- pageRowIndex: number
- column: DataTableColumn
}
-export interface DataTableRowPropsContext {
- row: Row
- rowIndex: number
- pageRowIndex: number
-}
+export interface DataTableColumnRenderContext extends DataTableRowBaseContext {}
-export interface DataTableCellPropsContext extends DataTableRowPropsContext {
- column: DataTableColumn
-}
+export interface DataTableRowPropsContext extends DataTableRowBaseContext {}
+
+export interface DataTableCellPropsContext extends DataTableRowBaseContext {}
-export type DataTableCellSpan = number | ((context: DataTableCellPropsContext) => number)
-export type DataTableSelectionDisabled = boolean | ((context: DataTableRowPropsContext) => boolean)
+export type DataTableColumnCellSpan = number | ((context: DataTableCellPropsContext) => number)
-export interface DataTableSelectionColumnContext extends DataTableRowPropsContext {
+export type DataTableColumnSelectionDisabled =
+ | boolean
+ | ((context: DataTableRowPropsContext) => boolean)
+
+export interface DataTableSelectionColumnContext extends DataTableRowBaseContext {
checked: boolean
}
-export interface DataTableExpandColumnContext extends DataTableRowPropsContext {
+export interface DataTableExpandColumnContext extends DataTableRowBaseContext {
expanded: boolean
}
@@ -48,14 +50,14 @@ export type DataTableCellProps =
| ((context: DataTableCellPropsContext) => HTMLAttributes | undefined)
export interface DataTableBaseColumn {
- fixed?: DataTableFixed
+ fixed?: DataTableColumnFixed
width?: string | number
minWidth?: string | number
- align?: DataTableAlign
- titleAlign?: DataTableAlign
+ align?: DataTableColumnAlign
+ titleAlign?: DataTableColumnAlign
titleColSpan?: number
- colSpan?: DataTableCellSpan
- rowSpan?: DataTableCellSpan
+ colSpan?: DataTableColumnCellSpan
+ rowSpan?: DataTableColumnCellSpan
cellProps?: DataTableCellProps
}
@@ -71,7 +73,7 @@ export interface DataTableSelectionColumn extends DataTableBaseColumn
key?: string
title?: string
multiple?: boolean
- disabled?: DataTableSelectionDisabled
+ disabled?: DataTableColumnSelectionDisabled
render?: never
}
@@ -126,19 +128,20 @@ export const props = {
},
remote: Boolean,
page: {
- type: [Number, String],
+ type: Number,
default: 1,
},
pageSize: {
- type: [Number, String],
+ type: Number,
default: 10,
},
- total: [Number, String],
+ total: Number,
maxHeight: [Number, String],
tree: {
type: [Boolean, Object] as PropType,
default: false,
},
+ surface: String as PropType,
cascade: {
type: Boolean,
default: true,
@@ -156,6 +159,10 @@ export const props = {
default: true,
},
cellBordered: Boolean,
+ tableLayout: {
+ type: String as PropType,
+ default: 'auto',
+ },
size: {
type: String as PropType<'small' | 'normal' | 'large'>,
default: 'normal',
diff --git a/packages/varlet-ui/src/locale/en-US.ts b/packages/varlet-ui/src/locale/en-US.ts
index dafbd5de026..c71f2244606 100644
--- a/packages/varlet-ui/src/locale/en-US.ts
+++ b/packages/varlet-ui/src/locale/en-US.ts
@@ -106,4 +106,6 @@ export default {
timePickerHint: 'SELECT TIME',
// select
selectEmptyText: 'No Data',
+ // data-table
+ dataTableEmptyText: 'No Data',
} satisfies Message
diff --git a/packages/varlet-ui/src/locale/fa-IR.ts b/packages/varlet-ui/src/locale/fa-IR.ts
index 12921a1f473..315b0fb7e8d 100644
--- a/packages/varlet-ui/src/locale/fa-IR.ts
+++ b/packages/varlet-ui/src/locale/fa-IR.ts
@@ -106,4 +106,6 @@ export default {
timePickerHint: 'انتخاب زمان',
// select
selectEmptyText: 'دادهای وجود ندارد',
+ // data-table
+ dataTableEmptyText: 'دادهای وجود ندارد',
} satisfies Message
diff --git a/packages/varlet-ui/src/locale/index.ts b/packages/varlet-ui/src/locale/index.ts
index 7580f2a6ef2..68d695a8f55 100644
--- a/packages/varlet-ui/src/locale/index.ts
+++ b/packages/varlet-ui/src/locale/index.ts
@@ -36,6 +36,8 @@ export type Message = {
timePickerHint: string
// select
selectEmptyText: string
+ // data-table
+ dataTableEmptyText: string
// internal
lang?: string
[key: PropertyKey]: any
diff --git a/packages/varlet-ui/src/locale/ja-JP.ts b/packages/varlet-ui/src/locale/ja-JP.ts
index 49f71a22fd4..a7b3813ad1b 100644
--- a/packages/varlet-ui/src/locale/ja-JP.ts
+++ b/packages/varlet-ui/src/locale/ja-JP.ts
@@ -106,4 +106,6 @@ export default {
timePickerHint: '時間を選択',
// select
selectEmptyText: 'データがありません',
+ // data-table
+ dataTableEmptyText: 'データがありません',
} satisfies Message
diff --git a/packages/varlet-ui/src/locale/zh-CN.ts b/packages/varlet-ui/src/locale/zh-CN.ts
index 373304d443b..6dc9777f778 100644
--- a/packages/varlet-ui/src/locale/zh-CN.ts
+++ b/packages/varlet-ui/src/locale/zh-CN.ts
@@ -106,4 +106,6 @@ export default {
timePickerHint: '选择时间',
// select
selectEmptyText: '暂无数据',
+ // data-table
+ dataTableEmptyText: '暂无数据',
} satisfies Message
diff --git a/packages/varlet-ui/src/locale/zh-TW.ts b/packages/varlet-ui/src/locale/zh-TW.ts
index 5c3dbe8ea92..cb3bbd40095 100644
--- a/packages/varlet-ui/src/locale/zh-TW.ts
+++ b/packages/varlet-ui/src/locale/zh-TW.ts
@@ -106,4 +106,6 @@ export default {
timePickerHint: '選擇時間',
// select
selectEmptyText: '暫無數據',
+ // data-table
+ dataTableEmptyText: '暫無數據',
} satisfies Message
diff --git a/packages/varlet-ui/src/table/Table.vue b/packages/varlet-ui/src/table/Table.vue
index c33d60f100d..d4aeef76a05 100644
--- a/packages/varlet-ui/src/table/Table.vue
+++ b/packages/varlet-ui/src/table/Table.vue
@@ -1,5 +1,5 @@
-
+
@@ -12,7 +12,7 @@
diff --git a/packages/varlet-ui/src/table/table.less b/packages/varlet-ui/src/table/table.less
index 7d07923812a..e16fd074990 100644
--- a/packages/varlet-ui/src/table/table.less
+++ b/packages/varlet-ui/src/table/table.less
@@ -1,5 +1,6 @@
:root {
--table-background: #fff;
+ --table-surface-low-background: var(--color-surface-container-highest);
--table-border-radius: 2px;
--table-thead-border-bottom: thin solid var(--color-outline);
--table-thead-th-text-color: rgba(0, 0, 0, 0.6);
@@ -96,6 +97,10 @@
}
}
+ &--surface-low {
+ --table-background: var(--table-surface-low-background);
+ }
+
&__footer {
width: 100%;
min-height: var(--table-row-height);
diff --git a/packages/varlet-ui/src/themes/__tests__/__snapshots__/index.spec.js.snap b/packages/varlet-ui/src/themes/__tests__/__snapshots__/index.spec.js.snap
index 8d8d08ad2c3..6278fb00fb4 100644
--- a/packages/varlet-ui/src/themes/__tests__/__snapshots__/index.spec.js.snap
+++ b/packages/varlet-ui/src/themes/__tests__/__snapshots__/index.spec.js.snap
@@ -306,19 +306,18 @@ exports[`dark theme 1`] = `
"--data-table-border-color": "var(--color-outline)",
"--data-table-border-radius": "2px",
"--data-table-cell-font-size": "16px",
- "--data-table-cell-min-width": "120px",
"--data-table-cell-padding": "0 16px",
- "--data-table-empty-color": "var(--color-text-disabled)",
"--data-table-empty-padding": "48px 16px",
+ "--data-table-empty-text-color": "var(--color-text-disabled)",
"--data-table-footer-padding": "12px 16px",
"--data-table-header-background": "#303030",
- "--data-table-header-color": "rgba(255, 255, 255, 0.6)",
"--data-table-header-font-size": "14px",
+ "--data-table-header-text-color": "rgba(255, 255, 255, 0.6)",
"--data-table-hover-background": "#4c4b4b",
- "--data-table-row-color": "#fff",
"--data-table-row-height": "46px",
- "--data-table-row-height-large": "52px",
- "--data-table-row-height-small": "40px",
+ "--data-table-row-large-height": "52px",
+ "--data-table-row-small-height": "40px",
+ "--data-table-row-text-color": "#fff",
"--data-table-striped-background": "#303030",
"--date-picker-actions-padding": "0 8px 12px 8px",
"--date-picker-body-background-color": "#303030",
@@ -1280,20 +1279,20 @@ exports[`md3Dark theme 1`] = `
"--data-table-border-color": "var(--color-outline)",
"--data-table-border-radius": "2px",
"--data-table-cell-font-size": "16px",
- "--data-table-cell-min-width": "120px",
"--data-table-cell-padding": "0 16px",
- "--data-table-empty-color": "var(--color-text-disabled)",
"--data-table-empty-padding": "48px 16px",
+ "--data-table-empty-text-color": "var(--color-text-disabled)",
"--data-table-footer-padding": "12px 16px",
"--data-table-header-background": "var(--color-surface-container-highest)",
- "--data-table-header-color": "rgba(255, 255, 255, 0.6)",
"--data-table-header-font-size": "14px",
+ "--data-table-header-text-color": "rgba(255, 255, 255, 0.6)",
"--data-table-hover-background": "var(--color-surface-container-highest)",
- "--data-table-row-color": "#fff",
"--data-table-row-height": "46px",
- "--data-table-row-height-large": "52px",
- "--data-table-row-height-small": "40px",
+ "--data-table-row-large-height": "52px",
+ "--data-table-row-small-height": "40px",
+ "--data-table-row-text-color": "#fff",
"--data-table-striped-background": "var(--color-surface-container-highest)",
+ "--data-table-surface-low-background": "#1c1b1d",
"--date-picker-actions-padding": "20px",
"--date-picker-body-background-color": "var(--color-surface-container-high)",
"--date-picker-body-height": "300px",
@@ -2239,20 +2238,20 @@ exports[`md3Light theme 1`] = `
"--data-table-border-color": "var(--color-outline)",
"--data-table-border-radius": "2px",
"--data-table-cell-font-size": "16px",
- "--data-table-cell-min-width": "120px",
"--data-table-cell-padding": "0 16px",
- "--data-table-empty-color": "var(--color-text-disabled)",
"--data-table-empty-padding": "48px 16px",
+ "--data-table-empty-text-color": "var(--color-text-disabled)",
"--data-table-footer-padding": "12px 16px",
"--data-table-header-background": "var(--color-surface-container-low)",
- "--data-table-header-color": "rgba(0, 0, 0, 0.6)",
"--data-table-header-font-size": "14px",
+ "--data-table-header-text-color": "rgba(0, 0, 0, 0.6)",
"--data-table-hover-background": "var(--color-surface-container-low)",
- "--data-table-row-color": "#555",
"--data-table-row-height": "46px",
- "--data-table-row-height-large": "52px",
- "--data-table-row-height-small": "40px",
+ "--data-table-row-large-height": "52px",
+ "--data-table-row-small-height": "40px",
+ "--data-table-row-text-color": "#555",
"--data-table-striped-background": "var(--color-surface-container-low)",
+ "--data-table-surface-low-background": "var(--color-surface-container-low)",
"--date-picker-actions-padding": "20px",
"--date-picker-body-background-color": "var(--color-surface-container-high)",
"--date-picker-body-height": "300px",
diff --git a/packages/varlet-ui/src/themes/dark/dataTable.ts b/packages/varlet-ui/src/themes/dark/dataTable.ts
index 2f53b8d2bd2..aaa230ef6a7 100644
--- a/packages/varlet-ui/src/themes/dark/dataTable.ts
+++ b/packages/varlet-ui/src/themes/dark/dataTable.ts
@@ -1,19 +1,19 @@
export default {
'--data-table-background': '#303030',
'--data-table-header-background': '#303030',
- '--data-table-header-color': 'rgba(255, 255, 255, 0.6)',
- '--data-table-row-color': '#fff',
+ '--data-table-header-text-color': 'rgba(255, 255, 255, 0.6)',
+ '--data-table-row-text-color': '#fff',
'--data-table-border-color': 'var(--color-outline)',
'--data-table-hover-background': '#4c4b4b',
'--data-table-striped-background': '#303030',
- '--data-table-empty-color': 'var(--color-text-disabled)',
+ '--data-table-empty-text-color': 'var(--color-text-disabled)',
'--data-table-border-radius': '2px',
'--data-table-cell-padding': '0 16px',
'--data-table-cell-font-size': '16px',
'--data-table-header-font-size': '14px',
'--data-table-row-height': '46px',
- '--data-table-row-height-small': '40px',
- '--data-table-row-height-large': '52px',
+ '--data-table-row-small-height': '40px',
+ '--data-table-row-large-height': '52px',
'--data-table-footer-padding': '12px 16px',
'--data-table-empty-padding': '48px 16px',
}
diff --git a/packages/varlet-ui/src/themes/md3-dark/dataTable.ts b/packages/varlet-ui/src/themes/md3-dark/dataTable.ts
index 204c125ae16..b21c225a1eb 100644
--- a/packages/varlet-ui/src/themes/md3-dark/dataTable.ts
+++ b/packages/varlet-ui/src/themes/md3-dark/dataTable.ts
@@ -1,19 +1,20 @@
export default {
'--data-table-background': 'var(--color-surface-container-highest)',
+ '--data-table-surface-low-background': '#1c1b1d',
'--data-table-header-background': 'var(--color-surface-container-highest)',
- '--data-table-header-color': 'rgba(255, 255, 255, 0.6)',
- '--data-table-row-color': '#fff',
+ '--data-table-header-text-color': 'rgba(255, 255, 255, 0.6)',
+ '--data-table-row-text-color': '#fff',
'--data-table-border-color': 'var(--color-outline)',
'--data-table-hover-background': 'var(--color-surface-container-highest)',
'--data-table-striped-background': 'var(--color-surface-container-highest)',
- '--data-table-empty-color': 'var(--color-text-disabled)',
+ '--data-table-empty-text-color': 'var(--color-text-disabled)',
'--data-table-border-radius': '2px',
'--data-table-cell-padding': '0 16px',
'--data-table-cell-font-size': '16px',
'--data-table-header-font-size': '14px',
'--data-table-row-height': '46px',
- '--data-table-row-height-small': '40px',
- '--data-table-row-height-large': '52px',
+ '--data-table-row-small-height': '40px',
+ '--data-table-row-large-height': '52px',
'--data-table-footer-padding': '12px 16px',
'--data-table-empty-padding': '48px 16px',
}
diff --git a/packages/varlet-ui/src/themes/md3-light/dataTable.ts b/packages/varlet-ui/src/themes/md3-light/dataTable.ts
index 91e35eb3d42..f7c16352a6e 100644
--- a/packages/varlet-ui/src/themes/md3-light/dataTable.ts
+++ b/packages/varlet-ui/src/themes/md3-light/dataTable.ts
@@ -1,19 +1,20 @@
export default {
'--data-table-background': 'var(--color-surface-container-low)',
+ '--data-table-surface-low-background': 'var(--color-surface-container-low)',
'--data-table-header-background': 'var(--color-surface-container-low)',
- '--data-table-header-color': 'rgba(0, 0, 0, 0.6)',
- '--data-table-row-color': '#555',
+ '--data-table-header-text-color': 'rgba(0, 0, 0, 0.6)',
+ '--data-table-row-text-color': '#555',
'--data-table-border-color': 'var(--color-outline)',
'--data-table-hover-background': 'var(--color-surface-container-low)',
'--data-table-striped-background': 'var(--color-surface-container-low)',
- '--data-table-empty-color': 'var(--color-text-disabled)',
+ '--data-table-empty-text-color': 'var(--color-text-disabled)',
'--data-table-border-radius': '2px',
'--data-table-cell-padding': '0 16px',
'--data-table-cell-font-size': '16px',
'--data-table-header-font-size': '14px',
'--data-table-row-height': '46px',
- '--data-table-row-height-small': '40px',
- '--data-table-row-height-large': '52px',
+ '--data-table-row-small-height': '40px',
+ '--data-table-row-large-height': '52px',
'--data-table-footer-padding': '12px 16px',
'--data-table-empty-padding': '48px 16px',
}
diff --git a/packages/varlet-ui/types/dataTable.d.ts b/packages/varlet-ui/types/dataTable.d.ts
index ad89dbac040..6b7912bdf77 100644
--- a/packages/varlet-ui/types/dataTable.d.ts
+++ b/packages/varlet-ui/types/dataTable.d.ts
@@ -5,31 +5,34 @@ export declare const dataTableProps: Record
export type DataTableKey = string | number
-export type DataTableAlign = 'left' | 'center' | 'right'
-export type DataTableFixed = 'left' | 'right'
+export type DataTableColumnAlign = 'left' | 'center' | 'right'
+export type DataTableColumnFixed = 'left' | 'right'
+export type DataTableSurface = 'low'
+export type DataTableTableLayout = 'auto' | 'fixed'
export type DataTableRowKey = keyof Row | string | ((row: Row, rowIndex: number) => DataTableKey)
-export interface DataTableColumnRenderContext {
+export interface DataTableRowBaseContext {
row: Row
rowIndex: number
- pageRowIndex: number
- column: DataTableColumn
}
-export interface DataTableRowPropsContext {
- row: Row
- rowIndex: number
- pageRowIndex: number
-}
+export interface DataTableColumnRenderContext extends DataTableRowBaseContext {}
+
+export interface DataTableRowPropsContext extends DataTableRowBaseContext {}
+
+export interface DataTableCellPropsContext extends DataTableRowBaseContext {}
+
+export type DataTableColumnCellSpan = number | ((context: DataTableCellPropsContext) => number)
+export type DataTableColumnSelectionDisabled =
+ | boolean
+ | ((context: DataTableRowPropsContext) => boolean)
-export interface DataTableCellPropsContext extends DataTableRowPropsContext {
- column: DataTableColumn
+export interface DataTableSelectionColumnContext extends DataTableRowBaseContext {
+ checked: boolean
}
-export type DataTableCellSpan = number | ((context: DataTableCellPropsContext) => number)
-export type DataTableSelectionDisabled = boolean | ((context: DataTableRowPropsContext) => boolean)
-export interface DataTableExpandColumnContext extends DataTableRowPropsContext {
+export interface DataTableExpandColumnContext extends DataTableRowBaseContext {
expanded: boolean
}
@@ -42,14 +45,14 @@ export type DataTableCellProps =
| ((context: DataTableCellPropsContext) => HTMLAttributes | undefined)
export interface DataTableBaseColumn {
- fixed?: DataTableFixed
+ fixed?: DataTableColumnFixed
width?: string | number
minWidth?: string | number
- align?: DataTableAlign
- titleAlign?: DataTableAlign
+ align?: DataTableColumnAlign
+ titleAlign?: DataTableColumnAlign
titleColSpan?: number
- colSpan?: DataTableCellSpan
- rowSpan?: DataTableCellSpan
+ colSpan?: DataTableColumnCellSpan
+ rowSpan?: DataTableColumnCellSpan
cellProps?: DataTableCellProps
}
@@ -65,7 +68,7 @@ export interface DataTableSelectionColumn extends DataTableBaseColumn
key?: string
title?: string
multiple?: boolean
- disabled?: DataTableSelectionDisabled
+ disabled?: DataTableColumnSelectionDisabled
render?: never
}
@@ -105,18 +108,19 @@ export interface DataTableProps extends BasicAttributes {
loading?: boolean
pagination?: boolean | DataTablePagination
remote?: boolean
- page?: number | string
- pageSize?: number | string
- total?: number | string
+ page?: number
+ pageSize?: number
+ total?: number
maxHeight?: number | string
tree?: boolean | DataTableTreeOption
+ surface?: DataTableSurface
cascade?: boolean
childrenKey?: string
elevation?: boolean | string | number
- striped?: boolean
cellBordered?: boolean
+ tableLayout?: DataTableTableLayout
size?: 'small' | 'normal' | 'large'
- emptyText?: string
+ checkedRowKeys?: DataTableKey[]
'onUpdate:checkedRowKeys'?: ListenerProp<(checkedRowKeys: DataTableKey[]) => void>
'onUpdate:page'?: ListenerProp<(page: number) => void>
'onUpdate:pageSize'?: ListenerProp<(pageSize: number) => void>
From 2f0b60a0075cc603700bf2fe2eec405399ac7be8 Mon Sep 17 00:00:00 2001
From: haoziqaq <357229046@qq.com>
Date: Fri, 22 May 2026 02:47:45 +0800
Subject: [PATCH 07/37] feat(data-table): implement useColumnsFixedOffsets for
managing fixed column offsets
---
.../varlet-ui/src/data-table/DataTable.vue | 41 ++-------------
.../src/data-table/useColumnsFixedOffsets.ts | 51 +++++++++++++++++++
2 files changed, 54 insertions(+), 38 deletions(-)
create mode 100644 packages/varlet-ui/src/data-table/useColumnsFixedOffsets.ts
diff --git a/packages/varlet-ui/src/data-table/DataTable.vue b/packages/varlet-ui/src/data-table/DataTable.vue
index 31d08df6ff7..caae1fbc18f 100644
--- a/packages/varlet-ui/src/data-table/DataTable.vue
+++ b/packages/varlet-ui/src/data-table/DataTable.vue
@@ -195,6 +195,7 @@ import {
type DataTableSelectionColumn,
type DataTableTreeOption,
} from './props'
+import { useColumnsFixedOffsets } from './useColumnsFixedOffsets'
const { name, n, classes } = createNamespace('data-table')
const defaultDataTableControlColumnWidth = 52
@@ -275,7 +276,7 @@ export default defineComponent({
props,
setup(props) {
const { t: pt } = injectLocaleProvider()
- const { page, pageSize } = toRefs(props)
+ const { columns, page, pageSize } = toRefs(props)
const checkedRowKeys = ref([...props.checkedRowKeys])
const expandedRowKeys = ref(new Set())
const collapsedTreeRowKeys = ref(new Set())
@@ -306,14 +307,7 @@ export default defineComponent({
return 0
})
})
-
- const leftFixedOffsets = computed(() => {
- return buildFixedOffsets('left')
- })
-
- const rightFixedOffsets = computed(() => {
- return buildFixedOffsets('right')
- })
+ const { leftFixedOffsets, rightFixedOffsets } = useColumnsFixedOffsets({ columns, columnWidths })
const paginationTotal = computed(() => {
if (props.pagination === false) {
@@ -627,35 +621,6 @@ export default defineComponent({
{ immediate: true },
)
- function buildFixedOffsets(direction: DataTableColumnFixed) {
- const offsets = Array(props.columns.length).fill(undefined)
- let offset = 0
-
- if (direction === 'left') {
- for (let index = 0; index < props.columns.length; index += 1) {
- if (props.columns[index].fixed !== 'left') {
- continue
- }
-
- offsets[index] = offset
- offset += columnWidths.value[index]
- }
-
- return offsets
- }
-
- for (let index = props.columns.length - 1; index >= 0; index -= 1) {
- if (props.columns[index].fixed !== 'right') {
- continue
- }
-
- offsets[index] = offset
- offset += columnWidths.value[index]
- }
-
- return offsets
- }
-
function getRowKey(row: Record, rowIndex: number) {
if (isFunction(props.rowKey)) {
return props.rowKey(row, rowIndex)
diff --git a/packages/varlet-ui/src/data-table/useColumnsFixedOffsets.ts b/packages/varlet-ui/src/data-table/useColumnsFixedOffsets.ts
new file mode 100644
index 00000000000..21dc2f30043
--- /dev/null
+++ b/packages/varlet-ui/src/data-table/useColumnsFixedOffsets.ts
@@ -0,0 +1,51 @@
+import { computed, type Ref } from 'vue'
+import type { DataTableColumn, DataTableColumnFixed } from './props'
+
+export interface UseColumnsFixedOffsetsOptions {
+ columns: Ref
+ columnWidths: Ref
+}
+
+export function useColumnsFixedOffsets({ columns, columnWidths }: UseColumnsFixedOffsetsOptions) {
+ const leftFixedOffsets = computed(() => {
+ return buildFixedOffsets('left')
+ })
+
+ const rightFixedOffsets = computed(() => {
+ return buildFixedOffsets('right')
+ })
+
+ function buildFixedOffsets(direction: DataTableColumnFixed) {
+ const offsets = Array(columns.value.length).fill(undefined)
+ let offset = 0
+
+ if (direction === 'left') {
+ for (let index = 0; index < columns.value.length; index += 1) {
+ if (columns.value[index].fixed !== 'left') {
+ continue
+ }
+
+ offsets[index] = offset
+ offset += columnWidths.value[index]
+ }
+
+ return offsets
+ }
+
+ for (let index = columns.value.length - 1; index >= 0; index -= 1) {
+ if (columns.value[index].fixed !== 'right') {
+ continue
+ }
+
+ offsets[index] = offset
+ offset += columnWidths.value[index]
+ }
+
+ return offsets
+ }
+
+ return {
+ leftFixedOffsets,
+ rightFixedOffsets,
+ }
+}
From 5530c1938fd34599e5fab6629ed34b61d32c336c Mon Sep 17 00:00:00 2001
From: haoziqaq <357229046@qq.com>
Date: Fri, 22 May 2026 21:21:26 +0800
Subject: [PATCH 08/37] refactor(data-table): optimize data handling and
improve pagination logic
- Removed unnecessary CSS variables and simplified footer styles in dataTable.less.
- Replaced Array.from with a utility function `times` for generating data arrays in documentation and examples.
- Updated DataTable props to remove the tree option interface and simplify type definitions.
- Introduced new useBodyRows and usePagination composables for better data management and pagination handling.
- Enhanced type definitions for better clarity and consistency across the data table components.
---
.../varlet-ui/src/data-table/DataTable.vue | 447 +++---------------
.../varlet-ui/src/data-table/dataTable.less | 4 -
.../varlet-ui/src/data-table/docs/en-US.md | 11 +-
.../varlet-ui/src/data-table/docs/zh-CN.md | 11 +-
.../src/data-table/example/index.vue | 5 +-
packages/varlet-ui/src/data-table/props.ts | 23 +-
.../varlet-ui/src/data-table/useBodyRows.ts | 241 ++++++++++
.../src/data-table/useColumnsFixedOffsets.ts | 43 +-
.../varlet-ui/src/data-table/usePagination.ts | 110 +++++
packages/varlet-ui/types/dataTable.d.ts | 20 +-
10 files changed, 470 insertions(+), 445 deletions(-)
create mode 100644 packages/varlet-ui/src/data-table/useBodyRows.ts
create mode 100644 packages/varlet-ui/src/data-table/usePagination.ts
diff --git a/packages/varlet-ui/src/data-table/DataTable.vue b/packages/varlet-ui/src/data-table/DataTable.vue
index caae1fbc18f..1279c30d9c8 100644
--- a/packages/varlet-ui/src/data-table/DataTable.vue
+++ b/packages/varlet-ui/src/data-table/DataTable.vue
@@ -7,7 +7,7 @@
n('$--box'),
[surface === 'low', n('--surface-low')],
[cellBordered, n('--cell-bordered')],
- [showFooter, n('--with-footer')],
+ [showPagination, n('--with-footer')],
n(`--${size}`),
)
"
@@ -51,11 +51,13 @@
tabindex="-1"
@update:model-value="toggleAllCurrentRows"
/>
- {{
- isSelectionColumn(headerCell.column) || isExpandColumn(headerCell.column)
- ? ''
- : headerCell.column.title
- }}
+
+ {{
+ isSelectionColumn(headerCell.column) || isExpandColumn(headerCell.column)
+ ? ''
+ : headerCell.column.title
+ }}
+
@@ -109,7 +111,7 @@
@@ -141,25 +143,25 @@
- {{ resolvedEmptyText }}
+ {{ (pt ? pt : t)('dataTableEmptyText') }}
-
+
@@ -172,8 +174,9 @@
+```
+
+### Resizable Columns
+
+Set `resizable` to enable drag resizing for a column. `minWidth` and `maxWidth` are used as resize limits.
+
+```html
+
+
+
+
+
+```
+
### Empty Text
```html
@@ -482,6 +520,7 @@ Set `max-height` to make the table body scroll internally while keeping the head
| `v-model:checked-row-keys` | Selected row keys | _Array
_ | `[]` |
| `total` | Total item count in remote mode | _number_ | `-` |
| `max-height` | Max height of the table body. When set, the header stays fixed and the body scrolls internally | _number \| string_ | `-` |
+| `scroll-x` | Table width used to enable horizontal scrolling. Usually paired with fixed columns | _number \| string_ | `-` |
| `table-layout` | Native `table-layout` value | _'auto' \| 'fixed'_ | `'auto'` |
| `tree` | Whether to explicitly enable tree data mode | _boolean_ | `false` |
| `cascade` | Whether tree selection should cascade | _boolean_ | `true` |
@@ -502,8 +541,10 @@ Set `max-height` to make the table body scroll internally while keeping the head
| `selectable` | Whether selection is enabled. Supports `boolean` or `(context) => boolean` | _boolean \| `(context) => boolean`_ | `true` |
| `expandable` | Whether the row can be expanded. Only works on expand columns | _`(context) => boolean`_ | `-` |
| `renderExpand` | Custom expanded content. Only works on expand columns | _`(context) => VNodeChild`_ | `-` |
+| `resizable` | Whether the column width can be resized by dragging | _boolean_ | `false` |
| `width` | Column width | _number \| string_ | `-` |
| `minWidth` | Column min width | _number \| string_ | `-` |
+| `maxWidth` | Column max width. Also used as the upper resize limit when `resizable` is enabled | _number \| string_ | `-` |
| `align` | Body cell align | _'left' \| 'center' \| 'right'_ | `'left'` |
| `titleAlign` | Header title align | _'left' \| 'center' \| 'right'_ | `align` |
| `titleColSpan` | Header col span. Set to `0` to hide the current header cell | _number_ | `1` |
diff --git a/packages/varlet-ui/src/data-table/docs/zh-CN.md b/packages/varlet-ui/src/data-table/docs/zh-CN.md
index ff3dfc33d38..a3b63ed7176 100644
--- a/packages/varlet-ui/src/data-table/docs/zh-CN.md
+++ b/packages/varlet-ui/src/data-table/docs/zh-CN.md
@@ -446,6 +446,44 @@ const columns = [{ key: 'name', title: '姓名' }]
```
+### 固定列
+
+设置 `scroll-x` 用来声明表格的横向滚动宽度,再通过 `fixed: 'left' | 'right'` 固定需要钉住的列。
+
+```html
+
+
+
+
+
+```
+
+### 可调整列宽
+
+设置 `resizable` 可以开启列拖拽调整宽度。`minWidth` 和 `maxWidth` 会作为拖拽时的宽度边界。
+
+```html
+
+
+
+
+
+```
+
### 空态文案
```html
@@ -482,6 +520,7 @@ const columns = [{ key: 'name', title: '姓名' }]
| `v-model:checked-row-keys` | 选中行的 key 集合 | _Array_ | `[]` |
| `total` | 远程分页总条数 | _number_ | `-` |
| `max-height` | 表格主体最大高度。设置后表头固定,内容区域内部滚动 | _number \| string_ | `-` |
+| `scroll-x` | 用于开启横向滚动的表格宽度,通常和固定列一起使用 | _number \| string_ | `-` |
| `table-layout` | 原生 `table-layout` 布局方式 | _'auto' \| 'fixed'_ | `'auto'` |
| `tree` | 是否显式开启树形数据 | _boolean_ | `false` |
| `cascade` | 树形选择是否开启级联 | _boolean_ | `true` |
@@ -502,8 +541,10 @@ const columns = [{ key: 'name', title: '姓名' }]
| `selectable` | 是否允许选择。支持 `boolean` 或 `(context) => boolean`,仅对选择列生效 | _boolean \| `(context) => boolean`_ | `true` |
| `expandable` | 是否允许展开该行,仅对展开列生效 | _`(context) => boolean`_ | `-` |
| `renderExpand` | 自定义展开内容,仅对展开列生效 | _`(context) => VNodeChild`_ | `-` |
+| `resizable` | 是否允许通过拖拽调整列宽 | _boolean_ | `false` |
| `width` | 列宽 | _number \| string_ | `-` |
| `minWidth` | 列最小宽度 | _number \| string_ | `-` |
+| `maxWidth` | 列最大宽度。开启 `resizable` 时也会作为拖拽的上限 | _number \| string_ | `-` |
| `align` | 内容对齐方式 | _'left' \| 'center' \| 'right'_ | `'left'` |
| `titleAlign` | 表头标题对齐方式 | _'left' \| 'center' \| 'right'_ | `align` |
| `titleColSpan` | 表头列合并数量,设为 `0` 时当前表头不渲染 | _number_ | `1` |
diff --git a/packages/varlet-ui/src/data-table/example/index.vue b/packages/varlet-ui/src/data-table/example/index.vue
index 269bf1153cb..f9d339e47fb 100644
--- a/packages/varlet-ui/src/data-table/example/index.vue
+++ b/packages/varlet-ui/src/data-table/example/index.vue
@@ -65,6 +65,47 @@ const spanData = [
{ id: 2, name: 'Linus', role: 'Maintainer', status: 'Offline' },
]
+const scrollColumns = [
+ { key: 'name', title: 'Name', fixed: 'left', width: 104 },
+ { key: 'role', title: 'Role', width: 126 },
+ { key: 'department', title: 'Dept', width: 120 },
+ { key: 'location', title: 'City', width: 120 },
+ { key: 'status', title: 'Status', fixed: 'right', width: 96 },
+]
+
+const scrollData = [
+ {
+ id: 1,
+ name: 'Ada',
+ role: 'FE Lead',
+ department: 'Exp',
+ location: 'HZ',
+ status: 'Online',
+ },
+ {
+ id: 2,
+ name: 'Linus',
+ role: 'Platform',
+ department: 'Infra',
+ location: 'SH',
+ status: 'Offline',
+ },
+ {
+ id: 3,
+ name: 'Taylor',
+ role: 'Designer',
+ department: 'Design',
+ location: 'SZ',
+ status: 'Busy',
+ },
+]
+
+const resizableColumns = [
+ { key: 'name', title: 'Name', width: 180, minWidth: 120, maxWidth: 260, resizable: true },
+ { key: 'role', title: 'Role', width: 220, minWidth: 160, resizable: true },
+ { key: 'status', title: 'Status', width: 140, maxWidth: 180, resizable: true },
+]
+
const checkedRowKeys = ref([1, 3])
const singleCheckedRowKeys = ref([2])
const treeCheckedRowKeys = ref([1, 11, 12])
@@ -320,6 +361,12 @@ watch(
{{ t('stickyHeader') }}
+ {{ t('fixedColumns') }}
+
+
+ {{ t('resizableColumns') }}
+
+
{{ t('emptyText') }}
{{ t('emptyTip') }}
diff --git a/packages/varlet-ui/src/data-table/example/locale/en-US.ts b/packages/varlet-ui/src/data-table/example/locale/en-US.ts
index 781cdd0bc4f..6e56017fe21 100644
--- a/packages/varlet-ui/src/data-table/example/locale/en-US.ts
+++ b/packages/varlet-ui/src/data-table/example/locale/en-US.ts
@@ -18,6 +18,8 @@ export default {
localPagination: 'Local Pagination',
remotePagination: 'Remote Pagination',
stickyHeader: 'Sticky Header',
+ fixedColumns: 'Fixed Columns',
+ resizableColumns: 'Resizable Columns',
emptyText: 'Empty Text',
emptyTip: 'No Data',
loading: 'Loading',
diff --git a/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts b/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
index 92d8f73757c..0f854f728b7 100644
--- a/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
+++ b/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
@@ -18,6 +18,8 @@ export default {
localPagination: '本地分页',
remotePagination: '远程分页',
stickyHeader: '固定表头',
+ fixedColumns: '固定列',
+ resizableColumns: '可调整列宽',
emptyText: '空态文案',
emptyTip: '暂无数据',
loading: '加载状态',
diff --git a/packages/varlet-ui/src/data-table/props.ts b/packages/varlet-ui/src/data-table/props.ts
index 1b19669cc75..f8293685ad2 100644
--- a/packages/varlet-ui/src/data-table/props.ts
+++ b/packages/varlet-ui/src/data-table/props.ts
@@ -45,8 +45,10 @@ export type DataTableCellProps =
export interface DataTableBaseColumn {
fixed?: DataTableColumnFixed
+ resizable?: boolean
width?: string | number
minWidth?: string | number
+ maxWidth?: string | number
align?: DataTableColumnAlign
titleAlign?: DataTableColumnAlign
titleColSpan?: number
@@ -127,6 +129,7 @@ export const props = {
},
total: Number,
maxHeight: [Number, String],
+ scrollX: [Number, String],
tree: Boolean,
surface: String as PropType,
cascade: {
diff --git a/packages/varlet-ui/src/data-table/useColumnWidths.ts b/packages/varlet-ui/src/data-table/useColumnWidths.ts
new file mode 100644
index 00000000000..2d2fb9871bd
--- /dev/null
+++ b/packages/varlet-ui/src/data-table/useColumnWidths.ts
@@ -0,0 +1,205 @@
+import { computed, onBeforeUnmount, ref, watch, type CSSProperties } from 'vue'
+import { toPxNum, toSizeUnit } from '../utils/elements'
+import type { DataTableColumn, DataTableExpandColumn, DataTableSelectionColumn } from './props'
+
+const defaultDataTableControlColumnWidth = 52
+
+interface UseColumnWidthsOptions {
+ columns: () => DataTableColumn[]
+ isSelectionColumn: (column: DataTableColumn) => column is DataTableSelectionColumn
+ isExpandColumn: (column: DataTableColumn) => column is DataTableExpandColumn
+}
+
+export interface DataTableResizableHeaderCell {
+ column: DataTableColumn
+ columnIndex: number
+}
+
+export function useColumnWidths({ columns, isSelectionColumn, isExpandColumn }: UseColumnWidthsOptions) {
+ const resizedColumnWidths = ref>({})
+ let stopActiveResize: (() => void) | undefined
+
+ const columnWidths = computed(() => {
+ return columns().map((column, columnIndex) => {
+ return getResolvedColumnWidth(column, columnIndex) ?? 0
+ })
+ })
+
+ watch(
+ columns,
+ () => {
+ const activeColumnIds = new Set(columns().map((column, columnIndex) => getColumnId(column, columnIndex)))
+ const nextWidths: Record = {}
+
+ Object.entries(resizedColumnWidths.value).forEach(([columnId, width]) => {
+ if (activeColumnIds.has(columnId)) {
+ nextWidths[columnId] = width
+ }
+ })
+
+ resizedColumnWidths.value = nextWidths
+ },
+ { immediate: true },
+ )
+
+ onBeforeUnmount(() => {
+ stopActiveResize?.()
+ })
+
+ function isColumnResizable(column: DataTableColumn) {
+ return column.resizable === true
+ }
+
+ function getColStyle(column: DataTableColumn, columnIndex: number) {
+ const style: CSSProperties = {}
+ const resizedWidth = getResizedColumnWidth(column, columnIndex)
+
+ if (resizedWidth != null) {
+ style.width = toSizeUnit(resizedWidth)
+ style.minWidth = toSizeUnit(resizedWidth)
+ style.maxWidth = toSizeUnit(resizedWidth)
+ return style
+ }
+
+ const defaultWidth = getColumnDefaultWidth(column)
+
+ if (defaultWidth != null) {
+ style.width = toSizeUnit(getLimitedColumnWidth(column, toPxNum(defaultWidth)))
+ }
+
+ const minWidth = getColumnMinWidth(column)
+
+ if (minWidth != null) {
+ style.minWidth = toSizeUnit(minWidth)
+ } else if (defaultWidth != null) {
+ style.minWidth = toSizeUnit(getLimitedColumnWidth(column, toPxNum(defaultWidth)))
+ }
+
+ const maxWidth = getColumnMaxWidth(column)
+
+ if (maxWidth != null) {
+ style.maxWidth = toSizeUnit(maxWidth)
+ }
+
+ return style
+ }
+
+ function startColumnResize(event: MouseEvent, headerCell: DataTableResizableHeaderCell) {
+ if (!isColumnResizable(headerCell.column)) {
+ return
+ }
+
+ event.preventDefault()
+ event.stopPropagation()
+
+ const headerCellElement = (event.currentTarget as HTMLElement | null)?.closest('th')
+
+ if (!headerCellElement) {
+ return
+ }
+
+ stopActiveResize?.()
+
+ const startX = event.clientX
+ const startWidth = headerCellElement.getBoundingClientRect().width
+ const columnId = getColumnId(headerCell.column, headerCell.columnIndex)
+
+ const handlePointerMove = (moveEvent: MouseEvent) => {
+ const nextWidth = getLimitedColumnWidth(headerCell.column, startWidth + moveEvent.clientX - startX)
+
+ resizedColumnWidths.value = {
+ ...resizedColumnWidths.value,
+ [columnId]: nextWidth,
+ }
+ }
+
+ const handlePointerUp = () => {
+ detach()
+ }
+
+ const detach = () => {
+ document.removeEventListener('mousemove', handlePointerMove)
+ document.removeEventListener('mouseup', handlePointerUp)
+ stopActiveResize = undefined
+ }
+
+ document.addEventListener('mousemove', handlePointerMove)
+ document.addEventListener('mouseup', handlePointerUp)
+ stopActiveResize = detach
+ }
+
+ function getResizedColumnWidth(column: DataTableColumn, columnIndex: number) {
+ return resizedColumnWidths.value[getColumnId(column, columnIndex)]
+ }
+
+ function getResolvedColumnWidth(column: DataTableColumn, columnIndex: number) {
+ const resizedWidth = getResizedColumnWidth(column, columnIndex)
+
+ if (resizedWidth != null) {
+ return resizedWidth
+ }
+
+ const defaultWidth = getColumnDefaultWidth(column)
+
+ if (defaultWidth != null) {
+ return getLimitedColumnWidth(column, toPxNum(defaultWidth))
+ }
+
+ const minWidth = getColumnMinWidth(column)
+
+ if (minWidth != null) {
+ return minWidth
+ }
+ }
+
+ function getColumnDefaultWidth(column: DataTableColumn) {
+ if (column.width != null) {
+ return column.width
+ }
+
+ if (isSelectionColumn(column) || isExpandColumn(column)) {
+ return defaultDataTableControlColumnWidth
+ }
+ }
+
+ function getColumnId(column: DataTableColumn, columnIndex: number) {
+ return `${column.key ?? column.type ?? 'column'}-${columnIndex}`
+ }
+
+ function getColumnMinWidth(column: DataTableColumn) {
+ if (column.minWidth == null) {
+ return
+ }
+
+ const minWidth = toPxNum(column.minWidth)
+ const maxWidth = getColumnMaxWidth(column)
+
+ if (maxWidth == null) {
+ return minWidth
+ }
+
+ return Math.min(minWidth, maxWidth)
+ }
+
+ function getColumnMaxWidth(column: DataTableColumn) {
+ if (column.maxWidth == null) {
+ return
+ }
+
+ return toPxNum(column.maxWidth)
+ }
+
+ function getLimitedColumnWidth(column: DataTableColumn, width: number) {
+ const minWidth = getColumnMinWidth(column) ?? 0
+ const maxWidth = getColumnMaxWidth(column) ?? Number.POSITIVE_INFINITY
+
+ return Math.min(Math.max(width, minWidth), maxWidth)
+ }
+
+ return {
+ columnWidths,
+ getColStyle,
+ isColumnResizable,
+ startColumnResize,
+ }
+}
diff --git a/packages/varlet-ui/src/data-table/useColumnsFixedOffsets.ts b/packages/varlet-ui/src/data-table/useColumnsFixedOffsets.ts
index 11fc0ed4c2b..5ea83681a8d 100644
--- a/packages/varlet-ui/src/data-table/useColumnsFixedOffsets.ts
+++ b/packages/varlet-ui/src/data-table/useColumnsFixedOffsets.ts
@@ -7,6 +7,14 @@ export interface UseColumnsFixedOffsetsOptions {
}
export function useColumnsFixedOffsets({ columns, columnWidths }: UseColumnsFixedOffsetsOptions) {
+ const lastLeftFixedColumnIndex = computed(() => {
+ return findEdgeFixedColumnIndex('left')
+ })
+
+ const firstRightFixedColumnIndex = computed(() => {
+ return findEdgeFixedColumnIndex('right')
+ })
+
const leftFixedOffsets = computed(() => {
return buildFixedOffsets('left')
})
@@ -66,8 +74,40 @@ export function useColumnsFixedOffsets({ columns, columnWidths }: UseColumnsFixe
return offsets
}
+ function isLastLeftFixedColumn(columnIndex: number) {
+ return lastLeftFixedColumnIndex.value === columnIndex
+ }
+
+ function isFirstRightFixedColumn(columnIndex: number) {
+ return firstRightFixedColumnIndex.value === columnIndex
+ }
+
+ function findEdgeFixedColumnIndex(direction: DataTableColumnFixed) {
+ const resolvedColumns = columns()
+
+ if (direction === 'left') {
+ for (let index = resolvedColumns.length - 1; index >= 0; index -= 1) {
+ if (resolvedColumns[index].fixed === 'left') {
+ return index
+ }
+ }
+
+ return -1
+ }
+
+ for (let index = 0; index < resolvedColumns.length; index += 1) {
+ if (resolvedColumns[index].fixed === 'right') {
+ return index
+ }
+ }
+
+ return -1
+ }
+
return {
getFixedStyle,
+ isFirstRightFixedColumn,
+ isLastLeftFixedColumn,
leftFixedOffsets,
rightFixedOffsets,
}
diff --git a/packages/varlet-ui/types/dataTable.d.ts b/packages/varlet-ui/types/dataTable.d.ts
index 2f46159f30f..edd1e3a0c88 100644
--- a/packages/varlet-ui/types/dataTable.d.ts
+++ b/packages/varlet-ui/types/dataTable.d.ts
@@ -38,8 +38,10 @@ export type DataTableCellProps =
export interface DataTableBaseColumn {
fixed?: DataTableColumnFixed
+ resizable?: boolean
width?: string | number
minWidth?: string | number
+ maxWidth?: string | number
align?: DataTableColumnAlign
titleAlign?: DataTableColumnAlign
titleColSpan?: number
@@ -100,6 +102,7 @@ export interface DataTableProps extends BasicAttributes {
pageSize?: number
total?: number
maxHeight?: number | string
+ scrollX?: number | string
tree?: boolean
surface?: DataTableSurface
cascade?: boolean
From 6b270e7a49b8160d16d017919e929108b02efbac Mon Sep 17 00:00:00 2001
From: haoziqaq <357229046@qq.com>
Date: Sat, 23 May 2026 02:10:58 +0800
Subject: [PATCH 14/37] feat(data-table): update table styles for better
responsiveness and adjust resize trigger dimensions
---
packages/varlet-ui/src/data-table/DataTable.vue | 3 ++-
.../src/data-table/__tests__/index.spec.js | 3 ++-
packages/varlet-ui/src/data-table/dataTable.less | 13 ++++---------
3 files changed, 8 insertions(+), 11 deletions(-)
diff --git a/packages/varlet-ui/src/data-table/DataTable.vue b/packages/varlet-ui/src/data-table/DataTable.vue
index f6f1e100f4e..404d9c9883c 100644
--- a/packages/varlet-ui/src/data-table/DataTable.vue
+++ b/packages/varlet-ui/src/data-table/DataTable.vue
@@ -262,10 +262,11 @@ export default defineComponent({
const tableStyle = computed(() => {
const style: CSSProperties = {
tableLayout: props.tableLayout,
+ minWidth: '100%',
}
if (props.scrollX != null) {
- style.minWidth = `max(100%, ${toSizeUnit(props.scrollX)})`
+ style.width = toSizeUnit(props.scrollX)
}
return style
diff --git a/packages/varlet-ui/src/data-table/__tests__/index.spec.js b/packages/varlet-ui/src/data-table/__tests__/index.spec.js
index 1a9502e314e..04bf144750b 100644
--- a/packages/varlet-ui/src/data-table/__tests__/index.spec.js
+++ b/packages/varlet-ui/src/data-table/__tests__/index.spec.js
@@ -474,7 +474,8 @@ describe('test data-table component props', () => {
})
expect(wrapper.find('.var-data-table__container').attributes('style')).toContain('overflow-x: auto;')
- expect(wrapper.find('.var-data-table__table').attributes('style')).toContain('min-width: max(100%, 640px);')
+ expect(wrapper.find('.var-data-table__table').attributes('style')).toContain('min-width: 100%;')
+ expect(wrapper.find('.var-data-table__table').attributes('style')).toContain('width: 640px;')
wrapper.unmount()
})
diff --git a/packages/varlet-ui/src/data-table/dataTable.less b/packages/varlet-ui/src/data-table/dataTable.less
index b6dededacec..c36839c5494 100644
--- a/packages/varlet-ui/src/data-table/dataTable.less
+++ b/packages/varlet-ui/src/data-table/dataTable.less
@@ -7,8 +7,8 @@
--data-table-border-color: var(--color-outline);
--data-table-hover-background: #eee;
--data-table-empty-text-color: var(--color-text-disabled);
- --data-table-resize-trigger-color: hsla(var(--hsl-on-surface-variant), 0.42);
- --data-table-resize-trigger-focus-color: hsla(var(--hsl-primary), 0.45);
+ --data-table-resize-trigger-color: hsla(var(--hsl-on-surface-variant), 0.36);
+ --data-table-resize-trigger-focus-color: hsla(var(--hsl-primary), 0.42);
--data-table-fixed-shadow-color: rgba(0, 0, 0, 0.04);
--data-table-border-radius: 2px;
--data-table-cell-padding: 0 16px;
@@ -97,9 +97,9 @@
&__resize-trigger {
position: absolute;
top: 0;
- right: -10px;
+ right: -5px;
z-index: 1;
- width: 20px;
+ width: 10px;
height: 100%;
padding: 0;
border: 0;
@@ -115,13 +115,8 @@
bottom: 10px;
width: 2px;
transform: translateX(-50%);
- border-radius: 999px;
background: var(--data-table-resize-trigger-color);
}
-
- &:focus-visible::before {
- background: var(--data-table-resize-trigger-focus-color);
- }
}
&__body-cell {
From 984e5267c199511f3507e4a44c5b3dca298d9792 Mon Sep 17 00:00:00 2001
From: haoziqaq <357229046@qq.com>
Date: Sat, 23 May 2026 02:48:49 +0800
Subject: [PATCH 15/37] feat(data-table): refactor DataTable component by
creating separate header and body cell components for improved structure and
maintainability
---
.../varlet-ui/src/data-table/DataTable.vue | 156 ++++-----------
.../src/data-table/DataTableBodyCell.vue | 180 ++++++++++++++++++
.../src/data-table/DataTableHeaderCell.vue | 132 +++++++++++++
.../varlet-ui/src/data-table/dataTable.less | 35 ++--
.../varlet-ui/src/data-table/docs/en-US.md | 20 +-
.../varlet-ui/src/data-table/docs/zh-CN.md | 20 +-
.../src/data-table/example/index.vue | 3 +
.../src/data-table/example/locale/en-US.ts | 1 +
.../src/data-table/example/locale/zh-CN.ts | 1 +
.../varlet-ui/src/themes/dark/dataTable.ts | 8 +-
.../src/themes/md3-dark/dataTable.ts | 8 +-
.../src/themes/md3-light/dataTable.ts | 8 +-
packages/varlet-ui/types/styleVars.d.ts | 10 +-
13 files changed, 418 insertions(+), 164 deletions(-)
create mode 100644 packages/varlet-ui/src/data-table/DataTableBodyCell.vue
create mode 100644 packages/varlet-ui/src/data-table/DataTableHeaderCell.vue
diff --git a/packages/varlet-ui/src/data-table/DataTable.vue b/packages/varlet-ui/src/data-table/DataTable.vue
index 404d9c9883c..c9512d636c1 100644
--- a/packages/varlet-ui/src/data-table/DataTable.vue
+++ b/packages/varlet-ui/src/data-table/DataTable.vue
@@ -25,128 +25,54 @@
-
-
-
- {{
- isSelectionColumn(headerCell.column) || isExpandColumn(headerCell.column)
- ? ''
- : headerCell.column.title
- }}
-
-
-
+ :all-current-rows-selected="allCurrentRowsSelected"
+ :some-current-rows-selected="someCurrentRowsSelected"
+ :has-selectable-rows="!!currentSelectableRows.length"
+ :is-selection-column="isSelectionColumn"
+ :is-expand-column="isExpandColumn"
+ :is-multiple-selection-column="isMultipleSelectionColumn"
+ :is-selection-column-selectable="isSelectionColumnSelectable"
+ :is-column-resizable="isColumnResizable"
+ :is-last-header-column="isLastHeaderColumn"
+ :is-last-left-fixed-column="isLastLeftFixedColumn"
+ :is-first-right-fixed-column="isFirstRightFixedColumn"
+ :toggle-current-selectable-rows="toggleCurrentSelectableRows"
+ :start-column-resize="startColumnResize"
+ />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ :tree="tree"
+ :is-selection-column="isSelectionColumn"
+ :is-expand-column="isExpandColumn"
+ :is-multiple-selection-column="isMultipleSelectionColumn"
+ :is-selection-column-selectable="isSelectionColumnSelectable"
+ :is-row-selectable="isRowSelectable"
+ :is-row-key-selected="isRowKeySelected"
+ :is-row-key-indeterminate="isRowKeyIndeterminate"
+ :is-row-expandable="isRowExpandable"
+ :is-last-left-fixed-column="isLastLeftFixedColumn"
+ :is-first-right-fixed-column="isFirstRightFixedColumn"
+ :toggle-row-selection="toggleRowSelection"
+ :toggle-row-expanded="toggleRowExpanded"
+ :toggle-tree-row-expanded="toggleTreeRowExpanded"
+ :render-cell="renderCell"
+ :get-cell-props="getCellProps"
+ />
@@ -196,15 +122,14 @@
import { call, callOrReturn, clamp, floor, isArray, isFunction } from '@varlet/shared'
import { useVModel } from '@varlet/use'
import { computed, defineComponent, type CSSProperties } from 'vue'
-import VarCheckbox from '../checkbox'
-import VarIcon from '../icon'
import VarLoading from '../loading'
import { t } from '../locale'
import { injectLocaleProvider } from '../locale-provider/provide'
import VarPagination from '../pagination'
-import VarRadio from '../radio'
import { createNamespace, formatElevation, MaybeVNode } from '../utils/components'
import { toSizeUnit } from '../utils/elements'
+import DataTableBodyCellComponent from './DataTableBodyCell.vue'
+import DataTableHeaderCellComponent from './DataTableHeaderCell.vue'
import {
props,
type DataTableColumn,
@@ -232,11 +157,10 @@ interface DataTableHeaderCell {
export default defineComponent({
name,
components: {
- VarCheckbox,
- VarIcon,
+ DataTableBodyCell: DataTableBodyCellComponent,
+ DataTableHeaderCell: DataTableHeaderCellComponent,
VarLoading,
VarPagination,
- VarRadio,
MaybeVNode,
},
props,
diff --git a/packages/varlet-ui/src/data-table/DataTableBodyCell.vue b/packages/varlet-ui/src/data-table/DataTableBodyCell.vue
new file mode 100644
index 00000000000..e1aac1e0504
--- /dev/null
+++ b/packages/varlet-ui/src/data-table/DataTableBodyCell.vue
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/varlet-ui/src/data-table/DataTableHeaderCell.vue b/packages/varlet-ui/src/data-table/DataTableHeaderCell.vue
new file mode 100644
index 00000000000..75323ea2d77
--- /dev/null
+++ b/packages/varlet-ui/src/data-table/DataTableHeaderCell.vue
@@ -0,0 +1,132 @@
+
+
+
+
+ {{ isSelectionColumn(headerCell.column) || isExpandColumn(headerCell.column) ? '' : headerCell.column.title }}
+
+
+
+
+
+
diff --git a/packages/varlet-ui/src/data-table/dataTable.less b/packages/varlet-ui/src/data-table/dataTable.less
index c36839c5494..64e4c68c954 100644
--- a/packages/varlet-ui/src/data-table/dataTable.less
+++ b/packages/varlet-ui/src/data-table/dataTable.less
@@ -1,14 +1,13 @@
:root {
--data-table-background: #fff;
--data-table-surface-low-background: var(--color-surface-container-highest);
- --data-table-header-background: #fff;
- --data-table-header-text-color: rgba(0, 0, 0, 0.6);
- --data-table-row-text-color: #555;
+ --data-table-header-cell-background: #fff;
+ --data-table-header-cell-text-color: rgba(0, 0, 0, 0.6);
+ --data-table-body-cell-text-color: #555;
--data-table-border-color: var(--color-outline);
- --data-table-hover-background: #eee;
+ --data-table-row-hover-background: #eee;
--data-table-empty-text-color: var(--color-text-disabled);
--data-table-resize-trigger-color: hsla(var(--hsl-on-surface-variant), 0.36);
- --data-table-resize-trigger-focus-color: hsla(var(--hsl-primary), 0.42);
--data-table-fixed-shadow-color: rgba(0, 0, 0, 0.04);
--data-table-border-radius: 2px;
--data-table-cell-padding: 0 16px;
@@ -47,7 +46,7 @@
}
&__header-row {
- background: var(--data-table-header-background);
+ background: var(--data-table-header-cell-background);
border-bottom: 1px solid var(--data-table-border-color);
}
@@ -57,7 +56,7 @@
transition: background-color 0.25s;
&:hover {
- background: var(--data-table-hover-background);
+ background: var(--data-table-row-hover-background);
}
}
@@ -69,7 +68,7 @@
&--surface-low {
--data-table-background: var(--data-table-surface-low-background);
- --data-table-header-background: var(--data-table-surface-low-background);
+ --data-table-header-cell-background: var(--data-table-surface-low-background);
}
&__cell {
@@ -86,10 +85,10 @@
}
&__header-cell {
- color: var(--data-table-header-text-color);
+ color: var(--data-table-header-cell-text-color);
font-size: var(--data-table-header-font-size);
font-weight: 500;
- background: var(--data-table-header-background);
+ background: var(--data-table-header-cell-background);
position: sticky;
top: 0;
}
@@ -120,7 +119,7 @@
}
&__body-cell {
- color: var(--data-table-row-text-color);
+ color: var(--data-table-body-cell-text-color);
}
&__fixed-cell {
@@ -154,11 +153,14 @@
}
}
- &__selection-cell {
+ &__selection-cell,
+ &__expand-cell {
text-align: center;
padding: var(--data-table-selection-cell-padding);
min-width: 52px;
+ }
+ &__selection-cell {
.var-checkbox,
.var-radio {
transform: none;
@@ -173,12 +175,6 @@
}
}
- &__expand-cell {
- text-align: center;
- padding: var(--data-table-selection-cell-padding);
- min-width: 52px;
- }
-
&__expand-trigger {
display: inline-flex;
align-items: center;
@@ -236,9 +232,6 @@
align-items: center;
justify-content: center;
width: 100%;
- }
-
- &__empty {
color: var(--data-table-empty-text-color);
padding: var(--data-table-empty-padding);
}
diff --git a/packages/varlet-ui/src/data-table/docs/en-US.md b/packages/varlet-ui/src/data-table/docs/en-US.md
index 3941ef146b3..1cbc8ed9cab 100644
--- a/packages/varlet-ui/src/data-table/docs/en-US.md
+++ b/packages/varlet-ui/src/data-table/docs/en-US.md
@@ -141,6 +141,16 @@ Use `surface="low"` for a subtler MD3-style surface layer.
```
+### Flat Table
+
+Set `elevation` to `0` to remove the card-like shadow and present the table as a flat surface.
+
+```html
+
+
+
+```
+
### Cell Spans
Use `rowSpan`, `colSpan`, and `titleColSpan` to merge body and header cells. Returning `0` hides the current cell, which is typically used together with a previous spanning cell.
@@ -578,12 +588,12 @@ const resizableColumns = [
| Variable | Default |
| --- | --- |
| `--data-table-background` | `#fff` |
-| `--data-table-header-background` | `#fff` |
-| `--data-table-surface-low-background` | `var(--color-surface-container-low)` |
-| `--data-table-header-text-color` | `rgba(0, 0, 0, 0.6)` |
-| `--data-table-row-text-color` | `#555` |
+| `--data-table-header-cell-background` | `#fff` |
+| `--data-table-surface-low-background` | `var(--color-surface-container-highest)` |
+| `--data-table-header-cell-text-color` | `rgba(0, 0, 0, 0.6)` |
+| `--data-table-body-cell-text-color` | `#555` |
| `--data-table-border-color` | `var(--color-outline)` |
-| `--data-table-hover-background` | `#eee` |
+| `--data-table-row-hover-background` | `#eee` |
| `--data-table-empty-text-color` | `var(--color-text-disabled)` |
| `--data-table-border-radius` | `2px` |
| `--data-table-cell-padding` | `0 16px` |
diff --git a/packages/varlet-ui/src/data-table/docs/zh-CN.md b/packages/varlet-ui/src/data-table/docs/zh-CN.md
index a3b63ed7176..3b66d31b3e4 100644
--- a/packages/varlet-ui/src/data-table/docs/zh-CN.md
+++ b/packages/varlet-ui/src/data-table/docs/zh-CN.md
@@ -141,6 +141,16 @@ const data = [
```
+### 无卡片表格
+
+设置 `elevation` 为 `0` 可以去掉卡片阴影,展示更纯粹的表格形态。
+
+```html
+
+
+
+```
+
### 单元格合并
通过 `rowSpan`、`colSpan` 和 `titleColSpan` 控制表体与表头合并。返回 `0` 时当前单元格不渲染,通常配合前一个单元格的跨行或跨列使用。
@@ -578,12 +588,12 @@ const resizableColumns = [
| 变量名 | 默认值 |
| --- | --- |
| `--data-table-background` | `#fff` |
-| `--data-table-header-background` | `#fff` |
-| `--data-table-surface-low-background` | `var(--color-surface-container-low)` |
-| `--data-table-header-text-color` | `rgba(0, 0, 0, 0.6)` |
-| `--data-table-row-text-color` | `#555` |
+| `--data-table-header-cell-background` | `#fff` |
+| `--data-table-surface-low-background` | `var(--color-surface-container-highest)` |
+| `--data-table-header-cell-text-color` | `rgba(0, 0, 0, 0.6)` |
+| `--data-table-body-cell-text-color` | `#555` |
| `--data-table-border-color` | `var(--color-outline)` |
-| `--data-table-hover-background` | `#eee` |
+| `--data-table-row-hover-background` | `#eee` |
| `--data-table-empty-text-color` | `var(--color-text-disabled)` |
| `--data-table-border-radius` | `2px` |
| `--data-table-cell-padding` | `0 16px` |
diff --git a/packages/varlet-ui/src/data-table/example/index.vue b/packages/varlet-ui/src/data-table/example/index.vue
index f9d339e47fb..e5c7675caff 100644
--- a/packages/varlet-ui/src/data-table/example/index.vue
+++ b/packages/varlet-ui/src/data-table/example/index.vue
@@ -288,6 +288,9 @@ watch(
{{ t('surfaceLow') }}
+ {{ t('flatTable') }}
+
+
{{ t('spans') }}
diff --git a/packages/varlet-ui/src/data-table/example/locale/en-US.ts b/packages/varlet-ui/src/data-table/example/locale/en-US.ts
index 6e56017fe21..25ddd419f70 100644
--- a/packages/varlet-ui/src/data-table/example/locale/en-US.ts
+++ b/packages/varlet-ui/src/data-table/example/locale/en-US.ts
@@ -6,6 +6,7 @@ export default {
customProps: 'Custom Props',
customRender: 'Custom Render',
surfaceLow: 'Subtle Background',
+ flatTable: 'Flat Table',
spans: 'Cell Spans',
selection: 'Selection',
singleSelection: 'Single Selection',
diff --git a/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts b/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
index 0f854f728b7..7f0bdcf46a8 100644
--- a/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
+++ b/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
@@ -6,6 +6,7 @@ export default {
customProps: '属性透传',
customRender: '自定义渲染',
surfaceLow: '弱背景色',
+ flatTable: '无卡片表格',
spans: '单元格合并',
selection: '选择列',
singleSelection: '单选',
diff --git a/packages/varlet-ui/src/themes/dark/dataTable.ts b/packages/varlet-ui/src/themes/dark/dataTable.ts
index aaa230ef6a7..143e753d0fa 100644
--- a/packages/varlet-ui/src/themes/dark/dataTable.ts
+++ b/packages/varlet-ui/src/themes/dark/dataTable.ts
@@ -1,10 +1,10 @@
export default {
'--data-table-background': '#303030',
- '--data-table-header-background': '#303030',
- '--data-table-header-text-color': 'rgba(255, 255, 255, 0.6)',
- '--data-table-row-text-color': '#fff',
+ '--data-table-header-cell-background': '#303030',
+ '--data-table-header-cell-text-color': 'rgba(255, 255, 255, 0.6)',
+ '--data-table-body-cell-text-color': '#fff',
'--data-table-border-color': 'var(--color-outline)',
- '--data-table-hover-background': '#4c4b4b',
+ '--data-table-row-hover-background': '#4c4b4b',
'--data-table-striped-background': '#303030',
'--data-table-empty-text-color': 'var(--color-text-disabled)',
'--data-table-border-radius': '2px',
diff --git a/packages/varlet-ui/src/themes/md3-dark/dataTable.ts b/packages/varlet-ui/src/themes/md3-dark/dataTable.ts
index b21c225a1eb..f7c06ebf853 100644
--- a/packages/varlet-ui/src/themes/md3-dark/dataTable.ts
+++ b/packages/varlet-ui/src/themes/md3-dark/dataTable.ts
@@ -1,11 +1,11 @@
export default {
'--data-table-background': 'var(--color-surface-container-highest)',
'--data-table-surface-low-background': '#1c1b1d',
- '--data-table-header-background': 'var(--color-surface-container-highest)',
- '--data-table-header-text-color': 'rgba(255, 255, 255, 0.6)',
- '--data-table-row-text-color': '#fff',
+ '--data-table-header-cell-background': 'var(--color-surface-container-highest)',
+ '--data-table-header-cell-text-color': 'rgba(255, 255, 255, 0.6)',
+ '--data-table-body-cell-text-color': '#fff',
'--data-table-border-color': 'var(--color-outline)',
- '--data-table-hover-background': 'var(--color-surface-container-highest)',
+ '--data-table-row-hover-background': 'var(--color-surface-container-highest)',
'--data-table-striped-background': 'var(--color-surface-container-highest)',
'--data-table-empty-text-color': 'var(--color-text-disabled)',
'--data-table-border-radius': '2px',
diff --git a/packages/varlet-ui/src/themes/md3-light/dataTable.ts b/packages/varlet-ui/src/themes/md3-light/dataTable.ts
index f7c16352a6e..2128ba89103 100644
--- a/packages/varlet-ui/src/themes/md3-light/dataTable.ts
+++ b/packages/varlet-ui/src/themes/md3-light/dataTable.ts
@@ -1,11 +1,11 @@
export default {
'--data-table-background': 'var(--color-surface-container-low)',
'--data-table-surface-low-background': 'var(--color-surface-container-low)',
- '--data-table-header-background': 'var(--color-surface-container-low)',
- '--data-table-header-text-color': 'rgba(0, 0, 0, 0.6)',
- '--data-table-row-text-color': '#555',
+ '--data-table-header-cell-background': 'var(--color-surface-container-low)',
+ '--data-table-header-cell-text-color': 'rgba(0, 0, 0, 0.6)',
+ '--data-table-body-cell-text-color': '#555',
'--data-table-border-color': 'var(--color-outline)',
- '--data-table-hover-background': 'var(--color-surface-container-low)',
+ '--data-table-row-hover-background': 'var(--color-surface-container-low)',
'--data-table-striped-background': 'var(--color-surface-container-low)',
'--data-table-empty-text-color': 'var(--color-text-disabled)',
'--data-table-border-radius': '2px',
diff --git a/packages/varlet-ui/types/styleVars.d.ts b/packages/varlet-ui/types/styleVars.d.ts
index 6b310b47c69..b44ed1c35cc 100644
--- a/packages/varlet-ui/types/styleVars.d.ts
+++ b/packages/varlet-ui/types/styleVars.d.ts
@@ -269,13 +269,13 @@ interface BaseStyleVars {
'--countdown-text-color'?: string
'--countdown-text-font-size'?: string
'--data-table-background'?: string
- '--data-table-header-background'?: string
- '--data-table-header-color'?: string
- '--data-table-row-color'?: string
+ '--data-table-header-cell-background'?: string
+ '--data-table-header-cell-text-color'?: string
+ '--data-table-body-cell-text-color'?: string
'--data-table-border-color'?: string
- '--data-table-hover-background'?: string
+ '--data-table-row-hover-background'?: string
'--data-table-striped-background'?: string
- '--data-table-empty-color'?: string
+ '--data-table-empty-text-color'?: string
'--data-table-border-radius'?: string
'--data-table-cell-padding'?: string
'--data-table-cell-font-size'?: string
From ceee00b8160404e8085db3fae90ca1238d1089b9 Mon Sep 17 00:00:00 2001
From: haoziqaq <357229046@qq.com>
Date: Sat, 23 May 2026 18:08:34 +0800
Subject: [PATCH 16/37] feat(data-table): add plain table mode with updated
styles and documentation
---
.../varlet-ui/src/data-table/DataTable.vue | 3 +-
.../src/data-table/__tests__/index.spec.js | 15 +++++++++
.../varlet-ui/src/data-table/dataTable.less | 12 ++++++-
.../varlet-ui/src/data-table/docs/en-US.md | 30 ++++-------------
.../varlet-ui/src/data-table/docs/zh-CN.md | 30 ++++-------------
.../src/data-table/example/index.vue | 21 ++----------
.../src/data-table/example/locale/en-US.ts | 3 +-
.../src/data-table/example/locale/zh-CN.ts | 3 +-
packages/varlet-ui/src/data-table/props.ts | 1 +
packages/varlet-ui/src/table/docs/en-US.md | 3 +-
packages/varlet-ui/src/table/docs/zh-CN.md | 3 +-
packages/varlet-ui/src/table/table.less | 4 ++-
.../__snapshots__/index.spec.js.snap | 33 +++++++++----------
.../varlet-ui/src/themes/dark/dataTable.ts | 5 +--
packages/varlet-ui/src/themes/dark/table.ts | 3 +-
.../src/themes/md3-dark/dataTable.ts | 5 +--
.../varlet-ui/src/themes/md3-dark/table.ts | 3 +-
.../src/themes/md3-light/dataTable.ts | 5 +--
.../varlet-ui/src/themes/md3-light/table.ts | 3 +-
packages/varlet-ui/types/dataTable.d.ts | 1 +
packages/varlet-ui/types/styleVars.d.ts | 8 +++--
21 files changed, 90 insertions(+), 104 deletions(-)
diff --git a/packages/varlet-ui/src/data-table/DataTable.vue b/packages/varlet-ui/src/data-table/DataTable.vue
index c9512d636c1..6937d2414fd 100644
--- a/packages/varlet-ui/src/data-table/DataTable.vue
+++ b/packages/varlet-ui/src/data-table/DataTable.vue
@@ -3,10 +3,11 @@
:class="
classes(
n(),
- formatElevation(elevation, 1),
+ [!plain, formatElevation(elevation, 1)],
n('$--box'),
[surface === 'low', n('--surface-low')],
[cellBordered, n('--cell-bordered')],
+ [plain, n('--plain')],
[showPagination, n('--with-footer')],
n(`--${size}`),
)
diff --git a/packages/varlet-ui/src/data-table/__tests__/index.spec.js b/packages/varlet-ui/src/data-table/__tests__/index.spec.js
index 04bf144750b..cd93a099f50 100644
--- a/packages/varlet-ui/src/data-table/__tests__/index.spec.js
+++ b/packages/varlet-ui/src/data-table/__tests__/index.spec.js
@@ -105,6 +105,21 @@ describe('test data-table component props', () => {
wrapper.unmount()
})
+ test('should support plain table mode', () => {
+ const wrapper = mount(VarDataTable, {
+ props: {
+ columns,
+ data,
+ pagination: false,
+ plain: true,
+ },
+ })
+
+ expect(wrapper.classes()).toContain('var-data-table--plain')
+ expect(wrapper.classes()).not.toContain('var-elevation--1')
+ wrapper.unmount()
+ })
+
test('should support selection column', async () => {
const onUpdateCheckedRowKeys = vi.fn()
const wrapper = mount(VarDataTable, {
diff --git a/packages/varlet-ui/src/data-table/dataTable.less b/packages/varlet-ui/src/data-table/dataTable.less
index 64e4c68c954..e514dcf4f22 100644
--- a/packages/varlet-ui/src/data-table/dataTable.less
+++ b/packages/varlet-ui/src/data-table/dataTable.less
@@ -5,7 +5,9 @@
--data-table-header-cell-text-color: rgba(0, 0, 0, 0.6);
--data-table-body-cell-text-color: #555;
--data-table-border-color: var(--color-outline);
- --data-table-row-hover-background: #eee;
+ --data-table-row-hover-background: #f5f5f5;
+ --data-table-surface-low-row-hover-background: var(--color-surface-container-highest);
+ --data-table-plain-row-hover-background: hsla(var(--hsl-on-surface), 0.04);
--data-table-empty-text-color: var(--color-text-disabled);
--data-table-resize-trigger-color: hsla(var(--hsl-on-surface-variant), 0.36);
--data-table-fixed-shadow-color: rgba(0, 0, 0, 0.04);
@@ -69,6 +71,14 @@
&--surface-low {
--data-table-background: var(--data-table-surface-low-background);
--data-table-header-cell-background: var(--data-table-surface-low-background);
+ --data-table-row-hover-background: var(--data-table-surface-low-row-hover-background);
+ }
+
+ &--plain {
+ --data-table-background: transparent;
+ --data-table-header-cell-background: transparent;
+ --data-table-row-hover-background: var(--data-table-plain-row-hover-background);
+ border-radius: 0;
}
&__cell {
diff --git a/packages/varlet-ui/src/data-table/docs/en-US.md b/packages/varlet-ui/src/data-table/docs/en-US.md
index 1cbc8ed9cab..c9603efe23e 100644
--- a/packages/varlet-ui/src/data-table/docs/en-US.md
+++ b/packages/varlet-ui/src/data-table/docs/en-US.md
@@ -141,13 +141,13 @@ Use `surface="low"` for a subtler MD3-style surface layer.
```
-### Flat Table
+### Plain Table
-Set `elevation` to `0` to remove the card-like shadow and present the table as a flat surface.
+Set `plain` to remove the card-like shadow, background, and radius, and present the table as a pure table surface.
```html
-
+
```
@@ -345,25 +345,6 @@ const columns = [
```
-### Pager Pagination
-
-Use `pagination` to configure built-in pager pagination.
-
-```html
-
-
-
-```
-
### Local Pagination
In local pagination mode, pass the full data set, bind `v-model:page` and `v-model:page-size`, and let the component slice it internally.
@@ -531,6 +512,7 @@ const resizableColumns = [
| `total` | Total item count in remote mode | _number_ | `-` |
| `max-height` | Max height of the table body. When set, the header stays fixed and the body scrolls internally | _number \| string_ | `-` |
| `scroll-x` | Table width used to enable horizontal scrolling. Usually paired with fixed columns | _number \| string_ | `-` |
+| `plain` | Whether to render as a plain table without card shadow, background, or radius | _boolean_ | `false` |
| `table-layout` | Native `table-layout` value | _'auto' \| 'fixed'_ | `'auto'` |
| `tree` | Whether to explicitly enable tree data mode | _boolean_ | `false` |
| `cascade` | Whether tree selection should cascade | _boolean_ | `true` |
@@ -593,7 +575,9 @@ const resizableColumns = [
| `--data-table-header-cell-text-color` | `rgba(0, 0, 0, 0.6)` |
| `--data-table-body-cell-text-color` | `#555` |
| `--data-table-border-color` | `var(--color-outline)` |
-| `--data-table-row-hover-background` | `#eee` |
+| `--data-table-row-hover-background` | `#f5f5f5` |
+| `--data-table-surface-low-row-hover-background` | `var(--color-surface-container-highest)` |
+| `--data-table-plain-row-hover-background` | `hsla(var(--hsl-on-surface), 0.04)` |
| `--data-table-empty-text-color` | `var(--color-text-disabled)` |
| `--data-table-border-radius` | `2px` |
| `--data-table-cell-padding` | `0 16px` |
diff --git a/packages/varlet-ui/src/data-table/docs/zh-CN.md b/packages/varlet-ui/src/data-table/docs/zh-CN.md
index 3b66d31b3e4..701d2347146 100644
--- a/packages/varlet-ui/src/data-table/docs/zh-CN.md
+++ b/packages/varlet-ui/src/data-table/docs/zh-CN.md
@@ -141,13 +141,13 @@ const data = [
```
-### 无卡片表格
+### 纯表格
-设置 `elevation` 为 `0` 可以去掉卡片阴影,展示更纯粹的表格形态。
+设置 `plain` 可以一并去掉卡片阴影、背景色和圆角,展示更纯粹的表格形态。
```html
-
+
```
@@ -345,25 +345,6 @@ const columns = [
```
-### 页码分页
-
-通过 `pagination` 配置内置页码分页。
-
-```html
-
-
-
-```
-
### 本地分页
本地分页模式下传入全量数据,并绑定 `v-model:page` / `v-model:page-size`,由组件内部切片。
@@ -531,6 +512,7 @@ const resizableColumns = [
| `total` | 远程分页总条数 | _number_ | `-` |
| `max-height` | 表格主体最大高度。设置后表头固定,内容区域内部滚动 | _number \| string_ | `-` |
| `scroll-x` | 用于开启横向滚动的表格宽度,通常和固定列一起使用 | _number \| string_ | `-` |
+| `plain` | 是否以纯表格形态渲染,不带卡片阴影、背景色和圆角 | _boolean_ | `false` |
| `table-layout` | 原生 `table-layout` 布局方式 | _'auto' \| 'fixed'_ | `'auto'` |
| `tree` | 是否显式开启树形数据 | _boolean_ | `false` |
| `cascade` | 树形选择是否开启级联 | _boolean_ | `true` |
@@ -593,7 +575,9 @@ const resizableColumns = [
| `--data-table-header-cell-text-color` | `rgba(0, 0, 0, 0.6)` |
| `--data-table-body-cell-text-color` | `#555` |
| `--data-table-border-color` | `var(--color-outline)` |
-| `--data-table-row-hover-background` | `#eee` |
+| `--data-table-row-hover-background` | `#f5f5f5` |
+| `--data-table-surface-low-row-hover-background` | `var(--color-surface-container-highest)` |
+| `--data-table-plain-row-hover-background` | `hsla(var(--hsl-on-surface), 0.04)` |
| `--data-table-empty-text-color` | `var(--color-text-disabled)` |
| `--data-table-border-radius` | `2px` |
| `--data-table-cell-padding` | `0 16px` |
diff --git a/packages/varlet-ui/src/data-table/example/index.vue b/packages/varlet-ui/src/data-table/example/index.vue
index e5c7675caff..6c29c93a0fc 100644
--- a/packages/varlet-ui/src/data-table/example/index.vue
+++ b/packages/varlet-ui/src/data-table/example/index.vue
@@ -210,13 +210,6 @@ const customRowProps = ({ row, rowIndex }) => ({
title: row.name,
})
-const pagerPagination = {
- simple: false,
- showSizeChanger: false,
- showQuickJumper: false,
- maxPagerCount: 2,
-}
-
const defaultPagination = {
showSizeChanger: false,
showQuickJumper: false,
@@ -232,13 +225,6 @@ const manyRows = times(48, (index) => ({
status: index % 3 === 0 ? 'Online' : 'Offline',
}))
-const compactPagedRows = times(12, (index) => ({
- id: index + 1,
- name: `Member ${index + 1}`,
- role: index % 2 === 0 ? 'Engineer' : 'Operator',
- status: index % 3 === 0 ? 'Online' : 'Offline',
-}))
-
const remotePage = ref(1)
const remotePageSize = ref(10)
const remoteLoading = ref(false)
@@ -288,8 +274,8 @@ watch(
{{ t('surfaceLow') }}
- {{ t('flatTable') }}
-
+ {{ t('plainTable') }}
+
{{ t('spans') }}
@@ -337,9 +323,6 @@ watch(
{{ t('expand') }}
- {{ t('pagerPagination') }}
-
-
{{ t('localPagination') }}
>,
default: () => [],
diff --git a/packages/varlet-ui/src/table/docs/en-US.md b/packages/varlet-ui/src/table/docs/en-US.md
index 409ae1151c1..ad582ed1844 100644
--- a/packages/varlet-ui/src/table/docs/en-US.md
+++ b/packages/varlet-ui/src/table/docs/en-US.md
@@ -192,13 +192,14 @@ Here are the CSS variables used by the component. Styles can be customized using
| Variable | Default |
| --- | --- |
| `--table-background` | `#fff` |
+| `--table-surface-low-row-hover-background` | `var(--color-surface-container-highest)` |
| `--table-border-radius` | `2px` |
| `--table-thead-border-bottom` | `thin solid var(--color-outline)` |
| `--table-thead-th-text-color` | `rgba(0, 0, 0, 0.6)` |
| `--table-thead-th-text-align` | `left` |
| `--table-thead-th-font-size` | `14px` |
| `--table-thead-tr-border-bottom` | `thin solid var(--color-outline)` |
-| `--table-tbody-tr-hover-background` | `#eee` |
+| `--table-tbody-tr-hover-background` | `#f5f5f5` |
| `--table-tbody-tr-border-bottom` | `thin solid var(--color-outline)` |
| `--table-tbody-td-text-color` | `#555` |
| `--table-tbody-td-font-size` | `16px` |
diff --git a/packages/varlet-ui/src/table/docs/zh-CN.md b/packages/varlet-ui/src/table/docs/zh-CN.md
index 0f20bb73f6e..734f822a90f 100644
--- a/packages/varlet-ui/src/table/docs/zh-CN.md
+++ b/packages/varlet-ui/src/table/docs/zh-CN.md
@@ -192,13 +192,14 @@ function get(current, size) {
| 变量名 | 默认值 |
| --- | --- |
| `--table-background` | `#fff` |
+| `--table-surface-low-row-hover-background` | `var(--color-surface-container-highest)` |
| `--table-border-radius` | `2px` |
| `--table-thead-border-bottom` | `thin solid var(--color-outline)` |
| `--table-thead-th-text-color` | `rgba(0, 0, 0, 0.6)` |
| `--table-thead-th-text-align` | `left` |
| `--table-thead-th-font-size` | `14px` |
| `--table-thead-tr-border-bottom` | `thin solid var(--color-outline)` |
-| `--table-tbody-tr-hover-background` | `#eee` |
+| `--table-tbody-tr-hover-background` | `#f5f5f5` |
| `--table-tbody-tr-border-bottom` | `thin solid var(--color-outline)` |
| `--table-tbody-td-text-color` | `#555` |
| `--table-tbody-td-font-size` | `16px` |
diff --git a/packages/varlet-ui/src/table/table.less b/packages/varlet-ui/src/table/table.less
index e16fd074990..e69d536d81e 100644
--- a/packages/varlet-ui/src/table/table.less
+++ b/packages/varlet-ui/src/table/table.less
@@ -1,13 +1,14 @@
:root {
--table-background: #fff;
--table-surface-low-background: var(--color-surface-container-highest);
+ --table-surface-low-row-hover-background: var(--color-surface-container-highest);
--table-border-radius: 2px;
--table-thead-border-bottom: thin solid var(--color-outline);
--table-thead-th-text-color: rgba(0, 0, 0, 0.6);
--table-thead-th-text-align: left;
--table-thead-th-font-size: 14px;
--table-thead-tr-border-bottom: thin solid var(--color-outline);
- --table-tbody-tr-hover-background: #eee;
+ --table-tbody-tr-hover-background: #f5f5f5;
--table-tbody-tr-border-bottom: thin solid var(--color-outline);
--table-tbody-td-text-color: #555;
--table-tbody-td-font-size: 16px;
@@ -99,6 +100,7 @@
&--surface-low {
--table-background: var(--table-surface-low-background);
+ --table-tbody-tr-hover-background: var(--table-surface-low-row-hover-background);
}
&__footer {
diff --git a/packages/varlet-ui/src/themes/__tests__/__snapshots__/index.spec.js.snap b/packages/varlet-ui/src/themes/__tests__/__snapshots__/index.spec.js.snap
index 6278fb00fb4..232905ac2a3 100644
--- a/packages/varlet-ui/src/themes/__tests__/__snapshots__/index.spec.js.snap
+++ b/packages/varlet-ui/src/themes/__tests__/__snapshots__/index.spec.js.snap
@@ -303,6 +303,7 @@ exports[`dark theme 1`] = `
"--counter-padding": "0 4px",
"--cubic-bezier": "cubic-bezier(0.25, 0.8, 0.5, 1)",
"--data-table-background": "#303030",
+ "--data-table-body-cell-text-color": "#fff",
"--data-table-border-color": "var(--color-outline)",
"--data-table-border-radius": "2px",
"--data-table-cell-font-size": "16px",
@@ -310,15 +311,13 @@ exports[`dark theme 1`] = `
"--data-table-empty-padding": "48px 16px",
"--data-table-empty-text-color": "var(--color-text-disabled)",
"--data-table-footer-padding": "12px 16px",
- "--data-table-header-background": "#303030",
+ "--data-table-header-cell-background": "#303030",
+ "--data-table-header-cell-text-color": "rgba(255, 255, 255, 0.6)",
"--data-table-header-font-size": "14px",
- "--data-table-header-text-color": "rgba(255, 255, 255, 0.6)",
- "--data-table-hover-background": "#4c4b4b",
"--data-table-row-height": "46px",
+ "--data-table-row-hover-background": "#4c4b4b",
"--data-table-row-large-height": "52px",
"--data-table-row-small-height": "40px",
- "--data-table-row-text-color": "#fff",
- "--data-table-striped-background": "#303030",
"--date-picker-actions-padding": "0 8px 12px 8px",
"--date-picker-body-background-color": "#303030",
"--date-picker-body-height": "280px",
@@ -848,7 +847,7 @@ exports[`dark theme 1`] = `
"--table-tbody-td-text-align": "left",
"--table-tbody-td-text-color": "#fff",
"--table-tbody-tr-border-bottom": "thin solid var(--color-outline)",
- "--table-tbody-tr-hover-background": "#4c4b4b",
+ "--table-tbody-tr-hover-background": "#3a3a3a",
"--table-thead-border-bottom": "thin solid var(--color-outline)",
"--table-thead-th-font-size": "14px",
"--table-thead-th-text-align": "left",
@@ -1276,6 +1275,7 @@ exports[`md3Dark theme 1`] = `
"--counter-padding": "0 4px",
"--cubic-bezier": "cubic-bezier(0.25, 0.8, 0.5, 1)",
"--data-table-background": "var(--color-surface-container-highest)",
+ "--data-table-body-cell-text-color": "#fff",
"--data-table-border-color": "var(--color-outline)",
"--data-table-border-radius": "2px",
"--data-table-cell-font-size": "16px",
@@ -1283,15 +1283,13 @@ exports[`md3Dark theme 1`] = `
"--data-table-empty-padding": "48px 16px",
"--data-table-empty-text-color": "var(--color-text-disabled)",
"--data-table-footer-padding": "12px 16px",
- "--data-table-header-background": "var(--color-surface-container-highest)",
+ "--data-table-header-cell-background": "var(--color-surface-container-highest)",
+ "--data-table-header-cell-text-color": "rgba(255, 255, 255, 0.6)",
"--data-table-header-font-size": "14px",
- "--data-table-header-text-color": "rgba(255, 255, 255, 0.6)",
- "--data-table-hover-background": "var(--color-surface-container-highest)",
"--data-table-row-height": "46px",
+ "--data-table-row-hover-background": "var(--color-surface-container-highest)",
"--data-table-row-large-height": "52px",
"--data-table-row-small-height": "40px",
- "--data-table-row-text-color": "#fff",
- "--data-table-striped-background": "var(--color-surface-container-highest)",
"--data-table-surface-low-background": "#1c1b1d",
"--date-picker-actions-padding": "20px",
"--date-picker-body-background-color": "var(--color-surface-container-high)",
@@ -1823,7 +1821,7 @@ exports[`md3Dark theme 1`] = `
"--table-tbody-td-text-align": "left",
"--table-tbody-td-text-color": "#fff",
"--table-tbody-tr-border-bottom": "thin solid var(--color-outline)",
- "--table-tbody-tr-hover-background": "var(--color-surface-container-highest)",
+ "--table-tbody-tr-hover-background": "var(--color-surface-container-high)",
"--table-thead-border-bottom": "thin solid var(--color-outline)",
"--table-thead-th-font-size": "14px",
"--table-thead-th-text-align": "left",
@@ -2235,6 +2233,7 @@ exports[`md3Light theme 1`] = `
"--counter-padding": "0 4px",
"--cubic-bezier": "cubic-bezier(0.25, 0.8, 0.5, 1)",
"--data-table-background": "var(--color-surface-container-low)",
+ "--data-table-body-cell-text-color": "#555",
"--data-table-border-color": "var(--color-outline)",
"--data-table-border-radius": "2px",
"--data-table-cell-font-size": "16px",
@@ -2242,15 +2241,13 @@ exports[`md3Light theme 1`] = `
"--data-table-empty-padding": "48px 16px",
"--data-table-empty-text-color": "var(--color-text-disabled)",
"--data-table-footer-padding": "12px 16px",
- "--data-table-header-background": "var(--color-surface-container-low)",
+ "--data-table-header-cell-background": "var(--color-surface-container-low)",
+ "--data-table-header-cell-text-color": "rgba(0, 0, 0, 0.6)",
"--data-table-header-font-size": "14px",
- "--data-table-header-text-color": "rgba(0, 0, 0, 0.6)",
- "--data-table-hover-background": "var(--color-surface-container-low)",
"--data-table-row-height": "46px",
+ "--data-table-row-hover-background": "var(--color-surface-container-low)",
"--data-table-row-large-height": "52px",
"--data-table-row-small-height": "40px",
- "--data-table-row-text-color": "#555",
- "--data-table-striped-background": "var(--color-surface-container-low)",
"--data-table-surface-low-background": "var(--color-surface-container-low)",
"--date-picker-actions-padding": "20px",
"--date-picker-body-background-color": "var(--color-surface-container-high)",
@@ -2780,7 +2777,7 @@ exports[`md3Light theme 1`] = `
"--table-tbody-td-text-align": "left",
"--table-tbody-td-text-color": "#555",
"--table-tbody-tr-border-bottom": "thin solid var(--color-outline)",
- "--table-tbody-tr-hover-background": "var(--color-surface-container-low)",
+ "--table-tbody-tr-hover-background": "var(--color-surface-container-high)",
"--table-thead-border-bottom": "thin solid var(--color-outline)",
"--table-thead-th-font-size": "14px",
"--table-thead-th-text-align": "left",
diff --git a/packages/varlet-ui/src/themes/dark/dataTable.ts b/packages/varlet-ui/src/themes/dark/dataTable.ts
index 143e753d0fa..6b448d3763b 100644
--- a/packages/varlet-ui/src/themes/dark/dataTable.ts
+++ b/packages/varlet-ui/src/themes/dark/dataTable.ts
@@ -4,8 +4,9 @@ export default {
'--data-table-header-cell-text-color': 'rgba(255, 255, 255, 0.6)',
'--data-table-body-cell-text-color': '#fff',
'--data-table-border-color': 'var(--color-outline)',
- '--data-table-row-hover-background': '#4c4b4b',
- '--data-table-striped-background': '#303030',
+ '--data-table-row-hover-background': '#3a3a3a',
+ '--data-table-surface-low-row-hover-background': '#2a2a2a',
+ '--data-table-plain-row-hover-background': 'hsla(var(--hsl-on-surface), 0.08)',
'--data-table-empty-text-color': 'var(--color-text-disabled)',
'--data-table-border-radius': '2px',
'--data-table-cell-padding': '0 16px',
diff --git a/packages/varlet-ui/src/themes/dark/table.ts b/packages/varlet-ui/src/themes/dark/table.ts
index efaa1db8f6c..b0c5594a8d0 100644
--- a/packages/varlet-ui/src/themes/dark/table.ts
+++ b/packages/varlet-ui/src/themes/dark/table.ts
@@ -1,9 +1,10 @@
export default {
'--table-background': '#303030',
+ '--table-surface-low-row-hover-background': '#2a2a2a',
'--table-thead-th-text-color': 'rgba(255, 255, 255, 0.6)',
'--table-thead-th-text-align': 'left',
'--table-tbody-td-text-color': '#fff',
- '--table-tbody-tr-hover-background': '#4c4b4b',
+ '--table-tbody-tr-hover-background': '#3a3a3a',
'--table-border-radius': '2px',
'--table-thead-border-bottom': 'thin solid var(--color-outline)',
'--table-thead-th-font-size': '14px',
diff --git a/packages/varlet-ui/src/themes/md3-dark/dataTable.ts b/packages/varlet-ui/src/themes/md3-dark/dataTable.ts
index f7c06ebf853..5bb8b9d7e9d 100644
--- a/packages/varlet-ui/src/themes/md3-dark/dataTable.ts
+++ b/packages/varlet-ui/src/themes/md3-dark/dataTable.ts
@@ -5,8 +5,9 @@ export default {
'--data-table-header-cell-text-color': 'rgba(255, 255, 255, 0.6)',
'--data-table-body-cell-text-color': '#fff',
'--data-table-border-color': 'var(--color-outline)',
- '--data-table-row-hover-background': 'var(--color-surface-container-highest)',
- '--data-table-striped-background': 'var(--color-surface-container-highest)',
+ '--data-table-row-hover-background': 'var(--color-surface-container-high)',
+ '--data-table-surface-low-row-hover-background': 'var(--color-surface-container-highest)',
+ '--data-table-plain-row-hover-background': 'hsla(var(--hsl-on-surface), 0.08)',
'--data-table-empty-text-color': 'var(--color-text-disabled)',
'--data-table-border-radius': '2px',
'--data-table-cell-padding': '0 16px',
diff --git a/packages/varlet-ui/src/themes/md3-dark/table.ts b/packages/varlet-ui/src/themes/md3-dark/table.ts
index 40d01b53303..6fac5478547 100644
--- a/packages/varlet-ui/src/themes/md3-dark/table.ts
+++ b/packages/varlet-ui/src/themes/md3-dark/table.ts
@@ -1,9 +1,10 @@
export default {
'--table-background': 'var(--color-surface-container-highest)',
+ '--table-surface-low-row-hover-background': '#1c1b1d',
'--table-thead-th-text-color': 'rgba(255, 255, 255, 0.6)',
'--table-thead-th-text-align': 'left',
'--table-tbody-td-text-color': '#fff',
- '--table-tbody-tr-hover-background': 'var(--color-surface-container-highest)',
+ '--table-tbody-tr-hover-background': 'var(--color-surface-container-high)',
'--table-border-radius': '2px',
'--table-thead-border-bottom': 'thin solid var(--color-outline)',
'--table-thead-th-font-size': '14px',
diff --git a/packages/varlet-ui/src/themes/md3-light/dataTable.ts b/packages/varlet-ui/src/themes/md3-light/dataTable.ts
index 2128ba89103..7fa59bc1013 100644
--- a/packages/varlet-ui/src/themes/md3-light/dataTable.ts
+++ b/packages/varlet-ui/src/themes/md3-light/dataTable.ts
@@ -5,8 +5,9 @@ export default {
'--data-table-header-cell-text-color': 'rgba(0, 0, 0, 0.6)',
'--data-table-body-cell-text-color': '#555',
'--data-table-border-color': 'var(--color-outline)',
- '--data-table-row-hover-background': 'var(--color-surface-container-low)',
- '--data-table-striped-background': 'var(--color-surface-container-low)',
+ '--data-table-row-hover-background': 'var(--color-surface-container-high)',
+ '--data-table-surface-low-row-hover-background': 'var(--color-surface-container-highest)',
+ '--data-table-plain-row-hover-background': 'hsla(var(--hsl-on-surface), 0.04)',
'--data-table-empty-text-color': 'var(--color-text-disabled)',
'--data-table-border-radius': '2px',
'--data-table-cell-padding': '0 16px',
diff --git a/packages/varlet-ui/src/themes/md3-light/table.ts b/packages/varlet-ui/src/themes/md3-light/table.ts
index 701ddaeba74..73c6bb72c60 100644
--- a/packages/varlet-ui/src/themes/md3-light/table.ts
+++ b/packages/varlet-ui/src/themes/md3-light/table.ts
@@ -1,6 +1,7 @@
export default {
'--table-background': 'var(--color-surface-container-low)',
- '--table-tbody-tr-hover-background': 'var(--color-surface-container-low)',
+ '--table-surface-low-row-hover-background': 'var(--color-surface-container-highest)',
+ '--table-tbody-tr-hover-background': 'var(--color-surface-container-high)',
'--table-border-radius': '2px',
'--table-thead-border-bottom': 'thin solid var(--color-outline)',
'--table-thead-th-text-color': 'rgba(0, 0, 0, 0.6)',
diff --git a/packages/varlet-ui/types/dataTable.d.ts b/packages/varlet-ui/types/dataTable.d.ts
index edd1e3a0c88..7f50b90713d 100644
--- a/packages/varlet-ui/types/dataTable.d.ts
+++ b/packages/varlet-ui/types/dataTable.d.ts
@@ -107,6 +107,7 @@ export interface DataTableProps extends BasicAttributes {
surface?: DataTableSurface
cascade?: boolean
childrenKey?: string
+ plain?: boolean
elevation?: boolean | string | number
cellBordered?: boolean
tableLayout?: DataTableTableLayout
diff --git a/packages/varlet-ui/types/styleVars.d.ts b/packages/varlet-ui/types/styleVars.d.ts
index b44ed1c35cc..abc8d87cca4 100644
--- a/packages/varlet-ui/types/styleVars.d.ts
+++ b/packages/varlet-ui/types/styleVars.d.ts
@@ -274,15 +274,16 @@ interface BaseStyleVars {
'--data-table-body-cell-text-color'?: string
'--data-table-border-color'?: string
'--data-table-row-hover-background'?: string
- '--data-table-striped-background'?: string
+ '--data-table-surface-low-row-hover-background'?: string
+ '--data-table-plain-row-hover-background'?: string
'--data-table-empty-text-color'?: string
'--data-table-border-radius'?: string
'--data-table-cell-padding'?: string
'--data-table-cell-font-size'?: string
'--data-table-header-font-size'?: string
'--data-table-row-height'?: string
- '--data-table-row-height-small'?: string
- '--data-table-row-height-large'?: string
+ '--data-table-row-small-height'?: string
+ '--data-table-row-large-height'?: string
'--data-table-footer-padding'?: string
'--data-table-empty-padding'?: string
'--counter-padding'?: string
@@ -780,6 +781,7 @@ interface BaseStyleVars {
'--table-row-height'?: string
'--table-row-padding'?: string
'--table-footer-border-top'?: string
+ '--table-surface-low-row-hover-background'?: string
'--tabs-item-horizontal-height'?: string
'--tabs-item-vertical-height'?: string
'--tabs-radius'?: string
From 7bb0d32231f60483d16ce08a8f6d1ec5fdac6c87 Mon Sep 17 00:00:00 2001
From: haoziqaq <357229046@qq.com>
Date: Sat, 23 May 2026 21:48:41 +0800
Subject: [PATCH 17/37] feat(data-table): add plain table mode with related
styles, documentation, and examples
---
.../varlet-ui/src/data-table/dataTable.less | 2 ++
.../varlet-ui/src/data-table/docs/en-US.md | 20 +++++------
.../varlet-ui/src/data-table/docs/zh-CN.md | 20 +++++------
.../src/data-table/example/index.vue | 6 ++--
packages/varlet-ui/src/table/Table.vue | 13 +++++++-
.../src/table/__tests__/index.spec.js | 13 ++++++++
packages/varlet-ui/src/table/docs/en-US.md | 33 +++++++++++++++++++
packages/varlet-ui/src/table/docs/zh-CN.md | 33 +++++++++++++++++++
.../varlet-ui/src/table/example/index.vue | 24 ++++++++++++++
.../src/table/example/locale/en-US.ts | 1 +
.../src/table/example/locale/zh-CN.ts | 1 +
packages/varlet-ui/src/table/props.ts | 1 +
packages/varlet-ui/src/table/table.less | 9 +++++
packages/varlet-ui/src/themes/dark/table.ts | 1 +
.../varlet-ui/src/themes/md3-dark/table.ts | 1 +
.../varlet-ui/src/themes/md3-light/table.ts | 1 +
packages/varlet-ui/types/styleVars.d.ts | 2 ++
packages/varlet-ui/types/table.d.ts | 2 ++
18 files changed, 159 insertions(+), 24 deletions(-)
diff --git a/packages/varlet-ui/src/data-table/dataTable.less b/packages/varlet-ui/src/data-table/dataTable.less
index e514dcf4f22..c9ccdfe6bc4 100644
--- a/packages/varlet-ui/src/data-table/dataTable.less
+++ b/packages/varlet-ui/src/data-table/dataTable.less
@@ -130,6 +130,8 @@
&__body-cell {
color: var(--data-table-body-cell-text-color);
+ background: inherit;
+ background-clip: padding-box;
}
&__fixed-cell {
diff --git a/packages/varlet-ui/src/data-table/docs/en-US.md b/packages/varlet-ui/src/data-table/docs/en-US.md
index c9603efe23e..96ff1d7e112 100644
--- a/packages/varlet-ui/src/data-table/docs/en-US.md
+++ b/packages/varlet-ui/src/data-table/docs/en-US.md
@@ -25,6 +25,16 @@ const data = [
```
+### Plain Table
+
+Set `plain` to remove the card-like shadow, background, and radius, and present the table as a pure table surface.
+
+```html
+
+
+
+```
+
### Cell Bordered
```html
@@ -141,16 +151,6 @@ Use `surface="low"` for a subtler MD3-style surface layer.
```
-### Plain Table
-
-Set `plain` to remove the card-like shadow, background, and radius, and present the table as a pure table surface.
-
-```html
-
-
-
-```
-
### Cell Spans
Use `rowSpan`, `colSpan`, and `titleColSpan` to merge body and header cells. Returning `0` hides the current cell, which is typically used together with a previous spanning cell.
diff --git a/packages/varlet-ui/src/data-table/docs/zh-CN.md b/packages/varlet-ui/src/data-table/docs/zh-CN.md
index 701d2347146..8af28f09132 100644
--- a/packages/varlet-ui/src/data-table/docs/zh-CN.md
+++ b/packages/varlet-ui/src/data-table/docs/zh-CN.md
@@ -25,6 +25,16 @@ const data = [
```
+### 纯表格
+
+设置 `plain` 可以一并去掉卡片阴影、背景色和圆角,展示更纯粹的表格形态。
+
+```html
+
+
+
+```
+
### 单元格分割线
```html
@@ -141,16 +151,6 @@ const data = [
```
-### 纯表格
-
-设置 `plain` 可以一并去掉卡片阴影、背景色和圆角,展示更纯粹的表格形态。
-
-```html
-
-
-
-```
-
### 单元格合并
通过 `rowSpan`、`colSpan` 和 `titleColSpan` 控制表体与表头合并。返回 `0` 时当前单元格不渲染,通常配合前一个单元格的跨行或跨列使用。
diff --git a/packages/varlet-ui/src/data-table/example/index.vue b/packages/varlet-ui/src/data-table/example/index.vue
index 6c29c93a0fc..e6ae7635c74 100644
--- a/packages/varlet-ui/src/data-table/example/index.vue
+++ b/packages/varlet-ui/src/data-table/example/index.vue
@@ -254,6 +254,9 @@ watch(
{{ t('basicUsage') }}
+ {{ t('plainTable') }}
+
+
{{ t('cellBordered') }}
@@ -274,9 +277,6 @@ watch(
{{ t('surfaceLow') }}
- {{ t('plainTable') }}
-
-
{{ t('spans') }}
diff --git a/packages/varlet-ui/src/table/Table.vue b/packages/varlet-ui/src/table/Table.vue
index d4aeef76a05..e90d8854862 100644
--- a/packages/varlet-ui/src/table/Table.vue
+++ b/packages/varlet-ui/src/table/Table.vue
@@ -1,5 +1,15 @@
-
+
@@ -31,6 +41,7 @@ export default defineComponent({
classes,
formatElevation,
surfaceLow,
+ plain: computed(() => props.plain),
}
},
})
diff --git a/packages/varlet-ui/src/table/__tests__/index.spec.js b/packages/varlet-ui/src/table/__tests__/index.spec.js
index 58b40a9524e..bbb3798a222 100644
--- a/packages/varlet-ui/src/table/__tests__/index.spec.js
+++ b/packages/varlet-ui/src/table/__tests__/index.spec.js
@@ -40,6 +40,19 @@ describe('test table component props', () => {
wrapper.unmount()
})
+ test('table plain', () => {
+ const wrapper = mount(VarTable, {
+ props: {
+ plain: true,
+ },
+ })
+
+ expect(wrapper.classes()).toContain('var-table--plain')
+ expect(wrapper.classes()).not.toContain('var-elevation--1')
+
+ wrapper.unmount()
+ })
+
test('table scroller height', () => {
const wrapper = mount(VarTable, {
props: {
diff --git a/packages/varlet-ui/src/table/docs/en-US.md b/packages/varlet-ui/src/table/docs/en-US.md
index ad582ed1844..c526f6f879e 100644
--- a/packages/varlet-ui/src/table/docs/en-US.md
+++ b/packages/varlet-ui/src/table/docs/en-US.md
@@ -32,6 +32,36 @@ A minimal table, when you need to display some data in the form of a table, you
```
+### Plain Table
+
+Set `plain` to remove the card-like shadow, background, and radius, and present the table as a pure table surface.
+
+```html
+
+
+
+
+ Name
+ Math
+ English
+
+
+
+
+ Jerry
+ 124
+ 38
+
+
+ Tom
+ 100
+ 135
+
+
+
+
+```
+
### Footer Slots
You can insert something in the tail slot, the most common is to insert a `Pagination`.
@@ -176,6 +206,7 @@ function get(current, size) {
|--------------| -------------- | -------- | ---------- |
| `full-width` | The width of the `table` (including the scrollable part) | _string \| number_ | `100%` |
| `elevation` | Elevation level, options `true` `false` and level of `0-24` | _string \| number \| boolean_| `true` |
+| `plain` | Whether to render as a plain table without card shadow, background, or radius | _boolean_ | `false` |
| `scroller-height` ***3.2.0*** | The height of the scroll container, which can be used to implement functions such as longitudinal partial scrolling and fixed table headers. | _string \| number_ | `-` |
### Slots
@@ -192,7 +223,9 @@ Here are the CSS variables used by the component. Styles can be customized using
| Variable | Default |
| --- | --- |
| `--table-background` | `#fff` |
+| `--table-surface-low-background` | `var(--color-surface-container-highest)` |
| `--table-surface-low-row-hover-background` | `var(--color-surface-container-highest)` |
+| `--table-plain-row-hover-background` | `hsla(var(--hsl-on-surface), 0.04)` |
| `--table-border-radius` | `2px` |
| `--table-thead-border-bottom` | `thin solid var(--color-outline)` |
| `--table-thead-th-text-color` | `rgba(0, 0, 0, 0.6)` |
diff --git a/packages/varlet-ui/src/table/docs/zh-CN.md b/packages/varlet-ui/src/table/docs/zh-CN.md
index 734f822a90f..6b7a0238cf7 100644
--- a/packages/varlet-ui/src/table/docs/zh-CN.md
+++ b/packages/varlet-ui/src/table/docs/zh-CN.md
@@ -32,6 +32,36 @@
```
+### 纯表格
+
+设置 `plain` 可以一并去掉卡片阴影、背景色和圆角,展示更纯粹的表格形态。
+
+```html
+
+
+
+
+ 姓名
+ 数学
+ 英语
+
+
+
+
+ 耗子君
+ 124
+ 38
+
+
+ 火猫桑
+ 100
+ 135
+
+
+
+
+```
+
### 尾部插槽
可以在尾部插槽中插入一些东西,最常见的是插入分页组件。
@@ -176,6 +206,7 @@ function get(current, size) {
|--------------| -------------- | -------- | ---------- |
| `full-width` | `table` 的宽度(包含可滚动部分) | _string \| number_ | `100%` |
| `elevation` | 海拔高度,可选值为 `true` `false` 和 `0-24` 的等级 | _string \| number \| boolean_| `true` |
+| `plain` | 是否以纯表格形态渲染,不带卡片阴影、背景色和圆角 | _boolean_ | `false` |
| `scroller-height` ***3.2.0*** | 滚动容器高度,可用于实现纵向局部滚动,固定表头等功能 | _string \| number_ | `-` |
### 插槽
@@ -192,7 +223,9 @@ function get(current, size) {
| 变量名 | 默认值 |
| --- | --- |
| `--table-background` | `#fff` |
+| `--table-surface-low-background` | `var(--color-surface-container-highest)` |
| `--table-surface-low-row-hover-background` | `var(--color-surface-container-highest)` |
+| `--table-plain-row-hover-background` | `hsla(var(--hsl-on-surface), 0.04)` |
| `--table-border-radius` | `2px` |
| `--table-thead-border-bottom` | `thin solid var(--color-outline)` |
| `--table-thead-th-text-color` | `rgba(0, 0, 0, 0.6)` |
diff --git a/packages/varlet-ui/src/table/example/index.vue b/packages/varlet-ui/src/table/example/index.vue
index 62fd2163789..d9c43f378a7 100644
--- a/packages/varlet-ui/src/table/example/index.vue
+++ b/packages/varlet-ui/src/table/example/index.vue
@@ -55,6 +55,30 @@ onThemeChange()
+ {{ t('plainTable') }}
+
+
+
+ {{ t('name') }}
+ {{ t('math') }}
+ {{ t('english') }}
+
+
+
+
+
+ {{ t('jerry') }}
+ 124
+ 38
+
+
+ {{ t('tom') }}
+ 100
+ 135
+
+
+
+
{{ t('slot') }}
diff --git a/packages/varlet-ui/src/table/example/locale/en-US.ts b/packages/varlet-ui/src/table/example/locale/en-US.ts
index fa3cc403825..553f51616a1 100644
--- a/packages/varlet-ui/src/table/example/locale/en-US.ts
+++ b/packages/varlet-ui/src/table/example/locale/en-US.ts
@@ -1,5 +1,6 @@
export default {
basicUsage: 'Basic Usage',
+ plainTable: 'Plain Table',
slot: 'Footer Slots',
fixedHeader: 'Fixed Table Header',
math: 'Math',
diff --git a/packages/varlet-ui/src/table/example/locale/zh-CN.ts b/packages/varlet-ui/src/table/example/locale/zh-CN.ts
index ce8c7096cd3..7e81c24cacd 100644
--- a/packages/varlet-ui/src/table/example/locale/zh-CN.ts
+++ b/packages/varlet-ui/src/table/example/locale/zh-CN.ts
@@ -1,5 +1,6 @@
export default {
basicUsage: '基本使用',
+ plainTable: '纯表格',
slot: '尾部插槽',
fixedHeader: '固定表头',
math: '数学',
diff --git a/packages/varlet-ui/src/table/props.ts b/packages/varlet-ui/src/table/props.ts
index fa260316249..037a33eb25e 100644
--- a/packages/varlet-ui/src/table/props.ts
+++ b/packages/varlet-ui/src/table/props.ts
@@ -15,4 +15,5 @@ export const props = {
default: true,
},
surface: String as PropType,
+ plain: Boolean,
}
diff --git a/packages/varlet-ui/src/table/table.less b/packages/varlet-ui/src/table/table.less
index e69d536d81e..9dcddc66f1c 100644
--- a/packages/varlet-ui/src/table/table.less
+++ b/packages/varlet-ui/src/table/table.less
@@ -2,6 +2,7 @@
--table-background: #fff;
--table-surface-low-background: var(--color-surface-container-highest);
--table-surface-low-row-hover-background: var(--color-surface-container-highest);
+ --table-plain-row-hover-background: hsla(var(--hsl-on-surface), 0.04);
--table-border-radius: 2px;
--table-thead-border-bottom: thin solid var(--color-outline);
--table-thead-th-text-color: rgba(0, 0, 0, 0.6);
@@ -78,6 +79,8 @@
td {
color: var(--table-tbody-td-text-color);
font-size: var(--table-tbody-td-font-size);
+ background: inherit;
+ background-clip: padding-box;
}
}
@@ -103,6 +106,12 @@
--table-tbody-tr-hover-background: var(--table-surface-low-row-hover-background);
}
+ &--plain {
+ --table-background: transparent;
+ --table-tbody-tr-hover-background: var(--table-plain-row-hover-background);
+ border-radius: 0;
+ }
+
&__footer {
width: 100%;
min-height: var(--table-row-height);
diff --git a/packages/varlet-ui/src/themes/dark/table.ts b/packages/varlet-ui/src/themes/dark/table.ts
index b0c5594a8d0..1f064b6d7b0 100644
--- a/packages/varlet-ui/src/themes/dark/table.ts
+++ b/packages/varlet-ui/src/themes/dark/table.ts
@@ -1,5 +1,6 @@
export default {
'--table-background': '#303030',
+ '--table-plain-row-hover-background': 'hsla(var(--hsl-on-surface), 0.08)',
'--table-surface-low-row-hover-background': '#2a2a2a',
'--table-thead-th-text-color': 'rgba(255, 255, 255, 0.6)',
'--table-thead-th-text-align': 'left',
diff --git a/packages/varlet-ui/src/themes/md3-dark/table.ts b/packages/varlet-ui/src/themes/md3-dark/table.ts
index 6fac5478547..83214f6aaf3 100644
--- a/packages/varlet-ui/src/themes/md3-dark/table.ts
+++ b/packages/varlet-ui/src/themes/md3-dark/table.ts
@@ -1,5 +1,6 @@
export default {
'--table-background': 'var(--color-surface-container-highest)',
+ '--table-plain-row-hover-background': 'hsla(var(--hsl-on-surface), 0.08)',
'--table-surface-low-row-hover-background': '#1c1b1d',
'--table-thead-th-text-color': 'rgba(255, 255, 255, 0.6)',
'--table-thead-th-text-align': 'left',
diff --git a/packages/varlet-ui/src/themes/md3-light/table.ts b/packages/varlet-ui/src/themes/md3-light/table.ts
index 73c6bb72c60..01b25ae88ad 100644
--- a/packages/varlet-ui/src/themes/md3-light/table.ts
+++ b/packages/varlet-ui/src/themes/md3-light/table.ts
@@ -1,5 +1,6 @@
export default {
'--table-background': 'var(--color-surface-container-low)',
+ '--table-plain-row-hover-background': 'hsla(var(--hsl-on-surface), 0.04)',
'--table-surface-low-row-hover-background': 'var(--color-surface-container-highest)',
'--table-tbody-tr-hover-background': 'var(--color-surface-container-high)',
'--table-border-radius': '2px',
diff --git a/packages/varlet-ui/types/styleVars.d.ts b/packages/varlet-ui/types/styleVars.d.ts
index abc8d87cca4..c591785dbf2 100644
--- a/packages/varlet-ui/types/styleVars.d.ts
+++ b/packages/varlet-ui/types/styleVars.d.ts
@@ -774,6 +774,8 @@ interface BaseStyleVars {
'--table-thead-th-font-size'?: string
'--table-thead-tr-border-bottom'?: string
'--table-tbody-tr-hover-background'?: string
+ '--table-surface-low-row-hover-background'?: string
+ '--table-plain-row-hover-background'?: string
'--table-tbody-tr-border-bottom'?: string
'--table-tbody-td-text-color'?: string
'--table-tbody-td-font-size'?: string
diff --git a/packages/varlet-ui/types/table.d.ts b/packages/varlet-ui/types/table.d.ts
index e0ef0e27d38..4ec17529283 100644
--- a/packages/varlet-ui/types/table.d.ts
+++ b/packages/varlet-ui/types/table.d.ts
@@ -7,6 +7,8 @@ export interface TableProps extends BasicAttributes {
fullWidth?: string | number
scrollerHeight?: string | number
elevation?: boolean | string | number
+ surface?: 'low'
+ plain?: boolean
}
export class Table extends VarComponent {
From c542b5050f26caea266f0dea6f4821e54b68b359 Mon Sep 17 00:00:00 2001
From: haoziqaq <357229046@qq.com>
Date: Sat, 23 May 2026 23:12:59 +0800
Subject: [PATCH 18/37] feat(data-table): add single and multiple column
sorting functionality
- Implemented single column sorting with controlled state using v-model:sorters.
- Added support for multiple column sorting by setting sort-mode to "multiple".
- Enhanced documentation for sorting features in zh-CN and en-US.
- Updated example files to demonstrate sorting capabilities.
- Introduced new props for sorters and sort mode in DataTable component.
- Created useSorter composable for managing sorting logic.
- Added new style variables for sorting triggers and surface low background.
---
.../varlet-ui/src/data-table/DataTable.vue | 18 ++-
.../src/data-table/DataTableHeaderCell.vue | 97 +++++++++++-
.../src/data-table/__tests__/index.spec.js | 94 +++++++++++
.../varlet-ui/src/data-table/dataTable.less | 85 +++++++++-
.../varlet-ui/src/data-table/docs/en-US.md | 148 +++++++++++++++++-
.../varlet-ui/src/data-table/docs/zh-CN.md | 148 +++++++++++++++++-
.../src/data-table/example/index.vue | 134 ++++++++++++++++
.../src/data-table/example/locale/en-US.ts | 4 +
.../src/data-table/example/locale/zh-CN.ts | 4 +
packages/varlet-ui/src/data-table/props.ts | 17 ++
.../varlet-ui/src/data-table/usePagination.ts | 8 +-
.../varlet-ui/src/data-table/useSorter.ts | 75 +++++++++
packages/varlet-ui/src/table/docs/en-US.md | 31 ++++
packages/varlet-ui/src/table/docs/zh-CN.md | 31 ++++
.../varlet-ui/src/table/example/index.vue | 24 +++
.../src/table/example/locale/en-US.ts | 1 +
.../src/table/example/locale/zh-CN.ts | 1 +
.../__snapshots__/index.spec.js.snap | 28 +++-
.../varlet-ui/src/themes/dark/dataTable.ts | 4 +
.../src/themes/md3-dark/dataTable.ts | 3 +
.../src/themes/md3-light/dataTable.ts | 3 +
packages/varlet-ui/types/dataTable.d.ts | 13 +-
packages/varlet-ui/types/styleVars.d.ts | 4 +
23 files changed, 955 insertions(+), 20 deletions(-)
create mode 100644 packages/varlet-ui/src/data-table/useSorter.ts
diff --git a/packages/varlet-ui/src/data-table/DataTable.vue b/packages/varlet-ui/src/data-table/DataTable.vue
index 6937d2414fd..8860b659c04 100644
--- a/packages/varlet-ui/src/data-table/DataTable.vue
+++ b/packages/varlet-ui/src/data-table/DataTable.vue
@@ -38,10 +38,13 @@
:is-expand-column="isExpandColumn"
:is-multiple-selection-column="isMultipleSelectionColumn"
:is-selection-column-selectable="isSelectionColumnSelectable"
+ :is-column-sortable="isColumnSortable"
+ :get-column-sorter-order="getColumnSorterOrder"
:is-column-resizable="isColumnResizable"
:is-last-header-column="isLastHeaderColumn"
:is-last-left-fixed-column="isLastLeftFixedColumn"
:is-first-right-fixed-column="isFirstRightFixedColumn"
+ :toggle-column-sorter="toggleColumnSorter"
:toggle-current-selectable-rows="toggleCurrentSelectableRows"
:start-column-resize="startColumnResize"
/>
@@ -112,8 +115,8 @@
/>
-
-
+
+
@@ -144,6 +147,7 @@ import { useColumnWidths } from './useColumnWidths'
import { useExpandRow } from './useExpandRow'
import { usePagination } from './usePagination'
import { useSelectionColumn } from './useSelectionColumn'
+import { useSorter } from './useSorter'
import { useTreeExpand } from './useTreeExpand'
const { name, n, classes } = createNamespace('data-table')
@@ -215,9 +219,16 @@ export default defineComponent({
columnWidths: () => columnWidths.value,
})
+ const { getColumnSorterOrder, isColumnSortable, toggleColumnSorter } = useSorter({
+ sorters: () => props.sorters,
+ sortMode: () => props.sortMode,
+ onUpdateSorters: () => props['onUpdate:sorters'],
+ })
+
const { paginationProps, paginationTotal, showPagination, pagedData } = usePagination({
pagination: () => props.pagination,
remote: () => props.remote,
+ loading: () => props.loading,
page: () => props.page,
pageSize: () => props.pageSize,
total: () => props.total,
@@ -404,12 +415,15 @@ export default defineComponent({
getCellProps,
isSelectionColumn,
isExpandColumn,
+ isColumnSortable,
isMultipleSelectionColumn,
isSelectionColumnSelectable,
+ getColumnSorterOrder,
isRowExpandable,
isRowKeyIndeterminate,
isRowKeySelected,
isRowSelectable,
+ toggleColumnSorter,
toggleCurrentSelectableRows,
toggleRowExpanded,
toggleTreeRowExpanded,
diff --git a/packages/varlet-ui/src/data-table/DataTableHeaderCell.vue b/packages/varlet-ui/src/data-table/DataTableHeaderCell.vue
index 75323ea2d77..f71b9b0abf3 100644
--- a/packages/varlet-ui/src/data-table/DataTableHeaderCell.vue
+++ b/packages/varlet-ui/src/data-table/DataTableHeaderCell.vue
@@ -22,8 +22,33 @@
tabindex="-1"
@update:model-value="toggleCurrentSelectableRows"
/>
+
+ {{ headerCell.column.title }}
+
+
+
+
+
- {{ isSelectionColumn(headerCell.column) || isExpandColumn(headerCell.column) ? '' : headerCell.column.title }}
+ {{ headerTitle }}
-import { defineComponent, type CSSProperties, type PropType } from 'vue'
+import { computed, defineComponent, type CSSProperties, type PropType } from 'vue'
import VarCheckbox from '../checkbox'
+import VarIcon from '../icon'
import { createNamespace } from '../utils/components'
-import type { DataTableColumn, DataTableExpandColumn, DataTableSelectionColumn } from './props'
+import type {
+ DataTableColumn,
+ DataTableExpandColumn,
+ DataTableFieldColumn,
+ DataTableSelectionColumn,
+ DataTableSorterOrder,
+} from './props'
const { n, classes } = createNamespace('data-table')
@@ -59,6 +91,7 @@ export default defineComponent({
name: 'DataTableHeaderCell',
components: {
VarCheckbox,
+ VarIcon,
},
props: {
headerCell: {
@@ -97,6 +130,18 @@ export default defineComponent({
type: Function as PropType<(column: DataTableSelectionColumn) => boolean>,
required: true,
},
+ isColumnSortable: {
+ type: Function as PropType<(column: DataTableColumn) => column is DataTableFieldColumn>,
+ required: true,
+ },
+ getColumnSorterOrder: {
+ type: Function as PropType<(columnKey: string) => DataTableSorterOrder | undefined>,
+ required: true,
+ },
+ toggleColumnSorter: {
+ type: Function as PropType<(columnKey: string) => void>,
+ required: true,
+ },
isColumnResizable: {
type: Function as PropType<(column: DataTableColumn) => boolean>,
required: true,
@@ -122,10 +167,54 @@ export default defineComponent({
required: true,
},
},
- setup() {
+ setup(props) {
+ const columnSorterOrder = computed(() => {
+ if (!props.isColumnSortable(props.headerCell.column)) {
+ return
+ }
+
+ return props.getColumnSorterOrder(props.headerCell.column.key)
+ })
+
+ const headerAlign = computed(() => {
+ return props.headerCell.column.titleAlign ?? props.headerCell.column.align ?? 'left'
+ })
+
+ const headerTitle = computed(() => {
+ return props.isSelectionColumn(props.headerCell.column) || props.isExpandColumn(props.headerCell.column)
+ ? ''
+ : props.headerCell.column.title
+ })
+
+ const sortTriggerStyle = computed(() => {
+ return {
+ position: 'absolute',
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0,
+ }
+ })
+
+ const sortTriggerLabel = computed(() => {
+ if (!props.isColumnSortable(props.headerCell.column)) {
+ return ''
+ }
+
+ const orderLabel =
+ columnSorterOrder.value === 'asc' ? 'ascending' : columnSorterOrder.value === 'desc' ? 'descending' : 'none'
+
+ return `Sort by ${props.headerCell.column.title}, current: ${orderLabel}`
+ })
+
return {
n,
classes,
+ columnSorterOrder,
+ headerTitle,
+ headerAlign,
+ sortTriggerStyle,
+ sortTriggerLabel,
}
},
})
diff --git a/packages/varlet-ui/src/data-table/__tests__/index.spec.js b/packages/varlet-ui/src/data-table/__tests__/index.spec.js
index cd93a099f50..58cf77236d9 100644
--- a/packages/varlet-ui/src/data-table/__tests__/index.spec.js
+++ b/packages/varlet-ui/src/data-table/__tests__/index.spec.js
@@ -86,6 +86,19 @@ describe('test data-table component props', () => {
wrapper.unmount()
})
+ test('should disable pagination while loading', () => {
+ const wrapper = mount(VarDataTable, {
+ props: {
+ columns,
+ data,
+ loading: true,
+ },
+ })
+
+ expect(wrapper.findComponent({ name: 'var-pagination' }).props('disabled')).toBe(true)
+ wrapper.unmount()
+ })
+
test('should support render function', () => {
const wrapper = mount(VarDataTable, {
props: {
@@ -120,6 +133,87 @@ describe('test data-table component props', () => {
wrapper.unmount()
})
+ test('should support single sorter cycle', async () => {
+ const onUpdateSorters = vi.fn()
+ const wrapper = mount(VarDataTable, {
+ props: {
+ columns: [
+ { key: 'name', title: 'Name', sorter: true },
+ { key: 'role', title: 'Role' },
+ ],
+ data,
+ pagination: false,
+ sorters: [],
+ 'onUpdate:sorters': onUpdateSorters,
+ },
+ })
+
+ const sortTrigger = wrapper.find('.var-data-table__sort-trigger')
+
+ await sortTrigger.trigger('click')
+ expect(onUpdateSorters).toHaveBeenLastCalledWith([{ key: 'name', order: 'asc' }])
+
+ await wrapper.setProps({ sorters: [{ key: 'name', order: 'asc' }] })
+ await sortTrigger.trigger('click')
+ expect(onUpdateSorters).toHaveBeenLastCalledWith([{ key: 'name', order: 'desc' }])
+
+ await wrapper.setProps({ sorters: [{ key: 'name', order: 'desc' }] })
+ await sortTrigger.trigger('click')
+ expect(onUpdateSorters).toHaveBeenLastCalledWith([])
+
+ wrapper.unmount()
+ })
+
+ test('should support multiple sorters', async () => {
+ const onUpdateSorters = vi.fn()
+ const wrapper = mount(VarDataTable, {
+ props: {
+ columns: [
+ { key: 'name', title: 'Name', sorter: true },
+ { key: 'role', title: 'Role', sorter: true },
+ ],
+ data,
+ pagination: false,
+ sorters: [{ key: 'name', order: 'asc' }],
+ sortMode: 'multiple',
+ 'onUpdate:sorters': onUpdateSorters,
+ },
+ })
+
+ const sortTriggers = wrapper.findAll('.var-data-table__sort-trigger')
+
+ await sortTriggers[1].trigger('click')
+
+ expect(onUpdateSorters).toHaveBeenLastCalledWith([
+ { key: 'name', order: 'asc' },
+ { key: 'role', order: 'asc' },
+ ])
+
+ wrapper.unmount()
+ })
+
+ test('should only render sorter trigger for sortable field columns', () => {
+ const wrapper = mount(VarDataTable, {
+ props: {
+ columns: [
+ { type: 'selection' },
+ { key: 'name', title: 'Name', sorter: true },
+ {
+ type: 'expand',
+ renderExpand: ({ row }) => h('div', row.role),
+ },
+ { key: 'role', title: 'Role' },
+ ],
+ data,
+ pagination: false,
+ },
+ })
+
+ expect(wrapper.findAll('.var-data-table__sort-trigger')).toHaveLength(1)
+
+ wrapper.unmount()
+ })
+
test('should support selection column', async () => {
const onUpdateCheckedRowKeys = vi.fn()
const wrapper = mount(VarDataTable, {
diff --git a/packages/varlet-ui/src/data-table/dataTable.less b/packages/varlet-ui/src/data-table/dataTable.less
index c9ccdfe6bc4..e1a8bbd4402 100644
--- a/packages/varlet-ui/src/data-table/dataTable.less
+++ b/packages/varlet-ui/src/data-table/dataTable.less
@@ -8,6 +8,9 @@
--data-table-row-hover-background: #f5f5f5;
--data-table-surface-low-row-hover-background: var(--color-surface-container-highest);
--data-table-plain-row-hover-background: hsla(var(--hsl-on-surface), 0.04);
+ --data-table-sort-trigger-color: hsla(var(--hsl-on-surface), 0.42);
+ --data-table-sort-trigger-active-color: var(--color-primary);
+ --data-table-sort-trigger-hover-background: hsla(var(--hsl-primary), 0.08);
--data-table-empty-text-color: var(--color-text-disabled);
--data-table-resize-trigger-color: hsla(var(--hsl-on-surface-variant), 0.36);
--data-table-fixed-shadow-color: rgba(0, 0, 0, 0.04);
@@ -49,7 +52,6 @@
&__header-row {
background: var(--data-table-header-cell-background);
- border-bottom: 1px solid var(--data-table-border-color);
}
&__row {
@@ -99,10 +101,91 @@
font-size: var(--data-table-header-font-size);
font-weight: 500;
background: var(--data-table-header-cell-background);
+ border-bottom: 1px solid var(--data-table-border-color);
position: sticky;
top: 0;
}
+ &__sort-trigger {
+ display: flex;
+ align-items: center;
+ width: auto;
+ height: auto;
+ padding: var(--data-table-cell-padding);
+ border: 0;
+ border-radius: 0;
+ background: transparent;
+ color: var(--data-table-sort-trigger-color);
+ cursor: pointer;
+ text-align: inherit;
+ transition:
+ color 0.2s ease,
+ background-color 0.2s ease;
+
+ &:hover,
+ &:focus-visible {
+ background: var(--data-table-sort-trigger-hover-background);
+ }
+
+ &:focus-visible {
+ outline: none;
+ }
+ }
+
+ &__sort-trigger--align-left {
+ justify-content: flex-start;
+ }
+
+ &__sort-trigger--align-center {
+ justify-content: center;
+ }
+
+ &__sort-trigger--align-right {
+ justify-content: flex-end;
+ }
+
+ &__sort-trigger--active {
+ color: var(--data-table-sort-trigger-active-color);
+ }
+
+ &__sort-trigger-text {
+ display: inline-flex;
+ align-items: center;
+ min-width: 0;
+ }
+
+ &__sort-trigger-icon {
+ display: inline-flex;
+ flex-direction: column;
+ justify-content: center;
+ gap: 0;
+ margin-inline-start: 8px;
+ vertical-align: middle;
+ color: inherit;
+ opacity: 0.72;
+ }
+
+ &__sort-trigger-icon-up,
+ &__sort-trigger-icon-down {
+ display: block;
+ font-size: 12px;
+ line-height: 1;
+ opacity: 0.45;
+ transition: opacity 0.2s ease;
+ }
+
+ &__sort-trigger-icon-up {
+ margin-bottom: -5px;
+ }
+
+ &__sort-trigger-icon-down {
+ margin-top: -5px;
+ }
+
+ &__sort-trigger-icon--active {
+ opacity: 1;
+ }
+
&__resize-trigger {
position: absolute;
top: 0;
diff --git a/packages/varlet-ui/src/data-table/docs/en-US.md b/packages/varlet-ui/src/data-table/docs/en-US.md
index 96ff1d7e112..3a3c4487fac 100644
--- a/packages/varlet-ui/src/data-table/docs/en-US.md
+++ b/packages/varlet-ui/src/data-table/docs/en-US.md
@@ -35,6 +35,65 @@ Set `plain` to remove the card-like shadow, background, and radius, and present
```
+### Single Column Sorting
+
+Set `column.sorter` to enable sorter interaction on a field column. The component only manages sorter state. You should control `v-model:sorters` and decide how to sort the data yourself.
+
+```html
+
+
+
+
+
+```
+
+### Multiple Column Sorting
+
+Set `sort-mode="multiple"` to keep multiple columns active at the same time.
+
+```html
+
+
+
+```
+
### Cell Bordered
```html
@@ -110,7 +169,7 @@ Use `column.render` to customize the cell content.
```html
+
+
+
+
+```
+
+### Right Alignment
+
+Use right-aligned headers and cells together with `sorter` and `resizable` to verify the trigger layout in a right-aligned case.
+
+```html
+
+
+
+
+
+```
+
## API
### Props
@@ -512,6 +639,8 @@ const resizableColumns = [
| `total` | Total item count in remote mode | _number_ | `-` |
| `max-height` | Max height of the table body. When set, the header stays fixed and the body scrolls internally | _number \| string_ | `-` |
| `scroll-x` | Table width used to enable horizontal scrolling. Usually paired with fixed columns | _number \| string_ | `-` |
+| `v-model:sorters` | Controlled sorter states | _DataTableSorter[]_ | `[]` |
+| `sort-mode` | Sorter mode | _'single' \| 'multiple'_ | `'single'` |
| `plain` | Whether to render as a plain table without card shadow, background, or radius | _boolean_ | `false` |
| `table-layout` | Native `table-layout` value | _'auto' \| 'fixed'_ | `'auto'` |
| `tree` | Whether to explicitly enable tree data mode | _boolean_ | `false` |
@@ -522,13 +651,14 @@ const resizableColumns = [
| `cell-bordered` | Whether to show cell dividers | _boolean_ | `false` |
| `size` | Table size | _'small' \| 'normal' \| 'large'_ | `'normal'` |
-### DataTableColumn
+#### DataTableColumn
| Prop | Description | Type | Default |
| --- | --- | --- | --- |
| `type` | Column type. Supports `selection` and `expand` | _'selection' \| 'expand'_ | `-` |
| `key` | Unique column key | _string_ | `-` |
| `title` | Column title | _string_ | `-` |
+| `sorter` | Whether the field column shows sorter interaction | _boolean_ | `false` |
| `multiple` | Whether the selection column allows multiple rows | _boolean_ | `true` |
| `selectable` | Whether selection is enabled. Supports `boolean` or `(context) => boolean` | _boolean \| `(context) => boolean`_ | `true` |
| `expandable` | Whether the row can be expanded. Only works on expand columns | _`(context) => boolean`_ | `-` |
@@ -545,7 +675,7 @@ const resizableColumns = [
| `cellProps` | Native cell props, supports object or function | _object \| (context) => object_ | `-` |
| `render` | Custom cell render | _`(context) => VNodeChild`_ | `-` |
-### DataTablePagination
+#### DataTablePagination
| Prop | Description | Type | Default |
| --- | --- | --- | --- |
@@ -557,12 +687,19 @@ const resizableColumns = [
| `sizeOption` | Page size options | _number[]_ | `[10, 20, 50, 100]` |
| `showTotal` | Total text renderer | _`(total: number, range: [number, number]) => string`_ | `-` |
+#### DataTableSorter
+
+| Prop | Description | Type | Default |
+| --- | --- | --- | --- |
+| `key` | Target column key | _string_ | `-` |
+| `order` | Sort order | _'asc' \| 'desc'_ | `-` |
+
### Slots
| Name | Description | Parameters |
| --- | --- | --- |
| `empty` | Custom empty content | `-` |
-| `loading` | Custom loading content | `-` |
+| `loading-description` | Custom loading description | `-` |
| `footer-prefix` | Content before pagination | `-` |
### Style Variables
@@ -578,6 +715,9 @@ const resizableColumns = [
| `--data-table-row-hover-background` | `#f5f5f5` |
| `--data-table-surface-low-row-hover-background` | `var(--color-surface-container-highest)` |
| `--data-table-plain-row-hover-background` | `hsla(var(--hsl-on-surface), 0.04)` |
+| `--data-table-sort-trigger-color` | `hsla(var(--hsl-on-surface), 0.42)` |
+| `--data-table-sort-trigger-active-color` | `var(--color-primary)` |
+| `--data-table-sort-trigger-hover-background` | `hsla(var(--hsl-primary), 0.08)` |
| `--data-table-empty-text-color` | `var(--color-text-disabled)` |
| `--data-table-border-radius` | `2px` |
| `--data-table-cell-padding` | `0 16px` |
diff --git a/packages/varlet-ui/src/data-table/docs/zh-CN.md b/packages/varlet-ui/src/data-table/docs/zh-CN.md
index 8af28f09132..8596fb37093 100644
--- a/packages/varlet-ui/src/data-table/docs/zh-CN.md
+++ b/packages/varlet-ui/src/data-table/docs/zh-CN.md
@@ -35,6 +35,65 @@ const data = [
```
+### 单列排序
+
+给字段列设置 `column.sorter` 后,可以开启排序交互。组件只管理排序状态,你需要通过 `v-model:sorters` 受控这些状态,并自行决定如何排序数据。
+
+```html
+
+
+
+
+
+```
+
+### 多列排序
+
+设置 `sort-mode="multiple"` 后,可以让多个列同时处于非无排序态。
+
+```html
+
+
+
+```
+
### 单元格分割线
```html
@@ -110,7 +169,7 @@ const customRowProps = ({ row, rowIndex }) => ({
```html
+
+
+
+
+```
+
+### 右对齐
+
+当你需要检查右对齐表头和单元格下排序按钮、列宽拖拽触发区的布局时,可以同时设置 `align`、`titleAlign` 为 `right`,并开启 `sorter` 和 `resizable`。
+
+```html
+
+
+
+
+
+```
+
## API
### Props
@@ -512,6 +639,8 @@ const resizableColumns = [
| `total` | 远程分页总条数 | _number_ | `-` |
| `max-height` | 表格主体最大高度。设置后表头固定,内容区域内部滚动 | _number \| string_ | `-` |
| `scroll-x` | 用于开启横向滚动的表格宽度,通常和固定列一起使用 | _number \| string_ | `-` |
+| `v-model:sorters` | 受控排序状态集合 | _DataTableSorter[]_ | `[]` |
+| `sort-mode` | 排序器模式 | _'single' \| 'multiple'_ | `'single'` |
| `plain` | 是否以纯表格形态渲染,不带卡片阴影、背景色和圆角 | _boolean_ | `false` |
| `table-layout` | 原生 `table-layout` 布局方式 | _'auto' \| 'fixed'_ | `'auto'` |
| `tree` | 是否显式开启树形数据 | _boolean_ | `false` |
@@ -522,13 +651,14 @@ const resizableColumns = [
| `cell-bordered` | 是否显示单元格分割线 | _boolean_ | `false` |
| `size` | 表格尺寸 | _'small' \| 'normal' \| 'large'_ | `'normal'` |
-### DataTableColumn
+#### DataTableColumn
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| `type` | 列类型。支持 `selection` 和 `expand` | _'selection' \| 'expand'_ | `-` |
| `key` | 列唯一 key | _string_ | `-` |
| `title` | 列标题 | _string_ | `-` |
+| `sorter` | 字段列是否显示排序交互 | _boolean_ | `false` |
| `multiple` | 选择列是否允许多选,仅对选择列生效 | _boolean_ | `true` |
| `selectable` | 是否允许选择。支持 `boolean` 或 `(context) => boolean`,仅对选择列生效 | _boolean \| `(context) => boolean`_ | `true` |
| `expandable` | 是否允许展开该行,仅对展开列生效 | _`(context) => boolean`_ | `-` |
@@ -545,7 +675,7 @@ const resizableColumns = [
| `cellProps` | 单元格属性透传,支持对象或函数 | _object \| (context) => object_ | `-` |
| `render` | 自定义单元格渲染 | _`(context) => VNodeChild`_ | `-` |
-### DataTablePagination
+#### DataTablePagination
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
@@ -557,12 +687,19 @@ const resizableColumns = [
| `sizeOption` | 每页条数选项 | _number[]_ | `[10, 20, 50, 100]` |
| `showTotal` | 总数文案渲染函数 | _`(total: number, range: [number, number]) => string`_ | `-` |
+#### DataTableSorter
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| `key` | 对应列 key | _string_ | `-` |
+| `order` | 排序方向 | _'asc' \| 'desc'_ | `-` |
+
### Slots
| 名称 | 说明 | 参数 |
| --- | --- | --- |
| `empty` | 自定义空态内容 | `-` |
-| `loading` | 自定义加载内容 | `-` |
+| `loading-description` | 自定义加载描述 | `-` |
| `footer-prefix` | 分页前置内容 | `-` |
### 样式变量
@@ -578,6 +715,9 @@ const resizableColumns = [
| `--data-table-row-hover-background` | `#f5f5f5` |
| `--data-table-surface-low-row-hover-background` | `var(--color-surface-container-highest)` |
| `--data-table-plain-row-hover-background` | `hsla(var(--hsl-on-surface), 0.04)` |
+| `--data-table-sort-trigger-color` | `hsla(var(--hsl-on-surface), 0.42)` |
+| `--data-table-sort-trigger-active-color` | `var(--color-primary)` |
+| `--data-table-sort-trigger-hover-background` | `hsla(var(--hsl-primary), 0.08)` |
| `--data-table-empty-text-color` | `var(--color-text-disabled)` |
| `--data-table-border-radius` | `2px` |
| `--data-table-cell-padding` | `0 16px` |
diff --git a/packages/varlet-ui/src/data-table/example/index.vue b/packages/varlet-ui/src/data-table/example/index.vue
index e6ae7635c74..e7cf8b5ed55 100644
--- a/packages/varlet-ui/src/data-table/example/index.vue
+++ b/packages/varlet-ui/src/data-table/example/index.vue
@@ -44,6 +44,11 @@ const surfaceColumns = [
{ key: 'role', title: 'Role' },
{ key: 'status', title: 'Status' },
]
+const sortableColumns = [
+ { key: 'name', title: 'Name', sorter: true },
+ { key: 'role', title: 'Role', sorter: true },
+ { key: 'status', title: 'Status', sorter: true },
+]
const spanColumns = [
{
@@ -105,8 +110,86 @@ const resizableColumns = [
{ key: 'role', title: 'Role', width: 220, minWidth: 160, resizable: true },
{ key: 'status', title: 'Status', width: 140, maxWidth: 180, resizable: true },
]
+const centeredSortableResizableColumns = [
+ { type: 'selection' },
+ {
+ type: 'expand',
+ renderExpand: renderExpandContent,
+ },
+ {
+ key: 'name',
+ title: 'Name',
+ width: 170,
+ minWidth: 120,
+ resizable: true,
+ sorter: true,
+ align: 'center',
+ titleAlign: 'center',
+ },
+ {
+ key: 'role',
+ title: 'Role',
+ width: 190,
+ minWidth: 140,
+ resizable: true,
+ sorter: true,
+ align: 'center',
+ titleAlign: 'center',
+ },
+ {
+ key: 'status',
+ title: 'Status',
+ width: 150,
+ minWidth: 120,
+ resizable: true,
+ sorter: true,
+ align: 'center',
+ titleAlign: 'center',
+ },
+]
+const rightAlignedSortableResizableColumns = [
+ { type: 'selection' },
+ {
+ type: 'expand',
+ renderExpand: renderExpandContent,
+ },
+ {
+ key: 'name',
+ title: 'Name',
+ width: 170,
+ minWidth: 120,
+ resizable: true,
+ sorter: true,
+ align: 'right',
+ titleAlign: 'right',
+ },
+ {
+ key: 'role',
+ title: 'Role',
+ width: 190,
+ minWidth: 140,
+ resizable: true,
+ sorter: true,
+ align: 'right',
+ titleAlign: 'right',
+ },
+ {
+ key: 'status',
+ title: 'Status',
+ width: 150,
+ minWidth: 120,
+ resizable: true,
+ sorter: true,
+ align: 'right',
+ titleAlign: 'right',
+ },
+]
const checkedRowKeys = ref([1, 3])
+const sorters = ref([])
+const multipleSorters = ref([])
+const centeredSorters = ref([])
+const rightAlignedSorters = ref([])
const singleCheckedRowKeys = ref([2])
const treeCheckedRowKeys = ref([1, 11, 12])
const treeNonCascadeCheckedRowKeys = ref([12])
@@ -184,6 +267,11 @@ const selectedRowNames = computed(() =>
.join(', '),
)
+const sortedData = computed(() => applySorters(data, sorters.value))
+const multipleSortedData = computed(() => applySorters(data, multipleSorters.value))
+const centeredSortedData = computed(() => applySorters(data, centeredSorters.value))
+const rightAlignedSortedData = computed(() => applySorters(data, rightAlignedSorters.value))
+
const alignedColumns = [
{ key: 'name', title: 'Name', minWidth: 140 },
{ key: 'role', title: 'Role', titleAlign: 'center', align: 'center', width: 120 },
@@ -248,6 +336,22 @@ watch(
},
{ immediate: true },
)
+
+function applySorters(rows, activeSorters) {
+ return activeSorters.reduceRight((currentRows, sorter) => {
+ return [...currentRows].sort((leftRow, rightRow) => {
+ const leftValue = leftRow[sorter.key]
+ const rightValue = rightRow[sorter.key]
+
+ if (leftValue === rightValue) {
+ return 0
+ }
+
+ const comparisonResult = leftValue > rightValue ? 1 : -1
+ return sorter.order === 'asc' ? comparisonResult : -comparisonResult
+ })
+ }, rows)
+}
@@ -257,6 +361,18 @@ watch(
{{ t('plainTable') }}
+ {{ t('sorter') }}
+
+
+ {{ t('multipleSorter') }}
+
+
{{ t('cellBordered') }}
@@ -360,4 +476,22 @@ watch(
{{ t('loading') }}
+
+ {{ t('centeredSortingAndResizing') }}
+
+
+ {{ t('rightAlignedSortingAndResizing') }}
+
diff --git a/packages/varlet-ui/src/data-table/example/locale/en-US.ts b/packages/varlet-ui/src/data-table/example/locale/en-US.ts
index af8d99bb5f1..2dddc045032 100644
--- a/packages/varlet-ui/src/data-table/example/locale/en-US.ts
+++ b/packages/varlet-ui/src/data-table/example/locale/en-US.ts
@@ -7,6 +7,8 @@ export default {
customRender: 'Custom Render',
surfaceLow: 'Subtle Background',
plainTable: 'Plain Table',
+ sorter: 'Single Column Sorting',
+ multipleSorter: 'Multiple Column Sorting',
spans: 'Cell Spans',
selection: 'Selection',
singleSelection: 'Single Selection',
@@ -23,4 +25,6 @@ export default {
emptyText: 'Empty Text',
emptyTip: 'No Data',
loading: 'Loading',
+ centeredSortingAndResizing: 'Centered Alignment',
+ rightAlignedSortingAndResizing: 'Right Alignment',
}
diff --git a/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts b/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
index 2e1b6801bea..fd21d33262c 100644
--- a/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
+++ b/packages/varlet-ui/src/data-table/example/locale/zh-CN.ts
@@ -7,6 +7,8 @@ export default {
customRender: '自定义渲染',
surfaceLow: '弱背景色',
plainTable: '纯表格',
+ sorter: '单列排序',
+ multipleSorter: '多列排序',
spans: '单元格合并',
selection: '选择列',
singleSelection: '单选',
@@ -23,4 +25,6 @@ export default {
emptyText: '空态文案',
emptyTip: '暂无数据',
loading: '加载状态',
+ centeredSortingAndResizing: '居中对齐',
+ rightAlignedSortingAndResizing: '右对齐',
}
diff --git a/packages/varlet-ui/src/data-table/props.ts b/packages/varlet-ui/src/data-table/props.ts
index 45646789ef6..fff712fd30e 100644
--- a/packages/varlet-ui/src/data-table/props.ts
+++ b/packages/varlet-ui/src/data-table/props.ts
@@ -4,6 +4,8 @@ import { defineListenerProp } from '../utils/components'
export type DataTableSurface = 'low'
export type DataTableTableLayout = 'auto' | 'fixed'
+export type DataTableSortMode = 'single' | 'multiple'
+export type DataTableSorterOrder = 'asc' | 'desc'
export type DataTableColumnAlign = 'left' | 'center' | 'right'
@@ -57,10 +59,16 @@ export interface DataTableBaseColumn {
cellProps?: DataTableCellProps
}
+export interface DataTableSorter {
+ key: string
+ order: DataTableSorterOrder
+}
+
export interface DataTableFieldColumn extends DataTableBaseColumn {
type?: undefined
key: string
title: string
+ sorter?: boolean
render?: (context: DataTableColumnRenderContext) => VNodeChild
}
@@ -130,6 +138,14 @@ export const props = {
total: Number,
maxHeight: [Number, String],
scrollX: [Number, String],
+ sorters: {
+ type: Array as PropType,
+ default: () => [],
+ },
+ sortMode: {
+ type: String as PropType,
+ default: 'single',
+ },
tree: Boolean,
surface: String as PropType,
cascade: {
@@ -161,4 +177,5 @@ export const props = {
'onUpdate:checkedRowKeys': defineListenerProp<(checkedRowKeys: Array) => void>(),
'onUpdate:page': defineListenerProp<(page: number) => void>(),
'onUpdate:pageSize': defineListenerProp<(pageSize: number) => void>(),
+ 'onUpdate:sorters': defineListenerProp<(sorters: DataTableSorter[]) => void>(),
}
diff --git a/packages/varlet-ui/src/data-table/usePagination.ts b/packages/varlet-ui/src/data-table/usePagination.ts
index 35800395db3..87428c27be2 100644
--- a/packages/varlet-ui/src/data-table/usePagination.ts
+++ b/packages/varlet-ui/src/data-table/usePagination.ts
@@ -22,6 +22,7 @@ const defaultPaginationOptions = {
interface UsePaginationOptions> {
pagination: () => boolean | DataTablePagination
remote: () => boolean
+ loading: () => boolean
page: () => number
pageSize: () => number
total: () => number | undefined
@@ -32,6 +33,7 @@ interface UsePaginationOptions> {
export function usePagination>({
pagination,
remote,
+ loading,
page,
pageSize,
total,
@@ -42,12 +44,16 @@ export function usePagination>({
const resolvedPagination = pagination()
if (isBoolean(resolvedPagination)) {
- return defaultPaginationOptions
+ return {
+ ...defaultPaginationOptions,
+ disabled: loading(),
+ }
}
return {
...defaultPaginationOptions,
...resolvedPagination,
+ disabled: loading() || resolvedPagination.disabled === true,
}
})
diff --git a/packages/varlet-ui/src/data-table/useSorter.ts b/packages/varlet-ui/src/data-table/useSorter.ts
new file mode 100644
index 00000000000..aade6d447c6
--- /dev/null
+++ b/packages/varlet-ui/src/data-table/useSorter.ts
@@ -0,0 +1,75 @@
+import { call } from '@varlet/shared'
+import { computed } from 'vue'
+import type {
+ DataTableColumn,
+ DataTableFieldColumn,
+ DataTableSortMode,
+ DataTableSorter,
+ DataTableSorterOrder,
+} from './props'
+
+interface UseSorterOptions {
+ sorters: () => DataTableSorter[]
+ sortMode: () => DataTableSortMode
+ onUpdateSorters: () => ((sorters: DataTableSorter[]) => void) | ((sorters: DataTableSorter[]) => void)[] | undefined
+}
+
+export function useSorter({ sorters, sortMode, onUpdateSorters }: UseSorterOptions) {
+ const activeSorters = computed(() => sorters())
+
+ function isColumnSortable(column: DataTableColumn): column is DataTableFieldColumn {
+ return column.type == null && column.sorter === true
+ }
+
+ function getColumnSorterOrder(columnKey: string): DataTableSorterOrder | undefined {
+ return activeSorters.value.find((sorter) => sorter.key === columnKey)?.order
+ }
+
+ function toggleColumnSorter(columnKey: string) {
+ const currentOrder = getColumnSorterOrder(columnKey)
+
+ if (sortMode() === 'single') {
+ call(
+ onUpdateSorters(),
+ currentOrder == null
+ ? [{ key: columnKey, order: 'asc' as const }]
+ : currentOrder === 'asc'
+ ? [{ key: columnKey, order: 'desc' as const }]
+ : [],
+ )
+ return
+ }
+
+ if (currentOrder == null) {
+ call(onUpdateSorters(), [...activeSorters.value, { key: columnKey, order: 'asc' as const }])
+ return
+ }
+
+ if (currentOrder === 'asc') {
+ call(
+ onUpdateSorters(),
+ activeSorters.value.map((sorter) =>
+ sorter.key !== columnKey
+ ? sorter
+ : {
+ ...sorter,
+ order: 'desc' as const,
+ },
+ ),
+ )
+ return
+ }
+
+ call(
+ onUpdateSorters(),
+ activeSorters.value.filter((sorter) => sorter.key !== columnKey),
+ )
+ }
+
+ return {
+ activeSorters,
+ isColumnSortable,
+ getColumnSorterOrder,
+ toggleColumnSorter,
+ }
+}
diff --git a/packages/varlet-ui/src/table/docs/en-US.md b/packages/varlet-ui/src/table/docs/en-US.md
index c526f6f879e..4cd910fb21b 100644
--- a/packages/varlet-ui/src/table/docs/en-US.md
+++ b/packages/varlet-ui/src/table/docs/en-US.md
@@ -62,6 +62,36 @@ Set `plain` to remove the card-like shadow, background, and radius, and present
```
+### Subtle Background
+
+Use `surface="low"` for a subtler MD3-style surface layer.
+
+```html
+
+
+
+
+ Name
+ Math
+ English
+
+
+
+
+ Jerry
+ 124
+ 38
+
+
+ Tom
+ 100
+ 135
+
+
+
+
+```
+
### Footer Slots
You can insert something in the tail slot, the most common is to insert a `Pagination`.
@@ -207,6 +237,7 @@ function get(current, size) {
| `full-width` | The width of the `table` (including the scrollable part) | _string \| number_ | `100%` |
| `elevation` | Elevation level, options `true` `false` and level of `0-24` | _string \| number \| boolean_| `true` |
| `plain` | Whether to render as a plain table without card shadow, background, or radius | _boolean_ | `false` |
+| `surface` | Subtle background style | _'low'_ | `-` |
| `scroller-height` ***3.2.0*** | The height of the scroll container, which can be used to implement functions such as longitudinal partial scrolling and fixed table headers. | _string \| number_ | `-` |
### Slots
diff --git a/packages/varlet-ui/src/table/docs/zh-CN.md b/packages/varlet-ui/src/table/docs/zh-CN.md
index 6b7a0238cf7..ac9a77507ad 100644
--- a/packages/varlet-ui/src/table/docs/zh-CN.md
+++ b/packages/varlet-ui/src/table/docs/zh-CN.md
@@ -62,6 +62,36 @@
```
+### 弱背景色
+
+通过 `surface="low"` 使用更接近 MD3 的弱背景层级。
+
+```html
+
+
+
+
+ 姓名
+ 数学
+ 英语
+
+
+
+
+ 耗子君
+ 124
+ 38
+
+
+ 火猫桑
+ 100
+ 135
+
+
+
+
+```
+
### 尾部插槽
可以在尾部插槽中插入一些东西,最常见的是插入分页组件。
@@ -207,6 +237,7 @@ function get(current, size) {
| `full-width` | `table` 的宽度(包含可滚动部分) | _string \| number_ | `100%` |
| `elevation` | 海拔高度,可选值为 `true` `false` 和 `0-24` 的等级 | _string \| number \| boolean_| `true` |
| `plain` | 是否以纯表格形态渲染,不带卡片阴影、背景色和圆角 | _boolean_ | `false` |
+| `surface` | 弱背景色风格 | _'low'_ | `-` |
| `scroller-height` ***3.2.0*** | 滚动容器高度,可用于实现纵向局部滚动,固定表头等功能 | _string \| number_ | `-` |
### 插槽
diff --git a/packages/varlet-ui/src/table/example/index.vue b/packages/varlet-ui/src/table/example/index.vue
index d9c43f378a7..4397b8fd99e 100644
--- a/packages/varlet-ui/src/table/example/index.vue
+++ b/packages/varlet-ui/src/table/example/index.vue
@@ -79,6 +79,30 @@ onThemeChange()
+ {{ t('surfaceLow') }}
+
+
+
+ {{ t('name') }}
+ {{ t('math') }}
+ {{ t('english') }}
+
+
+
+
+
+ {{ t('jerry') }}
+ 124
+ 38
+
+
+ {{ t('tom') }}
+ 100
+ 135
+
+
+
+
{{ t('slot') }}
diff --git a/packages/varlet-ui/src/table/example/locale/en-US.ts b/packages/varlet-ui/src/table/example/locale/en-US.ts
index 553f51616a1..418e949f06d 100644
--- a/packages/varlet-ui/src/table/example/locale/en-US.ts
+++ b/packages/varlet-ui/src/table/example/locale/en-US.ts
@@ -1,6 +1,7 @@
export default {
basicUsage: 'Basic Usage',
plainTable: 'Plain Table',
+ surfaceLow: 'Subtle Background',
slot: 'Footer Slots',
fixedHeader: 'Fixed Table Header',
math: 'Math',
diff --git a/packages/varlet-ui/src/table/example/locale/zh-CN.ts b/packages/varlet-ui/src/table/example/locale/zh-CN.ts
index 7e81c24cacd..7e5879b96f9 100644
--- a/packages/varlet-ui/src/table/example/locale/zh-CN.ts
+++ b/packages/varlet-ui/src/table/example/locale/zh-CN.ts
@@ -1,6 +1,7 @@
export default {
basicUsage: '基本使用',
plainTable: '纯表格',
+ surfaceLow: '弱背景色',
slot: '尾部插槽',
fixedHeader: '固定表头',
math: '数学',
diff --git a/packages/varlet-ui/src/themes/__tests__/__snapshots__/index.spec.js.snap b/packages/varlet-ui/src/themes/__tests__/__snapshots__/index.spec.js.snap
index 232905ac2a3..1fc238c27dc 100644
--- a/packages/varlet-ui/src/themes/__tests__/__snapshots__/index.spec.js.snap
+++ b/packages/varlet-ui/src/themes/__tests__/__snapshots__/index.spec.js.snap
@@ -314,10 +314,16 @@ exports[`dark theme 1`] = `
"--data-table-header-cell-background": "#303030",
"--data-table-header-cell-text-color": "rgba(255, 255, 255, 0.6)",
"--data-table-header-font-size": "14px",
+ "--data-table-plain-row-hover-background": "hsla(var(--hsl-on-surface), 0.08)",
"--data-table-row-height": "46px",
- "--data-table-row-hover-background": "#4c4b4b",
+ "--data-table-row-hover-background": "#3a3a3a",
"--data-table-row-large-height": "52px",
"--data-table-row-small-height": "40px",
+ "--data-table-sort-trigger-active-color": "var(--color-primary)",
+ "--data-table-sort-trigger-color": "hsla(var(--hsl-on-surface), 0.5)",
+ "--data-table-sort-trigger-hover-background": "hsla(var(--hsl-primary), 0.12)",
+ "--data-table-surface-low-background": "#2a2a2a",
+ "--data-table-surface-low-row-hover-background": "#2a2a2a",
"--date-picker-actions-padding": "0 8px 12px 8px",
"--date-picker-body-background-color": "#303030",
"--date-picker-body-height": "280px",
@@ -841,8 +847,10 @@ exports[`dark theme 1`] = `
"--table-background": "#303030",
"--table-border-radius": "2px",
"--table-footer-border-top": "thin solid var(--color-outline)",
+ "--table-plain-row-hover-background": "hsla(var(--hsl-on-surface), 0.08)",
"--table-row-height": "46px",
"--table-row-padding": "0 16px",
+ "--table-surface-low-row-hover-background": "#2a2a2a",
"--table-tbody-td-font-size": "16px",
"--table-tbody-td-text-align": "left",
"--table-tbody-td-text-color": "#fff",
@@ -1286,11 +1294,16 @@ exports[`md3Dark theme 1`] = `
"--data-table-header-cell-background": "var(--color-surface-container-highest)",
"--data-table-header-cell-text-color": "rgba(255, 255, 255, 0.6)",
"--data-table-header-font-size": "14px",
+ "--data-table-plain-row-hover-background": "hsla(var(--hsl-on-surface), 0.08)",
"--data-table-row-height": "46px",
- "--data-table-row-hover-background": "var(--color-surface-container-highest)",
+ "--data-table-row-hover-background": "var(--color-surface-container-high)",
"--data-table-row-large-height": "52px",
"--data-table-row-small-height": "40px",
+ "--data-table-sort-trigger-active-color": "var(--color-primary)",
+ "--data-table-sort-trigger-color": "hsla(var(--hsl-on-surface), 0.5)",
+ "--data-table-sort-trigger-hover-background": "hsla(var(--hsl-primary), 0.12)",
"--data-table-surface-low-background": "#1c1b1d",
+ "--data-table-surface-low-row-hover-background": "var(--color-surface-container-highest)",
"--date-picker-actions-padding": "20px",
"--date-picker-body-background-color": "var(--color-surface-container-high)",
"--date-picker-body-height": "300px",
@@ -1815,8 +1828,10 @@ exports[`md3Dark theme 1`] = `
"--table-background": "var(--color-surface-container-highest)",
"--table-border-radius": "2px",
"--table-footer-border-top": "thin solid var(--color-outline)",
+ "--table-plain-row-hover-background": "hsla(var(--hsl-on-surface), 0.08)",
"--table-row-height": "46px",
"--table-row-padding": "0 16px",
+ "--table-surface-low-row-hover-background": "#1c1b1d",
"--table-tbody-td-font-size": "16px",
"--table-tbody-td-text-align": "left",
"--table-tbody-td-text-color": "#fff",
@@ -2244,11 +2259,16 @@ exports[`md3Light theme 1`] = `
"--data-table-header-cell-background": "var(--color-surface-container-low)",
"--data-table-header-cell-text-color": "rgba(0, 0, 0, 0.6)",
"--data-table-header-font-size": "14px",
+ "--data-table-plain-row-hover-background": "hsla(var(--hsl-on-surface), 0.04)",
"--data-table-row-height": "46px",
- "--data-table-row-hover-background": "var(--color-surface-container-low)",
+ "--data-table-row-hover-background": "var(--color-surface-container-high)",
"--data-table-row-large-height": "52px",
"--data-table-row-small-height": "40px",
+ "--data-table-sort-trigger-active-color": "var(--color-primary)",
+ "--data-table-sort-trigger-color": "hsla(var(--hsl-on-surface), 0.42)",
+ "--data-table-sort-trigger-hover-background": "hsla(var(--hsl-primary), 0.08)",
"--data-table-surface-low-background": "var(--color-surface-container-low)",
+ "--data-table-surface-low-row-hover-background": "var(--color-surface-container-highest)",
"--date-picker-actions-padding": "20px",
"--date-picker-body-background-color": "var(--color-surface-container-high)",
"--date-picker-body-height": "300px",
@@ -2771,8 +2791,10 @@ exports[`md3Light theme 1`] = `
"--table-background": "var(--color-surface-container-low)",
"--table-border-radius": "2px",
"--table-footer-border-top": "thin solid var(--color-outline)",
+ "--table-plain-row-hover-background": "hsla(var(--hsl-on-surface), 0.04)",
"--table-row-height": "46px",
"--table-row-padding": "0 16px",
+ "--table-surface-low-row-hover-background": "var(--color-surface-container-highest)",
"--table-tbody-td-font-size": "16px",
"--table-tbody-td-text-align": "left",
"--table-tbody-td-text-color": "#555",
diff --git a/packages/varlet-ui/src/themes/dark/dataTable.ts b/packages/varlet-ui/src/themes/dark/dataTable.ts
index 6b448d3763b..4aa3a7b3c35 100644
--- a/packages/varlet-ui/src/themes/dark/dataTable.ts
+++ b/packages/varlet-ui/src/themes/dark/dataTable.ts
@@ -1,5 +1,6 @@
export default {
'--data-table-background': '#303030',
+ '--data-table-surface-low-background': '#2a2a2a',
'--data-table-header-cell-background': '#303030',
'--data-table-header-cell-text-color': 'rgba(255, 255, 255, 0.6)',
'--data-table-body-cell-text-color': '#fff',
@@ -7,6 +8,9 @@ export default {
'--data-table-row-hover-background': '#3a3a3a',
'--data-table-surface-low-row-hover-background': '#2a2a2a',
'--data-table-plain-row-hover-background': 'hsla(var(--hsl-on-surface), 0.08)',
+ '--data-table-sort-trigger-color': 'hsla(var(--hsl-on-surface), 0.5)',
+ '--data-table-sort-trigger-active-color': 'var(--color-primary)',
+ '--data-table-sort-trigger-hover-background': 'hsla(var(--hsl-primary), 0.12)',
'--data-table-empty-text-color': 'var(--color-text-disabled)',
'--data-table-border-radius': '2px',
'--data-table-cell-padding': '0 16px',
diff --git a/packages/varlet-ui/src/themes/md3-dark/dataTable.ts b/packages/varlet-ui/src/themes/md3-dark/dataTable.ts
index 5bb8b9d7e9d..fbd30d2a0d3 100644
--- a/packages/varlet-ui/src/themes/md3-dark/dataTable.ts
+++ b/packages/varlet-ui/src/themes/md3-dark/dataTable.ts
@@ -8,6 +8,9 @@ export default {
'--data-table-row-hover-background': 'var(--color-surface-container-high)',
'--data-table-surface-low-row-hover-background': 'var(--color-surface-container-highest)',
'--data-table-plain-row-hover-background': 'hsla(var(--hsl-on-surface), 0.08)',
+ '--data-table-sort-trigger-color': 'hsla(var(--hsl-on-surface), 0.5)',
+ '--data-table-sort-trigger-active-color': 'var(--color-primary)',
+ '--data-table-sort-trigger-hover-background': 'hsla(var(--hsl-primary), 0.12)',
'--data-table-empty-text-color': 'var(--color-text-disabled)',
'--data-table-border-radius': '2px',
'--data-table-cell-padding': '0 16px',
diff --git a/packages/varlet-ui/src/themes/md3-light/dataTable.ts b/packages/varlet-ui/src/themes/md3-light/dataTable.ts
index 7fa59bc1013..95c9c6f4124 100644
--- a/packages/varlet-ui/src/themes/md3-light/dataTable.ts
+++ b/packages/varlet-ui/src/themes/md3-light/dataTable.ts
@@ -8,6 +8,9 @@ export default {
'--data-table-row-hover-background': 'var(--color-surface-container-high)',
'--data-table-surface-low-row-hover-background': 'var(--color-surface-container-highest)',
'--data-table-plain-row-hover-background': 'hsla(var(--hsl-on-surface), 0.04)',
+ '--data-table-sort-trigger-color': 'hsla(var(--hsl-on-surface), 0.42)',
+ '--data-table-sort-trigger-active-color': 'var(--color-primary)',
+ '--data-table-sort-trigger-hover-background': 'hsla(var(--hsl-primary), 0.08)',
'--data-table-empty-text-color': 'var(--color-text-disabled)',
'--data-table-border-radius': '2px',
'--data-table-cell-padding': '0 16px',
diff --git a/packages/varlet-ui/types/dataTable.d.ts b/packages/varlet-ui/types/dataTable.d.ts
index 7f50b90713d..c3c62af4b63 100644
--- a/packages/varlet-ui/types/dataTable.d.ts
+++ b/packages/varlet-ui/types/dataTable.d.ts
@@ -7,6 +7,8 @@ export type DataTableColumnAlign = 'left' | 'center' | 'right'
export type DataTableColumnFixed = 'left' | 'right'
export type DataTableSurface = 'low'
export type DataTableTableLayout = 'auto' | 'fixed'
+export type DataTableSortMode = 'single' | 'multiple'
+export type DataTableSorterOrder = 'asc' | 'desc'
export type DataTableRowKey = keyof Row | string | ((row: Row, rowIndex: number) => string | number)
@@ -50,10 +52,16 @@ export interface DataTableBaseColumn {
cellProps?: DataTableCellProps
}
+export interface DataTableSorter {
+ key: string
+ order: DataTableSorterOrder
+}
+
export interface DataTableFieldColumn extends DataTableBaseColumn {
type?: undefined
key: string
title: string
+ sorter?: boolean
render?: (context: DataTableColumnRenderContext) => VNodeChild
}
@@ -103,6 +111,8 @@ export interface DataTableProps extends BasicAttributes {
total?: number
maxHeight?: number | string
scrollX?: number | string
+ sorters?: DataTableSorter[]
+ sortMode?: DataTableSortMode
tree?: boolean
surface?: DataTableSurface
cascade?: boolean
@@ -116,6 +126,7 @@ export interface DataTableProps extends BasicAttributes {
'onUpdate:checkedRowKeys'?: ListenerProp<(checkedRowKeys: Array) => void>
'onUpdate:page'?: ListenerProp<(page: number) => void>
'onUpdate:pageSize'?: ListenerProp<(pageSize: number) => void>
+ 'onUpdate:sorters'?: ListenerProp<(sorters: DataTableSorter[]) => void>
}
export class DataTable extends VarComponent {
@@ -124,7 +135,7 @@ export class DataTable extends VarComponent {
$props: DataTableProps
$slots: {
- loading(): VNode[]
+ loadingDescription(): VNode[]
footerPrefix(): VNode[]
}
}
diff --git a/packages/varlet-ui/types/styleVars.d.ts b/packages/varlet-ui/types/styleVars.d.ts
index c591785dbf2..8cb135ed53f 100644
--- a/packages/varlet-ui/types/styleVars.d.ts
+++ b/packages/varlet-ui/types/styleVars.d.ts
@@ -269,6 +269,7 @@ interface BaseStyleVars {
'--countdown-text-color'?: string
'--countdown-text-font-size'?: string
'--data-table-background'?: string
+ '--data-table-surface-low-background'?: string
'--data-table-header-cell-background'?: string
'--data-table-header-cell-text-color'?: string
'--data-table-body-cell-text-color'?: string
@@ -276,6 +277,9 @@ interface BaseStyleVars {
'--data-table-row-hover-background'?: string
'--data-table-surface-low-row-hover-background'?: string
'--data-table-plain-row-hover-background'?: string
+ '--data-table-sort-trigger-color'?: string
+ '--data-table-sort-trigger-active-color'?: string
+ '--data-table-sort-trigger-hover-background'?: string
'--data-table-empty-text-color'?: string
'--data-table-border-radius'?: string
'--data-table-cell-padding'?: string
From f7d7ff7bda9d91c702b987bbe552c251004c806c Mon Sep 17 00:00:00 2001
From: haoziqaq <357229046@qq.com>
Date: Sat, 23 May 2026 23:36:14 +0800
Subject: [PATCH 19/37] feat(data-table): add support for grouped header
columns with updated documentation and examples
---
.../varlet-ui/src/data-table/DataTable.vue | 172 ++++++++++++++----
.../src/data-table/DataTableHeaderCell.vue | 20 +-
.../src/data-table/__tests__/index.spec.js | 28 +++
.../varlet-ui/src/data-table/docs/en-US.md | 27 +++
.../varlet-ui/src/data-table/docs/zh-CN.md | 27 +++
.../src/data-table/example/index.vue | 16 ++
.../src/data-table/example/locale/en-US.ts | 1 +
.../src/data-table/example/locale/zh-CN.ts | 1 +
packages/varlet-ui/src/data-table/props.ts | 1 +
.../varlet-ui/src/data-table/useSorter.ts | 2 +-
packages/varlet-ui/types/dataTable.d.ts | 1 +
11 files changed, 252 insertions(+), 44 deletions(-)
diff --git a/packages/varlet-ui/src/data-table/DataTable.vue b/packages/varlet-ui/src/data-table/DataTable.vue
index 8860b659c04..da6eeb5ff5b 100644
--- a/packages/varlet-ui/src/data-table/DataTable.vue
+++ b/packages/varlet-ui/src/data-table/DataTable.vue
@@ -15,19 +15,19 @@
>