数据即 UI —— Vue.js 打造 Schema 驱动的动态前端
在传统前端开发中,表单页面是这样写的: 每个 要解决这个问题,前端必须与业务逻辑完全解耦:前端只提供原子化的组件和布局容器,页面的拓扑形态完全由后端下发的一份 JSON 元数据(Schema)动态决定。 这种架构在 Salesforce 中被称为 FlexiPage 和 Layout 体系。 在动手写前端代码之前,我们需要与后端确立一套接口规范。前端发起请求: 后端返回如下结构: 注意那个关键的 首先,将底层 UI 库的组件封装为符合 Meta 规范的原子组件: 同理封装 核心魔法是 Vue 的内置 这套代码中最精妙的一笔是 我们完全不用操心有多少个字段、什么嵌套结构。点击"提交"时, 整个流程分为 4 步:MetaForm 低代码引擎系列 · 第 2 篇
技术栈:Vue.js 3 + Composition API + 动态组件一、前端硬编码的终结
<!-- 硬编码的噩梦 -->
<el-form>
<el-input v-model="form.name" placeholder="姓名" />
<el-select v-model="form.gender">
<el-option label="男" value="male" />
<el-option label="女" value="female" />
</el-select>
<el-date-picker v-model="form.birthday" />
</el-form><input>、每个 <select> 都硬编码在 .vue 文件中。这种做法在低代码系统中无法存活:二、定义 Layout Schema 协议
GET /api/layout/{form_id}{
"layout_id": "lay_1001",
"title": "入职申请表",
"action_url": "/api/data/frm_1001",
"sections": [
{
"section_id": "sec_basic",
"section_title": "基础信息",
"columns": [
{
"items": [
{
"field_name": "employee_name",
"field_type": "string",
"component_type": "MetaInput",
"label": "姓名",
"required": true,
"max_length": 50,
"placeholder": "请输入真实姓名"
}
]
},
{
"items": [
{
"field_name": "gender",
"field_type": "string",
"component_type": "MetaSelect",
"label": "性别",
"required": false,
"options": ["男", "女"]
}
]
}
]
},
{
"section_id": "sec_detail",
"section_title": "详细信息",
"columns": [
{
"items": [
{
"field_name": "join_date",
"field_type": "date",
"component_type": "MetaDate",
"label": "入职日期"
}
]
}
]
}
]
}component_type 字段——它将指导 Vue 引擎进行组件的动态装载。三、Vue 3 动态渲染引擎实现
3.1 原子组件封装
<!-- MetaInput.vue -->
<template>
<!-- 动态读取 schema 中的 rules,转化为底层 UI 库的属性 -->
<el-form-item :label="schema.label" :required="schema.required">
<el-input
v-model="internalValue"
:placeholder="schema.placeholder"
:maxlength="schema.max_length"
:show-word-limit="!!schema.max_length"
/>
</el-form-item>
</template>
<script setup>
import { computed } from "vue";
const props = defineProps({
schema: Object,
modelValue: [String, Number],
});
const emit = defineEmits(["update:modelValue"]);
const internalValue = computed({
get: () => props.modelValue,
set: (val) => emit("update:modelValue", val),
});
</script>MetaSelect.vue、MetaDate.vue 等原子组件。架构师提示(关于动态校验规则下发):底层的原子组件不仅负责渲染 UI,更需要忠实地继承 Layout Schema 中定义的业务规则。如上面的
MetaInput,通过直接读取 JSON 中的 required 和 max_length 属性,并绑定到 <el-input> 上,前端无需硬编码任何繁复的校验逻辑,便自然拥有了浏览器和 UI 库提供的表单拦截能力。3.2 动态 Component Factory 解析器
<component> 指令,结合 is 属性:<!-- DynamicFormParser.vue -->
<template>
<div class="dynamic-form" v-if="layoutSchema">
<h2>{{ layoutSchema.title }}</h2>
<div
v-for="section in layoutSchema.sections"
:key="section.section_id"
class="form-section"
>
<h3>{{ section.section_title }}</h3>
<!-- 细化层级结构:遍历列 (Column) -->
<div class="section-layout" style="display: flex; gap: 24px;">
<div
v-for="(col, colIdx) in section.columns"
:key="colIdx"
class="layout-column"
style="flex: 1;"
>
<!-- 引擎核心:<component :is> 动态加载该列中的 items -->
<component
v-for="item in col.items"
:key="item.field_name"
:is="getComponent(item.component_type)"
:schema="item"
v-model="formData[item.field_name]"
/>
</div>
</div>
</div>
<el-button type="primary" @click="submitData">提交表单</el-button>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import axios from "axios";
import MetaInput from "./components/MetaInput.vue";
import MetaSelect from "./components/MetaSelect.vue";
import MetaDate from "./components/MetaDate.vue";
const props = defineProps({ formId: String });
const layoutSchema = ref(null);
const formData = ref({}); // 核心:所有动态组件的数据归宿
const componentMap = { MetaInput, MetaSelect, MetaDate };
const getComponent = (typeStr) => componentMap[typeStr] || MetaInput;
onMounted(async () => {
const { data } = await axios.get(`/api/layout/${props.formId}`);
layoutSchema.value = data;
});
const submitData = async () => {
// formData.value 就是完美的 JSON Payload
await axios.post(layoutSchema.value.action_url, formData.value);
};
</script>
<style scoped>
.grid {
display: grid;
gap: 16px;
}
.form-section {
margin-bottom: 24px;
}
</style>四、双向绑定的精髓
v-model="formData[item.field_name]"。formData 是一个初始化为空的 ref({}) 对象。当 Vue 渲染包含 field_name: "employee_name" 的组件时,它会自动在 formData 中创建键值 formData.employee_name,并通过 update:modelValue 事件实现双向绑定。formData.value 里就是一份完美的、准备好塞给后端 JSONB payload 的数据体。五、Schema 驱动渲染管线图解

<component :is="..."> 解析器读取 JSON 并进行路由分发v-model 双向绑定到集中的 JSON Payload State 中小结
formData 统一收集所有组件的值,与后端 DML 引擎无缝对接下一篇预告:前端收集好了
formData,这份 JSON Payload 如何安全地经过类型转换、规范化编码后落入 PostgreSQL 的 JSONB 堆表?请看第 3 篇《运行时数据引擎 —— DML 拦截与 JSONB 检索》。