如何用一份 JSON 配置搞定“法律计算器”的动态表单
引言:小明的工伤赔偿奇遇记
想象一下,当事人小明打开我们的“法律计算器”小程序,想算算工伤赔偿。
- 场景 A:他手抖选了“劳动关系”,页面立刻弹出“月工资是多少?”;
- 场景 B:他改主意选了“交通事故”,页面瞬间变身,开始问“伤残等级”;
- 场景 C:他填了个“30000”的月薪,系统立马提示:“哥们,这超过社平工资 3 倍了,你确定没填错?”(后端异步校验)。
作为开发者,你是不是已经开始头疼了?如果针对劳动争议、交通事故、借贷纠纷等等法律业务都分别写一个 .vue 页面,光是维护 v-if/v-else 就得掉一半头发。万一明天产品经理说:“在这个表单中间加个‘案发地点’字段”,你是不是得发版重新提审?
拒绝写死代码! 今天我们来聊聊如何用 数据驱动UI 架构,打造一个“千人千面”的法律计算器。
一、 核心原理:把前端做成“乐高底板”
在传统的开发模式中,前端是“建筑师”,负责设计页面结构(Template);后端只是“搬砖工”,负责提供数据(Data)。
但在 数据驱动UI 架构下,角色反转了。前端退化成了一块纯粹的 “乐高底板”,而后端发来的 JSON 配置,就是那张 “搭建图纸”。前端不关心业务逻辑,只负责一件事:给什么积木,就搭什么房子。
1. 渲染引擎:v-for 的魔法
让我们看看核心渲染引擎 customForm/index.vue 是如何工作的。它的核心逻辑极其简单,就像是在遍历一份清单:
<!-- components/customForm/index.vue (精简版) -->
<template>
<view class="custom-form">
<!-- 第一层循环:遍历表单分组 (Section) -->
<view v-for="(section, sIndex) in formConfig" :key="sIndex">
<view class="section-title">{{ section.title }}</view>
<!-- 第二层循环:遍历具体的题目 (Items) -->
<view v-for="(item, iIndex) in section.items" :key="iIndex">
<!-- 积木 A:普通输入框 (component === 3) -->
<u-form-item v-if="item.component === 3" :label="item.title">
<u-input v-model="formData[item.qId]" />
</u-form-item>
<!-- 积木 B:选择器 (component === 4) -->
<u-form-item v-if="item.component === 4" :label="item.title">
<view @click="openPicker(item)">{{ getLabel(item) }}</view>
</u-form-item>
<!-- 积木 C:复杂的利率选择器 (component === 9) -->
<rate-selector
v-if="item.component === 9"
:init-value="formData[item.qId]"
/>
</view>
</view>
</view>
</template>
前端不再写死 <input> 或 <select>,而是根据 JSON 中的 component 字段(3 代表输入框,4 代表选择器,9 代表复杂组件)动态决定渲染什么。
2. 每次修改表单都动态获取JSON数据
最精彩的部分来了。既然前端不写 v-if="salary > 30000",那条件分支怎么实现?
答案是:不要在前端做逻辑判断,把用户的每一次交互都告诉后端。
这是一个 (问后端) 的过程。在具体业务代码中,我们监听了表单的每一次变更:
// pages/enterpriseLaw/legalCalculator/form.vue
// 1. 用户修改了答案
handleFormChange(newAnswer) {
// 更新本地答案池
this.updateAnswers(newAnswer);
// 2. 核心:带着当前的答案,去问后端“下一步该展示什么?”
this.getDynamic();
},
async getDynamic() {
// 3. 调用接口,把所有已填答案扔给后端
const payload = {
appId: this.appId,
answers: this.answers
};
// 4. 后端的大脑开始飞速运转,计算出新的题目列表
const res = await this.$api.getDynamic(payload);
// 5. 前端拿到新的 JSON,Vue 自动 diff 更新视图
this.questions = res.data.questions;
}
点睛之笔:这就是“一份 JSON 配置”的真相。逻辑在后端,前端只是负责画图的“画笔”。 这样一来,无论是增加题目、修改逻辑分支,还是调整校验规则,都只需要后端改配置,前端代码一行都不用动!
二、 难点攻克:细节决定成败
痛点一:嵌套条件分支的“配置化”
如果题目之间有复杂的嵌套关系(例如 是否有借款 -> 有几笔 -> 第一笔利息 -> 怎么算的),JSON 结构该怎么设计?
我们采用 Section (分组) -> Group (实例) -> Items (题目) 的三层结构。Vue 的响应式系统在这里帮了大忙。当后端返回的 questions 数组发生变化(比如因为你选了“有借款”,数组里多了一个“借款详情”的 Section),Vue 会自动检测到数据的变化,并高效地修补 DOM。
// 后端返回的 JSON 结构示意
[
{
"groupTitle": "基本信息",
"items": [ ... ]
},
{
"groupTitle": "借款详情", // 只有当用户选了“有借款”才会返回这个 Section
"isGroup": true, // 标记为可重复的分组(如多笔借款)
"items": [ ... ]
}
]
痛点二:无缝嵌入异步校验
有些校验前端做不了,比如“赔偿金是否符合当地最新的法律标准”。这时候,我们需要把校验权也交给后端。
在代码中,我们设计了一个巧妙的 backendErrors 机制:
-
用户填完:触发
validateByBackend。 -
后端校验:发现 Q101 题目的金额填错了,返回错误 Map:
{ "q_101": "金额过大,请确认" }。 - 前端标红:
// customForm/index.vue
// 监听后端传来的错误对象
props: ['backendErrors'],
// 在模板中精准展示错误
<view v-if="backendErrors[item.qId]" class="backend-error">
{{ backendErrors[item.qId] }}
</view>
这样,异步的业务校验就像本地校验一样自然流畅,用户根本感觉不到请求的延迟。
痛点三:原子组件的扩展(RateSelector)
这时候有人会问:“如果我要一个超级复杂的组件,比如‘LPR 利率计算器’,JSON 配置能描述清楚吗?”
当然可以!这就是 数据驱动UI 的灵活性。我们不需要用 JSON 描述组件内部的每一个 div,而是把这个复杂组件封装成一个原子积木。
看看 components/customForm/RateSelector.vue,它内部包含了:
- 日/月/年利率的切换
- 百分比/千分比的换算
- LPR 动态查询
但在表单引擎眼里,它只是一个普通的积木:
// 如果 component 代码是 9,我就渲染 RateSelector
<rate-selector v-if="rawItem.component === 9" ... />
这样,我们既保持了引擎的通用性(处理普通输入框),又保留了处理复杂业务的能力(通过自定义组件扩展)。
三、 总结:从“搬砖”到“搭积木”
数据流转图
最后,让我们用一张图来总结整个流程:
graph TD
User[用户输入] -->|触发| Event[handleFormChange]
Event -->|携带 Current Answers| API[调用 getDynamic 接口]
API -->|发送至| Server[后端逻辑大脑]
Server -->|计算条件分支/校验| Config[生成新的 JSON 配置]
Config -->|返回| Frontend[前端 Vue 引擎]
Frontend -->|Vue Reactivity| DOM[界面无感刷新]
DOM -->|展示| User
架构优势
-
配置热更新: 运营人员想在表单里加一个“备注”字段?改一下数据库里的 JSON 配置就行了。用户端不需要发版,不需要更新,打开小程序就能看到新字段。这在法律法规频繁变动的行业简直是救命稻草。
-
逻辑复用: 我们只写了一套
customForm引擎,却同时支持了“劳动争议”、“交通事故”、“民间借贷”等等法律业务的计算器。每个计算器只是后端数据库里的一条配置记录而已。
“偷懒”是程序员的第一生产力。 把复杂的逻辑甩给后端,把繁琐的渲染交给引擎,我们前端开发者,终于可以安心地喝一杯咖啡了。☕️
如果你对这套代码感兴趣,欢迎在评论区留言