@studio-west/component-sw 0.12.12 → 0.12.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -66
- package/dist/SwTable-BryA1Lud.js +338 -0
- package/dist/SwTableColumn-DCxVGb9d.js +66 -0
- package/dist/component-sw.css +1 -1
- package/dist/index.cjs +6 -6
- package/dist/index.js +2 -2
- package/package.json +8 -5
- package/src/components/SwTable.vue +288 -104
- package/src/components/SwTableColumn.vue +65 -3
- package/dist/SwTable-DNt-mSdn.js +0 -167
- package/dist/SwTableColumn-S8ldvuhL.js +0 -28
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
2
|
+
<!-- Скрытый рендер слота: монтирует SwTableColumn, которые регистрируют себя через inject -->
|
|
3
|
+
<div style="display: none;" aria-hidden="true"><slot /></div>
|
|
4
|
+
<div
|
|
5
|
+
ref="wrapperRef"
|
|
6
|
+
class="sw-table-wrapper"
|
|
7
|
+
:class="{ 'fixed-active': isFixedActive }"
|
|
8
|
+
:style="wrapperStyle"
|
|
9
|
+
>
|
|
10
|
+
<table ref="tableRef" class="sw-table" :class="{ 'resizing': isResizing }">
|
|
3
11
|
<thead>
|
|
4
12
|
<tr v-for="(row, rowIndex) in headerRows" :key="rowIndex">
|
|
5
13
|
<th
|
|
@@ -7,40 +15,43 @@
|
|
|
7
15
|
:key="cell.key"
|
|
8
16
|
:colspan="cell.colspan"
|
|
9
17
|
:rowspan="cell.rowspan"
|
|
10
|
-
:
|
|
18
|
+
:class="getHeaderCellClass(cell)"
|
|
19
|
+
:style="getHeaderCellStyle(cell)"
|
|
20
|
+
@mouseenter="onHeaderEnter(cell)"
|
|
21
|
+
@mouseleave="onHeaderLeave"
|
|
11
22
|
>
|
|
12
23
|
{{ cell.label }}
|
|
24
|
+
<span
|
|
25
|
+
v-if="isResizableLeaf(cell)"
|
|
26
|
+
class="resize-handle"
|
|
27
|
+
@mousedown.prevent="startResize($event, cell)"
|
|
28
|
+
/>
|
|
13
29
|
</th>
|
|
14
30
|
</tr>
|
|
15
31
|
</thead>
|
|
16
32
|
<tbody>
|
|
17
|
-
<tr
|
|
33
|
+
<tr
|
|
34
|
+
v-for="(row, rowIndex) in processedTableData"
|
|
35
|
+
:key="rowIndex"
|
|
36
|
+
@mouseenter="hoveredRowIndex = rowIndex"
|
|
37
|
+
@mouseleave="hoveredRowIndex = null"
|
|
38
|
+
>
|
|
18
39
|
<td
|
|
19
40
|
v-for="cell in row"
|
|
20
41
|
:key="cell.key"
|
|
21
42
|
:colspan="cell.colspan"
|
|
22
43
|
:rowspan="cell.rowspan"
|
|
23
|
-
:
|
|
44
|
+
:class="getBodyCellClass(cell, rowIndex)"
|
|
45
|
+
:style="getBodyCellStyle(cell)"
|
|
24
46
|
>
|
|
25
|
-
<!--
|
|
47
|
+
<!-- Именованный слот по prop колонки -->
|
|
26
48
|
<slot
|
|
27
|
-
v-if="slots[cell.prop]"
|
|
49
|
+
v-if="cell.prop && slots[cell.prop]"
|
|
28
50
|
:name="cell.prop"
|
|
29
51
|
:row="cell.originalRow"
|
|
30
52
|
:$index="cell.rowIndex"
|
|
31
53
|
:value="cell.label"
|
|
32
54
|
/>
|
|
33
|
-
<!-- Затем пробуем слот default с данными конкретной ячейки -->
|
|
34
|
-
<slot
|
|
35
|
-
v-else-if="slots.default"
|
|
36
|
-
name="default"
|
|
37
|
-
:row="cell.originalRow"
|
|
38
|
-
:$index="cell.rowIndex"
|
|
39
|
-
:column="cell.prop"
|
|
40
|
-
:value="cell.label"
|
|
41
|
-
>
|
|
42
|
-
{{ cell.label }}
|
|
43
|
-
</slot>
|
|
44
55
|
<!-- Fallback на простое значение -->
|
|
45
56
|
<template v-else>
|
|
46
57
|
{{ cell.label }}
|
|
@@ -49,63 +60,195 @@
|
|
|
49
60
|
</tr>
|
|
50
61
|
</tbody>
|
|
51
62
|
</table>
|
|
63
|
+
</div>
|
|
52
64
|
</template>
|
|
53
65
|
|
|
54
66
|
<script setup>
|
|
55
|
-
import {
|
|
56
|
-
|
|
67
|
+
import {
|
|
68
|
+
ref, useSlots, computed, watchEffect, provide, reactive,
|
|
69
|
+
onMounted, onUnmounted, nextTick, watch
|
|
70
|
+
} from 'vue'
|
|
57
71
|
|
|
58
72
|
const props = defineProps({
|
|
59
|
-
data: {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
73
|
+
data: { type: Array, required: true },
|
|
74
|
+
resizable: { type: Boolean, default: true },
|
|
75
|
+
maxWidth: { type: [String, Number], default: undefined }
|
|
63
76
|
})
|
|
64
77
|
|
|
78
|
+
const emit = defineEmits(['column-resize'])
|
|
79
|
+
|
|
65
80
|
const slots = useSlots()
|
|
66
|
-
const columnTree =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
const columnTree = reactive([])
|
|
82
|
+
const columnWidths = ref({})
|
|
83
|
+
const isResizing = ref(false)
|
|
84
|
+
const hoveredColIndices = ref(null)
|
|
85
|
+
const hoveredRowIndex = ref(null)
|
|
86
|
+
const wrapperRef = ref(null)
|
|
87
|
+
const tableRef = ref(null)
|
|
88
|
+
const isOverflowing = ref(false)
|
|
89
|
+
const isClient = typeof window !== 'undefined' && typeof document !== 'undefined'
|
|
90
|
+
let columnOrder = 0
|
|
91
|
+
let resizeObserver = null
|
|
70
92
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (vnode.type === Symbol.for('v-fgt') || Array.isArray(vnode.children)) {
|
|
74
|
-
const children = Array.isArray(vnode.children) ? vnode.children : []
|
|
75
|
-
const childColumns = buildColumnTree(children)
|
|
76
|
-
result.push(...childColumns)
|
|
77
|
-
continue
|
|
78
|
-
}
|
|
93
|
+
provide('swTableColumns', columnTree)
|
|
94
|
+
provide('swTableAllocColumnOrder', () => columnOrder++)
|
|
79
95
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
function parseWidth(width) {
|
|
97
|
+
if (width == null || width === 'auto') return null
|
|
98
|
+
if (typeof width === 'number') return width
|
|
99
|
+
const parsed = parseInt(width, 10)
|
|
100
|
+
return Number.isNaN(parsed) ? null : parsed
|
|
101
|
+
}
|
|
102
|
+
function formatWidth(width) {
|
|
103
|
+
return width == null ? undefined : `${width}px`
|
|
104
|
+
}
|
|
105
|
+
function getColumnWidth(prop, defaultWidth) {
|
|
106
|
+
if (columnWidths.value[prop] != null) return columnWidths.value[prop]
|
|
107
|
+
return parseWidth(defaultWidth)
|
|
108
|
+
}
|
|
109
|
+
function getColumnWidthStyle(prop, defaultWidth) {
|
|
110
|
+
const width = getColumnWidth(prop, defaultWidth)
|
|
111
|
+
if (width == null) return {}
|
|
112
|
+
const formatted = formatWidth(width)
|
|
113
|
+
return { width: formatted, minWidth: formatted }
|
|
114
|
+
}
|
|
115
|
+
function getColumnPixelWidth(column) {
|
|
116
|
+
if (!column?.prop) return parseWidth(column?.width) ?? parseWidth(column?.minWidth) ?? 80
|
|
117
|
+
return getColumnWidth(column.prop, column.width)
|
|
118
|
+
?? parseWidth(column.minWidth) ?? 80
|
|
119
|
+
}
|
|
120
|
+
function getLeftOffsetForColIndex(colIndex) {
|
|
121
|
+
let offset = 0
|
|
122
|
+
for (let i = 0; i < colIndex; i++) {
|
|
123
|
+
offset += getColumnPixelWidth(flatColumns.value[i])
|
|
124
|
+
}
|
|
125
|
+
return offset
|
|
126
|
+
}
|
|
127
|
+
function getRightOffsetForColIndex(colIndex) {
|
|
128
|
+
let offset = 0
|
|
129
|
+
const columns = flatColumns.value
|
|
130
|
+
for (let i = columns.length - 1; i > colIndex; i--) {
|
|
131
|
+
offset += getColumnPixelWidth(columns[i])
|
|
132
|
+
}
|
|
133
|
+
return offset
|
|
134
|
+
}
|
|
135
|
+
function getCellFixedSide(cell) {
|
|
136
|
+
if (cell.fixed) return cell.fixed
|
|
137
|
+
if (cell.prop) return flatColumns.value.find((col) => col.prop === cell.prop)?.fixed
|
|
138
|
+
if (!cell.colIndices?.length) return null
|
|
139
|
+
const sides = cell.colIndices.map((i) => flatColumns.value[i]?.fixed).filter(Boolean)
|
|
140
|
+
if (sides.length && sides.every((side) => side === 'left')) return 'left'
|
|
141
|
+
if (sides.length && sides.every((side) => side === 'right')) return 'right'
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
function getFixedPositionStyle(cell, inHeader = false) {
|
|
145
|
+
const side = getCellFixedSide(cell)
|
|
146
|
+
if (!side) return {}
|
|
147
|
+
const startIndex = cell.colIndex ?? cell.colIndices[0]
|
|
148
|
+
const endIndex = cell.colIndex != null
|
|
149
|
+
? cell.colIndex + (cell.colspan || 1) - 1
|
|
150
|
+
: cell.colIndices[cell.colIndices.length - 1]
|
|
151
|
+
const zBase = inHeader ? 100 : 0
|
|
152
|
+
if (side === 'left') {
|
|
153
|
+
return {
|
|
154
|
+
position: 'sticky',
|
|
155
|
+
left: `${getLeftOffsetForColIndex(startIndex)}px`,
|
|
156
|
+
zIndex: zBase + startIndex + 1
|
|
106
157
|
}
|
|
107
158
|
}
|
|
108
|
-
return
|
|
159
|
+
return {
|
|
160
|
+
position: 'sticky',
|
|
161
|
+
right: `${getRightOffsetForColIndex(endIndex)}px`,
|
|
162
|
+
zIndex: zBase + flatColumns.value.length - endIndex
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function getHeaderCellStyle(cell) {
|
|
166
|
+
const style = { minHeight: cell.height && cell.height !== 'auto' ? cell.height : undefined}
|
|
167
|
+
if (cell.prop) Object.assign(style, getColumnWidthStyle(cell.prop, cell.width))
|
|
168
|
+
else if (cell.width && cell.width !== 'auto') style.minWidth = formatWidth(parseWidth(cell.width)) || cell.width
|
|
169
|
+
if (isFixedActive.value) Object.assign(style, getFixedPositionStyle(cell, true))
|
|
170
|
+
return style
|
|
171
|
+
}
|
|
172
|
+
function getBodyCellStyle(cell) {
|
|
173
|
+
const style = { ...cell.style }
|
|
174
|
+
if (isFixedActive.value) Object.assign(style, getFixedPositionStyle(cell))
|
|
175
|
+
return style
|
|
176
|
+
}
|
|
177
|
+
function isResizableLeaf(cell) {
|
|
178
|
+
if (!props.resizable || !cell.prop) return false
|
|
179
|
+
return cell.resizable !== false
|
|
180
|
+
}
|
|
181
|
+
function onHeaderEnter(cell) {hoveredColIndices.value = new Set(cell.colIndices)}
|
|
182
|
+
function onHeaderLeave() {hoveredColIndices.value = null}
|
|
183
|
+
function cellCoversHoveredColumn(cell) {
|
|
184
|
+
if (!hoveredColIndices.value) return false
|
|
185
|
+
const start = cell.colIndex
|
|
186
|
+
const end = start + (cell.colspan || 1) - 1
|
|
187
|
+
for (let i = start; i <= end; i++) {
|
|
188
|
+
if (hoveredColIndices.value.has(i)) return true
|
|
189
|
+
}
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
function isRowHoveredForCell(cell, rowIndex) {
|
|
193
|
+
const hover = hoveredRowIndex.value
|
|
194
|
+
if (hover === null) return false
|
|
195
|
+
return rowIndex <= hover && hover < rowIndex + (cell.rowspan || 1)
|
|
196
|
+
}
|
|
197
|
+
function getFixedCellClass(cell) {
|
|
198
|
+
const side = getCellFixedSide(cell)
|
|
199
|
+
return {
|
|
200
|
+
'fixed-left': isFixedActive.value && side === 'left',
|
|
201
|
+
'fixed-right': isFixedActive.value && side === 'right'
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function getBodyCellClass(cell, rowIndex) {
|
|
205
|
+
const colHover = cellCoversHoveredColumn(cell)
|
|
206
|
+
const rowHover = isRowHoveredForCell(cell, rowIndex)
|
|
207
|
+
return {
|
|
208
|
+
...getFixedCellClass(cell),
|
|
209
|
+
'col-hover': colHover && !rowHover,
|
|
210
|
+
'row-hover': rowHover && !colHover
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function getHeaderCellClass(cell) {
|
|
214
|
+
return {
|
|
215
|
+
...getFixedCellClass(cell),
|
|
216
|
+
'resizable': isResizableLeaf(cell),
|
|
217
|
+
'col-hover': cellCoversHoveredColumn(cell)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function getMinResizeWidth(cell) {
|
|
221
|
+
return parseWidth(cell.minWidth) ?? 48
|
|
222
|
+
}
|
|
223
|
+
function startResize(event, cell) {
|
|
224
|
+
if (!isClient) return
|
|
225
|
+
const th = event.currentTarget.closest('th')
|
|
226
|
+
if (!th || !cell.prop) return
|
|
227
|
+
const prop = cell.prop
|
|
228
|
+
const startX = event.clientX
|
|
229
|
+
const startWidth = getColumnWidth(prop, cell.width) ?? th.offsetWidth
|
|
230
|
+
const minWidth = getMinResizeWidth(cell)
|
|
231
|
+
isResizing.value = true
|
|
232
|
+
document.body.style.cursor = 'col-resize'
|
|
233
|
+
document.body.style.userSelect = 'none'
|
|
234
|
+
const onMouseMove = (e) => {
|
|
235
|
+
const nextWidth = Math.max(minWidth, startWidth + (e.clientX - startX))
|
|
236
|
+
columnWidths.value = { ...columnWidths.value, [prop]: nextWidth }
|
|
237
|
+
}
|
|
238
|
+
const onMouseUp = () => {
|
|
239
|
+
document.removeEventListener('mousemove', onMouseMove)
|
|
240
|
+
document.removeEventListener('mouseup', onMouseUp)
|
|
241
|
+
document.body.style.cursor = ''
|
|
242
|
+
document.body.style.userSelect = ''
|
|
243
|
+
isResizing.value = false
|
|
244
|
+
emit('column-resize', { prop, width: columnWidths.value[prop] })
|
|
245
|
+
nextTick(updateOverflowState)
|
|
246
|
+
}
|
|
247
|
+
document.addEventListener('mousemove', onMouseMove)
|
|
248
|
+
document.addEventListener('mouseup', onMouseUp)
|
|
249
|
+
}
|
|
250
|
+
function sortColumns(columns) {
|
|
251
|
+
return [...columns].sort((a, b) => a.order - b.order)
|
|
109
252
|
}
|
|
110
253
|
|
|
111
254
|
// Получение максимальной глубины дерева
|
|
@@ -124,26 +267,27 @@ function getMaxDepth(columns) {
|
|
|
124
267
|
// Преобразование дерева в строки для thead
|
|
125
268
|
function buildHeaderRows(columns, depth) {
|
|
126
269
|
const rows = Array.from({ length: depth }, () => [])
|
|
127
|
-
|
|
270
|
+
let leafColIndex = 0
|
|
128
271
|
function processColumns(cols, rowIndex, parentKey = '') {
|
|
129
|
-
|
|
130
|
-
|
|
272
|
+
const sortedCols = sortColumns(cols)
|
|
273
|
+
for (let i = 0; i < sortedCols.length; i++) {
|
|
274
|
+
const col = sortedCols[i]
|
|
131
275
|
const key = parentKey ? `${parentKey}-${col.prop || i}` : (col.prop || i)
|
|
132
|
-
|
|
133
276
|
if (col.children && col.children.length > 0) {
|
|
134
|
-
|
|
277
|
+
const startIndex = leafColIndex
|
|
135
278
|
const leafCount = countLeafColumns(col.children)
|
|
279
|
+
processColumns(col.children, rowIndex + 1, key)
|
|
136
280
|
rows[rowIndex].push({
|
|
137
281
|
key,
|
|
138
282
|
label: col.label,
|
|
139
283
|
colspan: leafCount,
|
|
140
284
|
rowspan: 1,
|
|
141
285
|
width: col.width,
|
|
142
|
-
height: col.height
|
|
286
|
+
height: col.height,
|
|
287
|
+
colIndices: Array.from({ length: leafCount }, (_, j) => startIndex + j)
|
|
143
288
|
})
|
|
144
|
-
processColumns(col.children, rowIndex + 1, key)
|
|
145
289
|
} else {
|
|
146
|
-
|
|
290
|
+
const colIndex = leafColIndex++
|
|
147
291
|
rows[rowIndex].push({
|
|
148
292
|
key: col.prop,
|
|
149
293
|
label: col.label,
|
|
@@ -151,7 +295,11 @@ function buildHeaderRows(columns, depth) {
|
|
|
151
295
|
rowspan: depth - rowIndex,
|
|
152
296
|
width: col.width,
|
|
153
297
|
height: col.height,
|
|
154
|
-
prop: col.prop
|
|
298
|
+
prop: col.prop,
|
|
299
|
+
resizable: col.resizable,
|
|
300
|
+
minWidth: col.minWidth,
|
|
301
|
+
colIndex,
|
|
302
|
+
colIndices: [colIndex]
|
|
155
303
|
})
|
|
156
304
|
}
|
|
157
305
|
}
|
|
@@ -163,9 +311,8 @@ function buildHeaderRows(columns, depth) {
|
|
|
163
311
|
// Подсчет количества листовых колонок
|
|
164
312
|
function countLeafColumns(columns) {
|
|
165
313
|
let count = 0
|
|
166
|
-
|
|
167
314
|
function traverse(cols) {
|
|
168
|
-
for (const col of cols) {
|
|
315
|
+
for (const col of sortColumns(cols)) {
|
|
169
316
|
if (col.children && col.children.length > 0) traverse(col.children)
|
|
170
317
|
else count++
|
|
171
318
|
}
|
|
@@ -177,9 +324,8 @@ function countLeafColumns(columns) {
|
|
|
177
324
|
// Извлечение плоского списка конечных колонок
|
|
178
325
|
function extractLeafColumns(columns) {
|
|
179
326
|
const result = []
|
|
180
|
-
|
|
181
327
|
function traverse(cols) {
|
|
182
|
-
for (const col of cols) {
|
|
328
|
+
for (const col of sortColumns(cols)) {
|
|
183
329
|
if (col.children && col.children.length > 0) traverse(col.children)
|
|
184
330
|
else result.push(col)
|
|
185
331
|
}
|
|
@@ -190,17 +336,21 @@ function extractLeafColumns(columns) {
|
|
|
190
336
|
|
|
191
337
|
// Обработка данных для tbody с поддержкой colspan
|
|
192
338
|
function processBodyData(data, columns) {
|
|
339
|
+
|
|
193
340
|
// Защита от undefined/null значений
|
|
194
341
|
if (!data || !Array.isArray(data)) return []
|
|
195
342
|
if (!columns || !Array.isArray(columns)) return []
|
|
343
|
+
|
|
196
344
|
// Функция для получения значения по пути (например, "gant.0")
|
|
197
345
|
function getValueByPath(obj, path) {
|
|
198
346
|
if (!obj || !path) return undefined
|
|
347
|
+
|
|
199
348
|
// Разбиваем путь по точкам
|
|
200
349
|
const keys = path.split('.')
|
|
201
350
|
let value = obj
|
|
202
351
|
for (const key of keys) {
|
|
203
352
|
if (value === null || value === undefined) return undefined
|
|
353
|
+
|
|
204
354
|
// Пробуем обратиться как к свойству объекта или элементу массива
|
|
205
355
|
value = value[key]
|
|
206
356
|
}
|
|
@@ -211,9 +361,11 @@ function processBodyData(data, columns) {
|
|
|
211
361
|
data.forEach((row, rowIndex) => {
|
|
212
362
|
const processedRow = []
|
|
213
363
|
columns.forEach((column, colIndex) => {
|
|
214
|
-
const key = `${rowIndex}-${
|
|
364
|
+
const key = `${rowIndex}-${colIndex}`
|
|
365
|
+
|
|
215
366
|
// Проверяем, не занята ли эта ячейка из-за rowspan/colspan
|
|
216
367
|
if (cellMap.has(key)) return
|
|
368
|
+
|
|
217
369
|
// Получаем значение по пути (поддержка "gant.0", "gant.1" и т.д.)
|
|
218
370
|
const cellData = getValueByPath(row, column.prop)
|
|
219
371
|
let cellValue = cellData
|
|
@@ -228,10 +380,16 @@ function processBodyData(data, columns) {
|
|
|
228
380
|
rowspan = cellData.rowspan || 1
|
|
229
381
|
style = cellData.style || {}
|
|
230
382
|
}
|
|
383
|
+
style = {
|
|
384
|
+
...style,
|
|
385
|
+
...getColumnWidthStyle(column.prop, column.width)
|
|
386
|
+
}
|
|
231
387
|
const cell = {
|
|
232
388
|
key,
|
|
233
389
|
label: cellValue,
|
|
234
390
|
prop: column.prop,
|
|
391
|
+
fixed: column.fixed,
|
|
392
|
+
colIndex,
|
|
235
393
|
colspan,
|
|
236
394
|
rowspan,
|
|
237
395
|
style,
|
|
@@ -243,46 +401,72 @@ function processBodyData(data, columns) {
|
|
|
243
401
|
for (let r = 0; r < rowspan; r++) {
|
|
244
402
|
for (let c = 0; c < colspan; c++) {
|
|
245
403
|
if (r === 0 && c === 0) continue // Основная ячейка
|
|
246
|
-
const cellKey = `${rowIndex + r}-${
|
|
247
|
-
|
|
404
|
+
const cellKey = `${rowIndex + r}-${colIndex + c}`
|
|
405
|
+
cellMap.set(cellKey, true)
|
|
248
406
|
}
|
|
249
407
|
}
|
|
250
408
|
})
|
|
251
|
-
|
|
252
409
|
if (processedRow.length > 0) {
|
|
253
410
|
processedRows.push(processedRow)
|
|
254
411
|
}
|
|
255
412
|
})
|
|
256
|
-
|
|
257
413
|
return processedRows
|
|
258
414
|
}
|
|
259
415
|
|
|
260
416
|
// Вычисляемые свойства
|
|
261
|
-
const maxDepth = computed(() => getMaxDepth(columnTree
|
|
262
|
-
const headerRows = computed(() => buildHeaderRows(columnTree
|
|
263
|
-
const flatColumns = computed(() => extractLeafColumns(columnTree
|
|
417
|
+
const maxDepth = computed(() => getMaxDepth(columnTree))
|
|
418
|
+
const headerRows = computed(() => buildHeaderRows(columnTree, maxDepth.value))
|
|
419
|
+
const flatColumns = computed(() => extractLeafColumns(columnTree))
|
|
264
420
|
const processedTableData = computed(() => processBodyData(props.data, flatColumns.value))
|
|
265
|
-
|
|
266
|
-
|
|
421
|
+
const hasFixedColumns = computed(() => flatColumns.value.some((col) => col.fixed))
|
|
422
|
+
const wrapperStyle = computed(() => {
|
|
423
|
+
if (props.maxWidth == null) return undefined
|
|
424
|
+
const width = typeof props.maxWidth === 'number'
|
|
425
|
+
? `${props.maxWidth}px`
|
|
426
|
+
: props.maxWidth
|
|
427
|
+
return { maxWidth: width }
|
|
428
|
+
})
|
|
429
|
+
const isFixedActive = computed(() => (
|
|
430
|
+
props.maxWidth != null && hasFixedColumns.value && isOverflowing.value
|
|
431
|
+
))
|
|
432
|
+
function updateOverflowState() {
|
|
433
|
+
if (!isClient) {
|
|
434
|
+
isOverflowing.value = false
|
|
435
|
+
return
|
|
436
|
+
}
|
|
437
|
+
const wrapper = wrapperRef.value
|
|
438
|
+
const table = tableRef.value
|
|
439
|
+
if (!wrapper || !table) {
|
|
440
|
+
isOverflowing.value = false
|
|
441
|
+
return
|
|
442
|
+
}
|
|
443
|
+
isOverflowing.value = table.scrollWidth > wrapper.clientWidth + 1
|
|
444
|
+
}
|
|
445
|
+
function initColumnWidths() {
|
|
446
|
+
const widths = {}
|
|
447
|
+
for (const column of flatColumns.value) {
|
|
448
|
+
const width = parseWidth(column.width)
|
|
449
|
+
if (column.prop && width != null) widths[column.prop] = width
|
|
450
|
+
}
|
|
451
|
+
columnWidths.value = widths
|
|
452
|
+
}
|
|
453
|
+
watchEffect(() => {
|
|
454
|
+
flatColumns.value
|
|
455
|
+
initColumnWidths()
|
|
456
|
+
})
|
|
457
|
+
watch(
|
|
458
|
+
[() => props.data, () => props.maxWidth, flatColumns, columnWidths, processedTableData],
|
|
459
|
+
() => nextTick(updateOverflowState)
|
|
460
|
+
)
|
|
267
461
|
onMounted(() => {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
console.log('Is component:', isComponent)
|
|
278
|
-
return isComponent
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
console.log('Valid VNodes:', validVNodes)
|
|
282
|
-
columnTree.value = buildColumnTree(validVNodes)
|
|
283
|
-
console.log('SwTable mounted - columnTree:', columnTree.value)
|
|
284
|
-
console.log('SwTable mounted - flatColumns:', flatColumns.value)
|
|
285
|
-
console.log('SwTable mounted - data:', props.data)
|
|
462
|
+
updateOverflowState()
|
|
463
|
+
if (!isClient || typeof ResizeObserver === 'undefined') return
|
|
464
|
+
resizeObserver = new ResizeObserver(() => updateOverflowState())
|
|
465
|
+
if (wrapperRef.value) resizeObserver.observe(wrapperRef.value)
|
|
466
|
+
if (tableRef.value) resizeObserver.observe(tableRef.value)
|
|
467
|
+
})
|
|
468
|
+
onUnmounted(() => {
|
|
469
|
+
resizeObserver?.disconnect()
|
|
470
|
+
resizeObserver = null
|
|
286
471
|
})
|
|
287
|
-
|
|
288
472
|
</script>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div style="display: none;"
|
|
2
|
+
<div style="display: none;"><slot /></div>
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
5
|
<script setup>
|
|
6
|
-
import {
|
|
6
|
+
import { inject, provide, onUnmounted, reactive, watch } from 'vue'
|
|
7
7
|
|
|
8
8
|
const props = defineProps({
|
|
9
9
|
prop: {
|
|
10
10
|
type: String,
|
|
11
|
-
|
|
11
|
+
default: undefined
|
|
12
12
|
},
|
|
13
13
|
label: {
|
|
14
14
|
type: String,
|
|
@@ -21,6 +21,68 @@ const props = defineProps({
|
|
|
21
21
|
height: {
|
|
22
22
|
type: [String, Number],
|
|
23
23
|
default: 'auto'
|
|
24
|
+
},
|
|
25
|
+
resizable: {
|
|
26
|
+
type: Boolean,
|
|
27
|
+
default: undefined
|
|
28
|
+
},
|
|
29
|
+
minWidth: {
|
|
30
|
+
type: [String, Number],
|
|
31
|
+
default: 48
|
|
32
|
+
},
|
|
33
|
+
fixed: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: undefined,
|
|
36
|
+
validator: (value) => value == null || value === 'left' || value === 'right'
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const tableColumns = inject('swTableColumns', null)
|
|
41
|
+
const parentColumn = inject('swTableParentColumn', null)
|
|
42
|
+
const allocColumnOrder = inject('swTableAllocColumnOrder', null)
|
|
43
|
+
|
|
44
|
+
const column = reactive({
|
|
45
|
+
order: allocColumnOrder?.() ?? 0,
|
|
46
|
+
prop: props.prop,
|
|
47
|
+
label: props.label,
|
|
48
|
+
width: props.width,
|
|
49
|
+
height: props.height,
|
|
50
|
+
resizable: props.resizable,
|
|
51
|
+
minWidth: props.minWidth,
|
|
52
|
+
fixed: props.fixed,
|
|
53
|
+
children: []
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
watch(() => props, (nextProps) => {
|
|
57
|
+
column.prop = nextProps.prop
|
|
58
|
+
column.label = nextProps.label
|
|
59
|
+
column.width = nextProps.width
|
|
60
|
+
column.height = nextProps.height
|
|
61
|
+
column.resizable = nextProps.resizable
|
|
62
|
+
column.minWidth = nextProps.minWidth
|
|
63
|
+
column.fixed = nextProps.fixed
|
|
64
|
+
}, { deep: true })
|
|
65
|
+
|
|
66
|
+
provide('swTableParentColumn', column)
|
|
67
|
+
|
|
68
|
+
function registerColumn() {
|
|
69
|
+
if (parentColumn) {
|
|
70
|
+
parentColumn.children.push(column)
|
|
71
|
+
} else if (tableColumns) {
|
|
72
|
+
tableColumns.push(column)
|
|
24
73
|
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function unregisterColumn() {
|
|
77
|
+
const list = parentColumn ? parentColumn.children : tableColumns
|
|
78
|
+
if (!list) return
|
|
79
|
+
const index = list.indexOf(column)
|
|
80
|
+
if (index > -1) list.splice(index, 1)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
registerColumn()
|
|
84
|
+
|
|
85
|
+
onUnmounted(() => {
|
|
86
|
+
unregisterColumn()
|
|
25
87
|
})
|
|
26
88
|
</script>
|