diff --git a/examples/views/grid/Group.vue b/examples/views/grid/Group.vue index aa079989ab12279764c02940b28c6605f856391f..cd3909f332ea8b2d43eb34a5adfb20a5d5fef077 100644 --- a/examples/views/grid/Group.vue +++ b/examples/views/grid/Group.vue @@ -10,6 +10,17 @@ {{ demoCodes[0] }} {{ demoCodes[1] }} + +

定制表头单元格所占的行

+ + +

{{ $t('app.body.button.showCode') }}

+ +
+      {{ demoCodes[2] }}
+      {{ demoCodes[3] }}
+    
+ @@ -53,9 +64,49 @@ export default defineComponent({ { id: 10008, name: 'Test8', nickname: 'T8', role: 'Develop', sex: 'Man', age: 35, address: 'Shenzhen' } ] }) - + const gridOptions2 = reactive({ + border: true, + stripe: true, + resizable: true, + useCustomHeaderRowSpan: true, + height: 500, + columns: [ + { + title: '序号', + customRowSpan: 3, + fixed: 'left', + children: [{ type: 'seq', title: '1' }] + }, + { + title: '基本信息', + children: [ + { title: 'Name', customRowSpan: 2, children: [{ field: 'name', title: '2' }] }, + { + title: '其他信息', + children: [ + { title: 'Nickname', children: [{ field: 'nickname', title: '3' }] }, + { title: 'Age', children: [{ field: 'age', title: '4' }] } + ] + }, + { title: 'Sex', customRowSpan: 2, children: [{ field: 'sex', title: '5' }] } + ] + }, + { title: 'Address', customRowSpan: 3, children: [{ field: 'address', title: '6', showOverflow: true }] } + ], + data: [ + { id: 10001, name: 'Test1', nickname: 'T1', role: 'Develop', sex: 'Man', age: 28, address: 'Shenzhen' }, + { id: 10002, name: 'Test2', nickname: 'T2', role: 'Test', sex: 'Women', age: 22, address: 'Guangzhou' }, + { id: 10003, name: 'Test3', nickname: 'T3', role: 'PM', sex: 'Man', age: 32, address: 'Shanghai' }, + { id: 10004, name: 'Test4', nickname: 'T4', role: 'Designer', sex: 'Women', age: 23, address: 'Shenzhen' }, + { id: 10005, name: 'Test5', nickname: 'T5', role: 'Develop', sex: 'Women', age: 30, address: 'Shanghai' }, + { id: 10006, name: 'Test6', nickname: 'T6', role: 'Designer', sex: 'Women', age: 21, address: 'Shenzhen' }, + { id: 10007, name: 'Test7', nickname: 'T7', role: 'Test', sex: 'Man', age: 29, address: 'Shenzhen' }, + { id: 10008, name: 'Test8', nickname: 'T8', role: 'Develop', sex: 'Man', age: 35, address: 'Shenzhen' } + ] + }) return { gridOptions, + gridOptions2, demoCodes: [ ` @@ -106,6 +157,62 @@ export default defineComponent({ } } }) + `, + ` + + `, + ` + import { defineComponent, reactive } from 'vue' + import { VxeGridProps } from 'vxe-table' + + export default defineComponent({ + setup () { + const gridOptions = reactive({ + border: true, + stripe: true, + resizable: true, + useCustomHeaderRowSpan: true, + height: 500, + columns: [ + { + title: '序号', + customRowSpan: 3, + fixed: 'left', + children: [{ type: 'seq', title: '1' }] + }, + { + title: '基本信息', + children: [ + { title: 'Name', customRowSpan: 2, children: [{ field: 'name', title: '2' }] }, + { + title: '其他信息', + children: [ + { title: 'Nickname', children: [{ field: 'nickname', title: '3' }] }, + { title: 'Age', children: [{ field: 'age', title: '4' }] } + ] + }, + { title: 'Sex', customRowSpan: 2, children: [{ field: 'sex', title: '5' }] } + ] + }, + { title: 'Address', customRowSpan: 3, children: [{ field: 'address', title: '6', showOverflow: true }] } + ], + data: [ + { id: 10001, name: 'Test1', nickname: 'T1', role: 'Develop', sex: 'Man', age: 28, address: 'Shenzhen' }, + { id: 10002, name: 'Test2', nickname: 'T2', role: 'Test', sex: 'Women', age: 22, address: 'Guangzhou' }, + { id: 10003, name: 'Test3', nickname: 'T3', role: 'PM', sex: 'Man', age: 32, address: 'Shanghai' }, + { id: 10004, name: 'Test4', nickname: 'T4', role: 'Designer', sex: 'Women', age: 23, address: 'Shenzhen' }, + { id: 10005, name: 'Test5', nickname: 'T5', role: 'Develop', sex: 'Women', age: 30, address: 'Shanghai' }, + { id: 10006, name: 'Test6', nickname: 'T6', role: 'Designer', sex: 'Women', age: 21, address: 'Shenzhen' }, + { id: 10007, name: 'Test7', nickname: 'T7', role: 'Test', sex: 'Man', age: 29, address: 'Shenzhen' }, + { id: 10008, name: 'Test8', nickname: 'T8', role: 'Develop', sex: 'Man', age: 35, address: 'Shenzhen' } + ] + }) + + return { + gridOptions + } + } + }) ` ] } diff --git a/examples/views/table/base/Group.vue b/examples/views/table/base/Group.vue index ffba00b628d523a8dc5ccdb52259311d2d8f2019..d58b972fc41cc54b3e4be2007270d4de2fd1aa61 100644 --- a/examples/views/table/base/Group.vue +++ b/examples/views/table/base/Group.vue @@ -1,5 +1,6 @@ @@ -123,11 +194,39 @@ export default defineComponent({ } } + const xTable3 = ref({} as VxeTableInstance) + const tableData3 = ref([ + { id: 10001, name: 'Test1', role: 'Develop', sex: 'Man', age: 28, address: 'test abc' }, + { id: 10002, name: 'Test2', role: 'Test', sex: 'Women', age: 22, address: 'Guangzhou' }, + { id: 10003, name: 'Test3', role: 'PM', sex: 'Man', age: 32, address: 'Shanghai' }, + { id: 10004, name: 'Test4', role: 'Designer', sex: 'Women', age: 23, address: 'test abc' }, + { id: 10005, name: 'Test5', role: 'Develop', sex: 'Women', age: 30, address: 'Shanghai' }, + { id: 10006, name: 'Test6', role: 'Designer', sex: 'Women', age: 21, address: 'test abc' }, + { id: 10007, name: 'Test7', role: 'Test', sex: 'Man', age: 29, address: 'test abc' }, + { id: 10008, name: 'Test8', role: 'Develop', sex: 'Man', age: 35, address: 'test abc' } + ]) + + const toggleFixedColumn3 = (field: string, type: VxeColumnPropTypes.Fixed) => { + const $table = xTable3.value + const column = $table.getColumnByField(field) + if (column) { + const groupFixed = column.fixed ? null : type + // 将分组整体设置固定列 + XEUtils.eachTree([column], column => { + column.fixed = groupFixed + }) + // 刷新列 + $table.refreshColumn() + } + } return { tableData1, xTable2, tableData2, toggleFixedColumn, + xTable3, + tableData3, + toggleFixedColumn3, demoCodes: [ ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `, + ` + import { defineComponent, ref } from 'vue' + import { VxeTableInstance, VxeColumnPropTypes } from 'vxe-table' + import XEUtils from 'xe-utils' + + export default defineComponent({ + setup () { + const xTable3 = ref({} as VxeTableInstance) + + const tableData3 = ref([ + { id: 10001, name: 'Test1', role: 'Develop', sex: 'Man', age: 28, address: 'test abc' }, + { id: 10002, name: 'Test2', role: 'Test', sex: 'Women', age: 22, address: 'Guangzhou' }, + { id: 10003, name: 'Test3', role: 'PM', sex: 'Man', age: 32, address: 'Shanghai' }, + { id: 10004, name: 'Test4', role: 'Designer', sex: 'Women', age: 23, address: 'test abc' }, + { id: 10005, name: 'Test5', role: 'Develop', sex: 'Women', age: 30, address: 'Shanghai' }, + { id: 10006, name: 'Test6', role: 'Designer', sex: 'Women', age: 21, address: 'test abc' }, + { id: 10007, name: 'Test7', role: 'Test', sex: 'Man', age: 29, address: 'test abc' }, + { id: 10008, name: 'Test8', role: 'Develop', sex: 'Man', age: 35, address: 'test abc' } + ]) + + const toggleFixedColumn3 = (field: string, type: VxeColumnPropTypes.Fixed) => { + const $table = xTable3.value + const column = $table.getColumnByField(field) + if (column) { + const groupFixed = column.fixed ? null : type + // 将分组整体设置固定列 + XEUtils.eachTree([column], column => { + column.fixed = groupFixed + }) + // 刷新列 + $table.refreshColumn() + } + } + + return { + xTable3, + tableData3, + toggleFixedColumn3 + } + } + } ` ] } diff --git a/packages/export/src/hook.ts b/packages/export/src/hook.ts index 38a25dd78b626ea421ae69126d9182a2b3c9d438..c73aefb5f4639583b81753261c14dacf6d48a2fb 100644 --- a/packages/export/src/hook.ts +++ b/packages/export/src/hook.ts @@ -31,7 +31,7 @@ const getConvertColumns = (columns: any) => { return result } -const convertToRows = (originColumns: any): any[][] => { +const convertToRows = (originColumns: any, useCustomRowSpan? : boolean): any[][] => { let maxLevel = 1 const traverse = (column: any, parent?: any) => { if (parent) { @@ -63,14 +63,46 @@ const convertToRows = (originColumns: any): any[][] => { } const allColumns = getConvertColumns(originColumns) - + const getGroupParentRowSpan = (columnId:string) => { + let r = 0 + for (let i = 0; i < allColumns.length; i++) { + const column = allColumns[i] + if (column.id === columnId) { + if (column._rowSpan && column._rowSpan > 1) { + r = r + column._rowSpan + } + if (column.parentId) { + r = r + getGroupParentRowSpan(column.parentId) + } + } + } + return r + } allColumns.forEach((column: any) => { - if (column.childNodes && column.childNodes.length) { - column._rowSpan = 1 + if (useCustomRowSpan) { + if (column.customRowSpan) { + column._rowSpan = column.customRowSpan + } else { + column._rowSpan = 1 + } + let alevel = column._level - 1 + if (column.parentId) { + let parentRowSpan = getGroupParentRowSpan(column.parentId) + parentRowSpan = parentRowSpan > 0 ? parentRowSpan - 1 : 0 + alevel = alevel + parentRowSpan + if (alevel >= maxLevel) { + alevel = maxLevel - 1 + } + } + rows[alevel].push(column) } else { - column._rowSpan = maxLevel - column._level + 1 + if (column.childNodes && column.childNodes.length) { + column._rowSpan = 1 + } else { + column._rowSpan = maxLevel - column._level + 1 + } + rows[column._level - 1].push(column) } - rows[column._level - 1].push(column) }) return rows diff --git a/packages/header/src/header.ts b/packages/header/src/header.ts index afec0dffc3718aa55f21b8bad7027d03c9c319de..25798340a4adcf9c1c2e1bba68d3bd6d2b3e1678 100644 --- a/packages/header/src/header.ts +++ b/packages/header/src/header.ts @@ -15,7 +15,8 @@ export default defineComponent({ tableColumn: Array as PropType, tableGroupColumn: Array as PropType, fixedColumn: Array as PropType, - fixedType: { type: String as PropType, default: null } + fixedType: { type: String as PropType, default: null }, + useCustomHeaderRowSpan: { type: Boolean, default: false } }, setup (props) { const $xetable = inject('$xetable', {} as VxeTableConstructor & VxeTableMethods & VxeTablePrivateMethods) @@ -35,7 +36,7 @@ export default defineComponent({ const uploadColumn = () => { const { isGroup } = tableReactData - headerColumn.value = isGroup ? convertToRows(props.tableGroupColumn) : [] + headerColumn.value = isGroup ? convertToRows(props.tableGroupColumn, props.useCustomHeaderRowSpan) : [] } const resizeMousedown = (evnt: MouseEvent, params: any) => { diff --git a/packages/header/src/util.ts b/packages/header/src/util.ts index 39618b54e7624cb455ba35f5c60d3fff9a659715..c91654ffe0a33fcdd7755f6ccbb18fe0debd38b6 100644 --- a/packages/header/src/util.ts +++ b/packages/header/src/util.ts @@ -14,7 +14,7 @@ const getAllColumns = (columns: any, parentColumn?: any) => { return result } -export const convertToRows = (originColumns: any): any[][] => { +export const convertToRows = (originColumns: any, useCustomRowSpan?: boolean): any[][] => { let maxLevel = 1 const traverse = (column: any, parent?: any) => { if (parent) { @@ -49,13 +49,48 @@ export const convertToRows = (originColumns: any): any[][] => { const allColumns = getAllColumns(originColumns) + // rowSpan 计算 + const getGroupParentRowSpan = (columnId: string) => { + let r = 0 + for (let i = 0; i < allColumns.length; i++) { + const column = allColumns[i] + if (column.id === columnId) { + if (column.rowSpan && column.rowSpan > 1) { + r = r + column.rowSpan + } + if (column.parentId) { + r = r + getGroupParentRowSpan(column.parentId) + } + } + } + return r + } + allColumns.forEach((column) => { - if (column.children && column.children.length && column.children.some((column: any) => column.visible)) { - column.rowSpan = 1 + if (useCustomRowSpan) { + if (column.customRowSpan) { + column.rowSpan = column.customRowSpan + } else { + column.rowSpan = 1 + } + let alevel = column.level - 1 + if (column.parentId) { + let parentRowSpan = getGroupParentRowSpan(column.parentId) + parentRowSpan = parentRowSpan > 0 ? parentRowSpan - 1 : 0 + alevel = alevel + parentRowSpan + if (alevel >= maxLevel) { + alevel = maxLevel - 1 + } + } + rows[alevel].push(column) } else { - column.rowSpan = maxLevel - column.level + 1 + if (column.children && column.children.length && column.children.some((column: any) => column.visible)) { + column.rowSpan = 1 + } else { + column.rowSpan = maxLevel - column.level + 1 + } + rows[column.level - 1].push(column) } - rows[column.level - 1].push(column) }) return rows diff --git a/packages/table/src/column.ts b/packages/table/src/column.ts index 4ab9d3ae72fc1d8df0b8b897bc462f4fba718941..d8476582e304010f0f32f4517397fac1becb88b4 100644 --- a/packages/table/src/column.ts +++ b/packages/table/src/column.ts @@ -27,6 +27,8 @@ export const columnProps = { headerAlign: String as PropType, // 表尾列的对齐方式 footerAlign: String as PropType, + // 定制行高 + customRowSpan: { type: [Number, String] as PropType, default: 1 }, // 当内容过长时显示为省略号 showOverflow: { type: [Boolean, String] as PropType, default: null }, // 当表头内容过长时显示为省略号 diff --git a/packages/table/src/columnInfo.ts b/packages/table/src/columnInfo.ts index 8fe97ad0cd6a382399f901604583ad890a5cf8a1..48a698c02b0f73efbaa7d5327d173980a8366188 100644 --- a/packages/table/src/columnInfo.ts +++ b/packages/table/src/columnInfo.ts @@ -75,6 +75,7 @@ export class ColumnInfo { className: _vm.className, headerClassName: _vm.headerClassName, footerClassName: _vm.footerClassName, + customRowSpan: _vm.customRowSpan, formatter: formatter, sortable: _vm.sortable, sortBy: _vm.sortBy, diff --git a/packages/table/src/props.ts b/packages/table/src/props.ts index db9229c5701199528187c20b87fd1b5ad179adb2..94c25b627acf85c37badfe9419dc44014f5d3881 100644 --- a/packages/table/src/props.ts +++ b/packages/table/src/props.ts @@ -160,5 +160,7 @@ export default { // (可能会被废弃的参数,不要使用) delayHover: { type: Number as PropType, default: () => GlobalConfig.table.delayHover as number }, // 额外的参数 - params: Object as PropType + params: Object as PropType, + // 使用自定义表头单元格行数方式 + useCustomHeaderRowSpan: { type: Boolean as PropType, default: () => GlobalConfig.table.useCustomHeaderRowSpan } } diff --git a/packages/table/src/table.ts b/packages/table/src/table.ts index 77699a4d0ef0f242aa9be0b8ed616ddcc40e1889..9d0bfe2b8dff5d562c85f061e53b3a3f032bc202 100644 --- a/packages/table/src/table.ts +++ b/packages/table/src/table.ts @@ -63,6 +63,7 @@ export default defineComponent({ parentHeight: 0, // 是否使用分组表头 isGroup: false, + useCustomHeaderRowSpan: false, isAllOverflow: false, // 复选框属性,是否全选 isAllSelected: false, @@ -892,6 +893,7 @@ export default defineComponent({ const mouseOpts = computeMouseOpts.value const isGroup = collectColumn.some(hasChildrenList) let isAllOverflow = !!props.showOverflow + const useCustomHeaderRowSpan = !!props.useCustomHeaderRowSpan let expandColumn: any let treeNodeColumn: any let checkboxColumn: any @@ -969,7 +971,7 @@ export default defineComponent({ errLog('vxe.error.errConflicts', ['mouse-config.area', 'column.type=expand']) } } - + reactData.useCustomHeaderRowSpan = useCustomHeaderRowSpan reactData.isGroup = isGroup reactData.treeNodeColumn = treeNodeColumn reactData.expandColumn = expandColumn @@ -5897,7 +5899,7 @@ export default defineComponent({ const renderVN = () => { const { loading, stripe, showHeader, height, treeConfig, mouseConfig, showFooter, highlightCell, highlightHoverRow, highlightHoverColumn, editConfig } = props - const { isGroup, overflowX, overflowY, scrollXLoad, scrollYLoad, scrollbarHeight, tableData, tableColumn, tableGroupColumn, footerTableData, initStore, columnStore, filterStore } = reactData + const { isGroup, useCustomHeaderRowSpan, overflowX, overflowY, scrollXLoad, scrollYLoad, scrollbarHeight, tableData, tableColumn, tableGroupColumn, footerTableData, initStore, columnStore, filterStore } = reactData const { leftList, rightList } = columnStore const tipConfig = computeTipConfig.value const treeOpts = computeTreeOpts.value @@ -5956,7 +5958,8 @@ export default defineComponent({ ref: refTableHeader, tableData, tableColumn, - tableGroupColumn + tableGroupColumn, + useCustomHeaderRowSpan }) : createCommentVNode(), /** * 表体 diff --git a/types/colgroup.d.ts b/types/colgroup.d.ts index 6917cd057d7aa9c13a7a784d500e77db44d62e70..e3b57c17949f59b321c6cc7984dd7275a94228ca 100644 --- a/types/colgroup.d.ts +++ b/types/colgroup.d.ts @@ -64,6 +64,10 @@ export type VxeColgroupProps = { * 是否可视 */ visible?: VxeColumnPropTypes.Visible + /** + * 定制行高 + */ + customRowSpan?: VxeColumnPropTypes.CustomRowSpan /** * 额外的参数 */ diff --git a/types/column.d.ts b/types/column.d.ts index 658eae0f324bae44c7efc42f94267d5f69408ee3..5ba7dd9013acfd1f10fecd096a3a30daf0718a5c 100644 --- a/types/column.d.ts +++ b/types/column.d.ts @@ -23,6 +23,7 @@ export namespace VxeColumnPropTypes { export type Align = 'left' | 'center' | 'right' | null export type HeaderAlign = Align export type FooterAlign = Align + export type CustomRowSpan = string | number export type ShowOverflow = VxeTablePropTypes.ShowOverflow export type ShowHeaderOverflow = ShowOverflow export type ShowFooterOverflow = ShowOverflow @@ -159,7 +160,6 @@ export namespace VxeColumnPropTypes { } export type Params = any - interface FilterSlotParams { $panel: VxeFilterPanel column: { @@ -363,5 +363,9 @@ export type VxeColumnProps = { /** * 额外的参数 */ - params?: VxeColumnPropTypes.Params + params?: VxeColumnPropTypes.Params, + /** + * 定制单元格行高 + */ + customRowSpan?: VxeColumnPropTypes.CustomRowSpan, } diff --git a/types/table.d.ts b/types/table.d.ts index ae355128c8f09a190880d223951a05534befccfc..f74555f82dc7c528a9052a0389e1eccb4934da7d 100644 --- a/types/table.d.ts +++ b/types/table.d.ts @@ -759,6 +759,8 @@ export interface TableReactData { parentHeight: number // 是否使用分组表头 isGroup: boolean + // 自定义表头单元格行数 + useCustomHeaderRowSpan: boolean isAllOverflow: boolean // 复选框属性,是否全选 isAllSelected: boolean @@ -1940,6 +1942,7 @@ export namespace VxeTablePropTypes { } export type Params = any + export type UseCustomHeaderRowSpan = boolean } export type VxeTableProps = { @@ -2053,6 +2056,7 @@ export type VxeTableProps = { scrollX?: VxeTablePropTypes.ScrollX scrollY?: VxeTablePropTypes.ScrollY params?: VxeTablePropTypes.Params + useCustomHeaderRowSpan?: VxeTablePropTypes.UseCustomHeaderRowSpan } export type VxeTableEmits = [ @@ -2615,4 +2619,4 @@ export namespace VxeTableEvents { export type ValidError = (params: VxeTableDefines.ValidErrorEventParams) => void export type Scroll = (params: VxeTableDefines.ScrollEventParams) => void export type Custom = (params: VxeTableDefines.CustomEventParams) => void -} \ No newline at end of file +}