环境监测 CMMS 的表单 DSL 实践:从逐一开发到声明式生成,工单交付效率提升 10 倍

目录

  1. 背景与业务语境
  2. 技术方案全景
  3. 难点攻坚
  4. [业务价值与 ROI](#业务价值与 ROI "#4-%E4%B8%9A%E5%8A%A1%E4%BB%B7%E5%80%BC%E4%B8%8E-roi")
  5. 个人思考与复用性

"低代码的本质不是少写代码,而是让正确的人做正确的事。"


1. 背景与业务语境

AirOps 是一套面向环境监测行业的 CMMS(计算机化维护管理系统),核心解决两个问题:计划性维护执行合规性质控审核。系统覆盖全国多个省市的空气质量监测站点,管理 PM2.5、PM10、SO₂、O₃ 等多类监测设备的运维全生命周期。

业务规模方面,系统承载 60+ 种工单类型(周检、月检、半年校准、故障排除、易耗品更换等),每种工单对应一张独立的表单记录表,涉及不同的检查项目、正常范围、判定逻辑和审核流程。

技术挑战的紧迫性 :按传统开发模式,每种工单类型需要一名前端工程师开发独立的 Vue 组件------包含表单布局、校验逻辑、数据绑定、提交流程。60+ 种工单意味着 60+ 个定制组件 ,且业务方持续新增工单类型。按每个组件平均 2-3 人天 计算,仅表单开发就需要 120-180 人天 ,占据前端团队近半年的产能。更致命的是,一线运维人员提出的表单调整需求(增删字段、修改检查项)必须等待前端排期,响应周期长达 1-2 周

不解决这个问题,前端团队将沦为"表单工厂",无法投入 GIS 可视化、数据分析等高价值功能的开发。


2. 技术方案全景

架构演进路径

yaml 复制代码
Phase 1: 硬编码时代          Phase 2: DSL 引擎          Phase 3: 编辑器工程化
┌─────────────────┐    ┌──────────────────────┐    ┌─────────────────────────┐
│  60+ Vue 组件    │    │  DSL 模板文本          │    │  CodeMirror 6 编辑器     │
│  每种工单一个     │ →  │      ↓                │ →  │  语法高亮 + Lint 检查     │
│  前端独占开发     │    │  解析引擎 → 动态渲染    │    │  非开发人员也能高效编写    │
│  2-3天/工单      │    │  运维人员可自行配置     │    │  错误实时反馈             │
└─────────────────┘    └──────────────────────┘    └─────────────────────────┘

DSL 引擎核心架构

scss 复制代码
DSL 模板文本
    │
    ▼
┌─────────────────────────────────────────────┐
│            parseTableConfig2()               │
│  ┌─────────┐  ┌──────────┐  ┌────────────┐  │
│  │行分割     │→│ 控制流处理 │→│ 表达式归一化│  │
│  │splitLines│  │for/default│  │parseStrToAll│  │
│  └─────────┘  └──────────┘  └────────────┘  │
│                      │                       │
│               ┌──────▼──────┐                │
│               │  getTdConfig │                │
│               │  单元格解析   │                │
│               └──────┬──────┘                │
│                      │                       │
│            ┌─────────▼─────────┐             │
│            │  组件工厂 (15+类型) │             │
│            │ input/select/date  │             │
│            │ calc/formula/image │             │
│            └─────────┬─────────┘             │
│                      │                       │
│               ┌──────▼──────┐                │
│               │  JSX 渲染    │                │
│               │  Vue VNode   │                │
│               └─────────────┘                │
└─────────────────────────────────────────────┘

选型决策:为什么自研 DSL 而非使用现成低代码平台?

维度 通用低代码平台 自研 DSL
学习成本 高(拖拽 + 配置面板 + 概念体系) 低(类似填表,纯文本)
表单表达力 通用但冗余 精准匹配业务(合并单元格、条件判断、公式计算)
部署方式 需额外平台/服务 零依赖,模板即文本,存数据库即可
定制深度 受限于平台能力 完全可控(自定义组件工厂)

反直觉决策 :我们没有选择市面上任何低代码平台(如 Formily、Amis),原因在于------环境监测工单表单的核心特征是强表格布局 + 合规性公式计算 ,而非通用的表单交互。一张典型的 SO₂ 分析仪周检记录表包含 20+ 行 × 6 列的严格表格布局,内嵌零点漂移计算公式、合格/不合格自动判定、多图片凭证上传。通用低代码平台的 JSON Schema 描述这种布局极其冗长,而我们的 DSL 只需 25 行文本即可完整表达。


3. 难点攻坚

难点一:DSL 语法设计------如何让非程序员 3 分钟上手?

Situation:运维团队有 60+ 种工单表单需要配置,但团队中没有前端工程师,只有熟悉业务的运维工程师。需要设计一种语法,让他们能直接"描述"表单结构。

Task:设计一套 DSL,满足:① 5 分钟可学会基本语法;② 覆盖 90% 的表单场景;③ 支持高级特性(循环、公式、条件判定)。

Action :核心设计原则是 "所见即所得的文本表格" ------每行就是表格的一行,| 就是单元格分隔符,{字段名} 就是数据绑定。

swift 复制代码
{NoTable}SO₂分析仪运行状况检查记录表(每周)
{Table$24$2}省(区、市):{下拉$Province$ProvinceName} 城市:{下拉$City$CityName}
{Table$5:7:6:6$1}仪器型号|{InstrumentModel}|校准日期|{日期$CalibrationDateTime$YYYY-MM-DD}
{Table$24$1}零点漂移(PPB):{计算$ZeroDrift$expr$ZeroDisplayValue-ZeroStandardConc$2}
{Table$24$1}  {判断$ZeroResult$ZeroDrift<=5 &&ZeroDrift>=-5$合格&&不合格}

关键语法糖设计:16 个中文简写关键字{下拉$}{日期$}{单选$}{计算$}{判断$} 等),在解析阶段通过 parseStrToAll() 归一化为统一的 {key$name#component$type} 完整格式。这让业务人员只需记忆几个中文词,而底层保持了完整的表达能力。

Result

  • 运维工程师 3 分钟学会基础语法,30 分钟独立完成第一张工单表单
  • 覆盖了 100% 现有工单类型(包括含循环、公式、条件判定的复杂表单)
  • 新工单类型从需求到上线:2-3 天 → 0.5-1 小时

难点二:CodeMirror 6 自定义语言支持------从"能用"到"好用"

Situation :DSL 投入使用后,一个实际问题浮现------模板在纯文本 <textarea> 中编写,没有语法高亮和错误提示。运维同事反馈:① 花括号容易漏写/多写;② {for$}{forEnd} 配对全靠肉眼数;③ Table$ 参数格式写错只有提交后才发现。每次调试平均浪费 15-30 分钟

Task:为自定义 DSL 实现完整的编辑器体验------语法高亮 + 实时错误检测,且不引入过重的依赖。

Action :选择 CodeMirror 6 的 StreamLanguage(流式解析器)而非完整的 Lezer Grammar。

为什么不用 Lezer Grammar? 这是第二个反直觉决策。Lezer 是 CodeMirror 6 官方推荐的解析器生成工具,但我们的 DSL 是严格按行解析 的(每行独立,无跨行嵌套语法),用 Lezer 编写 .grammar 文件然后编译反而增加了构建链复杂度。StreamLanguage 逐行逐 token 扫描,天然匹配我们的语法结构,实现代码量仅 180 行

Tokenizer 状态机设计(核心 3 个状态):

typescript 复制代码
interface DslState {
  inBrace: boolean;       // 是否在 {} 表达式内
  inDefaultData: boolean; // 是否在 defaultData JSON 块内
  afterHash: boolean;     // 是否在 # 属性分隔符之后
}

{} 外部只需识别 |(单元格分隔符)和 {(表达式开始);{} 内部按优先级匹配:行级控制关键字 → 组件关键字 → meta 属性 → 属性名 → 数字 → 标识符。关键字列表按长度降序排列 ,确保 {灵活下拉$} 优先于 {下拉$} 匹配。

Lint 规则引擎(10 条规则):

yaml 复制代码
规则 1:  花括号匹配(不允许嵌套)
规则 2-4: for/joinTextFor/defaultData 块配对检查(栈追踪)
规则 5:  defaultData 内 JSON.parse 格式校验
规则 6:  Table$ 参数格式验证(正则:/^\d+(\s*:\s*\d+)*$/ )
规则 7:  中文关键字拼写检查(Levenshtein 距离 ≤ 1 报 warning)
规则 8:  组件参数完整性({下拉$} 至少需要 2 个 $ 参数)
规则 9:  meta 属性目标有效性(仅限 table/tr/td)
规则 10: 必填标记 $* 位置检查

踩坑复盘 :首版 Lint 引擎上线后,{defaultData$} 块内的 JSON 内容中的 { } 被规则 1 误判为"不允许嵌套花括号"。根因是规则执行顺序错误 ------花括号匹配检查在 inDefaultData 状态判断之前执行。修复方案:将 defaultData 块检测提升为循环内第一个执行步骤,在进入任何语法规则前先 continue 跳过 JSON 内容行。这个 Bug 提醒我们:状态机的优先级比规则本身更重要

Result

  • 语法错误从"提交后才发现"变为实时红色波浪线提示
  • 模板调试时间:15-30 分钟 → 2-3 分钟
  • 引入依赖体积可控:CodeMirror 6 按需引入仅 ~150KB gzipped

4. 业务价值与 ROI

指标 优化前(硬编码) 优化后(DSL + 编辑器) 业务解读
单工单交付周期 2-3 人天 0.5-1 小时 交付速度提升 16-48 倍
前端人力占用 120-180 人天/60种工单 ≈0(运维自行配置) 前端团队释放,投入 GIS 可视化等高价值模块
需求响应周期 1-2 周(等前端排期) 当天 一线运维可自行调整表单字段
模板调试耗时 15-30 分钟/次 2-3 分钟/次 编辑器实时反馈,减少 80% 调试时间
配置参与者 仅前端工程师 运维工程师可独立完成 技术民主化,解除人力瓶颈

关键数据 :通过 DSL 引擎 + 编辑器工程化的组合方案,在投入约 1.5 人月 的情况下,替代了原本需要 6+ 人月 的逐一组件开发工作量,ROI 达 1:4。更重要的是,这是一次性投入持续收益------每新增一种工单类型,节省的边际成本趋近于零。


5. 个人思考与复用性

方法论沉淀

这个实践验证了一个普适的工程策略:当你发现团队在重复解决"同构但不同参"的问题时,就是引入 DSL 的最佳时机。DSL 的价值不在于技术复杂度,而在于它重新定义了"谁来做这件事"------将配置权从开发者转移到领域专家。

该方案可抽象为通用的"表格型表单 DSL 引擎",适用于任何以表格为核心布局的表单场景(质检记录、实验报告、巡检清单等)。

技术贡献

  • 编写了完整的 DSL 语法文档,包含所有关键字说明和示例
  • 面向运维团队进行了 2 次 DSL 编写培训,实现了技术能力的团队复制
  • CodeMirror 自定义语言实现可作为团队内部其他 DSL 编辑器需求的参考模板

"最好的架构不是最复杂的架构,而是让最多人能参与其中的架构。"


技术关键词DSL 低代码 CodeMirror 6 StreamLanguage 自定义Lint Vue 3 动态表单 CMMS 环境监测 工程效能

相关推荐
天若有情6732 小时前
一款极简且实用的本地 NPM 包目录管理方案(个人原创设计)
前端·npm·node.js
JamesYoung79712 小时前
第七部分 — 存储 chrome.storage(本地/同步/会话)+ 配额
前端·chrome
Mintopia2 小时前
CSS 你不知道的颜色用法:从现代语法到真实落地的配色策略
前端·css
undeflined2 小时前
EnvManage - 多环境开发代理管理工具
前端·javascript·node.js
三小河2 小时前
从零实现ollama本地大模型可视化+内网穿透
前端·javascript·面试
Mintopia2 小时前
高效简练的 CSS 架构:用最少规则支撑最大规模
前端·css
Cg136269159742 小时前
JS-对象-array数组
开发语言·前端·javascript
weixin456227192 小时前
省市区镇村五级联动
前端·javascript·chrome
窝子面2 小时前
二十三、第三方登录
前端·javascript·html