@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.
Files changed (203) hide show
  1. package/dist/index.d.ts +4 -25
  2. package/dist/index.js +3 -22
  3. package/dist/tools/builtin.js +279 -8
  4. package/dist/tools/context.d.ts +54 -11
  5. package/dist/tools/context.js +28 -5
  6. package/dist/tools/download.js +68 -0
  7. package/dist/tools/export.worker.d.ts +16 -0
  8. package/dist/tools/export.worker.js +29 -0
  9. package/dist/tools/registry.d.ts +7 -4
  10. package/dist/tools/registry.js +1 -3
  11. package/dist/types/index.d.ts +1 -0
  12. package/dist/types/sheet.d.ts +6 -5
  13. package/dist/vue/formula-bar.js +393 -0
  14. package/dist/vue/formula-suggest-list.js +66 -0
  15. package/dist/vue/import-file.js +140 -0
  16. package/dist/vue/insert-count-menu-item.js +68 -0
  17. package/dist/vue/insert-image.js +79 -0
  18. package/dist/vue/popup-helpers.js +73 -0
  19. package/dist/vue/popups/border-popup.js +75 -0
  20. package/dist/vue/popups/border-preset-glyph.js +106 -0
  21. package/dist/vue/popups/export-popup.js +51 -0
  22. package/dist/vue/popups/fill-color-popup.js +37 -0
  23. package/dist/vue/popups/find-popup.js +113 -0
  24. package/dist/vue/popups/font-color-popup.js +37 -0
  25. package/dist/vue/popups/font-size-popup.js +63 -0
  26. package/dist/vue/popups/import.worker.d.ts +21 -0
  27. package/dist/vue/popups/import.worker.js +41 -0
  28. package/dist/vue/popups/insert-image-popup.js +38 -0
  29. package/dist/vue/sheet-context-menu.js +200 -0
  30. package/dist/vue/sheet-tabs.js +162 -0
  31. package/dist/vue/sheet-toolbar.js +106 -0
  32. package/dist/vue/sheet.js +144 -138
  33. package/dist/vue/style.d.ts +11 -1
  34. package/dist/vue/style.js +11 -0
  35. package/dist/vue/style2.css +564 -3
  36. package/dist/vue/use-find-replace.js +161 -0
  37. package/dist/vue/use-formula-ref-select.js +37 -0
  38. package/dist/vue/use-formula-suggest.js +126 -0
  39. package/dist/vue/use-sheet-grid.js +180 -0
  40. package/dist/vue/use-sheet-state.js +122 -0
  41. package/dist/vue/use-sheet-tabs-bar.js +90 -0
  42. package/dist/vue/use-tool-groups.js +42 -0
  43. package/dist/vue/use-tool-popup.js +123 -0
  44. package/dist/vue/use-toolbar-scroll.js +72 -0
  45. package/package.json +12 -6
  46. package/src/env.d.ts +14 -0
  47. package/src/index.ts +203 -13
  48. package/src/tools/__test__/builtin.test.ts +208 -66
  49. package/src/tools/__test__/context.test.ts +62 -2
  50. package/src/tools/builtin.ts +350 -6
  51. package/src/tools/context.ts +89 -11
  52. package/src/tools/download.ts +82 -0
  53. package/src/tools/export.worker.ts +61 -0
  54. package/src/tools/registry.ts +16 -1
  55. package/src/types/index.ts +6 -0
  56. package/src/types/sheet.ts +5 -3
  57. package/src/vue/__test__/formula-bar.test.ts +455 -0
  58. package/src/vue/__test__/import-file.test.ts +183 -0
  59. package/src/vue/__test__/insert-count-menu-item.test.ts +123 -0
  60. package/src/vue/__test__/insert-image-popup.test.ts +89 -0
  61. package/src/vue/__test__/insert-image.test.ts +100 -0
  62. package/src/vue/__test__/sheet-component.test.ts +716 -27
  63. package/src/vue/__test__/sheet-context-menu.test.ts +206 -0
  64. package/src/vue/__test__/use-formula-ref-select.test.ts +35 -0
  65. package/src/vue/__test__/use-formula-suggest.test.ts +83 -0
  66. package/src/vue/formula-bar.vue +473 -0
  67. package/src/vue/formula-suggest-list.vue +64 -0
  68. package/src/vue/import-file.ts +173 -0
  69. package/src/vue/insert-count-menu-item.vue +69 -0
  70. package/src/vue/insert-image.ts +90 -0
  71. package/src/vue/parsing.ts +11 -0
  72. package/src/vue/popup-helpers.ts +50 -0
  73. package/src/vue/popups/border-popup.vue +93 -0
  74. package/src/vue/popups/border-preset-glyph.vue +111 -0
  75. package/src/vue/popups/export-popup.vue +52 -0
  76. package/src/vue/popups/fill-color-popup.vue +39 -0
  77. package/src/vue/popups/find-popup.vue +111 -0
  78. package/src/vue/popups/font-color-popup.vue +39 -0
  79. package/src/vue/popups/font-size-popup.vue +52 -0
  80. package/src/vue/popups/import.worker.ts +66 -0
  81. package/src/vue/popups/insert-image-popup.vue +39 -0
  82. package/src/vue/sheet-context-menu.ts +229 -0
  83. package/src/vue/sheet-tabs.vue +162 -0
  84. package/src/vue/sheet-toolbar.vue +91 -0
  85. package/src/vue/sheet.vue +163 -148
  86. package/src/vue/style.scss +646 -3
  87. package/src/vue/style.ts +12 -0
  88. package/src/vue/use-find-replace.ts +200 -0
  89. package/src/vue/use-formula-ref-select.ts +34 -0
  90. package/src/vue/use-formula-suggest.ts +150 -0
  91. package/src/vue/use-sheet-grid.ts +243 -0
  92. package/src/vue/use-sheet-state.ts +161 -0
  93. package/src/vue/use-sheet-tabs-bar.ts +115 -0
  94. package/src/vue/use-tool-groups.ts +61 -0
  95. package/src/vue/use-tool-popup.ts +150 -0
  96. package/src/vue/use-toolbar-scroll.ts +95 -0
  97. package/dist/core/address.d.ts +0 -60
  98. package/dist/core/address.js +0 -142
  99. package/dist/core/address.js.map +0 -1
  100. package/dist/core/cell-store.d.ts +0 -61
  101. package/dist/core/cell-store.js +0 -135
  102. package/dist/core/cell-store.js.map +0 -1
  103. package/dist/core/command/default-registry.d.ts +0 -10
  104. package/dist/core/command/default-registry.js +0 -18
  105. package/dist/core/command/default-registry.js.map +0 -1
  106. package/dist/core/command/history.d.ts +0 -64
  107. package/dist/core/command/history.js +0 -127
  108. package/dist/core/command/history.js.map +0 -1
  109. package/dist/core/command/merge-cells.d.ts +0 -21
  110. package/dist/core/command/merge-cells.js +0 -74
  111. package/dist/core/command/merge-cells.js.map +0 -1
  112. package/dist/core/command/registry.d.ts +0 -17
  113. package/dist/core/command/registry.js +0 -26
  114. package/dist/core/command/registry.js.map +0 -1
  115. package/dist/core/command/set-cell-formula.d.ts +0 -17
  116. package/dist/core/command/set-cell-formula.js +0 -29
  117. package/dist/core/command/set-cell-formula.js.map +0 -1
  118. package/dist/core/command/set-cell-value.d.ts +0 -22
  119. package/dist/core/command/set-cell-value.js +0 -35
  120. package/dist/core/command/set-cell-value.js.map +0 -1
  121. package/dist/core/command/types.d.ts +0 -68
  122. package/dist/core/events.js +0 -29
  123. package/dist/core/events.js.map +0 -1
  124. package/dist/core/formula/ast.d.ts +0 -58
  125. package/dist/core/formula/ast.js +0 -38
  126. package/dist/core/formula/ast.js.map +0 -1
  127. package/dist/core/formula/dependency-graph.d.ts +0 -70
  128. package/dist/core/formula/dependency-graph.js +0 -269
  129. package/dist/core/formula/dependency-graph.js.map +0 -1
  130. package/dist/core/formula/errors.d.ts +0 -25
  131. package/dist/core/formula/errors.js +0 -36
  132. package/dist/core/formula/errors.js.map +0 -1
  133. package/dist/core/formula/evaluator.d.ts +0 -41
  134. package/dist/core/formula/evaluator.js +0 -154
  135. package/dist/core/formula/evaluator.js.map +0 -1
  136. package/dist/core/formula/functions.d.ts +0 -30
  137. package/dist/core/formula/functions.js +0 -209
  138. package/dist/core/formula/functions.js.map +0 -1
  139. package/dist/core/formula/parser.d.ts +0 -7
  140. package/dist/core/formula/parser.js +0 -208
  141. package/dist/core/formula/parser.js.map +0 -1
  142. package/dist/core/formula/tokenizer.d.ts +0 -37
  143. package/dist/core/formula/tokenizer.js +0 -146
  144. package/dist/core/formula/tokenizer.js.map +0 -1
  145. package/dist/core/merge-manager.d.ts +0 -64
  146. package/dist/core/merge-manager.js +0 -111
  147. package/dist/core/merge-manager.js.map +0 -1
  148. package/dist/core/selection.d.ts +0 -31
  149. package/dist/core/selection.js +0 -62
  150. package/dist/core/selection.js.map +0 -1
  151. package/dist/core/sheet.d.ts +0 -105
  152. package/dist/core/sheet.js +0 -202
  153. package/dist/core/sheet.js.map +0 -1
  154. package/dist/core/workbook.d.ts +0 -43
  155. package/dist/core/workbook.js +0 -77
  156. package/dist/core/workbook.js.map +0 -1
  157. package/dist/grid/sheet-grid.d.ts +0 -59
  158. package/dist/grid/sheet-grid.js +0 -230
  159. package/dist/grid/sheet-grid.js.map +0 -1
  160. package/dist/tools/builtin.js.map +0 -1
  161. package/dist/tools/context.js.map +0 -1
  162. package/dist/tools/registry.js.map +0 -1
  163. package/dist/vue/index.d.ts +0 -2
  164. package/dist/vue/sheet.js.map +0 -1
  165. package/dist/vue/sheet.vue.d.ts +0 -24
  166. package/src/core/__test__/address.test.ts +0 -113
  167. package/src/core/__test__/cell-store.test.ts +0 -97
  168. package/src/core/__test__/command.test.ts +0 -377
  169. package/src/core/__test__/formula-adversarial.test.ts +0 -265
  170. package/src/core/__test__/formula-engine.test.ts +0 -470
  171. package/src/core/__test__/formula-functions.test.ts +0 -178
  172. package/src/core/__test__/formula-parser.test.ts +0 -160
  173. package/src/core/__test__/history.test.ts +0 -225
  174. package/src/core/__test__/merge-manager.test.ts +0 -106
  175. package/src/core/__test__/sheet.test.ts +0 -142
  176. package/src/core/address.ts +0 -166
  177. package/src/core/cell-store.ts +0 -173
  178. package/src/core/command/default-registry.ts +0 -15
  179. package/src/core/command/history.ts +0 -167
  180. package/src/core/command/index.ts +0 -7
  181. package/src/core/command/merge-cells.ts +0 -74
  182. package/src/core/command/registry.ts +0 -30
  183. package/src/core/command/set-cell-formula.ts +0 -29
  184. package/src/core/command/set-cell-value.ts +0 -38
  185. package/src/core/command/types.ts +0 -73
  186. package/src/core/events.ts +0 -37
  187. package/src/core/formula/ast.ts +0 -66
  188. package/src/core/formula/dependency-graph.ts +0 -329
  189. package/src/core/formula/errors.ts +0 -41
  190. package/src/core/formula/evaluator.ts +0 -219
  191. package/src/core/formula/functions.ts +0 -266
  192. package/src/core/formula/index.ts +0 -7
  193. package/src/core/formula/parser.ts +0 -198
  194. package/src/core/formula/tokenizer.ts +0 -160
  195. package/src/core/merge-manager.ts +0 -158
  196. package/src/core/selection.ts +0 -66
  197. package/src/core/sheet.ts +0 -257
  198. package/src/core/workbook.ts +0 -99
  199. package/src/grid/__test__/canvas-mock.ts +0 -49
  200. package/src/grid/__test__/setup.ts +0 -3
  201. package/src/grid/__test__/sheet-grid-formula.test.ts +0 -140
  202. package/src/grid/__test__/sheet-grid.test.ts +0 -177
  203. 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>