ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

第 6 篇:SchemaForm 渲染器核心实现

第 6 篇:SchemaForm 渲染器核心实现 第 6 篇SchemaForm 渲染器核心实现本篇目标写出本系列的心脏一个能消费第 5 篇 Schema、自动渲染出表单的SchemaForm.vue。约 200 行复制进项目即可用。0. 先确认组件都装好了第 2 篇已装input textarea select radio-group checkbox switch label button card。导入路径以 shadcn-vue 生成的src/components/ui/*为准例如import{Input}from/components/ui/inputimport{Select,SelectContent,SelectItem,SelectTrigger,SelectValue}from/components/ui/select1. 渲染器SchemaForm.vue创建src/components/form/SchemaForm.vuescript setup langts import { computed } from vue import { Input } from /components/ui/input import { Textarea } from /components/ui/textarea import { Label } from /components/ui/label import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from /components/ui/select import { RadioGroup, RadioGroupItem } from /components/ui/radio-group import { Checkbox } from /components/ui/checkbox import { Switch } from /components/ui/switch import type { FieldSchema } from ./schema const props defineProps{ fields: FieldSchema[] modelValue: Recordstring, any columns?: number }() const emit defineEmits{ (e: update:modelValue, value: Recordstring, any): void }() const gridStyle computed(() ({ gridTemplateColumns: repeat(${props.columns ?? 2}, minmax(0, 1fr)), })) // 更新单个字段触发父级 v-model function setField(field: FieldSchema, value: any) { emit(update:modelValue, { ...props.modelValue, [field.name]: value }) } // 统一入口number 转数字其余原样 function onUpdate(field: FieldSchema, value: any) { setField(field, field.type number ? Number(value) : value) } // 联动显隐第 7 篇详述 function visible(field: FieldSchema): boolean { const sw field.showWhen if (!sw) return true return props.modelValue[sw.field] sw.equals } // 基础校验必填 正则 function errorOf(field: FieldSchema): string { const v props.modelValue[field.name] if (field.required (v undefined || v null || v )) { return ${field.label}为必填 } if (field.pattern typeof v string v !new RegExp(field.pattern).test(v)) { return ${field.label}格式不正确 } return } /script template div classgrid gap-[var(--spacing-field)] :stylegridStyle template v-forfield in fields :keyfield.name div v-ifvisible(field) classmin-w-0 :stylefield.span 2 ? { gridColumn: 1 / -1 } : undefined Label :forf-${field.name} classmb-1.5 block text-foreground {{ field.label }}span v-iffield.required classml-0.5 text-destructive*/span /Label !-- 单行文本 / 数字 -- Input v-iffield.type input || field.type number :idf-${field.name} :typefield.type number ? number : text :model-valuemodelValue[field.name] ?? :placeholderfield.placeholder :disabledfield.disabled update:model-valueonUpdate(field, $event) / !-- 多行文本 -- Textarea v-else-iffield.type textarea :idf-${field.name} :model-valuemodelValue[field.name] ?? :placeholderfield.placeholder :disabledfield.disabled update:model-valueonUpdate(field, $event) / !-- 日期先用原生 input 保证稳定想升级见第 7 篇 -- Input v-else-iffield.type date :idf-${field.name} typedate :model-valuemodelValue[field.name] ?? :disabledfield.disabled update:model-valueonUpdate(field, $event) / !-- 下拉 -- Select v-else-iffield.type select :model-valuemodelValue[field.name] ?? :disabledfield.disabled update:model-valueonUpdate(field, $event) SelectTrigger :idf-${field.name} classw-full SelectValue :placeholderfield.placeholder ?? 请选择 / /SelectTrigger SelectContent SelectItem v-foro in field.options ?? [] :keyo.value :valueo.value {{ o.label }} /SelectItem /SelectContent /Select !-- 单选组 -- RadioGroup v-else-iffield.type radio :model-valuemodelValue[field.name] ?? :disabledfield.disabled update:model-valueonUpdate(field, $event) classflex flex-wrap gap-4 div v-foro in field.options ?? [] :keyo.value classflex items-center gap-2 RadioGroupItem :id${field.name}-${o.value} :valueo.value / Label :for${field.name}-${o.value}{{ o.label }}/Label /div /RadioGroup !-- 复选 -- Checkbox v-else-iffield.type checkbox :idf-${field.name} :checked!!modelValue[field.name] :disabledfield.disabled update:checkedonUpdate(field, $event) / !-- 开关 -- Switch v-else-iffield.type switch :idf-${field.name} :checked!!modelValue[field.name] :disabledfield.disabled update:checkedonUpdate(field, $event) / p v-iffield.help classmt-1 text-[var(--text-caption)] text-muted-foreground {{ field.help }} /p p v-iferrorOf(field) classmt-1 text-[var(--text-caption)] text-destructive {{ errorOf(field) }} /p /div /template /div /template说明update:checked是 shadcn-vue 的 Checkbox / Switch 的真实事件名对应checkedprop。若你的版本事件名有出入以src/components/ui/checkbox、switch源码里的defineEmits为准。2. 一个可直接运行的表单页创建src/pages/CustomerPage.vuescript setup langts import { reactive, ref } from vue import PageShell from /components/layout/PageShell.vue import PageHeader from /components/layout/PageHeader.vue import SectionCard from /components/layout/SectionCard.vue import SchemaForm from /components/form/SchemaForm.vue import { Button } from /components/ui/button import type { PageSchema } from /components/form/schema const schema: PageSchema { title: 客户资料, columns: 2, fields: [ { name: name, label: 姓名, type: input, required: true, placeholder: 请输入姓名 }, { name: gender, label: 性别, type: radio, options: [{ label: 男, value: male }, { label: 女, value: female }] }, { name: birth, label: 出生日期, type: date }, { name: phone, label: 手机号, type: input, pattern: ^1\\d{10}$, placeholder: 11 位手机号 }, { name: dept, label: 部门, type: select, options: [{ label: 研发, value: rd }, { label: 产品, value: pm }] }, { name: active, label: 启用, type: switch, default: true }, { name: remark, label: 备注, type: textarea, span: 2 }, ], } // 用 default 初始化 const form reactiveRecordstring, any({}) for (const f of schema.fields) { if (f.default ! undefined) form[f.name] f.default } function submit() { console.log(提交数据, form) } /script template PageShell PageHeader :titleschema.title ?? description配置驱动表单示例 template #actionsButton clicksubmit保存/Button/template /PageHeader SectionCard SchemaForm v-modelform :fieldsschema.fields :columnsschema.columns / /SectionCard /PageShell /templatepnpm dev打开页面即可看到由 schema 渲染出的完整表单必填项留空提交会提示。3. 为什么这样设计对应第 1 篇的两层分离渲染器只做「类型 → 组件」的机械映射不掺业务加新页面 复制 CustomerPage 的壳 换一份 schemaAI 后续只改 schema永远碰不到渲染器与布局4. 对照现成库什么时候值得用真实存在、可替代自研的方案均为已核实项目方案特点何时考虑FormKitVue3 原生JSON Schema 支持FormKitSchema内置校验/主题需要复杂校验和视觉且接受它自带一套 UI 风格Formily (Vue)阿里标准 JSON Schema 驱动跨框架需要复杂联动、跨端能接受较重依赖vue-json-schema-form基于 JSON Schema 渲染表单支持 Vue3想直接用标准 JSON Schema 规范本系列选自研因为代码量小200 行、样式完全受 Tokens 控制、字段体系按自用收敛。如果你的需求开始超过自研能撑的复杂度嵌套子表、复杂联动、可视化配置再考虑上面三者——这是诚实边界。5. 验证页面能渲染出全部 7 种字段类型input/textarea/number/select/radio/checkbox/switch/date。必填字段留空控制台提交时能看到或页面出现错误提示。改schema里任意字段的label刷新后文案跟着变其余不动。明暗切换后表单样式跟随 Tokens。本篇小结渲染器把 schema 变成表单是「配置驱动」的落地点自研 200 行够用需求复杂度上去了再换现成库下一步把校验、联动、列表页补上第 7 篇进阶
返回列表