@veltra/desktop 1.7.19 → 1.8.0

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 (71) hide show
  1. package/dist/components/batch-edit/batch-edit-form.js +40 -26
  2. package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
  3. package/dist/components/batch-edit/use-handlers.js +3 -3
  4. package/dist/components/batch-edit/use-handlers.js.map +1 -1
  5. package/dist/components/batch-edit/use-shortcut-key.js +4 -2
  6. package/dist/components/batch-edit/use-shortcut-key.js.map +1 -1
  7. package/dist/components/card/style2.css +0 -2
  8. package/dist/components/dropdown/dropdown.js +5 -3
  9. package/dist/components/dropdown/dropdown.js.map +1 -1
  10. package/dist/components/expression-editor/components/path-preview.js +3 -6
  11. package/dist/components/expression-editor/components/path-preview.js.map +1 -1
  12. package/dist/components/expression-editor/components/variable-picker.js +49 -12
  13. package/dist/components/expression-editor/components/variable-picker.js.map +1 -1
  14. package/dist/components/expression-editor/core/editor.js +20 -0
  15. package/dist/components/expression-editor/core/editor.js.map +1 -1
  16. package/dist/components/expression-editor/expression-editor.js +53 -18
  17. package/dist/components/expression-editor/expression-editor.js.map +1 -1
  18. package/dist/components/expression-editor/style2.css +135 -73
  19. package/dist/components/form/form.js.map +1 -1
  20. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js +0 -1
  21. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js.map +1 -1
  22. package/dist/components/form/style2.css +8 -3
  23. package/dist/components/index.d.ts +26 -26
  24. package/dist/components/index.js +52 -52
  25. package/dist/components/multi-select/multi-select.js +16 -12
  26. package/dist/components/multi-select/multi-select.js.map +1 -1
  27. package/dist/components/multi-select/style2.css +7 -0
  28. package/dist/components/multi-tree-select/multi-tree-select.js +82 -56
  29. package/dist/components/multi-tree-select/multi-tree-select.js.map +1 -1
  30. package/dist/components/multi-tree-select/style2.css +7 -0
  31. package/dist/components/select/select.js +7 -3
  32. package/dist/components/select/select.js.map +1 -1
  33. package/dist/components/select/style2.css +7 -0
  34. package/dist/components/select/use-options.js +14 -3
  35. package/dist/components/select/use-options.js.map +1 -1
  36. package/dist/components/tree-select/style2.css +7 -0
  37. package/dist/components/tree-select/tree-select.js +49 -17
  38. package/dist/components/tree-select/tree-select.js.map +1 -1
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +2 -2
  41. package/dist/types/dropdown.d.ts +10 -0
  42. package/dist/types/multi-tree-select.d.ts +7 -1
  43. package/dist/types/tree-select.d.ts +7 -1
  44. package/package.json +1 -1
  45. package/src/components/batch-edit/__test__/batch-edit.test.ts +112 -1
  46. package/src/components/batch-edit/batch-edit-form.vue +21 -2
  47. package/src/components/batch-edit/use-handlers.ts +6 -5
  48. package/src/components/batch-edit/use-shortcut-key.ts +5 -2
  49. package/src/components/card/style.scss +0 -2
  50. package/src/components/dropdown/dropdown.vue +9 -4
  51. package/src/components/expression-editor/components/path-preview.vue +1 -2
  52. package/src/components/expression-editor/components/variable-picker.vue +54 -13
  53. package/src/components/expression-editor/core/editor.ts +28 -0
  54. package/src/components/expression-editor/expression-editor.vue +47 -17
  55. package/src/components/expression-editor/style.scss +167 -82
  56. package/src/components/form/form.vue +0 -1
  57. package/src/components/form/style.scss +11 -4
  58. package/src/components/multi-select/multi-select.vue +11 -2
  59. package/src/components/multi-select/style.scss +6 -0
  60. package/src/components/multi-tree-select/multi-tree-select.vue +58 -9
  61. package/src/components/multi-tree-select/style.scss +6 -0
  62. package/src/components/select/__test__/select.test.ts +78 -0
  63. package/src/components/select/select.vue +13 -3
  64. package/src/components/select/style.scss +6 -0
  65. package/src/components/select/use-options.ts +25 -4
  66. package/src/components/tree-select/__test__/tree-select.test.ts +116 -0
  67. package/src/components/tree-select/style.scss +6 -0
  68. package/src/components/tree-select/tree-select.vue +63 -13
  69. package/src/types/dropdown.ts +10 -0
  70. package/src/types/multi-tree-select.ts +12 -1
  71. package/src/types/tree-select.ts +12 -1
@@ -1,12 +1,13 @@
1
1
  import _sfc_main$1 from "../icon/icon.js";
2
2
  import _sfc_main$2 from "../dropdown/dropdown.js";
3
3
  import input_default from "../input/input.js";
4
- import _sfc_main$3 from "../tree/tree.js";
4
+ import _sfc_main$3 from "../loading/loading.js";
5
+ import _sfc_main$4 from "../tree/tree.js";
5
6
  import { Fragment, Transition, computed, createBlock, createCommentVNode, createElementBlock, createSlots, createTextVNode, createVNode, defineComponent, mergeModels, mergeProps, nextTick, normalizeClass, openBlock, renderSlot, shallowRef, toDisplayString, unref, useModel, useSlots, useTemplateRef, watch, withCtx, withModifiers } from "vue";
6
7
  import { useFormFallbackProps, useUserAction } from "@veltra/compositions";
7
8
  import { FORM_EMPTY_CONTENT, bem, fieldKey, injectFormContext } from "@veltra/utils";
8
9
  import { ArrowDown, Close } from "@veltra/icons/normal";
9
- import { dfs, o } from "@cat-kit/core";
10
+ import { debounce, dfs, o } from "@cat-kit/core";
10
11
  //#region src/components/tree-select/tree-select.vue
11
12
  const _sfc_main = /*@__PURE__*/ defineComponent({
12
13
  name: "UTreeSelect",
@@ -14,6 +15,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
14
15
  __name: "tree-select",
15
16
  props: /*@__PURE__*/ mergeModels({
16
17
  modelValue: {},
18
+ data: {},
17
19
  placeholder: { default: "请选择" },
18
20
  clearable: {
19
21
  type: Boolean,
@@ -49,7 +51,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
49
51
  labelKey: { default: "label" },
50
52
  valueKey: { default: "value" },
51
53
  childrenKey: {},
52
- data: {},
53
54
  disabledNode: {},
54
55
  checkOnClickNode: { type: Boolean },
55
56
  checkStrictly: { type: Boolean },
@@ -77,6 +78,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
77
78
  "disabled",
78
79
  "label",
79
80
  "readonly",
81
+ "data",
80
82
  "contentClass",
81
83
  "contentStyle",
82
84
  "minWidth",
@@ -86,11 +88,37 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
86
88
  const cls = bem("tree-select");
87
89
  const labelKey = computed(() => fieldKey(props.labelKey, "label"));
88
90
  const valueKey = computed(() => fieldKey(props.valueKey, "value"));
89
- /**过滤 */
91
+ /** 是否可搜索:显式声明或数据源为远程函数 */
92
+ const filterable = computed(() => props.filterable || typeof props.data === "function");
93
+ /** 过滤 */
90
94
  const qs = shallowRef("");
91
- watch(qs, (qs) => {
92
- treeRef.value?.filter(qs);
93
- });
95
+ const treeRef = shallowRef();
96
+ /** 远程搜索返回的树数据 */
97
+ const remoteData = shallowRef();
98
+ /** 面板渲染的树数据:data 为函数时来自远程搜索,否则为静态数据 */
99
+ const treeData = computed(() => typeof props.data === "function" ? remoteData.value : props.data);
100
+ const remoteLoading = shallowRef(false);
101
+ /** 远程请求序号;响应返回时序号不一致说明已有更新的请求,结果作废 */
102
+ let remoteSeq = 0;
103
+ const debouncedRemoteSearch = debounce(async (query, dataFn) => {
104
+ const seq = ++remoteSeq;
105
+ remoteLoading.value = true;
106
+ try {
107
+ const data = await dataFn(query);
108
+ if (seq !== remoteSeq || query !== qs.value) return;
109
+ remoteData.value = data ?? [];
110
+ if (query) nextTick(() => treeRef.value?.expandAll());
111
+ } finally {
112
+ if (seq === remoteSeq) remoteLoading.value = false;
113
+ }
114
+ }, 200);
115
+ watch([qs, () => props.data], ([query, data]) => {
116
+ if (typeof data === "function") {
117
+ debouncedRemoteSearch(query, data);
118
+ return;
119
+ }
120
+ treeRef.value?.filter(query);
121
+ }, { immediate: true });
94
122
  const model = useModel(__props, "modelValue");
95
123
  /** 内部展示文案,仅由 data 推导;通过 update:text 单向通知父级 */
96
124
  const label = shallowRef();
@@ -110,17 +138,17 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
110
138
  }
111
139
  /** 触发输入框的值:查询态下承载查询串,否则展示选中标签 */
112
140
  const inputValue = computed(() => {
113
- if (props.filterable && querying.value) return qs.value;
141
+ if (filterable.value && querying.value) return qs.value;
114
142
  return model.value || model.value === 0 ? label.value : void 0;
115
143
  });
116
144
  /** 查询态下已选标签降级为占位提示 */
117
145
  const inputPlaceholder = computed(() => {
118
146
  const display = model.value || model.value === 0 ? label.value : void 0;
119
- if (props.filterable && querying.value && display) return display;
147
+ if (filterable.value && querying.value && display) return display;
120
148
  return props.placeholder;
121
149
  });
122
150
  function handleQueryInput(value) {
123
- if (!props.filterable) return;
151
+ if (!filterable.value) return;
124
152
  qs.value = value;
125
153
  }
126
154
  const hovered = shallowRef(false);
@@ -135,20 +163,19 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
135
163
  * 非输入区域(箭头、留白)放行,维持原开合行为。
136
164
  */
137
165
  function handleTriggerClickCapture(e) {
138
- if (!props.filterable || disabled.value) return;
166
+ if (!filterable.value || disabled.value) return;
139
167
  if (!(e.target instanceof HTMLInputElement)) return;
140
168
  e.stopPropagation();
141
169
  if (!dropdownVisible.value) dropdownRef.value?.open();
142
170
  }
143
171
  watch(dropdownVisible, (visible) => {
144
- if (visible && props.filterable) {
172
+ if (visible && filterable.value) {
145
173
  querying.value = true;
146
174
  nextTick(() => inputRef.value?.el?.focus());
147
175
  } else if (!visible) querying.value = false;
148
176
  });
149
177
  const { formProps } = injectFormContext();
150
178
  const { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props]);
151
- const treeRef = shallowRef();
152
179
  const dropdownRef = shallowRef();
153
180
  const { userAction, isUserActive } = useUserAction();
154
181
  /** 清空 */
@@ -159,7 +186,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
159
186
  emit("change", void 0);
160
187
  });
161
188
  /** 外部回显:按 data 查找 label(O(n));用户动作期内跳过 */
162
- watch([() => props.data, model], ([data, modelVal]) => {
189
+ watch([treeData, model], ([data, modelVal]) => {
163
190
  if (isUserActive()) return;
164
191
  if (!data?.length || modelVal === void 0 || modelVal === null || modelVal === "") {
165
192
  setLabel(void 0);
@@ -222,7 +249,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
222
249
  clearable: false,
223
250
  "model-value": inputValue.value,
224
251
  "onUpdate:modelValue": handleQueryInput,
225
- "native-readonly": !__props.filterable || !querying.value,
252
+ "native-readonly": !filterable.value || !querying.value,
226
253
  onClickCapture: handleTriggerClickCapture
227
254
  }, createSlots({
228
255
  suffix: withCtx(() => [createVNode(Transition, {
@@ -258,7 +285,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
258
285
  "model-value",
259
286
  "native-readonly"
260
287
  ])]),
261
- content: withCtx(() => [createCommentVNode(" 菜单列表 "), createVNode(unref(_sfc_main$3), mergeProps(treeProps.value, {
288
+ content: withCtx(() => [createCommentVNode(" 菜单列表 "), remoteLoading.value ? (openBlock(), createElementBlock("div", {
289
+ key: 0,
290
+ class: normalizeClass(unref(cls).e("loading"))
291
+ }, [createVNode(unref(_sfc_main$3), { type: "dual-ring" })], 2)) : (openBlock(), createBlock(unref(_sfc_main$4), mergeProps({ key: 1 }, treeProps.value, {
292
+ data: treeData.value,
262
293
  selected: model.value,
263
294
  "onUpdate:selected": unref(handleSelect),
264
295
  ref_key: "treeRef",
@@ -268,10 +299,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
268
299
  slots,
269
300
  "scroll-to-view": ""
270
301
  }), null, 16, [
302
+ "data",
271
303
  "selected",
272
304
  "onUpdate:selected",
273
305
  "class"
274
- ])]),
306
+ ]))]),
275
307
  _: 3
276
308
  }, 16, [
277
309
  "class",
@@ -1 +1 @@
1
- {"version":3,"file":"tree-select.js","names":["$attrs","$slots"],"sources":["../../../src/components/tree-select/tree-select.vue"],"sourcesContent":["<template>\n <u-dropdown\n v-if=\"!readonly\"\n v-bind=\"$attrs\"\n :class=\"[cls.b, bem.is('disabled', disabled)]\"\n trigger=\"click\"\n :content-class=\"[cls.e('panel'), cls.em('panel', size), contentClass]\"\n :content-style=\"contentStyle\"\n ref=\"dropdownRef\"\n v-model:visible=\"dropdownVisible\"\n :min-width=\"minWidth\"\n :width=\"width\"\n :disabled=\"disabled\"\n @update:visible=\"handleDropdownVisible\"\n @mouseenter=\"hovered = true\"\n @mouseleave=\"hovered = false\"\n >\n <template #trigger>\n <u-input\n ref=\"inputRef\"\n :size=\"size\"\n :disabled=\"disabled\"\n :placeholder=\"inputPlaceholder\"\n :clearable=\"false\"\n :model-value=\"inputValue\"\n @update:model-value=\"handleQueryInput\"\n :native-readonly=\"!filterable || !querying\"\n @click.capture=\"handleTriggerClickCapture\"\n >\n <template #prefix v-if=\"$slots.prefix\">\n <slot name=\"prefix\" />\n </template>\n <template #suffix>\n <transition name=\"zoom-in\" mode=\"out-in\">\n <u-icon\n v-if=\"showClear\"\n :class=\"cls.e('clear')\"\n title=\"清除\"\n key=\"clear\"\n @click.stop=\"handleClear\"\n >\n <Close />\n </u-icon>\n <u-icon v-else :class=\"cls.e('arrow')\" key=\"arrow\"><ArrowDown /></u-icon>\n </transition>\n </template>\n </u-input>\n </template>\n <template #content>\n <!-- 菜单列表 -->\n\n <u-tree\n v-bind=\"treeProps\"\n :selected=\"model\"\n @update:selected=\"handleSelect\"\n ref=\"treeRef\"\n selectable\n :class=\"cls.e('content-tree')\"\n :slots=\"slots\"\n scroll-to-view\n />\n </template>\n </u-dropdown>\n\n <template v-else>\n {{ label || FORM_EMPTY_CONTENT }}\n </template>\n</template>\n\n<script lang=\"ts\" setup>\nimport { dfs, o } from '@cat-kit/core'\nimport { useFormFallbackProps, useUserAction } from '@veltra/compositions'\nimport { ArrowDown, Close } from '@veltra/icons/normal'\nimport { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport type { TreeSelectProps, TreeSelectEmits, TreeExposed, InputExposed } from '../../types'\nimport { UDropdown } from '../dropdown'\nimport { UIcon } from '../icon'\nimport { UInput } from '../input'\nimport { UTree } from '../tree'\nimport type { TreeSlotsScope } from '../tree/di'\n\ndefineOptions({ name: 'UTreeSelect', inheritAttrs: false })\n\nconst props = withDefaults(defineProps<TreeSelectProps>(), {\n labelKey: 'label',\n valueKey: 'value',\n placeholder: '请选择',\n expandAll: false,\n clearable: true,\n disabled: undefined,\n readonly: undefined,\n filterable: false,\n minWidth: '280px'\n})\n\nconst emit = defineEmits<TreeSelectEmits>()\n\nconst slots = defineSlots<{\n /** 默认插槽 */\n default?: (props: TreeSlotsScope) => any\n /** 前缀插槽 */\n prefix?: () => any\n}>()\n\nconst treeProps = computed(() => {\n return o(props as Record<string, any>).omit([\n 'tips',\n 'field',\n 'placeholder',\n 'disabled',\n 'label',\n 'readonly',\n 'contentClass',\n 'contentStyle',\n 'minWidth',\n 'width'\n ])\n})\n\nconst cls = bem('tree-select')\n\nconst labelKey = computed(() => fieldKey(props.labelKey, 'label'))\nconst valueKey = computed(() => fieldKey(props.valueKey, 'value'))\n\n/**过滤 */\nconst qs = shallowRef('')\nwatch(qs, (qs) => {\n treeRef.value?.filter(qs)\n})\n\nconst model = defineModel<string | number>()\n\n/** 内部展示文案,仅由 data 推导;通过 update:text 单向通知父级 */\nconst label = shallowRef<string>()\n\nconst dropdownVisible = shallowRef(false)\n\n/**\n * 是否处于查询态:决定触发输入框显示查询串还是选中标签。\n * 与面板可见状态解耦 —— 选择后立即退出查询态,\n * 不等面板关闭动画结束(否则显示值会延迟一个动画时长才恢复)。\n */\nconst querying = shallowRef(false)\n\nconst inputRef = useTemplateRef<InputExposed>('inputRef')\n\n/** 更新内部文案;值变化时单向 emit update:text */\nfunction setLabel(next?: string) {\n if (label.value === next) return\n label.value = next\n emit('update:text', next)\n}\n\n/** 触发输入框的值:查询态下承载查询串,否则展示选中标签 */\nconst inputValue = computed(() => {\n if (props.filterable && querying.value) return qs.value\n return model.value || model.value === 0 ? label.value : undefined\n})\n\n/** 查询态下已选标签降级为占位提示 */\nconst inputPlaceholder = computed(() => {\n const display = model.value || model.value === 0 ? label.value : undefined\n if (props.filterable && querying.value && display) {\n return display\n }\n return props.placeholder\n})\n\nfunction handleQueryInput(value: string) {\n if (!props.filterable) return\n qs.value = value\n}\n\nconst hovered = shallowRef(false)\n\n/** 悬停且存在选中值时展示清除按钮(替代下拉箭头) */\nconst showClear = computed(() => {\n if (!props.clearable || disabled.value || !hovered.value) return false\n return !!(model.value || model.value === 0 ? label.value : undefined)\n})\n\n/**\n * 过滤模式下拦截输入区域的点击:\n * 阻止 dropdown 的 trigger 开合切换,保持面板展开以不中断输入。\n * 非输入区域(箭头、留白)放行,维持原开合行为。\n */\nfunction handleTriggerClickCapture(e: MouseEvent) {\n if (!props.filterable || disabled.value) return\n if (!(e.target instanceof HTMLInputElement)) return\n\n e.stopPropagation()\n if (!dropdownVisible.value) dropdownRef.value?.open()\n}\n\nwatch(dropdownVisible, (visible) => {\n if (visible && props.filterable) {\n // 面板展开后进入查询态并聚焦输入框,可以立即输入查询\n querying.value = true\n nextTick(() => inputRef.value?.el?.focus())\n } else if (!visible) {\n querying.value = false\n }\n})\n\nconst { formProps } = injectFormContext()\n\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])\n\nconst treeRef = shallowRef<TreeExposed>()\n\nconst dropdownRef = shallowRef<InstanceType<typeof UDropdown>>()\n\nconst { userAction, isUserActive } = useUserAction()\n\n/** 清空 */\nconst handleClear = userAction(() => {\n model.value = ''\n setLabel(undefined)\n emit('clear')\n emit('change', undefined)\n})\n\n/** 外部回显:按 data 查找 label(O(n));用户动作期内跳过 */\nwatch(\n [() => props.data, model],\n ([data, modelVal]) => {\n if (isUserActive()) return\n if (!data?.length || modelVal === undefined || modelVal === null || modelVal === '') {\n setLabel(undefined)\n return\n }\n\n let nextLabel: string | undefined\n const childrenKey = props.childrenKey ?? 'children'\n data.some((item) => {\n let founded = false\n dfs(\n item,\n (v) => {\n if (v[valueKey.value] === modelVal) {\n nextLabel = v[labelKey.value]\n founded = true\n return true\n }\n },\n childrenKey\n )\n\n return founded\n })\n setLabel(nextLabel)\n },\n { immediate: true }\n)\n\n/** 用户选择:O(1) 写入值与文案,跳过 data 回显查找 */\nconst handleSelect = userAction(\n (selected?: string | number, selectedData?: Record<string, any>) => {\n model.value = selected ?? ''\n\n if (selectedData) {\n setLabel(o(selectedData).get(labelKey.value))\n } else {\n setLabel(undefined)\n }\n\n emit('change', selectedData)\n // 立即退出查询态,输入框同步恢复显示选中标签\n querying.value = false\n qs.value = ''\n dropdownRef.value?.close()\n }\n)\nconst handleDropdownVisible = (visible: boolean) => {\n if (!visible) {\n qs.value = ''\n }\n}\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"tree-select.js","names":["$attrs","$slots"],"sources":["../../../src/components/tree-select/tree-select.vue"],"sourcesContent":["<template>\n <u-dropdown\n v-if=\"!readonly\"\n v-bind=\"$attrs\"\n :class=\"[cls.b, bem.is('disabled', disabled)]\"\n trigger=\"click\"\n :content-class=\"[cls.e('panel'), cls.em('panel', size), contentClass]\"\n :content-style=\"contentStyle\"\n ref=\"dropdownRef\"\n v-model:visible=\"dropdownVisible\"\n :min-width=\"minWidth\"\n :width=\"width\"\n :disabled=\"disabled\"\n @update:visible=\"handleDropdownVisible\"\n @mouseenter=\"hovered = true\"\n @mouseleave=\"hovered = false\"\n >\n <template #trigger>\n <u-input\n ref=\"inputRef\"\n :size=\"size\"\n :disabled=\"disabled\"\n :placeholder=\"inputPlaceholder\"\n :clearable=\"false\"\n :model-value=\"inputValue\"\n @update:model-value=\"handleQueryInput\"\n :native-readonly=\"!filterable || !querying\"\n @click.capture=\"handleTriggerClickCapture\"\n >\n <template #prefix v-if=\"$slots.prefix\">\n <slot name=\"prefix\" />\n </template>\n <template #suffix>\n <transition name=\"zoom-in\" mode=\"out-in\">\n <u-icon\n v-if=\"showClear\"\n :class=\"cls.e('clear')\"\n title=\"清除\"\n key=\"clear\"\n @click.stop=\"handleClear\"\n >\n <Close />\n </u-icon>\n <u-icon v-else :class=\"cls.e('arrow')\" key=\"arrow\"><ArrowDown /></u-icon>\n </transition>\n </template>\n </u-input>\n </template>\n <template #content>\n <!-- 菜单列表 -->\n\n <div v-if=\"remoteLoading\" :class=\"cls.e('loading')\">\n <ULoading type=\"dual-ring\" />\n </div>\n <u-tree\n v-else\n v-bind=\"treeProps\"\n :data=\"treeData\"\n :selected=\"model\"\n @update:selected=\"handleSelect\"\n ref=\"treeRef\"\n selectable\n :class=\"cls.e('content-tree')\"\n :slots=\"slots\"\n scroll-to-view\n />\n </template>\n </u-dropdown>\n\n <template v-else>\n {{ label || FORM_EMPTY_CONTENT }}\n </template>\n</template>\n\n<script lang=\"ts\" setup>\nimport { debounce, dfs, o } from '@cat-kit/core'\nimport { useFormFallbackProps, useUserAction } from '@veltra/compositions'\nimport { ArrowDown, Close } from '@veltra/icons/normal'\nimport { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport type { TreeSelectProps, TreeSelectEmits, TreeExposed, InputExposed } from '../../types'\nimport { UDropdown } from '../dropdown'\nimport { UIcon } from '../icon'\nimport { UInput } from '../input'\nimport { ULoading } from '../loading'\nimport { UTree } from '../tree'\nimport type { TreeSlotsScope } from '../tree/di'\n\ndefineOptions({ name: 'UTreeSelect', inheritAttrs: false })\n\nconst props = withDefaults(defineProps<TreeSelectProps>(), {\n labelKey: 'label',\n valueKey: 'value',\n placeholder: '请选择',\n expandAll: false,\n clearable: true,\n disabled: undefined,\n readonly: undefined,\n filterable: false,\n minWidth: '280px'\n})\n\nconst emit = defineEmits<TreeSelectEmits>()\n\nconst slots = defineSlots<{\n /** 默认插槽 */\n default?: (props: TreeSlotsScope) => any\n /** 前缀插槽 */\n prefix?: () => any\n}>()\n\nconst treeProps = computed(() => {\n return o(props as Record<string, any>).omit([\n 'tips',\n 'field',\n 'placeholder',\n 'disabled',\n 'label',\n 'readonly',\n 'data',\n 'contentClass',\n 'contentStyle',\n 'minWidth',\n 'width'\n ])\n})\n\nconst cls = bem('tree-select')\n\nconst labelKey = computed(() => fieldKey(props.labelKey, 'label'))\nconst valueKey = computed(() => fieldKey(props.valueKey, 'value'))\n\n/** 是否可搜索:显式声明或数据源为远程函数 */\nconst filterable = computed(() => props.filterable || typeof props.data === 'function')\n\n/** 过滤 */\nconst qs = shallowRef('')\n\nconst treeRef = shallowRef<TreeExposed>()\n\n/** 远程搜索返回的树数据 */\nconst remoteData = shallowRef<Record<string, any>[]>()\n\n/** 面板渲染的树数据:data 为函数时来自远程搜索,否则为静态数据 */\nconst treeData = computed(() => (typeof props.data === 'function' ? remoteData.value : props.data))\n\nconst remoteLoading = shallowRef(false)\n/** 远程请求序号;响应返回时序号不一致说明已有更新的请求,结果作废 */\nlet remoteSeq = 0\n\nconst debouncedRemoteSearch = debounce(\n async (\n query: string,\n dataFn: (qs: string) => Promise<Record<string, any>[]> | Record<string, any>[]\n ) => {\n const seq = ++remoteSeq\n remoteLoading.value = true\n try {\n const data = await dataFn(query)\n // 响应到达时查询词可能已变化或已有更新的请求,过期结果直接丢弃\n if (seq !== remoteSeq || query !== qs.value) return\n remoteData.value = data ?? []\n // 搜索结果展示完整命中路径,与本地过滤自动展开祖先的行为对齐\n if (query) nextTick(() => treeRef.value?.expandAll())\n } finally {\n if (seq === remoteSeq) remoteLoading.value = false\n }\n },\n 200\n)\n\nwatch(\n [qs, () => props.data],\n ([query, data]) => {\n if (typeof data === 'function') {\n debouncedRemoteSearch(query, data)\n return\n }\n treeRef.value?.filter(query)\n },\n { immediate: true }\n)\n\nconst model = defineModel<string | number>()\n\n/** 内部展示文案,仅由 data 推导;通过 update:text 单向通知父级 */\nconst label = shallowRef<string>()\n\nconst dropdownVisible = shallowRef(false)\n\n/**\n * 是否处于查询态:决定触发输入框显示查询串还是选中标签。\n * 与面板可见状态解耦 —— 选择后立即退出查询态,\n * 不等面板关闭动画结束(否则显示值会延迟一个动画时长才恢复)。\n */\nconst querying = shallowRef(false)\n\nconst inputRef = useTemplateRef<InputExposed>('inputRef')\n\n/** 更新内部文案;值变化时单向 emit update:text */\nfunction setLabel(next?: string) {\n if (label.value === next) return\n label.value = next\n emit('update:text', next)\n}\n\n/** 触发输入框的值:查询态下承载查询串,否则展示选中标签 */\nconst inputValue = computed(() => {\n if (filterable.value && querying.value) return qs.value\n return model.value || model.value === 0 ? label.value : undefined\n})\n\n/** 查询态下已选标签降级为占位提示 */\nconst inputPlaceholder = computed(() => {\n const display = model.value || model.value === 0 ? label.value : undefined\n if (filterable.value && querying.value && display) {\n return display\n }\n return props.placeholder\n})\n\nfunction handleQueryInput(value: string) {\n if (!filterable.value) return\n qs.value = value\n}\n\nconst hovered = shallowRef(false)\n\n/** 悬停且存在选中值时展示清除按钮(替代下拉箭头) */\nconst showClear = computed(() => {\n if (!props.clearable || disabled.value || !hovered.value) return false\n return !!(model.value || model.value === 0 ? label.value : undefined)\n})\n\n/**\n * 过滤模式下拦截输入区域的点击:\n * 阻止 dropdown 的 trigger 开合切换,保持面板展开以不中断输入。\n * 非输入区域(箭头、留白)放行,维持原开合行为。\n */\nfunction handleTriggerClickCapture(e: MouseEvent) {\n if (!filterable.value || disabled.value) return\n if (!(e.target instanceof HTMLInputElement)) return\n\n e.stopPropagation()\n if (!dropdownVisible.value) dropdownRef.value?.open()\n}\n\nwatch(dropdownVisible, (visible) => {\n if (visible && filterable.value) {\n // 面板展开后进入查询态并聚焦输入框,可以立即输入查询\n querying.value = true\n nextTick(() => inputRef.value?.el?.focus())\n } else if (!visible) {\n querying.value = false\n }\n})\n\nconst { formProps } = injectFormContext()\n\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])\n\nconst dropdownRef = shallowRef<InstanceType<typeof UDropdown>>()\n\nconst { userAction, isUserActive } = useUserAction()\n\n/** 清空 */\nconst handleClear = userAction(() => {\n model.value = ''\n setLabel(undefined)\n emit('clear')\n emit('change', undefined)\n})\n\n/** 外部回显:按 data 查找 label(O(n));用户动作期内跳过 */\nwatch(\n [treeData, model],\n ([data, modelVal]) => {\n if (isUserActive()) return\n if (!data?.length || modelVal === undefined || modelVal === null || modelVal === '') {\n setLabel(undefined)\n return\n }\n\n let nextLabel: string | undefined\n const childrenKey = props.childrenKey ?? 'children'\n data.some((item) => {\n let founded = false\n dfs(\n item,\n (v) => {\n if (v[valueKey.value] === modelVal) {\n nextLabel = v[labelKey.value]\n founded = true\n return true\n }\n },\n childrenKey\n )\n\n return founded\n })\n setLabel(nextLabel)\n },\n { immediate: true }\n)\n\n/** 用户选择:O(1) 写入值与文案,跳过 data 回显查找 */\nconst handleSelect = userAction(\n (selected?: string | number, selectedData?: Record<string, any>) => {\n model.value = selected ?? ''\n\n if (selectedData) {\n setLabel(o(selectedData).get(labelKey.value))\n } else {\n setLabel(undefined)\n }\n\n emit('change', selectedData)\n // 立即退出查询态,输入框同步恢复显示选中标签\n querying.value = false\n qs.value = ''\n dropdownRef.value?.close()\n }\n)\nconst handleDropdownVisible = (visible: boolean) => {\n if (!visible) {\n qs.value = ''\n }\n}\n</script>\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import _sfc_main$68 from "./components/scroll/scroll.js";
4
4
  import _sfc_main$83 from "./components/tip/tip.js";
5
5
  import _sfc_main$28 from "./components/dropdown/dropdown.js";
6
6
  import input_default from "./components/input/input.js";
7
+ import _sfc_main$47 from "./components/loading/loading.js";
7
8
  import _sfc_main$70 from "./components/select/select.js";
8
9
  import _sfc_main$21 from "./components/condition-editor/condition-editor.js";
9
10
  import _sfc_main$77 from "./components/tabs/tabs-horizontal.js";
@@ -30,7 +31,6 @@ import _sfc_main$10 from "./components/card/card-content.js";
30
31
  import _sfc_main$9 from "./components/card/card-action.js";
31
32
  import _sfc_main$23 from "./components/date-panel/date-panel.js";
32
33
  import _sfc_main$25 from "./components/date-range-picker/date-range-picker.js";
33
- import _sfc_main$47 from "./components/loading/loading.js";
34
34
  import _sfc_main$64 from "./components/progress-nodes/progress-nodes.js";
35
35
  import _sfc_main$1 from "./components/auto-complete/auto-complete.js";
36
36
  import _sfc_main$63 from "./components/progress/progress.js";
package/dist/index.js CHANGED
@@ -6,6 +6,8 @@ import _sfc_main$83 from "./components/tip/tip.js";
6
6
  import { createEmptyGroup, createEmptyLeaf, evaluate } from "./components/condition-editor/core/evaluator.js";
7
7
  import _sfc_main$28 from "./components/dropdown/dropdown.js";
8
8
  import input_default from "./components/input/input.js";
9
+ import _sfc_main$47 from "./components/loading/loading.js";
10
+ import { vLoading } from "./components/loading/directive.js";
9
11
  import _sfc_main$70 from "./components/select/select.js";
10
12
  import _sfc_main$21 from "./components/condition-editor/condition-editor.js";
11
13
  import _sfc_main$77 from "./components/tabs/tabs-horizontal.js";
@@ -33,8 +35,6 @@ import _sfc_main$10 from "./components/card/card-content.js";
33
35
  import _sfc_main$9 from "./components/card/card-action.js";
34
36
  import _sfc_main$23 from "./components/date-panel/date-panel.js";
35
37
  import _sfc_main$25 from "./components/date-range-picker/date-range-picker.js";
36
- import _sfc_main$47 from "./components/loading/loading.js";
37
- import { vLoading } from "./components/loading/directive.js";
38
38
  import _sfc_main$64 from "./components/progress-nodes/progress-nodes.js";
39
39
  import _sfc_main$1 from "./components/auto-complete/auto-complete.js";
40
40
  import _sfc_main$63 from "./components/progress/progress.js";
@@ -8,6 +8,16 @@ export interface DropdownProps {
8
8
  * @default 'hover'
9
9
  */
10
10
  trigger?: 'hover' | 'click' | 'custom';
11
+ /**
12
+ * 弹出方向;空间不足时仍会自动翻转
13
+ * @default 'bottom'
14
+ */
15
+ direction?: 'top' | 'bottom' | 'left' | 'right';
16
+ /**
17
+ * 弹出对齐方式
18
+ * @default 'start'
19
+ */
20
+ alignment?: 'center' | 'start' | 'end';
11
21
  /**
12
22
  * 宽度
13
23
  * @default - 跟随触发宽度
@@ -3,8 +3,14 @@ import { CSSProperties } from "vue";
3
3
  import { DeconstructValue, FormComponentProps } from "@veltra/utils";
4
4
  //#region src/types/multi-tree-select.d.ts
5
5
  /** 树形多选组件组件属性 */
6
- export interface MultiTreeSelectProps extends FormComponentProps, Omit<TreeProps, 'selected' | 'checked' | 'selectable' | 'checkable'> {
6
+ export interface MultiTreeSelectProps extends FormComponentProps, Omit<TreeProps, 'selected' | 'checked' | 'selectable' | 'checkable' | 'data'> {
7
7
  modelValue?: (string | number)[];
8
+ /**
9
+ * 树数据
10
+ * @description 如果传入一个函数,那么filterable会被强制启用;
11
+ * 函数按查询词返回匹配的树,初始以空串调用一次(加载默认树)
12
+ */
13
+ data?: Record<string, any>[] | ((qs: string) => Promise<Record<string, any>[]> | Record<string, any>[]);
8
14
  /**自定义占位文字 */
9
15
  placeholder?: string;
10
16
  /**
@@ -3,8 +3,14 @@ import { CSSProperties } from "vue";
3
3
  import { DeconstructValue, FormComponentProps } from "@veltra/utils";
4
4
  //#region src/types/tree-select.d.ts
5
5
  /** 树形选择器组件属性 */
6
- export interface TreeSelectProps extends FormComponentProps, Omit<TreeProps, 'selected' | 'checked' | 'selectable' | 'checkable'> {
6
+ export interface TreeSelectProps extends FormComponentProps, Omit<TreeProps, 'selected' | 'checked' | 'selectable' | 'checkable' | 'data'> {
7
7
  modelValue?: string | number;
8
+ /**
9
+ * 树数据
10
+ * @description 如果传入一个函数,那么filterable会被强制启用;
11
+ * 函数按查询词返回匹配的树,初始以空串调用一次(加载默认树)
12
+ */
13
+ data?: Record<string, any>[] | ((qs: string) => Promise<Record<string, any>[]> | Record<string, any>[]);
8
14
  /** 自定义占位文字 */
9
15
  placeholder?: string;
10
16
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veltra/desktop",
3
- "version": "1.7.19",
3
+ "version": "1.8.0",
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"
@@ -393,6 +393,117 @@ describe('UBatchEdit formMode', () => {
393
393
  unmount()
394
394
  })
395
395
 
396
+ it('弹框新增「保存并继续」:插入后弹框保持打开、表单重置,可连续录入', async () => {
397
+ const saveMethod = vi.fn()
398
+ const { host, data, model, unmount } = mountBatchEdit({
399
+ props: { formMode: 'dialog', quickEdit: false, saveMethod }
400
+ })
401
+
402
+ clickEl(host.querySelector<HTMLElement>('.u-batch-edit__add-btn')!)
403
+ await waitDialogOpen()
404
+
405
+ const continueBtn = () =>
406
+ queryDialog()!.querySelector<HTMLElement>(
407
+ '.u-batch-edit__form-actions button[title="保存并继续"]'
408
+ )!
409
+
410
+ // 编辑已有行走 update,不显示「保存并继续」由面板用例覆盖;新增弹框内应存在
411
+ expect(continueBtn()).toBeTruthy()
412
+
413
+ setInputValue(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!, '第一条')
414
+ await nextTick()
415
+ await nextTick()
416
+ clickEl(continueBtn())
417
+ await waitDialogSettled()
418
+
419
+ // 插入一行但弹框保持打开,表单重置为初始值
420
+ expect(data.value).toHaveLength(3)
421
+ expect(data.value[2]).toMatchObject({ label: '第一条' })
422
+ expect(queryDialog()).toBeTruthy()
423
+ expect(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!.value).toBe('')
424
+ expect(model.label).toBe('')
425
+
426
+ // 继续录入第二条后普通保存:追加到末尾并关闭弹框
427
+ setInputValue(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!, '第二条')
428
+ await nextTick()
429
+ await nextTick()
430
+ clickEl(querySaveButton(queryDialog()!)!)
431
+ await waitDialogClosed()
432
+
433
+ expect(saveMethod).toHaveBeenCalledTimes(2)
434
+ expect(data.value).toHaveLength(4)
435
+ expect(data.value[3]).toMatchObject({ label: '第二条' })
436
+
437
+ unmount()
438
+ })
439
+
440
+ it('弹框模式不响应 Ctrl/Cmd + S:不保存、弹框保持打开,快捷键提示不再出现', async () => {
441
+ const saveMethod = vi.fn()
442
+ const { host, data, unmount } = mountBatchEdit({
443
+ props: { formMode: 'dialog', quickEdit: false, saveMethod }
444
+ })
445
+
446
+ clickEl(host.querySelector<HTMLElement>('.u-batch-edit__add-btn')!)
447
+ await waitDialogOpen()
448
+
449
+ setInputValue(queryDialog()!.querySelector<HTMLInputElement>('.u-input input')!, '新名称')
450
+ await nextTick()
451
+ await nextTick()
452
+
453
+ queryDialog()!.dispatchEvent(
454
+ new KeyboardEvent('keydown', {
455
+ key: 's',
456
+ code: 'KeyS',
457
+ ctrlKey: true,
458
+ bubbles: true,
459
+ cancelable: true
460
+ })
461
+ )
462
+ await waitDialogSettled()
463
+
464
+ expect(saveMethod).not.toHaveBeenCalled()
465
+ expect(data.value).toHaveLength(2)
466
+ expect(queryDialog()).toBeTruthy()
467
+ expect(queryDialog()!.querySelector('.u-batch-edit__form-hint')!.textContent).not.toContain(
468
+ 'Ctrl + S'
469
+ )
470
+
471
+ // Esc 仍可关闭弹框
472
+ queryDialog()!.dispatchEvent(
473
+ new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true })
474
+ )
475
+ await waitDialogClosed()
476
+ expect(saveMethod).not.toHaveBeenCalled()
477
+
478
+ unmount()
479
+ })
480
+
481
+ it('面板模式 Ctrl/Cmd + S 保存仍可用(组件获焦时生效)', async () => {
482
+ const saveMethod = vi.fn()
483
+ const { host, unmount } = mountBatchEdit({ props: { quickEdit: false, saveMethod } })
484
+
485
+ await clickEditRow(host, 0)
486
+
487
+ const input = host.querySelector<HTMLInputElement>('.u-batch-edit__form .u-input input')!
488
+ // 面板模式快捷键要求组件获焦:focusin 冒泡到根布局后置位
489
+ input.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
490
+ input.dispatchEvent(
491
+ new KeyboardEvent('keydown', {
492
+ key: 's',
493
+ code: 'KeyS',
494
+ ctrlKey: true,
495
+ bubbles: true,
496
+ cancelable: true
497
+ })
498
+ )
499
+ await waitDialogSettled()
500
+
501
+ expect(saveMethod).toHaveBeenCalledTimes(1)
502
+ expect(saveMethod.mock.calls[0]![1]).toBe('update')
503
+
504
+ unmount()
505
+ })
506
+
396
507
  it('弹框内取消 / 关闭按钮 / 遮罩点击均不保存,且 model 恢复初始值', async () => {
397
508
  const saveMethod = vi.fn()
398
509
  const { host, data, model, unmount } = mountBatchEdit({
@@ -461,7 +572,7 @@ describe('UBatchEdit 重入保护', () => {
461
572
  await nextTick()
462
573
  await nextTick()
463
574
 
464
- // 同一 tick 内连续两次触发保存(模拟保存进行中再按 Ctrl/Cmd+S),中间故意不等待
575
+ // 同一 tick 内连续两次点击保存(模拟保存进行中的重复触发),中间故意不等待
465
576
  const saveBtn = querySaveButton(queryDialog()!)!
466
577
  clickEl(saveBtn)
467
578
  clickEl(saveBtn)
@@ -54,7 +54,11 @@
54
54
  <template v-if="props.readonly"> 只读模式 </template>
55
55
 
56
56
  <span :class="{ [bem.is('concealed')]: !shortcutActive }">
57
- <template v-if="!(state.formActionType === 'update' && props.quickEdit)">
57
+ <template
58
+ v-if="
59
+ props.formMode === 'panel' && !(state.formActionType === 'update' && props.quickEdit)
60
+ "
61
+ >
58
62
  <u-kbd>Ctrl + S</u-kbd> 保存 ·
59
63
  </template>
60
64
  <u-kbd>Esc</u-kbd> 关闭
@@ -63,6 +67,15 @@
63
67
 
64
68
  <div :class="cls.e('form-actions')">
65
69
  <u-button size="small" text :loading="state.loading" @click="handleClose"> 取消 </u-button>
70
+ <u-button
71
+ v-if="showSaveContinueBtn"
72
+ size="small"
73
+ title="保存并继续"
74
+ :loading="state.loading"
75
+ @click="handleSave(true)"
76
+ >
77
+ 保存并继续
78
+ </u-button>
66
79
  <u-button
67
80
  v-if="showSaveBtn"
68
81
  type="primary"
@@ -70,7 +83,7 @@
70
83
  title="保存"
71
84
  :icon="Save"
72
85
  :loading="state.loading"
73
- @click="handleSave"
86
+ @click="handleSave()"
74
87
  />
75
88
  </div>
76
89
  </footer>
@@ -151,6 +164,12 @@ const showSaveBtn = computed(() => {
151
164
  return creatable.value || updatable.value || creatableChild.value
152
165
  })
153
166
 
167
+ /** 弹框模式新增类操作显示「保存并继续」:保存后重置表单继续录入,不关闭弹框 */
168
+ const showSaveContinueBtn = computed(() => {
169
+ if (props.formMode !== 'dialog') return false
170
+ return creatable.value || creatableChild.value
171
+ })
172
+
154
173
  const headerInfo = computed(() => FORM_ACTION_HEADER_MAP[state.formActionType])
155
174
 
156
175
  /** 头部图标色调修饰类:样式中 createChild 对应的修饰类为 is-child */
@@ -40,7 +40,8 @@ interface Options {
40
40
  }
41
41
 
42
42
  export interface EditReturned {
43
- handleSave: () => Promise<void>
43
+ /** @param keepOpen 新增类操作保存后不关闭表单(弹框模式「保存并继续」) */
44
+ handleSave: (keepOpen?: boolean) => Promise<void>
44
45
  handleClose: () => void
45
46
  handleCreate: () => void
46
47
  handleDelete: (row: TableRow) => Promise<void>
@@ -153,8 +154,8 @@ export function useHandlers(options: Options): EditReturned {
153
154
  }
154
155
  }
155
156
 
156
- /** 保存 */
157
- const handleSave = runWithLoading(async () => {
157
+ /** 保存;keepOpen 为 true 时(「保存并继续」)新增保存后不关闭表单 */
158
+ const handleSave = runWithLoading(async (keepOpen = false) => {
158
159
  const { model, saveMethod } = props
159
160
  const valid = await formRef.value?.validate()
160
161
  if (!model || !valid) return
@@ -172,8 +173,8 @@ export function useHandlers(options: Options): EditReturned {
172
173
  }
173
174
  insert(item)
174
175
 
175
- // 弹框模式保存成功即关闭,不走面板的连续新增
176
- if (props.formMode === 'dialog') {
176
+ // 弹框模式保存成功即关闭,连续新增只能由「保存并继续」显式触发
177
+ if (props.formMode === 'dialog' && !keepOpen) {
177
178
  resetState()
178
179
  return
179
180
  }
@@ -40,10 +40,11 @@ export function useShortcutKey(options: Options) {
40
40
  /**
41
41
  * 键盘快捷键:
42
42
  * - Esc 关闭表单
43
- * - ⌘/Ctrl + S 保存(快速编辑模式下编辑行时除外)
43
+ * - ⌘/Ctrl + S 保存(仅面板模式;快速编辑模式下编辑行时除外)
44
44
  *
45
45
  * 面板模式仅组件获焦时生效;弹框模式表单被 teleport 到 body,
46
- * 组件聚焦状态覆盖不到,改为弹框打开期间生效
46
+ * 组件聚焦状态覆盖不到,改为弹框打开期间生效。
47
+ * 弹框模式不支持 ⌘/Ctrl + S 保存,保存只能走底部按钮
47
48
  */
48
49
  function handleKeydown(e: KeyboardEvent) {
49
50
  const active = props.formMode === 'dialog' ? state.formVisible : focused.value
@@ -52,6 +53,8 @@ export function useShortcutKey(options: Options) {
52
53
 
53
54
  if (e.key === 'Escape') return runEscape(e)
54
55
 
56
+ if (props.formMode === 'dialog') return
57
+
55
58
  const meta = e.metaKey || e.ctrlKey
56
59
  if (!meta) return
57
60
 
@@ -7,14 +7,12 @@ $root-name: card;
7
7
  @include m.b($root-name) {
8
8
  border-radius: fn.component-var(card, radius, fn.use-var(radius, large));
9
9
  background-color: fn.use-var(bg-color, top);
10
- border: fn.use-var(border-muted);
11
10
  box-shadow: fn.use-var(shadow, sm);
12
11
  backdrop-filter: fn.use-var(bg-filter);
13
12
  -webkit-backdrop-filter: fn.use-var(bg-filter);
14
13
  overflow: hidden;
15
14
 
16
15
  @include m.is(integrate) {
17
- border: none;
18
16
  box-shadow: none;
19
17
  --u-card-header-bg: transparent;
20
18
  --u-card-action-bg: transparent;
@@ -22,7 +22,7 @@
22
22
  import { useModel, usePop, useTransition } from '@veltra/compositions'
23
23
  import { vClickOutside } from '@veltra/directives'
24
24
  import { bem, extractNormalVNodes, setStyles, zIndex } from '@veltra/utils'
25
- import { shallowRef, computed, createVNode, cloneVNode, useTemplateRef, useAttrs } from 'vue'
25
+ import { shallowRef, computed, createVNode, cloneVNode, useTemplateRef, useAttrs, toRef } from 'vue'
26
26
 
27
27
  import type { DropdownProps, DropdownExposed, DropdownEmits } from '../../types'
28
28
  import { UNodeRender } from '../node-render'
@@ -30,7 +30,12 @@ import { useNest } from '../tip/use-nest'
30
30
 
31
31
  defineOptions({ name: 'UDropdown', inheritAttrs: false })
32
32
 
33
- const props = withDefaults(defineProps<DropdownProps>(), { trigger: 'hover', contentTag: 'div' })
33
+ const props = withDefaults(defineProps<DropdownProps>(), {
34
+ trigger: 'hover',
35
+ direction: 'bottom',
36
+ alignment: 'start',
37
+ contentTag: 'div'
38
+ })
34
39
 
35
40
  const emit = defineEmits<DropdownEmits>()
36
41
 
@@ -121,8 +126,8 @@ const transitionName = shallowRef('slide-down')
121
126
  const { update, popperContainerId } = usePop({
122
127
  triggerRef: triggerDom,
123
128
  contentRef,
124
- direction: 'bottom',
125
- alignment: 'start',
129
+ direction: toRef(() => props.direction),
130
+ alignment: toRef(() => props.alignment),
126
131
  onPop(position) {
127
132
  transitionName.value = position.placement.includes('top') ? 'slide-up' : 'slide-down'
128
133
 
@@ -6,9 +6,8 @@
6
6
  v-for="(node, i) in path"
7
7
  :key="`${i}-${node.value || node.label}`"
8
8
  :class="[cls.e('path-tree-item'), bem.is('active', i === path.length - 1)]"
9
- :style="{ paddingLeft: `${i * 14}px` }"
9
+ :style="{ '--depth': i }"
10
10
  >
11
- <span v-if="i > 0" :class="cls.e('path-tree-branch')">└</span>
12
11
  <span :class="cls.e('path-tree-label')">{{ node.label }}</span>
13
12
  </li>
14
13
  </ul>