@veltra/sheet 2.2.0 → 2.3.1

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.
Files changed (86) hide show
  1. package/dist/components/report/binding-badge.js +45 -21
  2. package/dist/components/report/designer/col-widths.js +18 -4
  3. package/dist/components/report/designer/conditional-rules/helpers.js +3 -3
  4. package/dist/components/report/designer/float-panel.js +73 -36
  5. package/dist/components/report/designer/rule-row.js +111 -127
  6. package/dist/components/report/designer/rules-dialog.js +5 -22
  7. package/dist/components/report/hub-dataset-editor.js +16 -1
  8. package/dist/components/report/preview-grid-size.js +32 -0
  9. package/dist/components/report/report-designer.js +75 -25
  10. package/dist/components/report/report-viewer.js +38 -25
  11. package/dist/components/report/style2.css +44 -39
  12. package/dist/components/report/use-report-designer.js +34 -12
  13. package/dist/components/report/use-report-viewer.js +6 -1
  14. package/dist/components/sheet/popups/border-popup.js +30 -2
  15. package/dist/components/sheet/popups/fill-color-popup.js +4 -3
  16. package/dist/components/sheet/popups/font-color-popup.js +2 -2
  17. package/dist/components/sheet/popups/font-size-popup.js +2 -2
  18. package/dist/components/sheet/sheet-context-menu.js +6 -3
  19. package/dist/components/sheet/sheet.js +8 -0
  20. package/dist/components/sheet/use-sheet-grid.js +14 -4
  21. package/dist/components/sheet/use-sheet-state.js +3 -2
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +2 -2
  24. package/dist/report/binding.d.ts +49 -11
  25. package/dist/report/binding.js +186 -29
  26. package/dist/report/connector.js +1 -1
  27. package/dist/report/export-xlsx.d.ts +3 -5
  28. package/dist/report/export-xlsx.js +7 -10
  29. package/dist/report/index.d.ts +2 -2
  30. package/dist/report/render/aggregate.js +20 -8
  31. package/dist/report/template.d.ts +8 -4
  32. package/dist/report/template.js +5 -2
  33. package/dist/tools/apply-style.js +29 -0
  34. package/dist/tools/builtin.js +17 -17
  35. package/dist/tools/context.d.ts +38 -5
  36. package/dist/tools/context.js +38 -1
  37. package/dist/types/report-designer.d.ts +2 -0
  38. package/dist/types/report-viewer.d.ts +2 -2
  39. package/dist/types/sheet.d.ts +4 -0
  40. package/package.json +15 -6
  41. package/src/components/report/__test__/conditional-rules-helpers.test.ts +1 -0
  42. package/src/components/report/__test__/preview-grid-size.test.ts +63 -0
  43. package/src/components/report/__test__/report-designer.test.ts +76 -0
  44. package/src/components/report/__test__/report-viewer.test.ts +24 -1
  45. package/src/components/report/__test__/use-report-designer.test.ts +142 -10
  46. package/src/components/report/binding-badge.ts +39 -20
  47. package/src/components/report/designer/col-widths.ts +34 -11
  48. package/src/components/report/designer/conditional-rules/helpers.ts +6 -3
  49. package/src/components/report/designer/float-panel.vue +71 -27
  50. package/src/components/report/designer/rule-row.vue +67 -67
  51. package/src/components/report/designer/rules-dialog.vue +3 -19
  52. package/src/components/report/hub-dataset-editor.vue +19 -1
  53. package/src/components/report/preview-grid-size.ts +33 -0
  54. package/src/components/report/report-designer.vue +79 -15
  55. package/src/components/report/report-viewer.vue +37 -29
  56. package/src/components/report/style-designer.scss +45 -41
  57. package/src/components/report/use-report-designer.ts +63 -14
  58. package/src/components/report/use-report-viewer.ts +11 -2
  59. package/src/components/sheet/__test__/sheet-component.test.ts +37 -0
  60. package/src/components/sheet/__test__/sheet-context-menu.test.ts +3 -2
  61. package/src/components/sheet/popups/border-popup.vue +31 -3
  62. package/src/components/sheet/popups/fill-color-popup.vue +5 -4
  63. package/src/components/sheet/popups/font-color-popup.vue +2 -2
  64. package/src/components/sheet/popups/font-size-popup.vue +2 -2
  65. package/src/components/sheet/sheet-context-menu.ts +8 -2
  66. package/src/components/sheet/sheet.vue +2 -0
  67. package/src/components/sheet/use-sheet-grid.ts +17 -3
  68. package/src/components/sheet/use-sheet-state.ts +8 -3
  69. package/src/report/__test__/binding.test.ts +103 -1
  70. package/src/report/__test__/connector.test.ts +21 -0
  71. package/src/report/__test__/export-xlsx.test.ts +4 -4
  72. package/src/report/__test__/render.test.ts +102 -0
  73. package/src/report/__test__/template.test.ts +11 -0
  74. package/src/report/binding.ts +278 -47
  75. package/src/report/connector.ts +3 -2
  76. package/src/report/export-xlsx.ts +9 -14
  77. package/src/report/render/aggregate.ts +29 -8
  78. package/src/report/template.ts +8 -4
  79. package/src/tools/__test__/apply-style.test.ts +129 -0
  80. package/src/tools/apply-style.ts +41 -0
  81. package/src/tools/builtin.ts +18 -18
  82. package/src/tools/context.ts +67 -4
  83. package/src/tools/index.ts +1 -0
  84. package/src/types/report-designer.ts +2 -0
  85. package/src/types/report-viewer.ts +2 -2
  86. package/src/types/sheet.ts +4 -0
@@ -0,0 +1,129 @@
1
+ import { createRange } from '@veltra/sheet-core/core/address'
2
+ import { Sheet } from '@veltra/sheet-core/core/sheet'
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import { classifySelectionStyleTarget, type SelectionStyleTarget } from '../apply-style'
6
+ import { createSheetContext } from '../context'
7
+
8
+ describe('classifySelectionStyleTarget', () => {
9
+ const cases: Array<{
10
+ name: string
11
+ range: ReturnType<typeof createRange>
12
+ rows: number
13
+ cols: number
14
+ expected: SelectionStyleTarget
15
+ }> = [
16
+ {
17
+ name: '整行(行号选区)',
18
+ range: createRange({ row: 2, col: 0 }, { row: 2, col: 25 }),
19
+ rows: 100,
20
+ cols: 26,
21
+ expected: 'row'
22
+ },
23
+ {
24
+ name: '多行整行',
25
+ range: createRange({ row: 1, col: 0 }, { row: 3, col: 25 }),
26
+ rows: 100,
27
+ cols: 26,
28
+ expected: 'row'
29
+ },
30
+ {
31
+ name: '整列(列头选区)',
32
+ range: createRange({ row: 0, col: 4 }, { row: 99, col: 4 }),
33
+ rows: 100,
34
+ cols: 26,
35
+ expected: 'col'
36
+ },
37
+ {
38
+ name: '局部区域',
39
+ range: createRange({ row: 0, col: 0 }, { row: 2, col: 2 }),
40
+ rows: 100,
41
+ cols: 26,
42
+ expected: 'cell'
43
+ },
44
+ {
45
+ name: '全表(行列皆满)→ 格样式',
46
+ range: createRange({ row: 0, col: 0 }, { row: 99, col: 25 }),
47
+ rows: 100,
48
+ cols: 26,
49
+ expected: 'cell'
50
+ }
51
+ ]
52
+
53
+ for (const c of cases) {
54
+ it(c.name, () => {
55
+ expect(classifySelectionStyleTarget(c.range, c.rows, c.cols)).toBe(c.expected)
56
+ })
57
+ }
58
+ })
59
+
60
+ describe('SheetContext.applyStyle', () => {
61
+ it('无 resolveGridSize:一律 setCellStyle', () => {
62
+ const sheet = new Sheet()
63
+ const ctx = createSheetContext(sheet)
64
+ const range = createRange({ row: 0, col: 0 }, { row: 0, col: 25 })
65
+ ctx.applyStyle(range, { fill: { color: '#FF0000' } })
66
+ expect(sheet.getRowStyle(0)).toBeUndefined()
67
+ expect(sheet.getCellStyle({ row: 0, col: 0 })).toEqual({ fill: { color: '#FF0000' } })
68
+ expect(sheet.getCellStyle({ row: 0, col: 25 })).toEqual({ fill: { color: '#FF0000' } })
69
+ })
70
+
71
+ it('整行选区 → setRowStyle;插列后新空格继承填充', () => {
72
+ const sheet = new Sheet()
73
+ const ctx = createSheetContext(sheet, undefined, {
74
+ resolveGridSize: () => ({ rows: 100, cols: 26 })
75
+ })
76
+ const range = createRange({ row: 1, col: 0 }, { row: 1, col: 25 })
77
+ ctx.applyStyle(range, { fill: { color: '#AABBCC' } })
78
+
79
+ expect(sheet.getRowStyle(1)).toEqual({ fill: { color: '#AABBCC' } })
80
+ // 不物化逐格样式
81
+ expect(sheet.getCellStyle({ row: 1, col: 0 })).toBeUndefined()
82
+ expect(sheet.getEffectiveStyle({ row: 1, col: 10 })).toEqual({ fill: { color: '#AABBCC' } })
83
+
84
+ sheet.insertCols(0, 1)
85
+ expect(sheet.getEffectiveStyle({ row: 1, col: 0 })).toEqual({ fill: { color: '#AABBCC' } })
86
+ expect(sheet.getEffectiveStyle({ row: 1, col: 26 })).toEqual({ fill: { color: '#AABBCC' } })
87
+ })
88
+
89
+ it('整列选区 → setColStyle;插行后新空格继承', () => {
90
+ const sheet = new Sheet()
91
+ const ctx = createSheetContext(sheet, undefined, {
92
+ resolveGridSize: () => ({ rows: 50, cols: 10 })
93
+ })
94
+ const range = createRange({ row: 0, col: 2 }, { row: 49, col: 2 })
95
+ ctx.applyStyle(range, { font: { bold: true } })
96
+
97
+ expect(sheet.getColStyle(2)).toEqual({ font: { bold: true } })
98
+ expect(sheet.getCellStyle({ row: 0, col: 2 })).toBeUndefined()
99
+ expect(sheet.getEffectiveStyle({ row: 20, col: 2 })).toEqual({ font: { bold: true } })
100
+
101
+ sheet.insertRows(0, 1)
102
+ expect(sheet.getEffectiveStyle({ row: 0, col: 2 })).toEqual({ font: { bold: true } })
103
+ })
104
+
105
+ it('局部选区仍走 setCellStyle', () => {
106
+ const sheet = new Sheet()
107
+ const ctx = createSheetContext(sheet, undefined, {
108
+ resolveGridSize: () => ({ rows: 100, cols: 26 })
109
+ })
110
+ const range = createRange({ row: 0, col: 0 }, { row: 0, col: 1 })
111
+ ctx.applyStyle(range, { fill: { color: '#112233' } })
112
+ expect(sheet.getRowStyle(0)).toBeUndefined()
113
+ expect(sheet.getCellStyle({ row: 0, col: 0 })).toEqual({ fill: { color: '#112233' } })
114
+ expect(sheet.getCellStyle({ row: 0, col: 1 })).toEqual({ fill: { color: '#112233' } })
115
+ })
116
+
117
+ it('applyStyle 整行写入进 undo', () => {
118
+ const sheet = new Sheet()
119
+ const ctx = createSheetContext(sheet, undefined, {
120
+ resolveGridSize: () => ({ rows: 100, cols: 26 })
121
+ })
122
+ ctx.applyStyle(createRange({ row: 0, col: 0 }, { row: 0, col: 25 }), {
123
+ fill: { color: '#00FF00' }
124
+ })
125
+ expect(sheet.getRowStyle(0)).toBeDefined()
126
+ expect(ctx.undo()).toBe(true)
127
+ expect(sheet.getRowStyle(0)).toBeUndefined()
128
+ })
129
+ })
@@ -0,0 +1,41 @@
1
+ import type { CellRange } from '@veltra/sheet-core/core/address'
2
+ import type { SetAxisStyleItem } from '@veltra/sheet-core/core/command/set-axis-style'
3
+ import type { CellStylePatch } from '@veltra/sheet-core/core/style/types'
4
+
5
+ /** 样式写入目标:整行 / 整列默认样式,或逐格 */
6
+ export type SelectionStyleTarget = 'row' | 'col' | 'cell'
7
+
8
+ /**
9
+ * 按选区相对渲染网格的跨度判定样式写入目标(对齐 Excel):
10
+ * - 跨满全部列且未跨满全部行 → 行默认样式(rowStyles)
11
+ * - 跨满全部行且未跨满全部列 → 列默认样式(colStyles)
12
+ * - 其余(含全表 / 局部)→ 单元格样式
13
+ */
14
+ export function classifySelectionStyleTarget(
15
+ range: CellRange,
16
+ renderRows: number,
17
+ renderCols: number
18
+ ): SelectionStyleTarget {
19
+ const cols = Math.max(renderCols, 1)
20
+ const rows = Math.max(renderRows, 1)
21
+ const spansAllCols = range.start.col === 0 && range.end.col >= cols - 1
22
+ const spansAllRows = range.start.row === 0 && range.end.row >= rows - 1
23
+ if (spansAllCols && !spansAllRows) return 'row'
24
+ if (spansAllRows && !spansAllCols) return 'col'
25
+ return 'cell'
26
+ }
27
+
28
+ /** 整行/整列选区 → set-axis-style 写入项(partial)或清除项(clear) */
29
+ export function axisStyleItemsForRange(
30
+ range: CellRange,
31
+ axis: 'row' | 'col',
32
+ patch: { partial: CellStylePatch } | { clear: true }
33
+ ): SetAxisStyleItem[] {
34
+ const start = axis === 'row' ? range.start.row : range.start.col
35
+ const end = axis === 'row' ? range.end.row : range.end.col
36
+ const items: SetAxisStyleItem[] = []
37
+ for (let index = start; index <= end; index++) {
38
+ items.push({ index, ...patch })
39
+ }
40
+ return items
41
+ }
@@ -44,8 +44,8 @@ function toggleFontFlag(
44
44
  ): void {
45
45
  const sel = selectionRange(ctx)
46
46
  if (!sel) return
47
- const on = ctx.getCellStyle(sel.activeCell)?.font?.[field] === true
48
- ctx.setCellStyle(sel.range, { font: { [field]: on ? null : true } })
47
+ const on = ctx.getEffectiveStyle(sel.activeCell)?.font?.[field] === true
48
+ ctx.applyStyle(sel.range, { font: { [field]: on ? null : true } })
49
49
  }
50
50
 
51
51
  /** 对齐 toggle:点当前档 = 清除,点其它档 = 切换 */
@@ -56,11 +56,11 @@ function toggleAlign(
56
56
  ): void {
57
57
  const sel = selectionRange(ctx)
58
58
  if (!sel) return
59
- const current = ctx.getCellStyle(sel.activeCell)?.align?.[axis]
59
+ const current = ctx.getEffectiveStyle(sel.activeCell)?.align?.[axis]
60
60
  if (current === value) {
61
- ctx.setCellStyle(sel.range, { align: { [axis]: null } })
61
+ ctx.applyStyle(sel.range, { align: { [axis]: null } })
62
62
  } else {
63
- ctx.setCellStyle(sel.range, { align: { [axis]: value } })
63
+ ctx.applyStyle(sel.range, { align: { [axis]: value } })
64
64
  }
65
65
  }
66
66
 
@@ -181,7 +181,7 @@ registerTool({
181
181
  disabled: (ctx) => !ctx.getSelection().activeCell,
182
182
  active: (ctx) => {
183
183
  const active = ctx.getSelection().activeCell
184
- return !!active && ctx.getCellStyle(active)?.font?.bold === true
184
+ return !!active && ctx.getEffectiveStyle(active)?.font?.bold === true
185
185
  },
186
186
  onClick: (ctx) => toggleFontFlag(ctx, 'bold')
187
187
  })
@@ -196,7 +196,7 @@ registerTool({
196
196
  disabled: (ctx) => !ctx.getSelection().activeCell,
197
197
  active: (ctx) => {
198
198
  const active = ctx.getSelection().activeCell
199
- return !!active && ctx.getCellStyle(active)?.font?.italic === true
199
+ return !!active && ctx.getEffectiveStyle(active)?.font?.italic === true
200
200
  },
201
201
  onClick: (ctx) => toggleFontFlag(ctx, 'italic')
202
202
  })
@@ -211,7 +211,7 @@ registerTool({
211
211
  disabled: (ctx) => !ctx.getSelection().activeCell,
212
212
  active: (ctx) => {
213
213
  const active = ctx.getSelection().activeCell
214
- return !!active && ctx.getCellStyle(active)?.font?.underline === true
214
+ return !!active && ctx.getEffectiveStyle(active)?.font?.underline === true
215
215
  },
216
216
  onClick: (ctx) => toggleFontFlag(ctx, 'underline')
217
217
  })
@@ -226,7 +226,7 @@ registerTool({
226
226
  disabled: (ctx) => !ctx.getSelection().activeCell,
227
227
  active: (ctx) => {
228
228
  const active = ctx.getSelection().activeCell
229
- return !!active && ctx.getCellStyle(active)?.font?.strikethrough === true
229
+ return !!active && ctx.getEffectiveStyle(active)?.font?.strikethrough === true
230
230
  },
231
231
  onClick: (ctx) => toggleFontFlag(ctx, 'strikethrough')
232
232
  })
@@ -265,7 +265,7 @@ registerTool({
265
265
  disabled: (ctx) => !ctx.getSelection().activeCell,
266
266
  active: (ctx) => {
267
267
  const active = ctx.getSelection().activeCell
268
- return !!active && ctx.getCellStyle(active)?.align?.horizontal === 'left'
268
+ return !!active && ctx.getEffectiveStyle(active)?.align?.horizontal === 'left'
269
269
  },
270
270
  onClick: (ctx) => toggleAlign(ctx, 'horizontal', 'left')
271
271
  })
@@ -280,7 +280,7 @@ registerTool({
280
280
  disabled: (ctx) => !ctx.getSelection().activeCell,
281
281
  active: (ctx) => {
282
282
  const active = ctx.getSelection().activeCell
283
- return !!active && ctx.getCellStyle(active)?.align?.horizontal === 'center'
283
+ return !!active && ctx.getEffectiveStyle(active)?.align?.horizontal === 'center'
284
284
  },
285
285
  onClick: (ctx) => toggleAlign(ctx, 'horizontal', 'center')
286
286
  })
@@ -295,7 +295,7 @@ registerTool({
295
295
  disabled: (ctx) => !ctx.getSelection().activeCell,
296
296
  active: (ctx) => {
297
297
  const active = ctx.getSelection().activeCell
298
- return !!active && ctx.getCellStyle(active)?.align?.horizontal === 'right'
298
+ return !!active && ctx.getEffectiveStyle(active)?.align?.horizontal === 'right'
299
299
  },
300
300
  onClick: (ctx) => toggleAlign(ctx, 'horizontal', 'right')
301
301
  })
@@ -310,7 +310,7 @@ registerTool({
310
310
  disabled: (ctx) => !ctx.getSelection().activeCell,
311
311
  active: (ctx) => {
312
312
  const active = ctx.getSelection().activeCell
313
- return !!active && ctx.getCellStyle(active)?.align?.vertical === 'top'
313
+ return !!active && ctx.getEffectiveStyle(active)?.align?.vertical === 'top'
314
314
  },
315
315
  onClick: (ctx) => toggleAlign(ctx, 'vertical', 'top')
316
316
  })
@@ -325,7 +325,7 @@ registerTool({
325
325
  disabled: (ctx) => !ctx.getSelection().activeCell,
326
326
  active: (ctx) => {
327
327
  const active = ctx.getSelection().activeCell
328
- return !!active && ctx.getCellStyle(active)?.align?.vertical === 'middle'
328
+ return !!active && ctx.getEffectiveStyle(active)?.align?.vertical === 'middle'
329
329
  },
330
330
  onClick: (ctx) => toggleAlign(ctx, 'vertical', 'middle')
331
331
  })
@@ -340,7 +340,7 @@ registerTool({
340
340
  disabled: (ctx) => !ctx.getSelection().activeCell,
341
341
  active: (ctx) => {
342
342
  const active = ctx.getSelection().activeCell
343
- return !!active && ctx.getCellStyle(active)?.align?.vertical === 'bottom'
343
+ return !!active && ctx.getEffectiveStyle(active)?.align?.vertical === 'bottom'
344
344
  },
345
345
  onClick: (ctx) => toggleAlign(ctx, 'vertical', 'bottom')
346
346
  })
@@ -355,13 +355,13 @@ registerTool({
355
355
  disabled: (ctx) => !ctx.getSelection().activeCell,
356
356
  active: (ctx) => {
357
357
  const active = ctx.getSelection().activeCell
358
- return !!active && ctx.getCellStyle(active)?.align?.wrap === true
358
+ return !!active && ctx.getEffectiveStyle(active)?.align?.wrap === true
359
359
  },
360
360
  onClick: (ctx) => {
361
361
  const sel = selectionRange(ctx)
362
362
  if (!sel) return
363
- const on = ctx.getCellStyle(sel.activeCell)?.align?.wrap === true
364
- ctx.setCellStyle(sel.range, { align: { wrap: on ? null : true } })
363
+ const on = ctx.getEffectiveStyle(sel.activeCell)?.align?.wrap === true
364
+ ctx.applyStyle(sel.range, { align: { wrap: on ? null : true } })
365
365
  }
366
366
  })
367
367
 
@@ -10,6 +10,8 @@ import type { FrozenState, Sheet } from '@veltra/sheet-core/core/sheet'
10
10
  import type { CellStyle, CellStylePatch } from '@veltra/sheet-core/core/style/types'
11
11
  import type { Workbook } from '@veltra/sheet-core/core/workbook'
12
12
 
13
+ import { axisStyleItemsForRange, classifySelectionStyleTarget } from './apply-style'
14
+
13
15
  /**
14
16
  * SheetContext:工具的唯一操作门面。
15
17
  *
@@ -43,12 +45,33 @@ export interface SheetContext {
43
45
  getCellInfo(addr: CellAddress): CellInfo
44
46
 
45
47
  // ─── 样式(写入经命令系统,可 undo) ───────────────────────
46
- /** 设置区域样式(部分合并语义,见 CellStylePatch);空样式 = 删除 s 字段 */
48
+ /**
49
+ * 按选区形态写样式(Excel 语义):
50
+ * 整行选区 → rowStyles;整列选区 → colStyles;其余 → 逐格。
51
+ * 判定依赖 createSheetContext 注入的渲染尺寸;未注入时一律走 setCellStyle。
52
+ */
53
+ applyStyle(range: CellRange, partial: CellStylePatch): void
54
+ /**
55
+ * 选区样式目标(与 applyStyle 同一判定);未注入渲染尺寸时恒为 'cell'。
56
+ */
57
+ resolveStyleTarget(range: CellRange): 'row' | 'col' | 'cell'
58
+ /**
59
+ * 按选区形态清除样式:整行/整列清对应默认样式;其余清单元格 s。
60
+ * 未注入渲染尺寸时一律走 clearCellStyle。
61
+ */
62
+ clearStyle(range: CellRange): void
63
+ /** 设置区域单元格样式(部分合并语义,见 CellStylePatch);空样式 = 删除 s 字段 */
47
64
  setCellStyle(range: CellRange, partial: CellStylePatch): void
48
- /** 清除区域样式(保留值 / 公式) */
65
+ /** 清除区域单元格样式(保留值 / 公式) */
49
66
  clearCellStyle(range: CellRange): void
50
- /** 读取单元格样式(原始存储语义:被覆盖格 → undefined) */
67
+ /** 设置行默认样式(部分合并,进 undo) */
68
+ setRowStyle(row: number, partial: CellStylePatch): void
69
+ /** 设置列默认样式(部分合并,进 undo) */
70
+ setColStyle(col: number, partial: CellStylePatch): void
71
+ /** 读取单元格样式(原始存储语义:被覆盖格 → undefined;不含行列默认) */
51
72
  getCellStyle(addr: CellAddress): CellStyle | undefined
73
+ /** 有效样式:列 → 行 → 格字段级叠加(空格可继承行列默认) */
74
+ getEffectiveStyle(addr: CellAddress): CellStyle | undefined
52
75
 
53
76
  // ─── 写入(全部经命令系统,可 undo) ───────────────────────
54
77
  setCellValue(addr: CellAddress, value: CellValue): void
@@ -105,6 +128,15 @@ export interface SheetContext {
105
128
  onImageChange(handler: (payload: { id?: string }) => void): () => void
106
129
  }
107
130
 
131
+ /** createSheetContext 可选配置 */
132
+ export interface SheetContextOptions {
133
+ /**
134
+ * 当前渲染网格尺寸(与 SheetGrid / resolveRenderSize 一致)。
135
+ * applyStyle / clearStyle 用其判定整行/整列;缺省时不走行列默认样式。
136
+ */
137
+ resolveGridSize?: () => { rows: number; cols: number }
138
+ }
139
+
108
140
  /**
109
141
  * 创建工具上下文。
110
142
  * @param resolveSheet 活动 sheet 解析器(USheet 传 `() => activeSheet`,
@@ -113,14 +145,21 @@ export interface SheetContext {
113
145
  * 支持传解析函数 `() => Workbook`——USheet 在 props.workbook 动态切换后,
114
146
  * 上下文始终解析到**当前**工作簿(#2:旧实现取构造时值快照,宿主切换
115
147
  * workbook prop 后导出工具仍导出旧工作簿)
148
+ * @param options.resolveGridSize 渲染尺寸解析器(整行/整列样式路由)
116
149
  */
117
150
  export function createSheetContext(
118
151
  resolveSheet: Sheet | (() => Sheet),
119
- workbook?: Workbook | (() => Workbook)
152
+ workbook?: Workbook | (() => Workbook),
153
+ options?: SheetContextOptions
120
154
  ): SheetContext {
121
155
  const sheet = typeof resolveSheet === 'function' ? resolveSheet : () => resolveSheet
122
156
  const resolveWorkbook =
123
157
  typeof workbook === 'function' ? workbook : workbook != null ? () => workbook : undefined
158
+ const resolveGridSize = options?.resolveGridSize
159
+ const resolveTarget = (range: CellRange) => {
160
+ const size = resolveGridSize?.()
161
+ return size ? classifySelectionStyleTarget(range, size.rows, size.cols) : 'cell'
162
+ }
124
163
  return {
125
164
  get workbook() {
126
165
  return resolveWorkbook?.()
@@ -143,9 +182,33 @@ export function createSheetContext(
143
182
  mergeCells: (range) => sheet().mergeCells(range),
144
183
  unmergeCells: (range) => sheet().unmergeCells(range),
145
184
 
185
+ applyStyle: (range, partial) => {
186
+ const target = resolveTarget(range)
187
+ if (target === 'row' || target === 'col') {
188
+ const items = axisStyleItemsForRange(range, target, { partial })
189
+ if (items.length === 0) return
190
+ sheet().executeCommand('sheet.command.set-axis-style', { axis: target, items })
191
+ return
192
+ }
193
+ sheet().setCellStyle(range, partial)
194
+ },
195
+ resolveStyleTarget: (range) => resolveTarget(range),
196
+ clearStyle: (range) => {
197
+ const target = resolveTarget(range)
198
+ if (target === 'row' || target === 'col') {
199
+ const items = axisStyleItemsForRange(range, target, { clear: true })
200
+ if (items.length === 0) return
201
+ sheet().executeCommand('sheet.command.set-axis-style', { axis: target, items })
202
+ return
203
+ }
204
+ sheet().clearCellStyle(range)
205
+ },
146
206
  setCellStyle: (range, partial) => sheet().setCellStyle(range, partial),
147
207
  clearCellStyle: (range) => sheet().clearCellStyle(range),
208
+ setRowStyle: (row, partial) => sheet().setRowStyle(row, partial),
209
+ setColStyle: (col, partial) => sheet().setColStyle(col, partial),
148
210
  getCellStyle: (addr) => sheet().getCellStyle(addr),
211
+ getEffectiveStyle: (addr) => sheet().getEffectiveStyle(addr),
149
212
  executeCommand: <R = void>(commandId: string, params: unknown): R | undefined =>
150
213
  sheet().executeCommand<R>(commandId, params),
151
214
 
@@ -1,2 +1,3 @@
1
+ export * from './apply-style'
1
2
  export * from './context'
2
3
  export * from './registry'
@@ -24,6 +24,8 @@ export interface ReportDesignerProps {
24
24
  /** 报表设计器组件定义的事件 */
25
25
  export interface ReportDesignerEmits {
26
26
  (e: 'update:connections', value: DataConnection[]): void
27
+ /** 数据集增删改或数据中枢关闭:宿主可立即持久化工作区 */
28
+ (e: 'datasets-change'): void
27
29
  }
28
30
 
29
31
  /** 在组件内部引用 */
@@ -16,8 +16,8 @@ export interface ReportViewerProps {
16
16
  /** 承载填充报表的工作簿(USheet 先例:缺省内部自建单 sheet 工作簿) */
17
17
  workbook?: Workbook
18
18
  /**
19
- * 列宽(sheet-core 列宽未进 SheetSnapshot):载入填充结果后写入 VTable 运行时,
20
- * 供 `exportXlsx()` 读取保真列宽(设计器预览态传入设计态捕获值)
19
+ * 列宽显式覆盖(可选):优先于模板 `colWidths` / 展开映射结果写入模型与网格。
20
+ * 列宽已进 `SheetSnapshot.colWidths`,通常无需再传。
21
21
  */
22
22
  colWidths?: ReadonlyArray<ReportColWidthEntry>
23
23
  }
@@ -39,6 +39,10 @@ export interface SheetProps {
39
39
  showFormulaBar?: boolean
40
40
  /** 是否显示底部 sheet 标签栏,默认 true */
41
41
  showTabs?: boolean
42
+ /** 是否显示行号列,默认 true */
43
+ showRowHeader?: boolean
44
+ /** 是否显示列字母表头,默认 true */
45
+ showColHeader?: boolean
42
46
  /** 只读预览(关闭编辑回写、填充柄等写入口) */
43
47
  readonly?: boolean
44
48
  }