@veltra/sheet 2.0.0 → 2.1.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.
- package/dist/index.d.ts +4 -25
- package/dist/index.js +3 -22
- package/dist/tools/builtin.js +279 -8
- package/dist/tools/context.d.ts +54 -11
- package/dist/tools/context.js +28 -5
- package/dist/tools/download.js +68 -0
- package/dist/tools/export.worker.d.ts +16 -0
- package/dist/tools/export.worker.js +29 -0
- package/dist/tools/registry.d.ts +7 -4
- package/dist/tools/registry.js +1 -3
- package/dist/types/index.d.ts +1 -0
- package/dist/types/sheet.d.ts +6 -5
- package/dist/vue/formula-bar.js +393 -0
- package/dist/vue/formula-suggest-list.js +66 -0
- package/dist/vue/import-file.js +140 -0
- package/dist/vue/insert-count-menu-item.js +68 -0
- package/dist/vue/insert-image.js +79 -0
- package/dist/vue/popup-helpers.js +73 -0
- package/dist/vue/popups/border-popup.js +75 -0
- package/dist/vue/popups/border-preset-glyph.js +106 -0
- package/dist/vue/popups/export-popup.js +51 -0
- package/dist/vue/popups/fill-color-popup.js +37 -0
- package/dist/vue/popups/find-popup.js +113 -0
- package/dist/vue/popups/font-color-popup.js +37 -0
- package/dist/vue/popups/font-size-popup.js +63 -0
- package/dist/vue/popups/import.worker.d.ts +21 -0
- package/dist/vue/popups/import.worker.js +41 -0
- package/dist/vue/popups/insert-image-popup.js +38 -0
- package/dist/vue/sheet-context-menu.js +200 -0
- package/dist/vue/sheet-tabs.js +162 -0
- package/dist/vue/sheet-toolbar.js +106 -0
- package/dist/vue/sheet.js +144 -138
- package/dist/vue/style.d.ts +11 -1
- package/dist/vue/style.js +11 -0
- package/dist/vue/style2.css +564 -3
- package/dist/vue/use-find-replace.js +161 -0
- package/dist/vue/use-formula-ref-select.js +37 -0
- package/dist/vue/use-formula-suggest.js +126 -0
- package/dist/vue/use-sheet-grid.js +180 -0
- package/dist/vue/use-sheet-state.js +122 -0
- package/dist/vue/use-sheet-tabs-bar.js +90 -0
- package/dist/vue/use-tool-groups.js +42 -0
- package/dist/vue/use-tool-popup.js +123 -0
- package/dist/vue/use-toolbar-scroll.js +72 -0
- package/package.json +12 -6
- package/src/env.d.ts +14 -0
- package/src/index.ts +203 -13
- package/src/tools/__test__/builtin.test.ts +208 -66
- package/src/tools/__test__/context.test.ts +62 -2
- package/src/tools/builtin.ts +350 -6
- package/src/tools/context.ts +89 -11
- package/src/tools/download.ts +82 -0
- package/src/tools/export.worker.ts +61 -0
- package/src/tools/registry.ts +16 -1
- package/src/types/index.ts +6 -0
- package/src/types/sheet.ts +5 -3
- package/src/vue/__test__/formula-bar.test.ts +455 -0
- package/src/vue/__test__/import-file.test.ts +183 -0
- package/src/vue/__test__/insert-count-menu-item.test.ts +123 -0
- package/src/vue/__test__/insert-image-popup.test.ts +89 -0
- package/src/vue/__test__/insert-image.test.ts +100 -0
- package/src/vue/__test__/sheet-component.test.ts +716 -27
- package/src/vue/__test__/sheet-context-menu.test.ts +206 -0
- package/src/vue/__test__/use-formula-ref-select.test.ts +35 -0
- package/src/vue/__test__/use-formula-suggest.test.ts +83 -0
- package/src/vue/formula-bar.vue +473 -0
- package/src/vue/formula-suggest-list.vue +64 -0
- package/src/vue/import-file.ts +173 -0
- package/src/vue/insert-count-menu-item.vue +69 -0
- package/src/vue/insert-image.ts +90 -0
- package/src/vue/parsing.ts +11 -0
- package/src/vue/popup-helpers.ts +50 -0
- package/src/vue/popups/border-popup.vue +93 -0
- package/src/vue/popups/border-preset-glyph.vue +111 -0
- package/src/vue/popups/export-popup.vue +52 -0
- package/src/vue/popups/fill-color-popup.vue +39 -0
- package/src/vue/popups/find-popup.vue +111 -0
- package/src/vue/popups/font-color-popup.vue +39 -0
- package/src/vue/popups/font-size-popup.vue +52 -0
- package/src/vue/popups/import.worker.ts +66 -0
- package/src/vue/popups/insert-image-popup.vue +39 -0
- package/src/vue/sheet-context-menu.ts +229 -0
- package/src/vue/sheet-tabs.vue +162 -0
- package/src/vue/sheet-toolbar.vue +91 -0
- package/src/vue/sheet.vue +163 -148
- package/src/vue/style.scss +646 -3
- package/src/vue/style.ts +12 -0
- package/src/vue/use-find-replace.ts +200 -0
- package/src/vue/use-formula-ref-select.ts +34 -0
- package/src/vue/use-formula-suggest.ts +150 -0
- package/src/vue/use-sheet-grid.ts +243 -0
- package/src/vue/use-sheet-state.ts +161 -0
- package/src/vue/use-sheet-tabs-bar.ts +115 -0
- package/src/vue/use-tool-groups.ts +61 -0
- package/src/vue/use-tool-popup.ts +150 -0
- package/src/vue/use-toolbar-scroll.ts +95 -0
- package/dist/core/address.d.ts +0 -60
- package/dist/core/address.js +0 -142
- package/dist/core/address.js.map +0 -1
- package/dist/core/cell-store.d.ts +0 -61
- package/dist/core/cell-store.js +0 -135
- package/dist/core/cell-store.js.map +0 -1
- package/dist/core/command/default-registry.d.ts +0 -10
- package/dist/core/command/default-registry.js +0 -18
- package/dist/core/command/default-registry.js.map +0 -1
- package/dist/core/command/history.d.ts +0 -64
- package/dist/core/command/history.js +0 -127
- package/dist/core/command/history.js.map +0 -1
- package/dist/core/command/merge-cells.d.ts +0 -21
- package/dist/core/command/merge-cells.js +0 -74
- package/dist/core/command/merge-cells.js.map +0 -1
- package/dist/core/command/registry.d.ts +0 -17
- package/dist/core/command/registry.js +0 -26
- package/dist/core/command/registry.js.map +0 -1
- package/dist/core/command/set-cell-formula.d.ts +0 -17
- package/dist/core/command/set-cell-formula.js +0 -29
- package/dist/core/command/set-cell-formula.js.map +0 -1
- package/dist/core/command/set-cell-value.d.ts +0 -22
- package/dist/core/command/set-cell-value.js +0 -35
- package/dist/core/command/set-cell-value.js.map +0 -1
- package/dist/core/command/types.d.ts +0 -68
- package/dist/core/events.js +0 -29
- package/dist/core/events.js.map +0 -1
- package/dist/core/formula/ast.d.ts +0 -58
- package/dist/core/formula/ast.js +0 -38
- package/dist/core/formula/ast.js.map +0 -1
- package/dist/core/formula/dependency-graph.d.ts +0 -70
- package/dist/core/formula/dependency-graph.js +0 -269
- package/dist/core/formula/dependency-graph.js.map +0 -1
- package/dist/core/formula/errors.d.ts +0 -25
- package/dist/core/formula/errors.js +0 -36
- package/dist/core/formula/errors.js.map +0 -1
- package/dist/core/formula/evaluator.d.ts +0 -41
- package/dist/core/formula/evaluator.js +0 -154
- package/dist/core/formula/evaluator.js.map +0 -1
- package/dist/core/formula/functions.d.ts +0 -30
- package/dist/core/formula/functions.js +0 -209
- package/dist/core/formula/functions.js.map +0 -1
- package/dist/core/formula/parser.d.ts +0 -7
- package/dist/core/formula/parser.js +0 -208
- package/dist/core/formula/parser.js.map +0 -1
- package/dist/core/formula/tokenizer.d.ts +0 -37
- package/dist/core/formula/tokenizer.js +0 -146
- package/dist/core/formula/tokenizer.js.map +0 -1
- package/dist/core/merge-manager.d.ts +0 -64
- package/dist/core/merge-manager.js +0 -111
- package/dist/core/merge-manager.js.map +0 -1
- package/dist/core/selection.d.ts +0 -31
- package/dist/core/selection.js +0 -62
- package/dist/core/selection.js.map +0 -1
- package/dist/core/sheet.d.ts +0 -105
- package/dist/core/sheet.js +0 -202
- package/dist/core/sheet.js.map +0 -1
- package/dist/core/workbook.d.ts +0 -43
- package/dist/core/workbook.js +0 -77
- package/dist/core/workbook.js.map +0 -1
- package/dist/grid/sheet-grid.d.ts +0 -59
- package/dist/grid/sheet-grid.js +0 -230
- package/dist/grid/sheet-grid.js.map +0 -1
- package/dist/tools/builtin.js.map +0 -1
- package/dist/tools/context.js.map +0 -1
- package/dist/tools/registry.js.map +0 -1
- package/dist/vue/index.d.ts +0 -2
- package/dist/vue/sheet.js.map +0 -1
- package/dist/vue/sheet.vue.d.ts +0 -24
- package/src/core/__test__/address.test.ts +0 -113
- package/src/core/__test__/cell-store.test.ts +0 -97
- package/src/core/__test__/command.test.ts +0 -377
- package/src/core/__test__/formula-adversarial.test.ts +0 -265
- package/src/core/__test__/formula-engine.test.ts +0 -470
- package/src/core/__test__/formula-functions.test.ts +0 -178
- package/src/core/__test__/formula-parser.test.ts +0 -160
- package/src/core/__test__/history.test.ts +0 -225
- package/src/core/__test__/merge-manager.test.ts +0 -106
- package/src/core/__test__/sheet.test.ts +0 -142
- package/src/core/address.ts +0 -166
- package/src/core/cell-store.ts +0 -173
- package/src/core/command/default-registry.ts +0 -15
- package/src/core/command/history.ts +0 -167
- package/src/core/command/index.ts +0 -7
- package/src/core/command/merge-cells.ts +0 -74
- package/src/core/command/registry.ts +0 -30
- package/src/core/command/set-cell-formula.ts +0 -29
- package/src/core/command/set-cell-value.ts +0 -38
- package/src/core/command/types.ts +0 -73
- package/src/core/events.ts +0 -37
- package/src/core/formula/ast.ts +0 -66
- package/src/core/formula/dependency-graph.ts +0 -329
- package/src/core/formula/errors.ts +0 -41
- package/src/core/formula/evaluator.ts +0 -219
- package/src/core/formula/functions.ts +0 -266
- package/src/core/formula/index.ts +0 -7
- package/src/core/formula/parser.ts +0 -198
- package/src/core/formula/tokenizer.ts +0 -160
- package/src/core/merge-manager.ts +0 -158
- package/src/core/selection.ts +0 -66
- package/src/core/sheet.ts +0 -257
- package/src/core/workbook.ts +0 -99
- package/src/grid/__test__/canvas-mock.ts +0 -49
- package/src/grid/__test__/setup.ts +0 -3
- package/src/grid/__test__/sheet-grid-formula.test.ts +0 -140
- package/src/grid/__test__/sheet-grid.test.ts +0 -177
- package/src/grid/sheet-grid.ts +0 -283
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="cls.e('insert-count-menu')"
|
|
4
|
+
@click="confirm"
|
|
5
|
+
@keydown.enter.capture.prevent="confirm"
|
|
6
|
+
@keydown.escape.capture.prevent="close"
|
|
7
|
+
>
|
|
8
|
+
<span :class="cls.e('insert-count-prefix')">{{ prefix }}</span>
|
|
9
|
+
<u-number-input
|
|
10
|
+
v-model="count"
|
|
11
|
+
size="small"
|
|
12
|
+
:min="min"
|
|
13
|
+
:max="max"
|
|
14
|
+
:step="1"
|
|
15
|
+
:clearable="false"
|
|
16
|
+
:class="cls.e('insert-count-input')"
|
|
17
|
+
@click.stop
|
|
18
|
+
/>
|
|
19
|
+
<span :class="cls.e('insert-count-suffix')">{{ suffix }}</span>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script lang="ts" setup>
|
|
24
|
+
import { ContextmenuRootDIKey, UNumberInput } from '@veltra/desktop'
|
|
25
|
+
import { bem } from '@veltra/utils'
|
|
26
|
+
import { inject, ref } from 'vue'
|
|
27
|
+
|
|
28
|
+
defineOptions({ name: 'USheetInsertCountMenuItem' })
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 右键菜单内嵌数量输入项:文案 + UNumberInput。
|
|
32
|
+
* 点击菜单项本体按当前值执行;输入框区域点击不冒泡。
|
|
33
|
+
* Enter 提交、Esc 关闭;提交/取消均经 ContextmenuRootDIKey 关闭菜单。
|
|
34
|
+
* Enter/Esc 使用 capture:UNumberInput 内部 `@keydown.stop`,冒泡阶段收不到。
|
|
35
|
+
*/
|
|
36
|
+
const props = withDefaults(
|
|
37
|
+
defineProps<{
|
|
38
|
+
/** 输入框前缀文案,如「在上方插入」 */
|
|
39
|
+
prefix: string
|
|
40
|
+
/** 输入框后缀文案,如「行」 */
|
|
41
|
+
suffix: string
|
|
42
|
+
/** 默认数量 */
|
|
43
|
+
defaultValue?: number
|
|
44
|
+
min?: number
|
|
45
|
+
max?: number
|
|
46
|
+
}>(),
|
|
47
|
+
{ defaultValue: 1, min: 1, max: 1000 }
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const emit = defineEmits<{ confirm: [n: number] }>()
|
|
51
|
+
|
|
52
|
+
const cls = bem('sheet')
|
|
53
|
+
const root = inject(ContextmenuRootDIKey, null)
|
|
54
|
+
|
|
55
|
+
const count = ref(props.defaultValue)
|
|
56
|
+
|
|
57
|
+
function clamp(n: number): number {
|
|
58
|
+
return Math.min(props.max, Math.max(props.min, Math.floor(n) || props.min))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function close(): void {
|
|
62
|
+
root?.onItemClickEnd()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function confirm(): void {
|
|
66
|
+
emit('confirm', clamp(count.value ?? props.defaultValue))
|
|
67
|
+
close()
|
|
68
|
+
}
|
|
69
|
+
</script>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { message } from '@veltra/desktop'
|
|
2
|
+
import type { SheetImageType } from '@veltra/sheet-core/core/image'
|
|
3
|
+
|
|
4
|
+
import type { SheetContext } from '../tools/context'
|
|
5
|
+
|
|
6
|
+
/** 文件选择 accept(工具栏弹层与右键菜单共用;不用裸 image/*) */
|
|
7
|
+
export const INSERT_IMAGE_ACCEPT = '.png,.jpg,.jpeg,.gif,.svg,.webp'
|
|
8
|
+
|
|
9
|
+
const MIME_TO_TYPE: Record<string, SheetImageType> = {
|
|
10
|
+
'image/png': 'png',
|
|
11
|
+
'image/jpeg': 'jpeg',
|
|
12
|
+
'image/jpg': 'jpeg',
|
|
13
|
+
'image/gif': 'gif',
|
|
14
|
+
'image/svg+xml': 'svg',
|
|
15
|
+
'image/webp': 'webp'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const EXT_TO_TYPE: Record<string, SheetImageType> = {
|
|
19
|
+
png: 'png',
|
|
20
|
+
jpg: 'jpeg',
|
|
21
|
+
jpeg: 'jpeg',
|
|
22
|
+
gif: 'gif',
|
|
23
|
+
svg: 'svg',
|
|
24
|
+
webp: 'webp'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** MIME → hucre 图片类型;MIME 为空时回落扩展名 */
|
|
28
|
+
export function resolveImageType(file: File): SheetImageType | undefined {
|
|
29
|
+
const mime = file.type.trim().toLowerCase()
|
|
30
|
+
if (mime && MIME_TO_TYPE[mime]) return MIME_TO_TYPE[mime]
|
|
31
|
+
const ext = file.name.includes('.') ? file.name.slice(file.name.lastIndexOf('.') + 1) : ''
|
|
32
|
+
return EXT_TO_TYPE[ext.toLowerCase()]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 从本地 File 插入浮动图片:读字节 → 映射类型 → 锚定活动格 → ctx.insertImage。
|
|
37
|
+
* 不支持的格式 / 无活动格时 message.error 提示并返回 undefined。
|
|
38
|
+
*/
|
|
39
|
+
export async function insertImageFromFile(
|
|
40
|
+
ctx: SheetContext,
|
|
41
|
+
file: File
|
|
42
|
+
): Promise<string | undefined> {
|
|
43
|
+
const type = resolveImageType(file)
|
|
44
|
+
if (!type) {
|
|
45
|
+
message.error('不支持的图片格式,请选择 png / jpeg / gif / svg / webp')
|
|
46
|
+
return undefined
|
|
47
|
+
}
|
|
48
|
+
const active = ctx.getSelection().activeCell
|
|
49
|
+
if (!active) {
|
|
50
|
+
message.error('请先选择单元格')
|
|
51
|
+
return undefined
|
|
52
|
+
}
|
|
53
|
+
const buffer = await file.arrayBuffer()
|
|
54
|
+
return ctx.insertImage({
|
|
55
|
+
data: new Uint8Array(buffer),
|
|
56
|
+
type,
|
|
57
|
+
anchor: { from: { row: active.row, col: active.col } }
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 拉起系统文件选择框(右键菜单用;不用 UFilePicker——无编程式打开 API)。
|
|
63
|
+
* 选中文件后走 insertImageFromFile。
|
|
64
|
+
*/
|
|
65
|
+
export function pickAndInsertImage(ctx: SheetContext): void {
|
|
66
|
+
const input = document.createElement('input')
|
|
67
|
+
input.type = 'file'
|
|
68
|
+
input.accept = INSERT_IMAGE_ACCEPT
|
|
69
|
+
input.hidden = true
|
|
70
|
+
const cleanup = (): void => {
|
|
71
|
+
input.remove()
|
|
72
|
+
}
|
|
73
|
+
input.addEventListener('change', () => {
|
|
74
|
+
const file = input.files?.[0]
|
|
75
|
+
cleanup()
|
|
76
|
+
if (file) void insertImageFromFile(ctx, file)
|
|
77
|
+
})
|
|
78
|
+
// 取消选择时也清理节点(focus 回窗口后延迟判定)
|
|
79
|
+
window.addEventListener(
|
|
80
|
+
'focus',
|
|
81
|
+
() => {
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
if (document.body.contains(input) && !input.files?.length) cleanup()
|
|
84
|
+
}, 300)
|
|
85
|
+
},
|
|
86
|
+
{ once: true }
|
|
87
|
+
)
|
|
88
|
+
document.body.appendChild(input)
|
|
89
|
+
input.click()
|
|
90
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { InjectionKey, Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* xlsx 解析中状态(历史 inject key,现由 import-file 直接写 sheet.vue 持有的 ref)。
|
|
5
|
+
* 保留导出以便测试或扩展仍可 provide/inject。
|
|
6
|
+
*/
|
|
7
|
+
export const SHEET_PARSING_KEY: InjectionKey<Ref<boolean>> = Symbol('sheet-parsing')
|
|
8
|
+
|
|
9
|
+
/** 解析进度(worker 分片构建期间 done/total;readXlsx 同步解析段无进度) */
|
|
10
|
+
export const SHEET_PARSE_PROGRESS_KEY: InjectionKey<Ref<{ done: number; total: number }>> =
|
|
11
|
+
Symbol('sheet-parse-progress')
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CellRange } from '@veltra/sheet-core/core/address'
|
|
2
|
+
import type { BorderPreset } from '@veltra/sheet-core/core/style/border-presets'
|
|
3
|
+
import { BORDER_STYLE_WIDTH, type BorderLineStyle } from '@veltra/sheet-core/core/style/types'
|
|
4
|
+
|
|
5
|
+
import type { SheetContext } from '../tools/context'
|
|
6
|
+
|
|
7
|
+
/** 当前选区(ranges[0] 优先;无区域选区时用活动格单格) */
|
|
8
|
+
export function currentRange(context: SheetContext): CellRange | null {
|
|
9
|
+
const { activeCell, ranges } = context.getSelection()
|
|
10
|
+
return ranges[0] ?? (activeCell ? { start: activeCell, end: activeCell } : null)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ─── 边框面板常量 ────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export const BORDER_LINE_STYLES: BorderLineStyle[] = ['thin', 'medium', 'thick', 'dashed', 'dotted']
|
|
16
|
+
|
|
17
|
+
export const BORDER_LINE_TITLES: Record<BorderLineStyle, string> = {
|
|
18
|
+
thin: '细线',
|
|
19
|
+
medium: '中粗线',
|
|
20
|
+
thick: '粗线',
|
|
21
|
+
dashed: '虚线',
|
|
22
|
+
dotted: '点线'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 田字格 glyph 标识(与预设 id 对齐,供 SVG 高亮对应边) */
|
|
26
|
+
export type BorderPresetGlyph = BorderPreset
|
|
27
|
+
|
|
28
|
+
export const BORDER_PRESETS: ReadonlyArray<{
|
|
29
|
+
id: BorderPreset
|
|
30
|
+
title: string
|
|
31
|
+
glyph: BorderPresetGlyph
|
|
32
|
+
}> = [
|
|
33
|
+
{ id: 'outer', title: '外边框', glyph: 'outer' },
|
|
34
|
+
{ id: 'inner', title: '内边框', glyph: 'inner' },
|
|
35
|
+
{ id: 'all', title: '所有边框', glyph: 'all' },
|
|
36
|
+
{ id: 'top', title: '上边框', glyph: 'top' },
|
|
37
|
+
{ id: 'bottom', title: '下边框', glyph: 'bottom' },
|
|
38
|
+
{ id: 'left', title: '左边框', glyph: 'left' },
|
|
39
|
+
{ id: 'right', title: '右边框', glyph: 'right' },
|
|
40
|
+
{ id: 'none', title: '无边框', glyph: 'none' }
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
/** 边框预设 id(= core BorderPreset;预设补丁生成见 core/style/border-presets) */
|
|
44
|
+
export type BorderPresetId = BorderPreset
|
|
45
|
+
|
|
46
|
+
/** 线型示例条样式(边框面板的线型按钮 swatch) */
|
|
47
|
+
export function lineSwatchStyle(line: BorderLineStyle): Record<string, string> {
|
|
48
|
+
const dash = line === 'dashed' ? 'dashed' : line === 'dotted' ? 'dotted' : 'solid'
|
|
49
|
+
return { borderBottom: `${BORDER_STYLE_WIDTH[line]}px ${dash} #000` }
|
|
50
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="cls.e('popup-row')">
|
|
3
|
+
<span :class="cls.e('popup-label')">线型</span>
|
|
4
|
+
<button
|
|
5
|
+
v-for="line in BORDER_LINE_STYLES"
|
|
6
|
+
:key="line"
|
|
7
|
+
type="button"
|
|
8
|
+
:class="[cls.e('popup-line'), bem.is('active', borderLineStyle === line)]"
|
|
9
|
+
:title="BORDER_LINE_TITLES[line]"
|
|
10
|
+
@click="borderLineStyle = line"
|
|
11
|
+
>
|
|
12
|
+
<span :class="cls.e('popup-line-swatch')" :style="lineSwatchStyle(line)" />
|
|
13
|
+
</button>
|
|
14
|
+
</div>
|
|
15
|
+
<div :class="cls.e('popup-row')">
|
|
16
|
+
<span :class="cls.e('popup-label')">颜色</span>
|
|
17
|
+
<u-palette
|
|
18
|
+
:model-value="borderColor"
|
|
19
|
+
@update:model-value="(value) => (borderColor = value || borderColor)"
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
<div :class="cls.e('popup-row')">
|
|
23
|
+
<button
|
|
24
|
+
v-for="preset in BORDER_PRESETS"
|
|
25
|
+
:key="preset.id"
|
|
26
|
+
type="button"
|
|
27
|
+
:class="cls.e('popup-preset')"
|
|
28
|
+
:title="preset.title"
|
|
29
|
+
@click="applyBorderPreset(preset.id)"
|
|
30
|
+
>
|
|
31
|
+
<u-sheet-border-preset-glyph :glyph="preset.glyph" />
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script lang="ts" setup>
|
|
37
|
+
import { UPalette } from '@veltra/desktop'
|
|
38
|
+
import type { SetCellStyleItem } from '@veltra/sheet-core/core/command/set-cell-style'
|
|
39
|
+
import { buildBorderPresetItems } from '@veltra/sheet-core/core/style/border-presets'
|
|
40
|
+
import {
|
|
41
|
+
BORDER_STYLE_WIDTH,
|
|
42
|
+
type BorderEdge,
|
|
43
|
+
type BorderLineStyle
|
|
44
|
+
} from '@veltra/sheet-core/core/style/types'
|
|
45
|
+
import { bem } from '@veltra/utils'
|
|
46
|
+
import { ref } from 'vue'
|
|
47
|
+
|
|
48
|
+
import type { SheetContext } from '../../tools/context'
|
|
49
|
+
import {
|
|
50
|
+
BORDER_LINE_STYLES,
|
|
51
|
+
BORDER_LINE_TITLES,
|
|
52
|
+
BORDER_PRESETS,
|
|
53
|
+
currentRange,
|
|
54
|
+
lineSwatchStyle,
|
|
55
|
+
type BorderPresetId
|
|
56
|
+
} from '../popup-helpers'
|
|
57
|
+
import USheetBorderPresetGlyph from './border-preset-glyph.vue'
|
|
58
|
+
|
|
59
|
+
defineOptions({ name: 'USheetBorderPopup' })
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 边框面板(外边框 / 内边框 / 所有边框 / 单侧边框 / 无边框预设 + 线型 / 颜色)。
|
|
63
|
+
* 预设补丁由 core/style/border-presets 生成(含邻居格共享边同步,对齐
|
|
64
|
+
* Excel「写入时同步、最近设置生效」);面板打开期间的写入由
|
|
65
|
+
* use-tool-popup 事务包裹为一个 undo 单元。
|
|
66
|
+
*/
|
|
67
|
+
const props = defineProps<{
|
|
68
|
+
/** 工具上下文(写入走命令系统,天然可撤销) */
|
|
69
|
+
context: SheetContext
|
|
70
|
+
}>()
|
|
71
|
+
|
|
72
|
+
const cls = bem('sheet')
|
|
73
|
+
|
|
74
|
+
/** 当前线型 / 颜色 */
|
|
75
|
+
const borderLineStyle = ref<BorderLineStyle>('thin')
|
|
76
|
+
const borderColor = ref('#000000')
|
|
77
|
+
|
|
78
|
+
/** 应用边框预设:一次 executeCommand(items 批量,含邻居同步补丁)= 一个 undo 单元 */
|
|
79
|
+
function applyBorderPreset(preset: BorderPresetId): void {
|
|
80
|
+
const range = currentRange(props.context)
|
|
81
|
+
if (!range) return
|
|
82
|
+
const edge: BorderEdge = {
|
|
83
|
+
style: borderLineStyle.value,
|
|
84
|
+
width: BORDER_STYLE_WIDTH[borderLineStyle.value],
|
|
85
|
+
color: borderColor.value
|
|
86
|
+
}
|
|
87
|
+
const items: SetCellStyleItem[] = buildBorderPresetItems(range, preset, edge, (addr) =>
|
|
88
|
+
props.context.getCellStyle(addr)
|
|
89
|
+
).map(({ addr, patch }) => ({ addr, partial: patch }))
|
|
90
|
+
if (items.length === 0) return
|
|
91
|
+
props.context.executeCommand('sheet.command.set-cell-style', { items })
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg
|
|
3
|
+
:class="cls.e('popup-preset-glyph')"
|
|
4
|
+
viewBox="0 0 16 16"
|
|
5
|
+
width="16"
|
|
6
|
+
height="16"
|
|
7
|
+
aria-hidden="true"
|
|
8
|
+
>
|
|
9
|
+
<!-- 田字格:外框 + 十字;高亮边用 currentColor,其余用 muted -->
|
|
10
|
+
<line
|
|
11
|
+
x1="2"
|
|
12
|
+
y1="2"
|
|
13
|
+
x2="14"
|
|
14
|
+
y2="2"
|
|
15
|
+
:stroke="stroke(edges.top)"
|
|
16
|
+
stroke-width="1.5"
|
|
17
|
+
stroke-linecap="square"
|
|
18
|
+
/>
|
|
19
|
+
<line
|
|
20
|
+
x1="2"
|
|
21
|
+
y1="14"
|
|
22
|
+
x2="14"
|
|
23
|
+
y2="14"
|
|
24
|
+
:stroke="stroke(edges.bottom)"
|
|
25
|
+
stroke-width="1.5"
|
|
26
|
+
stroke-linecap="square"
|
|
27
|
+
/>
|
|
28
|
+
<line
|
|
29
|
+
x1="2"
|
|
30
|
+
y1="2"
|
|
31
|
+
x2="2"
|
|
32
|
+
y2="14"
|
|
33
|
+
:stroke="stroke(edges.left)"
|
|
34
|
+
stroke-width="1.5"
|
|
35
|
+
stroke-linecap="square"
|
|
36
|
+
/>
|
|
37
|
+
<line
|
|
38
|
+
x1="14"
|
|
39
|
+
y1="2"
|
|
40
|
+
x2="14"
|
|
41
|
+
y2="14"
|
|
42
|
+
:stroke="stroke(edges.right)"
|
|
43
|
+
stroke-width="1.5"
|
|
44
|
+
stroke-linecap="square"
|
|
45
|
+
/>
|
|
46
|
+
<line
|
|
47
|
+
x1="2"
|
|
48
|
+
y1="8"
|
|
49
|
+
x2="14"
|
|
50
|
+
y2="8"
|
|
51
|
+
:stroke="stroke(edges.innerH)"
|
|
52
|
+
stroke-width="1.5"
|
|
53
|
+
stroke-linecap="square"
|
|
54
|
+
/>
|
|
55
|
+
<line
|
|
56
|
+
x1="8"
|
|
57
|
+
y1="2"
|
|
58
|
+
x2="8"
|
|
59
|
+
y2="14"
|
|
60
|
+
:stroke="stroke(edges.innerV)"
|
|
61
|
+
stroke-width="1.5"
|
|
62
|
+
stroke-linecap="square"
|
|
63
|
+
/>
|
|
64
|
+
</svg>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script lang="ts" setup>
|
|
68
|
+
import { bem } from '@veltra/utils'
|
|
69
|
+
import { computed } from 'vue'
|
|
70
|
+
|
|
71
|
+
import type { BorderPresetGlyph } from '../popup-helpers'
|
|
72
|
+
|
|
73
|
+
defineOptions({ name: 'USheetBorderPresetGlyph' })
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 边框预设田字格 glyph(包内联 SVG,不进 @veltra/icons)。
|
|
77
|
+
* 3×3 网格(外框 + 十字),按 glyph 高亮对应边。
|
|
78
|
+
*/
|
|
79
|
+
const props = defineProps<{ glyph: BorderPresetGlyph }>()
|
|
80
|
+
|
|
81
|
+
const cls = bem('sheet')
|
|
82
|
+
|
|
83
|
+
/** 田字格各线段是否高亮 */
|
|
84
|
+
interface GlyphEdges {
|
|
85
|
+
top: boolean
|
|
86
|
+
bottom: boolean
|
|
87
|
+
left: boolean
|
|
88
|
+
right: boolean
|
|
89
|
+
innerH: boolean
|
|
90
|
+
innerV: boolean
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const MUTED = 'var(--u-border-muted-color, #c0c4cc)'
|
|
94
|
+
const ACTIVE = 'currentColor'
|
|
95
|
+
|
|
96
|
+
function stroke(active: boolean): string {
|
|
97
|
+
return active ? ACTIVE : MUTED
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const edges = computed<GlyphEdges>(() => {
|
|
101
|
+
const g = props.glyph
|
|
102
|
+
return {
|
|
103
|
+
top: g === 'outer' || g === 'all' || g === 'top',
|
|
104
|
+
bottom: g === 'outer' || g === 'all' || g === 'bottom',
|
|
105
|
+
left: g === 'outer' || g === 'all' || g === 'left',
|
|
106
|
+
right: g === 'outer' || g === 'all' || g === 'right',
|
|
107
|
+
innerH: g === 'inner' || g === 'all',
|
|
108
|
+
innerV: g === 'inner' || g === 'all'
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
</script>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="cls.e('export-menu')">
|
|
3
|
+
<button type="button" :class="cls.e('export-option')" @click="exportXlsx">
|
|
4
|
+
导出 Excel (.xlsx)
|
|
5
|
+
</button>
|
|
6
|
+
<button type="button" :class="cls.e('export-option')" @click="exportCsv">
|
|
7
|
+
导出 CSV (.csv)
|
|
8
|
+
</button>
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script lang="ts" setup>
|
|
13
|
+
import { message } from '@veltra/desktop'
|
|
14
|
+
import { bem } from '@veltra/utils'
|
|
15
|
+
|
|
16
|
+
import type { SheetContext } from '../../tools/context'
|
|
17
|
+
import { exportSheetCsvFile, exportWorkbookFile } from '../../tools/download'
|
|
18
|
+
|
|
19
|
+
defineOptions({ name: 'USheetExportPopup' })
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 导出选择面板(xlsx / csv)。不参与面板事务:点击即下载并关闭。
|
|
23
|
+
*/
|
|
24
|
+
const props = defineProps<{ context: SheetContext }>()
|
|
25
|
+
|
|
26
|
+
const emit = defineEmits<{ close: [] }>()
|
|
27
|
+
|
|
28
|
+
const cls = bem('sheet')
|
|
29
|
+
|
|
30
|
+
function exportXlsx(): void {
|
|
31
|
+
emit('close')
|
|
32
|
+
// 大工作簿序列化在 worker 中进行(数秒):给常驻反馈,失败(如 sheet 名含
|
|
33
|
+
// Excel 非法字符,hucre 1.0 写入校验抛 InvalidArgumentError)明确报错,
|
|
34
|
+
// 绝不静默失败
|
|
35
|
+
const loading = message({ message: '正在导出…', duration: 0 })
|
|
36
|
+
exportWorkbookFile(props.context)
|
|
37
|
+
.then(() => loading.close())
|
|
38
|
+
.catch((err: unknown) => {
|
|
39
|
+
loading.close()
|
|
40
|
+
message.error(`导出失败:${err instanceof Error ? err.message : String(err)}`)
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function exportCsv(): void {
|
|
45
|
+
emit('close')
|
|
46
|
+
try {
|
|
47
|
+
exportSheetCsvFile(props.context)
|
|
48
|
+
} catch (err) {
|
|
49
|
+
message.error(`导出失败:${err instanceof Error ? err.message : String(err)}`)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<u-palette :model-value="fillColor" @update:model-value="applyFillColor" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script lang="ts" setup>
|
|
6
|
+
import { UPalette } from '@veltra/desktop'
|
|
7
|
+
import { onMounted, ref } from 'vue'
|
|
8
|
+
|
|
9
|
+
import type { SheetContext } from '../../tools/context'
|
|
10
|
+
import { currentRange } from '../popup-helpers'
|
|
11
|
+
|
|
12
|
+
defineOptions({ name: 'USheetFillColorPopup' })
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 填充颜色面板(UPalette 调色板 + 无填充 = 清除 fill 保留边框)。
|
|
16
|
+
* 面板打开期间的写入由 use-tool-popup 事务包裹为一个 undo 单元。
|
|
17
|
+
*/
|
|
18
|
+
const props = defineProps<{
|
|
19
|
+
/** 工具上下文(写入走命令系统,天然可撤销) */
|
|
20
|
+
context: SheetContext
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
/** 填充颜色('' = 无填充) */
|
|
24
|
+
const fillColor = ref('')
|
|
25
|
+
|
|
26
|
+
// 打开时初始化为活动格当前填充色
|
|
27
|
+
onMounted(() => {
|
|
28
|
+
const active = props.context.getSelection().activeCell
|
|
29
|
+
const style = active ? props.context.getCellStyle(active) : undefined
|
|
30
|
+
fillColor.value = style?.fill?.color ?? ''
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
/** 填充颜色变化('' = 无填充:清除 fill 保留边框) */
|
|
34
|
+
function applyFillColor(color: string): void {
|
|
35
|
+
const range = currentRange(props.context)
|
|
36
|
+
if (!range) return
|
|
37
|
+
props.context.setCellStyle(range, color ? { fill: { color } } : { fill: {} })
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="cls.e('find-row')">
|
|
3
|
+
<u-input
|
|
4
|
+
v-model="findQuery"
|
|
5
|
+
:placeholder="'查找内容'"
|
|
6
|
+
size="small"
|
|
7
|
+
:class="cls.e('find-input')"
|
|
8
|
+
@keydown="handleFindKeydown"
|
|
9
|
+
/>
|
|
10
|
+
<span :class="cls.e('find-count')">{{ findCountText }}</span>
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
:class="cls.e('find-nav')"
|
|
14
|
+
:disabled="!canFind"
|
|
15
|
+
title="上一个(Shift+Enter)"
|
|
16
|
+
@click="findPrevious"
|
|
17
|
+
>
|
|
18
|
+
↑
|
|
19
|
+
</button>
|
|
20
|
+
<button
|
|
21
|
+
type="button"
|
|
22
|
+
:class="cls.e('find-nav')"
|
|
23
|
+
:disabled="!canFind"
|
|
24
|
+
title="下一个(Enter)"
|
|
25
|
+
@click="findForward"
|
|
26
|
+
>
|
|
27
|
+
↓
|
|
28
|
+
</button>
|
|
29
|
+
<button type="button" :class="cls.e('find-close')" title="关闭" @click="emit('close')">
|
|
30
|
+
✕
|
|
31
|
+
</button>
|
|
32
|
+
</div>
|
|
33
|
+
<div :class="cls.e('find-row')">
|
|
34
|
+
<u-input
|
|
35
|
+
v-model="findReplace"
|
|
36
|
+
placeholder="替换为"
|
|
37
|
+
size="small"
|
|
38
|
+
:class="cls.e('find-input')"
|
|
39
|
+
@keydown="handleFindKeydown"
|
|
40
|
+
/>
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
:class="cls.e('find-btn')"
|
|
44
|
+
:disabled="!canReplace"
|
|
45
|
+
@click="replaceCurrent"
|
|
46
|
+
>
|
|
47
|
+
替换
|
|
48
|
+
</button>
|
|
49
|
+
<button type="button" :class="cls.e('find-btn')" :disabled="!canReplace" @click="replaceAll">
|
|
50
|
+
全部替换
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
<div :class="cls.e('find-row')">
|
|
54
|
+
<label :class="cls.e('find-option')">
|
|
55
|
+
<input v-model="caseSensitive" type="checkbox" />区分大小写
|
|
56
|
+
</label>
|
|
57
|
+
<label :class="cls.e('find-option')">
|
|
58
|
+
<input v-model="wholeCell" type="checkbox" />整格匹配
|
|
59
|
+
</label>
|
|
60
|
+
<label :class="cls.e('find-option')">
|
|
61
|
+
查找
|
|
62
|
+
<select v-model="searchIn" :class="cls.e('find-select')">
|
|
63
|
+
<option value="value">按显示值</option>
|
|
64
|
+
<option value="formula">按公式</option>
|
|
65
|
+
</select>
|
|
66
|
+
</label>
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<script lang="ts" setup>
|
|
71
|
+
import { UInput } from '@veltra/desktop'
|
|
72
|
+
import type { Sheet } from '@veltra/sheet-core/core/sheet'
|
|
73
|
+
import { bem } from '@veltra/utils'
|
|
74
|
+
|
|
75
|
+
import type { SheetContext } from '../../tools/context'
|
|
76
|
+
import { useFindReplace } from '../use-find-replace'
|
|
77
|
+
|
|
78
|
+
defineOptions({ name: 'USheetFindPopup' })
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 查找条(关键词 / 上一个 / 下一个 / 命中计数 / 关闭 + 替换 / 全部替换)。
|
|
82
|
+
* 不参与面板事务:每次替换独立为一个 undo 单元;全部替换 = 一次批量 = 单 undo 单元。
|
|
83
|
+
* 弹层随 v-if 挂载 / 销毁,每次打开都是全新状态(关键词清空)。
|
|
84
|
+
*/
|
|
85
|
+
const props = defineProps<{
|
|
86
|
+
/** 目标 sheet(弹层存活期间固定;tab 切换会关闭弹层) */
|
|
87
|
+
sheet: Sheet
|
|
88
|
+
/** 工具上下文(命中定位与替换写入入口) */
|
|
89
|
+
context: SheetContext
|
|
90
|
+
}>()
|
|
91
|
+
|
|
92
|
+
const emit = defineEmits<{ close: [] }>()
|
|
93
|
+
|
|
94
|
+
const cls = bem('sheet')
|
|
95
|
+
|
|
96
|
+
const {
|
|
97
|
+
findQuery,
|
|
98
|
+
findReplace,
|
|
99
|
+
caseSensitive,
|
|
100
|
+
wholeCell,
|
|
101
|
+
searchIn,
|
|
102
|
+
canFind,
|
|
103
|
+
canReplace,
|
|
104
|
+
findCountText,
|
|
105
|
+
findForward,
|
|
106
|
+
findPrevious,
|
|
107
|
+
handleFindKeydown,
|
|
108
|
+
replaceCurrent,
|
|
109
|
+
replaceAll
|
|
110
|
+
} = useFindReplace({ getSheet: () => props.sheet, context: props.context })
|
|
111
|
+
</script>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<u-palette :model-value="fontColor" @update:model-value="applyFontColor" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script lang="ts" setup>
|
|
6
|
+
import { UPalette } from '@veltra/desktop'
|
|
7
|
+
import { onMounted, ref } from 'vue'
|
|
8
|
+
|
|
9
|
+
import type { SheetContext } from '../../tools/context'
|
|
10
|
+
import { currentRange } from '../popup-helpers'
|
|
11
|
+
|
|
12
|
+
defineOptions({ name: 'USheetFontColorPopup' })
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 字体颜色面板(UPalette;空值 = 清除 color 保留其它字体字段)。
|
|
16
|
+
* 面板打开期间的写入由 use-tool-popup 事务包裹为一个 undo 单元。
|
|
17
|
+
*/
|
|
18
|
+
const props = defineProps<{
|
|
19
|
+
/** 工具上下文(写入走命令系统,天然可撤销) */
|
|
20
|
+
context: SheetContext
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
/** 字体颜色('' = 自动/默认) */
|
|
24
|
+
const fontColor = ref('')
|
|
25
|
+
|
|
26
|
+
// 打开时初始化为活动格当前字体色
|
|
27
|
+
onMounted(() => {
|
|
28
|
+
const active = props.context.getSelection().activeCell
|
|
29
|
+
const style = active ? props.context.getCellStyle(active) : undefined
|
|
30
|
+
fontColor.value = style?.font?.color ?? ''
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
/** 字体颜色变化('' = 清除 color) */
|
|
34
|
+
function applyFontColor(color: string): void {
|
|
35
|
+
const range = currentRange(props.context)
|
|
36
|
+
if (!range) return
|
|
37
|
+
props.context.setCellStyle(range, color ? { font: { color } } : { font: { color: null } })
|
|
38
|
+
}
|
|
39
|
+
</script>
|