@veltra/sheet 2.2.0 → 2.3.0
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/dist/components/report/binding-badge.js +45 -21
- package/dist/components/report/designer/col-widths.js +18 -4
- package/dist/components/report/designer/conditional-rules/helpers.js +3 -3
- package/dist/components/report/designer/float-panel.js +73 -36
- package/dist/components/report/designer/rule-row.js +111 -127
- package/dist/components/report/designer/rules-dialog.js +5 -22
- package/dist/components/report/preview-grid-size.js +32 -0
- package/dist/components/report/report-designer.js +75 -25
- package/dist/components/report/report-viewer.js +38 -25
- package/dist/components/report/style2.css +44 -39
- package/dist/components/report/use-report-designer.js +34 -12
- package/dist/components/report/use-report-viewer.js +6 -1
- package/dist/components/sheet/popups/border-popup.js +30 -2
- package/dist/components/sheet/popups/fill-color-popup.js +2 -2
- package/dist/components/sheet/popups/font-color-popup.js +2 -2
- package/dist/components/sheet/popups/font-size-popup.js +2 -2
- package/dist/components/sheet/sheet-context-menu.js +6 -3
- package/dist/components/sheet/sheet.js +8 -0
- package/dist/components/sheet/use-sheet-grid.js +14 -4
- package/dist/components/sheet/use-sheet-state.js +3 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/report/binding.d.ts +49 -11
- package/dist/report/binding.js +186 -29
- package/dist/report/connector.js +1 -1
- package/dist/report/export-xlsx.d.ts +3 -5
- package/dist/report/export-xlsx.js +7 -10
- package/dist/report/index.d.ts +2 -2
- package/dist/report/render/aggregate.js +20 -8
- package/dist/report/template.d.ts +8 -4
- package/dist/report/template.js +5 -2
- package/dist/tools/apply-style.js +29 -0
- package/dist/tools/builtin.js +17 -17
- package/dist/tools/context.d.ts +38 -5
- package/dist/tools/context.js +38 -1
- package/dist/types/report-designer.d.ts +2 -0
- package/dist/types/report-viewer.d.ts +2 -2
- package/dist/types/sheet.d.ts +4 -0
- package/package.json +6 -6
- package/src/components/report/__test__/conditional-rules-helpers.test.ts +1 -0
- package/src/components/report/__test__/preview-grid-size.test.ts +63 -0
- package/src/components/report/__test__/report-designer.test.ts +76 -0
- package/src/components/report/__test__/report-viewer.test.ts +24 -1
- package/src/components/report/__test__/use-report-designer.test.ts +142 -10
- package/src/components/report/binding-badge.ts +39 -20
- package/src/components/report/designer/col-widths.ts +34 -11
- package/src/components/report/designer/conditional-rules/helpers.ts +6 -3
- package/src/components/report/designer/float-panel.vue +71 -27
- package/src/components/report/designer/rule-row.vue +67 -67
- package/src/components/report/designer/rules-dialog.vue +3 -19
- package/src/components/report/preview-grid-size.ts +33 -0
- package/src/components/report/report-designer.vue +79 -15
- package/src/components/report/report-viewer.vue +37 -29
- package/src/components/report/style-designer.scss +45 -41
- package/src/components/report/use-report-designer.ts +63 -14
- package/src/components/report/use-report-viewer.ts +11 -2
- package/src/components/sheet/__test__/sheet-component.test.ts +37 -0
- package/src/components/sheet/__test__/sheet-context-menu.test.ts +3 -2
- package/src/components/sheet/popups/border-popup.vue +31 -3
- package/src/components/sheet/popups/fill-color-popup.vue +2 -2
- package/src/components/sheet/popups/font-color-popup.vue +2 -2
- package/src/components/sheet/popups/font-size-popup.vue +2 -2
- package/src/components/sheet/sheet-context-menu.ts +8 -2
- package/src/components/sheet/sheet.vue +2 -0
- package/src/components/sheet/use-sheet-grid.ts +17 -3
- package/src/components/sheet/use-sheet-state.ts +8 -3
- package/src/report/__test__/binding.test.ts +103 -1
- package/src/report/__test__/connector.test.ts +21 -0
- package/src/report/__test__/export-xlsx.test.ts +4 -4
- package/src/report/__test__/render.test.ts +102 -0
- package/src/report/__test__/template.test.ts +11 -0
- package/src/report/binding.ts +278 -47
- package/src/report/connector.ts +3 -2
- package/src/report/export-xlsx.ts +9 -14
- package/src/report/render/aggregate.ts +29 -8
- package/src/report/template.ts +8 -4
- package/src/tools/__test__/apply-style.test.ts +129 -0
- package/src/tools/apply-style.ts +41 -0
- package/src/tools/builtin.ts +18 -18
- package/src/tools/context.ts +67 -4
- package/src/tools/index.ts +1 -0
- package/src/types/report-designer.ts +2 -0
- package/src/types/report-viewer.ts +2 -2
- package/src/types/sheet.ts +4 -0
|
@@ -62,6 +62,22 @@
|
|
|
62
62
|
min-width: 0;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
@include m.e(pick-hint) {
|
|
66
|
+
position: absolute;
|
|
67
|
+
left: 50%;
|
|
68
|
+
bottom: fn.use-var(gap, small);
|
|
69
|
+
z-index: 17;
|
|
70
|
+
transform: translateX(-50%);
|
|
71
|
+
padding: 4px fn.use-var(gap, small);
|
|
72
|
+
border-radius: fn.use-var(radius, default);
|
|
73
|
+
background: color-mix(in srgb, fn.use-var(bg-color, top) 92%, transparent);
|
|
74
|
+
border: 1px dashed fn.use-var(color, primary);
|
|
75
|
+
font-size: fn.use-var(font-size-main, small);
|
|
76
|
+
color: fn.use-var(text-color, second);
|
|
77
|
+
pointer-events: none;
|
|
78
|
+
white-space: nowrap;
|
|
79
|
+
}
|
|
80
|
+
|
|
65
81
|
/* 数据中枢 drawer 面板宽度(class 经 $attrs 落到 .u-drawer 面板;须压过 .u-drawer.is-right 的 320px) */
|
|
66
82
|
@include m.e(hub-drawer) {
|
|
67
83
|
&.u-drawer {
|
|
@@ -123,7 +139,7 @@
|
|
|
123
139
|
}
|
|
124
140
|
}
|
|
125
141
|
|
|
126
|
-
/* Action Pill
|
|
142
|
+
/* Action Pill:选中绑定格上方的悬浮编辑卡(预设 + 互补的展开/聚合 / 条件样式 / 排序 / 清除绑定) */
|
|
127
143
|
@include m.b(report-float-panel) {
|
|
128
144
|
position: absolute;
|
|
129
145
|
z-index: 18;
|
|
@@ -223,7 +239,7 @@
|
|
|
223
239
|
}
|
|
224
240
|
|
|
225
241
|
@include m.e(parent-select) {
|
|
226
|
-
width:
|
|
242
|
+
width: 180px;
|
|
227
243
|
flex: none;
|
|
228
244
|
}
|
|
229
245
|
|
|
@@ -244,11 +260,12 @@
|
|
|
244
260
|
@include m.b(report-rules-dialog) {
|
|
245
261
|
display: flex;
|
|
246
262
|
flex-direction: column;
|
|
247
|
-
gap:
|
|
263
|
+
gap: 10px;
|
|
248
264
|
|
|
249
265
|
@include m.e(hint) {
|
|
250
266
|
margin: 0;
|
|
251
267
|
font-size: 12px;
|
|
268
|
+
line-height: 1.5;
|
|
252
269
|
color: fn.use-var(text-color, second);
|
|
253
270
|
}
|
|
254
271
|
|
|
@@ -278,17 +295,18 @@
|
|
|
278
295
|
display: flex;
|
|
279
296
|
align-items: flex-start;
|
|
280
297
|
gap: 8px;
|
|
281
|
-
padding: 10px;
|
|
298
|
+
padding: 10px 10px 10px 6px;
|
|
282
299
|
border: fn.use-var(border-muted);
|
|
283
300
|
border-radius: 8px;
|
|
284
|
-
background: fn.use-var(bg-color,
|
|
301
|
+
background: fn.use-var(bg-color, middle);
|
|
285
302
|
|
|
286
303
|
@include m.e(handle) {
|
|
287
304
|
flex: none;
|
|
288
|
-
width:
|
|
289
|
-
margin-top:
|
|
290
|
-
color: fn.use-var(text-color,
|
|
291
|
-
font-size:
|
|
305
|
+
width: 16px;
|
|
306
|
+
margin-top: 6px;
|
|
307
|
+
color: fn.use-var(text-color, placeholder);
|
|
308
|
+
font-size: 12px;
|
|
309
|
+
letter-spacing: -1px;
|
|
292
310
|
line-height: 1;
|
|
293
311
|
cursor: grab;
|
|
294
312
|
user-select: none;
|
|
@@ -307,32 +325,33 @@
|
|
|
307
325
|
gap: 8px;
|
|
308
326
|
}
|
|
309
327
|
|
|
310
|
-
@include m.e(
|
|
328
|
+
@include m.e(when) {
|
|
311
329
|
display: flex;
|
|
312
330
|
align-items: center;
|
|
313
|
-
gap:
|
|
331
|
+
gap: 6px;
|
|
314
332
|
flex-wrap: wrap;
|
|
315
333
|
}
|
|
316
334
|
|
|
317
|
-
@include m.e(
|
|
318
|
-
|
|
319
|
-
|
|
335
|
+
@include m.e(then) {
|
|
336
|
+
display: flex;
|
|
337
|
+
align-items: center;
|
|
338
|
+
gap: 6px;
|
|
339
|
+
flex-wrap: wrap;
|
|
320
340
|
}
|
|
321
341
|
|
|
322
|
-
@include m.e(
|
|
323
|
-
width: 72px;
|
|
342
|
+
@include m.e(lead) {
|
|
324
343
|
flex: none;
|
|
344
|
+
font-size: 12px;
|
|
345
|
+
color: fn.use-var(text-color, second);
|
|
325
346
|
}
|
|
326
347
|
|
|
327
|
-
@include m.e(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
align-items: center;
|
|
331
|
-
gap: 8px;
|
|
348
|
+
@include m.e(field-select) {
|
|
349
|
+
width: 128px;
|
|
350
|
+
flex: none;
|
|
332
351
|
}
|
|
333
352
|
|
|
334
353
|
@include m.e(operator) {
|
|
335
|
-
width:
|
|
354
|
+
width: 96px;
|
|
336
355
|
flex: none;
|
|
337
356
|
}
|
|
338
357
|
|
|
@@ -345,12 +364,12 @@
|
|
|
345
364
|
}
|
|
346
365
|
|
|
347
366
|
@include m.e(scalar-input) {
|
|
348
|
-
width:
|
|
367
|
+
width: 120px;
|
|
349
368
|
flex: none;
|
|
350
369
|
}
|
|
351
370
|
|
|
352
371
|
@include m.e(between-input) {
|
|
353
|
-
width:
|
|
372
|
+
width: 88px;
|
|
354
373
|
flex: none;
|
|
355
374
|
}
|
|
356
375
|
|
|
@@ -359,18 +378,6 @@
|
|
|
359
378
|
color: fn.use-var(text-color, second);
|
|
360
379
|
}
|
|
361
380
|
|
|
362
|
-
@include m.e(style) {
|
|
363
|
-
display: flex;
|
|
364
|
-
align-items: center;
|
|
365
|
-
gap: 8px;
|
|
366
|
-
flex-wrap: wrap;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
@include m.e(style-label) {
|
|
370
|
-
font-size: 12px;
|
|
371
|
-
color: fn.use-var(text-color, second);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
381
|
@include m.e(toggle) {
|
|
375
382
|
min-width: 28px;
|
|
376
383
|
padding-inline: 6px;
|
|
@@ -378,14 +385,11 @@
|
|
|
378
385
|
|
|
379
386
|
@include m.e(preview) {
|
|
380
387
|
flex: none;
|
|
381
|
-
margin-top: 2px;
|
|
382
388
|
}
|
|
383
389
|
|
|
384
|
-
@include m.e(
|
|
390
|
+
@include m.e(remove) {
|
|
385
391
|
flex: none;
|
|
386
|
-
|
|
387
|
-
flex-direction: column;
|
|
388
|
-
gap: 2px;
|
|
392
|
+
margin-top: 2px;
|
|
389
393
|
}
|
|
390
394
|
}
|
|
391
395
|
|
|
@@ -8,13 +8,16 @@ import {
|
|
|
8
8
|
applyReportPreset,
|
|
9
9
|
createReportBinding,
|
|
10
10
|
formatCellAddress,
|
|
11
|
-
inferColParentCandidate,
|
|
12
11
|
inferDropPreset,
|
|
13
|
-
|
|
12
|
+
inferParentsForPreset,
|
|
13
|
+
isColGroupBinding,
|
|
14
14
|
isHorizontalExpandBinding,
|
|
15
|
+
isRowGroupBinding,
|
|
15
16
|
isVerticalExpandBinding,
|
|
16
17
|
listColParentCandidates,
|
|
17
|
-
listRowParentCandidates
|
|
18
|
+
listRowParentCandidates,
|
|
19
|
+
prefersGroupParent,
|
|
20
|
+
presetStillApplies
|
|
18
21
|
} from '../../report/binding'
|
|
19
22
|
import type { DataConnection, QueryResult, Result } from '../../report/connector'
|
|
20
23
|
import { buildParamDefs } from '../../report/params'
|
|
@@ -227,10 +230,9 @@ export function useReportDesigner(options: UseReportDesignerOptions): UseReportD
|
|
|
227
230
|
let binding = createReportBinding(dataset, fieldName)
|
|
228
231
|
binding = applyReportPreset(binding, preset)
|
|
229
232
|
|
|
230
|
-
const
|
|
231
|
-
if (rowParent) binding.rowParent = rowParent
|
|
232
|
-
|
|
233
|
-
if (colParent) binding.colParent = colParent
|
|
233
|
+
const parents = inferParentsForPreset(target, preset, getBindingAt)
|
|
234
|
+
if (parents.rowParent) binding.rowParent = parents.rowParent
|
|
235
|
+
if (parents.colParent) binding.colParent = parents.colParent
|
|
234
236
|
|
|
235
237
|
workbook.value.activeSheet.setCellMeta(target, REPORT_META_NAMESPACE, binding)
|
|
236
238
|
}
|
|
@@ -276,14 +278,18 @@ export function useReportDesigner(options: UseReportDesignerOptions): UseReportD
|
|
|
276
278
|
metaTick.value
|
|
277
279
|
const binding = activeBinding.value
|
|
278
280
|
if (!binding) return []
|
|
279
|
-
return listRowParentCandidates(binding, bindingEntries.value
|
|
281
|
+
return listRowParentCandidates(binding, bindingEntries.value, {
|
|
282
|
+
preferGroup: prefersGroupParent(binding)
|
|
283
|
+
})
|
|
280
284
|
})
|
|
281
285
|
|
|
282
286
|
const colParentCandidates = computed((): CellAddress[] => {
|
|
283
287
|
metaTick.value
|
|
284
288
|
const binding = activeBinding.value
|
|
285
289
|
if (!binding) return []
|
|
286
|
-
return listColParentCandidates(binding, bindingEntries.value
|
|
290
|
+
return listColParentCandidates(binding, bindingEntries.value, {
|
|
291
|
+
preferGroup: prefersGroupParent(binding)
|
|
292
|
+
})
|
|
287
293
|
})
|
|
288
294
|
|
|
289
295
|
const bindingEntries = computed((): TopologyBindingEntry[] => {
|
|
@@ -296,6 +302,12 @@ export function useReportDesigner(options: UseReportDesignerOptions): UseReportD
|
|
|
296
302
|
return entries
|
|
297
303
|
})
|
|
298
304
|
|
|
305
|
+
function parentNeedsCorrection(addr: CellAddress | undefined): boolean {
|
|
306
|
+
if (!addr) return true
|
|
307
|
+
const parent = getBindingAt(addr)
|
|
308
|
+
return !parent || parent.aggregate === 'list'
|
|
309
|
+
}
|
|
310
|
+
|
|
299
311
|
function patchActiveBinding(patch: Partial<ReportBinding>): void {
|
|
300
312
|
const cell = activeCell.value
|
|
301
313
|
const binding = activeBinding.value
|
|
@@ -304,7 +316,25 @@ export function useReportDesigner(options: UseReportDesignerOptions): UseReportD
|
|
|
304
316
|
const next: ReportBinding = { ...binding, ...patch }
|
|
305
317
|
if ('rowParent' in patch && patch.rowParent === undefined) delete next.rowParent
|
|
306
318
|
if ('colParent' in patch && patch.colParent === undefined) delete next.colParent
|
|
307
|
-
if (
|
|
319
|
+
if (
|
|
320
|
+
('aggregate' in patch || 'expand' in patch) &&
|
|
321
|
+
!('preset' in patch) &&
|
|
322
|
+
!presetStillApplies(next)
|
|
323
|
+
) {
|
|
324
|
+
delete next.preset
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (patch.preset === 'subtotal') {
|
|
328
|
+
const inferred = inferParentsForPreset(cell, 'subtotal', getBindingAt)
|
|
329
|
+
if (parentNeedsCorrection(next.rowParent)) {
|
|
330
|
+
if (inferred.rowParent) next.rowParent = inferred.rowParent
|
|
331
|
+
else delete next.rowParent
|
|
332
|
+
}
|
|
333
|
+
if (parentNeedsCorrection(next.colParent)) {
|
|
334
|
+
if (inferred.colParent) next.colParent = inferred.colParent
|
|
335
|
+
else delete next.colParent
|
|
336
|
+
}
|
|
337
|
+
}
|
|
308
338
|
|
|
309
339
|
workbook.value.activeSheet.setCellMeta(cell, REPORT_META_NAMESPACE, next)
|
|
310
340
|
}
|
|
@@ -319,17 +349,36 @@ export function useReportDesigner(options: UseReportDesignerOptions): UseReportD
|
|
|
319
349
|
parentPick.value = null
|
|
320
350
|
}
|
|
321
351
|
|
|
352
|
+
function isValidParentTarget(
|
|
353
|
+
mode: ParentPickMode,
|
|
354
|
+
source: ReportBinding,
|
|
355
|
+
target: ReportBinding | undefined
|
|
356
|
+
): boolean {
|
|
357
|
+
if (!target) return false
|
|
358
|
+
if (target.dataset !== source.dataset) return false
|
|
359
|
+
const groupOnly = prefersGroupParent(source)
|
|
360
|
+
if (mode === 'row') {
|
|
361
|
+
return groupOnly ? isRowGroupBinding(target) : isVerticalExpandBinding(target)
|
|
362
|
+
}
|
|
363
|
+
return groupOnly ? isColGroupBinding(target) : isHorizontalExpandBinding(target)
|
|
364
|
+
}
|
|
365
|
+
|
|
322
366
|
function pickParentAt(target: CellAddress): void {
|
|
323
367
|
const pick = parentPick.value
|
|
324
368
|
if (!pick) return
|
|
325
369
|
if (target.row === pick.source.row && target.col === pick.source.col) return
|
|
326
370
|
|
|
327
371
|
const sourceBinding = getBindingAt(pick.source)
|
|
372
|
+
if (!sourceBinding) {
|
|
373
|
+
parentPick.value = null
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
|
|
328
377
|
const targetBinding = getBindingAt(target)
|
|
329
|
-
if (!sourceBinding
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
378
|
+
if (!isValidParentTarget(pick.mode, sourceBinding, targetBinding)) {
|
|
379
|
+
workbook.value.activeSheet.selectCell(pick.source)
|
|
380
|
+
return
|
|
381
|
+
}
|
|
333
382
|
|
|
334
383
|
const patch = pick.mode === 'row' ? { rowParent: { ...target } } : { colParent: { ...target } }
|
|
335
384
|
const next: ReportBinding = { ...sourceBinding, ...patch }
|
|
@@ -2,7 +2,8 @@ import type { SheetSnapshot } from '@veltra/sheet-core'
|
|
|
2
2
|
import { computed, ref, shallowRef, watch, type ComputedRef, type Ref, type ShallowRef } from 'vue'
|
|
3
3
|
|
|
4
4
|
import type { ConnectorError } from '../../report/connector'
|
|
5
|
-
import {
|
|
5
|
+
import type { ReportColWidthEntry } from '../../report/export-xlsx'
|
|
6
|
+
import { renderReport, resolveFilledColWidths } from '../../report/render'
|
|
6
7
|
import {
|
|
7
8
|
fetchTemplateRecords,
|
|
8
9
|
resolveParamDefaults,
|
|
@@ -22,6 +23,8 @@ export interface UseReportViewerReturn {
|
|
|
22
23
|
error: ShallowRef<ConnectorError | null>
|
|
23
24
|
/** 最近一次成功展开渲染的 Filled Report 快照(组件壳据此替换网格内容) */
|
|
24
25
|
filledSnapshot: ShallowRef<SheetSnapshot | null>
|
|
26
|
+
/** 展开后物理列宽(由模板 colWidths 映射;无模板列宽时为 undefined) */
|
|
27
|
+
filledColWidths: ShallowRef<ReadonlyArray<ReportColWidthEntry> | undefined>
|
|
25
28
|
/** 重新取数并展开渲染 */
|
|
26
29
|
refresh: () => Promise<void>
|
|
27
30
|
/** Filter Bar 改值:写入新参数值并重新取数 */
|
|
@@ -36,6 +39,7 @@ export function useReportViewer(props: ReportViewerProps): UseReportViewerReturn
|
|
|
36
39
|
const loading = ref(false)
|
|
37
40
|
const error = shallowRef<ConnectorError | null>(null)
|
|
38
41
|
const filledSnapshot = shallowRef<SheetSnapshot | null>(null)
|
|
42
|
+
const filledColWidths = shallowRef<ReadonlyArray<ReportColWidthEntry> | undefined>(undefined)
|
|
39
43
|
|
|
40
44
|
const params = computed(() => resolveTemplateParams(props.template))
|
|
41
45
|
// 初始值由下方 immediate watch 播种(参数默认值)
|
|
@@ -54,9 +58,13 @@ export function useReportViewer(props: ReportViewerProps): UseReportViewerReturn
|
|
|
54
58
|
loading.value = false
|
|
55
59
|
if (!result.ok) {
|
|
56
60
|
error.value = result.error
|
|
61
|
+
filledColWidths.value = undefined
|
|
57
62
|
return
|
|
58
63
|
}
|
|
59
64
|
filledSnapshot.value = renderReport(template, result.data)
|
|
65
|
+
filledColWidths.value = template.colWidths?.length
|
|
66
|
+
? resolveFilledColWidths(template, result.data, template.colWidths)
|
|
67
|
+
: undefined
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
function setValues(next: ParamValues): void {
|
|
@@ -69,11 +77,12 @@ export function useReportViewer(props: ReportViewerProps): UseReportViewerReturn
|
|
|
69
77
|
() => props.template,
|
|
70
78
|
() => {
|
|
71
79
|
filledSnapshot.value = null
|
|
80
|
+
filledColWidths.value = undefined
|
|
72
81
|
values.value = resolveParamDefaults(params.value)
|
|
73
82
|
void refresh()
|
|
74
83
|
},
|
|
75
84
|
{ immediate: true }
|
|
76
85
|
)
|
|
77
86
|
|
|
78
|
-
return { params, values, loading, error, filledSnapshot, refresh, setValues }
|
|
87
|
+
return { params, values, loading, error, filledSnapshot, filledColWidths, refresh, setValues }
|
|
79
88
|
}
|
|
@@ -258,6 +258,43 @@ describe('USheet 组件', () => {
|
|
|
258
258
|
expect(gridAfter).not.toBe(gridBefore)
|
|
259
259
|
})
|
|
260
260
|
|
|
261
|
+
it('showRowHeader/showColHeader 为 false 时隐藏行号与列头', async () => {
|
|
262
|
+
const workbook = createWorkbook()
|
|
263
|
+
const exposed: { value: SheetExposed | undefined } = { value: undefined }
|
|
264
|
+
mount(
|
|
265
|
+
() => ({ workbook, rows: 8, cols: 4, showRowHeader: false, showColHeader: false }),
|
|
266
|
+
exposed
|
|
267
|
+
)
|
|
268
|
+
await nextTick()
|
|
269
|
+
|
|
270
|
+
workbook.activeSheet.setCellValue({ row: 0, col: 0 }, 'A1')
|
|
271
|
+
await nextTick()
|
|
272
|
+
const table = exposed.value!.getGrid()!.getTable()
|
|
273
|
+
expect(table.options.showHeader).toBe(false)
|
|
274
|
+
expect(table.options.rowSeriesNumber).toBeUndefined()
|
|
275
|
+
expect(table.columnHeaderLevelCount).toBe(0)
|
|
276
|
+
expect(table.rowCount).toBe(8)
|
|
277
|
+
expect(table.getCellValue(0, 0)).toBe('A1')
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('删除行后渲染行数跟随模型收缩(不被 props.rows 撑回)', async () => {
|
|
281
|
+
const workbook = createWorkbook()
|
|
282
|
+
const exposed: { value: SheetExposed | undefined } = { value: undefined }
|
|
283
|
+
mount(() => ({ workbook, rows: 10, cols: 6 }), exposed)
|
|
284
|
+
await nextTick()
|
|
285
|
+
|
|
286
|
+
const sheet = exposed.value!.getActiveSheet()
|
|
287
|
+
expect(sheet.rows).toBe(10)
|
|
288
|
+
expect(exposed.value!.getGrid()!.getTable().rowCount).toBe(11) // 列头 + 10
|
|
289
|
+
|
|
290
|
+
sheet.deleteRows(0, 2)
|
|
291
|
+
await nextTick()
|
|
292
|
+
await nextTick()
|
|
293
|
+
|
|
294
|
+
expect(sheet.rows).toBe(8)
|
|
295
|
+
expect(exposed.value!.getGrid()!.getTable().rowCount).toBe(9) // 列头 + 8
|
|
296
|
+
})
|
|
297
|
+
|
|
261
298
|
it('resolveCellStyle prop 传入 SheetGrid 视口渲染', async () => {
|
|
262
299
|
const workbook = createWorkbook()
|
|
263
300
|
const sheet = workbook.activeSheet
|
|
@@ -36,8 +36,9 @@ describe('sheet-context-menu helpers', () => {
|
|
|
36
36
|
expect(isColInSelection(ranges, 6)).toBe(false)
|
|
37
37
|
})
|
|
38
38
|
|
|
39
|
-
it('resolveRenderSize
|
|
40
|
-
expect(resolveRenderSize(100, 26, 120, 10)).toEqual({ rows: 120, cols:
|
|
39
|
+
it('resolveRenderSize:模型已声明时以模型为准(可低于 props)', () => {
|
|
40
|
+
expect(resolveRenderSize(100, 26, 120, 10)).toEqual({ rows: 120, cols: 10 })
|
|
41
|
+
expect(resolveRenderSize(100, 26, 29, 26)).toEqual({ rows: 29, cols: 26 })
|
|
41
42
|
expect(resolveRenderSize(undefined, undefined, 0, 0)).toEqual({ rows: 100, cols: 26 })
|
|
42
43
|
})
|
|
43
44
|
})
|
|
@@ -40,7 +40,8 @@ import { buildBorderPresetItems } from '@veltra/sheet-core/core/style/border-pre
|
|
|
40
40
|
import {
|
|
41
41
|
BORDER_STYLE_WIDTH,
|
|
42
42
|
type BorderEdge,
|
|
43
|
-
type BorderLineStyle
|
|
43
|
+
type BorderLineStyle,
|
|
44
|
+
type CellStylePatch
|
|
44
45
|
} from '@veltra/sheet-core/core/style/types'
|
|
45
46
|
import { bem } from '@veltra/utils'
|
|
46
47
|
import { ref } from 'vue'
|
|
@@ -63,6 +64,7 @@ defineOptions({ name: 'USheetBorderPopup' })
|
|
|
63
64
|
* 预设补丁由 core/style/border-presets 生成(含邻居格共享边同步,对齐
|
|
64
65
|
* Excel「写入时同步、最近设置生效」);面板打开期间的写入由
|
|
65
66
|
* use-tool-popup 事务包裹为一个 undo 单元。
|
|
67
|
+
* 整行/整列选区下,均匀预设(all/none/单侧)走 applyStyle → 行列默认样式。
|
|
66
68
|
*/
|
|
67
69
|
const props = defineProps<{
|
|
68
70
|
/** 工具上下文(写入走命令系统,天然可撤销) */
|
|
@@ -75,7 +77,27 @@ const cls = bem('sheet')
|
|
|
75
77
|
const borderLineStyle = ref<BorderLineStyle>('thin')
|
|
76
78
|
const borderColor = ref('#000000')
|
|
77
79
|
|
|
78
|
-
/**
|
|
80
|
+
/** 均匀边框补丁(可写进行/列默认样式);outer/inner 几何相关 → null */
|
|
81
|
+
function uniformBorderPatch(preset: BorderPresetId, edge: BorderEdge): CellStylePatch | null {
|
|
82
|
+
switch (preset) {
|
|
83
|
+
case 'all':
|
|
84
|
+
return { border: { top: edge, right: edge, bottom: edge, left: edge } }
|
|
85
|
+
case 'none':
|
|
86
|
+
return { border: { top: null, right: null, bottom: null, left: null } }
|
|
87
|
+
case 'top':
|
|
88
|
+
return { border: { top: edge } }
|
|
89
|
+
case 'bottom':
|
|
90
|
+
return { border: { bottom: edge } }
|
|
91
|
+
case 'left':
|
|
92
|
+
return { border: { left: edge } }
|
|
93
|
+
case 'right':
|
|
94
|
+
return { border: { right: edge } }
|
|
95
|
+
default:
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 应用边框预设:整行/整列均匀预设走 applyStyle;其余逐格批量(含邻居同步) */
|
|
79
101
|
function applyBorderPreset(preset: BorderPresetId): void {
|
|
80
102
|
const range = currentRange(props.context)
|
|
81
103
|
if (!range) return
|
|
@@ -84,8 +106,14 @@ function applyBorderPreset(preset: BorderPresetId): void {
|
|
|
84
106
|
width: BORDER_STYLE_WIDTH[borderLineStyle.value],
|
|
85
107
|
color: borderColor.value
|
|
86
108
|
}
|
|
109
|
+
const target = props.context.resolveStyleTarget(range)
|
|
110
|
+
const uniform = target !== 'cell' ? uniformBorderPatch(preset, edge) : null
|
|
111
|
+
if (uniform) {
|
|
112
|
+
props.context.applyStyle(range, uniform)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
87
115
|
const items: SetCellStyleItem[] = buildBorderPresetItems(range, preset, edge, (addr) =>
|
|
88
|
-
props.context.
|
|
116
|
+
props.context.getEffectiveStyle(addr)
|
|
89
117
|
).map(({ addr, patch }) => ({ addr, partial: patch }))
|
|
90
118
|
if (items.length === 0) return
|
|
91
119
|
props.context.executeCommand('sheet.command.set-cell-style', { items })
|
|
@@ -26,7 +26,7 @@ const fillColor = ref('')
|
|
|
26
26
|
// 打开时初始化为活动格当前填充色
|
|
27
27
|
onMounted(() => {
|
|
28
28
|
const active = props.context.getSelection().activeCell
|
|
29
|
-
const style = active ? props.context.
|
|
29
|
+
const style = active ? props.context.getEffectiveStyle(active) : undefined
|
|
30
30
|
fillColor.value = style?.fill?.color ?? ''
|
|
31
31
|
})
|
|
32
32
|
|
|
@@ -34,6 +34,6 @@ onMounted(() => {
|
|
|
34
34
|
function applyFillColor(color: string): void {
|
|
35
35
|
const range = currentRange(props.context)
|
|
36
36
|
if (!range) return
|
|
37
|
-
props.context.
|
|
37
|
+
props.context.applyStyle(range, color ? { fill: { color } } : { fill: {} })
|
|
38
38
|
}
|
|
39
39
|
</script>
|
|
@@ -26,7 +26,7 @@ const fontColor = ref('')
|
|
|
26
26
|
// 打开时初始化为活动格当前字体色
|
|
27
27
|
onMounted(() => {
|
|
28
28
|
const active = props.context.getSelection().activeCell
|
|
29
|
-
const style = active ? props.context.
|
|
29
|
+
const style = active ? props.context.getEffectiveStyle(active) : undefined
|
|
30
30
|
fontColor.value = style?.font?.color ?? ''
|
|
31
31
|
})
|
|
32
32
|
|
|
@@ -34,6 +34,6 @@ onMounted(() => {
|
|
|
34
34
|
function applyFontColor(color: string): void {
|
|
35
35
|
const range = currentRange(props.context)
|
|
36
36
|
if (!range) return
|
|
37
|
-
props.context.
|
|
37
|
+
props.context.applyStyle(range, color ? { font: { color } } : { font: { color: null } })
|
|
38
38
|
}
|
|
39
39
|
</script>
|
|
@@ -39,7 +39,7 @@ const currentSize = ref<number | undefined>(undefined)
|
|
|
39
39
|
|
|
40
40
|
onMounted(() => {
|
|
41
41
|
const active = props.context.getSelection().activeCell
|
|
42
|
-
const style = active ? props.context.
|
|
42
|
+
const style = active ? props.context.getEffectiveStyle(active) : undefined
|
|
43
43
|
currentSize.value = style?.font?.size
|
|
44
44
|
})
|
|
45
45
|
|
|
@@ -47,6 +47,6 @@ function applyFontSize(size: number): void {
|
|
|
47
47
|
const range = currentRange(props.context)
|
|
48
48
|
if (!range) return
|
|
49
49
|
currentSize.value = size
|
|
50
|
-
props.context.
|
|
50
|
+
props.context.applyStyle(range, { font: { size } })
|
|
51
51
|
}
|
|
52
52
|
</script>
|
|
@@ -39,14 +39,20 @@ export function wholeColRange(col: number, rowCount: number): CellRange {
|
|
|
39
39
|
return createRange({ row: 0, col }, { row: Math.max(rowCount - 1, 0), col })
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* 渲染尺寸:模型已声明时以模型为准(删行可低于 props);未声明时回落 props。
|
|
44
|
+
* 与 SheetGrid / useSheetGrid 一致——避免 ensureTableSize(props) 把删行结果撑回。
|
|
45
|
+
*/
|
|
43
46
|
export function resolveRenderSize(
|
|
44
47
|
propsRows: number | undefined,
|
|
45
48
|
propsCols: number | undefined,
|
|
46
49
|
sheetRows: number,
|
|
47
50
|
sheetCols: number
|
|
48
51
|
): { rows: number; cols: number } {
|
|
49
|
-
return {
|
|
52
|
+
return {
|
|
53
|
+
rows: sheetRows > 0 ? sheetRows : (propsRows ?? 100),
|
|
54
|
+
cols: sheetCols > 0 ? sheetCols : (propsCols ?? 26)
|
|
55
|
+
}
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
function insertCountRender(options: {
|
|
@@ -109,12 +109,16 @@ export function useSheetGrid(options: UseSheetGridOptions) {
|
|
|
109
109
|
const el = document.createElement('div')
|
|
110
110
|
el.className = 'u-sheet__grid-instance'
|
|
111
111
|
host.appendChild(el)
|
|
112
|
+
// 模型已声明尺寸时传入模型值,避免删行后被 props 经 ensureTableSize 撑回
|
|
113
|
+
const { rows, cols } = resolveRenderSize(props.rows, props.cols, sheet.rows, sheet.cols)
|
|
112
114
|
const grid = new SheetGrid({
|
|
113
115
|
container: el,
|
|
114
116
|
sheet,
|
|
115
|
-
rows
|
|
116
|
-
cols
|
|
117
|
+
rows,
|
|
118
|
+
cols,
|
|
117
119
|
readonly: props.readonly,
|
|
120
|
+
showRowHeader: props.showRowHeader,
|
|
121
|
+
showColHeader: props.showColHeader,
|
|
118
122
|
resolveDisplayValue: props.resolveDisplayValue,
|
|
119
123
|
resolveCellStyle: props.resolveCellStyle,
|
|
120
124
|
resolveCellRenderer: props.resolveCellRenderer,
|
|
@@ -237,7 +241,17 @@ export function useSheetGrid(options: UseSheetGridOptions) {
|
|
|
237
241
|
|
|
238
242
|
onMounted(activateGrid)
|
|
239
243
|
|
|
240
|
-
|
|
244
|
+
// props 尺寸显式增大时先扩张模型,再重建(删行缩小不走这条路径)
|
|
245
|
+
watch(
|
|
246
|
+
() =>
|
|
247
|
+
[props.rows, props.cols, props.readonly, props.showRowHeader, props.showColHeader] as const,
|
|
248
|
+
([nextRows, nextCols]) => {
|
|
249
|
+
const sheet = getActiveSheet()
|
|
250
|
+
if (nextRows != null) sheet.ensureTableSize(nextRows, 0)
|
|
251
|
+
if (nextCols != null) sheet.ensureTableSize(0, nextCols)
|
|
252
|
+
rebuildGrid()
|
|
253
|
+
}
|
|
254
|
+
)
|
|
241
255
|
|
|
242
256
|
onBeforeUnmount(() => {
|
|
243
257
|
invalidateAll()
|
|
@@ -5,6 +5,7 @@ import { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vu
|
|
|
5
5
|
import { createSheetContext } from '../../tools/context'
|
|
6
6
|
import { defaultToolRegistry } from '../../tools/registry'
|
|
7
7
|
import type { SheetProps } from '../../types'
|
|
8
|
+
import { resolveRenderSize } from './sheet-context-menu'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* useSheetState 与宿主组件的协作钩子。
|
|
@@ -44,10 +45,14 @@ export function useSheetState(props: SheetProps, emit: Emit, hooks: SheetStateHo
|
|
|
44
45
|
() => sheetList.value[activeIndex.value] ?? workbook.value.activeSheet
|
|
45
46
|
)
|
|
46
47
|
|
|
47
|
-
/** 工具上下文:动态解析活动 sheet
|
|
48
|
+
/** 工具上下文:动态解析活动 sheet / 工作簿 / 渲染尺寸,tab 切换后自动指向最新值 */
|
|
48
49
|
const context = createSheetContext(
|
|
49
50
|
() => activeSheet.value,
|
|
50
|
-
() => workbook.value
|
|
51
|
+
() => workbook.value,
|
|
52
|
+
{
|
|
53
|
+
resolveGridSize: () =>
|
|
54
|
+
resolveRenderSize(props.rows, props.cols, activeSheet.value.rows, activeSheet.value.cols)
|
|
55
|
+
}
|
|
51
56
|
)
|
|
52
57
|
|
|
53
58
|
/** 工具栏状态版本号:visible/disabled/active 是 (ctx) => boolean 纯函数,状态源变化时 bump 触发重算 */
|
|
@@ -80,7 +85,7 @@ export function useSheetState(props: SheetProps, emit: Emit, hooks: SheetStateHo
|
|
|
80
85
|
// 整表内容替换(导入 / undo/redo 回放):restore 静默不发 cell-change,
|
|
81
86
|
// 状态源需 bump(grid 层自行订阅 content-reset 全量刷新,此处不重建)
|
|
82
87
|
sheet.on('content-reset', scheduleBump),
|
|
83
|
-
// 行列插入/删除 →
|
|
88
|
+
// 行列插入/删除 → 重建网格(渲染行列数跟随模型 sheet.rows/cols,
|
|
84
89
|
// 数据/选区/冻结/行高随重建恢复;低频操作直接重建)
|
|
85
90
|
sheet.on('structure-change', hooks.rebuildGrid)
|
|
86
91
|
]
|