FormDialog
弹窗表单,主要用在简单的事件打开表单场景
注意
该组件经过重构,完全摒弃了通过id传递上下文的方式,使用时请注意函数入参的改动。现在通过Vue中JSX的插槽写法实现类似的功能。
提示
使用函数式组件时可以通过解构的方式快速拿到form,具体请参考template案例。
Markup Schema 案例
<script setup lang="tsx">
import { FormDialog, FormItem, FormLayout, Input } from '@silver-formily/element-plus'
import { createSchemaField } from '@silver-formily/vue'
import { ElButton } from 'element-plus'
const { SchemaField, SchemaStringField } = createSchemaField({
components: {
FormItem,
Input,
},
})
// 弹框表单组件
const DialogForm = {
props: ['form'],
render() {
return (
<FormLayout labelCol={6} wrapperCol={10}>
<SchemaField>
<SchemaStringField
name="aaa"
required
title="输入框1"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaStringField
name="bbb"
required
title="输入框2"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaStringField
name="ccc"
required
title="输入框3"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaStringField
name="ddd"
required
title="输入框4"
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
</FormLayout>
)
},
}
function handleOpen() {
FormDialog('弹框表单', DialogForm)
.forOpen((payload, next) => {
setTimeout(() => {
next({
initialValues: {
aaa: '123',
},
})
}, 1000)
})
.forConfirm((payload, next) => {
setTimeout(() => {
console.log(payload)
next(payload)
}, 1000)
})
.forCancel((payload, next) => {
setTimeout(() => {
console.log(payload)
next(payload)
}, 1000)
})
.open()
.then(console.log)
.catch(console.error)
}
</script>
<template>
<ElButton @click="handleOpen">
点击打开表单
</ElButton>
</template>JSON Schema 案例
<script setup lang="tsx">
import { FormDialog, FormItem, FormLayout, Input } from '@silver-formily/element-plus'
import { createSchemaField } from '@silver-formily/vue'
import { ElButton } from 'element-plus'
const { SchemaField } = createSchemaField({
components: {
FormItem,
Input,
},
})
// 弹框表单组件
const DialogForm = {
data() {
const schema = {
type: 'object',
properties: {
aaa: {
'type': 'string',
'title': '输入框1',
'required': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
bbb: {
'type': 'string',
'title': '输入框2',
'required': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
ccc: {
'type': 'string',
'title': '输入框3',
'required': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
ddd: {
'type': 'string',
'title': '输入框4',
'required': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
}
return {
schema,
}
},
render() {
return (
<FormLayout labelCol={6} wrapperCol={10}>
<SchemaField schema={this.schema} />
</FormLayout>
)
},
}
function handleOpen() {
FormDialog('弹框表单', DialogForm)
.forOpen((payload, next) => {
setTimeout(() => {
next({
initialValues: {
aaa: '123',
},
})
}, 1000)
})
.forConfirm((payload, next) => {
setTimeout(() => {
console.log(payload)
next(payload)
}, 1000)
})
.forCancel((payload, next) => {
setTimeout(() => {
console.log(payload)
next(payload)
}, 1000)
})
.open()
.then(console.log)
.catch(console.error)
}
</script>
<template>
<ElButton @click="handleOpen">
点击打开表单
</ElButton>
</template>Template 案例
<script setup lang="tsx">
import { FormDialog, FormItem, FormLayout, Input } from '@silver-formily/element-plus'
import { Field } from '@silver-formily/vue'
import { ElButton } from 'element-plus'
function handleOpen() {
FormDialog('弹框表单', ({ form }) => {
console.log('form', form)
return (
<FormLayout labelCol={6} wrapperCol={10}>
<Field
name="aaa"
required
title="输入框1"
decorator={[FormItem]}
component={[Input]}
/>
<Field
name="bbb"
required
title="输入框2"
decorator={[FormItem]}
component={[Input]}
/>
<Field
name="ccc"
required
title="输入框3"
decorator={[FormItem]}
component={[Input]}
/>
<Field
name="ddd"
required
title="输入框4"
decorator={[FormItem]}
component={[Input]}
/>
</FormLayout>
)
})
.forOpen((payload, next) => {
setTimeout(() => {
next({
initialValues: {
aaa: '123',
},
})
}, 1000)
})
.forConfirm((payload, next) => {
setTimeout(() => {
console.log(payload)
next(payload)
}, 1000)
})
.forCancel((payload, next) => {
setTimeout(() => {
console.log(payload)
next(payload)
}, 1000)
})
.open()
.then(console.log)
.catch(console.error)
}
</script>
<template>
<ElButton @click="handleOpen">
点击打开表单
</ElButton>
</template>Template 插槽案例
<script setup lang="tsx">
import { FormDialog, FormItem, FormLayout, Input } from '@silver-formily/element-plus'
import { Field } from '@silver-formily/vue'
import { ElButton } from 'element-plus'
function handleOpen() {
FormDialog('弹框表单', {
header: ({ reject }) => (
<div>
<ElButton onClick={() => reject()}>关闭</ElButton>
<span>这是标题</span>
</div>
),
default: () => (
<FormLayout labelCol={6} wrapperCol={10}>
<Field
name="aaa"
required
title="输入框1"
decorator={[FormItem]}
component={[Input]}
/>
<Field
name="bbb"
required
title="输入框2"
decorator={[FormItem]}
component={[Input]}
/>
<Field
name="ccc"
required
title="输入框3"
decorator={[FormItem]}
component={[Input]}
/>
<Field
name="ddd"
required
title="输入框4"
decorator={[FormItem]}
component={[Input]}
/>
</FormLayout>
),
footer: ({ form, resolve, reject }) => {
return [
<ElButton
onClick={() => reject()}
>
取消
</ElButton>,
<ElButton loading={form.submitting} onClick={() => resolve('extra')}>extra</ElButton>,
<ElButton loading={form.submitting} onClick={() => resolve('saveDraft')}>保存草稿</ElButton>,
<ElButton
type="primary"
loading={form.submitting}
onClick={() => resolve()}
>
确定
</ElButton>,
]
},
}, ['extra', 'saveDraft'])
.forOpen((payload, next) => {
next({
initialValues: {
aaa: '123',
},
})
})
.forConfirm((payload, next) => {
setTimeout(() => {
next(payload)
}, 1000)
})
.forExtra((payload, next) => {
setTimeout(() => {
console.log('extra')
next(payload)
}, 1000)
})
.forSaveDraft((payload, next) => {
setTimeout(() => {
console.log('saveDraft')
next(payload)
}, 1000)
})
.forCancel((payload, next) => {
setTimeout(() => {
next(payload)
}, 1000)
})
.open()
.then(console.log)
.catch(console.error)
}
</script>
<template>
<ElButton @click="handleOpen">
点击打开表单
</ElButton>
</template>回车提交配置
FormDialog 默认会在输入框激活时响应键盘回车并触发 resolve。若需要关闭该行为,可通过 enterSubmit: false 禁用监听。
<script setup lang="tsx">
import { FormDialog, FormItem, FormLayout, Input } from '@silver-formily/element-plus'
import { Field } from '@silver-formily/vue'
import { ElButton, ElSpace } from 'element-plus'
function renderForm() {
return (
<FormLayout labelCol={6} wrapperCol={12} layout="vertical">
<Field
name="user"
required
title="用户名"
decorator={[FormItem]}
component={[Input, { placeholder: '回车试试' }]}
/>
</FormLayout>
)
}
function openDialog({ title, enterSubmit }: { title: string, enterSubmit?: boolean }) {
FormDialog({ title, enterSubmit }, renderForm)
.forConfirm((form, next) => {
console.log('submit', form.values)
next()
})
.open()
.catch(console.warn)
}
function handleDefault() {
openDialog({ title: '默认允许回车提交' })
}
function handleDisabled() {
openDialog({ title: '禁用回车提交', enterSubmit: false })
}
</script>
<template>
<ElSpace>
<ElButton @click="handleDefault">
默认回车提交
</ElButton>
<ElButton @click="handleDisabled">
禁用回车提交
</ElButton>
</ElSpace>
</template>弹出层嵌套示例
可以在一个 FormDialog 中继续调用 FormDialog 打开新的弹窗,组件会自动只让最顶层实例响应快捷键和提交。
<script setup lang="tsx">
import { FormDialog, FormItem, FormLayout, Input } from '@silver-formily/element-plus'
import { Field } from '@silver-formily/vue'
import { ElButton } from 'element-plus'
function renderParentForm() {
return (
<FormLayout labelCol={6} wrapperCol={12} layout="vertical">
<Field
name="company"
title="公司名称"
decorator={[FormItem]}
component={[Input, { placeholder: '输入公司' }]}
/>
</FormLayout>
)
}
function renderChildForm() {
return (
<FormLayout labelCol={6} wrapperCol={12} layout="vertical">
<Field
name="contact"
title="联系人"
decorator={[FormItem]}
component={[Input, { placeholder: '输入联系人' }]}
/>
</FormLayout>
)
}
function openChildDialog() {
FormDialog('二级弹窗', renderChildForm)
.forConfirm((form, next) => {
console.log('child submit', form.values)
next()
})
.open()
.catch(console.warn)
}
function handleOpen() {
FormDialog('一级弹窗', () => (
<div>
{renderParentForm()}
<ElButton class="mt-2" type="primary" onClick={openChildDialog}>
打开二级弹窗
</ElButton>
</div>
))
.forConfirm((form, next) => {
console.log('parent submit', form.values)
next()
})
.open()
.catch(console.warn)
}
</script>
<template>
<ElButton @click="handleOpen">
打开包含子弹窗的表单
</ElButton>
</template>API
FormDialog 函数入参
| 参数 | 说明 | 类型 |
|---|---|---|
title或formDialogProps | 标题或Dialog组件的props | string FormDialogProps |
formDialogSlots | 表单弹窗组件的内容,支持组件,VNode和插槽的写法 | Component VNode[] ()=>VNode[] FormDialogSlots |
dynamicMiddlewareNames | 动态中间件名称列表,使用时会转成Camel Case命名风格。 | string[]除了cancel confirm open |
注意
formDialogProps是有保留值的。传入modelValue、onUpdate:modelValue不会生效,已被FormDialog组件内部使用。
完整函数类型声明(参数的具体类型参见类型声明):
interface FormDialog {
(
title: IFormDialogProps | string,
content?: Component | FormDialogSlotContent,
dynamicMiddlewareNames?: string[]
): IFormDialog
}title
函数的第一个参数,传入字符串时会作为标题显示。可以传入IFormDialogProps来进行自定义。请优先使用forOpen、forConfirm、forCancel等中间件来控制弹框的生命周期。
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
cancelText | 取消按钮文字 | string | 取消 |
cancelButtonProps | 取消按钮的props | ButtonProps | - |
okText | 确定按钮文字 | string | 确定 |
okButtonProps | 确定按钮的props | ButtonProps | - |
loadingText | 加载中文字 | string | loading |
enterSubmit | 是否允许在输入框回车时立即触发 resolve | boolean | true |
其余参数请参考参考 https://cn.element-plus.org/zh-CN/component/dialog.html
content
函数的第二个参数,除了可以传入组件和VNode之外还可以接受Vue中JSX的插槽写法自定义header与footer。
| 插槽名 | 说明 | 类型 |
|---|---|---|
default | 表单弹窗组件的内容,支持组件,VNode和插槽的写法 | - |
header | 头部插槽,可以通过作用域插槽调用resolve或reject来关闭,resovle可以接受dynamicMiddlewareNames中传入的字符串 | FormDialogSlotProps |
footer | 底部插槽,可以通过作用域插槽调用resolve或reject来关闭,resovle可以接受dynamicMiddlewareNames中传入的字符串 | FormDialogSlotProps |
dynamicMiddlewareNames
函数的第三个参数,是一个字符串数组,用于触发自定义footer或header中的按钮事件。
比如需要在弹框中额外添加保存草稿的功能,那么就可以在dynamicMiddlewareNames中传入'saveDraft',然后在footer中的按钮上绑定事件resolve('saveDraft')。 最后在可以像forConfirm一样添加forSaveDraft的相关逻辑。具体使用可以参考Demo中的例子。
提示
传入dynamicMiddlewareNames中的字符串会被转成Camel Case命名风格,比如'save-draft'会被转成'saveDraft'。
IFormDialog 函数返回
函数的返回值,是一个是一个Promise对象,因此可以进行await操作来优化逻辑书写,需要调用open方法来打开弹框。可以进行链式调用来处理不同逻辑下的事件处理。现在支持通过dynamicMiddlewareNames来传入自定义的事件来处理业务逻辑。
| 方法名 | 说明 | 类型 |
|---|---|---|
open | 打开弹框 | (IFormProps)=>Promise<IFormProps.values> |
forOpen | 打开弹框事件 | (IMiddleware<IFormProps>)=>IFormDialog |
forConfirm | 确认事件 | (IMiddleware<Form>)=>IFormDialog |
forCancel | 取消事件 | (IMiddleware<Form>)=>IFormDialog |
for${Dynamic} | 自定义事件 | (IMiddleware<Form>)=>IFormDialog |
提示
自定义事件中的Dynamic的值为dynamicMiddlewareNames中传入的字符串,通过作用域插槽中的resolve方法来触发对应的事件。 传入dynamicMiddlewareNames中的字符串在调用方法时会被转成Pascal Case命名风格,比如传入['save-draft']应该调用'forSaveDraft'。
提示
现在所有通过非resolve调用关闭的弹框都会作为错误抛出,因此在async/await写法中如果await了FormDialog则此之后的逻辑都只在表单成功提交后才会执行。
类型声明
IFormDialogProps
export type IFormDialogProps = Partial<DialogProps> & {
cancelText?: string
cancelButtonProps?: ButtonProps
okText?: string
okButtonProps?: ButtonProps
loadingText?: string
enterSubmit?: boolean
}FormDialogSlots
export interface FormDialogSlotProps {
resolve: (type?: string) => void
reject: () => void
form: Form
}
export interface FormDialogSlots {
header?: (props: FormDialogSlotProps) => VNode
default?: () => VNode
footer?: (props: FormDialogSlotProps) => VNode
}IFormDialog
export interface IFormDialog<T extends object = any> {
forOpen: (middleware: IMiddleware<IFormProps<T>>) => IFormDialog<T>
forConfirm: (middleware: IMiddleware<Form<T>>) => IFormDialog<T>
forCancel: (middleware: IMiddleware<Form<T>>) => IFormDialog<T>
[key: `for${string}`]: (middleware: IMiddleware<IFormProps<T>> | IMiddleware<Form<T>>) => IFormDialog<T>
open: (props?: IFormProps<T>) => Promise<any>
close: () => void
}