你是不是也写过几十个中后台页面:
- 搜索表单重复粘贴?
- 状态字段手动判断?
- 时间字段反复 format?
- 页面跳转状态丢失?
看起来你"很会写",但其实你只是写得多。
这次我们来讲点硬的,不止封装功能,而是把一个列表页写成系统、抽成规则。
🚩 一个最普通的中后台列表页
你熟得不能再熟的结构:
- 搜索 + 表单
- Tab 状态切换
- 表格展示 + 分页
- 编辑跳转 or 弹窗操作
以前我们怎么写?👇
vue
<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、分页拼来拼去
🎯 我要的不是组件,而是规则系统
我希望的列表页只需要这样 👇
vue
<SmartListView
:form-rule="rule"
:table-setup="tableSetup"
:fetch-setup="fetchSetup"
:tabs-static-list="tabsArr"
/>
剩下的交给组件:
✅ 表单结构自动渲染
✅ 默认值 + 时间字段自动处理
✅ 查询 / 分页 / 缓存集成
✅ Tab 切换自动记忆
✅ 自定义 slot 灵活扩展
🧱 设计重点拆解
1️⃣ 字段 schema + formModel 解耦
js
formSetup = [
{
type: 'datePicker',
field: 'updateDate',
title: '时间范围',
value: [new Date(), moment().add(30, 'days').toDate()],
props: { type: 'range' }
}
]
🔍 schema 控结构,formModel 控值
🧠 这就是典型的配置驱动,字段即规则。
2️⃣ fetchSetup 接管一切行为
js
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️⃣ 搜索 + 清空统一封装
js
@submit → getFormData() → processTimeAdapter() → fetch()
@clear → resetFields + clearStorage
再也不写 search()、reset()、page = 1,这些都应该在组件内搞定!
🧨 实战踩坑回顾(是真的踩了)
❌ setValue
后值又被清空?
👉 是 v-model 自动同步值导致的,得放在 this.$nextTick
后。
❌ formDefaults 被污染?
👉 记得 deepClone
,Vue 响应式会污染原始值!
❌ tabsArr 是接口来的,但子组件不更新?
👉 用 :key="smartListKey"
让组件重新挂载。
🧠 软件工程视角的组件设计
🧩 设计模式:
模式 | 体现 |
---|---|
策略模式 | 不同字段转换策略 |
模板方法 | SmartListView 结构统一,slot 灵活插入 |
观察者模式 | watch formRule 响应变更 |
🧱 工程思想更重要:
- 字段结构统一
- 状态行为集中管理
- 页面逻辑降到最低
🧬 真正的统一方案:我把一页内容,封成了一份 schema
经过一轮又一轮封装、踩坑、重构,我最终把所有列表页的结构 ------ 表单、表格、请求、tabs ------ 都抽象成了一个 pageSchema
配置对象。
从此,页面不再是写出来的,而是「定义出来的」。
ini
<SmartListView :schema="pageSchema" />
一行组件调用,搞定整个列表页。
✅ 我实现了这些功能:
form.fields
→ 自动渲染搜索表单 + 默认值填充table.columns
→ 自动生成表格结构 + 权限 + 字典映射fetch.url
→ 统一封装查询逻辑 + 时间字段转换 + loading 管理tabs.options
→ 动态状态 Tab + 状态缓存 + 自动切换请求schema.transform
→ 自动把字段从组件值映射到接口字段(如日期 range)
再复杂的业务,只需要维护一份 schema 配置。
🔥 现在新增一个列表页只需要:
css
export const logPageSchema = {
form: {{ fields: [...] }},
table: {{ columns: [...] }},
fetch: {{ url: '/api/logs' }},
};
以前你要写 300 行 Vue,现在你只需要写 30 行配置。
💬 配置是抽象能力
很多人一看到 schema 就说:
"太麻烦了,还不如直接写。"
但你要知道:
写代码是为了抽象,抽象是为了规模化。
写 v-model
和写 schema
,不是谁轻松谁牛逼,而是:
谁能写出 更多人复用 、未来还能自演化 的页面,谁就赢了。
🎯 思考:你以为你在写页面,其实你在构建规则系统
封装 SmartListView
的过程,不是把逻辑揉进组件,而是把行为抽象成规则,把重复变成结构。
你在做的,不只是「减少代码量」,而是:
- 让表单字段具备声明式规则性
- 让页面状态具备可还原能力
- 让组件逻辑具备平台通用性
SmartListView 渲染实现结构图
真正的高级工程感,是这样的:
- 字段有定义中心
- 状态有恢复机制
- 表单有 schema 驱动
- 页面不再"实现功能",而是"组合能力"
到这一步,你不再是"封装组件的人",而是:
在定义规则的人
在推动系统的人
在构建平台能力的人
总结一句话:页面≠代码堆砌,字段≠值输入,表单≠UI控件。
真正的前端能力,是让规则生成页面,而不是手写页面。
如果你也在封装列表页、踩坑表单 schema、搞不定状态回填------欢迎评论区分享,我们一起把组件写成系统,写成能力!
🏁 结语:页面 ≠ 技术,规则才是资产
页面能跑只是初级
页面清晰是中级
页面可控是高级
真正的高级前端,不写页面,而是定义页面怎么写。
欢迎留言交流 👉 你最近写的列表页,还在重复写啥?