从列表页到规则引擎:一个组件封装过程中的前端认知进阶

942 阅读6分钟

你是不是也写过几十个中后台页面:

  • 搜索表单重复粘贴?
  • 状态字段手动判断?
  • 时间字段反复 format?
  • 页面跳转状态丢失?

看起来你“很会写”,但其实你只是写得多。

这次我们来讲点硬的,不止封装功能,而是把一个列表页写成系统、抽成规则

🚩 一个最普通的中后台列表页

你熟得不能再熟的结构:

  • 搜索 + 表单
  • Tab 状态切换
  • 表格展示 + 分页
  • 编辑跳转 or 弹窗操作

以前我们怎么写?👇

<el-form>
  <el-input v-model="query.name" />
  <el-date-picker v-model="query.date" />
  <el-button @click="fetchData">搜索</el-button>
</el-form>

<el-table :data="tableData" />

写是能写,但每次都得重复处理这些:

  • query 对象手动构造
  • 表单字段手动初始化
  • 跳转回来状态丢了
  • loading、分页拼来拼去

🎯 我要的不是组件,而是规则系统

我希望的列表页只需要这样 👇

<SmartListView
  :form-rule="rule"
  :table-setup="tableSetup"
  :fetch-setup="fetchSetup"
  :tabs-static-list="tabsArr"
/>

剩下的交给组件:

✅ 表单结构自动渲染
✅ 默认值 + 时间字段自动处理
✅ 查询 / 分页 / 缓存集成
✅ Tab 切换自动记忆
✅ 自定义 slot 灵活扩展

🧱 设计重点拆解

1️⃣ 字段 schema + formModel 解耦

formSetup = [
  {
    type: 'datePicker',
    field: 'updateDate',
    title: '时间范围',
    value: [new Date(), moment().add(30, 'days').toDate()],
    props: { type: 'range' }
  }
]

🔍 schema 控结构,formModel 控值
🧠 这就是典型的配置驱动,字段即规则。

2️⃣ fetchSetup 接管一切行为

fetchSetup = {
  url: '/api/list',
  tabs: {
    tabFilterKey: 'status',
    formDefaults: {
      updateDate: [new Date(), moment().add(30, 'days').toDate()]
    }
  },
  timeAdapter: {
    updateDate: ['startTime', 'endTime']
  }
}

你不需要再 format时间字段,也不需要自己转换字段名了。

3️⃣ Tab 切换自动触发查询 + 状态缓存

  • 每个 Tab 对应一个搜索状态
  • 切换时恢复上一次状态
  • 再次挂载保留分页信息

这不就是“列表状态保持”的理想体验?

4️⃣ 搜索 + 清空统一封装

@submit → getFormData() → processTimeAdapter() → fetch()
@clear  → resetFields + clearStorage

再也不写 search()、reset()、page = 1,这些都应该在组件内搞定!

🔍 DSL:结构性思维的语言表达

🛠️ 打磨 DSL,是现在工作中最核心的底层建设。

我们正在构建的,不仅是一套配置语言,而是一个领域的“语义接口”。它不是静态的结构定义,而是连接人类、业务与系统之间的桥梁:

  • 它表达结构性思维:不是页面怎么写,而是页面该如何“被定义”;
  • 它支持系统智能化:只有结构清晰、语义明确的 DSL,才能被 AI 理解、被平台自动化处理;
  • 它决定了你的系统能不能规模化扩展,而不被代码复杂度拖垮。

你现在的角色,正在做三种底层的事情:

像什么?你在做的事
🧠 语言学家定义业务词汇、属性与组合规则
🧮 编译器工程师设计输入如何解释、转译为执行结构
🧱 知识工程师构建 AI 可理解的语义结构网格

💡 那 DSL 要怎么打磨?4 个关键角度:

✅ 1. DSL 的语义表达能力

  • 字段能否表达“行为语义”?(如输入型、展示型、计算型?)
  • 是否支持表征字段“用途”、“来源”、“依赖关系”?

✅ 2. DSL 的语法设计

  • 使用纯 JSON、类 YAML,还是定义一套更优雅的嵌套语法?
  • 是否支持复用结构、定义宏?能否表示字段继承与组合?

✅ 3. DSL 的可编排性与链式能力

  • 能否表达 field → rule → condition → feedback 的链式结构?
  • 是否支持条件可控的可视逻辑?下拉值的语义推荐来源?

✅ 4. DSL 的智能生成能力

  • 是否支持 AI 辅助补全字段定义?
  • 能否从表结构/文档/页面行为中提取 DSL 草稿?
  • 是否已有预留接口,用于 Prompt 自动生成/更新 DSL 模块?

这不是简单的“前端工程封装”,而是你在用 DSL 建立一个:

结构驱动的认知框架,平台可理解的语义语言,AI 可读取的接口协议。

这才是平台化工程中真正的“语言层系统能力”。

🧨 实战踩坑回顾(是真的踩了)

setValue 后值又被清空?

👉 是 v-model 自动同步值导致的,得放在 this.$nextTick 后。

❌ formDefaults 被污染?

👉 记得 deepClone,Vue 响应式会污染原始值!

❌ tabsArr 是接口来的,但子组件不更新?

👉 用 :key="smartListKey" 让组件重新挂载。

🧠 软件工程视角的组件设计

🧩 设计模式:

模式体现
策略模式不同字段转换策略
模板方法SmartListView 结构统一,slot 灵活插入
观察者模式watch formRule 响应变更

🧱 工程思想更重要:

  • 字段结构统一
  • 状态行为集中管理
  • 页面逻辑降到最低

🧬 真正的统一方案:我把一页内容,封成了一份 schema

经过一轮又一轮封装、踩坑、重构,我最终把所有列表页的结构 —— 表单、表格、请求、tabs —— 都抽象成了一个 pageSchema 配置对象。

从此,页面不再是写出来的,而是「定义出来的」。

<SmartListView :schema="pageSchema" />

一行组件调用,搞定整个列表页。

✅ 我实现了这些功能:

  • form.fields → 自动渲染搜索表单 + 默认值填充
  • table.columns → 自动生成表格结构 + 权限 + 字典映射
  • fetch.url → 统一封装查询逻辑 + 时间字段转换 + loading 管理
  • tabs.options → 动态状态 Tab + 状态缓存 + 自动切换请求
  • schema.transform → 自动把字段从组件值映射到接口字段(如日期 range)

再复杂的业务,只需要维护一份 schema 配置。

🔥 现在新增一个列表页只需要:

export const logPageSchema = {
  form: {{ fields: [...] }},
  table: {{ columns: [...] }},
  fetch: {{ url: '/api/logs' }},
};

以前你要写 300 行 Vue,现在你只需要写 30 行配置。

💬 配置是抽象能力

很多人一看到 schema 就说:

“太麻烦了,还不如直接写。”

但你要知道:

写代码是为了抽象,抽象是为了规模化。

v-model 和写 schema,不是谁轻松谁牛逼,而是:

谁能写出 更多人复用未来还能自演化 的页面,谁就赢了。

🎯 思考:你以为你在写页面,其实你在构建规则系统

封装 SmartListView 的过程,不是把逻辑揉进组件,而是把行为抽象成规则,把重复变成结构。

你在做的,不只是「减少代码量」,而是:

  • 让表单字段具备声明式规则性
  • 让页面状态具备可还原能力
  • 让组件逻辑具备平台通用性

SmartListView 渲染实现结构图

image.png 真正的高级工程感,是这样的:

  • 字段有定义中心
  • 状态有恢复机制
  • 表单有 schema 驱动
  • 页面不再“实现功能”,而是“组合能力”

到这一步,你不再是“封装组件的人”,而是:

在定义规则的人
在推动系统的人
在构建平台能力的人

总结一句话:页面≠代码堆砌,字段≠值输入,表单≠UI控件。
真正的前端能力,是让规则生成页面,而不是手写页面。

如果你也在封装列表页、踩坑表单 schema、搞不定状态回填——欢迎评论区分享,我们一起把组件写成系统,写成能力!

🏁 结语:页面 ≠ 技术,规则才是资产

页面能跑只是初级
页面清晰是中级
页面可控是高级

真正的高级前端,不写页面,而是定义页面怎么写。

欢迎留言交流 👉 你最近写的列表页,还在重复写啥?