@veltra/desktop 1.7.12 → 1.7.15

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 (190) hide show
  1. package/dist/components/batch-edit/batch-edit-form.js +9 -3
  2. package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
  3. package/dist/components/batch-edit/batch-edit-list.js +2 -1
  4. package/dist/components/batch-edit/batch-edit-list.js.map +1 -1
  5. package/dist/components/batch-edit/batch-edit.js +57 -30
  6. package/dist/components/batch-edit/batch-edit.js.map +1 -1
  7. package/dist/components/batch-edit/helper.d.ts +1 -2
  8. package/dist/components/batch-edit/style.d.ts +1 -0
  9. package/dist/components/batch-edit/style.js +1 -0
  10. package/dist/components/batch-edit/style2.css +14 -0
  11. package/dist/components/batch-edit/use-dialog-effect.js +20 -0
  12. package/dist/components/batch-edit/use-dialog-effect.js.map +1 -0
  13. package/dist/components/batch-edit/use-edit-state.js +22 -3
  14. package/dist/components/batch-edit/use-edit-state.js.map +1 -1
  15. package/dist/components/batch-edit/use-handlers.js +5 -0
  16. package/dist/components/batch-edit/use-handlers.js.map +1 -1
  17. package/dist/components/batch-edit/use-shortcut-key.js +5 -2
  18. package/dist/components/batch-edit/use-shortcut-key.js.map +1 -1
  19. package/dist/components/condition-editor/core/evaluator.d.ts +4 -5
  20. package/dist/components/contextmenu/api.d.ts +1 -2
  21. package/dist/components/contextmenu/di.d.ts +2 -3
  22. package/dist/components/file-viewer/file-viewer.js +21 -14
  23. package/dist/components/file-viewer/file-viewer.js.map +1 -1
  24. package/dist/components/file-viewer/helper.js +2 -0
  25. package/dist/components/file-viewer/helper.js.map +1 -1
  26. package/dist/components/file-viewer/previewers/ofd-previewer.js +196 -0
  27. package/dist/components/file-viewer/previewers/ofd-previewer.js.map +1 -0
  28. package/dist/components/file-viewer/style2.css +27 -0
  29. package/dist/components/loading/directive.d.ts +1 -2
  30. package/dist/components/message/message.d.ts +1 -2
  31. package/dist/components/message-confirm/message-confirm.d.ts +1 -2
  32. package/dist/components/notification/notification.d.ts +1 -2
  33. package/dist/components/table/node/col.js +19 -0
  34. package/dist/components/table/node/col.js.map +1 -1
  35. package/dist/components/table/table-cell.js +2 -0
  36. package/dist/components/table/table-cell.js.map +1 -1
  37. package/dist/components/table/table-foot.js +2 -0
  38. package/dist/components/table/table-foot.js.map +1 -1
  39. package/dist/components/table/table-head.js +1 -0
  40. package/dist/components/table/table-head.js.map +1 -1
  41. package/dist/components/table/table-row.js +2 -0
  42. package/dist/components/table/table-row.js.map +1 -1
  43. package/dist/components/table/use-columns.d.ts +1 -2
  44. package/dist/components/table-editor/style2.css +23 -3
  45. package/dist/components/table-editor/table-editor.js +213 -63
  46. package/dist/components/table-editor/table-editor.js.map +1 -1
  47. package/dist/components/tip/style2.css +63 -0
  48. package/dist/components/tip/tip.js +6 -1
  49. package/dist/components/tip/tip.js.map +1 -1
  50. package/dist/index.d.ts +4 -4
  51. package/dist/install.d.ts +3 -3
  52. package/dist/packages/ofd-core/dist/color.js +69 -0
  53. package/dist/packages/ofd-core/dist/color.js.map +1 -0
  54. package/dist/packages/ofd-core/dist/ctm.js +55 -0
  55. package/dist/packages/ofd-core/dist/ctm.js.map +1 -0
  56. package/dist/packages/ofd-core/dist/error.js +14 -0
  57. package/dist/packages/ofd-core/dist/error.js.map +1 -0
  58. package/dist/packages/ofd-core/dist/font.js +384 -0
  59. package/dist/packages/ofd-core/dist/font.js.map +1 -0
  60. package/dist/packages/ofd-core/dist/index.js +6 -0
  61. package/dist/packages/ofd-core/dist/number.js +13 -0
  62. package/dist/packages/ofd-core/dist/number.js.map +1 -0
  63. package/dist/packages/ofd-core/dist/page-objects.js +181 -0
  64. package/dist/packages/ofd-core/dist/page-objects.js.map +1 -0
  65. package/dist/packages/ofd-core/dist/parse.js +65 -0
  66. package/dist/packages/ofd-core/dist/parse.js.map +1 -0
  67. package/dist/packages/ofd-core/dist/render.js +361 -0
  68. package/dist/packages/ofd-core/dist/render.js.map +1 -0
  69. package/dist/packages/ofd-core/dist/xml.js +199 -0
  70. package/dist/packages/ofd-core/dist/xml.js.map +1 -0
  71. package/dist/packages/ofd-core/dist/zip.js +93 -0
  72. package/dist/packages/ofd-core/dist/zip.js.map +1 -0
  73. package/dist/style.d.ts +1 -1
  74. package/dist/style.js +1 -1
  75. package/dist/types/action.d.ts +7 -8
  76. package/dist/types/animation.d.ts +4 -5
  77. package/dist/types/auto-complete.d.ts +4 -5
  78. package/dist/types/badge.d.ts +4 -5
  79. package/dist/types/batch-edit.d.ts +15 -10
  80. package/dist/types/breadcrumb.d.ts +6 -7
  81. package/dist/types/button.d.ts +5 -6
  82. package/dist/types/calendar.d.ts +7 -8
  83. package/dist/types/card.d.ts +7 -8
  84. package/dist/types/cascade.d.ts +6 -7
  85. package/dist/types/check-tag.d.ts +4 -5
  86. package/dist/types/checkbox-group.d.ts +3 -4
  87. package/dist/types/checkbox.d.ts +5 -6
  88. package/dist/types/code-editor.d.ts +5 -6
  89. package/dist/types/collapse.d.ts +6 -7
  90. package/dist/types/condition-editor.d.ts +11 -12
  91. package/dist/types/contextmenu.d.ts +5 -6
  92. package/dist/types/css-transition.d.ts +4 -5
  93. package/dist/types/date-panel.d.ts +3 -4
  94. package/dist/types/date-picker.d.ts +5 -6
  95. package/dist/types/date-range-picker.d.ts +6 -7
  96. package/dist/types/dialog.d.ts +5 -6
  97. package/dist/types/drawer.d.ts +6 -7
  98. package/dist/types/dropdown.d.ts +4 -5
  99. package/dist/types/dual-nav.d.ts +6 -7
  100. package/dist/types/empty.d.ts +4 -5
  101. package/dist/types/expression-editor.d.ts +6 -7
  102. package/dist/types/file-picker.d.ts +4 -5
  103. package/dist/types/file-viewer.d.ts +6 -7
  104. package/dist/types/float-button.d.ts +5 -6
  105. package/dist/types/form-item.d.ts +3 -4
  106. package/dist/types/form.d.ts +4 -5
  107. package/dist/types/gantt-chart.d.ts +4 -5
  108. package/dist/types/grid-input.d.ts +4 -5
  109. package/dist/types/grid.d.ts +7 -8
  110. package/dist/types/group-input.d.ts +4 -5
  111. package/dist/types/group-nav.d.ts +3 -4
  112. package/dist/types/icon.d.ts +3 -4
  113. package/dist/types/image-cropper.d.ts +9 -10
  114. package/dist/types/index.d.ts +4 -4
  115. package/dist/types/input.d.ts +4 -5
  116. package/dist/types/layout.d.ts +4 -5
  117. package/dist/types/list.d.ts +4 -5
  118. package/dist/types/loading.d.ts +5 -6
  119. package/dist/types/message-confirm.d.ts +8 -9
  120. package/dist/types/message.d.ts +8 -9
  121. package/dist/types/multi-auto-complete.d.ts +4 -5
  122. package/dist/types/multi-select.d.ts +4 -5
  123. package/dist/types/multi-tree-select.d.ts +4 -5
  124. package/dist/types/nav.d.ts +5 -6
  125. package/dist/types/node-render.d.ts +2 -3
  126. package/dist/types/notification.d.ts +8 -9
  127. package/dist/types/number-input.d.ts +3 -4
  128. package/dist/types/number-range-input.d.ts +4 -5
  129. package/dist/types/number.d.ts +3 -4
  130. package/dist/types/paginator.d.ts +4 -5
  131. package/dist/types/palette.d.ts +7 -8
  132. package/dist/types/password-input.d.ts +4 -5
  133. package/dist/types/pop-confirm.d.ts +4 -5
  134. package/dist/types/pop.d.ts +4 -5
  135. package/dist/types/progress-nodes.d.ts +4 -5
  136. package/dist/types/progress.d.ts +4 -5
  137. package/dist/types/quick-batch-edit.d.ts +4 -5
  138. package/dist/types/radio-group.d.ts +4 -5
  139. package/dist/types/radio.d.ts +4 -5
  140. package/dist/types/rich-text-editor.d.ts +6 -7
  141. package/dist/types/scroll.d.ts +5 -6
  142. package/dist/types/segment.d.ts +5 -6
  143. package/dist/types/select.d.ts +4 -5
  144. package/dist/types/slider.d.ts +4 -5
  145. package/dist/types/steps.d.ts +5 -6
  146. package/dist/types/switch.d.ts +4 -5
  147. package/dist/types/table-editor.d.ts +30 -10
  148. package/dist/types/table.d.ts +23 -13
  149. package/dist/types/tabs.d.ts +13 -14
  150. package/dist/types/tag.d.ts +3 -4
  151. package/dist/types/text-editor.d.ts +5 -6
  152. package/dist/types/text.d.ts +4 -5
  153. package/dist/types/textarea.d.ts +4 -5
  154. package/dist/types/theme.d.ts +3 -4
  155. package/dist/types/tip.d.ts +11 -8
  156. package/dist/types/tree-select.d.ts +4 -5
  157. package/dist/types/tree.d.ts +6 -6
  158. package/dist/types/watermark.d.ts +4 -5
  159. package/package.json +8 -7
  160. package/src/components/batch-edit/__test__/batch-edit.test.ts +191 -4
  161. package/src/components/batch-edit/batch-edit-form.vue +17 -3
  162. package/src/components/batch-edit/batch-edit-list.vue +2 -1
  163. package/src/components/batch-edit/batch-edit.vue +22 -4
  164. package/src/components/batch-edit/style.scss +20 -0
  165. package/src/components/batch-edit/style.ts +1 -0
  166. package/src/components/batch-edit/use-dialog-effect.ts +9 -3
  167. package/src/components/batch-edit/use-edit-state.ts +32 -4
  168. package/src/components/batch-edit/use-handlers.ts +10 -0
  169. package/src/components/batch-edit/use-shortcut-key.ts +6 -2
  170. package/src/components/file-viewer/__test__/helper.test.ts +13 -0
  171. package/src/components/file-viewer/file-viewer.vue +23 -16
  172. package/src/components/file-viewer/helper.ts +2 -0
  173. package/src/components/file-viewer/previewers/ofd-previewer.vue +216 -0
  174. package/src/components/file-viewer/style.scss +33 -0
  175. package/src/components/table/__test__/column-style.test.ts +67 -0
  176. package/src/components/table/node/col.ts +30 -1
  177. package/src/components/table/table-cell.vue +7 -2
  178. package/src/components/table/table-foot.vue +2 -0
  179. package/src/components/table/table-head.tsx +1 -0
  180. package/src/components/table/table-row.tsx +2 -0
  181. package/src/components/table-editor/__test__/table-editor.test.ts +471 -0
  182. package/src/components/table-editor/style.scss +42 -4
  183. package/src/components/table-editor/table-editor.vue +265 -38
  184. package/src/components/tip/style.scss +15 -0
  185. package/src/components/tip/tip.vue +1 -1
  186. package/src/types/batch-edit.ts +6 -0
  187. package/src/types/file-viewer.ts +1 -1
  188. package/src/types/table-editor.ts +29 -7
  189. package/src/types/table.ts +12 -0
  190. package/src/types/tip.ts +6 -1
@@ -1,12 +1,16 @@
1
1
  import { CSSProperties } from "vue";
2
- import { DeconstructValue } from "@veltra/utils";
2
+ import { ColorType, DeconstructValue } from "@veltra/utils";
3
3
  //#region src/types/tip.d.ts
4
- type TipDirection = 'top' | 'bottom' | 'left' | 'right';
5
- type TipAlign = 'center' | 'start' | 'end';
4
+ export type TipDirection = 'top' | 'bottom' | 'left' | 'right';
5
+ export type TipAlign = 'center' | 'start' | 'end';
6
+ /** tip类型 */
7
+ export type TipType = ColorType;
6
8
  /** tip提示组件组件属性 */
7
- interface TipProps {
9
+ export interface TipProps {
8
10
  /** 控制显影 */
9
11
  visible?: boolean;
12
+ /** tip类型 */
13
+ type?: TipType;
10
14
  /**提示内容 */
11
15
  content?: string;
12
16
  /** 自定义tip样式 */
@@ -45,13 +49,12 @@ interface TipProps {
45
49
  showDelay?: number;
46
50
  }
47
51
  /** tip提示组件组件定义的事件 */
48
- interface TipEmits {
52
+ export interface TipEmits {
49
53
  (e: 'update:visible', value: boolean): void;
50
54
  }
51
55
  /** tip提示组件组件暴露的属性和方法(组件内部使用) */
52
- interface _TipExposed {}
56
+ export interface _TipExposed {}
53
57
  /** tip提示组件组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
54
- type TipExposed = DeconstructValue<_TipExposed>;
58
+ export type TipExposed = DeconstructValue<_TipExposed>;
55
59
  //#endregion
56
- export { TipAlign, TipDirection, TipEmits, TipExposed, TipProps, _TipExposed };
57
60
  //# sourceMappingURL=tip.d.ts.map
@@ -3,7 +3,7 @@ import { CSSProperties } from "vue";
3
3
  import { DeconstructValue, FormComponentProps } from "@veltra/utils";
4
4
  //#region src/types/tree-select.d.ts
5
5
  /** 树形选择器组件属性 */
6
- interface TreeSelectProps extends FormComponentProps, Omit<TreeProps, 'selected' | 'checked' | 'selectable' | 'checkable'> {
6
+ export interface TreeSelectProps extends FormComponentProps, Omit<TreeProps, 'selected' | 'checked' | 'selectable' | 'checkable'> {
7
7
  modelValue?: string | number;
8
8
  /** 自定义占位文字 */
9
9
  placeholder?: string;
@@ -31,7 +31,7 @@ interface TreeSelectProps extends FormComponentProps, Omit<TreeProps, 'selected'
31
31
  contentClass?: unknown;
32
32
  }
33
33
  /** 树形选择器组件定义的事件 */
34
- interface TreeSelectEmits {
34
+ export interface TreeSelectEmits {
35
35
  (e: 'clear'): void;
36
36
  (e: 'update:modelValue', value?: string | number): void;
37
37
  (e: 'change', selectedData?: Record<string, any>): void;
@@ -42,9 +42,8 @@ interface TreeSelectEmits {
42
42
  (e: 'update:text', text?: string): void;
43
43
  }
44
44
  /** 树形选择器组件暴露的属性和方法(组件内部使用) */
45
- interface _TreeSelectExposed {}
45
+ export interface _TreeSelectExposed {}
46
46
  /** 树形选择器组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
47
- type TreeSelectExposed = DeconstructValue<_TreeSelectExposed>;
47
+ export type TreeSelectExposed = DeconstructValue<_TreeSelectExposed>;
48
48
  //#endregion
49
- export { TreeSelectEmits, TreeSelectExposed, TreeSelectProps, _TreeSelectExposed };
50
49
  //# sourceMappingURL=tree-select.d.ts.map
@@ -20,7 +20,7 @@ interface TreeNode$1<Data extends Record<string, any> = Record<string, any>> ext
20
20
  bubbleSet: (setter: (node: TreeNode$1<Data>) => void) => void;
21
21
  }
22
22
  /** 树组件属性 */
23
- interface TreeProps {
23
+ export interface TreeProps {
24
24
  /** 是否展开所有节点 */
25
25
  expandAll?: boolean;
26
26
  /** 是否在点击节点的时候展开或者收缩节点 */
@@ -62,7 +62,7 @@ interface TreeProps {
62
62
  /** 使选中项或多选项出现在滚动视图中 */
63
63
  scrollToView?: boolean;
64
64
  }
65
- interface TreeEmit {
65
+ export interface TreeEmit {
66
66
  /** 节点展开/折叠事件 */
67
67
  (e: 'expand', node: TreeNode$1): void;
68
68
  /** 节点点击事件 */
@@ -76,7 +76,7 @@ interface TreeEmit {
76
76
  /** 选中项同步完成事件 */
77
77
  (e: 'selected-synced', selected?: Record<string, any>): void;
78
78
  }
79
- interface TreeNodeProps {
79
+ export interface TreeNodeProps {
80
80
  node: TreeNode$1;
81
81
  /**
82
82
  * 虚拟项索引:对应 `nodes` 数组中的绝对位置。
@@ -86,7 +86,7 @@ interface TreeNodeProps {
86
86
  measureElement?: (index: number, el: Element | null) => void;
87
87
  }
88
88
  /** 树组件暴露的属性和方法(组件内部使用) */
89
- interface _TreeExposed {
89
+ export interface _TreeExposed {
90
90
  /** 滚动到目标元素 */
91
91
  scrollTo: (index: number) => void;
92
92
  /**
@@ -112,7 +112,7 @@ interface _TreeExposed {
112
112
  collapseAll(): void;
113
113
  }
114
114
  /** 树组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
115
- type TreeExposed = DeconstructValue<_TreeExposed>;
115
+ export type TreeExposed = DeconstructValue<_TreeExposed>;
116
116
  //#endregion
117
- export { TreeEmit, TreeExposed, TreeNode$1 as TreeNode, TreeNodeProps, TreeProps, _TreeExposed };
117
+ export { TreeNode$1 as TreeNode };
118
118
  //# sourceMappingURL=tree.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { DeconstructValue } from "@veltra/utils";
2
2
  //#region src/types/watermark.d.ts
3
3
  /** watermark组件属性 */
4
- interface WatermarkProps {
4
+ export interface WatermarkProps {
5
5
  /** 文字 */
6
6
  text?: string;
7
7
  /** 图片 */
@@ -14,11 +14,10 @@ interface WatermarkProps {
14
14
  fontSize?: number;
15
15
  }
16
16
  /** watermark组件定义的事件 */
17
- interface WatermarkEmits {}
17
+ export interface WatermarkEmits {}
18
18
  /** watermark组件暴露的属性和方法(组件内部使用) */
19
- interface _WatermarkExposed {}
19
+ export interface _WatermarkExposed {}
20
20
  /** watermark组件暴露的属性和方法(组件外部使用, 引用的值会被自动解构) */
21
- type WatermarkExposed = DeconstructValue<_WatermarkExposed>;
21
+ export type WatermarkExposed = DeconstructValue<_WatermarkExposed>;
22
22
  //#endregion
23
- export { WatermarkEmits, WatermarkExposed, WatermarkProps, _WatermarkExposed };
24
23
  //# sourceMappingURL=watermark.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veltra/desktop",
3
- "version": "1.7.12",
3
+ "version": "1.7.15",
4
4
  "homepage": "https://github.com/cabinet-fe/ultra-ui#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/cabinet-fe/ultra-ui/issues"
@@ -75,17 +75,18 @@
75
75
  "@codemirror/search": "6.7.1",
76
76
  "@codemirror/state": "6.7.1",
77
77
  "@codemirror/theme-one-dark": "6.1.3",
78
- "@codemirror/view": "6.43.9"
78
+ "@codemirror/view": "6.43.9",
79
+ "@veltra/ofd-core": "1.0.1"
79
80
  },
80
81
  "peerDependencies": {
81
82
  "@cat-kit/core": ">=1.2.1",
82
83
  "@cat-kit/fe": ">=1.2.1",
83
- "@veltra/compositions": "^1.7.12",
84
- "@veltra/directives": "^1.7.12",
84
+ "@veltra/compositions": "^1.7.14",
85
+ "@veltra/directives": "^1.7.14",
85
86
  "@veltra/icons": "^1.5.0",
86
- "@veltra/sheet-core": "^2.5.6",
87
- "@veltra/styles": "^1.7.12",
88
- "@veltra/utils": "^1.7.12",
87
+ "@veltra/sheet-core": "^2.5.7",
88
+ "@veltra/styles": "^1.7.14",
89
+ "@veltra/utils": "^1.7.14",
89
90
  "vue": ">=3.5.42"
90
91
  },
91
92
  "peerDependenciesMeta": {
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it, vi } from 'vitest'
2
2
  import { createApp, h, nextTick, reactive, ref } from 'vue'
3
3
 
4
4
  import { UInput } from '../../input'
@@ -6,7 +6,13 @@ import { USelect } from '../../select'
6
6
  import { defineTableColumns } from '../../table'
7
7
  import { UBatchEdit } from '../index'
8
8
 
9
- function mountBatchEdit() {
9
+ interface MountOptions {
10
+ props?: Record<string, any>
11
+ /** 给「名称」输入框附加校验规则 */
12
+ labelRules?: Record<string, any>
13
+ }
14
+
15
+ function mountBatchEdit(options: MountOptions = {}) {
10
16
  const host = document.createElement('div')
11
17
  document.body.appendChild(host)
12
18
 
@@ -42,11 +48,12 @@ function mountBatchEdit() {
42
48
  columns,
43
49
  model,
44
50
  rowKey: 'id',
45
- quickEdit: true
51
+ quickEdit: true,
52
+ ...options.props
46
53
  },
47
54
  {
48
55
  form: () => [
49
- h(UInput, { field: 'label', label: '名称' }),
56
+ h(UInput, { field: 'label', label: '名称', rules: options.labelRules }),
50
57
  h(USelect, {
51
58
  field: 'behavior',
52
59
  label: '行为',
@@ -197,3 +204,183 @@ describe('UBatchEdit quick-edit 切换编辑行', () => {
197
204
  unmount()
198
205
  })
199
206
  })
207
+
208
+ function queryDialog() {
209
+ return document.body.querySelector<HTMLElement>('.u-batch-edit__form-dialog')
210
+ }
211
+
212
+ /** 等弹框(teleport 到 body)的进入 / 离开过渡走完 */
213
+ async function flushDialog() {
214
+ await nextTick()
215
+ await nextTick()
216
+ await new Promise((resolve) => setTimeout(resolve, 20))
217
+ await nextTick()
218
+ }
219
+
220
+ function clickEl(el: HTMLElement) {
221
+ el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
222
+ }
223
+
224
+ function setInputValue(input: HTMLInputElement, value: string) {
225
+ input.value = value
226
+ input.dispatchEvent(
227
+ new InputEvent('input', { bubbles: true, data: value, inputType: 'insertText' })
228
+ )
229
+ }
230
+
231
+ function querySaveButton(dialog: HTMLElement) {
232
+ return dialog.querySelector<HTMLElement>('.u-batch-edit__form-actions button[title="保存"]')
233
+ }
234
+
235
+ function queryCancelButton(dialog: HTMLElement) {
236
+ return [...dialog.querySelectorAll<HTMLElement>('.u-batch-edit__form-actions button')].find(
237
+ (btn) => btn.textContent?.includes('取消')
238
+ )
239
+ }
240
+
241
+ describe('UBatchEdit formMode', () => {
242
+ it('默认(不传 formMode)仍渲染右侧面板,不出现弹框', async () => {
243
+ const { host, unmount } = mountBatchEdit()
244
+
245
+ await clickRow(host, 0)
246
+ const aside = host.querySelector<HTMLElement>('.u-batch-edit__form')
247
+ expect(aside).toBeTruthy()
248
+ expect(aside!.style.display).not.toBe('none')
249
+ expect(queryDialog()).toBeNull()
250
+
251
+ unmount()
252
+ })
253
+
254
+ it('formMode="dialog" 时不渲染右列,触发编辑后以弹框打开表单', async () => {
255
+ const { host, model, unmount } = mountBatchEdit({
256
+ props: { formMode: 'dialog', quickEdit: false }
257
+ })
258
+
259
+ expect(host.querySelector('.u-batch-edit__form')).toBeNull()
260
+ expect(queryDialog()).toBeNull()
261
+
262
+ await clickRow(host, 0)
263
+ await flushDialog()
264
+
265
+ const dialog = queryDialog()
266
+ expect(dialog).toBeTruthy()
267
+ // 表格占满整行,右列不再渲染
268
+ expect(host.querySelector('.u-batch-edit__form')).toBeNull()
269
+ // 弹框内复用原表单头部 / 表单体 / 底部操作区
270
+ expect(dialog!.querySelector('.u-batch-edit__form-header')).toBeTruthy()
271
+ expect(dialog!.querySelector('.u-batch-edit__form-body')).toBeTruthy()
272
+ expect(dialog!.querySelector('.u-batch-edit__form-actions')).toBeTruthy()
273
+ // 编辑行已回显
274
+ expect(model.label).toBe('存草稿')
275
+
276
+ unmount()
277
+ })
278
+
279
+ it('弹框内保存走校验与 saveMethod,成功后关闭弹框', async () => {
280
+ const saveMethod = vi.fn()
281
+ const { host, data, unmount } = mountBatchEdit({
282
+ props: { formMode: 'dialog', quickEdit: false, saveMethod },
283
+ labelRules: { required: true }
284
+ })
285
+
286
+ // 打开新增弹框
287
+ clickEl(host.querySelector<HTMLElement>('.u-batch-edit__add-btn')!)
288
+ await flushDialog()
289
+ expect(queryDialog()).toBeTruthy()
290
+
291
+ // 必填校验失败:不调用 saveMethod,弹框保持打开
292
+ clickEl(querySaveButton(queryDialog()!)!)
293
+ await flushDialog()
294
+ expect(saveMethod).not.toHaveBeenCalled()
295
+ expect(queryDialog()).toBeTruthy()
296
+
297
+ // 填写后保存:走 saveMethod 并插入数据,成功后关闭弹框
298
+ setInputValue(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!, '新名称')
299
+ await nextTick()
300
+ await nextTick()
301
+ clickEl(querySaveButton(queryDialog()!)!)
302
+ await flushDialog()
303
+
304
+ expect(saveMethod).toHaveBeenCalledTimes(1)
305
+ expect(saveMethod.mock.calls[0]![0]).toMatchObject({ label: '新名称' })
306
+ expect(saveMethod.mock.calls[0]![1]).toBe('create')
307
+ expect(data.value).toHaveLength(3)
308
+ expect(data.value[2]).toMatchObject({ label: '新名称' })
309
+ expect(queryDialog()).toBeNull()
310
+
311
+ unmount()
312
+ })
313
+
314
+ it('弹框内取消 / 关闭按钮 / 遮罩点击均不保存,且 model 恢复初始值', async () => {
315
+ const saveMethod = vi.fn()
316
+ const { host, data, model, unmount } = mountBatchEdit({
317
+ props: { formMode: 'dialog', quickEdit: false, saveMethod }
318
+ })
319
+
320
+ // 编辑第 1 行后取消
321
+ await clickRow(host, 0)
322
+ await flushDialog()
323
+ setInputValue(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!, '改过的名称')
324
+ await nextTick()
325
+ await nextTick()
326
+ clickEl(queryCancelButton(queryDialog()!)!)
327
+ await flushDialog()
328
+
329
+ expect(saveMethod).not.toHaveBeenCalled()
330
+ expect(data.value[0]).toMatchObject({ label: '存草稿' })
331
+ // model 恢复初始快照,而不是残留上一行数据
332
+ expect(model.label).toBe('')
333
+ expect(model.submitType).toBeUndefined()
334
+ expect(queryDialog()).toBeNull()
335
+
336
+ // 取消后再新增:表单应为初始值(快照不被上一行污染)
337
+ clickEl(host.querySelector<HTMLElement>('.u-batch-edit__add-btn')!)
338
+ await flushDialog()
339
+ expect(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!.value).toBe('')
340
+
341
+ // 表单头部的关闭按钮同样不保存
342
+ setInputValue(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!, '随便写')
343
+ await nextTick()
344
+ await nextTick()
345
+ clickEl(queryDialog()!.querySelector<HTMLElement>('.u-batch-edit__form-close')!)
346
+ await flushDialog()
347
+ expect(saveMethod).not.toHaveBeenCalled()
348
+ expect(model.label).toBe('')
349
+ expect(data.value).toHaveLength(2)
350
+ expect(queryDialog()).toBeNull()
351
+
352
+ // 弹框自身关闭交互(点击遮罩)也不保存
353
+ await clickRow(host, 1)
354
+ await flushDialog()
355
+ setInputValue(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!, '改过的名称')
356
+ await nextTick()
357
+ await nextTick()
358
+ document
359
+ .querySelector<HTMLElement>('.u-dialog__overlay')!
360
+ .dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }))
361
+ await flushDialog()
362
+
363
+ expect(saveMethod).not.toHaveBeenCalled()
364
+ expect(data.value[1]).toMatchObject({ label: '调接口' })
365
+ expect(model.label).toBe('')
366
+ expect(queryDialog()).toBeNull()
367
+
368
+ unmount()
369
+ })
370
+ })
371
+
372
+ describe('UBatchEdit 属性透传', () => {
373
+ it('style / class 等非 props 属性透传到根布局元素(组件为片段根,需手动继承)', async () => {
374
+ const { host, unmount } = mountBatchEdit({
375
+ props: { style: 'height: 500px', class: 'custom-batch-edit' }
376
+ })
377
+ await nextTick()
378
+
379
+ const root = host.querySelector<HTMLElement>('.u-batch-edit')
380
+ expect(root).toBeTruthy()
381
+ expect(root!.classList.contains('custom-batch-edit')).toBe(true)
382
+ expect(root!.style.height).toBe('500px')
383
+
384
+ unmount()
385
+ })
386
+ })
@@ -1,6 +1,11 @@
1
1
  <template>
2
- <!-- 表单常驻挂载(快照只在挂载时对传入的 model 拍一次),仅用 v-show 控制显隐 -->
3
- <aside :class="cls.e('form')" v-if="!!props.model" v-show="state.formVisible">
2
+ <!-- 面板模式:表单常驻挂载(快照只在挂载时对传入的 model 拍一次),仅用 v-show 控制显隐;
3
+ 弹框模式:表单随弹框挂载 / 卸载,model 初始快照由 use-edit-state 维护 -->
4
+ <aside
5
+ :class="[cls.e('form'), bem.is('dialog', props.formMode === 'dialog')]"
6
+ v-if="!!props.model"
7
+ v-show="state.formVisible"
8
+ >
4
9
  <header :class="cls.e('form-header')">
5
10
  <span :class="[cls.e('form-icon'), bem.is(state.formActionType)]">
6
11
  <u-icon>
@@ -48,7 +53,7 @@
48
53
  <span :class="cls.e('form-hint')">
49
54
  <template v-if="props.readonly"> 只读模式 </template>
50
55
 
51
- <span :class="{ [bem.is('concealed')]: !focused }">
56
+ <span :class="{ [bem.is('concealed')]: !shortcutActive }">
52
57
  <template v-if="!(state.formActionType === 'update' && props.quickEdit)">
53
58
  <u-kbd>Ctrl + S</u-kbd> 保存 ·
54
59
  </template>
@@ -86,6 +91,7 @@ import { UKbd } from '../kbd'
86
91
  import { UScroll } from '../scroll'
87
92
  import { BatchEditDIKey } from './di'
88
93
  import { FORM_ACTION_HEADER_MAP } from './form-action-header'
94
+ import { useDialogEffect } from './use-dialog-effect'
89
95
 
90
96
  defineOptions({ name: 'UBatchEditForm' })
91
97
 
@@ -96,6 +102,14 @@ const { cls, props, state, handleClose, handleSave, staticFeatures, dynamicFeatu
96
102
 
97
103
  const focused = toRef(batchEditCtx, 'focused')
98
104
 
105
+ // 弹框模式下,弹框自身关闭(遮罩点击 / 右上角关闭)时同步走 handleClose,不保存
106
+ useDialogEffect({ closeForm: handleClose })
107
+
108
+ /** 快捷键提示是否可见:面板模式随聚焦显隐;弹框模式弹框打开期间快捷键始终可用 */
109
+ const shortcutActive = computed(() => {
110
+ return props.formMode === 'dialog' ? state.formVisible : focused.value
111
+ })
112
+
99
113
  const formComponentRef = shallowRef<FormExposed>()
100
114
 
101
115
  defineExpose<_FormExposed>({
@@ -98,7 +98,8 @@ const tableProps = computed(() => {
98
98
  'saveMethod',
99
99
  'features',
100
100
  'mode',
101
- 'beforeCreate'
101
+ 'beforeCreate',
102
+ 'formMode'
102
103
  ])
103
104
  })
104
105
 
@@ -9,15 +9,29 @@
9
9
  @focusin="handleFocusIn"
10
10
  @focusout="handleFocusOut"
11
11
  @keydown.capture="handleKeydown"
12
+ v-bind="$attrs"
12
13
  >
13
14
  <!-- 编辑列表 -->
14
15
  <BatchEditList :slots="slots" />
15
16
 
16
17
  <!-- 编辑表单 -->
17
- <BatchEditForm ref="form" v-slot="scoped">
18
+ <BatchEditForm v-if="props.formMode === 'panel'" ref="form" v-slot="scoped">
18
19
  <slot name="form" v-bind="scoped" />
19
20
  </BatchEditForm>
20
21
  </u-layout>
22
+
23
+ <!-- 弹框模式:表单经 UDialog 呈现(teleport 到 body),快捷键监听落在弹框上,Esc 与弹框自带的 keyup.esc 去重 -->
24
+ <u-dialog
25
+ v-if="props.formMode === 'dialog'"
26
+ v-model="state.formVisible"
27
+ :class="cls.e('form-dialog')"
28
+ @keydown.capture="handleKeydown"
29
+ @keyup.esc.stop
30
+ >
31
+ <BatchEditForm ref="form" v-slot="scoped">
32
+ <slot name="form" v-bind="scoped" />
33
+ </BatchEditForm>
34
+ </u-dialog>
21
35
  </template>
22
36
 
23
37
  <script lang="ts" setup>
@@ -25,6 +39,7 @@ import { bem } from '@veltra/utils'
25
39
  import { computed, provide, useTemplateRef } from 'vue'
26
40
 
27
41
  import type { BatchEditEmits, BatchEditProps, BatchEditSlots, FormExposed } from '../../types'
42
+ import { UDialog } from '../dialog'
28
43
  import { ULayout } from '../layout'
29
44
  import BatchEditForm from './batch-edit-form.vue'
30
45
  import BatchEditList from './batch-edit-list.vue'
@@ -34,9 +49,12 @@ import { useFeatures } from './use-features'
34
49
  import { useHandlers } from './use-handlers'
35
50
  import { useShortcutKey } from './use-shortcut-key'
36
51
 
37
- defineOptions({ name: 'UBatchEdit' })
52
+ defineOptions({ name: 'UBatchEdit', inheritAttrs: false })
38
53
 
39
- const props = withDefaults(defineProps<BatchEditProps>(), { cols: () => ['1fr', '420px'] })
54
+ const props = withDefaults(defineProps<BatchEditProps>(), {
55
+ cols: () => ['1fr', '420px'],
56
+ formMode: 'panel'
57
+ })
40
58
 
41
59
  const emit = defineEmits<BatchEditEmits>()
42
60
 
@@ -61,7 +79,7 @@ const { handleFocusIn, handleFocusOut, handleKeydown, focused } = useShortcutKey
61
79
  })
62
80
 
63
81
  const cols = computed(() => {
64
- return state.formVisible ? props.cols : undefined
82
+ return props.formMode === 'panel' && state.formVisible ? props.cols : undefined
65
83
  })
66
84
 
67
85
  provide(BatchEditDIKey, {
@@ -52,6 +52,14 @@ $root-name: batch-edit;
52
52
  overflow: hidden;
53
53
  isolation: isolate;
54
54
  min-height: 0;
55
+
56
+ // 弹框模式:背景 / 圆角 / 阴影由弹框外壳提供,表单只保留内部布局并限高滚动
57
+ @include m.is(dialog) {
58
+ max-height: min(600px, 80vh);
59
+ background-color: transparent;
60
+ border-radius: 0;
61
+ box-shadow: none;
62
+ }
55
63
  }
56
64
 
57
65
  @include m.e(form-header) {
@@ -171,3 +179,15 @@ $root-name: batch-edit;
171
179
  flex-shrink: 0;
172
180
  }
173
181
  }
182
+
183
+ // ---------- 弹框模式 ----------
184
+ // 弹框 teleport 到 body,不在 .u-batch-edit 内,样式置于顶层
185
+ #{fn.bem(batch-edit, form-dialog)} {
186
+ width: 560px;
187
+ max-width: calc(100vw - 32px);
188
+
189
+ // 表单自带头部(图标 / 标题 / 关闭按钮),弹框头部不再展示
190
+ #{fn.bem(dialog, header)} {
191
+ display: none;
192
+ }
193
+ }
@@ -1,5 +1,6 @@
1
1
  import '../action/style'
2
2
  import '../button/style'
3
+ import '../dialog/style'
3
4
  import '../form/style'
4
5
  import '../icon/style'
5
6
  import '../layout/style'
@@ -10,12 +10,18 @@ interface Options {
10
10
  /**
11
11
  * 在表单上的副作用。
12
12
  * 包括:关闭表单
13
+ *
14
+ * sync 触发:弹框关闭会随即卸载表单组件,默认 pre 时序下 watcher 可能随组件卸载被丢弃
13
15
  */
14
16
  export function useDialogEffect(options: Options) {
15
17
  const dialogCtx = inject(DialogDIKey, undefined)
16
18
  if (!dialogCtx) return
17
19
 
18
- watch(dialogCtx.visible, (visible) => {
19
- !visible && options.closeForm?.()
20
- })
20
+ watch(
21
+ dialogCtx.visible,
22
+ (visible) => {
23
+ !visible && options.closeForm?.()
24
+ },
25
+ { flush: 'sync' }
26
+ )
21
27
  }
@@ -1,4 +1,4 @@
1
- import { o } from '@cat-kit/core'
1
+ import { copy, o } from '@cat-kit/core'
2
2
  import { nextTick, shallowReactive, shallowRef, watch, type ShallowRef } from 'vue'
3
3
 
4
4
  import type { BatchEditProps, BatchEditStates, FormExposed } from '../../types'
@@ -24,9 +24,37 @@ export function useEditState(options: Options) {
24
24
  /** 是否正在以编程方式重置/回显表单,此期间禁止 quick-edit 回写行数据 */
25
25
  const syncing = shallowRef(false)
26
26
 
27
+ /**
28
+ * model 初始快照。
29
+ * 面板模式由常驻挂载的 UForm 自行维护快照;dialog 模式表单随弹框卸载,
30
+ * UForm 快照会在每次打开时按已回显的 model 重拍,故这里单独保留一份供恢复
31
+ */
32
+ const initialModel = shallowRef<Record<string, any>>()
33
+
34
+ watch(
35
+ () => props.model,
36
+ (model) => {
37
+ initialModel.value = props.formMode === 'dialog' && model ? copy(model) : undefined
38
+ },
39
+ { immediate: true }
40
+ )
41
+
42
+ /** 重置表单数据到 model 初始快照 */
43
+ function resetModel() {
44
+ if (props.formMode !== 'dialog') {
45
+ formRef.value?.reset()
46
+ return
47
+ }
48
+
49
+ formRef.value?.clearValidate()
50
+ if (!props.model || !initialModel.value) return
51
+ for (const key of Object.keys(initialModel.value)) {
52
+ o(props.model).set(key, copy(initialModel.value[key]))
53
+ }
54
+ }
55
+
27
56
  function resetState() {
28
- // 表单常驻挂载,重置即恢复到 model 初始快照
29
- formRef.value?.reset()
57
+ resetModel()
30
58
 
31
59
  if (state.row) {
32
60
  state.row.isCurrent = false
@@ -47,7 +75,7 @@ export function useEditState(options: Options) {
47
75
  // 先重置回 model 初始快照,再同步回显行数据。
48
76
  // 同一 tick 内的连续写入会被表单字段 watcher 合并,
49
77
  // 重置产生的默认值不会触发 field:update 回写行数据
50
- formRef.value?.reset()
78
+ resetModel()
51
79
 
52
80
  if (row) {
53
81
  if (props.model) {
@@ -170,6 +170,12 @@ export function useHandlers(options: Options): EditReturned {
170
170
  }
171
171
  insert(item)
172
172
 
173
+ // 弹框模式保存成功即关闭,不走面板的连续新增
174
+ if (props.formMode === 'dialog') {
175
+ resetState()
176
+ return
177
+ }
178
+
173
179
  // 推进插入点,便于连续新增;并重置表单到初始默认值
174
180
  const next = last(state.indexPath) + 1
175
181
  state.indexPath = [...state.indexPath.slice(0, -1), next]
@@ -190,6 +196,10 @@ export function useHandlers(options: Options): EditReturned {
190
196
  Object.keys(item).forEach((key) => {
191
197
  o(row.data).set(key, o(item).get(key))
192
198
  })
199
+
200
+ if (props.formMode === 'dialog') {
201
+ resetState()
202
+ }
193
203
  })
194
204
 
195
205
  const handleDelete = runWithLoading(async (row: TableRow) => {
@@ -38,12 +38,16 @@ export function useShortcutKey(options: Options) {
38
38
  }
39
39
 
40
40
  /**
41
- * 键盘快捷键(仅组件获焦时生效):
41
+ * 键盘快捷键:
42
42
  * - Esc 关闭表单
43
43
  * - ⌘/Ctrl + S 保存(快速编辑模式下编辑行时除外)
44
+ *
45
+ * 面板模式仅组件获焦时生效;弹框模式表单被 teleport 到 body,
46
+ * 组件聚焦状态覆盖不到,改为弹框打开期间生效
44
47
  */
45
48
  function handleKeydown(e: KeyboardEvent) {
46
- if (!focused.value) return
49
+ const active = props.formMode === 'dialog' ? state.formVisible : focused.value
50
+ if (!active) return
47
51
  if (props.readonly && e.key !== 'Escape') return
48
52
 
49
53
  if (e.key === 'Escape') return runEscape(e)