FormDrawer
抽屉表单,主要用在简单的事件打开表单场景
注意
该组件经过重构,完全摒弃了通过id传递上下文的方式,使用时请注意函数入参的改动。现在通过Vue中JSX的插槽写法实现类似的功能。
提示
使用函数式组件时可以通过解构的方式快速拿到form,具体请参考template案例。
Markup Schema 案例
<script setup lang="tsx">
import { FormDrawer, 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 DrawerForm = {
props: ['form'],
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() {
FormDrawer('抽屉表单', DrawerForm)
.forOpen((props, next) => {
setTimeout(() => {
next()
}, 1000)
})
.open({
initialValues: {
aaa: '123',
},
})
.then(console.log)
}
</script>
<template>
<ElButton @click="handleOpen">
点击打开表单
</ElButton>
</template>JSON Schema 案例
<script setup lang="tsx">
import { FormDrawer, 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 DrawerForm = {
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() {
FormDrawer('抽屉表单', DrawerForm)
.open({
initialValues: {
aaa: '123',
},
})
.then((values) => {
console.log('values', values)
})
.catch((error) => {
console.log(error)
})
}
</script>
<template>
<ElButton @click="handleOpen">
点击打开表单
</ElButton>
</template>Template 案例
<script setup lang="tsx">
import { FormDrawer, FormItem, FormLayout, Input } from '@silver-formily/element-plus'
import { Field } from '@silver-formily/vue'
import { ElButton } from 'element-plus'
function handleOpen() {
FormDrawer('抽屉表单', ({ 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>
)
})
.forConfirm(async (form, next) => {
setTimeout(() => {
console.log('form', form)
next()
}, 1000)
})
.open({
initialValues: {
aaa: '123',
},
})
.then((values) => {
console.log('values', values)
})
.catch((error) => {
console.log(error)
})
}
</script>
<template>
<ElButton @click="handleOpen">
点击打开表单
</ElButton>
</template>Template 插槽案例
<script setup lang="tsx">
import { FormDrawer, FormItem, FormLayout, Input } from '@silver-formily/element-plus'
import { Field } from '@silver-formily/vue'
import { ElButton } from 'element-plus'
function handleOpen() {
FormDrawer('抽屉表单', {
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>回车提交配置
FormDrawer 同样会默认监听输入框中的键盘回车来调用 resolve。当抽屉内有自定义快捷键或嵌套的弹层时,可以将 enterSubmit 设为 false 来单独关闭。
<script setup lang="tsx">
import { FormDrawer, 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="email"
required
title="邮箱"
decorator={[FormItem]}
component={[Input, { placeholder: '输入后按回车' }]}
/>
</FormLayout>
)
}
function openDrawer({ title, enterSubmit }: { title: string, enterSubmit?: boolean }) {
FormDrawer({ title, enterSubmit }, renderForm)
.forConfirm((form, next) => {
console.log('submit', form.values)
next()
})
.open()
.catch(console.warn)
}
function handleDefault() {
openDrawer({ title: '默认允许回车提交' })
}
function handleDisabled() {
openDrawer({ title: '禁用回车提交', enterSubmit: false })
}
</script>
<template>
<ElSpace>
<ElButton @click="handleDefault">
默认回车提交
</ElButton>
<ElButton @click="handleDisabled">
禁用回车提交
</ElButton>
</ElSpace>
</template>API
FormDrawer 函数入参
| 参数 | 说明 | 类型 |
|---|---|---|
title或formDrawerProps | 标题或Drawer组件的props | string FormDrawerProps |
formDrawerSlots | 表单抽屉组件的内容,支持组件,VNode和插槽的写法 | Component VNode[] ()=>VNode[] FormDrawerSlots |
dynamicMiddlewareNames | 动态中间件名称列表,使用时会转成Camel Case命名风格。 | string[]除了cancel confirm open |
注意
formDrawerProps是有保留值的。传入modelValue、onUpdate:modelValue不会生效,已被FormDialog组件内部使用。
完整函数类型声明(参数的具体类型参见类型声明):
interface FormDrawer {
(
title: IFormDrawerProps | string,
content?: Component | FormDrawerSlotContent,
dynamicMiddlewareNames?: string[]
): IFormDrawer
}title
函数的第一个参数,传入字符串时会作为标题显示。可以传入 IFormDrawerProps 来进行自定义。请优先使用 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/drawer.html
content
函数的第二个参数,除了可以传入组件和VNode之外还可以接受Vue中JSX的插槽写法自定义 header 与 footer 。
| 插槽名 | 说明 | 类型 |
|---|---|---|
default | 表单弹窗组件的内容,支持组件,VNode和插槽的写法 | - |
header | 头部插槽,可以通过作用域插槽调用resolve或reject来关闭,resovle可以接受dynamicMiddlewareNames中传入的字符串 | FormDrawerSlotProps |
footer | 底部插槽,可以通过作用域插槽调用resolve或reject来关闭,resovle可以接受dynamicMiddlewareNames中传入的字符串 | FormDrawerSlotProps |
dynamicMiddlewareNames
函数的第三个参数,是一个字符串数组,用于触发自定义footer或header中的按钮事件。
比如需要在抽屉中额外添加保存草稿的功能,那么就可以在 dynamicMiddlewareNames 中传入 'saveDraft' ,然后在 footer 中的按钮上绑定事件 resolve('saveDraft') 。 最后在可以像 forConfirm 一样添加 forSaveDraft 的相关逻辑。具体使用可以参考Demo中的例子。
提示
传入dynamicMiddlewareNames中的字符串会被转成Camel Case命名风格,比如'save-draft'会被转成'saveDraft'。
IFormDrawer 函数返回
函数的返回值,是一个是一个Promise对象,因此可以进行await操作来优化逻辑书写,需要调用open方法来打开抽屉。可以进行链式调用来处理不同逻辑下的事件处理。现在支持通过dynamicMiddlewareNames来传入自定义的事件来处理业务逻辑。
| 方法名 | 说明 | 类型 |
|---|---|---|
open | 打开抽屉 | (IFormProps)=>Primise<IFormProps.values> |
forOpen | 打开抽屉事件 | (IMiddleware<IFormProps>)=>IFormDrawer |
forConfirm | 确认事件 | (IMiddleware<Form>)=>IFormDrawer |
forCancel | 取消事件 | (IMiddleware<Form>)=>IFormDrawer |
for${Dynamic} | 自定义事件 | (IMiddleware<Form>)=>IFormDrawer |
提示
自定义事件中的Dynamic的值为dynamicMiddlewareNames中传入的字符串,通过作用域插槽中的resolve方法来触发对应的事件。 传入dynamicMiddlewareNames中的字符串在调用方法时会被转成Pascal Case命名风格,比如传入['save-draft']应该调用'forSaveDraft'。
提示
现在所有通过非resolve调用关闭的弹框都会作为错误抛出,因此在async/await写法中如果await了FormDrawer则此之后的逻辑都只在表单成功提交后才会执行。
类型声明
IFormDrawerProps
export type IFormDrawerProps = Partial<DrawerProps> & {
cancelText?: string
cancelButtonProps?: ButtonProps
okText?: string
okButtonProps?: ButtonProps
loadingText?: string
enterSubmit?: boolean
}FormDrawerSlots
export interface FormDrawerSlotProps {
resolve: (type?: string) => void
reject: () => void
form: Form
}
export interface FormDrawerSlots {
header?: (props: FormDrawerSlotProps) => VNode
default?: () => VNode
footer?: (props: FormDrawerSlotProps) => VNode
}IFormDrawer
export interface IFormDrawer<T extends object = any> {
forOpen: (middleware: IMiddleware<IFormProps<T>>) => IFormDrawer<T>
forConfirm: (middleware: IMiddleware<Form<T>>) => IFormDrawer<T>
forCancel: (middleware: IMiddleware<Form<T>>) => IFormDrawer<T>
[key: `for${string}`]: (middleware: IMiddleware<IFormProps<T>> | IMiddleware<Form<T>>) => IFormDrawer<T>
open: (props?: IFormProps<T>) => Promise<any>
close: () => void
}