diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3b7f8187..9fd86547 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,14 +1,14 @@ # default onwer -* anncwb@126.com vince292007@gmail.com +* anncwb@126.com vince292007@gmail.com netfan@foxmail.com # vben core onwer -/.github/ anncwb@126.com vince292007@gmail.com -/.vscode/ anncwb@126.com vince292007@gmail.com -/packages/ anncwb@126.com vince292007@gmail.com -/packages/@core/ anncwb@126.com vince292007@gmail.com -/internal/ anncwb@126.com vince292007@gmail.com -/scripts/ anncwb@126.com vince292007@gmail.com +/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com +/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com +/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com +/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com +/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com +/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com # vben team onwer -apps/ anncwb@126.com vince292007@gmail.com @vbenjs/team-v5 -docs/ anncwb@126.com vince292007@gmail.com @vbenjs/team-v5 +apps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 +docs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 diff --git a/apps/web-antd/src/adapter/component/index.ts b/apps/web-antd/src/adapter/component/index.ts index 9f8a059f..e882435d 100644 --- a/apps/web-antd/src/adapter/component/index.ts +++ b/apps/web-antd/src/adapter/component/index.ts @@ -108,7 +108,13 @@ async function initComponentAdapter() { return h(Button, { ...props, attrs, type: 'default' }, slots); }, Divider, - IconPicker, + IconPicker: (props, { attrs, slots }) => { + return h( + IconPicker, + { iconSlot: 'addonAfter', inputComponent: Input, ...props, ...attrs }, + slots, + ); + }, Input: withDefaultPlaceholder(Input, 'input'), InputNumber: withDefaultPlaceholder(InputNumber, 'input'), InputPassword: withDefaultPlaceholder(InputPassword, 'input'), diff --git a/apps/web-ele/src/adapter/component/index.ts b/apps/web-ele/src/adapter/component/index.ts index e6d677be..451fef4a 100644 --- a/apps/web-ele/src/adapter/component/index.ts +++ b/apps/web-ele/src/adapter/component/index.ts @@ -4,6 +4,7 @@ */ import type { BaseFormComponentType } from '@vben/common-ui'; +import type { Recordable } from '@vben/types'; import type { Component, SetupContext } from 'vue'; import { h } from 'vue'; @@ -88,15 +89,67 @@ async function initComponentAdapter() { return h(ElButton, { ...props, attrs, type: 'primary' }, slots); }, Divider: ElDivider, - IconPicker, + IconPicker: (props, { attrs, slots }) => { + return h( + IconPicker, + { + iconSlot: 'append', + modelValueProp: 'model-value', + inputComponent: ElInput, + ...props, + ...attrs, + }, + slots, + ); + }, Input: withDefaultPlaceholder(ElInput, 'input'), InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'), RadioGroup: ElRadioGroup, Select: withDefaultPlaceholder(ElSelect, 'select'), Space: ElSpace, Switch: ElSwitch, - TimePicker: ElTimePicker, - DatePicker: ElDatePicker, + TimePicker: (props, { attrs, slots }) => { + const { name, id, isRange } = props; + const extraProps: Recordable = {}; + if (isRange) { + if (name && !Array.isArray(name)) { + extraProps.name = [name, `${name}_end`]; + } + if (id && !Array.isArray(id)) { + extraProps.id = [id, `${id}_end`]; + } + } + return h( + ElTimePicker, + { + ...props, + ...attrs, + ...extraProps, + }, + slots, + ); + }, + DatePicker: (props, { attrs, slots }) => { + const { name, id, type } = props; + const extraProps: Recordable = {}; + if (type && type.includes('range')) { + if (name && !Array.isArray(name)) { + extraProps.name = [name, `${name}_end`]; + } + if (id && !Array.isArray(id)) { + extraProps.id = [id, `${id}_end`]; + } + } + return h( + ElDatePicker, + { + ...props, + ...attrs, + ...extraProps, + }, + slots, + ); + }, TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'), Upload: ElUpload, }; diff --git a/apps/web-naive/src/adapter/component/index.ts b/apps/web-naive/src/adapter/component/index.ts index a007d52d..6fa96510 100644 --- a/apps/web-naive/src/adapter/component/index.ts +++ b/apps/web-naive/src/adapter/component/index.ts @@ -89,7 +89,13 @@ async function initComponentAdapter() { return h(NButton, { ...props, attrs, type: 'primary' }, slots); }, Divider: NDivider, - IconPicker, + IconPicker: (props, { attrs, slots }) => { + return h( + IconPicker, + { iconSlot: 'suffix', inputComponent: NInput, ...props, ...attrs }, + slots, + ); + }, Input: withDefaultPlaceholder(NInput, 'input'), InputNumber: withDefaultPlaceholder(NInputNumber, 'input'), RadioGroup: NRadioGroup, diff --git a/docs/src/components/common-ui/vben-form.md b/docs/src/components/common-ui/vben-form.md index ea3c53a5..ce27e1f2 100644 --- a/docs/src/components/common-ui/vben-form.md +++ b/docs/src/components/common-ui/vben-form.md @@ -316,6 +316,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单 | commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - | | schema | 表单项的每一项配置 | `FormSchema` | - | | submitOnEnter | 按下回车健时提交表单 | `boolean` | false | +| submitOnChange | 字段值改变时提交表单 | `boolean` | false | ### TS 类型说明 diff --git a/docs/src/components/layout-ui/page.md b/docs/src/components/layout-ui/page.md index 8a33775c..29fbdd40 100644 --- a/docs/src/components/layout-ui/page.md +++ b/docs/src/components/layout-ui/page.md @@ -18,15 +18,14 @@ outline: deep ### Props -| 属性名 | 描述 | 类型 | 默认值 | -| --- | --- | --- | --- | -| title | 页面标题 | `string\|slot` | - | -| description | 页面描述(标题下的内容) | `string\|slot` | - | -| contentClass | 内容区域的class | `string` | - | -| headerClass | 头部区域的class | `string` | - | -| footerClass | 底部区域的class | `string` | - | -| autoContentHeight | 自动调整内容区域的高度 | `boolean` | `false` | -| fixedHeader | 固定头部在页面内容区域顶部,在滚动时保持可见 | `boolean` | `false` | +| 属性名 | 描述 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| title | 页面标题 | `string\|slot` | - | - | +| description | 页面描述(标题下的内容) | `string\|slot` | - | - | +| contentClass | 内容区域的class | `string` | - | - | +| headerClass | 头部区域的class | `string` | - | - | +| footerClass | 底部区域的class | `string` | - | - | +| autoContentHeight | 自动调整内容区域的高度 | `boolean` | `false` | - | ::: tip 注意 diff --git a/docs/src/demos/vben-vxe-table/form/index.vue b/docs/src/demos/vben-vxe-table/form/index.vue index a5e8a547..b5be6c65 100644 --- a/docs/src/demos/vben-vxe-table/form/index.vue +++ b/docs/src/demos/vben-vxe-table/form/index.vue @@ -76,6 +76,8 @@ const formOptions: VbenFormProps = { submitButtonOptions: { content: '查询', }, + // 是否在字段值改变时提交表单 + submitOnChange: false, // 按下回车时是否提交表单 submitOnEnter: false, }; diff --git a/packages/@core/base/shared/src/utils/date.ts b/packages/@core/base/shared/src/utils/date.ts index 522e9946..3736b9ad 100644 --- a/packages/@core/base/shared/src/utils/date.ts +++ b/packages/@core/base/shared/src/utils/date.ts @@ -16,3 +16,11 @@ export function formatDate(time: number | string, format = 'YYYY-MM-DD') { export function formatDateTime(time: number | string) { return formatDate(time, 'YYYY-MM-DD HH:mm:ss'); } + +export function isDate(value: any): value is Date { + return value instanceof Date; +} + +export function isDayjsObject(value: any): value is dayjs.Dayjs { + return dayjs.isDayjs(value); +} diff --git a/packages/@core/ui-kit/form-ui/src/form-api.ts b/packages/@core/ui-kit/form-ui/src/form-api.ts index 08318f3c..585afaff 100644 --- a/packages/@core/ui-kit/form-ui/src/form-api.ts +++ b/packages/@core/ui-kit/form-ui/src/form-api.ts @@ -14,6 +14,8 @@ import { Store } from '@vben-core/shared/store'; import { bindMethods, createMerge, + isDate, + isDayjsObject, isFunction, isObject, mergeWithArrayOverride, @@ -36,6 +38,7 @@ function getDefaultState(): VbenFormProps { showCollapseButton: false, showDefaultActions: true, submitButtonOptions: {}, + submitOnChange: false, submitOnEnter: false, wrapperClass: 'grid-cols-1', }; @@ -251,10 +254,19 @@ export class FormApi { return; } + /** + * 合并算法有待改进,目前的算法不支持object类型的值。 + * antd的日期时间相关组件的值类型为dayjs对象 + * element-plus的日期时间相关组件的值类型可能为Date对象 + * 以上两种类型需要排除深度合并 + */ const fieldMergeFn = createMerge((obj, key, value) => { if (key in obj) { obj[key] = - !Array.isArray(obj[key]) && isObject(obj[key]) + !Array.isArray(obj[key]) && + isObject(obj[key]) && + !isDayjsObject(obj[key]) && + !isDate(obj[key]) ? fieldMergeFn(obj[key], value) : value; } diff --git a/packages/@core/ui-kit/form-ui/src/types.ts b/packages/@core/ui-kit/form-ui/src/types.ts index 5855ccc6..30daaee2 100644 --- a/packages/@core/ui-kit/form-ui/src/types.ts +++ b/packages/@core/ui-kit/form-ui/src/types.ts @@ -342,6 +342,12 @@ export interface VbenFormProps< */ submitButtonOptions?: ActionButtonOptions; + /** + * 是否在字段值改变时提交表单 + * @default false + */ + submitOnChange?: boolean; + /** * 是否在回车时提交表单 * @default false diff --git a/packages/@core/ui-kit/form-ui/src/vben-use-form.vue b/packages/@core/ui-kit/form-ui/src/vben-use-form.vue index 0401d48e..a1395328 100644 --- a/packages/@core/ui-kit/form-ui/src/vben-use-form.vue +++ b/packages/@core/ui-kit/form-ui/src/vben-use-form.vue @@ -6,7 +6,9 @@ import type { ExtendedFormApi, VbenFormProps } from './types'; import { useForwardPriorityValues } from '@vben-core/composables'; // import { isFunction } from '@vben-core/shared/utils'; -import { useTemplateRef } from 'vue'; +import { toRaw, useTemplateRef, watch } from 'vue'; + +import { useDebounceFn } from '@vueuse/core'; import FormActions from './components/form-actions.vue'; import { @@ -56,6 +58,17 @@ function handleKeyDownEnter(event: KeyboardEvent) { formActionsRef.value?.handleSubmit?.(); } + +watch( + () => form.values, + useDebounceFn(() => { + (props.handleValuesChange ?? state.value.handleValuesChange)?.( + toRaw(form.values), + ); + state.value.submitOnChange && props.formApi?.submitForm(); + }, 300), + { deep: true }, +);