Appearance
BizFormDialog 弹窗表单
fields 配置驱动的新增 / 编辑二合一表单弹窗:8 类控件声明式渲染、24 栅格布局、required 自动规则、show / disabled 联动、提交 loading 契约(done())。与 BizSearchForm / BizTable 组成完整 CRUD 闭环。
mode="drawer" 可切换为右侧抽屉形态——不遮挡列表上下文、适合长表单,表单配置与校验完全一致。
表单渲染内核 BizFormBody 已下沉为公开包 BizForm(页面级 Schema 表单,字段方言为本组件的超集:BizFormField / BizFormFieldType 由其统一导出,旧字段配置完全兼容,另支持 datetime / time / cascader / checkbox / rate / slider)。
基础用法
新增传空对象(走 defaultValue),编辑传行数据(自动回填);提交经
@submit(values, done),保存成功后由业务侧关闭弹窗。<script setup lang="ts">
/** 基础用法:新增 / 编辑二合一——打开前给 :model 赋值(新增空对象走 defaultValue,编辑传行数据) */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizButton, BizFormDialog } from 'vue3-ep-biz-lib'
import type { BizFormField } from 'vue3-ep-biz-lib'
const fields: BizFormField[] = [
{ prop: 'name', label: '姓名', required: true, attrs: { maxlength: 20 } },
{ prop: 'mobile', label: '手机号', required: true, attrs: { maxlength: 11 } },
{
prop: 'dept',
label: '部门',
type: 'select',
required: true,
options: [
{ label: '研发部', value: 'rd' },
{ label: '安全管理部', value: 'sec' },
{ label: '综合部', value: 'gen' },
],
},
{
prop: 'role',
label: '角色',
type: 'radio',
options: [
{ label: '管理员', value: 'admin' },
{ label: '普通用户', value: 'user' },
],
},
{ prop: 'entryDate', label: '入职日期', type: 'date' },
{ prop: 'enabled', label: '是否启用', type: 'switch', defaultValue: true },
{ prop: 'remark', label: '备注', type: 'textarea', span: 24, attrs: { maxlength: 100 } },
]
const visible = ref(false)
const editing = ref(false)
/** 打开弹窗的初始数据(内部会浅拷贝,不污染外部对象) */
const form = ref<Record<string, unknown>>({})
/** 模拟的列表行(编辑数据源) */
const row: Record<string, unknown> = {
name: '李四',
mobile: '13900139000',
dept: 'sec',
role: 'user',
entryDate: '2025-03-18',
enabled: false,
remark: '原备注信息',
}
const openAdd = (): void => {
editing.value = false
form.value = {}
visible.value = true
}
const openEdit = (): void => {
editing.value = true
form.value = { ...row }
visible.value = true
}
/** 模拟保存接口(组件库不含业务接口):done() 结束按钮 loading,保存成功后再关弹窗 */
const onSubmit = (values: Record<string, unknown>, done: () => void): void => {
window.setTimeout(() => {
done()
visible.value = false
ElMessage.success(`${editing.value ? '编辑' : '新增'}成功:${String(values.name ?? '')}`)
}, 800)
}
</script>
<template>
<div>
<BizButton type="primary" @click="openAdd">新增用户</BizButton>
<BizButton @click="openEdit">编辑(回填行数据)</BizButton>
<BizFormDialog
v-model="visible"
:title="editing ? '编辑用户' : '新增用户'"
:fields="fields"
:model="form"
width="640px"
@submit="onSubmit"
/>
</div>
</template>
显示代码
抽屉形态
mode="drawer" 从右侧滑出,fields / 校验 / 提交契约与弹窗完全一致;抽屉宽度用 size 透传(沿用 dialog 的 width 会被自动映射),其余 el-drawer 属性同样经 attrs 透传。<script setup lang="ts">
/** 抽屉形态:mode="drawer" 右侧滑出,表单配置与 dialog 完全一致;宽度用 size(沿用的 width 会自动映射) */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizButton, BizFormDialog } from 'vue3-ep-biz-lib'
import type { BizFormField } from 'vue3-ep-biz-lib'
const fields: BizFormField[] = [
{ prop: 'name', label: '商品名称', required: true, attrs: { maxlength: 30 } },
{ prop: 'barcode', label: '条形码', attrs: { maxlength: 20 } },
{
prop: 'category',
label: '类目',
type: 'select',
required: true,
options: [
{ label: '食品饮料', value: 'food' },
{ label: '日用百货', value: 'daily' },
{ label: '数码家电', value: 'digital' },
],
},
{ prop: 'price', label: '售价(元)', type: 'number', required: true, attrs: { min: 0, precision: 2 } },
{ prop: 'stock', label: '库存', type: 'number', attrs: { min: 0, precision: 0 } },
{ prop: 'onShelf', label: '上架销售', type: 'switch', defaultValue: true },
{ prop: 'detail', label: '商品详情', type: 'textarea', span: 24, attrs: { maxlength: 200 } },
]
const visible = ref(false)
const editing = ref(false)
const form = ref<Record<string, unknown>>({})
const row: Record<string, unknown> = {
name: '家用空气净化器 X3',
barcode: '6901234567890',
category: 'digital',
price: 1299,
stock: 56,
onShelf: false,
detail: 'CADR 400m³/h,适用 28-48㎡ 房间。',
}
const openAdd = (): void => {
editing.value = false
form.value = {}
visible.value = true
}
const openEdit = (): void => {
editing.value = true
form.value = { ...row }
visible.value = true
}
const onSubmit = (values: Record<string, unknown>, done: () => void): void => {
window.setTimeout(() => {
done()
visible.value = false
ElMessage.success(`${editing.value ? '编辑' : '新增'}成功:${String(values.name ?? '')}`)
}, 800)
}
</script>
<template>
<div>
<BizButton type="primary" @click="openAdd">新增商品</BizButton>
<BizButton @click="openEdit">编辑(回填行数据)</BizButton>
<BizFormDialog
v-model="visible"
mode="drawer"
:title="editing ? '编辑商品' : '新增商品'"
:fields="fields"
:model="form"
size="520px"
destroy-on-close
@submit="onSubmit"
/>
</div>
</template>
显示代码
其他用法
打开后试试:切换「是否加急」看联动显隐;切换「锁定级别」后紧急程度变为禁用。
附加校验规则(pattern / min)、show 联动显隐(加急字段)、函数式 disabled(锁定后禁选)、attrs 透传控件属性(maxlength / show-word-limit / precision)。
<script setup lang="ts">
/** 其他用法:附加校验规则 / 联动显隐(show)/ 函数禁用(disabled)/ attrs 透传控件属性 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizButton, BizFormDialog } from 'vue3-ep-biz-lib'
import type { BizFormField } from 'vue3-ep-biz-lib'
const fields: BizFormField[] = [
{ prop: 'title', label: '任务标题', span: 24, required: true, attrs: { maxlength: 30, 'show-word-limit': true } },
{ prop: 'owner', label: '负责人', required: true, rules: [{ pattern: /^[\u4e00-\u9fa5]{2,4}$/, message: '请输入 2-4 位中文姓名', trigger: 'blur' }] },
{ prop: 'contact', label: '联系电话', rules: [{ pattern: /^1\d{10}$/, message: '手机号格式不正确', trigger: 'blur' }] },
{ prop: 'budget', label: '预算(万)', type: 'number', required: true, attrs: { min: 0, max: 1000, precision: 2 } },
{
prop: 'level',
label: '紧急程度',
type: 'select',
required: true,
options: [
{ label: '常规', value: 'normal' },
{ label: '紧急', value: 'urgent' },
],
/** 函数式禁用:任务已锁定时不可调整 */
disabled: (model) => model.locked === true,
},
{ prop: 'locked', label: '锁定级别', type: 'switch', defaultValue: false },
{ prop: 'urgent', label: '是否加急', type: 'switch', defaultValue: false },
/** 联动显隐:加急时才出现 */
{ prop: 'range', label: '完成期限', type: 'daterange', span: 24, required: true, show: (model) => model.urgent === true },
{ prop: 'reason', label: '加急说明', type: 'textarea', span: 24, show: (model) => model.urgent === true, rules: [{ min: 10, message: '至少输入 10 个字', trigger: 'blur' }] },
]
const visible = ref(false)
const form = ref<Record<string, unknown>>({})
const onSubmit = (values: Record<string, unknown>, done: () => void): void => {
window.setTimeout(() => {
done()
visible.value = false
ElMessage.success(`提交成功:${String(values.title ?? '')}`)
}, 800)
}
</script>
<template>
<div>
<BizButton type="primary" @click="visible = true">新建任务</BizButton>
<span class="tip">打开后试试:切换「是否加急」看联动显隐;切换「锁定级别」后紧急程度变为禁用。</span>
<BizFormDialog v-model="visible" title="新建任务" :fields="fields" :model="form" width="680px" @submit="onSubmit" />
</div>
</template>
<style scoped>
.tip {
margin-left: 12px;
font-size: 13px;
color: var(--el-text-color-secondary);
}
</style>
显示代码
新增 / 编辑复用与字段联动
切换「客户类型」观察统一信用代码显隐;打开「无需跟单」后跟单人禁用(值保留)。
客户录入场景:同一份 fields 弹新增 / 编辑;「客户类型」切企业时
show 联动出统一信用代码(必填),「无需跟单」开关经 disabled 函数联动禁用跟单人(值保留)。<script setup lang="ts">
/** 场景:新增/编辑复用 + 字段联动(show 显隐 / disabled 函数 / 默认值) */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizButton, BizFormDialog } from 'vue3-ep-biz-lib'
import type { BizFormField } from 'vue3-ep-biz-lib'
const fields: BizFormField[] = [
{
prop: 'type',
label: '客户类型',
type: 'radio',
required: true,
defaultValue: 'person',
options: [
{ label: '个人', value: 'person' },
{ label: '企业', value: 'enterprise' },
],
},
{ prop: 'name', label: '客户名称', required: true, attrs: { maxlength: 30 } },
/* 联动显隐:仅企业客户显示统一社会信用代码(隐藏时提交值仍保留) */
{
prop: 'uscc',
label: '统一信用代码',
required: true,
show: (model) => model.type === 'enterprise',
attrs: { maxlength: 18 },
},
{ prop: 'mobile', label: '联系电话', required: true, attrs: { maxlength: 11 } },
/* 联动禁用:勾选「无需跟单」后跟单人禁用 */
{ prop: 'noFollow', label: '无需跟单', type: 'switch', defaultValue: false },
{
prop: 'follower',
label: '跟单人',
disabled: (model) => model.noFollow === true,
attrs: { placeholder: '分配跟单销售' },
},
{ prop: 'remark', label: '备注', type: 'textarea', span: 24 },
]
const visible = ref(false)
const editing = ref(false)
const form = ref<Record<string, unknown>>({})
const editingRow: Record<string, unknown> = {
type: 'enterprise',
name: '云联科技(南京)有限公司',
uscc: '91320114MA1X9Y2K3L',
mobile: '025-88886666',
noFollow: false,
follower: '王五',
remark: '年框客户',
}
const openAdd = (): void => {
editing.value = false
form.value = {}
visible.value = true
}
const openEdit = (): void => {
editing.value = true
form.value = { ...editingRow }
visible.value = true
}
const onSubmit = (values: Record<string, unknown>, done: () => void): void => {
window.setTimeout(() => {
done()
visible.value = false
ElMessage.success(`${editing.value ? '编辑' : '新增'}成功:${String(values.name ?? '')}`)
}, 600)
}
</script>
<template>
<div>
<BizButton type="primary" @click="openAdd">新增客户</BizButton>
<BizButton @click="openEdit">编辑(企业客户回填)</BizButton>
<BizFormDialog v-model="visible" :title="editing ? '编辑客户' : '新增客户'" :fields="fields" :model="form" width="600px" @submit="onSubmit" />
<div style="margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary)">
切换「客户类型」观察统一信用代码显隐;打开「无需跟单」后跟单人禁用(值保留)。
</div>
</div>
</template>
显示代码
实名认证正则校验
实名场景:身份证 18 位正则、邮箱格式 rules、错误文案就地提示。
<script setup lang="ts">
/** 场景:实名认证——手机/身份证/邮箱正则校验与错误文案 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizButton, BizFormDialog } from 'vue3-ep-biz-lib'
import type { BizFormField } from 'vue3-ep-biz-lib'
const ID_CARD = /^\d{6}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/
const fields: BizFormField[] = [
{ prop: 'realName', label: '真实姓名', required: true, span: 24, attrs: { maxlength: 20 } },
{
prop: 'idCard',
label: '身份证号',
span: 24,
required: true,
rules: [{ pattern: ID_CARD, message: '身份证号格式不正确', trigger: 'blur' }],
attrs: { maxlength: 18 },
},
{
prop: 'email',
label: '邮箱',
span: 24,
rules: [{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }],
},
]
const visible = ref(false)
const form = ref<Record<string, unknown>>({})
const onSubmit = (values: Record<string, unknown>, done: () => void): void => {
window.setTimeout(() => {
done()
visible.value = false
ElMessage.success(`实名信息已提交:${String(values.realName ?? '')}`)
}, 600)
}
</script>
<template>
<div>
<BizButton type="primary" @click="visible = true">实名认证</BizButton>
<BizFormDialog v-model="visible" title="实名认证" :fields="fields" :model="form" width="520px" @submit="onSubmit" />
</div>
</template>
显示代码
进销存多列布局
建档场景:24 栅格混排——span 8 三列(类目 / 单位 / 规格)、span 12 两列(进价 / 售价)、textarea 整行。
<script setup lang="ts">
/** 场景:进销存录入——24 栅格混排(8 三列 / 12 两列 / 24 整行) */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizButton, BizFormDialog } from 'vue3-ep-biz-lib'
import type { BizFormField } from 'vue3-ep-biz-lib'
const fields: BizFormField[] = [
{ prop: 'goodsName', label: '商品名称', span: 12, required: true },
{ prop: 'barcode', label: '条码', span: 12 },
{ prop: 'category', label: '类目', type: 'select', span: 8, options: [{ label: '食品', value: 'food' }, { label: '百货', value: 'daily' }] },
{ prop: 'unit', label: '单位', type: 'select', span: 8, options: [{ label: '件', value: 'pc' }, { label: '箱', value: 'box' }] },
{ prop: 'spec', label: '规格', span: 8, attrs: { placeholder: '如 500ml×24' } },
{ prop: 'purchase', label: '进价(元)', type: 'number', span: 8, required: true, attrs: { min: 0, precision: 2 } },
{ prop: 'sale', label: '售价(元)', type: 'number', span: 8, required: true, attrs: { min: 0, precision: 2 } },
{ prop: 'stock', label: '期初库存', type: 'number', span: 8, attrs: { min: 0 } },
{ prop: 'shelfLife', label: '保质期(天)', type: 'number', span: 12 },
{ prop: 'enabled', label: '立即上架', type: 'switch', span: 12, defaultValue: true },
{ prop: 'note', label: '备注', type: 'textarea', span: 24 },
]
const visible = ref(false)
const form = ref<Record<string, unknown>>({})
const onSubmit = (_values: Record<string, unknown>, done: () => void): void => {
window.setTimeout(() => {
done()
visible.value = false
ElMessage.success('商品已建档')
}, 500)
}
</script>
<template>
<div>
<BizButton type="primary" @click="visible = true">新增商品</BizButton>
<BizFormDialog v-model="visible" title="新增商品" :fields="fields" :model="form" width="720px" @submit="onSubmit" />
</div>
</template>
显示代码
默认值与编辑回填
优先级场景:新增走 defaultValue(草稿 / 中 / 开通知),编辑以 :model 为准覆盖默认值——一次打开即可验证。
<script setup lang="ts">
/** 场景:新增默认值 vs 编辑回填——defaultValue 与 :model 的优先关系 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizButton, BizFormDialog } from 'vue3-ep-biz-lib'
import type { BizFormField } from 'vue3-ep-biz-lib'
const fields: BizFormField[] = [
{ prop: 'status', label: '初始状态', type: 'select', defaultValue: 'draft', options: [{ label: '草稿', value: 'draft' }, { label: '生效', value: 'active' }] },
{ prop: 'priority', label: '优先级', type: 'radio', defaultValue: 'mid', options: [{ label: '低', value: 'low' }, { label: '中', value: 'mid' }, { label: '高', value: 'high' }] },
{ prop: 'enabled', label: '启用通知', type: 'switch', defaultValue: true },
{ prop: 'title', label: '标题', required: true },
]
const visible = ref(false)
const isEdit = ref(false)
const form = ref<Record<string, unknown>>({})
const editingRow: Record<string, unknown> = {
status: 'active',
priority: 'high',
enabled: false,
title: '既有公告(编辑态以 model 为准,defaultValue 不生效)',
}
const open = (edit: boolean): void => {
isEdit.value = edit
form.value = edit ? { ...editingRow } : {}
visible.value = true
}
const onSubmit = (values: Record<string, unknown>, done: () => void): void => {
window.setTimeout(() => {
done()
visible.value = false
ElMessage.success(`${isEdit.value ? '编辑' : '新增'}提交:${JSON.stringify(values).slice(0, 80)}…`)
}, 500)
}
</script>
<template>
<div>
<BizButton type="primary" @click="open(false)">新增(走默认值)</BizButton>
<BizButton @click="open(true)">编辑(model 覆盖默认值)</BizButton>
<BizFormDialog v-model="visible" :title="isEdit ? '编辑公告' : '新增公告'" :fields="fields" :model="form" width="560px" @submit="onSubmit" />
</div>
</template>
显示代码
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| model-value / v-model | boolean | false | 弹窗显隐 |
| title | string | '' | 弹窗标题(新增 / 编辑由业务侧拼接) |
| fields | BizFormField[] | [] | 表单字段配置 |
| model | Record<string, unknown> | {} | 打开时的初始数据(内部浅拷贝,不污染外部) |
| confirm-text | string | '确 定' | 确认按钮文案 |
| cancel-text | string | '取 消' | 取消按钮文案 |
| label-width | string | number | '100px' | 表单标签宽度 |
| mode | 'dialog' | 'drawer' | 'dialog' | 弹层形态:dialog 居中弹窗 / drawer 右侧抽屉;抽屉宽度经 size 透传(width 自动映射) |
BizFormField:
| 属性名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| prop | string | 是 | 字段名(提交数据 key) |
| label | string | 是 | 标签 |
| type | 'input' | 'textarea' | 'number' | 'select' | 'date' | 'datetime' | 'time' | 'daterange' | 'cascader' | 'switch' | 'radio' | 'checkbox' | 'rate' | 'slider' | 'slot' | 否 | 控件类型,默认 'input' |
| span | number | 否 | 栅格占位(24 制,默认 12 两列) |
| default-value | unknown | 否 | 默认值(新增时字段缺省回填) |
| options | { label, value, disabled?, children? }[] | 否 | 选项(select / radio / cascader,cascader 子级经 children 嵌套) |
| required | boolean | 否 | 自动追加 required 规则(输入类 blur / 选择类 change) |
| rules | FormItemRule[] | 否 | 附加校验规则(与 required 叠加) |
| placeholder | string | 否 | 占位文案(默认按类型生成"请输入/请选择") |
| attrs | Record<string, unknown> | 否 | 透传内部 el 控件属性(clearable / maxlength / rows / precision...),优先级高于预设 |
| show | (model) => boolean | 否 | 联动显隐(返回 false 隐藏该字段) |
| disabled | boolean | ((model) => boolean) | 否 | 是否禁用(支持按当前表单值联动) |
弹窗 / 抽屉属性经
v-bind="$attrs"全量透传:dialog 形态透传 el-dialog(width/top/destroy-on-close/append-to-body...),drawer 形态透传 el-drawer(size/direction/with-header...),事件与插槽同理。
Events
| 事件名 | 参数 | 说明 |
|---|---|---|
| submit | (values: Record<string, unknown>, done: () => void) | 校验通过后触发;done() 结束确认按钮 loading,是否关弹窗由业务侧决定 |
| update:model-value | (value: boolean) | v-model 显隐更新 |
Slots
| 插槽名 | 说明 |
|---|---|
| default | 追加在表单末尾的自定义内容(如提示条、协议勾选) |
Expose
| 属性/方法 | 说明 |
|---|---|
| validate | () => Promise<boolean> 主动校验 |
| resetFields | () => void 清除校验状态 |
| formModel | 当前内部表单数据(只读快照用途) |