diff --git a/packages/devui-vue/devui/table/src/components/body-td/body-td.tsx b/packages/devui-vue/devui/table/src/components/body-td/body-td.tsx index 9b76b8c8bf..63afeb21c5 100644 --- a/packages/devui-vue/devui/table/src/components/body-td/body-td.tsx +++ b/packages/devui-vue/devui/table/src/components/body-td/body-td.tsx @@ -1,6 +1,7 @@ -import { defineComponent, toRef } from 'vue'; +import { defineComponent, toRef, inject } from 'vue'; import type { PropType } from 'vue'; import { Column } from '../column/column-types'; +import { TABLE_TOKEN } from '../../table-types'; import { useFixedColumn } from '../../composable/use-table'; export default defineComponent({ @@ -20,14 +21,13 @@ export default defineComponent({ }, }, setup(props: { column: Column; row: any; index: number }) { + const table = inject(TABLE_TOKEN); const column = toRef(props, 'column'); - - // 固定列 - const { stickyCell, offsetStyle } = useFixedColumn(column); + const { stickyClass, stickyStyle } = useFixedColumn(column); return () => ( - - {column.value.renderCell?.(props.row, props.index)} + + {column.value.renderCell?.(props.row, column.value, table.store, props.index)} ); }, diff --git a/packages/devui-vue/devui/table/src/components/body/body.tsx b/packages/devui-vue/devui/table/src/components/body/body.tsx index b7a4c50edd..c748efad21 100644 --- a/packages/devui-vue/devui/table/src/components/body/body.tsx +++ b/packages/devui-vue/devui/table/src/components/body/body.tsx @@ -1,7 +1,7 @@ import { defineComponent, inject, computed } from 'vue'; -import { TABLE_TOKEN } from '../../table-types'; +import { TABLE_TOKEN, DefaultRow } from '../../table-types'; +import { Column } from '../column/column-types'; import TD from '../body-td/body-td'; -import { Checkbox } from '../../../../checkbox'; import { useNamespace } from '../../../../shared/hooks/use-namespace'; import { useMergeCell } from './use-body'; import './body.scss'; @@ -10,33 +10,24 @@ export default defineComponent({ name: 'DTableBody', setup() { const table = inject(TABLE_TOKEN); - const { _data: data, flatColumns, _checkList: checkList, isFixedLeft } = table.store.states; + const { _data: data, flatColumns } = table.store.states; const ns = useNamespace('table'); const hoverEnabled = computed(() => table.props.rowHoveredHighlight); const { tableSpans, removeCells } = useMergeCell(); - const tdAttrs = computed(() => (isFixedLeft.value ? { class: `${ns.m('sticky-cell')} left`, style: 'left:0;' } : null)); - - const renderCheckbox = (index: number) => - table.props.checkable ? ( - - - - ) : null; return () => ( - {data.value.map((row, rowIndex) => { + {data.value.map((row: DefaultRow, rowIndex: number) => { return ( - {renderCheckbox(rowIndex)} - {flatColumns.value.map((column, columnIndex) => { + {flatColumns.value.map((column: Column, columnIndex: number) => { const cellId = `${rowIndex}-${columnIndex}`; const [rowspan, colspan] = tableSpans.value[cellId] ?? [1, 1]; if (removeCells.value.includes(cellId)) { return null; } - return ; + return ; })} ); diff --git a/packages/devui-vue/devui/table/src/components/column/column-types.ts b/packages/devui-vue/devui/table/src/components/column/column-types.ts index 8d24a5c949..e44960c0cf 100644 --- a/packages/devui-vue/devui/table/src/components/column/column-types.ts +++ b/packages/devui-vue/devui/table/src/components/column/column-types.ts @@ -1,9 +1,14 @@ import type { PropType, ExtractPropTypes, VNode, Slot, ComponentInternalInstance } from 'vue'; +import { DefaultRow } from '../../table-types'; +import { TableStore } from '../../store/store-types'; -export type Formatter = (row: T, cellValue: R, index: number) => VNode[]; +// eslint-disable-next-line no-use-before-define +export type Formatter = (row: DefaultRow, column: Column, cellValue: any, rowIndex: number) => VNode[]; export type CompareFn = (field: string, a: T, b: T) => boolean; +export type ColumnType = 'checkable' | ''; + export interface FilterConfig { id: number | string; name: string; @@ -11,7 +16,11 @@ export interface FilterConfig { checked?: boolean; } -export const TableColumnProps = { +export const tableColumnProps = { + type: { + type: String as PropType, + default: '', + }, header: { type: String, default: '', @@ -63,7 +72,7 @@ export const TableColumnProps = { }, }; -export type TableColumnPropsTypes = ExtractPropTypes; +export type TableColumnProps = ExtractPropTypes; export type FilterResults = (string | number)[]; @@ -74,8 +83,9 @@ export interface CustomFilterProps { export type CustomFilterSlot = (props: CustomFilterProps) => VNode[]; -export interface Column { +export interface Column { id?: string; + type?: ColumnType; field?: string; width?: number; minWidth?: number; @@ -88,15 +98,15 @@ export interface Column { filterList?: FilterConfig[]; fixedLeft?: string; fixedRight?: string; - renderHeader?: () => void; - renderCell?: (row: T, index: number) => void; - formatter?: Formatter; - compareFn?: CompareFn; + renderHeader?: (column: Column, store: TableStore) => VNode; + renderCell?: (rowData: DefaultRow, columnItem: Column, store: TableStore, rowIndex: number) => VNode; + formatter?: Formatter; + compareFn?: CompareFn; customFilterTemplate?: CustomFilterSlot; subColumns?: Slot; } -export interface TableColumn extends ComponentInternalInstance { +export interface TableColumn extends ComponentInternalInstance { columnId: string; - columnConfig: Partial>; + columnConfig: Partial; } diff --git a/packages/devui-vue/devui/table/src/components/column/column.tsx b/packages/devui-vue/devui/table/src/components/column/column.tsx index 66f3685541..8bf90aa791 100644 --- a/packages/devui-vue/devui/table/src/components/column/column.tsx +++ b/packages/devui-vue/devui/table/src/components/column/column.tsx @@ -1,5 +1,5 @@ import { inject, defineComponent, onBeforeUnmount, onMounted, toRefs, watch, ref, getCurrentInstance, onBeforeMount, h } from 'vue'; -import { TableColumnProps, TableColumnPropsTypes, TableColumn } from './column-types'; +import { tableColumnProps, TableColumnProps, TableColumn } from './column-types'; import { TABLE_TOKEN, Table, DefaultRow } from '../../table-types'; import { createColumn, useRender } from './use-column'; @@ -7,9 +7,9 @@ let columnIdInit = 1; export default defineComponent({ name: 'DColumn', - props: TableColumnProps, - setup(props: TableColumnPropsTypes, ctx) { - const instance = getCurrentInstance() as TableColumn; + props: tableColumnProps, + setup(props: TableColumnProps, ctx) { + const instance = getCurrentInstance() as TableColumn; const column = createColumn(toRefs(props), ctx.slots); const owner = inject(TABLE_TOKEN) as Table; const isSubColumn = ref(false); diff --git a/packages/devui-vue/devui/table/src/components/column/config.tsx b/packages/devui-vue/devui/table/src/components/column/config.tsx new file mode 100644 index 0000000000..18cfa59c7a --- /dev/null +++ b/packages/devui-vue/devui/table/src/components/column/config.tsx @@ -0,0 +1,28 @@ +import { h } from 'vue'; +import type { VNode } from 'vue'; +import { DefaultRow } from '../../table-types'; +import { Column } from './column-types'; +import { TableStore } from '../../store/store-types'; +import { Checkbox } from '../../../../checkbox'; + +export const cellMap = { + checkable: { + renderHeader(column: Column, store: TableStore): VNode { + return h(Checkbox, { + modelValue: store.states._checkAll.value, + halfchecked: store.states._halfChecked.value, + onChange: (val: boolean) => { + store.states._checkAll.value = val; + }, + }); + }, + renderCell(rowData: DefaultRow, column: Column, store: TableStore, rowIndex: number): VNode { + return h(Checkbox, { + modelValue: store.states._checkList.value[rowIndex], + onChange: (val: boolean) => { + store.states._checkList.value[rowIndex] = val; + }, + }); + }, + }, +}; diff --git a/packages/devui-vue/devui/table/src/components/column/use-column.ts b/packages/devui-vue/devui/table/src/components/column/use-column.ts index 38b93dd174..2d1dc9bf12 100644 --- a/packages/devui-vue/devui/table/src/components/column/use-column.ts +++ b/packages/devui-vue/devui/table/src/components/column/use-column.ts @@ -1,15 +1,14 @@ import { watch, reactive, onBeforeMount, computed, h, getCurrentInstance } from 'vue'; import type { ToRefs, Slots, ComputedRef } from 'vue'; -import { Table } from '../../table-types'; -import { Column, TableColumnPropsTypes, TableColumn } from './column-types'; +import { Table, DefaultRow } from '../../table-types'; +import { Column, TableColumnProps, TableColumn } from './column-types'; +import { TableStore } from '../../store/store-types'; import { formatWidth, formatMinWidth } from '../../utils'; +import { cellMap } from './config'; -function defaultRenderHeader(this: Column) { - return h('span', { class: 'title' }, this.header); -} - -export function createColumn = any>(props: ToRefs, templates: Slots): Column { +export function createColumn(props: ToRefs, templates: Slots): Column { const { + type, field, header, sortable, @@ -24,15 +23,20 @@ export function createColumn = any>(props: ToR fixedLeft, fixedRight, } = props; - const column: Column = reactive({}); + const column: Column = reactive({}); + column.type = type.value; + + function defaultRenderHeader(columnItem: Column) { + return h('span', { class: 'title' }, columnItem.header); + } - function defaultRenderCell>(rowData: K, index: number) { - const value = rowData[this.field]; + function defaultRenderCell(rowData: DefaultRow, columnItem: Column, store: TableStore, rowIndex: number) { + const value = columnItem.field ? rowData[columnItem.field] : ''; if (templates.default) { return templates.default(rowData); } - if (this.formatter) { - return this.formatter(rowData, value, index); + if (columnItem.formatter) { + return columnItem.formatter(rowData, columnItem, value, rowIndex); } return value?.toString?.() ?? ''; @@ -69,8 +73,8 @@ export function createColumn = any>(props: ToR watch( [fixedLeft, fixedRight], ([left, right]) => { - column.fixedLeft = left?.value; - column.fixedRight = right?.value; + column.fixedLeft = left; + column.fixedRight = right; }, { immediate: true } ); @@ -84,8 +88,8 @@ export function createColumn = any>(props: ToR // 基础渲染功能 onBeforeMount(() => { - column.renderHeader = defaultRenderHeader; - column.renderCell = defaultRenderCell; + column.renderHeader = type.value ? cellMap[type.value].renderHeader : defaultRenderHeader; + column.renderCell = type.value ? cellMap[type.value].renderCell : defaultRenderCell; column.formatter = formatter?.value; column.customFilterTemplate = templates.customFilterTemplate; column.subColumns = templates.subColumns; @@ -95,10 +99,10 @@ export function createColumn = any>(props: ToR } export function useRender(): { - columnOrTableParent: ComputedRef | TableColumn>; + columnOrTableParent: ComputedRef | TableColumn>; getColumnIndex: (children: Array, child: unknown) => number; } { - const instance = getCurrentInstance() as TableColumn; + const instance = getCurrentInstance() as TableColumn; const columnOrTableParent = computed(() => { let parent: any = instance?.parent; while (parent && !parent.tableId && !parent.columnId) { diff --git a/packages/devui-vue/devui/table/src/components/header-th/header-th.tsx b/packages/devui-vue/devui/table/src/components/header-th/header-th.tsx index 236b686a3c..bda79e50b3 100644 --- a/packages/devui-vue/devui/table/src/components/header-th/header-th.tsx +++ b/packages/devui-vue/devui/table/src/components/header-th/header-th.tsx @@ -1,5 +1,5 @@ import { defineComponent, inject, toRefs } from 'vue'; -import { PropType } from 'vue'; +import type { PropType } from 'vue'; import { Column } from '../column/column-types'; import { TABLE_TOKEN } from '../../table-types'; import { Sort } from '../sort'; @@ -20,12 +20,12 @@ export default defineComponent({ const { column } = toRefs(props); const directionRef = useSort(table.store, column); const filteredRef = useFilter(table.store, column); - const { stickyCell, offsetStyle } = useFixedColumn(column); + const { stickyClass, stickyStyle } = useFixedColumn(column); return () => ( - +
- {props.column.renderHeader?.()} + {props.column.renderHeader?.(column.value, table.store)} {props.column.filterable && ( )} diff --git a/packages/devui-vue/devui/table/src/components/header/header.scss b/packages/devui-vue/devui/table/src/components/header/header.scss index cbd490115e..e6bae9d75a 100644 --- a/packages/devui-vue/devui/table/src/components/header/header.scss +++ b/packages/devui-vue/devui/table/src/components/header/header.scss @@ -6,6 +6,7 @@ font-size: $devui-font-size-card-title; color: $devui-text; font-weight: 700; + border: none; background-color: $devui-base-bg; th { @@ -19,7 +20,6 @@ position: relative; display: flex; align-items: center; - padding-left: 2px; padding-right: 8px; .title { diff --git a/packages/devui-vue/devui/table/src/components/header/header.tsx b/packages/devui-vue/devui/table/src/components/header/header.tsx index 7419d9beb8..32cbed2f93 100644 --- a/packages/devui-vue/devui/table/src/components/header/header.tsx +++ b/packages/devui-vue/devui/table/src/components/header/header.tsx @@ -1,6 +1,4 @@ -import { defineComponent, inject, computed } from 'vue'; -import { TABLE_TOKEN } from '../../table-types'; -import { Checkbox } from '../../../../checkbox'; +import { defineComponent } from 'vue'; import TH from '../header-th/header-th'; import { useNamespace } from '../../../../shared/hooks/use-namespace'; import { useHeader } from './use-header'; @@ -10,26 +8,14 @@ import '../body/body.scss'; export default defineComponent({ name: 'DTableHeader', setup() { - const table = inject(TABLE_TOKEN); - const { _checkAll: checkAll, _halfChecked: halfChecked, isFixedLeft } = table.store.states; const ns = useNamespace('table'); const { headerRows } = useHeader(); - const thAttrs = computed(() => (isFixedLeft.value ? { class: `${ns.m('sticky-cell')} left`, style: 'left:0;' } : null)); - const checkbox = computed(() => - table.props.checkable ? ( - - - - ) : null - ); - return () => ( - {headerRows.value.map((subColumns, rowIndex) => ( + {headerRows.value.map((subColumns) => ( - {checkbox.value} - {subColumns.map((column, columnIndex) => ( + {subColumns.map((column, columnIndex: number) => ( ))} diff --git a/packages/devui-vue/devui/table/src/composable/use-table.ts b/packages/devui-vue/devui/table/src/composable/use-table.ts index c0de2a26ef..2457b7c820 100644 --- a/packages/devui-vue/devui/table/src/composable/use-table.ts +++ b/packages/devui-vue/devui/table/src/composable/use-table.ts @@ -16,7 +16,7 @@ export function useTable(props: TablePropsTypes): TableConfig { [ns.m('header-bg')]: props.headerBg, [ns.m('layout-auto')]: props.tableLayout === 'auto', [ns.m(`${props.size}`)]: true, - [ns.m(`${props.borderType}`)]: props.borderType, + [ns.m(`${props.borderType}`)]: Boolean(props.borderType), })); const style: ComputedRef = computed(() => ({ maxHeight: props.maxHeight, @@ -27,35 +27,19 @@ export function useTable(props: TablePropsTypes): TableConfig { return { classes, style }; } -export const useFixedColumn = (column: Ref): ToRefs<{ stickyCell: string; offsetStyle: string }> => { +export const useFixedColumn = ( + column: Ref +): ToRefs<{ stickyClass: ComputedRef>; stickyStyle: ComputedRef }> => { const ns = useNamespace('table'); - const stickyCell = computed(() => { - const col = column.value; - if (col.fixedLeft) { - return `${ns.m('sticky-cell')} left`; - } - - if (col.fixedRight) { - return `${ns.m('sticky-cell')} right`; - } - return undefined; - }); - - const offsetStyle = computed(() => { - const col = column.value; - if (col.fixedLeft) { - return `left:${col.fixedLeft}`; - } - - if (col.fixedRight) { - return `right:${col.fixedRight}`; - } + const stickyClass = computed(() => ({ + [ns.e('checkable-cell')]: column.value.type === 'checkable', + [ns.m('sticky-cell')]: Boolean(column.value.fixedLeft) || Boolean(column.value.fixedRight), + })); - return undefined; - }); + const stickyStyle = computed(() => ({ + left: column.value.fixedLeft, + right: column.value.fixedRight, + })); - return { - stickyCell, - offsetStyle, - }; + return { stickyClass, stickyStyle }; }; diff --git a/packages/devui-vue/devui/table/src/store/index.ts b/packages/devui-vue/devui/table/src/store/index.ts index 0afda733ea..94e5dd5c50 100644 --- a/packages/devui-vue/devui/table/src/store/index.ts +++ b/packages/devui-vue/devui/table/src/store/index.ts @@ -27,47 +27,9 @@ function doFlattenColumns(columns: any) { return result; } -export function createStore(dataSource: Ref): TableStore { - const _data: Ref = ref([]); - watch( - dataSource, - (value: T[]) => { - _data.value = [...value]; - }, - { deep: true, immediate: true } - ); - - const { _columns, flatColumns, insertColumn, removeColumn, sortColumn, updateColumns } = createColumnGenerator(); - const { _checkAll, _checkList, _halfChecked, getCheckedRows } = createSelection(dataSource, _data); - const { sortData } = createSorter(dataSource, _data); - const { filterData, resetFilterData } = createFilter(dataSource, _data); - - const { isFixedLeft } = createFixedLogic(_columns); - - return { - states: { - _data, - _columns, - flatColumns, - _checkList, - _checkAll, - _halfChecked, - isFixedLeft, - }, - insertColumn, - sortColumn, - removeColumn, - updateColumns, - getCheckedRows, - sortData, - filterData, - resetFilterData, - }; -} - const createColumnGenerator = () => { - const _columns: Ref[]> = ref([]); - const flatColumns: Ref[]> = ref([]); + const _columns: Ref = ref([]); + const flatColumns: Ref = ref([]); const sortColumn = () => { _columns.value.sort((a, b) => a.order - b.order); @@ -112,7 +74,6 @@ const createSelection = (dataSource: Ref, _data: Ref) => { get: () => _checkAllRecord.value, set: (val: boolean) => { _checkAllRecord.value = val; - // 只有在 set 的时候变更 _checkList 的数据 for (let i = 0; i < _checkList.value.length; i++) { _checkList.value[i] = val; } @@ -128,7 +89,6 @@ const createSelection = (dataSource: Ref, _data: Ref) => { { deep: true, immediate: true } ); - // checkList 只有全为true的时候 watch( _checkList, (list) => { @@ -203,3 +163,41 @@ const createFixedLogic = (columns: Ref) => { return { isFixedLeft }; }; + +export function createStore(dataSource: Ref): TableStore { + const _data: Ref = ref([]); + watch( + dataSource, + (value: T[]) => { + _data.value = [...value]; + }, + { deep: true, immediate: true } + ); + + const { _columns, flatColumns, insertColumn, removeColumn, sortColumn, updateColumns } = createColumnGenerator(); + const { _checkAll, _checkList, _halfChecked, getCheckedRows } = createSelection(dataSource, _data); + const { sortData } = createSorter(dataSource, _data); + const { filterData, resetFilterData } = createFilter(dataSource, _data); + + const { isFixedLeft } = createFixedLogic(_columns); + + return { + states: { + _data, + _columns, + flatColumns, + _checkList, + _checkAll, + _halfChecked, + isFixedLeft, + }, + insertColumn, + sortColumn, + removeColumn, + updateColumns, + getCheckedRows, + sortData, + filterData, + resetFilterData, + }; +} diff --git a/packages/devui-vue/devui/table/src/store/store-types.ts b/packages/devui-vue/devui/table/src/store/store-types.ts index f7507b0723..f8b426849e 100644 --- a/packages/devui-vue/devui/table/src/store/store-types.ts +++ b/packages/devui-vue/devui/table/src/store/store-types.ts @@ -5,16 +5,16 @@ import { Column, CompareFn, FilterResults } from '../components/column/column-ty export interface TableStore> { states: { _data: Ref; - _columns: Ref[]>; - flatColumns: Ref[]>; + _columns: Ref; + flatColumns: Ref; _checkList: Ref; _checkAll: Ref; _halfChecked: Ref; isFixedLeft: Ref; }; - insertColumn(column: Column, parent: any): void; + insertColumn(column: Column, parent: any): void; sortColumn(): void; - removeColumn(column: Column): void; + removeColumn(column: Column): void; updateColumns(): void; getCheckedRows(): T[]; sortData(field: string, direction: SortDirection, compareFn: CompareFn): void; diff --git a/packages/devui-vue/docs/components/table/index.md b/packages/devui-vue/docs/components/table/index.md index 906df91f71..d567a23dd5 100644 --- a/packages/devui-vue/docs/components/table/index.md +++ b/packages/devui-vue/docs/components/table/index.md @@ -14,7 +14,7 @@ ```vue