@veltra/desktop 1.0.13 → 1.0.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 (166) hide show
  1. package/dist/components/action/action-group.js +110 -3
  2. package/dist/components/action/action-group.js.map +1 -1
  3. package/dist/components/action/action-group.vue.d.ts +16 -3
  4. package/dist/components/action/action.js +26 -13
  5. package/dist/components/action/action.js.map +1 -1
  6. package/dist/components/action/style.js +2 -1
  7. package/dist/components/action/style2.css +35 -7
  8. package/dist/components/batch-edit/batch-edit-form.js +106 -42
  9. package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
  10. package/dist/components/batch-edit/batch-edit-list.js +84 -47
  11. package/dist/components/batch-edit/batch-edit-list.js.map +1 -1
  12. package/dist/components/batch-edit/batch-edit.js +42 -10
  13. package/dist/components/batch-edit/batch-edit.js.map +1 -1
  14. package/dist/components/batch-edit/style.js +2 -4
  15. package/dist/components/batch-edit/style2.css +202 -30
  16. package/dist/components/batch-edit/use-edit.js +45 -9
  17. package/dist/components/batch-edit/use-edit.js.map +1 -1
  18. package/dist/components/cascade/cascade.js +3 -1
  19. package/dist/components/cascade/cascade.js.map +1 -1
  20. package/dist/components/code-editor/code-editor.js +145 -49
  21. package/dist/components/code-editor/code-editor.js.map +1 -1
  22. package/dist/components/code-editor/code-editor.vue.d.ts +2 -0
  23. package/dist/components/code-editor/style2.css +87 -19
  24. package/dist/components/dialog/dialog.js +6 -2
  25. package/dist/components/dialog/dialog.js.map +1 -1
  26. package/dist/components/dialog/style2.css +2 -3
  27. package/dist/components/expression-editor/components/path-preview.js +27 -0
  28. package/dist/components/expression-editor/components/path-preview.js.map +1 -0
  29. package/dist/components/expression-editor/components/variable-picker.js +200 -153
  30. package/dist/components/expression-editor/components/variable-picker.js.map +1 -1
  31. package/dist/components/expression-editor/core/chip.js +54 -0
  32. package/dist/components/expression-editor/core/chip.js.map +1 -0
  33. package/dist/components/expression-editor/core/editor.js +425 -0
  34. package/dist/components/expression-editor/core/editor.js.map +1 -0
  35. package/dist/components/expression-editor/core/mention.js +91 -0
  36. package/dist/components/expression-editor/core/mention.js.map +1 -0
  37. package/dist/components/expression-editor/core/model.js +64 -0
  38. package/dist/components/expression-editor/core/model.js.map +1 -0
  39. package/dist/components/expression-editor/di.js +4 -10
  40. package/dist/components/expression-editor/di.js.map +1 -1
  41. package/dist/components/expression-editor/expression-editor.js +168 -114
  42. package/dist/components/expression-editor/expression-editor.js.map +1 -1
  43. package/dist/components/expression-editor/expression-editor.vue.d.ts +2 -1
  44. package/dist/components/expression-editor/style.js +0 -2
  45. package/dist/components/expression-editor/style2.css +145 -218
  46. package/dist/components/index.js +54 -54
  47. package/dist/components/layout/layout.js +12 -11
  48. package/dist/components/layout/layout.js.map +1 -1
  49. package/dist/components/multi-select/multi-select.vue.d.ts +1 -1
  50. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +1 -1
  51. package/dist/components/number-input/number-input.js +4 -4
  52. package/dist/components/number-input/number-input.js.map +1 -1
  53. package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
  54. package/dist/components/rich-text-editor/rich-text-editor.js +1 -1
  55. package/dist/components/rich-text-editor/toolbar.js +2 -2
  56. package/dist/components/steps/steps.vue.d.ts +1 -1
  57. package/dist/components/table/use-table.js +16 -0
  58. package/dist/components/table/use-table.js.map +1 -1
  59. package/dist/components/tabs/tabs-horizontal.vue.d.ts +3 -3
  60. package/dist/components/tabs/tabs-vertical.vue.d.ts +2 -2
  61. package/dist/components/tabs/tabs.vue.d.ts +3 -3
  62. package/dist/components/tip/tip.js +4 -5
  63. package/dist/components/tip/tip.js.map +1 -1
  64. package/dist/components/tip/tip.vue.d.ts +1 -1
  65. package/dist/components/tree/style2.css +18 -12
  66. package/dist/components/tree/tree-node.js +7 -4
  67. package/dist/components/tree/tree-node.js.map +1 -1
  68. package/dist/components/tree-select/tree-select.vue.d.ts +1 -1
  69. package/dist/index.d.ts +3 -3
  70. package/dist/index.js +83 -83
  71. package/dist/style.js +3 -3
  72. package/dist/types/action.d.ts +38 -4
  73. package/dist/types/cascade.d.ts +2 -3
  74. package/dist/types/code-editor.d.ts +9 -2
  75. package/dist/types/expression-editor.d.ts +9 -1
  76. package/dist/types/index.d.ts +2 -2
  77. package/package.json +11 -12
  78. package/src/components/action/action-group.vue +126 -56
  79. package/src/components/action/action.vue +35 -12
  80. package/src/components/action/style.scss +46 -10
  81. package/src/components/action/style.ts +2 -1
  82. package/src/components/batch-edit/batch-edit-form.vue +121 -45
  83. package/src/components/batch-edit/batch-edit-list.vue +56 -39
  84. package/src/components/batch-edit/batch-edit.vue +57 -6
  85. package/src/components/batch-edit/style.scss +251 -37
  86. package/src/components/batch-edit/style.ts +5 -8
  87. package/src/components/batch-edit/use-edit.ts +58 -11
  88. package/src/components/cascade/cascade.vue +3 -1
  89. package/src/components/code-editor/code-editor.vue +198 -63
  90. package/src/components/code-editor/style.scss +110 -12
  91. package/src/components/dialog/dialog.vue +6 -2
  92. package/src/components/dialog/style.scss +4 -4
  93. package/src/components/expression-editor/__test__/chip.test.ts +86 -0
  94. package/src/components/expression-editor/__test__/editor.test.ts +47 -0
  95. package/src/components/expression-editor/__test__/mention.test.ts +122 -0
  96. package/src/components/expression-editor/__test__/model.test.ts +127 -0
  97. package/src/components/expression-editor/components/path-preview.vue +32 -0
  98. package/src/components/expression-editor/components/variable-picker.vue +225 -219
  99. package/src/components/expression-editor/core/chip.ts +101 -0
  100. package/src/components/expression-editor/core/editor.ts +514 -0
  101. package/src/components/expression-editor/core/mention.ts +121 -0
  102. package/src/components/expression-editor/core/model.ts +109 -0
  103. package/src/components/expression-editor/di.ts +4 -32
  104. package/src/components/expression-editor/expression-editor.vue +184 -151
  105. package/src/components/expression-editor/style.scss +148 -219
  106. package/src/components/expression-editor/style.ts +0 -2
  107. package/src/components/layout/layout.vue +17 -19
  108. package/src/components/number-input/number-input.vue +4 -4
  109. package/src/components/table/use-table.ts +16 -0
  110. package/src/components/tip/tip.vue +4 -3
  111. package/src/components/tree/style.scss +30 -33
  112. package/src/components/tree/tree-node.vue +4 -2
  113. package/src/types/action.ts +42 -4
  114. package/src/types/cascade.ts +4 -3
  115. package/src/types/code-editor.ts +9 -2
  116. package/src/types/expression-editor.ts +9 -0
  117. package/dist/components/action/action-group.vue_vue_type_script_setup_true_lang.js +0 -65
  118. package/dist/components/action/action-group.vue_vue_type_script_setup_true_lang.js.map +0 -1
  119. package/dist/components/expression-editor/constants.js +0 -9
  120. package/dist/components/expression-editor/constants.js.map +0 -1
  121. package/dist/components/expression-editor/internal/editor-runtime/create-runtime.js +0 -47
  122. package/dist/components/expression-editor/internal/editor-runtime/create-runtime.js.map +0 -1
  123. package/dist/components/expression-editor/internal/editor-runtime/model-sync.js +0 -49
  124. package/dist/components/expression-editor/internal/editor-runtime/model-sync.js.map +0 -1
  125. package/dist/components/expression-editor/internal/features/commands/register-command-packs.js +0 -215
  126. package/dist/components/expression-editor/internal/features/commands/register-command-packs.js.map +0 -1
  127. package/dist/components/expression-editor/internal/features/drag-drop/drag-drop-service.js +0 -33
  128. package/dist/components/expression-editor/internal/features/drag-drop/drag-drop-service.js.map +0 -1
  129. package/dist/components/expression-editor/internal/features/insertion/insertion-service.js +0 -33
  130. package/dist/components/expression-editor/internal/features/insertion/insertion-service.js.map +0 -1
  131. package/dist/components/expression-editor/nodes/variable-node.js +0 -94
  132. package/dist/components/expression-editor/nodes/variable-node.js.map +0 -1
  133. package/dist/components/expression-editor/parser.js +0 -32
  134. package/dist/components/expression-editor/parser.js.map +0 -1
  135. package/dist/components/expression-editor/plain-text.js +0 -19
  136. package/dist/components/expression-editor/plain-text.js.map +0 -1
  137. package/dist/components/expression-editor/use-context.js +0 -69
  138. package/dist/components/expression-editor/use-context.js.map +0 -1
  139. package/dist/components/expression-editor/use-decorators.js +0 -27
  140. package/dist/components/expression-editor/use-decorators.js.map +0 -1
  141. package/dist/components/expression-editor/use-expression-drag-drop.js +0 -275
  142. package/dist/components/expression-editor/use-expression-drag-drop.js.map +0 -1
  143. package/src/components/batch-edit/batch-edit-new.vue +0 -168
  144. package/src/components/batch-edit/style-new.scss +0 -35
  145. package/src/components/batch-edit/use-edit-new.ts +0 -68
  146. package/src/components/batch-edit/use-tip.ts +0 -47
  147. package/src/components/expression-editor/__test__/architecture-boundaries.test.ts +0 -103
  148. package/src/components/expression-editor/__test__/architecture-compatibility.test.ts +0 -52
  149. package/src/components/expression-editor/__test__/drag-drop.test.ts +0 -171
  150. package/src/components/expression-editor/__test__/parser.test.ts +0 -62
  151. package/src/components/expression-editor/constants.ts +0 -6
  152. package/src/components/expression-editor/internal/contracts/editor-runtime.ts +0 -11
  153. package/src/components/expression-editor/internal/editor-runtime/create-runtime.ts +0 -64
  154. package/src/components/expression-editor/internal/editor-runtime/index.ts +0 -1
  155. package/src/components/expression-editor/internal/editor-runtime/model-sync.ts +0 -68
  156. package/src/components/expression-editor/internal/features/commands/register-command-packs.ts +0 -517
  157. package/src/components/expression-editor/internal/features/drag-drop/drag-drop-service.ts +0 -51
  158. package/src/components/expression-editor/internal/features/insertion/insertion-service.ts +0 -56
  159. package/src/components/expression-editor/nodes/variable-node.tsx +0 -118
  160. package/src/components/expression-editor/parser.ts +0 -46
  161. package/src/components/expression-editor/plain-text.ts +0 -25
  162. package/src/components/expression-editor/plan.md +0 -22
  163. package/src/components/expression-editor/use-context.ts +0 -134
  164. package/src/components/expression-editor/use-decorators.tsx +0 -29
  165. package/src/components/expression-editor/use-editor.ts +0 -12
  166. package/src/components/expression-editor/use-expression-drag-drop.ts +0 -396
@@ -1,73 +1,143 @@
1
1
  <template>
2
- <component v-for="node of getSlotsNodes()" :key="node.key" :is="node" />
2
+ <div :class="rootCls">
3
+ <component v-for="node of normalNodes" :key="node.key" :is="node" />
4
+
5
+ <u-tip
6
+ v-if="dropdownNodes.length"
7
+ :class="cls.e('dropdown')"
8
+ direction="bottom"
9
+ alignment="end"
10
+ trigger="click"
11
+ >
12
+ <template #content>
13
+ <component v-for="node of dropdownNodes" :key="node.key" :is="node" />
14
+ </template>
15
+
16
+ <u-action
17
+ :class="cls.e('more')"
18
+ :type
19
+ :size
20
+ :text
21
+ :circle="hover"
22
+ :icon="hover ? MoreFilled : undefined"
23
+ title="更多"
24
+ >
25
+ <template v-if="!hover">
26
+ 更多
27
+ <u-icon>
28
+ <ArrowDown />
29
+ </u-icon>
30
+ </template>
31
+ </u-action>
32
+ </u-tip>
33
+ </div>
3
34
  </template>
4
35
 
5
- <script lang="tsx" setup>
6
- import type { ActionGroupProps } from "../../types";
7
- import { ArrowDown } from '@veltra/icons/normal'
8
- import { UIcon } from "../icon";
9
- import { UButton } from "../button";
10
- import { provide, type VNode } from "vue";
11
- import { bem, extractNormalVNodes } from "@veltra/utils";
12
- import { UTip } from "../tip";
13
- import { ActionDIKey } from "./di";
36
+ <script lang="ts" setup>
37
+ import { ArrowDown, MoreFilled } from '@veltra/icons/normal'
38
+ import { bem, extractNormalVNodes } from '@veltra/utils'
39
+ import type { ColorType } from '@veltra/utils'
40
+ import { cloneVNode, computed, provide, type VNode } from 'vue'
41
+
42
+ import type { ActionGroupProps } from '../../types'
43
+ import { UIcon } from '../icon'
44
+ import { UTip } from '../tip'
45
+ import UAction from './action.vue'
46
+ import { ActionDIKey } from './di'
14
47
 
15
48
  defineOptions({
16
- name: "ActionGroup",
17
- inheritAttrs: false,
18
- });
49
+ name: 'ActionGroup',
50
+ inheritAttrs: false
51
+ })
52
+
53
+ // 此处不直接复用 `ActionGroupProps` 类型作为 `defineProps` 泛型参数,
54
+ // 是为了规避 Vue SFC 编译器在跨文件类型解析时偶发的 prop 未注册问题
55
+ // (表现为运行时 `[Vue warn]: Property "x" was accessed during render but is not defined on instance`)。
56
+ // 通过本地接口定义 + 类型相等性约束保证与外部类型一致。
57
+ interface _Props {
58
+ loading?: boolean
59
+ circle?: boolean
60
+ max?: number
61
+ hover?: boolean
62
+ size?: 'small' | 'default' | 'large'
63
+ text?: boolean
64
+ type?: ColorType
65
+ }
19
66
 
20
- const props = withDefaults(defineProps<ActionGroupProps>(), {
67
+ type _AssertEqual = [ActionGroupProps] extends [_Props]
68
+ ? [_Props] extends [ActionGroupProps]
69
+ ? true
70
+ : never
71
+ : never
72
+
73
+ const _assert: _AssertEqual = true
74
+ void _assert
75
+
76
+ const props = withDefaults(defineProps<_Props>(), {
21
77
  max: 3,
22
78
  loading: false,
23
79
  circle: false,
24
- });
80
+ hover: false,
81
+ size: 'small',
82
+ text: true,
83
+ type: 'primary'
84
+ })
25
85
 
26
- const cls = bem("action-group");
27
- const actionCls = bem("action");
86
+ const cls = bem('action-group')
28
87
 
29
88
  const slots = defineSlots<{
30
- default?: () => VNode[];
31
- }>();
32
-
33
- function getSlotsNodes() {
34
- const nodes = slots.default?.();
35
-
36
- if (!nodes) return [];
37
- const extractedNodes = extractNormalVNodes(nodes).filter(
38
- // @ts-ignore
39
- (node) => node.type?.name === "Action",
40
- );
41
-
42
- let normalNodes: VNode[] = [];
43
- let hiddenNodes: VNode[] = [];
44
- if (extractedNodes.length === props.max) {
45
- normalNodes = extractedNodes;
46
- } else {
47
- normalNodes = extractedNodes.slice(0, props.max - 1);
48
- hiddenNodes = extractedNodes.slice(props.max - 1);
49
- }
89
+ default?: () => VNode[]
90
+ }>()
50
91
 
51
- const dropdown = hiddenNodes.length ? (
52
- <UTip direction="bottom" class={cls.e("dropdown")}>
53
- {{
54
- content: () => hiddenNodes,
55
- default: () => (
56
- <UButton text size="small" type="primary" class={actionCls.b}>
57
- 更多
58
- <UIcon>
59
- <ArrowDown />
60
- </UIcon>
61
- </UButton>
62
- ),
63
- }}
64
- </UTip>
65
- ) : null;
66
-
67
- return dropdown ? [...normalNodes, dropdown] : normalNodes;
92
+ const rootCls = computed(() => [cls.b, bem.is('hover', props.hover)])
93
+
94
+ function isInDropdownNode(node: VNode): boolean {
95
+ const p = node.props as Record<string, unknown> | null
96
+ if (!p) return false
97
+ const v = p.inDropdown ?? p['in-dropdown']
98
+ return v === true || v === '' || v === 'true'
68
99
  }
69
100
 
101
+ const partitioned = computed(() => {
102
+ const nodes = slots.default?.()
103
+
104
+ if (!nodes) return { visible: [] as VNode[], hidden: [] as VNode[] }
105
+
106
+ const extracted = extractNormalVNodes(nodes).filter(
107
+ // 仅保留 UAction 子节点
108
+ (node) => (node.type as { name?: string } | null | undefined)?.name === 'Action'
109
+ )
110
+
111
+ const visible: VNode[] = []
112
+ const fixedHidden: VNode[] = []
113
+
114
+ extracted.forEach((node) => {
115
+ if (isInDropdownNode(node)) {
116
+ fixedHidden.push(node)
117
+ } else {
118
+ visible.push(node)
119
+ }
120
+ })
121
+
122
+ const willOverflow = visible.length > props.max
123
+ const overflow = willOverflow ? visible.splice(props.max - 1) : []
124
+
125
+ return {
126
+ visible,
127
+ hidden: [...overflow, ...fixedHidden]
128
+ }
129
+ })
130
+
131
+ const normalNodes = computed(() => partitioned.value.visible)
132
+
133
+ // 下拉菜单中的 UAction 强制以非圆形、文本模式呈现,避免视觉与其他菜单项不一致
134
+ const dropdownNodes = computed(() => {
135
+ return partitioned.value.hidden.map((node) =>
136
+ cloneVNode(node, { inDropdown: true, circle: false })
137
+ )
138
+ })
139
+
70
140
  provide(ActionDIKey, {
71
- groupProps: props,
72
- });
141
+ groupProps: props
142
+ })
73
143
  </script>
@@ -47,32 +47,55 @@ defineOptions({
47
47
  inheritAttrs: false
48
48
  })
49
49
 
50
+ // 这里所有可继承自 `<u-action-group>` 的默认值都强制为 `undefined`,
51
+ // 否则 Vue 对 Boolean 类型 prop 的默认值(false)会让 `??` 兜底始终失效
50
52
  const props = withDefaults(defineProps<ActionProps>(), {
51
- size: 'small',
52
- text: true,
53
- type: 'primary',
54
53
  inDropdown: false,
55
54
  loading: undefined,
56
- circle: undefined
55
+ circle: undefined,
56
+ text: undefined,
57
+ size: undefined,
58
+ type: undefined
57
59
  })
58
60
 
59
- const buttonProps = computed(() => {
60
- return o(props as Record<string, any>).omit(['needConfirm', 'loading', 'circle', 'propagate'])
61
- })
62
-
63
- const emit = defineEmits<ActionEmits>()
64
-
65
- const cls = bem('action')
66
-
67
61
  const ctx = inject(ActionDIKey, undefined)
68
62
 
63
+ const size = computed(() => props.size ?? ctx?.groupProps.size ?? 'small')
64
+ const text = computed(() => props.text ?? ctx?.groupProps.text ?? true)
65
+ const type = computed(() => props.type ?? ctx?.groupProps.type ?? 'primary')
66
+
69
67
  const loading = computed(() => {
70
68
  return props.loading ?? ctx?.groupProps.loading
71
69
  })
72
70
 
71
+ // 下拉菜单中的操作项强制非圆形以呈现完整文本,否则跟随用户/分组配置
73
72
  const circle = computed(() => {
73
+ if (props.inDropdown) return false
74
74
  return props.circle ?? ctx?.groupProps.circle
75
75
  })
76
+
77
+ const buttonProps = computed(() => {
78
+ return {
79
+ ...o(props as Record<string, any>).omit([
80
+ 'needConfirm',
81
+ 'loading',
82
+ 'circle',
83
+ 'propagate',
84
+ 'size',
85
+ 'text',
86
+ 'type',
87
+ 'inDropdown'
88
+ ]),
89
+ size: size.value,
90
+ text: text.value,
91
+ type: type.value
92
+ }
93
+ })
94
+
95
+ const emit = defineEmits<ActionEmits>()
96
+
97
+ const cls = bem('action')
98
+
76
99
  function handleConfirm() {
77
100
  emit('run')
78
101
  }
@@ -1,27 +1,63 @@
1
1
  @use 'pkg:@veltra/styles/mixins' as m;
2
2
  @use 'pkg:@veltra/styles/functions' as fn;
3
- @use 'pkg:@veltra/styles/vars';
4
3
 
5
- // 方便拼接
6
4
  $root-name: action;
7
5
  $group-name: action-group;
8
6
 
9
7
  @include m.b($group-name) {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ vertical-align: middle;
11
+ gap: 6px;
12
+
13
+ // 下拉触发按钮的占位类(与子项一致的间距处理)
14
+ @include m.e(more) {
15
+ flex-shrink: 0;
16
+ }
17
+
18
+ // 紧凑悬浮模式:默认半透明,仅在悬浮 / 当前激活态显现
19
+ // 适用于行内(表格行、列表项)操作栏,搭配 .u-action-group 子项达到一致的圆形按钮观感
20
+ @include m.is(hover) {
21
+ gap: 2px;
22
+ padding: 2px 4px;
23
+ border-radius: 999px;
24
+ opacity: 0.45;
25
+ transition:
26
+ opacity 0.18s ease,
27
+ background-color 0.18s ease,
28
+ box-shadow 0.18s ease;
29
+
30
+ &:hover,
31
+ &:focus-within {
32
+ opacity: 1;
33
+ background-color: color-mix(in srgb, #{fn.use-var(bg-color, top)} 70%, transparent);
34
+ backdrop-filter: blur(8px);
35
+ -webkit-backdrop-filter: blur(8px);
36
+ box-shadow: 0 1px 4px color-mix(in srgb, #{fn.use-var(shadow, color)} 35%, transparent);
37
+ }
38
+ }
39
+
40
+ // 下拉菜单内容容器:模拟菜单项视觉
10
41
  @include m.e(dropdown) {
11
- padding: 6px;
12
- min-width: 100px;
42
+ padding: 4px;
43
+ min-width: 140px;
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: 2px;
13
47
 
14
48
  @include m.b(button) {
15
49
  width: 100%;
16
- display: block;
17
- margin-left: 0 !important;
18
- margin-bottom: 6px;
50
+ justify-content: flex-start;
51
+ padding: 0 12px;
52
+
53
+ // 下拉项不再使用同级间距,恢复块级布局
54
+ & + & {
55
+ margin-left: 0;
56
+ }
19
57
  }
20
58
  }
21
59
  }
22
60
 
23
61
  @include m.b($root-name) {
24
- & + & {
25
- margin-left: 6px;
26
- }
62
+ flex-shrink: 0;
27
63
  }
@@ -1,4 +1,5 @@
1
1
  import '../button/style'
2
- import '../dropdown/style'
2
+ import '../icon/style'
3
3
  import '../pop-confirm/style'
4
+ import '../tip/style'
4
5
  import './style.scss'
@@ -1,58 +1,87 @@
1
1
  <template>
2
- <u-card :class="cls.e('form')" v-if="state.visible && !!props.model">
3
- <u-card-header>
4
- <template v-if="props.readonly">详情</template>
5
- <template v-else-if="!!state.parentRow">新增子级</template>
6
- <template v-else-if="state.type === 'create'">新增</template>
7
- <template v-else-if="state.type === 'update'">编辑</template>
8
- </u-card-header>
2
+ <transition name="batch-edit-panel" appear>
3
+ <aside :class="cls.e('form')" v-if="state.visible && !!props.model">
4
+ <header :class="cls.e('form-header')">
5
+ <span :class="[cls.e('form-icon'), bem.is(headerInfo.tone)]">
6
+ <u-icon>
7
+ <component :is="headerInfo.icon" />
8
+ </u-icon>
9
+ </span>
10
+ <div :class="cls.e('form-title')">
11
+ <h3 :class="cls.e('form-title-text')">{{ headerInfo.title }}</h3>
12
+ <span v-if="headerInfo.chip" :class="cls.e('form-chip')">
13
+ {{ headerInfo.chip }}
14
+ </span>
15
+ </div>
16
+ <u-button
17
+ :class="cls.e('form-close')"
18
+ :icon="Close"
19
+ text
20
+ circle
21
+ size="small"
22
+ title="关闭"
23
+ @click="handleClose"
24
+ />
25
+ </header>
9
26
 
10
- <transition name="fade" appear mode="out-in">
11
- <u-scroll always :class="cls.e('form-wrap')">
12
- <u-form
13
- :model="props.model"
14
- :readonly="props.readonly"
15
- @keyup.enter="handleSave"
16
- :label-width="props.labelWidth"
17
- >
18
- <template #default="{ data, model }">
19
- <slot
20
- v-bind="{
21
- data,
22
- model,
23
- row: state.row,
24
- depth: state.depth,
25
- indexes: insertIndexes,
26
- index: state.row?.index
27
- }"
28
- />
29
- </template>
30
- </u-form>
31
- </u-scroll>
32
- </transition>
27
+ <transition name="fade" appear mode="out-in">
28
+ <u-scroll always :class="cls.e('form-body')" :key="bodyKey">
29
+ <u-form
30
+ :model="props.model"
31
+ :readonly="props.readonly"
32
+ @keyup.enter="handleSave"
33
+ :label-width="props.labelWidth"
34
+ >
35
+ <template #default="{ data, model }">
36
+ <slot
37
+ v-bind="{
38
+ data,
39
+ model,
40
+ row: state.row,
41
+ depth: state.depth,
42
+ indexes: insertIndexes,
43
+ index: state.row?.index
44
+ }"
45
+ />
46
+ </template>
47
+ </u-form>
48
+ </u-scroll>
49
+ </transition>
33
50
 
34
- <u-card-action :class="cls.e('action')">
35
- <u-button text type="primary" :loading="state.loading" @click="handleClose"> 关闭 </u-button>
51
+ <footer :class="cls.e('form-footer')">
52
+ <span :class="cls.e('form-hint')" v-if="!props.readonly && state.dataUpdated">
53
+ 有未保存改动
54
+ </span>
55
+ <span :class="cls.e('form-hint')" v-else-if="!props.readonly">
56
+ <kbd>Enter</kbd> 保存 · <kbd>Esc</kbd> 关闭
57
+ </span>
58
+ <span :class="cls.e('form-hint')" v-else>只读模式</span>
36
59
 
37
- <u-button
38
- v-if="!props.readonly && state.dataUpdated && (creatable || updatable)"
39
- :type="state.type === 'create' ? 'success' : 'primary'"
40
- :loading="state.loading"
41
- @click="handleSave"
42
- plain
43
- >
44
- 确认{{ state.type === 'create' ? '新增' : '修改' }}
45
- </u-button>
46
- </u-card-action>
47
- </u-card>
60
+ <div :class="cls.e('form-actions')">
61
+ <u-button text :loading="state.loading" @click="handleClose"> 取消 </u-button>
62
+ <u-button
63
+ v-if="!props.readonly && (creatable || updatable)"
64
+ :type="state.type === 'create' ? 'success' : 'primary'"
65
+ :loading="state.loading"
66
+ :disabled="!state.dataUpdated"
67
+ @click="handleSave"
68
+ >
69
+ {{ state.type === 'create' ? '保存新增' : '保存修改' }}
70
+ </u-button>
71
+ </div>
72
+ </footer>
73
+ </aside>
74
+ </transition>
48
75
  </template>
49
76
 
50
77
  <script lang="ts" setup>
51
- import { computed, inject } from 'vue'
78
+ import { AddChild, Close, EditPen, Plus, View } from '@veltra/icons/normal'
79
+ import { bem } from '@veltra/utils'
80
+ import { computed, inject, type Component } from 'vue'
52
81
 
53
82
  import { UButton } from '../button'
54
- import { UCard, UCardAction, UCardHeader } from '../card'
55
83
  import { UForm } from '../form'
84
+ import { UIcon } from '../icon'
56
85
  import { UScroll } from '../scroll'
57
86
  import { BatchEditDIKey } from './di'
58
87
 
@@ -82,4 +111,51 @@ const updatable = computed(() => {
82
111
  (staticFeatures.value.has('update') || dynamicFeatures.value.update?.(state.row))
83
112
  )
84
113
  })
114
+
115
+ interface HeaderInfo {
116
+ icon: Component
117
+ title: string
118
+ chip?: string
119
+ tone: 'create' | 'update' | 'view' | 'child'
120
+ }
121
+
122
+ const headerInfo = computed<HeaderInfo>(() => {
123
+ if (props.readonly) {
124
+ return {
125
+ icon: View,
126
+ title: '查看详情',
127
+ chip: state.row ? `第 ${state.row.index + 1} 行` : undefined,
128
+ tone: 'view'
129
+ }
130
+ }
131
+ if (state.parentRow) {
132
+ return {
133
+ icon: AddChild,
134
+ title: '新增子级',
135
+ chip: `归属于第 ${state.parentRow.index + 1} 行`,
136
+ tone: 'child'
137
+ }
138
+ }
139
+ if (state.type === 'create') {
140
+ return {
141
+ icon: Plus,
142
+ title: '新增',
143
+ chip: insertIndexes.value.length
144
+ ? `插入到第 ${insertIndexes.value[insertIndexes.value.length - 1]! + 1} 位`
145
+ : undefined,
146
+ tone: 'create'
147
+ }
148
+ }
149
+ return {
150
+ icon: EditPen,
151
+ title: '编辑',
152
+ chip: state.row ? `第 ${state.row.index + 1} 行` : undefined,
153
+ tone: 'update'
154
+ }
155
+ })
156
+
157
+ /** 通过 row 切换 / 类型切换驱动表单内容淡出淡入 */
158
+ const bodyKey = computed(() => {
159
+ return `${state.type}-${state.row?.uid ?? 'create'}-${state.parentRow?.uid ?? 'root'}`
160
+ })
85
161
  </script>