告别“CV工程师”:手把手教你设计一套 B 端低代码 DSL

本文内容引用 哲玄-大前端全栈实践

1. 咱们的痛点:为什么天天在写重复代码?

兄弟们,做 B 端后台开发的日常是不是这样的? 早上来了,产品经理说:"加个用户管理页面。" 你想了想:

  1. 写个 Router 路由。
  2. 画个 Search Bar(搜索栏),里面放 Input 和 Select。
  3. 画个 Table(表格),搞定分页逻辑。
  4. 搞个 Dialog(弹窗),写表单验证。
  5. 调 API,绑定数据......

下午,产品经理又来了:"再加个订单管理页面。" 你一看,这特么跟上午那个页面长得有 90% 是一样的啊! 只是字段从"用户名"变成了"订单号",接口换了一个而已。

于是,我们变成了毫无感情的 Ctrl+C / Ctrl+V 机器

这套 DSL 的设计初衷就是: 把那 80% 重复的 "头部、侧边栏、搜索、表格"抽象成 JSON 配置;剩下的 20% 复杂的逻辑,留给你去挥洒才华。


先附上图和完整的DSL配置

js 复制代码
module.exports = {
  "mode": "dashboard",
  // 头部菜单
  "menu": [{
    // 菜单唯一描述
    "key": "",
    // 菜单name
    "name": "",
    // 菜单类型 group分组 / module
    "menuType": "",
    // 子菜单 当menuType为group时有效
    "subMenu": [{

    }, ...],
    // 菜单行为,当menuType为module时有效。 枚举值:iframe / custom / schema / sider
    "moduleType": "",
    // 当moduleType为sider时有效
    siderConfig:{
      menu:[{},...]
    },
    // 当moduleType为iframe时有效
    iframeConfig: {
      path: "", // iframe地址
    },
    // 当moduleType为custom时有效
    customConfig: {
      path: "" // 自定义路由路径
    },
    // 当moduleType为schema时有效
    schemaConfig: {
      api: "", // 数据源api地址,遵循restful风格
      // json-schema描述
      schema: {
        type: "object",
        properties: {
          key: {
            ...schema, //标准json-schema描述
            type: "string",// 字段类型
            label: "字段名称",// 字段名称
            // 字段在table中的配置
            tableOption:{
              ...elTableColumnConfig, // 标准el-table-column配置
              tofixed: 2, // 数字类型时的小数位数
              visible: true // 是否在table中显示
            },
            // 字段在search-bar中的配置
            searchOption:{
              ...elComponentConfig, // 标准el-component-column组件配置
              comType:'', // 配置组件类型 input/select/dynamicSelect/date-picker/date-range等
              default:'', // 默认值
              // comType为select生效
              enumList:[
                {
                  label:'',
                  value:''
                }
              ],
              // comType为dynamicSelect生效
              api:''
            }
            
          },
          ...
        },
      },
      tableConfig: {
        headerButtons: [
          {
            label:'', // 按钮名称
            eventKey:'', // 按钮事件名
            eventOption:{
               // 按钮事件参数配置
              params:{
                "paramsKey":"schema::fieldKey" // schema:: 开头表示取schema中的字段值
              }
            }, // 按钮事件配置
            ...elButtonConfig, // 标准el-button配置
          }
        ],
        rowButtons: [
          {
            label:'', // 按钮名称
            eventKey:'', // 按钮事件名
            eventOption:{}, // 按钮事件配置
            ...elButtonConfig, // 标准el-button配置
          }
        ]
      }, // table相关配置
      searchConfig: {}, // 搜索相关配置
      components: {}, // 模块组件
    }, ...]
}

2. 宏观架构:像"搭积木"一样组装页面

先看这张架构大图,别被吓到了,其实它就讲了两件事: "怎么配""怎么染"

2.1 底座:BFF Server 的"继承大法"

看架构图的最底下(红色虚线框区域)。 我们借鉴了面向对象的思想。比如你要做一个"电商后台":

  • 领域模型(基类) :我们定义好一套所有页面通用的规则。比如,所有表格默认都有"创建时间",所有搜索栏默认都有"重置"按钮。
  • 项目配置(子类) :具体到"订单管理"页面时,你只需要继承基类,然后说"我要加个订单金额字段"。

好处? 修改基类,一百个页面同时生效,不用一个个文件去改。

2.2 页面骨架:路由分发与组件分工

看中间蓝色的区域,它在工程实现上其实就是一套精心设计的 Vue Router 配置

我们没有写死页面,而是预先定义好了几个"核心容器组件"(View),就像这是几辆不同功能的"车",JSON 数据就是"乘客",路由决定了把乘客装进哪辆车里。

来看看这段核心路由代码:

JavaScript

javascript 复制代码
import Dashboard from "./dashboard.vue";
import boot from "@/boot";

// 1. 定义"引擎"列表:这里就是我们的策略库
const componentList = [
  {
    path: "iframe", // 对应 moduleType: iframe
    component: () => import("./complex-view/iframe-view/iframe-view.vue"),
  },
  {
    path: "schema", // 对应 moduleType: schema(核心低代码页)
    component: () => import("./complex-view/schema-view/schema-view.vue"),
  },
  {
    path: "todo",   // 待办/自定义页
    component: () => import("./todo/todo.vue"),
  },
];

// 2. 动态生成扁平路由
const routes = componentList.map((item) => ({
  path: `/view/dashboard/${item.path}`,
  component: item.component,
}));

// 3. 侧边栏布局(Sider Layout)策略
// 如果配置了侧边栏,就让 sider-view 作为父路由,把 componentList 作为子路由嵌套进去
routes.push({
  path: "/view/dashboard/sider",
  component: () => import("./complex-view/sider-view/sider-view.vue"),
  children: componentList,
});

// 4. 侧边栏兜底策略(Wildcard Route)
// 处理多级深层菜单的情况,保证 url 即使很长也能匹配到 sider 布局
routes.push({
  path: "/view/dashboard/sider/:chapters+",
  component: () => import("./complex-view/sider-view/sider-view.vue"),
});

boot(Dashboard, { routes });

这段代码揭示了 DSL 运行的实质流程:

(1) 路由即分发(The Router Dispatcher)

  • 当 URL 匹配到 /view/dashboard/schema 时,Vue Router 自动加载 Schema-View 组件。
  • 当 URL 匹配到 /view/dashboard/iframe 时,加载 Iframe-View 组件。
  • 侧边栏的巧妙处理 :代码中专门为 /sider 路径配置了 children,这意味着如果你的页面配置了侧边栏,系统会先渲染 sider-view 框架,再把具体的内容(schema 或 iframe)渲染到 <router-view> 插槽中。

(2) Schema-View 的内部构造

一旦路由命中了 Schema-View,这个组件内部其实又做了一次精细化分工。它不是一个巨大的黑盒,而是由两个核心子面板组成的:

  • 上层:Search-Panel(搜索面板) 它负责接收 JSON 中的 searchOption 配置,动态生成 Input、Select 等表单项。
  • 下层:Table-Panel(表格面板) 它负责接收 JSON 中的 tableOption 配置,负责数据的展示、分页以及行列操作。

总结一下: 路由负责**"选车" (选 Sider 还是 Schema 还是 Iframe),而 Schema-View 负责"装货"**(把 JSON 拆分成搜索配置和表格配置,分发给上下两个面板)。这样一来,结构清晰,维护也非常容易。

3. JSON 核心揭秘:一份配置,掌控全局

接下来我们对着那段 JSON 代码,看看它是怎么指挥前端干活的。

3.1 路由的大脑:menumoduleType

JSON 最外层的 menu 决定了系统的导航结构。这里有个最关键的开关叫 menuTypemoduleType

这也是为了防止"一刀切"。我们不能因为用了低代码,就写不了复杂页面。

  • 如果是标准增删改查 :设 moduleType: "schema"。引擎自动干活,你喝咖啡。
  • 如果是超复杂的数据大屏 :设 moduleType: "custom"。引擎让路,加载你手写的 Vue 组件。
  • 如果是老系统页面 :设 moduleType: "iframe"。直接内嵌完事。

3.2 字段的"单源真理":最骚的操作在这里

请重点看 JSON 里的 properties 字段。这是整个 DSL 最精华的部分。

以往我们写代码,搜索栏写一遍 <el-input v-model="name">,表格里又写一遍 <el-table-column prop="name">两边是割裂的。

在这个 DSL 里,我们把一个字段(比如 key)的所有属性聚合在一起:

JavaScript

arduino 复制代码
key: {
  label: "字段名称", // 通用名称
  // 1. 告诉表格怎么展示
  tableOption: { 
    visible: true, 
    tofixed: 2 // 假如是数字,自动保留2位小数
  },
  // 2. 告诉搜索栏怎么搜索
  searchOption: { 
    comType: 'select', // 自动渲染成下拉框
    enumList: [...]    // 下拉选项
  }
}

看懂了吗? 你只需要定义一次 key。解析器读取 searchOption 就在上面渲染搜索框,读取 tableOption 就在下面渲染表格列。 改一个字段名,搜索和表格同时更新。 这才叫"不重复造轮子"。

3.3 按钮与事件:schema:: 的魔法

表格肯定要有操作按钮,比如"编辑"、"删除"。 在 headerButtonsrowButtons 里,你可能会疑惑这一行: "paramsKey": "schema::fieldKey"

这是我们约定的一种动态取值语法

  • 场景 :点击"删除"按钮,需要调 API 删掉当前行,API 需要 id 参数。
  • 原理 :当前端引擎看到 schema:: 开头时,它就知道:"哦,用户不是要传死字符串,而是要我去当前这一行的数据 里,把 fieldKey 对应的值取出来传给后端。"

4. 运行流程总结

把图和代码串起来,整个流程是这样的:

  1. 加载:你打开页面,BFF Server 把合并好的 JSON 扔给前端。

  2. 路由 :前端 Router 看到 moduleType: "schema",就把任务交给通用模板页。

  3. 渲染

    • Search 引擎 遍历 JSON 里的 properties,把带有 searchOption 的字段挑出来,生成搜索栏。
    • Table 引擎 遍历 properties,把带有 tableOption 的字段挑出来,生成表格列。
  4. 交互:用户点"搜索",引擎自动收集所有搜索框的值,拼接 API URL,刷新表格数据。


5. 写在最后

这套 DSL 的本质,不是为了炫技,而是为了偷懒(褒义)。

它把我们从繁琐的 DOM 结构和 UI 库 API 中解放出来,让我们只关注业务数据本身 。毕竟,作为开发者,我们的价值应该体现在解决复杂的业务逻辑上,而不是比谁写的 <el-table-column> 更多,对吧?

相关推荐
喷火龙8号5 小时前
JWT 认证方案深度对比:单 Token 扩展刷新 vs 双 Token 验证
后端·设计模式·架构
黑客思维者5 小时前
XGW-9000系列高端新能源电站边缘网关硬件架构设计
网络·架构·硬件架构·嵌入式·新能源·计算机硬件·电站
leafff1235 小时前
一文了解:智能体大模型LangChain 和 Dify有什么区别?
人工智能·架构·langchain
测试人社区-小明6 小时前
从前端体验到后端架构:Airbnb全栈SDET面试深度解析
前端·网络·人工智能·面试·职场和发展·架构·自动化
码界奇点6 小时前
基于微服务架构的分布式量化交易系统设计与实现
分布式·微服务·架构·车载系统·毕业设计·源代码管理
GIOTTO情6 小时前
技术深度:Infoseek 媒体发布系统的微服务架构与二次开发实战
微服务·架构·媒体
拾忆,想起6 小时前
Dubbo深度解析:从零到一,高性能RPC框架如何重塑微服务架构
网络协议·微服务·云原生·性能优化·rpc·架构·dubbo
聊天QQ:4877392787 小时前
逆变器下垂控制:负载跳变实验的神奇之旅
架构
CinzWS7 小时前
车规级高可靠性DMA控制器(G-DMA)架构设计--第二章 IP核心架构设计 2.1 顶层系统架构
架构·系统架构·dma