@veltra/sheet 2.4.0 → 2.5.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 (147) hide show
  1. package/dist/components/sheet/formula-bar.js +50 -7
  2. package/dist/components/sheet/style2.css +2 -2
  3. package/dist/index.d.ts +2 -19
  4. package/dist/index.js +2 -15
  5. package/dist/types/index.d.ts +1 -3
  6. package/package.json +5 -6
  7. package/src/components/sheet/__test__/formula-bar.test.ts +29 -0
  8. package/src/components/sheet/formula-bar.vue +67 -6
  9. package/src/components/sheet/style.scss +1 -1
  10. package/src/index.ts +1 -27
  11. package/src/types/index.ts +0 -2
  12. package/dist/components/report/binding-badge.js +0 -118
  13. package/dist/components/report/designer/cell-coords.js +0 -46
  14. package/dist/components/report/designer/col-widths.js +0 -29
  15. package/dist/components/report/designer/conditional-rules/helpers.js +0 -139
  16. package/dist/components/report/designer/drill-dialog.js +0 -258
  17. package/dist/components/report/designer/drop-highlight-overlay.js +0 -76
  18. package/dist/components/report/designer/float-panel.js +0 -434
  19. package/dist/components/report/designer/role.js +0 -17
  20. package/dist/components/report/designer/rule-preview.js +0 -33
  21. package/dist/components/report/designer/rule-row.js +0 -253
  22. package/dist/components/report/designer/rules-dialog.js +0 -118
  23. package/dist/components/report/designer/topology-overlay.js +0 -122
  24. package/dist/components/report/designer/topology.js +0 -64
  25. package/dist/components/report/designer/use-grid-overlay.js +0 -75
  26. package/dist/components/report/designer-hub.js +0 -177
  27. package/dist/components/report/field-panel-helpers.js +0 -33
  28. package/dist/components/report/field-panel.js +0 -132
  29. package/dist/components/report/filter-bar.js +0 -74
  30. package/dist/components/report/hub-connection-form.js +0 -145
  31. package/dist/components/report/hub-dataset-editor.js +0 -409
  32. package/dist/components/report/preview-grid-size.js +0 -32
  33. package/dist/components/report/print.js +0 -39
  34. package/dist/components/report/report-designer.js +0 -428
  35. package/dist/components/report/report-viewer.js +0 -253
  36. package/dist/components/report/style.d.ts +0 -17
  37. package/dist/components/report/style.js +0 -18
  38. package/dist/components/report/style2.css +0 -965
  39. package/dist/components/report/use-report-designer.js +0 -387
  40. package/dist/components/report/use-report-viewer.js +0 -140
  41. package/dist/report/binding.d.ts +0 -83
  42. package/dist/report/binding.js +0 -334
  43. package/dist/report/connector.d.ts +0 -66
  44. package/dist/report/connector.js +0 -159
  45. package/dist/report/drill.d.ts +0 -48
  46. package/dist/report/drill.js +0 -78
  47. package/dist/report/export-xlsx.d.ts +0 -13
  48. package/dist/report/export-xlsx.js +0 -13
  49. package/dist/report/filter-bar.d.ts +0 -10
  50. package/dist/report/filter-bar.js +0 -31
  51. package/dist/report/index.d.ts +0 -14
  52. package/dist/report/params.d.ts +0 -13
  53. package/dist/report/params.js +0 -61
  54. package/dist/report/print.d.ts +0 -23
  55. package/dist/report/print.js +0 -197
  56. package/dist/report/render/aggregate.js +0 -193
  57. package/dist/report/render/builder.d.ts +0 -12
  58. package/dist/report/render/builder.js +0 -139
  59. package/dist/report/render/coordinate.d.ts +0 -25
  60. package/dist/report/render/coordinate.js +0 -733
  61. package/dist/report/render/index.d.ts +0 -14
  62. package/dist/report/render/index.js +0 -21
  63. package/dist/report/render/style-resolver.js +0 -50
  64. package/dist/report/render/template-index.d.ts +0 -24
  65. package/dist/report/render/template-index.js +0 -137
  66. package/dist/report/rules/evaluate.d.ts +0 -25
  67. package/dist/report/rules/evaluate.js +0 -82
  68. package/dist/report/rules/index.d.ts +0 -2
  69. package/dist/report/template.d.ts +0 -63
  70. package/dist/report/template.js +0 -83
  71. package/dist/report/types.d.ts +0 -103
  72. package/dist/types/report-designer.d.ts +0 -51
  73. package/dist/types/report-viewer.d.ts +0 -57
  74. package/src/components/report/__test__/binding-badge.test.ts +0 -92
  75. package/src/components/report/__test__/conditional-rules-helpers.test.ts +0 -88
  76. package/src/components/report/__test__/field-panel-helpers.test.ts +0 -65
  77. package/src/components/report/__test__/float-panel-position.test.ts +0 -45
  78. package/src/components/report/__test__/preview-grid-size.test.ts +0 -63
  79. package/src/components/report/__test__/report-designer.test.ts +0 -559
  80. package/src/components/report/__test__/report-drill.test.ts +0 -692
  81. package/src/components/report/__test__/report-viewer.test.ts +0 -400
  82. package/src/components/report/__test__/topology.test.ts +0 -143
  83. package/src/components/report/__test__/use-report-designer.test.ts +0 -804
  84. package/src/components/report/binding-badge.ts +0 -122
  85. package/src/components/report/designer/cell-coords.ts +0 -95
  86. package/src/components/report/designer/col-widths.ts +0 -56
  87. package/src/components/report/designer/conditional-rules/helpers.ts +0 -189
  88. package/src/components/report/designer/drill-dialog.vue +0 -245
  89. package/src/components/report/designer/drop-highlight-overlay.vue +0 -75
  90. package/src/components/report/designer/float-panel.vue +0 -444
  91. package/src/components/report/designer/role.ts +0 -32
  92. package/src/components/report/designer/rule-preview.vue +0 -40
  93. package/src/components/report/designer/rule-row.vue +0 -252
  94. package/src/components/report/designer/rules-dialog.vue +0 -110
  95. package/src/components/report/designer/topology-overlay.vue +0 -128
  96. package/src/components/report/designer/topology.ts +0 -124
  97. package/src/components/report/designer/use-grid-overlay.ts +0 -114
  98. package/src/components/report/designer-hub.vue +0 -198
  99. package/src/components/report/field-panel-helpers.ts +0 -44
  100. package/src/components/report/field-panel.vue +0 -136
  101. package/src/components/report/filter-bar.vue +0 -77
  102. package/src/components/report/hub-connection-form.vue +0 -124
  103. package/src/components/report/hub-dataset-editor.vue +0 -387
  104. package/src/components/report/index.ts +0 -2
  105. package/src/components/report/preview-grid-size.ts +0 -33
  106. package/src/components/report/print.ts +0 -44
  107. package/src/components/report/report-designer.vue +0 -454
  108. package/src/components/report/report-viewer.vue +0 -289
  109. package/src/components/report/style-designer.scss +0 -653
  110. package/src/components/report/style-hub.scss +0 -292
  111. package/src/components/report/style-viewer.scss +0 -144
  112. package/src/components/report/style.scss +0 -3
  113. package/src/components/report/style.ts +0 -20
  114. package/src/components/report/use-report-designer.ts +0 -603
  115. package/src/components/report/use-report-viewer.ts +0 -227
  116. package/src/report/__test__/binding.test.ts +0 -320
  117. package/src/report/__test__/connector.test.ts +0 -239
  118. package/src/report/__test__/drill.test.ts +0 -194
  119. package/src/report/__test__/export-xlsx.test.ts +0 -96
  120. package/src/report/__test__/filter-bar.test.ts +0 -58
  121. package/src/report/__test__/params.test.ts +0 -156
  122. package/src/report/__test__/print.test.ts +0 -170
  123. package/src/report/__test__/render.test.ts +0 -856
  124. package/src/report/__test__/rules.test.ts +0 -131
  125. package/src/report/__test__/template.test.ts +0 -366
  126. package/src/report/binding.ts +0 -474
  127. package/src/report/connector.ts +0 -206
  128. package/src/report/drill.ts +0 -112
  129. package/src/report/export-xlsx.ts +0 -15
  130. package/src/report/filter-bar.ts +0 -36
  131. package/src/report/index.ts +0 -16
  132. package/src/report/params.ts +0 -96
  133. package/src/report/print.ts +0 -234
  134. package/src/report/render/__test__/aggregate.test.ts +0 -16
  135. package/src/report/render/__test__/coordinate.test.ts +0 -405
  136. package/src/report/render/aggregate.ts +0 -289
  137. package/src/report/render/builder.ts +0 -211
  138. package/src/report/render/coordinate.ts +0 -1239
  139. package/src/report/render/index.ts +0 -30
  140. package/src/report/render/style-resolver.ts +0 -68
  141. package/src/report/render/template-index.ts +0 -200
  142. package/src/report/rules/evaluate.ts +0 -113
  143. package/src/report/rules/index.ts +0 -7
  144. package/src/report/template.ts +0 -145
  145. package/src/report/types.ts +0 -116
  146. package/src/types/report-designer.ts +0 -55
  147. package/src/types/report-viewer.ts +0 -54
@@ -1,252 +0,0 @@
1
- <template>
2
- <div :class="cls.b">
3
- <span :class="cls.e('handle')" title="拖拽排序">⋮⋮</span>
4
-
5
- <div :class="cls.e('main')">
6
- <div :class="cls.e('when')">
7
- <span :class="cls.e('lead')">当</span>
8
- <u-select
9
- size="small"
10
- :class="cls.e('field-select')"
11
- :model-value="rule.field ?? SELF_FIELD_VALUE"
12
- :options="fieldOptions"
13
- @update:model-value="onEvalField"
14
- />
15
- <u-select
16
- size="small"
17
- :class="cls.e('operator')"
18
- :model-value="rule.operator"
19
- :options="operatorOptions"
20
- @update:model-value="onOperator"
21
- />
22
- <div :class="cls.e('value')">
23
- <template v-if="rule.operator === 'between'">
24
- <u-number-input
25
- v-if="numericField"
26
- size="small"
27
- :class="cls.e('between-input')"
28
- :model-value="betweenStart as number"
29
- @update:model-value="(v) => patchBetween(0, v ?? 0)"
30
- />
31
- <u-input
32
- v-else
33
- size="small"
34
- :class="cls.e('between-input')"
35
- :model-value="String(betweenStart)"
36
- @update:model-value="(v) => patchBetween(0, String(v ?? ''))"
37
- />
38
- <span :class="cls.e('between-sep')">至</span>
39
- <u-number-input
40
- v-if="numericField"
41
- size="small"
42
- :class="cls.e('between-input')"
43
- :model-value="betweenEnd as number"
44
- @update:model-value="(v) => patchBetween(1, v ?? 0)"
45
- />
46
- <u-input
47
- v-else
48
- size="small"
49
- :class="cls.e('between-input')"
50
- :model-value="String(betweenEnd)"
51
- @update:model-value="(v) => patchBetween(1, String(v ?? ''))"
52
- />
53
- </template>
54
-
55
- <u-input
56
- v-else-if="rule.operator === 'contains' || (!numericField && rule.operator === 'eq')"
57
- size="small"
58
- :class="cls.e('scalar-input')"
59
- :model-value="String(rule.value ?? '')"
60
- placeholder="比较值"
61
- @update:model-value="(v) => patchValue(String(v ?? ''))"
62
- />
63
-
64
- <u-number-input
65
- v-else
66
- size="small"
67
- :class="cls.e('scalar-input')"
68
- :model-value="scalarNumber"
69
- @update:model-value="(v) => patchValue(v ?? 0)"
70
- />
71
- </div>
72
- <span :class="cls.e('lead')">时</span>
73
- </div>
74
-
75
- <div :class="cls.e('then')">
76
- <span :class="cls.e('lead')">样式</span>
77
- <span title="背景">
78
- <u-palette
79
- size="small"
80
- :model-value="rule.style.fill?.color ?? ''"
81
- @update:model-value="patchFillColor"
82
- />
83
- </span>
84
- <span title="文字">
85
- <u-palette
86
- size="small"
87
- :model-value="rule.style.font?.color ?? ''"
88
- @update:model-value="patchFontColor"
89
- />
90
- </span>
91
- <u-button-group>
92
- <u-button
93
- size="small"
94
- :class="cls.e('toggle')"
95
- :type="rule.style.font?.bold ? 'primary' : undefined"
96
- plain
97
- title="加粗"
98
- @click="toggleBold"
99
- >
100
- B
101
- </u-button>
102
- <u-button
103
- size="small"
104
- :class="cls.e('toggle')"
105
- :type="rule.style.font?.italic ? 'primary' : undefined"
106
- plain
107
- title="斜体"
108
- @click="toggleItalic"
109
- >
110
- I
111
- </u-button>
112
- </u-button-group>
113
- <u-report-rule-preview :class="cls.e('preview')" :rule="rule" />
114
-
115
- <span :class="cls.e('lead')">应用到</span>
116
- <u-button-group>
117
- <u-button
118
- v-for="option in RULE_SCOPE_OPTIONS"
119
- :key="option.value"
120
- size="small"
121
- plain
122
- :type="(rule.scope ?? 'cell') === option.value ? 'primary' : undefined"
123
- :title="
124
- option.value === 'row' ? '染满物理输出行(交叉表会盖住同行所有列)' : '只改本格'
125
- "
126
- @click="onScope(option.value)"
127
- >
128
- {{ option.label }}
129
- </u-button>
130
- </u-button-group>
131
- </div>
132
- </div>
133
-
134
- <u-button size="small" text type="danger" :class="cls.e('remove')" @click="emit('remove')">
135
- 删除
136
- </u-button>
137
- </div>
138
- </template>
139
-
140
- <script lang="ts" setup>
141
- import { UButton, UButtonGroup, UInput, UNumberInput, UPalette, USelect } from '@veltra/desktop'
142
- import { bem } from '@veltra/utils'
143
- import { computed } from 'vue'
144
-
145
- import type { ConditionalOperator, ConditionalRule, DatasetField } from '../../../report/types'
146
- import {
147
- coerceValueForOperator,
148
- isNumericFieldType,
149
- operatorsForFieldType,
150
- readBetweenValue,
151
- resolveEvalFieldType,
152
- RULE_SCOPE_OPTIONS,
153
- ruleEvalFieldOptions,
154
- SELF_FIELD_VALUE,
155
- writeBetweenValue
156
- } from './conditional-rules/helpers'
157
- import UReportRulePreview from './rule-preview.vue'
158
-
159
- defineOptions({ name: 'UReportRuleRow' })
160
-
161
- const props = defineProps<{
162
- rule: ConditionalRule
163
- bindingField: string
164
- datasetFields: readonly DatasetField[]
165
- }>()
166
-
167
- const emit = defineEmits<{ 'update:rule': [rule: ConditionalRule]; remove: [] }>()
168
-
169
- const cls = bem('report-rule-row')
170
-
171
- const evalFieldType = computed(() =>
172
- resolveEvalFieldType(props.rule, props.bindingField, props.datasetFields)
173
- )
174
- const operatorOptions = computed(() => operatorsForFieldType(evalFieldType.value))
175
- const numericField = computed(() => isNumericFieldType(evalFieldType.value))
176
- const fieldOptions = computed(() => ruleEvalFieldOptions(props.bindingField, props.datasetFields))
177
-
178
- const betweenStart = computed(() => readBetweenValue(props.rule.value, 0, evalFieldType.value))
179
- const betweenEnd = computed(() => readBetweenValue(props.rule.value, 1, evalFieldType.value))
180
-
181
- const scalarNumber = computed((): number => {
182
- const raw = props.rule.value
183
- if (typeof raw === 'number' && Number.isFinite(raw)) return raw
184
- const parsed = Number(raw)
185
- return Number.isFinite(parsed) ? parsed : 0
186
- })
187
-
188
- function patchRule(patch: Partial<ConditionalRule>): void {
189
- emit('update:rule', { ...props.rule, ...patch })
190
- }
191
-
192
- function onEvalField(field: string | undefined): void {
193
- const next: Partial<ConditionalRule> = {}
194
- if (!field) {
195
- next.field = undefined
196
- } else {
197
- next.field = field
198
- }
199
- const nextType = resolveEvalFieldType(
200
- { ...props.rule, ...next },
201
- props.bindingField,
202
- props.datasetFields
203
- )
204
- const operator = operatorsForFieldType(nextType)[0]?.value ?? props.rule.operator
205
- next.operator = operator
206
- next.value = coerceValueForOperator(operator, nextType, props.rule.value)
207
- patchRule(next)
208
- }
209
-
210
- function onScope(scope: 'cell' | 'row'): void {
211
- patchRule({ scope: scope === 'cell' ? undefined : scope })
212
- }
213
-
214
- function onOperator(operator: ConditionalOperator): void {
215
- patchRule({
216
- operator,
217
- value: coerceValueForOperator(operator, evalFieldType.value, props.rule.value)
218
- })
219
- }
220
-
221
- function patchValue(value: unknown): void {
222
- patchRule({ value })
223
- }
224
-
225
- function patchBetween(index: 0 | 1, next: number | string): void {
226
- patchRule({ value: writeBetweenValue(props.rule.value, index, next, evalFieldType.value) })
227
- }
228
-
229
- function patchFillColor(color: string): void {
230
- patchRule({ style: { ...props.rule.style, fill: color ? { color } : undefined } })
231
- }
232
-
233
- function patchFontColor(color: string): void {
234
- patchRule({
235
- style: { ...props.rule.style, font: { ...props.rule.style.font, color: color || undefined } }
236
- })
237
- }
238
-
239
- function toggleBold(): void {
240
- const bold = !props.rule.style.font?.bold
241
- patchRule({
242
- style: { ...props.rule.style, font: { ...props.rule.style.font, bold: bold || undefined } }
243
- })
244
- }
245
-
246
- function toggleItalic(): void {
247
- const italic = !props.rule.style.font?.italic
248
- patchRule({
249
- style: { ...props.rule.style, font: { ...props.rule.style.font, italic: italic || undefined } }
250
- })
251
- }
252
- </script>
@@ -1,110 +0,0 @@
1
- <template>
2
- <u-dialog
3
- v-model="visible"
4
- title="条件样式"
5
- style="width: min(600px, 92vw); max-height: 85vh"
6
- @close="onClose"
7
- >
8
- <div :class="cls.b">
9
- <p :class="cls.e('hint')">规则按顺序叠加。整行高亮适合明细表,交叉表会染满整行。</p>
10
-
11
- <ul v-if="draftItems.length" ref="parentRef" :class="cls.e('list')">
12
- <li v-for="(item, index) in draftItems" :key="item.id" :class="cls.e('item')">
13
- <u-report-rule-row
14
- :rule="item.rule"
15
- :binding-field="bindingField"
16
- :dataset-fields="datasetFields"
17
- @update:rule="item.rule = $event"
18
- @remove="removeRule(index)"
19
- />
20
- </li>
21
- </ul>
22
- <p v-else :class="cls.e('empty')">还没有规则。</p>
23
-
24
- <u-button size="small" plain @click="addRule">添加规则</u-button>
25
- </div>
26
-
27
- <template #footer>
28
- <u-button size="small" @click="visible = false">取消</u-button>
29
- <u-button size="small" type="primary" @click="confirm">确定</u-button>
30
- </template>
31
- </u-dialog>
32
- </template>
33
-
34
- <script lang="ts" setup>
35
- import { animations, useDnD } from '@veltra/compositions'
36
- import { UButton, UDialog } from '@veltra/desktop'
37
- import { bem } from '@veltra/utils'
38
- import { computed, ref, watch } from 'vue'
39
-
40
- import type { ConditionalRule, DatasetField } from '../../../report/types'
41
- import {
42
- cloneRulesFromDraft,
43
- createDraftItem,
44
- initDraftFromRules,
45
- type DraftRuleItem
46
- } from './conditional-rules/helpers'
47
- import UReportRuleRow from './rule-row.vue'
48
-
49
- defineOptions({ name: 'UReportRulesDialog' })
50
-
51
- const props = withDefaults(
52
- defineProps<{
53
- modelValue: boolean
54
- rules: ConditionalRule[]
55
- bindingField: string
56
- datasetFields?: readonly DatasetField[]
57
- /** @deprecated 使用 datasetFields + resolveEvalFieldType */
58
- fieldType?: DatasetField['type']
59
- }>(),
60
- { datasetFields: () => [], fieldType: 'number' }
61
- )
62
-
63
- const emit = defineEmits<{
64
- 'update:modelValue': [value: boolean]
65
- save: [rules: ConditionalRule[]]
66
- }>()
67
-
68
- const cls = bem('report-rules-dialog')
69
-
70
- const draftItems = ref<DraftRuleItem[]>([])
71
-
72
- const { parentRef } = useDnD<DraftRuleItem>({
73
- values: draftItems,
74
- dragHandle: '.u-report-rule-row__handle',
75
- plugins: [animations()]
76
- })
77
-
78
- const visible = computed({
79
- get: () => props.modelValue,
80
- set: (value: boolean) => emit('update:modelValue', value)
81
- })
82
-
83
- watch(
84
- () => props.modelValue,
85
- (open) => {
86
- if (!open) return
87
- draftItems.value = initDraftFromRules(props.rules, props.fieldType, props.bindingField)
88
- }
89
- )
90
-
91
- function addRule(): void {
92
- draftItems.value = [
93
- ...draftItems.value,
94
- createDraftItem(undefined, props.fieldType, props.bindingField)
95
- ]
96
- }
97
-
98
- function removeRule(index: number): void {
99
- draftItems.value = draftItems.value.filter((_, i) => i !== index)
100
- }
101
-
102
- function confirm(): void {
103
- emit('save', cloneRulesFromDraft(draftItems.value))
104
- visible.value = false
105
- }
106
-
107
- function onClose(): void {
108
- visible.value = false
109
- }
110
- </script>
@@ -1,128 +0,0 @@
1
- <template>
2
- <svg v-if="visible" :class="cls.b" :width="svgWidth" :height="svgHeight" aria-hidden="true">
3
- <defs>
4
- <marker
5
- :id="rowArrowMarkerId"
6
- markerWidth="8"
7
- markerHeight="8"
8
- refX="6"
9
- refY="4"
10
- orient="auto"
11
- markerUnits="strokeWidth"
12
- >
13
- <path d="M0,0 L8,4 L0,8 Z" fill="var(--u-color-primary, #2563eb)" />
14
- </marker>
15
- <marker
16
- :id="colArrowMarkerId"
17
- markerWidth="8"
18
- markerHeight="8"
19
- refX="6"
20
- refY="4"
21
- orient="auto"
22
- markerUnits="strokeWidth"
23
- >
24
- <path d="M0,0 L8,4 L0,8 Z" fill="var(--u-color-warning, #d97706)" />
25
- </marker>
26
- </defs>
27
- <path
28
- v-for="(item, index) in arcPaths"
29
- :key="index"
30
- :class="[cls.e('arc'), cls.em('arc', item.direction)]"
31
- :d="item.path"
32
- :marker-end="`url(#${item.direction === 'row' ? rowArrowMarkerId : colArrowMarkerId})`"
33
- />
34
- </svg>
35
- </template>
36
-
37
- <script lang="ts" setup>
38
- import type { CellAddress } from '@veltra/sheet-core'
39
- import type { SheetGrid } from '@veltra/sheet-core/grid'
40
- import { bem } from '@veltra/utils'
41
- import { computed, ref, toRef } from 'vue'
42
-
43
- import type { ReportBinding } from '../../../report/types'
44
- import {
45
- buildTopologyArcPath,
46
- collectTopologyLinks,
47
- type TopologyBindingEntry,
48
- type TopologyLinkDirection
49
- } from './topology'
50
- import { readCellOverlayRect, readGridOverlaySize, useGridOverlaySync } from './use-grid-overlay'
51
-
52
- defineOptions({ name: 'UReportTopologyOverlay' })
53
-
54
- const props = defineProps<{
55
- cell: CellAddress | null
56
- binding: ReportBinding | null
57
- entries: TopologyBindingEntry[]
58
- metaTick: number
59
- hostEl: HTMLElement | null
60
- getGrid: () => SheetGrid | undefined
61
- getBindingAt: (addr: CellAddress) => ReportBinding | undefined
62
- }>()
63
-
64
- const cls = bem('report-topology')
65
-
66
- const instanceId = Math.random().toString(36).slice(2, 8)
67
- const rowArrowMarkerId = `u-report-topology-row-arrow-${instanceId}`
68
- const colArrowMarkerId = `u-report-topology-col-arrow-${instanceId}`
69
-
70
- const hostEl = toRef(props, 'hostEl')
71
-
72
- const svgWidth = ref(0)
73
- const svgHeight = ref(0)
74
- const arcPaths = ref<Array<{ path: string; direction: TopologyLinkDirection }>>([])
75
- const inView = ref(false)
76
-
77
- const visible = computed(
78
- () => !!props.binding && !!props.cell && inView.value && arcPaths.value.length > 0
79
- )
80
-
81
- function updateOverlay(): void {
82
- const cell = props.cell
83
- const binding = props.binding
84
- const host = props.hostEl
85
- if (!cell || !binding || !host) {
86
- inView.value = false
87
- arcPaths.value = []
88
- return
89
- }
90
-
91
- const size = readGridOverlaySize(host)
92
- if (!size) {
93
- inView.value = false
94
- arcPaths.value = []
95
- return
96
- }
97
-
98
- svgWidth.value = size.width
99
- svgHeight.value = size.height
100
-
101
- const links = collectTopologyLinks(cell, binding, props.entries, props.getBindingAt)
102
- const paths: Array<{ path: string; direction: TopologyLinkDirection }> = []
103
-
104
- for (const link of links) {
105
- const fromRect = readCellOverlayRect(link.from, host, props.getGrid)
106
- const toRect = readCellOverlayRect(link.to, host, props.getGrid)
107
- if (!fromRect || !toRect) continue
108
- paths.push({
109
- direction: link.direction,
110
- path: buildTopologyArcPath(
111
- { x: fromRect.centerX, y: fromRect.centerY },
112
- { x: toRect.centerX, y: toRect.centerY }
113
- )
114
- })
115
- }
116
-
117
- arcPaths.value = paths
118
- inView.value = paths.length > 0
119
- }
120
-
121
- useGridOverlaySync({
122
- hostEl,
123
- getGrid: () => props.getGrid(),
124
- watchSources: () =>
125
- [props.cell?.row, props.cell?.col, props.binding, props.metaTick, props.hostEl] as const,
126
- update: updateOverlay
127
- })
128
- </script>
@@ -1,124 +0,0 @@
1
- import type { CellAddress } from '@veltra/sheet-core'
2
-
3
- import type { ReportBinding } from '../../../report/types'
4
-
5
- export type TopologyLinkDirection = 'row' | 'col'
6
-
7
- export interface TopologyLink {
8
- /** 子格(依赖方) */
9
- from: CellAddress
10
- /** 父绑定格 */
11
- to: CellAddress
12
- /** 连线方向:行方向父格 / 列方向父格 */
13
- direction: TopologyLinkDirection
14
- }
15
-
16
- export interface TopologyBindingEntry {
17
- addr: CellAddress
18
- binding: ReportBinding
19
- }
20
-
21
- function linkKey(from: CellAddress, to: CellAddress, direction: TopologyLinkDirection): string {
22
- return `${direction}:${from.row},${from.col}->${to.row},${to.col}`
23
- }
24
-
25
- function sameAddress(a: CellAddress, b: CellAddress): boolean {
26
- return a.row === b.row && a.col === b.col
27
- }
28
-
29
- function pushLink(
30
- links: TopologyLink[],
31
- seen: Set<string>,
32
- from: CellAddress,
33
- to: CellAddress,
34
- direction: TopologyLinkDirection
35
- ): void {
36
- const key = linkKey(from, to, direction)
37
- if (seen.has(key)) return
38
- links.push({ from, to, direction })
39
- seen.add(key)
40
- }
41
-
42
- /** 查找将指定格作为行方向父格的绑定格 */
43
- export function findCellsWithRowParent(
44
- parent: CellAddress,
45
- entries: readonly TopologyBindingEntry[]
46
- ): CellAddress[] {
47
- const children: CellAddress[] = []
48
- for (const { addr, binding } of entries) {
49
- if (binding.rowParent && sameAddress(binding.rowParent, parent)) {
50
- children.push(addr)
51
- }
52
- }
53
- return children
54
- }
55
-
56
- /** 查找将指定格作为列方向父格的绑定格 */
57
- export function findCellsWithColParent(
58
- parent: CellAddress,
59
- entries: readonly TopologyBindingEntry[]
60
- ): CellAddress[] {
61
- const children: CellAddress[] = []
62
- for (const { addr, binding } of entries) {
63
- if (binding.colParent && sameAddress(binding.colParent, parent)) {
64
- children.push(addr)
65
- }
66
- }
67
- return children
68
- }
69
-
70
- /** 收集选中格相关的拓扑连线:沿 rowParent / colParent 链上行、下行到直接子绑定 */
71
- export function collectTopologyLinks(
72
- cell: CellAddress,
73
- binding: ReportBinding,
74
- entries: readonly TopologyBindingEntry[],
75
- getBindingAt: (addr: CellAddress) => ReportBinding | undefined
76
- ): TopologyLink[] {
77
- const links: TopologyLink[] = []
78
- const seen = new Set<string>()
79
-
80
- let rowCursor = cell
81
- let rowBinding: ReportBinding | undefined = binding
82
- while (rowBinding?.rowParent) {
83
- const parent = rowBinding.rowParent
84
- pushLink(links, seen, rowCursor, parent, 'row')
85
- rowCursor = parent
86
- rowBinding = getBindingAt(parent)
87
- }
88
-
89
- let colCursor = cell
90
- let colBinding: ReportBinding | undefined = binding
91
- while (colBinding?.colParent) {
92
- const parent = colBinding.colParent
93
- pushLink(links, seen, colCursor, parent, 'col')
94
- colCursor = parent
95
- colBinding = getBindingAt(parent)
96
- }
97
-
98
- for (const child of findCellsWithRowParent(cell, entries)) {
99
- pushLink(links, seen, child, cell, 'row')
100
- }
101
-
102
- for (const child of findCellsWithColParent(cell, entries)) {
103
- pushLink(links, seen, child, cell, 'col')
104
- }
105
-
106
- return links
107
- }
108
-
109
- /** @deprecated 05 将删除;暂保留别名 */
110
- export const findCellsWithLeftParent = findCellsWithRowParent
111
-
112
- /** 子格 → 父格的二次贝塞尔弧线路径 */
113
- export function buildTopologyArcPath(
114
- from: { x: number; y: number },
115
- to: { x: number; y: number }
116
- ): string {
117
- const dx = to.x - from.x
118
- const dy = to.y - from.y
119
- const distance = Math.hypot(dx, dy) || 1
120
- const lift = Math.min(48, Math.max(16, distance * 0.35))
121
- const cx = (from.x + to.x) / 2
122
- const cy = (from.y + to.y) / 2 - lift * Math.sign(dy || -1)
123
- return `M ${from.x} ${from.y} Q ${cx} ${cy} ${to.x} ${to.y}`
124
- }