Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.DS_Store
node_modules
.pnpm-store/
dist/
dist2/
dist2.7/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<template>
<tiny-grid :row-span="rowSpanFields" :data="tableData">
<tiny-grid-column type="index" width="60"></tiny-grid-column>
<tiny-grid-column type="expand" width="60">
<template #default="{ row }">
<ul>
<li>
<span>公司名称:</span>
<span>{{ row.name }}</span>
</li>
<li>
<span>区域:</span>
<span>{{ row.area }}</span>
</li>
<li>
<span>城市:</span>
<span>{{ row.city }}</span>
</li>
</ul>
</template>
</tiny-grid-column>
<tiny-grid-column field="area" title="区域"></tiny-grid-column>
<tiny-grid-column field="province" title="省份"></tiny-grid-column>
<tiny-grid-column field="city" title="城市"></tiny-grid-column>
<tiny-grid-column field="name" title="公司名称"></tiny-grid-column>
</tiny-grid>
</template>

<script setup>
import { ref } from 'vue'
import { TinyGrid, TinyGridColumn } from '@opentiny/vue'

const rowSpanFields = ref([{ field: 'area' }, { field: 'province' }])
const tableData = ref([
{
id: '1',
name: 'WWW 科技 YX 公司',
area: '华南区',
province: '广东省',
city: '深圳'
},
{
id: '2',
name: 'RFV 有限责任公司',
area: '华南区',
province: '广东省',
city: '中山'
},
{
id: '3',
name: 'YHN 科技 YX 公司',
area: '华南区',
province: '广东省',
city: '韶关'
},
{
id: '4',
name: 'GFD 科技 YX 公司',
area: '华东区',
province: '福建省',
city: '福州'
},
{
id: '5',
name: 'TGB 科技 YX 公司',
area: '华东区',
province: '福建省',
city: '龙岩'
}
])
</script>
23 changes: 23 additions & 0 deletions examples/sites/demos/pc/app/grid/span/row-span-with-expand.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { test, expect } from '@playwright/test'

test('行合并与展开行:展开为全宽且不重叠', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('grid-span#span-row-span-with-expand')

const areaCell = page.getByRole('cell', { name: '华南区' }).first()
await expect(areaCell).toHaveAttribute('rowspan', '3')

await page.getByRole('row', { name: /中山/ }).locator('.tiny-grid__expanded').click()
const expandRow = page.locator('.tiny-grid-body__expanded-row')
await expect(expandRow).toBeVisible()
await expect(page.locator('.tiny-grid-body__expanded-cell')).toContainText('中山')
await expect(expandRow.locator('td')).toHaveAttribute('colspan', '6')
await expect(areaCell).toHaveAttribute('rowspan', '2')

const shaoguanRow = page.getByRole('row', { name: /韶关/ })
await expect(shaoguanRow.getByRole('cell', { name: '华南区' })).toBeVisible()

const expandBox = await expandRow.boundingBox()
const areaBox = await areaCell.boundingBox()
expect(expandBox.y).toBeGreaterThanOrEqual(areaBox.y + areaBox.height - 2)
})
80 changes: 80 additions & 0 deletions examples/sites/demos/pc/app/grid/span/row-span-with-expand.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<template>
<tiny-grid :row-span="rowSpanFields" :data="tableData">
<tiny-grid-column type="index" width="60"></tiny-grid-column>
<tiny-grid-column type="expand" width="60">
<template #default="{ row }">
<ul>
<li>
<span>公司名称:</span>
<span>{{ row.name }}</span>
</li>
<li>
<span>区域:</span>
<span>{{ row.area }}</span>
</li>
<li>
<span>城市:</span>
<span>{{ row.city }}</span>
</li>
</ul>
</template>
</tiny-grid-column>
<tiny-grid-column field="area" title="区域"></tiny-grid-column>
<tiny-grid-column field="province" title="省份"></tiny-grid-column>
<tiny-grid-column field="city" title="城市"></tiny-grid-column>
<tiny-grid-column field="name" title="公司名称"></tiny-grid-column>
</tiny-grid>
</template>

<script>
import { TinyGrid, TinyGridColumn } from '@opentiny/vue'

export default {
components: {
TinyGrid,
TinyGridColumn
},
data() {
return {
rowSpanFields: [{ field: 'area' }, { field: 'province' }],
tableData: [
{
id: '1',
name: 'WWW 科技 YX 公司',
area: '华南区',
province: '广东省',
city: '深圳'
},
{
id: '2',
name: 'RFV 有限责任公司',
area: '华南区',
province: '广东省',
city: '中山'
},
{
id: '3',
name: 'YHN 科技 YX 公司',
area: '华南区',
province: '广东省',
city: '韶关'
},
{
id: '4',
name: 'GFD 科技 YX 公司',
area: '华东区',
province: '福建省',
city: '福州'
},
{
id: '5',
name: 'TGB 科技 YX 公司',
area: '华东区',
province: '福建省',
city: '龙岩'
}
]
}
}
}
</script>
14 changes: 14 additions & 0 deletions examples/sites/demos/pc/app/grid/webdoc/grid-span.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ export default {
},
codeFiles: ['span/row-span.vue']
},
{
demoId: 'span-row-span-with-expand',
name: {
'zh-CN': '行合并与展开行',
'en-US': 'Row Span with Expand'
},
desc: {
'zh-CN':
'<p>同时配置 <code>row-span</code> 与列 <code>type=&quot;expand&quot;</code>。展开行占满整行宽度;合并在展开处断开,避免与展开内容重叠。</p>\n',
'en-US':
'<p>Use <code>row-span</code> with column <code>type=&quot;expand&quot;</code>. The expand row is full width; merges split at the expand so content does not overlap. </p>\n'
},
codeFiles: ['span/row-span-with-expand.vue']
},
{
demoId: 'span-column-span',
name: { 'zh-CN': '列合并', 'en-US': 'Column Merge' },
Expand Down
83 changes: 83 additions & 0 deletions packages/vue/src/grid/__tests__/split-rowspan-at-expand.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { describe, expect, test } from 'vitest'
import { splitRowspanAtExpand } from '../src/composable/useCellSpan'

const col = (property) => ({ property, id: property })

const cell = (rowspan, column, extra = {}) => ({
attrs: { rowspan, colspan: rowspan > 0 ? 1 : 0, visible: rowspan > 0, _dataRowspan: rowspan, ...extra },
params: { column }
})

describe('splitRowspanAtExpand', () => {
test('no-op without expandeds', () => {
const area = col('area')
const rows = [
[cell(3, area)],
[cell(0, area)],
[cell(0, area)]
]
const data = [{ area: 'A' }, { area: 'A' }, { area: 'A' }]
splitRowspanAtExpand(rows, data, { expandeds: [], rowSpan: [{ field: 'area' }] })
expect(rows.map((r) => r[0].attrs.rowspan)).toEqual([3, 0, 0])
})

test('expand without merges is identity', () => {
const city = col('city')
const r0 = { city: '深圳' }
const r1 = { city: '中山' }
const rows = [[cell(1, city)], [cell(1, city)]]
splitRowspanAtExpand(rows, [r0, r1], { expandeds: [r0], rowSpan: [] })
expect(rows.map((r) => r[0].attrs.rowspan)).toEqual([1, 1])
expect(rows[0][0].attrs.colspan).toBe(1)
})

test('splits merge when middle row expands (#4200)', () => {
const area = col('area')
const r0 = { area: '华南区' }
const r1 = { area: '华南区' }
const r2 = { area: '华南区' }
const rows = [[cell(3, area)], [cell(0, area)], [cell(0, area)]]
const data = [r0, r1, r2]

splitRowspanAtExpand(rows, data, {
expandeds: [r1],
rowSpan: [{ field: 'area' }]
})

expect(rows[0][0].attrs).toMatchObject({ rowspan: 2, visible: true })
expect(rows[1][0].attrs).toMatchObject({ rowspan: 0, visible: false })
expect(rows[2][0].attrs).toMatchObject({ rowspan: 1, visible: true })
})

test('expand on first row restarts merge below', () => {
const area = col('area')
const r0 = { area: 'A' }
const r1 = { area: 'A' }
const r2 = { area: 'A' }
const rows = [[cell(3, area)], [cell(0, area)], [cell(0, area)]]

splitRowspanAtExpand(rows, [r0, r1, r2], {
expandeds: [r0],
rowSpan: [{ field: 'area' }]
})

expect(rows[0][0].attrs.rowspan).toBe(1)
expect(rows[1][0].attrs).toMatchObject({ rowspan: 2, visible: true })
expect(rows[2][0].attrs).toMatchObject({ rowspan: 0, visible: false })
})

test('expand on last row of group keeps full merge above', () => {
const area = col('area')
const r0 = { area: 'A' }
const r1 = { area: 'A' }
const r2 = { area: 'A' }
const rows = [[cell(3, area)], [cell(0, area)], [cell(0, area)]]

splitRowspanAtExpand(rows, [r0, r1, r2], {
expandeds: [r2],
rowSpan: [{ field: 'area' }]
})

expect(rows.map((r) => r[0].attrs.rowspan)).toEqual([3, 0, 0])
})
})
60 changes: 59 additions & 1 deletion packages/vue/src/grid/src/composable/useCellSpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export const useCellSpan = (bodyVm, bodyProps) => {
() => $table.visibleColumn,
() => bodyProps.tableData,
() => $table.isColumnWidthAssigned,
() => $table.columnStore.resizeList
() => $table.columnStore.resizeList,
() => $table.expandeds.map((r) => getRowid($table, r)).join()
],
([visibleColumn, tableData, isColumnWidthAssigned]) => {
if (!Array.isArray(tableData) || !isColumnWidthAssigned) {
Expand Down Expand Up @@ -76,6 +77,8 @@ export const useCellSpan = (bodyVm, bodyProps) => {
stateNormalCell(normalState, normalTable, params, $table)
}

splitRowspanAtExpand(normalTable, tableData, $table)

if (leftList.length > 0) {
adjustColspan(normalTable, leftList.length - 1, true)
}
Expand Down Expand Up @@ -184,6 +187,7 @@ const doSpan = ({ attrs, params, rowSpan, spanMethod }) => {

attrs.rowspan = rowspan
attrs.colspan = colspan
attrs._dataRowspan = rowspan
attrs.visible = rowspan > 0 && colspan > 0
}

Expand Down Expand Up @@ -216,6 +220,60 @@ const rowSpanMethod = (rowSpan, { row, $rowIndex, column, data }) => {
}
}

export const splitRowspanAtExpand = (normalTable, tableData, $table) => {
const expandeds = $table.expandeds
if (!expandeds?.length || !normalTable.length) return

const expandedSet = new Set(expandeds)
const colCount = normalTable[0].length

for (let c = 0; c < colCount; c++) {
let coverUntil = -1
for (let r = 0; r < normalTable.length; r++) {
const { attrs, params } = normalTable[r][c]
if (r <= coverUntil) {
attrs.rowspan = 0
attrs.colspan = 0
attrs.visible = false
continue
}

let intended = attrs._dataRowspan
if (!(intended > 0)) {
intended = remainingEqualSpan(tableData, r, params.column, $table)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

let span = 1
while (span < intended && r + span < normalTable.length) {
if (expandedSet.has(tableData[r + span - 1])) break
span++
}

attrs.rowspan = span
if (!(attrs.colspan > 0)) attrs.colspan = 1
attrs.visible = true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
coverUntil = r + span - 1
}
}
}

const remainingEqualSpan = (tableData, start, column, $table) => {
const { rowSpan, spanMethod } = $table
if (spanMethod || !rowSpan?.length || !column?.property) return 1

const fields = rowSpan.map((item) => item.field)
if (!fields.includes(column.property)) return 1

const cellVal = tableData[start]?.[column.property]
if (!cellVal) return 1

let count = 1
while (start + count < tableData.length && tableData[start + count][column.property] === cellVal) {
count++
}
return count
}

const adjustColspan = (table, pos, isLeft) => {
if (pos < 0) {
return
Expand Down
Loading