@veltra/sheet 2.1.1 → 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 +100 -0
- package/dist/components/report/designer/cell-coords.js +46 -0
- package/dist/components/report/designer/col-widths.js +29 -0
- package/dist/components/report/designer/conditional-rules/helpers.js +139 -0
- package/dist/components/report/designer/drop-highlight-overlay.js +76 -0
- package/dist/components/report/designer/float-panel.js +422 -0
- package/dist/components/report/designer/role.js +17 -0
- package/dist/components/report/designer/rule-preview.js +33 -0
- package/dist/components/report/designer/rule-row.js +253 -0
- package/dist/components/report/designer/rules-dialog.js +118 -0
- package/dist/components/report/designer/topology-overlay.js +122 -0
- package/dist/components/report/designer/topology.js +64 -0
- package/dist/components/report/designer/use-grid-overlay.js +75 -0
- package/dist/components/report/designer-hub.js +177 -0
- package/dist/components/report/field-panel-helpers.js +33 -0
- package/dist/components/report/field-panel.js +132 -0
- package/dist/components/report/filter-bar.js +74 -0
- package/dist/components/report/hub-connection-form.js +145 -0
- package/dist/components/report/hub-dataset-editor.js +394 -0
- package/dist/components/report/preview-grid-size.js +32 -0
- package/dist/components/report/report-designer.js +393 -0
- package/dist/components/report/report-viewer.js +124 -0
- package/dist/components/report/style.d.ts +17 -0
- package/dist/components/report/style.js +18 -0
- package/dist/components/report/style2.css +874 -0
- package/dist/components/report/use-report-designer.js +382 -0
- package/dist/components/report/use-report-viewer.js +55 -0
- package/dist/{vue → components/sheet}/formula-bar.js +1 -1
- package/dist/{vue → components/sheet}/formula-suggest-list.js +1 -1
- package/dist/{vue → components/sheet}/import-file.js +1 -1
- package/dist/{vue → components/sheet}/insert-count-menu-item.js +1 -1
- package/dist/{vue → components/sheet}/insert-image.js +1 -1
- package/dist/{vue → components/sheet}/popup-helpers.js +1 -1
- package/dist/{vue → components/sheet}/popups/border-popup.js +31 -3
- package/dist/{vue → components/sheet}/popups/border-preset-glyph.js +1 -1
- package/dist/{vue → components/sheet}/popups/export-popup.js +2 -2
- package/dist/{vue → components/sheet}/popups/fill-color-popup.js +3 -3
- package/dist/{vue → components/sheet}/popups/find-popup.js +1 -1
- package/dist/{vue → components/sheet}/popups/font-color-popup.js +3 -3
- package/dist/{vue → components/sheet}/popups/font-size-popup.js +3 -3
- package/dist/{vue → components/sheet}/popups/import.worker.d.ts +1 -1
- package/dist/{vue → components/sheet}/popups/import.worker.js +1 -1
- package/dist/{vue → components/sheet}/popups/insert-image-popup.js +1 -1
- package/dist/{vue → components/sheet}/sheet-context-menu.js +7 -4
- package/dist/{vue → components/sheet}/sheet-tabs.js +1 -1
- package/dist/{vue → components/sheet}/sheet-toolbar.js +1 -1
- package/dist/{vue → components/sheet}/sheet.js +16 -1
- package/dist/{vue → components/sheet}/use-find-replace.js +1 -1
- package/dist/{vue → components/sheet}/use-formula-ref-select.js +1 -1
- package/dist/{vue → components/sheet}/use-sheet-grid.js +22 -4
- package/dist/{vue → components/sheet}/use-sheet-state.js +6 -5
- package/dist/{vue → components/sheet}/use-sheet-tabs-bar.js +1 -1
- package/dist/{vue → components/sheet}/use-tool-groups.js +2 -2
- package/dist/{vue → components/sheet}/use-tool-popup.js +2 -2
- package/dist/{vue → components/sheet}/use-toolbar-scroll.js +1 -1
- package/dist/index.d.ts +17 -3
- package/dist/index.js +13 -3
- package/dist/report/binding.d.ts +83 -0
- package/dist/report/binding.js +332 -0
- package/dist/report/connector.d.ts +66 -0
- package/dist/report/connector.js +159 -0
- package/dist/report/export-xlsx.d.ts +17 -0
- package/dist/report/export-xlsx.js +92 -0
- package/dist/report/filter-bar.d.ts +10 -0
- package/dist/report/filter-bar.js +31 -0
- package/dist/report/index.d.ts +12 -0
- package/dist/report/params.d.ts +13 -0
- package/dist/report/params.js +61 -0
- package/dist/report/render/aggregate.js +169 -0
- package/dist/report/render/builder.d.ts +12 -0
- package/dist/report/render/builder.js +139 -0
- package/dist/report/render/coordinate.d.ts +25 -0
- package/dist/report/render/coordinate.js +733 -0
- package/dist/report/render/index.d.ts +14 -0
- package/dist/report/render/index.js +21 -0
- package/dist/report/render/style-resolver.js +50 -0
- package/dist/report/render/template-index.d.ts +24 -0
- package/dist/report/render/template-index.js +137 -0
- package/dist/report/rules/evaluate.d.ts +25 -0
- package/dist/report/rules/evaluate.js +82 -0
- package/dist/report/rules/index.d.ts +2 -0
- package/dist/report/template.d.ts +63 -0
- package/dist/report/template.js +83 -0
- package/dist/report/types.d.ts +85 -0
- package/dist/tools/apply-style.js +29 -0
- package/dist/tools/builtin.js +17 -17
- package/dist/tools/context.d.ts +39 -6
- package/dist/tools/context.js +38 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/report-designer.d.ts +38 -0
- package/dist/types/report-viewer.d.ts +38 -0
- package/dist/types/sheet.d.ts +21 -1
- package/package.json +6 -8
- package/src/components/report/__test__/conditional-rules-helpers.test.ts +88 -0
- package/src/components/report/__test__/field-panel-helpers.test.ts +65 -0
- package/src/components/report/__test__/float-panel-position.test.ts +45 -0
- package/src/components/report/__test__/preview-grid-size.test.ts +63 -0
- package/src/components/report/__test__/report-designer.test.ts +464 -0
- package/src/components/report/__test__/report-viewer.test.ts +357 -0
- package/src/components/report/__test__/topology.test.ts +143 -0
- package/src/components/report/__test__/use-report-designer.test.ts +697 -0
- package/src/components/report/binding-badge.ts +92 -0
- package/src/components/report/designer/cell-coords.ts +94 -0
- package/src/components/report/designer/col-widths.ts +56 -0
- package/src/components/report/designer/conditional-rules/helpers.ts +189 -0
- package/src/components/report/designer/drop-highlight-overlay.vue +74 -0
- package/src/components/report/designer/float-panel.vue +431 -0
- package/src/components/report/designer/role.ts +32 -0
- package/src/components/report/designer/rule-preview.vue +40 -0
- package/src/components/report/designer/rule-row.vue +252 -0
- package/src/components/report/designer/rules-dialog.vue +110 -0
- package/src/components/report/designer/topology-overlay.vue +127 -0
- package/src/components/report/designer/topology.ts +124 -0
- package/src/components/report/designer/use-grid-overlay.ts +113 -0
- package/src/components/report/designer-hub.vue +198 -0
- package/src/components/report/field-panel-helpers.ts +44 -0
- package/src/components/report/field-panel.vue +136 -0
- package/src/components/report/filter-bar.vue +77 -0
- package/src/components/report/hub-connection-form.vue +124 -0
- package/src/components/report/hub-dataset-editor.vue +369 -0
- package/src/components/report/index.ts +2 -0
- package/src/components/report/preview-grid-size.ts +33 -0
- package/src/components/report/report-designer.vue +420 -0
- package/src/components/report/report-viewer.vue +136 -0
- package/src/components/report/style-designer.scss +587 -0
- package/src/components/report/style-hub.scss +292 -0
- package/src/components/report/style-viewer.scss +104 -0
- package/src/components/report/style.scss +3 -0
- package/src/components/report/style.ts +20 -0
- package/src/components/report/use-report-designer.ts +596 -0
- package/src/components/report/use-report-viewer.ts +88 -0
- package/src/{vue → components/sheet}/__test__/formula-bar.test.ts +37 -18
- package/src/{vue → components/sheet}/__test__/insert-image-popup.test.ts +1 -1
- package/src/{vue → components/sheet}/__test__/insert-image.test.ts +1 -1
- package/src/{vue → components/sheet}/__test__/sheet-component.test.ts +77 -3
- package/src/{vue → components/sheet}/__test__/sheet-context-menu.test.ts +4 -3
- package/src/{vue → components/sheet}/formula-bar.vue +1 -1
- package/src/{vue → components/sheet}/insert-image.ts +1 -1
- package/src/{vue → components/sheet}/popup-helpers.ts +1 -1
- package/src/{vue → components/sheet}/popups/border-popup.vue +32 -4
- package/src/{vue → components/sheet}/popups/export-popup.vue +2 -2
- package/src/{vue → components/sheet}/popups/fill-color-popup.vue +3 -3
- package/src/{vue → components/sheet}/popups/find-popup.vue +1 -1
- package/src/{vue → components/sheet}/popups/font-color-popup.vue +3 -3
- package/src/{vue → components/sheet}/popups/font-size-popup.vue +3 -3
- package/src/{vue → components/sheet}/popups/insert-image-popup.vue +1 -1
- package/src/{vue → components/sheet}/sheet-context-menu.ts +10 -4
- package/src/{vue → components/sheet}/sheet-toolbar.vue +2 -2
- package/src/{vue → components/sheet}/sheet.vue +6 -3
- package/src/{vue → components/sheet}/use-find-replace.ts +1 -1
- package/src/{vue → components/sheet}/use-sheet-grid.ts +23 -5
- package/src/{vue → components/sheet}/use-sheet-state.ts +11 -6
- package/src/{vue → components/sheet}/use-tool-groups.ts +2 -2
- package/src/{vue → components/sheet}/use-tool-popup.ts +2 -2
- package/src/index.ts +24 -188
- package/src/report/__test__/binding.test.ts +320 -0
- package/src/report/__test__/connector.test.ts +239 -0
- package/src/report/__test__/export-xlsx.test.ts +118 -0
- package/src/report/__test__/filter-bar.test.ts +58 -0
- package/src/report/__test__/params.test.ts +156 -0
- package/src/report/__test__/render.test.ts +830 -0
- package/src/report/__test__/rules.test.ts +131 -0
- package/src/report/__test__/template.test.ts +366 -0
- package/src/report/binding.ts +474 -0
- package/src/report/connector.ts +206 -0
- package/src/report/export-xlsx.ts +113 -0
- package/src/report/filter-bar.ts +36 -0
- package/src/report/index.ts +13 -0
- package/src/report/params.ts +96 -0
- package/src/report/render/__test__/aggregate.test.ts +16 -0
- package/src/report/render/__test__/coordinate.test.ts +405 -0
- package/src/report/render/aggregate.ts +261 -0
- package/src/report/render/builder.ts +211 -0
- package/src/report/render/coordinate.ts +1239 -0
- package/src/report/render/index.ts +30 -0
- package/src/report/render/style-resolver.ts +68 -0
- package/src/report/render/template-index.ts +200 -0
- package/src/report/rules/evaluate.ts +113 -0
- package/src/report/rules/index.ts +7 -0
- package/src/report/template.ts +145 -0
- package/src/report/types.ts +96 -0
- package/src/tools/__test__/apply-style.test.ts +129 -0
- package/src/tools/apply-style.ts +41 -0
- package/src/tools/builtin.ts +20 -20
- package/src/tools/context.ts +67 -4
- package/src/tools/index.ts +1 -0
- package/src/types/index.ts +2 -6
- package/src/types/report-designer.ts +38 -0
- package/src/types/report-viewer.ts +37 -0
- package/src/types/sheet.ts +26 -1
- /package/dist/{vue → components/sheet}/style.d.ts +0 -0
- /package/dist/{vue → components/sheet}/style.js +0 -0
- /package/dist/{vue → components/sheet}/style2.css +0 -0
- /package/dist/{vue → components/sheet}/use-formula-suggest.js +0 -0
- /package/src/{vue → components/sheet}/__test__/import-file.test.ts +0 -0
- /package/src/{vue → components/sheet}/__test__/insert-count-menu-item.test.ts +0 -0
- /package/src/{vue → components/sheet}/__test__/use-formula-ref-select.test.ts +0 -0
- /package/src/{vue → components/sheet}/__test__/use-formula-suggest.test.ts +0 -0
- /package/src/{vue → components/sheet}/formula-suggest-list.vue +0 -0
- /package/src/{vue → components/sheet}/import-file.ts +0 -0
- /package/src/{vue → components/sheet}/index.ts +0 -0
- /package/src/{vue → components/sheet}/insert-count-menu-item.vue +0 -0
- /package/src/{vue → components/sheet}/parsing.ts +0 -0
- /package/src/{vue → components/sheet}/popups/border-preset-glyph.vue +0 -0
- /package/src/{vue → components/sheet}/popups/import.worker.ts +0 -0
- /package/src/{vue → components/sheet}/sheet-tabs.vue +0 -0
- /package/src/{vue → components/sheet}/style.scss +0 -0
- /package/src/{vue → components/sheet}/style.ts +0 -0
- /package/src/{vue → components/sheet}/use-formula-ref-select.ts +0 -0
- /package/src/{vue → components/sheet}/use-formula-suggest.ts +0 -0
- /package/src/{vue → components/sheet}/use-sheet-tabs-bar.ts +0 -0
- /package/src/{vue → components/sheet}/use-toolbar-scroll.ts +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { CellAddress } from '@veltra/sheet-core'
|
|
2
|
+
import {
|
|
3
|
+
CustomLayout,
|
|
4
|
+
type ResolveCellRenderer,
|
|
5
|
+
type ResolveCellStyleHook
|
|
6
|
+
} from '@veltra/sheet-core'
|
|
7
|
+
|
|
8
|
+
import { formatBindingPlaceholderParts, inferReportPreset } from '../../report/binding'
|
|
9
|
+
import type { ReportBinding, ReportPreset } from '../../report/types'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 绑定徽章预设配色(设计态富渲染徽章;canvas 绘制需具体色值,无法消费 CSS 变量)。
|
|
13
|
+
* `cell` 为整格极浅底色,`fg` 为强调色(聚合标签徽章底色)。
|
|
14
|
+
*/
|
|
15
|
+
export interface ReportBadgeColors {
|
|
16
|
+
cell: string
|
|
17
|
+
fg: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const REPORT_PRESET_BADGE_COLORS: Record<ReportPreset, ReportBadgeColors> = {
|
|
21
|
+
groupHeader: { cell: '#eef4ff', fg: '#2f54eb' },
|
|
22
|
+
detail: { cell: '#effaf3', fg: '#1f9254' },
|
|
23
|
+
subtotal: { cell: '#fff7e6', fg: '#d46b08' },
|
|
24
|
+
grandTotal: { cell: '#fff1f0', fg: '#cf1322' },
|
|
25
|
+
cross: { cell: '#f8f5ff', fg: '#5f3dc4' }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const CUSTOM_PRESET_BADGE_COLOR: ReportBadgeColors = { cell: '#f7f8fa', fg: '#495057' }
|
|
29
|
+
|
|
30
|
+
/** 徽章字段标签文字色(中性深灰,与强调色形成层次) */
|
|
31
|
+
const BADGE_LABEL_COLOR = '#343a40'
|
|
32
|
+
|
|
33
|
+
/** @deprecated 05 将更名 */
|
|
34
|
+
export const REPORT_ROLE_BADGE_COLORS = REPORT_PRESET_BADGE_COLORS
|
|
35
|
+
|
|
36
|
+
function resolveBadgeColor(binding: ReportBinding): ReportBadgeColors {
|
|
37
|
+
const preset = inferReportPreset(binding)
|
|
38
|
+
return preset ? REPORT_PRESET_BADGE_COLORS[preset] : CUSTOM_PRESET_BADGE_COLOR
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 绑定格角色底色(走 resolveCellStyle,保留 VTable 默认边框绘制) */
|
|
42
|
+
export function createBindingBadgeStyleResolver(
|
|
43
|
+
getBindingAt: (addr: CellAddress) => ReportBinding | undefined
|
|
44
|
+
): ResolveCellStyleHook {
|
|
45
|
+
return (addr, base) => {
|
|
46
|
+
const binding = getBindingAt(addr)
|
|
47
|
+
if (!binding) return base
|
|
48
|
+
|
|
49
|
+
const { cell } = resolveBadgeColor(binding)
|
|
50
|
+
return { ...base, fill: { color: cell } }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 绑定格富渲染徽章(ADR-0004 `resolveCellRenderer` 首个消费者):
|
|
56
|
+
* 绑定单元格渲染为左对齐徽章(强调色聚合标签 + 中性色字段标签),
|
|
57
|
+
* 未绑定格返回 `undefined` 回落默认渲染。
|
|
58
|
+
*/
|
|
59
|
+
export function createBindingBadgeRenderer(
|
|
60
|
+
getBindingAt: (addr: CellAddress) => ReportBinding | undefined,
|
|
61
|
+
resolveLabel?: (datasetId: string, fieldName: string) => string
|
|
62
|
+
): ResolveCellRenderer {
|
|
63
|
+
return (addr) => {
|
|
64
|
+
const binding = getBindingAt(addr)
|
|
65
|
+
if (!binding) return undefined
|
|
66
|
+
|
|
67
|
+
const { fg } = resolveBadgeColor(binding)
|
|
68
|
+
const { tag, label } = formatBindingPlaceholderParts(binding, resolveLabel)
|
|
69
|
+
|
|
70
|
+
const rootContainer = new CustomLayout.Container({
|
|
71
|
+
display: 'flex',
|
|
72
|
+
flexDirection: 'row',
|
|
73
|
+
justifyContent: 'flex-start',
|
|
74
|
+
alignItems: 'center'
|
|
75
|
+
})
|
|
76
|
+
// VTable 布局层 Tag 组件(vrender-components)与其 INode 声明存在结构性类型偏差,
|
|
77
|
+
// 运行时完全兼容(VTable 官方 customLayout 徽章用法即如此),此处仅收敛类型
|
|
78
|
+
const tagEl = new CustomLayout.Tag({
|
|
79
|
+
text: tag,
|
|
80
|
+
textStyle: { fontSize: 10, fontWeight: 600, fill: '#ffffff' },
|
|
81
|
+
panel: { visible: true, fill: fg, cornerRadius: 3 },
|
|
82
|
+
padding: [1, 4, 1, 4],
|
|
83
|
+
marginLeft: 6,
|
|
84
|
+
marginRight: 5
|
|
85
|
+
})
|
|
86
|
+
rootContainer.add(tagEl as unknown as Parameters<typeof rootContainer.add>[0])
|
|
87
|
+
rootContainer.add(
|
|
88
|
+
new CustomLayout.Text({ text: label, fontSize: 11, fontWeight: 500, fill: BADGE_LABEL_COLOR })
|
|
89
|
+
)
|
|
90
|
+
return { rootContainer, renderDefault: false }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { CellAddress, SheetGrid } from '@veltra/sheet-core'
|
|
2
|
+
|
|
3
|
+
export interface CellOverlayRect {
|
|
4
|
+
left: number
|
|
5
|
+
top: number
|
|
6
|
+
right: number
|
|
7
|
+
bottom: number
|
|
8
|
+
width: number
|
|
9
|
+
height: number
|
|
10
|
+
centerX: number
|
|
11
|
+
centerY: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface GridOverlayLayout {
|
|
15
|
+
offsetX: number
|
|
16
|
+
offsetY: number
|
|
17
|
+
viewW: number
|
|
18
|
+
viewH: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** VTable 坐标 = 模型地址 + 行号列/列头偏移(通常 +1) */
|
|
22
|
+
export const TABLE_ADDR_OFFSET = 1
|
|
23
|
+
|
|
24
|
+
export function resolveGridOverlayLayout(
|
|
25
|
+
host: HTMLElement
|
|
26
|
+
): { gridEl: HTMLElement; layout: GridOverlayLayout } | null {
|
|
27
|
+
const gridEl = host.querySelector('.u-sheet__grid-instance') as HTMLElement | null
|
|
28
|
+
if (!gridEl) return null
|
|
29
|
+
|
|
30
|
+
const hostBox = host.getBoundingClientRect()
|
|
31
|
+
const gridBox = gridEl.getBoundingClientRect()
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
gridEl,
|
|
35
|
+
layout: {
|
|
36
|
+
offsetX: gridBox.left - hostBox.left,
|
|
37
|
+
offsetY: gridBox.top - hostBox.top,
|
|
38
|
+
viewW: gridEl.clientWidth,
|
|
39
|
+
viewH: gridEl.clientHeight
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 将屏幕坐标换算为网格落点地址(用于字段拖拽 bind) */
|
|
45
|
+
export function resolveGridDropAddress(
|
|
46
|
+
grid: SheetGrid,
|
|
47
|
+
gridEl: HTMLElement,
|
|
48
|
+
clientX: number,
|
|
49
|
+
clientY: number
|
|
50
|
+
): CellAddress | null {
|
|
51
|
+
const rect = gridEl.getBoundingClientRect()
|
|
52
|
+
return grid.hitTestSheetAddr(clientX - rect.left, clientY - rect.top) ?? null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getCellOverlayRect(
|
|
56
|
+
grid: SheetGrid,
|
|
57
|
+
cell: CellAddress,
|
|
58
|
+
layout: GridOverlayLayout
|
|
59
|
+
): CellOverlayRect | null {
|
|
60
|
+
const table = grid.getTable()
|
|
61
|
+
let cellRect: { left: number; top: number; right: number; bottom: number }
|
|
62
|
+
try {
|
|
63
|
+
cellRect = table.getCellRelativeRect(
|
|
64
|
+
cell.col + TABLE_ADDR_OFFSET,
|
|
65
|
+
cell.row + TABLE_ADDR_OFFSET
|
|
66
|
+
) as { left: number; top: number; right: number; bottom: number }
|
|
67
|
+
} catch {
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const left = layout.offsetX + cellRect.left
|
|
72
|
+
const top = layout.offsetY + cellRect.top
|
|
73
|
+
const right = layout.offsetX + cellRect.right
|
|
74
|
+
const bottom = layout.offsetY + cellRect.bottom
|
|
75
|
+
|
|
76
|
+
const outOfView =
|
|
77
|
+
cellRect.bottom < 0 ||
|
|
78
|
+
cellRect.right < 0 ||
|
|
79
|
+
cellRect.top > layout.viewH ||
|
|
80
|
+
cellRect.left > layout.viewW
|
|
81
|
+
|
|
82
|
+
if (outOfView) return null
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
left,
|
|
86
|
+
top,
|
|
87
|
+
right,
|
|
88
|
+
bottom,
|
|
89
|
+
width: right - left,
|
|
90
|
+
height: bottom - top,
|
|
91
|
+
centerX: (left + right) / 2,
|
|
92
|
+
centerY: (top + bottom) / 2
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Sheet } from '@veltra/sheet-core'
|
|
2
|
+
|
|
3
|
+
import type { ReportColWidthEntry } from '../../../report/export-xlsx'
|
|
4
|
+
|
|
5
|
+
/** VTable 列宽读写所需的最小表接口(含行号列探测,隐藏行头时偏移为 0) */
|
|
6
|
+
type VTableColWidthApi = {
|
|
7
|
+
setColWidth: (col: number, width: number) => void
|
|
8
|
+
getColWidth: (col: number) => number
|
|
9
|
+
readonly colCount: number
|
|
10
|
+
readonly columnHeaderLevelCount: number
|
|
11
|
+
isSeriesNumber: (col: number, row: number) => boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** 列宽读写目标:Sheet 模型或 SheetGrid.getTable() 的最小接口(headless 测试可桩) */
|
|
15
|
+
export type ColWidthTarget = { getTable: () => VTableColWidthApi }
|
|
16
|
+
|
|
17
|
+
/** 行号列数(隐藏行头时为 0;与 GridCoords 探测一致) */
|
|
18
|
+
function seriesNumberColCount(table: VTableColWidthApi): number {
|
|
19
|
+
const rowOffset = table.columnHeaderLevelCount
|
|
20
|
+
let col = 0
|
|
21
|
+
while (col < table.colCount && table.isSeriesNumber(col, rowOffset)) col++
|
|
22
|
+
return col
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 将模型列宽写入 VTable(模型 → grid 同步辅助) */
|
|
26
|
+
export function applyGridColWidths(
|
|
27
|
+
grid: ColWidthTarget | undefined | null,
|
|
28
|
+
widths: ReadonlyArray<ReportColWidthEntry>
|
|
29
|
+
): void {
|
|
30
|
+
if (!grid) return
|
|
31
|
+
const table = grid.getTable()
|
|
32
|
+
const colOffset = seriesNumberColCount(table)
|
|
33
|
+
for (const [sheetCol, width] of widths) {
|
|
34
|
+
table.setColWidth(sheetCol + colOffset, width)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 把列宽条目写入 Sheet 模型(不进 undo,对称 setColWidth) */
|
|
39
|
+
export function applySheetColWidths(
|
|
40
|
+
sheet: Sheet | undefined | null,
|
|
41
|
+
widths: ReadonlyArray<ReportColWidthEntry>
|
|
42
|
+
): void {
|
|
43
|
+
if (!sheet) return
|
|
44
|
+
for (const [col, width] of widths) sheet.setColWidth(col, width)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 从当前网格读取指定模型列宽度;网格未就绪时返回 null */
|
|
48
|
+
export function readGridColWidths(
|
|
49
|
+
grid: ColWidthTarget | undefined | null,
|
|
50
|
+
cols: ReadonlyArray<number>
|
|
51
|
+
): Array<[number, number]> | null {
|
|
52
|
+
if (!grid) return null
|
|
53
|
+
const table = grid.getTable()
|
|
54
|
+
const colOffset = seriesNumberColCount(table)
|
|
55
|
+
return cols.map((sheetCol) => [sheetCol, table.getColWidth(sheetCol + colOffset)])
|
|
56
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { ConditionalOperator, ConditionalRule, DatasetField } from '../../../../report/types'
|
|
2
|
+
|
|
3
|
+
export interface DraftRuleItem {
|
|
4
|
+
id: string
|
|
5
|
+
rule: ConditionalRule
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const OPERATOR_LABELS: Record<ConditionalOperator, string> = {
|
|
9
|
+
gt: '大于',
|
|
10
|
+
gte: '大于等于',
|
|
11
|
+
lt: '小于',
|
|
12
|
+
lte: '小于等于',
|
|
13
|
+
eq: '等于',
|
|
14
|
+
between: '介于',
|
|
15
|
+
contains: '包含'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const RULE_SCOPE_OPTIONS = [
|
|
19
|
+
{ value: 'cell' as const, label: '本格' },
|
|
20
|
+
{ value: 'row' as const, label: '整行' }
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
/** 求值字段下拉的「本格字段」哨兵值(USelect 无法匹配 undefined) */
|
|
24
|
+
export const SELF_FIELD_VALUE = ''
|
|
25
|
+
|
|
26
|
+
/** 求值字段下拉:缺省项表示本格绑定字段 */
|
|
27
|
+
export function ruleEvalFieldOptions(
|
|
28
|
+
bindingField: string,
|
|
29
|
+
datasetFields: readonly DatasetField[]
|
|
30
|
+
): Array<{ value: string; label: string }> {
|
|
31
|
+
return [
|
|
32
|
+
{ value: SELF_FIELD_VALUE, label: '本格字段' },
|
|
33
|
+
...datasetFields
|
|
34
|
+
.filter((field) => field.name !== bindingField)
|
|
35
|
+
.map((field) => ({ value: field.name, label: field.label || field.name }))
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 按规则所选求值字段解析字段类型(运算符映射用) */
|
|
40
|
+
export function resolveEvalFieldType(
|
|
41
|
+
rule: ConditionalRule,
|
|
42
|
+
bindingField: string,
|
|
43
|
+
datasetFields: readonly DatasetField[]
|
|
44
|
+
): DatasetField['type'] {
|
|
45
|
+
const fieldName = rule.field ?? bindingField
|
|
46
|
+
return datasetFields.find((field) => field.name === fieldName)?.type ?? 'number'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const NUMERIC_OPERATORS: ConditionalOperator[] = ['gt', 'gte', 'lt', 'lte', 'eq', 'between']
|
|
50
|
+
const STRING_OPERATORS: ConditionalOperator[] = ['eq', 'contains']
|
|
51
|
+
|
|
52
|
+
let draftIdSeq = 0
|
|
53
|
+
|
|
54
|
+
function nextDraftId(): string {
|
|
55
|
+
draftIdSeq += 1
|
|
56
|
+
return `rule-${draftIdSeq}`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 按字段类型返回可用运算符 */
|
|
60
|
+
export function operatorsForFieldType(
|
|
61
|
+
fieldType: DatasetField['type']
|
|
62
|
+
): Array<{ value: ConditionalOperator; label: string }> {
|
|
63
|
+
const ops = fieldType === 'string' ? STRING_OPERATORS : NUMERIC_OPERATORS
|
|
64
|
+
return ops.map((value) => ({ value, label: OPERATOR_LABELS[value] }))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isNumericFieldType(fieldType: DatasetField['type']): boolean {
|
|
68
|
+
return fieldType === 'number' || fieldType === 'date'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function defaultRuleValue(
|
|
72
|
+
operator: ConditionalOperator,
|
|
73
|
+
fieldType: DatasetField['type']
|
|
74
|
+
): unknown {
|
|
75
|
+
if (operator === 'between') {
|
|
76
|
+
return isNumericFieldType(fieldType) ? [0, 100] : ['', '']
|
|
77
|
+
}
|
|
78
|
+
if (operator === 'contains' || (operator === 'eq' && fieldType === 'string')) {
|
|
79
|
+
return ''
|
|
80
|
+
}
|
|
81
|
+
return 0
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function defaultRuleStyle(): ConditionalRule['style'] {
|
|
85
|
+
return { fill: { color: '#FEF3C7' }, font: { color: '#B45309' } }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createDraftItem(
|
|
89
|
+
rule?: ConditionalRule,
|
|
90
|
+
fieldType: DatasetField['type'] = 'number',
|
|
91
|
+
_bindingField?: string
|
|
92
|
+
): DraftRuleItem {
|
|
93
|
+
if (rule) {
|
|
94
|
+
return { id: nextDraftId(), rule: cloneRule(rule) }
|
|
95
|
+
}
|
|
96
|
+
const operator = operatorsForFieldType(fieldType)[0]?.value ?? 'eq'
|
|
97
|
+
return {
|
|
98
|
+
id: nextDraftId(),
|
|
99
|
+
rule: { operator, value: defaultRuleValue(operator, fieldType), style: defaultRuleStyle() }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function initDraftFromRules(
|
|
104
|
+
rules: readonly ConditionalRule[],
|
|
105
|
+
fieldType: DatasetField['type'],
|
|
106
|
+
bindingField?: string
|
|
107
|
+
): DraftRuleItem[] {
|
|
108
|
+
if (rules.length === 0) return []
|
|
109
|
+
return rules.map((rule) => createDraftItem(rule, fieldType, bindingField))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function cloneRule(rule: ConditionalRule): ConditionalRule {
|
|
113
|
+
return {
|
|
114
|
+
operator: rule.operator,
|
|
115
|
+
value: cloneRuleValue(rule.value),
|
|
116
|
+
field: rule.field,
|
|
117
|
+
scope: rule.scope,
|
|
118
|
+
style: {
|
|
119
|
+
...rule.style,
|
|
120
|
+
fill: rule.style.fill ? { ...rule.style.fill } : undefined,
|
|
121
|
+
font: rule.style.font ? { ...rule.style.font } : undefined
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function cloneRuleValue(value: unknown): unknown {
|
|
127
|
+
if (Array.isArray(value)) return [...value]
|
|
128
|
+
return value
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function cloneRulesFromDraft(items: readonly DraftRuleItem[]): ConditionalRule[] {
|
|
132
|
+
return items.map((item) => cloneRule(item.rule))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 运算符切换时重置比较值为合理缺省 */
|
|
136
|
+
export function coerceValueForOperator(
|
|
137
|
+
operator: ConditionalOperator,
|
|
138
|
+
fieldType: DatasetField['type'],
|
|
139
|
+
current: unknown
|
|
140
|
+
): unknown {
|
|
141
|
+
if (operator === 'between') {
|
|
142
|
+
if (Array.isArray(current) && current.length === 2) return [...current]
|
|
143
|
+
return defaultRuleValue('between', fieldType)
|
|
144
|
+
}
|
|
145
|
+
if (operator === 'contains') {
|
|
146
|
+
return typeof current === 'string' ? current : ''
|
|
147
|
+
}
|
|
148
|
+
if (isNumericFieldType(fieldType)) {
|
|
149
|
+
const n = typeof current === 'number' && Number.isFinite(current) ? current : Number(current)
|
|
150
|
+
return Number.isFinite(n) ? n : 0
|
|
151
|
+
}
|
|
152
|
+
return typeof current === 'string' ? current : ''
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function toDisplayString(value: unknown): string {
|
|
156
|
+
if (typeof value === 'string') return value
|
|
157
|
+
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
|
|
158
|
+
return String(value)
|
|
159
|
+
}
|
|
160
|
+
return ''
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function readBetweenValue(
|
|
164
|
+
value: unknown,
|
|
165
|
+
index: 0 | 1,
|
|
166
|
+
fieldType: DatasetField['type']
|
|
167
|
+
): number | string {
|
|
168
|
+
const pair =
|
|
169
|
+
Array.isArray(value) && value.length === 2 ? value : defaultRuleValue('between', fieldType)
|
|
170
|
+
const raw = (pair as [unknown, unknown])[index]
|
|
171
|
+
if (isNumericFieldType(fieldType)) {
|
|
172
|
+
const n = typeof raw === 'number' ? raw : Number(raw)
|
|
173
|
+
return Number.isFinite(n) ? n : 0
|
|
174
|
+
}
|
|
175
|
+
return typeof raw === 'string' ? raw : toDisplayString(raw)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function writeBetweenValue(
|
|
179
|
+
value: unknown,
|
|
180
|
+
index: 0 | 1,
|
|
181
|
+
next: number | string,
|
|
182
|
+
fieldType: DatasetField['type']
|
|
183
|
+
): [unknown, unknown] {
|
|
184
|
+
const current = readBetweenValue(value, 0, fieldType)
|
|
185
|
+
const currentEnd = readBetweenValue(value, 1, fieldType)
|
|
186
|
+
const pair: [unknown, unknown] = [current, currentEnd]
|
|
187
|
+
pair[index] = next
|
|
188
|
+
return pair
|
|
189
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="cell && visible" :class="cls.b" :style="highlightStyle" aria-hidden="true" />
|
|
3
|
+
<div v-else-if="showFallback" :class="cls.e('fallback')" role="status">
|
|
4
|
+
松开将绑定到当前选区 {{ fallbackLabel }}
|
|
5
|
+
</div>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script lang="ts" setup>
|
|
9
|
+
import type { CellAddress, SheetGrid } from '@veltra/sheet-core'
|
|
10
|
+
import { bem } from '@veltra/utils'
|
|
11
|
+
import { computed, ref, toRef } from 'vue'
|
|
12
|
+
|
|
13
|
+
import { readCellOverlayRect, useGridOverlaySync } from './use-grid-overlay'
|
|
14
|
+
|
|
15
|
+
defineOptions({ name: 'UReportDropHighlightOverlay' })
|
|
16
|
+
|
|
17
|
+
const props = defineProps<{
|
|
18
|
+
cell: CellAddress | null
|
|
19
|
+
hostEl: HTMLElement | null
|
|
20
|
+
getGrid: () => SheetGrid | undefined
|
|
21
|
+
/** 字段拖拽进行中(dragover 时为 true) */
|
|
22
|
+
dragging: boolean
|
|
23
|
+
/** hit-test 落空时提示回退的选区标签(如 A1) */
|
|
24
|
+
fallbackLabel: string
|
|
25
|
+
}>()
|
|
26
|
+
|
|
27
|
+
const cls = bem('report-drop-highlight')
|
|
28
|
+
|
|
29
|
+
const hostEl = toRef(props, 'hostEl')
|
|
30
|
+
|
|
31
|
+
const rectLeft = ref(0)
|
|
32
|
+
const rectTop = ref(0)
|
|
33
|
+
const rectWidth = ref(0)
|
|
34
|
+
const rectHeight = ref(0)
|
|
35
|
+
const inView = ref(false)
|
|
36
|
+
|
|
37
|
+
const visible = computed(() => !!props.cell && inView.value)
|
|
38
|
+
const showFallback = computed(() => props.dragging && !props.cell)
|
|
39
|
+
|
|
40
|
+
const highlightStyle = computed(() => ({
|
|
41
|
+
left: `${rectLeft.value}px`,
|
|
42
|
+
top: `${rectTop.value}px`,
|
|
43
|
+
width: `${rectWidth.value}px`,
|
|
44
|
+
height: `${rectHeight.value}px`
|
|
45
|
+
}))
|
|
46
|
+
|
|
47
|
+
function updateHighlight(): void {
|
|
48
|
+
const cell = props.cell
|
|
49
|
+
const host = props.hostEl
|
|
50
|
+
if (!cell || !host) {
|
|
51
|
+
inView.value = false
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const rect = readCellOverlayRect(cell, host, props.getGrid)
|
|
56
|
+
if (!rect) {
|
|
57
|
+
inView.value = false
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
inView.value = true
|
|
62
|
+
rectLeft.value = Math.round(rect.left)
|
|
63
|
+
rectTop.value = Math.round(rect.top)
|
|
64
|
+
rectWidth.value = Math.round(rect.width)
|
|
65
|
+
rectHeight.value = Math.round(rect.height)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
useGridOverlaySync({
|
|
69
|
+
hostEl,
|
|
70
|
+
getGrid: () => props.getGrid(),
|
|
71
|
+
watchSources: () => [props.cell?.row, props.cell?.col, props.hostEl, props.dragging] as const,
|
|
72
|
+
update: updateHighlight
|
|
73
|
+
})
|
|
74
|
+
</script>
|