@veltra/desktop 1.7.8 → 1.7.10

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 (74) hide show
  1. package/dist/components/batch-edit/batch-edit-form.js +7 -3
  2. package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
  3. package/dist/components/batch-edit/batch-edit.js +1 -0
  4. package/dist/components/batch-edit/batch-edit.js.map +1 -1
  5. package/dist/components/batch-edit/use-edit-state.js.map +1 -1
  6. package/dist/components/form/form.js.map +1 -1
  7. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js +31 -6
  8. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js.map +1 -1
  9. package/dist/components/form/is-field-modified.js +12 -0
  10. package/dist/components/form/is-field-modified.js.map +1 -0
  11. package/dist/components/form/style2.css +37 -1
  12. package/dist/components/form/use-form-fields.js +11 -6
  13. package/dist/components/form/use-form-fields.js.map +1 -1
  14. package/dist/components/form-item/form-item.js.map +1 -1
  15. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js +18 -5
  16. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
  17. package/dist/components/image-cropper/draw-crop.js +63 -0
  18. package/dist/components/image-cropper/draw-crop.js.map +1 -0
  19. package/dist/components/image-cropper/image-cropper.js +344 -0
  20. package/dist/components/image-cropper/image-cropper.js.map +1 -0
  21. package/dist/components/image-cropper/style.d.ts +1 -0
  22. package/dist/components/image-cropper/style.js +1 -0
  23. package/dist/components/image-cropper/style2.css +205 -0
  24. package/dist/components/image-cropper/use-image-loader.js +59 -0
  25. package/dist/components/image-cropper/use-image-loader.js.map +1 -0
  26. package/dist/components/image-cropper/use-preview.js +44 -0
  27. package/dist/components/image-cropper/use-preview.js.map +1 -0
  28. package/dist/components/image-cropper/use-selection.js +183 -0
  29. package/dist/components/image-cropper/use-selection.js.map +1 -0
  30. package/dist/components/image-cropper/use-transform.js +182 -0
  31. package/dist/components/image-cropper/use-transform.js.map +1 -0
  32. package/dist/components/index.d.ts +2 -1
  33. package/dist/components/index.js +3 -1
  34. package/dist/components/index.js.map +1 -1
  35. package/dist/index.d.ts +47 -45
  36. package/dist/index.js +46 -45
  37. package/dist/types/batch-edit.d.ts +2 -0
  38. package/dist/types/form-item.d.ts +4 -1
  39. package/dist/types/form.d.ts +10 -1
  40. package/dist/types/image-cropper.d.ts +68 -0
  41. package/dist/types/index.d.ts +2 -1
  42. package/package.json +6 -6
  43. package/src/components/auto-complete/style.scss +3 -15
  44. package/src/components/batch-edit/__test__/batch-edit.test.ts +138 -0
  45. package/src/components/batch-edit/batch-edit-form.vue +18 -4
  46. package/src/components/batch-edit/use-edit-state.ts +1 -1
  47. package/src/components/contextmenu/style.scss +4 -20
  48. package/src/components/form/__test__/form.test.ts +80 -0
  49. package/src/components/form/__test__/is-field-modified.test.ts +37 -0
  50. package/src/components/form/form.vue +28 -4
  51. package/src/components/form/is-field-modified.ts +16 -0
  52. package/src/components/form/style.scss +53 -2
  53. package/src/components/form/use-form-fields.ts +20 -6
  54. package/src/components/form-item/__test__/form-item.test.ts +90 -0
  55. package/src/components/form-item/form-item.vue +42 -7
  56. package/src/components/image-cropper/__test__/draw-crop.test.ts +131 -0
  57. package/src/components/image-cropper/__test__/image-cropper.test.ts +744 -0
  58. package/src/components/image-cropper/__test__/use-transform.test.ts +229 -0
  59. package/src/components/image-cropper/draw-crop.ts +112 -0
  60. package/src/components/image-cropper/image-cropper.vue +302 -0
  61. package/src/components/image-cropper/index.ts +1 -0
  62. package/src/components/image-cropper/style.scss +232 -0
  63. package/src/components/image-cropper/style.ts +1 -0
  64. package/src/components/image-cropper/use-image-loader.ts +79 -0
  65. package/src/components/image-cropper/use-preview.ts +58 -0
  66. package/src/components/image-cropper/use-selection.ts +255 -0
  67. package/src/components/image-cropper/use-transform.ts +256 -0
  68. package/src/components/index.ts +2 -0
  69. package/src/components/select/style.scss +3 -15
  70. package/src/types/batch-edit.ts +2 -0
  71. package/src/types/form-item.ts +4 -1
  72. package/src/types/form.ts +10 -2
  73. package/src/types/image-cropper.ts +73 -0
  74. package/src/types/index.ts +1 -0
@@ -17,7 +17,7 @@
17
17
 
18
18
  <section :class="cls.e('wrapper')">
19
19
  <div :class="cls.e('content')">
20
- <slot></slot>
20
+ <ContentSlot />
21
21
  </div>
22
22
 
23
23
  <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->
@@ -37,9 +37,18 @@ import { o } from '@cat-kit/core'
37
37
  import { useConfig, useFallbackProps } from '@veltra/compositions'
38
38
  import { bem, withUnit } from '@veltra/utils'
39
39
  import { injectFormContext } from '@veltra/utils'
40
- import { type CSSProperties, computed, onBeforeUnmount, shallowRef, watch } from 'vue'
41
-
42
- import type { FormItemProps, ComponentSize } from '../../types'
40
+ import {
41
+ type CSSProperties,
42
+ type VNode,
43
+ cloneVNode,
44
+ computed,
45
+ onBeforeUnmount,
46
+ shallowRef,
47
+ useSlots,
48
+ watch
49
+ } from 'vue'
50
+
51
+ import type { FormItemEmits, FormItemProps, ComponentSize } from '../../types'
43
52
  import { UGridItem } from '../grid'
44
53
  import { UTip } from '../tip'
45
54
  import { formItemCls as cls, defineField } from './helper'
@@ -49,6 +58,10 @@ defineOptions({ name: 'UFormItem' })
49
58
 
50
59
  const props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined })
51
60
 
61
+ const emit = defineEmits<FormItemEmits>()
62
+
63
+ const slots = useSlots()
64
+
52
65
  defineSlots<{
53
66
  /** 标签插槽 */
54
67
  label?: () => any
@@ -56,8 +69,30 @@ defineSlots<{
56
69
  }>()
57
70
 
58
71
  /** 表单组件上下文 */
59
- const { formProps, registerField, unregisterField, shouldValidate, handleFieldChange } =
60
- injectFormContext()
72
+ const {
73
+ formProps,
74
+ registerField,
75
+ unregisterField,
76
+ shouldValidate,
77
+ handleFieldUpdate,
78
+ handleFieldChange
79
+ } = injectFormContext()
80
+
81
+ function wrapControlChange(node: VNode) {
82
+ if (!node || typeof node.type === 'symbol') return node
83
+
84
+ return cloneVNode(node, {
85
+ onChange: (...args: any[]) => {
86
+ emit('change', ...args)
87
+ if (props.field) handleFieldChange?.(props.field, ...args)
88
+ }
89
+ })
90
+ }
91
+
92
+ function ContentSlot() {
93
+ const nodes = slots.default?.() ?? []
94
+ return nodes.map(wrapControlChange)
95
+ }
61
96
 
62
97
  const { config } = useConfig()
63
98
 
@@ -137,7 +172,7 @@ watch(
137
172
  stopWatchFieldValue = watch(
138
173
  () => o(model).get(field),
139
174
  (value) => {
140
- handleFieldChange?.(field, value)
175
+ handleFieldUpdate?.(field, value)
141
176
  fieldItem.validate()
142
177
  }
143
178
  )
@@ -0,0 +1,131 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ import { cropToResult, drawCropToCanvas } from '../draw-crop'
4
+
5
+ /** 记录调用参数的 2d 上下文桩 */
6
+ function createCtxStub() {
7
+ return { translate: vi.fn(), rotate: vi.fn(), scale: vi.fn(), drawImage: vi.fn() }
8
+ }
9
+
10
+ function createCanvasStub(ctx: ReturnType<typeof createCtxStub>) {
11
+ return { width: 0, height: 0, getContext: () => ctx } as unknown as HTMLCanvasElement
12
+ }
13
+
14
+ const img = {} as HTMLImageElement
15
+ const selection = { x: 10, y: 20, width: 200, height: 100 }
16
+ const identity = { rotation: 0, flipX: false, flipY: false }
17
+
18
+ describe('drawCropToCanvas', () => {
19
+ it('无变换时按选区原图像素尺寸输出', () => {
20
+ const ctx = createCtxStub()
21
+ const canvas = createCanvasStub(ctx)
22
+
23
+ drawCropToCanvas(img, selection, identity, canvas)
24
+
25
+ expect(canvas.width).toBe(200)
26
+ expect(canvas.height).toBe(100)
27
+ expect(ctx.rotate).toHaveBeenCalledWith(0)
28
+ expect(ctx.scale).toHaveBeenCalledWith(1, 1)
29
+ expect(ctx.drawImage).toHaveBeenCalledWith(img, 10, 20, 200, 100, -100, -50, 200, 100)
30
+ })
31
+
32
+ it('旋转 90° / 270° 时输出尺寸交换宽高', () => {
33
+ const ctx = createCtxStub()
34
+ const canvas = createCanvasStub(ctx)
35
+
36
+ drawCropToCanvas(img, selection, { ...identity, rotation: 90 }, canvas)
37
+
38
+ expect(canvas.width).toBe(100)
39
+ expect(canvas.height).toBe(200)
40
+ expect(ctx.rotate).toHaveBeenCalledWith(Math.PI / 2)
41
+ // 变换顺序与舞台一致:先旋转后缩放(翻转)
42
+ expect(ctx.translate).toHaveBeenCalledWith(50, 100)
43
+ })
44
+
45
+ it('翻转以负缩放体现', () => {
46
+ const ctx = createCtxStub()
47
+ const canvas = createCanvasStub(ctx)
48
+
49
+ drawCropToCanvas(img, selection, { ...identity, flipX: true, flipY: true }, canvas)
50
+
51
+ expect(ctx.scale).toHaveBeenCalledWith(-1, -1)
52
+ })
53
+
54
+ it('传入输出尺寸时按该尺寸缩放输出', () => {
55
+ const ctx = createCtxStub()
56
+ const canvas = createCanvasStub(ctx)
57
+
58
+ drawCropToCanvas(img, selection, identity, canvas, { width: 400, height: 200 })
59
+
60
+ expect(canvas.width).toBe(400)
61
+ expect(canvas.height).toBe(200)
62
+ expect(ctx.scale).toHaveBeenCalledWith(2, 2)
63
+ })
64
+
65
+ it('输出尺寸只传一个维度时另一维按选区比例推算', () => {
66
+ const ctx = createCtxStub()
67
+ const canvas = createCanvasStub(ctx)
68
+
69
+ drawCropToCanvas(img, selection, identity, canvas, { width: 100 })
70
+ expect(canvas.width).toBe(100)
71
+ expect(canvas.height).toBe(50)
72
+
73
+ drawCropToCanvas(img, selection, identity, canvas, { height: 400 })
74
+ expect(canvas.width).toBe(800)
75
+ expect(canvas.height).toBe(400)
76
+ })
77
+
78
+ it('选区像素为小数时输出尺寸取整', () => {
79
+ const ctx = createCtxStub()
80
+ const canvas = createCanvasStub(ctx)
81
+
82
+ drawCropToCanvas(img, { x: 0, y: 0, width: 100.4, height: 50.6 }, identity, canvas)
83
+
84
+ expect(canvas.width).toBe(100)
85
+ expect(canvas.height).toBe(51)
86
+ })
87
+ })
88
+
89
+ describe('cropToResult', () => {
90
+ afterEach(() => {
91
+ vi.restoreAllMocks()
92
+ })
93
+
94
+ it('默认按原图选区像素导出 Blob + base64 dataURL', async () => {
95
+ const toBlobSpy = vi.spyOn(HTMLCanvasElement.prototype, 'toBlob')
96
+
97
+ const { blob, base64 } = await cropToResult(img, selection, identity)
98
+
99
+ // 离屏画布按选区 200×100 输出
100
+ const canvas = toBlobSpy.mock.instances[0] as unknown as HTMLCanvasElement
101
+ expect(canvas.width).toBe(200)
102
+ expect(canvas.height).toBe(100)
103
+ expect(blob).toBeInstanceOf(Blob)
104
+ expect(blob.type).toBe('image/png')
105
+ expect(base64.startsWith('data:image/png;base64,')).toBe(true)
106
+ })
107
+
108
+ it('传入输出尺寸时按该尺寸缩放导出', async () => {
109
+ const toBlobSpy = vi.spyOn(HTMLCanvasElement.prototype, 'toBlob')
110
+
111
+ await cropToResult(img, selection, identity, { width: 100 })
112
+
113
+ const canvas = toBlobSpy.mock.instances[0] as unknown as HTMLCanvasElement
114
+ expect(canvas.width).toBe(100)
115
+ expect(canvas.height).toBe(50)
116
+ })
117
+
118
+ it('canvas 被跨域图片污染(toBlob 抛错)时拒绝为可捕获错误', async () => {
119
+ vi.spyOn(HTMLCanvasElement.prototype, 'toBlob').mockImplementation(() => {
120
+ throw new DOMException('The canvas has been tainted by cross-origin data.', 'SecurityError')
121
+ })
122
+
123
+ await expect(cropToResult(img, selection, identity)).rejects.toThrow(/tainted/)
124
+ })
125
+
126
+ it('toBlob 未产出数据时拒绝而不是静默返回', async () => {
127
+ vi.spyOn(HTMLCanvasElement.prototype, 'toBlob').mockImplementation((cb) => cb(null))
128
+
129
+ await expect(cropToResult(img, selection, identity)).rejects.toThrow(/got null/)
130
+ })
131
+ })