Appearance
BizStepsForm 分步表单
el-steps 向导表单容器:每个步骤可配置独立字段(schema 结构与 BizFormDialog 完全一致),下一步前仅校验本步字段,末步完成时聚合各步数据触发 finish。适用于入驻登记、开通向导等多步资料填报。
基础用法
0
基础信息
企业与联系人
0
经营信息
类目与规模
两步入驻向导:第一步校验基础信息(含正则),第二步经营信息,完成后展示聚合提交数据。
<script setup lang="ts">
/** 基础用法:两步入驻向导,每步独立校验,完成展示聚合结果 */
import { ref } from 'vue'
import { ElDialog, ElMessage } from 'element-plus'
import { BizStepsForm } from 'vue3-ep-biz-lib'
import type { BizStepsItem } from 'vue3-ep-biz-lib'
const steps: BizStepsItem[] = [
{
title: '基础信息',
description: '企业与联系人',
fields: [
{ prop: 'company', label: '企业名称', required: true, span: 24 },
{ prop: 'creditCode', label: '统一社会信用代码', required: true, span: 24, rules: [{ pattern: /^[0-9A-HJ-NPQRTUWXY]{18}$/, message: '信用代码格式不正确', trigger: 'blur' }] },
{ prop: 'contact', label: '联系人', required: true },
{ prop: 'mobile', label: '手机号', required: true, rules: [{ pattern: /^1\d{10}$/, message: '手机号格式不正确', trigger: 'blur' }] },
],
},
{
title: '经营信息',
description: '类目与规模',
fields: [
{
prop: 'category', label: '经营类目', type: 'select', required: true, span: 24,
options: [
{ label: '医疗器械', value: '医疗器械' },
{ label: '食品保健', value: '食品保健' },
{ label: '其他', value: '其他' },
],
},
{ prop: 'staffCount', label: '员工规模', type: 'number', span: 24, attrs: { min: 1, max: 9999 } },
{ prop: 'invoice', label: '需要开具发票', type: 'switch', span: 24 },
],
},
]
const resultVisible = ref(false)
const result = ref<Record<string, unknown>>({})
const onFinish = (values: Record<string, unknown>): void => {
result.value = values
resultVisible.value = true
ElMessage.success('已完成全部步骤')
}
</script>
<template>
<BizStepsForm :steps="steps" @finish="onFinish" />
<ElDialog v-model="resultVisible" title="聚合提交数据" width="480px">
<pre style="margin: 0; white-space: pre-wrap; word-break: break-all">{{ JSON.stringify(result, null, 2) }}</pre>
</ElDialog>
</template>
显示代码
自定义步骤与按钮区
0
账号信息
0
确认信息
自定义内容步骤(step-1 插槽)
未配 fields 的步骤用
step-<index> 插槽渲染任意内容(作用域提供聚合 model);footer 插槽接管按钮区,作用域提供 active / prev / next / finish,可自由组织「上一步 / 下一步 / 提交 / 存草稿」。<script setup lang="ts">
/** 进阶:无 fields 步骤用 step-<index> 插槽自定义内容;footer 插槽接管按钮区(含存草稿) */
import { ref } from 'vue'
import { ElButton, ElDescriptions, ElMessage } from 'element-plus'
import { BizStepsForm } from 'vue3-ep-biz-lib'
import type { BizStepsItem } from 'vue3-ep-biz-lib'
const form = ref<Record<string, unknown>>({})
const steps: BizStepsItem[] = [
{
title: '账号信息',
fields: [
{ prop: 'username', label: '用户名', required: true, span: 24 },
{ prop: 'email', label: '邮箱', required: true, span: 24, rules: [{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }] },
],
},
{ title: '确认信息', description: '自定义内容步骤(step-1 插槽)' },
]
const onFinish = (values: Record<string, unknown>): void => {
ElMessage.success(`提交成功:${values.username} / ${values.email}`)
}
</script>
<template>
<BizStepsForm v-model="form" :steps="steps" @finish="onFinish">
<!-- 无 fields 的步骤:插槽作用域拿到聚合 model -->
<template #step-1="{ model }">
<ElDescriptions :column="1" border style="max-width: 420px">
<el-descriptions-item label="用户名">{{ model.username || '(待填写)' }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ model.email || '(待填写)' }}</el-descriptions-item>
</ElDescriptions>
</template>
<!-- footer 插槽接管按钮区:作用域提供 active / prev / next / finish -->
<template #footer="{ active, prev, next, finish }">
<ElButton v-if="active > 0" @click="prev">上一步</ElButton>
<ElButton v-if="active === 0" type="primary" @click="next">下一步</ElButton>
<ElButton v-else type="success" @click="finish">确认并创建</ElButton>
<ElButton text @click="ElMessage.info('已保存草稿(示例)')">存草稿</ElButton>
</template>
</BizStepsForm>
</template>
显示代码
编辑回填向导
0
基础信息
0
经营信息
编辑场景:详情接口返回的既有数据直接作为
v-model 初始值,仅修订涉及步骤;未配置字段的额外字段(licenseNo)在提交数据中原样透传不丢失。<script setup lang="ts">
/** 场景:编辑回填向导——v-model 传入既有数据,仅修订部分步骤 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizStepsForm } from 'vue3-ep-biz-lib'
import type { BizFormField } from 'vue3-ep-biz-lib'
const basicFields: BizFormField[] = [
{ prop: 'shopName', label: '门店名称', required: true, attrs: { maxlength: 20 } },
{ prop: 'contact', label: '联系电话', required: true, attrs: { maxlength: 11 } },
]
const bizFields: BizFormField[] = [
{
prop: 'category',
label: '经营类目',
type: 'select',
required: true,
options: [
{ label: '生鲜果蔬', value: 'fresh' },
{ label: '日用百货', value: 'daily' },
{ label: '数码家电', value: 'digital' },
],
},
{ prop: 'monthly', label: '月均流水(元)', type: 'number', required: true, attrs: { min: 0 } },
]
/** 编辑态:详情接口返回的既有数据直接作为初始 model,未涉及字段原样保留 */
const model = ref<Record<string, unknown>>({
shopName: '雨花台社区店',
contact: '13800138000',
category: 'fresh',
monthly: 186000,
licenseNo: '91320114MA1X9Y2K3L',
})
const result = ref('')
const onFinish = (values: Record<string, unknown>): void => {
result.value = JSON.stringify(values)
ElMessage.success('修订已提交')
}
</script>
<template>
<div style="max-width: 560px">
<BizStepsForm
v-model="model"
:steps="[
{ title: '基础信息', fields: basicFields },
{ title: '经营信息', fields: bizFields },
]"
finish-text="保存修订"
@finish="onFinish"
/>
<div v-if="result !== ''" style="margin-top: 12px; font-size: 13px; word-break: break-all; color: var(--el-text-color-secondary)">
提交数据:{{ result }}(未配置字段的 licenseNo 原样透传,不丢字段)
</div>
</div>
</template>
显示代码
自助终端注册
0
身份核验
0
人脸比对
自定义内容步骤
0
联系信息
自助机场景:schema 步骤(身份核验)与自定义内容步骤(人脸比对 ElResult)混排。
<script setup lang="ts">
/** 场景:自助终端注册——schema 步骤 + 自定义内容步骤混合 */
import { ref } from 'vue'
import { ElMessage, ElResult } from 'element-plus'
import { BizStepsForm } from 'vue3-ep-biz-lib'
import type { BizFormField } from 'vue3-ep-biz-lib'
const idFields: BizFormField[] = [
{ prop: 'name', label: '姓名', required: true, span: 24 },
{ prop: 'idCard', label: '身份证号', required: true, span: 24, attrs: { maxlength: 18 } },
]
const contactFields: BizFormField[] = [
{ prop: 'mobile', label: '手机号', required: true, span: 24, attrs: { maxlength: 11 } },
{ prop: 'address', label: '联系地址', type: 'textarea', span: 24 },
]
const model = ref<Record<string, unknown>>({})
const result = ref('')
const onFinish = (values: Record<string, unknown>): void => {
result.value = JSON.stringify(values)
ElMessage.success('注册完成,已发放访客卡')
}
</script>
<template>
<div style="max-width: 560px">
<BizStepsForm
v-model="model"
:steps="[
{ title: '身份核验', fields: idFields },
{ title: '人脸比对', description: '自定义内容步骤' },
{ title: '联系信息', fields: contactFields },
]"
@finish="onFinish"
>
<template #step-1>
<ElResult icon="success" title="人脸比对通过" sub-title="相似度 98.6%(示意)" style="padding: 24px 0" />
</template>
</BizStepsForm>
<div v-if="result !== ''" style="margin-top: 10px; font-size: 13px; color: var(--el-text-color-secondary); word-break: break-all">提交数据:{{ result }}</div>
</div>
</template>
显示代码
小额贷款申请
0
基础信息
0
借款方案
0
确认提交
汇总确认
金融场景:金额 / 期限联动预估月供,末步 step-2 插槽 ElDescriptions 汇总确认 + ElAlert 授权提示。
<script setup lang="ts">
/** 场景:小额贷款申请——金额期限联动提示 + 末步确认信息 */
import { computed, ref } from 'vue'
import { ElAlert, ElDescriptions, ElMessage } from 'element-plus'
import { BizStepsForm } from 'vue3-ep-biz-lib'
import type { BizFormField } from 'vue3-ep-biz-lib'
const baseFields: BizFormField[] = [
{ prop: 'name', label: '借款人', required: true },
{ prop: 'mobile', label: '手机号', required: true, attrs: { maxlength: 11 } },
{ prop: 'employer', label: '工作单位' },
{ prop: 'monthly', label: '月收入(元)', type: 'number', required: true, attrs: { min: 0 } },
]
const loanFields: BizFormField[] = [
{ prop: 'amount', label: '借款金额(元)', type: 'number', required: true, attrs: { min: 1000, max: 200000 } },
{ prop: 'term', label: '期限(月)', type: 'select', required: true, defaultValue: 12, options: [
{ label: '6 个月', value: 6 }, { label: '12 个月', value: 12 }, { label: '24 个月', value: 24 },
] },
]
const model = ref<Record<string, unknown>>({ term: 12 })
const monthlyPay = computed(() => {
const amount = Number(model.value.amount ?? 0)
const term = Number(model.value.term ?? 12)
if (amount < term) return '—'
return `¥ ${(amount / term).toFixed(2)}(等额本金首期,示意)`
})
const onFinish = (): void => {
ElMessage.success('申请已提交,等待电审')
}
</script>
<template>
<div style="max-width: 620px">
<BizStepsForm
v-model="model"
:steps="[
{ title: '基础信息', fields: baseFields },
{ title: '借款方案', fields: loanFields },
{ title: '确认提交', description: '汇总确认' },
]"
finish-text="提交申请"
@finish="onFinish"
>
<template #step-2="{ model: m }">
<ElDescriptions :column="2" border size="small" style="margin: 8px 0">
<ElDescriptions.Item label="借款人">{{ m.name || '—' }}</ElDescriptions.Item>
<ElDescriptions.Item label="手机号">{{ m.mobile || '—' }}</ElDescriptions.Item>
<ElDescriptions.Item label="金额">¥ {{ m.amount ?? '—' }}</ElDescriptions.Item>
<ElDescriptions.Item label="期限">{{ m.term }} 个月</ElDescriptions.Item>
<ElDescriptions.Item label="预估月供" :span="2">{{ monthlyPay }}</ElDescriptions.Item>
</ElDescriptions>
<ElAlert type="info" :closable="false" show-icon title="提交即授权征信查询;放款前请保持电话畅通" />
</template>
</BizStepsForm>
</div>
</template>
显示代码
分步校验拦截
0
邮箱与邀请码
0
设置密码
点「下一步」留空或输错格式——仅本步字段标红拦截,通过后才进入下一步
校验场景:邀请码格式(两位大写 + 四位数字)、邮箱格式、密码长度——本步不过不放行,错误就地标红。
<script setup lang="ts">
/** 场景:分步校验拦截——本步未通过不放行,错误就地提示 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizStepsForm } from 'vue3-ep-biz-lib'
import type { BizFormField } from 'vue3-ep-biz-lib'
const step1: BizFormField[] = [
{ prop: 'email', label: '工作邮箱', required: true, span: 24, rules: [{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }] },
{ prop: 'inviteCode', label: '邀请码', required: true, span: 24, rules: [{ pattern: /^[A-Z]{2}\d{4}$/, message: '格式:两位大写字母 + 四位数字', trigger: 'blur' }] },
]
const step2: BizFormField[] = [
{ prop: 'password', label: '设置密码', required: true, span: 24, rules: [{ min: 8, message: '至少 8 位', trigger: 'blur' }] },
]
const model = ref<Record<string, unknown>>({})
const onFinish = (): void => {
ElMessage.success('开通成功')
}
</script>
<template>
<div style="max-width: 520px">
<BizStepsForm
v-model="model"
:steps="[
{ title: '邮箱与邀请码', fields: step1 },
{ title: '设置密码', fields: step2 },
]"
@finish="onFinish"
/>
<div style="margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary)">
点「下一步」留空或输错格式——仅本步字段标红拦截,通过后才进入下一步
</div>
</div>
</template>
显示代码
草稿箱(基础)
0
基本信息
0
开通配置
(暂无)
填几个字段(停留 1 秒)→ 点「模拟再次进入」:出现草稿恢复条,点「恢复草稿」回填数据并回到暂存时的步骤。
draft-key 传入即启用:字段变化 800ms 防抖暂存 localStorage(含当前步),再次进入出现恢复条,恢复后回填并回到暂存步骤。<script setup lang="ts">
/** 草稿箱基础:字段变化 800ms 防抖暂存(含当前步),再次进入提示恢复 */
import { nextTick, ref } from 'vue'
import { BizStepsForm } from 'vue3-ep-biz-lib'
const renderKey = ref(0)
const lastEvent = ref('(暂无)')
const steps = [
{
title: '基本信息',
fields: [
{ prop: 'name', label: '姓名', type: 'input' as const, required: true },
{ prop: 'phone', label: '手机号', type: 'input' as const },
],
},
{
title: '开通配置',
fields: [
{ prop: 'plan', label: '套餐', type: 'select' as const, required: true, options: [{ label: '基础版', value: 'basic' }, { label: '专业版', value: 'pro' }] },
{ prop: 'remark', label: '备注', type: 'textarea' as const },
],
},
]
const remount = (): void => {
renderKey.value += 1
void nextTick()
}
</script>
<template>
<div style="max-width: 560px">
<BizStepsForm
:key="renderKey"
:steps="steps"
draft-key="docs-steps-draft-basic"
@draft-save="() => (lastEvent = 'draft-save:草稿已暂存')"
@draft-restore="(v) => (lastEvent = `draft-restore:已恢复 ${Object.keys(v).length} 个字段`)"
@finish="() => (lastEvent = 'finish:已提交并清理草稿')"
/>
<div style="margin-top: 12px; display: flex; gap: 8px; align-items: center">
<el-button size="small" @click="remount">模拟再次进入(重新挂载)</el-button>
<span style="font-size: 12px; color: var(--el-text-color-placeholder)">{{ lastEvent }}</span>
</div>
<p style="margin: 8px 0 0; font-size: 13px; color: var(--el-text-color-secondary)">
填几个字段(停留 1 秒)→ 点「模拟再次进入」:出现草稿恢复条,点「恢复草稿」回填数据并回到暂存时的步骤。
</p>
</div>
</template>
显示代码
草稿箱(放弃重填)
0
申请信息
0
明细
先填数据生成草稿 → 再次进入 → 点「放弃并重填」:草稿删除、表单回到空白;再次重新挂载将不再出现恢复条。
恢复条选「放弃并重填」即清理草稿从空白开始;此后重新进入不再提示。
<script setup lang="ts">
/** 放弃草稿:恢复条选择「放弃并重填」即清理 localStorage 并从空白开始 */
import { nextTick, ref } from 'vue'
import { BizStepsForm } from 'vue3-ep-biz-lib'
const renderKey = ref(0)
const steps = [
{
title: '申请信息',
fields: [
{ prop: 'title', label: '申请标题', type: 'input' as const, required: true },
{ prop: 'dept', label: '申请部门', type: 'input' as const },
],
},
{
title: '明细',
fields: [{ prop: 'detail', label: '补充说明', type: 'textarea' as const }],
},
]
</script>
<template>
<div style="max-width: 560px">
<BizStepsForm :key="renderKey" :steps="steps" draft-key="docs-steps-draft-discard" />
<div style="margin-top: 12px">
<el-button size="small" @click="renderKey += 1; void nextTick()">模拟再次进入</el-button>
</div>
<p style="margin: 8px 0 0; font-size: 13px; color: var(--el-text-color-secondary)">
先填数据生成草稿 → 再次进入 → 点「放弃并重填」:草稿删除、表单回到空白;再次重新挂载将不再出现恢复条。
</p>
</div>
</template>
显示代码
草稿箱(提交即清理)
0
入参
0
确认
finish 后草稿自动删除并暂停暂存——提交后重新挂载验证无恢复条。<script setup lang="ts">
/** 提交即清理:finish 后草稿自动删除并暂停暂存(重新挂载无恢复条) */
import { ref } from 'vue'
import { BizStepsForm } from 'vue3-ep-biz-lib'
import { ElMessage } from 'element-plus'
const renderKey = ref(0)
const finished = ref(false)
const steps = [
{
title: '入参',
fields: [
{ prop: 'appName', label: '应用名', type: 'input' as const, required: true },
],
},
{
title: '确认',
fields: [{ prop: 'confirmNote', label: '确认备注', type: 'input' as const }],
},
]
</script>
<template>
<div style="max-width: 560px">
<BizStepsForm
:key="renderKey"
:steps="steps"
draft-key="docs-steps-draft-finish"
finish-text="提交申请"
@finish="() => { finished = true; ElMessage.success('finish:提交成功,草稿已自动清理') }"
/>
<div style="margin-top: 12px; display: flex; gap: 8px; align-items: center">
<el-button size="small" :disabled="!finished" @click="renderKey += 1">提交后重新挂载验证</el-button>
<span v-if="finished" style="font-size: 12px; color: var(--el-text-color-placeholder)">重新挂载后不出现恢复条 = 草稿已清理</span>
</div>
</div>
</template>
显示代码
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| steps | BizStepsItem[] | —(必填) | 步骤配置;每步 { title, description?, fields? },fields 结构同 BizFormDialog |
| v-model | Record<string, unknown> | — | 聚合表单数据双向同步 |
| finish-text | string | '提 交' | 末步完成按钮文案 |
| prev-text / next-text | string | '上一步' / '下一步' | 导航按钮文案 |
| label-width | string / number | '100px' | 表单标签宽度 |
| draft-key | string | — | 草稿箱键(传入即启用):字段变化 800ms 防抖暂存 localStorage(含当前步索引),再次进入提示恢复 / 放弃;finish 后自动清理;跨标签页共享同键 |
其余 attrs 透传根容器。
Events
| 事件名 | 参数 | 说明 |
|---|---|---|
| update:modelValue | (values) | 任一字段变化即同步聚合数据 |
| change | (step: number) | 步骤切换 |
| finish | (values) | 末步校验通过点完成,携带聚合数据(草稿随之清理) |
| draft-save | (values) | 草稿落盘(800ms 防抖后触发) |
| draft-restore | (values) | 恢复条确认恢复后触发(携带恢复的聚合数据) |
Slots
| 插槽名 | 说明 |
|---|---|
| step-<index> | 自定义步骤内容(作用域:model / step);该步未配 fields 时生效 |
| footer | 自定义按钮区(作用域:active / prev / next / finish) |
Expose
| 属性/方法 | 说明 |
|---|---|
| active | 当前步索引 |
| model | 聚合表单模型(响应式) |
| next() / prev() | 下一步(带校验)/ 上一步 |
| goTo(index) | 跳转指定步(不校验) |
| clearDraft() | 清除草稿(draft-key 场景手动清理) |