配置驱动页面:表单、表格到「低代码」的第一步
目录
在中后台项目里,经常会出现这样的需求:
- 一堆「结构相似、字段不同」的表单;
- 一堆「上面查询、下面表格」的列表页。
如果每个页面都手写一次:表单 + 校验 + 表格列 + 操作按钮,不仅枯燥,还很难保持统一风格。
这篇文章讲的是我在项目里做的一次尝试:
用一份 JSON 配置描述页面,再写一个「解释器组件」负责渲染表单和表格。
这是往低代码方向迈出的第一小步。
1. 先从「配置表单」开始
以一个常见的查询表单为例,我们希望用一份配置来描述:
- 有哪些字段;
- 每个字段是什么类型(输入框、下拉、日期范围等);
- 默认值、占位文案、选项等等。
1.1 配置结构示例
// types.ts
export type FieldType = 'input' | 'select' | 'daterange' | 'switch';
export interface FormField {
prop: string;
label: string;
type: FieldType;
placeholder?: string;
options?: Array<{ label: string; value: string | number }>;
span?: number; // 栅格宽度
required?: boolean;
}
某个页面里的配置:
// orderListPageConfig.ts
export const searchFields: FormField[] = [
{ prop: 'keyword', label: '关键词', type: 'input', placeholder: '订单号/客户名' },
{ prop: 'status', label: '状态', type: 'select', options: [
{ label: '全部', value: '' },
{ label: '待支付', value: 'PENDING' },
{ label: '已支付', value: 'PAID' },
]},
{ prop: 'dateRange', label: '创建时间', type: 'daterange' },
];
1.2 通用 FormRenderer 组件
<!-- FormRenderer.vue(示意) -->
<template>
<el-form :model="model" class="config-form" label-width="90px">
<el-row :gutter="16">
<el-col
v-for="field in fields"
:key="field.prop"
:span="field.span || 8"
>
<el-form-item :label="field.label" :prop="field.prop">
<component
:is="getComponent(field)"
v-model="model[field.prop]"
v-bind="getComponentProps(field)"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<el-button type="primary" @click="$emit('search', model)">查询</el-button>
<el-button @click="onReset">重置</el-button>
</el-form-item>
</el-form>
</template>
getComponent 根据类型返回不同组件:
function getComponent(field: FormField) {
switch (field.type) {
case 'input':
return 'el-input';
case 'select':
return 'el-select';
case 'daterange':
return 'el-date-picker';
default:
return 'el-input';
}
}
getComponentProps 返回对应属性(占位、选项等)。
使用时页面只需要:
<FormRenderer
:fields="searchFields"
v-model="searchModel"
@search="handleSearch"
/>
2. 再把「表格」也配置起来
同理,表格列也可以用配置描述:
export type ColumnType = 'text' | 'money' | 'datetime' | 'statusTag';
export interface TableColumn {
prop: string;
label: string;
width?: number;
type?: ColumnType;
align?: 'left' | 'center' | 'right';
formatter?: (row: any) => string;
}
示例:
export const tableColumns: TableColumn[] = [
{ prop: 'orderNo', label: '订单号', width: 180 },
{ prop: 'customerName', label: '客户姓名' },
{ prop: 'amount', label: '金额', type: 'money', align: 'right' },
{ prop: 'status', label: '状态', type: 'statusTag' },
{ prop: 'createdAt', label: '创建时间', type: 'datetime', width: 160 },
];
通用 <TableRenderer> 组件根据配置渲染:
<el-table :data="data">
<el-table-column
v-for="col in columns"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:align="col.align || 'left'"
>
<template #default="{ row }">
<span v-if="col.type === 'money'">
{{ formatMoney(row[col.prop]) }}
</span>
<span v-else-if="col.type === 'datetime'">
{{ formatDateTime(row[col.prop]) }}
</span>
<StatusTag
v-else-if="col.type === 'statusTag'"
:value="row[col.prop]"
/>
<span v-else>
{{ col.formatter ? col.formatter(row) : row[col.prop] }}
</span>
</template>
</el-table-column>
</el-table>
3. 拼装成「配置驱动列表页」
最后,我们再加上分页和操作区,用一个通用 ListPage 组件把这些组合起来:
<ListPage
:search-fields="searchFields"
:columns="tableColumns"
:request="fetchOrderList"
/>
内部做的事情:
- 使用
FormRenderer渲染查询区; - 监听 search 事件发起请求;
- 使用
TableRenderer渲染表格; - 管理分页信息、loading 状态;
- 为常见操作预留插槽(比如顶部按钮、自定义列等)。
页面本身只负责:
- 提供配置;
- 实现
fetchOrderList请求; - 处理业务上的自定义操作(导出、批量操作等)。
4. 这算不算低代码?
严格意义上,它还不是「给运营可视化拖拽」那种低代码平台,但它具备了几个低代码的核心特征:
-
页面结构由配置驱动
- 改一个字段、加一个查询条件,只要改配置;
- 结构统一、风格一致。
-
表单和表格的「解释器」可复用
- 新开一个类似的页面,成本非常低;
- 只要写配置而不是从零写模板。
-
有能力进一步抽象到可视化
- 后续完全可以在后台给这些 JSON 配置配一套可视化编辑器;
- 前端渲染层不需要改。
所以可以把它看作是:从「纯代码写死页面」向「配置驱动」过渡的第一步。
5. 踩坑与权衡
-
配置不要「无限泛化」
- 一上来就支持各种嵌套布局、奇怪交互,配置会很快失控;
- 建议先 cover 70% 常见场景,剩下的靠插槽自定义。
-
调试体验要考虑
- 对开发同学来说,完全看不到模板可能会不适应;
- 可以在文档里配一些「配置 → 渲染结果」的示例截图,降低心智负担。
-
和设计规范打通
- 配置里如果乱写样式,会破坏统一性;
- 把样式收敛到组件内部,让配置更专注于“结构与含义”。
6. 小结
配置驱动页面,本质是在做三件事:
- 把页面中高度重复的结构(表单、表格、分页)抽出解释器;
- 用一份 JSON 来描述「页面长什么样」;
- 让新增/修改页面变成「改配置」而不是「改模板」。
它还谈不上「完整低代码平台」,但已经能明显减少重复劳动、统一交互体验,也为以后更上层的可视化搭建预留了空间。