@veltra/desktop 1.7.9 → 1.7.11
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.
- package/dist/components/batch-edit/batch-edit-form.js +7 -3
- package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
- package/dist/components/batch-edit/batch-edit.js +1 -0
- package/dist/components/batch-edit/batch-edit.js.map +1 -1
- package/dist/components/batch-edit/use-edit-state.js.map +1 -1
- package/dist/components/button/button.js +0 -1
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/drawer/drawer.js +18 -10
- package/dist/components/drawer/drawer.js.map +1 -1
- package/dist/components/drawer/style2.css +15 -0
- package/dist/components/form/form.js.map +1 -1
- package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js +31 -6
- package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/form/is-field-modified.js +12 -0
- package/dist/components/form/is-field-modified.js.map +1 -0
- package/dist/components/form/style2.css +37 -1
- package/dist/components/form/use-form-fields.js +11 -6
- package/dist/components/form/use-form-fields.js.map +1 -1
- package/dist/components/form-item/form-item.js.map +1 -1
- package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js +26 -6
- package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/types/action.d.ts +6 -1
- package/dist/types/batch-edit.d.ts +2 -0
- package/dist/types/button.d.ts +5 -1
- package/dist/types/drawer.d.ts +6 -3
- package/dist/types/form-item.d.ts +4 -1
- package/dist/types/form.d.ts +10 -1
- package/dist/types/layout.d.ts +2 -0
- package/package.json +6 -6
- package/src/components/batch-edit/__test__/batch-edit.test.ts +138 -0
- package/src/components/batch-edit/batch-edit-form.vue +18 -4
- package/src/components/batch-edit/use-edit-state.ts +1 -1
- package/src/components/button/__test__/button.test.ts +48 -0
- package/src/components/button/button.vue +1 -8
- package/src/components/drawer/__test__/drawer.test.ts +118 -0
- package/src/components/drawer/drawer.vue +5 -2
- package/src/components/drawer/style.scss +15 -0
- package/src/components/form/__test__/form.test.ts +124 -0
- package/src/components/form/__test__/is-field-modified.test.ts +37 -0
- package/src/components/form/form.vue +28 -4
- package/src/components/form/is-field-modified.ts +16 -0
- package/src/components/form/style.scss +53 -2
- package/src/components/form/use-form-fields.ts +20 -6
- package/src/components/form-item/__test__/form-item.test.ts +129 -0
- package/src/components/form-item/form-item.vue +47 -8
- package/src/types/action.ts +6 -1
- package/src/types/batch-edit.ts +2 -0
- package/src/types/button.ts +5 -1
- package/src/types/drawer.ts +6 -3
- package/src/types/form-item.ts +4 -1
- package/src/types/form.ts +10 -2
- package/src/types/layout.ts +2 -0
|
@@ -2,9 +2,9 @@ import _sfc_main from "../tip/tip.js";
|
|
|
2
2
|
import _sfc_main$1 from "../grid/grid-item.js";
|
|
3
3
|
import { defineField, formItemCls } from "./helper.js";
|
|
4
4
|
import { validateField } from "./validate.js";
|
|
5
|
-
import { Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, renderSlot, shallowRef, toDisplayString, unref, watch, withCtx } from "vue";
|
|
5
|
+
import { Transition, cloneVNode, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, renderSlot, shallowRef, toDisplayString, unref, watch, withCtx } from "vue";
|
|
6
6
|
import { useConfig, useFallbackProps } from "@veltra/compositions";
|
|
7
|
-
import { bem, injectFormContext, withUnit } from "@veltra/utils";
|
|
7
|
+
import { bem, extractNormalVNodes, injectFormContext, withUnit } from "@veltra/utils";
|
|
8
8
|
import { o } from "@cat-kit/core";
|
|
9
9
|
//#region src/components/form-item/form-item.vue?vue&type=script&setup=true&lang.tsx
|
|
10
10
|
var form_item_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
@@ -25,10 +25,27 @@ var form_item_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
25
25
|
rules: {},
|
|
26
26
|
size: {}
|
|
27
27
|
},
|
|
28
|
-
|
|
28
|
+
emits: ["change"],
|
|
29
|
+
setup(__props, { emit: __emit }) {
|
|
29
30
|
const props = __props;
|
|
31
|
+
const emit = __emit;
|
|
30
32
|
/** 表单组件上下文 */
|
|
31
|
-
const { formProps, registerField, unregisterField, shouldValidate, handleFieldChange } = injectFormContext();
|
|
33
|
+
const { formProps, registerField, unregisterField, shouldValidate, handleFieldUpdate, handleFieldChange } = injectFormContext();
|
|
34
|
+
function wrapControlChange(node) {
|
|
35
|
+
if (!node || typeof node.type === "symbol") return node;
|
|
36
|
+
return cloneVNode(node, { onChange: (...args) => {
|
|
37
|
+
emit("change", ...args);
|
|
38
|
+
if (props.field) handleFieldChange?.(props.field, ...args);
|
|
39
|
+
} });
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 承载默认插槽并拦截控件 change。
|
|
43
|
+
* 必须经填充插槽接收内容(而非直接读 UFormItem 的 $slots):
|
|
44
|
+
* 读父级 $slots 时组件自身没有更新路径,父级重渲染后动态 props(disabled、data 等)会冻结在首帧。
|
|
45
|
+
*/
|
|
46
|
+
function ContentSlot(_, { slots }) {
|
|
47
|
+
return extractNormalVNodes(slots.default?.() ?? []).map(wrapControlChange);
|
|
48
|
+
}
|
|
32
49
|
const { config } = useConfig();
|
|
33
50
|
const { size, readonly } = useFallbackProps([formProps ?? {}, props], {
|
|
34
51
|
size: "default",
|
|
@@ -77,7 +94,7 @@ var form_item_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
77
94
|
stopWatchFieldValue?.();
|
|
78
95
|
if (!field || !model) return;
|
|
79
96
|
stopWatchFieldValue = watch(() => o(model).get(field), (value) => {
|
|
80
|
-
|
|
97
|
+
handleFieldUpdate?.(field, value);
|
|
81
98
|
fieldItem.validate();
|
|
82
99
|
});
|
|
83
100
|
}, { immediate: true });
|
|
@@ -102,7 +119,10 @@ var form_item_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
102
119
|
default: withCtx(() => [createElementVNode("span", { class: normalizeClass(unref(formItemCls).e("tip-label")) }, [renderSlot(_ctx.$slots, "label", {}, () => [createTextVNode(toDisplayString(__props.label) + toDisplayString(labelPosition.value === "top" ? "" : ":"), 1)])], 2)]),
|
|
103
120
|
_: 3
|
|
104
121
|
}, 8, ["content", "class"])) : renderSlot(_ctx.$slots, "label", {}, () => [createTextVNode(toDisplayString(__props.label) + toDisplayString(labelPosition.value === "top" ? "" : ":"), 1)], void 0, 1)], 6)) : createCommentVNode("v-if", true), createElementVNode("section", { class: normalizeClass(unref(formItemCls).e("wrapper")) }, [
|
|
105
|
-
createElementVNode("div", { class: normalizeClass(unref(formItemCls).e("content")) }, [
|
|
122
|
+
createElementVNode("div", { class: normalizeClass(unref(formItemCls).e("content")) }, [createVNode(ContentSlot, null, {
|
|
123
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
|
|
124
|
+
_: 3
|
|
125
|
+
})], 2),
|
|
106
126
|
createCommentVNode(" 只有表单控件处于非只读状态时,才显示错误提示 "),
|
|
107
127
|
!unref(readonly) && !unref(formProps)?.noTips ? (openBlock(), createElementBlock("section", {
|
|
108
128
|
key: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-item.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', !!rules?.required)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\" :show-delay=\"500\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <slot></slot>\n </div>\n\n <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->\n <section :class=\"cls.e('error')\" v-if=\"!readonly && !formProps?.noTips\">\n <transition name=\"form-item-tips\" mode=\"out-in\">\n <span :class=\"cls.e('error-text')\" v-if=\"!!errorTip\">\n {{ errorTip }}\n </span>\n </transition>\n </section>\n </section>\n </u-grid-item>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { useConfig, useFallbackProps } from '@veltra/compositions'\nimport { bem, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {
|
|
1
|
+
{"version":3,"file":"form-item.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', !!rules?.required)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\" :show-delay=\"500\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <ContentSlot>\n <slot></slot>\n </ContentSlot>\n </div>\n\n <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->\n <section :class=\"cls.e('error')\" v-if=\"!readonly && !formProps?.noTips\">\n <transition name=\"form-item-tips\" mode=\"out-in\">\n <span :class=\"cls.e('error-text')\" v-if=\"!!errorTip\">\n {{ errorTip }}\n </span>\n </transition>\n </section>\n </section>\n </u-grid-item>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { useConfig, useFallbackProps } from '@veltra/compositions'\nimport { bem, extractNormalVNodes, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n type CSSProperties,\n type SetupContext,\n type VNode,\n cloneVNode,\n computed,\n onBeforeUnmount,\n shallowRef,\n watch\n} from 'vue'\n\nimport type { FormItemEmits, FormItemProps, ComponentSize } from '../../types'\nimport { UGridItem } from '../grid'\nimport { UTip } from '../tip'\nimport { formItemCls as cls, defineField } from './helper'\nimport { validateField } from './validate'\n\ndefineOptions({ name: 'UFormItem' })\n\nconst props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined })\n\nconst emit = defineEmits<FormItemEmits>()\n\ndefineSlots<{\n /** 标签插槽 */\n label?: () => any\n default?: () => any\n}>()\n\n/** 表单组件上下文 */\nconst {\n formProps,\n registerField,\n unregisterField,\n shouldValidate,\n handleFieldUpdate,\n handleFieldChange\n} = injectFormContext()\n\nfunction wrapControlChange(node: VNode) {\n if (!node || typeof node.type === 'symbol') return node\n\n return cloneVNode(node, {\n onChange: (...args: any[]) => {\n emit('change', ...args)\n if (props.field) handleFieldChange?.(props.field, ...args)\n }\n })\n}\n\n/**\n * 承载默认插槽并拦截控件 change。\n * 必须经填充插槽接收内容(而非直接读 UFormItem 的 $slots):\n * 读父级 $slots 时组件自身没有更新路径,父级重渲染后动态 props(disabled、data 等)会冻结在首帧。\n */\nfunction ContentSlot(_: unknown, { slots }: SetupContext) {\n return extractNormalVNodes(slots.default?.() ?? []).map(wrapControlChange)\n}\n\nconst { config } = useConfig()\n\nconst { size, readonly } = useFallbackProps([formProps ?? {}, props], {\n size: 'default' as ComponentSize,\n readonly: false\n})\n\nconst errorTip = shallowRef<string>()\n/** 递增序号,丢弃过期的异步校验结果 */\nlet validateSeq = 0\n/** 最近一次 validate 的 Promise,供过期调用等待最新结果 */\nlet latestValidatePromise: Promise<boolean> = Promise.resolve(true)\n\nconst labelPosition = computed(() => props.labelPosition ?? formProps?.labelPosition ?? 'left')\n\nconst className = computed(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('error', !!errorTip.value),\n bem.is('label-top', labelPosition.value === 'top')\n ].join(' ')\n})\n\n/** label样式 */\nconst labelStyles = computed<CSSProperties>(() => {\n if (labelPosition.value === 'top') return {}\n return {\n width: withUnit(props.labelWidth ?? formProps?.labelWidth ?? config.form.labelWidth, 'px')\n }\n})\n\nconst fieldItem = defineField({\n clearValidate() {\n errorTip.value = ''\n },\n async validate() {\n if (!props.field || !formProps?.model || !props.rules || !shouldValidate?.()) return true\n\n const seq = ++validateSeq\n\n latestValidatePromise = validateField(formProps.model!, props.field!, props.rules!).then(\n (tip) => {\n if (seq !== validateSeq) return latestValidatePromise\n\n errorTip.value = tip\n return !errorTip.value\n }\n )\n return latestValidatePromise\n }\n})\n\nwatch(\n () => props.field,\n (field, oldField) => {\n if (oldField) {\n unregisterField?.(oldField)\n }\n if (field) {\n registerField?.(field, fieldItem)\n }\n },\n { immediate: true }\n)\n\nlet stopWatchFieldValue: (() => void) | undefined\n\nwatch(\n [() => formProps?.model, () => props.field],\n ([model, field]) => {\n stopWatchFieldValue?.()\n\n if (!field || !model) return\n\n stopWatchFieldValue = watch(\n () => o(model).get(field),\n (value) => {\n handleFieldUpdate?.(field, value)\n fieldItem.validate()\n }\n )\n },\n { immediate: true }\n)\n\nonBeforeUnmount(() => {\n props.field && unregisterField?.(props.field)\n})\n</script>\n"],"mappings":"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"}
|
package/dist/types/action.d.ts
CHANGED
|
@@ -3,7 +3,12 @@ import { ColorType, DeconstructValue } from "@veltra/utils";
|
|
|
3
3
|
//#region src/types/action.d.ts
|
|
4
4
|
/** 操作组件属性 */
|
|
5
5
|
interface ActionProps extends ButtonProps {
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* 是否需要确认:true 时点击只弹确认气泡(UPopConfirm),点气泡里的「确认」才触发 `run`
|
|
8
|
+
* @default false
|
|
9
|
+
* @description 不要在本项的 `run` 回调里再调 `messageConfirm`:`run` 触发时气泡已经确认过一次,
|
|
10
|
+
* 会变成两次确认。需要弹窗级确认时去掉 `needConfirm`,只在 `run` 里用 `messageConfirm`
|
|
11
|
+
*/
|
|
7
12
|
needConfirm?: boolean;
|
|
8
13
|
/**
|
|
9
14
|
* 是否始终位于下拉菜单中,无视 `max` 限制
|
|
@@ -99,6 +99,8 @@ interface BatchEditProps extends TableProps {
|
|
|
99
99
|
}
|
|
100
100
|
/** 批量编辑组件定义的事件 */
|
|
101
101
|
interface BatchEditEmits extends TableEmits {
|
|
102
|
+
/** 表单控件 change,透传自 UForm */
|
|
103
|
+
(e: 'field:change', field: string, ...args: any[]): void;
|
|
102
104
|
/** 更新数据 */
|
|
103
105
|
(e: 'update:data', value: Record<string, any>[]): void;
|
|
104
106
|
/** 点击底部「新增一行」 */
|
package/dist/types/button.d.ts
CHANGED
|
@@ -3,7 +3,11 @@ import { ColorType, ComponentProps, DeconstructValue } from "@veltra/utils";
|
|
|
3
3
|
//#region src/types/button.d.ts
|
|
4
4
|
/** 按钮类型 */
|
|
5
5
|
type ButtonType = ColorType;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* 按钮属性类型
|
|
8
|
+
* @description 组件不注入默认 `aria-label`:可访问名来自默认插槽文本,
|
|
9
|
+
* 或你自己传入的 `aria-label`(透传到根元素);只有图标的按钮必须自行传 `aria-label`
|
|
10
|
+
*/
|
|
7
11
|
interface ButtonProps extends ComponentProps {
|
|
8
12
|
/** 按钮类型 */
|
|
9
13
|
type?: ButtonType;
|
package/dist/types/drawer.d.ts
CHANGED
|
@@ -12,15 +12,18 @@ interface DrawerProps {
|
|
|
12
12
|
direction?: DrawerDirection;
|
|
13
13
|
/** 是否显示关闭按钮 */
|
|
14
14
|
showClose?: boolean;
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* 抽屉标题;传入时在内容区上方渲染标题栏
|
|
17
|
+
* @description 不传则不渲染标题栏,内容仍全部来自默认插槽
|
|
18
|
+
*/
|
|
16
19
|
title?: string;
|
|
17
20
|
}
|
|
18
21
|
/** 抽屉组件定义的事件 */
|
|
19
22
|
interface DrawerEmits {
|
|
20
23
|
(e: 'update:modelValue', value: boolean): void;
|
|
21
|
-
/**
|
|
24
|
+
/** 开始关闭时触发(点遮罩、点关闭按钮) */
|
|
22
25
|
(e: 'close'): void;
|
|
23
|
-
/**
|
|
26
|
+
/** 完全关闭后触发:抽屉与遮罩的退出动画结束、节点已移除时 */
|
|
24
27
|
(e: 'closed'): void;
|
|
25
28
|
}
|
|
26
29
|
/** 抽屉组件暴露的属性和方法(组件内部使用) */
|
|
@@ -8,7 +8,10 @@ interface FormItemProps extends FormComponentProps {
|
|
|
8
8
|
labelPosition?: 'top' | 'left';
|
|
9
9
|
}
|
|
10
10
|
/** 组件项组件定义的事件 */
|
|
11
|
-
interface FormItemEmits {
|
|
11
|
+
interface FormItemEmits {
|
|
12
|
+
/** 内部控件 change,参数与控件一致 */
|
|
13
|
+
(e: 'change', ...args: any[]): void;
|
|
14
|
+
}
|
|
12
15
|
/** 组件项组件暴露的属性和方法 */
|
|
13
16
|
interface FormItemExposed {}
|
|
14
17
|
//#endregion
|
package/dist/types/form.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ interface FormProps extends ComponentProps {
|
|
|
10
10
|
cols?: number;
|
|
11
11
|
/** 表单数据 */
|
|
12
12
|
model?: Record<string, any>;
|
|
13
|
+
/** 开启后,字段当前值与基准值不同时,在控件下方展示「变更前」 */
|
|
14
|
+
showModified?: boolean;
|
|
15
|
+
/** 「变更前」标签文案,默认「变更前:」 */
|
|
16
|
+
modifiedLabel?: string;
|
|
17
|
+
/** 变更前基准数据;未传时回退到 model 引用首次传入时的快照(与 reset 一致) */
|
|
18
|
+
initialModel?: Record<string, any>;
|
|
13
19
|
/** 表单项label宽度 */
|
|
14
20
|
labelWidth?: string | number;
|
|
15
21
|
/** 表单项 label 位置 */
|
|
@@ -22,7 +28,10 @@ interface FormProps extends ComponentProps {
|
|
|
22
28
|
disabled?: boolean;
|
|
23
29
|
}
|
|
24
30
|
interface FormEmits {
|
|
25
|
-
|
|
31
|
+
/** 控件 change 事件,仅用户操作触发 */
|
|
32
|
+
(e: 'field:change', field: string, ...args: any[]): void;
|
|
33
|
+
/** model 字段值更新,含编程写入 */
|
|
34
|
+
(e: 'field:update', field: string, value: any): void;
|
|
26
35
|
}
|
|
27
36
|
interface _FormExposed {
|
|
28
37
|
el: ShallowRef<HTMLElement | null | undefined>;
|
package/dist/types/layout.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ interface LayoutProps {
|
|
|
18
18
|
* const cols = ['200px', '1fr']
|
|
19
19
|
* ```
|
|
20
20
|
* [fr是什么?](https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex_value)
|
|
21
|
+
* @description 默认插槽的每个直接子元素按顺序占一列,用自定义组件包一层时该组件必须是单根元素;
|
|
22
|
+
* 子元素渲染出 0 个(`v-if` 为假)或多个根节点时会挤动后面的列,此时自行给子元素写 `grid-column`
|
|
21
23
|
*/
|
|
22
24
|
cols?: string[] | string;
|
|
23
25
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veltra/desktop",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.11",
|
|
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"
|
|
@@ -80,12 +80,12 @@
|
|
|
80
80
|
"peerDependencies": {
|
|
81
81
|
"@cat-kit/core": ">=1.2.1",
|
|
82
82
|
"@cat-kit/fe": ">=1.2.1",
|
|
83
|
-
"@veltra/compositions": "^1.7.
|
|
84
|
-
"@veltra/directives": "^1.7.
|
|
83
|
+
"@veltra/compositions": "^1.7.11",
|
|
84
|
+
"@veltra/directives": "^1.7.11",
|
|
85
85
|
"@veltra/icons": "^1.5.0",
|
|
86
|
-
"@veltra/sheet-core": "^2.5.
|
|
87
|
-
"@veltra/styles": "^1.7.
|
|
88
|
-
"@veltra/utils": "^1.7.
|
|
86
|
+
"@veltra/sheet-core": "^2.5.6",
|
|
87
|
+
"@veltra/styles": "^1.7.11",
|
|
88
|
+
"@veltra/utils": "^1.7.11",
|
|
89
89
|
"vue": ">=3.5.42"
|
|
90
90
|
},
|
|
91
91
|
"peerDependenciesMeta": {
|
|
@@ -30,6 +30,8 @@ function mountBatchEdit() {
|
|
|
30
30
|
apiMethod: undefined as string | undefined
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
+
const fieldChanges: { field: string; args: unknown[] }[] = []
|
|
34
|
+
|
|
33
35
|
const app = createApp({
|
|
34
36
|
render() {
|
|
35
37
|
return h(
|
|
@@ -39,6 +41,9 @@ function mountBatchEdit() {
|
|
|
39
41
|
'onUpdate:data': (value: any[]) => {
|
|
40
42
|
data.value = value
|
|
41
43
|
},
|
|
44
|
+
'onField:change': (field: string, ...args: unknown[]) => {
|
|
45
|
+
fieldChanges.push({ field, args })
|
|
46
|
+
},
|
|
42
47
|
columns,
|
|
43
48
|
model,
|
|
44
49
|
rowKey: 'id',
|
|
@@ -89,6 +94,7 @@ function mountBatchEdit() {
|
|
|
89
94
|
host,
|
|
90
95
|
data,
|
|
91
96
|
model,
|
|
97
|
+
fieldChanges,
|
|
92
98
|
unmount() {
|
|
93
99
|
app.unmount()
|
|
94
100
|
host.remove()
|
|
@@ -196,4 +202,136 @@ describe('UBatchEdit quick-edit 切换编辑行', () => {
|
|
|
196
202
|
|
|
197
203
|
unmount()
|
|
198
204
|
})
|
|
205
|
+
|
|
206
|
+
it('切换行回显不触发 field:change', async () => {
|
|
207
|
+
const { host, fieldChanges, unmount } = mountBatchEdit()
|
|
208
|
+
|
|
209
|
+
fieldChanges.length = 0
|
|
210
|
+
await clickRow(host, 0)
|
|
211
|
+
await clickRow(host, 1)
|
|
212
|
+
|
|
213
|
+
expect(fieldChanges).toHaveLength(0)
|
|
214
|
+
|
|
215
|
+
unmount()
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('用户变更 Select 触发 field:change', async () => {
|
|
219
|
+
const { host, fieldChanges, unmount } = mountBatchEdit()
|
|
220
|
+
|
|
221
|
+
await clickRow(host, 0)
|
|
222
|
+
fieldChanges.length = 0
|
|
223
|
+
|
|
224
|
+
const input = host.querySelector<HTMLInputElement>('.u-select input')
|
|
225
|
+
expect(input).toBeTruthy()
|
|
226
|
+
input!.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
227
|
+
await nextTick()
|
|
228
|
+
await nextTick()
|
|
229
|
+
|
|
230
|
+
const option = [...document.body.querySelectorAll<HTMLElement>('.u-select__option')].find(
|
|
231
|
+
(el) => el.textContent === '接口'
|
|
232
|
+
)
|
|
233
|
+
expect(option).toBeTruthy()
|
|
234
|
+
option!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
235
|
+
await nextTick()
|
|
236
|
+
await nextTick()
|
|
237
|
+
|
|
238
|
+
expect(fieldChanges).toHaveLength(1)
|
|
239
|
+
expect(fieldChanges[0]).toEqual({ field: 'behavior', args: [{ label: '接口', value: 'api' }] })
|
|
240
|
+
|
|
241
|
+
unmount()
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('field:change 联动改其他字段不循环触发', async () => {
|
|
245
|
+
const host = document.createElement('div')
|
|
246
|
+
document.body.appendChild(host)
|
|
247
|
+
|
|
248
|
+
const columns = defineTableColumns([
|
|
249
|
+
{ key: 'label', name: '名称' },
|
|
250
|
+
{ key: 'behavior', name: '行为' }
|
|
251
|
+
])
|
|
252
|
+
|
|
253
|
+
const data = ref([
|
|
254
|
+
{ id: '1', label: '存草稿', behavior: 'common-resource', submitType: 'DRAFT' },
|
|
255
|
+
{ id: '2', label: '调接口', behavior: 'api', submitType: 'SUBMIT', apiMethod: 'POST' }
|
|
256
|
+
])
|
|
257
|
+
|
|
258
|
+
const model = reactive({
|
|
259
|
+
id: '',
|
|
260
|
+
label: '',
|
|
261
|
+
behavior: 'event',
|
|
262
|
+
submitType: undefined as string | undefined,
|
|
263
|
+
apiMethod: undefined as string | undefined
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const fieldChanges: string[] = []
|
|
267
|
+
|
|
268
|
+
const app = createApp({
|
|
269
|
+
render() {
|
|
270
|
+
return h(
|
|
271
|
+
UBatchEdit,
|
|
272
|
+
{
|
|
273
|
+
data: data.value,
|
|
274
|
+
'onUpdate:data': (value: any[]) => {
|
|
275
|
+
data.value = value
|
|
276
|
+
},
|
|
277
|
+
'onField:change': (field: string) => {
|
|
278
|
+
fieldChanges.push(field)
|
|
279
|
+
if (field === 'behavior') {
|
|
280
|
+
model.submitType = undefined
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
columns,
|
|
284
|
+
model,
|
|
285
|
+
rowKey: 'id',
|
|
286
|
+
quickEdit: true
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
form: () => [
|
|
290
|
+
h(UInput, { field: 'label', label: '名称' }),
|
|
291
|
+
h(USelect, {
|
|
292
|
+
field: 'behavior',
|
|
293
|
+
label: '行为',
|
|
294
|
+
options: [
|
|
295
|
+
{ label: '事件', value: 'event' },
|
|
296
|
+
{ label: '通用资源', value: 'common-resource' },
|
|
297
|
+
{ label: '接口', value: 'api' }
|
|
298
|
+
]
|
|
299
|
+
}),
|
|
300
|
+
h(USelect, {
|
|
301
|
+
field: 'submitType',
|
|
302
|
+
label: '提交类型',
|
|
303
|
+
options: [
|
|
304
|
+
{ label: 'DRAFT', value: 'DRAFT' },
|
|
305
|
+
{ label: 'SUBMIT', value: 'SUBMIT' }
|
|
306
|
+
]
|
|
307
|
+
})
|
|
308
|
+
]
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
app.mount(host)
|
|
315
|
+
|
|
316
|
+
await clickRow(host, 0)
|
|
317
|
+
fieldChanges.length = 0
|
|
318
|
+
|
|
319
|
+
const input = host.querySelector<HTMLInputElement>('.u-select input')
|
|
320
|
+
input!.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
321
|
+
await nextTick()
|
|
322
|
+
await nextTick()
|
|
323
|
+
|
|
324
|
+
const option = [...document.body.querySelectorAll<HTMLElement>('.u-select__option')].find(
|
|
325
|
+
(el) => el.textContent === '接口'
|
|
326
|
+
)
|
|
327
|
+
option!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
328
|
+
await nextTick()
|
|
329
|
+
await nextTick()
|
|
330
|
+
|
|
331
|
+
expect(fieldChanges).toEqual(['behavior'])
|
|
332
|
+
expect(model.submitType).toBeUndefined()
|
|
333
|
+
|
|
334
|
+
app.unmount()
|
|
335
|
+
host.remove()
|
|
336
|
+
})
|
|
199
337
|
})
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
:model="props.model"
|
|
29
29
|
:readonly="props.readonly"
|
|
30
30
|
:label-width="props.labelWidth"
|
|
31
|
-
@field:
|
|
31
|
+
@field:update="handleFieldUpdate"
|
|
32
|
+
@field:change="handleFormFieldChange"
|
|
32
33
|
>
|
|
33
34
|
<slot
|
|
34
35
|
v-bind="{
|
|
@@ -91,8 +92,17 @@ defineOptions({ name: 'UBatchEditForm' })
|
|
|
91
92
|
|
|
92
93
|
const batchEditCtx = inject(BatchEditDIKey)!
|
|
93
94
|
|
|
94
|
-
const {
|
|
95
|
-
|
|
95
|
+
const {
|
|
96
|
+
cls,
|
|
97
|
+
props,
|
|
98
|
+
state,
|
|
99
|
+
emit,
|
|
100
|
+
handleClose,
|
|
101
|
+
handleSave,
|
|
102
|
+
staticFeatures,
|
|
103
|
+
dynamicFeatures,
|
|
104
|
+
syncing
|
|
105
|
+
} = batchEditCtx
|
|
96
106
|
|
|
97
107
|
const focused = toRef(batchEditCtx, 'focused')
|
|
98
108
|
|
|
@@ -105,7 +115,7 @@ defineExpose<_FormExposed>({
|
|
|
105
115
|
reset: () => formComponentRef.value?.reset()
|
|
106
116
|
})
|
|
107
117
|
|
|
108
|
-
function
|
|
118
|
+
function handleFieldUpdate(field: string, value: any) {
|
|
109
119
|
// 编程方式重置/回显期间不写回行数据,避免默认值污染源数据
|
|
110
120
|
if (syncing.value) return
|
|
111
121
|
|
|
@@ -114,6 +124,10 @@ function handleFieldChange(field: string, value: any) {
|
|
|
114
124
|
}
|
|
115
125
|
}
|
|
116
126
|
|
|
127
|
+
function handleFormFieldChange(field: string, ...args: any[]) {
|
|
128
|
+
emit('field:change', field, ...args)
|
|
129
|
+
}
|
|
130
|
+
|
|
117
131
|
const creatable = computed(() => {
|
|
118
132
|
if (state.formActionType !== 'create') return false
|
|
119
133
|
return staticFeatures.value.has('create') || dynamicFeatures.value.create?.(state.row)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { createApp, h } from 'vue'
|
|
3
|
+
|
|
4
|
+
import UButton from '../button.vue'
|
|
5
|
+
|
|
6
|
+
function mountButton(props: Record<string, unknown> = {}, slot = '新建用户') {
|
|
7
|
+
const host = document.createElement('div')
|
|
8
|
+
document.body.appendChild(host)
|
|
9
|
+
|
|
10
|
+
const app = createApp({ render: () => h(UButton, props, () => slot) })
|
|
11
|
+
app.mount(host)
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
host,
|
|
15
|
+
button: host.querySelector('button')!,
|
|
16
|
+
unmount() {
|
|
17
|
+
app.unmount()
|
|
18
|
+
host.remove()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('UButton 原生属性与可访问名', () => {
|
|
24
|
+
it('不注入默认 aria-label:可访问名来自默认插槽文本', () => {
|
|
25
|
+
const { button, unmount } = mountButton()
|
|
26
|
+
|
|
27
|
+
expect(button.getAttribute('aria-label')).toBeNull()
|
|
28
|
+
expect(button.textContent?.trim()).toBe('新建用户')
|
|
29
|
+
|
|
30
|
+
unmount()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('自己传的 aria-label 透传到根元素,图标按钮仍可命名', () => {
|
|
34
|
+
const { button, unmount } = mountButton({ 'aria-label': '编辑' }, '')
|
|
35
|
+
|
|
36
|
+
expect(button.getAttribute('aria-label')).toBe('编辑')
|
|
37
|
+
|
|
38
|
+
unmount()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('原生 type 固定为 button,放进 form 里不会触发提交', () => {
|
|
42
|
+
const { button, unmount } = mountButton()
|
|
43
|
+
|
|
44
|
+
expect(button.getAttribute('type')).toBe('button')
|
|
45
|
+
|
|
46
|
+
unmount()
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<button
|
|
3
|
-
:class="classList"
|
|
4
|
-
type="button"
|
|
5
|
-
aria-label="button"
|
|
6
|
-
ref="buttonRef"
|
|
7
|
-
v-ripple="ripple"
|
|
8
|
-
@click="handleClick"
|
|
9
|
-
>
|
|
2
|
+
<button :class="classList" type="button" ref="buttonRef" v-ripple="ripple" @click="handleClick">
|
|
10
3
|
<!-- 加载图标 -->
|
|
11
4
|
<u-icon v-if="loading" :class="[bem.is('loading')]" :size="iconSize">
|
|
12
5
|
<component :is="loadingIcon" />
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { createApp, h, nextTick, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import UDrawer from '../drawer.vue'
|
|
5
|
+
|
|
6
|
+
function mountDrawer(
|
|
7
|
+
props: Record<string, unknown> = {},
|
|
8
|
+
handlers: { onClose?: () => void; onClosed?: () => void } = {}
|
|
9
|
+
) {
|
|
10
|
+
const host = document.createElement('div')
|
|
11
|
+
document.body.appendChild(host)
|
|
12
|
+
|
|
13
|
+
const model = ref(false)
|
|
14
|
+
const closedCount = ref(0)
|
|
15
|
+
const app = createApp({
|
|
16
|
+
render: () =>
|
|
17
|
+
h(
|
|
18
|
+
UDrawer,
|
|
19
|
+
{
|
|
20
|
+
...props,
|
|
21
|
+
modelValue: model.value,
|
|
22
|
+
'onUpdate:modelValue': (value: boolean) => {
|
|
23
|
+
model.value = value
|
|
24
|
+
},
|
|
25
|
+
onClose: () => handlers.onClose?.(),
|
|
26
|
+
onClosed: () => {
|
|
27
|
+
closedCount.value += 1
|
|
28
|
+
handlers.onClosed?.()
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
() => '抽屉内容'
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
app.mount(host)
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
host,
|
|
38
|
+
model,
|
|
39
|
+
closedCount,
|
|
40
|
+
panel: () => document.body.querySelector<HTMLElement>('.u-drawer'),
|
|
41
|
+
overlay: () => document.body.querySelector<HTMLElement>('.u-drawer-overlay'),
|
|
42
|
+
unmount() {
|
|
43
|
+
app.unmount()
|
|
44
|
+
host.remove()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function openDrawer(ctx: ReturnType<typeof mountDrawer>) {
|
|
50
|
+
ctx.model.value = true
|
|
51
|
+
await vi.waitFor(() => {
|
|
52
|
+
expect(ctx.panel()).toBeTruthy()
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('UDrawer title', () => {
|
|
57
|
+
it('传入 title 时渲染标题栏', async () => {
|
|
58
|
+
const ctx = mountDrawer({ title: '用户详情' })
|
|
59
|
+
await openDrawer(ctx)
|
|
60
|
+
|
|
61
|
+
expect(ctx.panel()!.querySelector('.u-drawer__title')?.textContent).toBe('用户详情')
|
|
62
|
+
|
|
63
|
+
ctx.unmount()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('不传 title 时不渲染标题栏', async () => {
|
|
67
|
+
const ctx = mountDrawer()
|
|
68
|
+
await openDrawer(ctx)
|
|
69
|
+
|
|
70
|
+
expect(ctx.panel()!.querySelector('.u-drawer__header')).toBeNull()
|
|
71
|
+
|
|
72
|
+
ctx.unmount()
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('UDrawer closed', () => {
|
|
77
|
+
it('关闭动画结束后触发 closed,且晚于 close', async () => {
|
|
78
|
+
const order: string[] = []
|
|
79
|
+
const ctx = mountDrawer(
|
|
80
|
+
{ showClose: true },
|
|
81
|
+
{ onClose: () => order.push('close'), onClosed: () => order.push('closed') }
|
|
82
|
+
)
|
|
83
|
+
await openDrawer(ctx)
|
|
84
|
+
|
|
85
|
+
ctx.panel()!.querySelector<HTMLElement>('.u-drawer__close')!.click()
|
|
86
|
+
await vi.waitFor(() => {
|
|
87
|
+
expect(order).toContain('closed')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
expect(order).toEqual(['close', 'closed'])
|
|
91
|
+
// 完全关闭后遮罩与抽屉都已移除
|
|
92
|
+
expect(ctx.overlay()).toBeNull()
|
|
93
|
+
|
|
94
|
+
ctx.unmount()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('点遮罩关闭也触发 closed', async () => {
|
|
98
|
+
const ctx = mountDrawer()
|
|
99
|
+
await openDrawer(ctx)
|
|
100
|
+
|
|
101
|
+
ctx.overlay()!.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
102
|
+
await vi.waitFor(() => {
|
|
103
|
+
expect(ctx.closedCount.value).toBe(1)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
ctx.unmount()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('从未打开时不触发 closed', async () => {
|
|
110
|
+
const ctx = mountDrawer()
|
|
111
|
+
await nextTick()
|
|
112
|
+
await nextTick()
|
|
113
|
+
|
|
114
|
+
expect(ctx.closedCount.value).toBe(0)
|
|
115
|
+
|
|
116
|
+
ctx.unmount()
|
|
117
|
+
})
|
|
118
|
+
})
|