(1)公式编辑器: shuttle-formula

319 阅读7分钟

什么是公式编辑器

    deepseek: 公式编辑器是一种通过图形界面或代码输入创建和编辑复杂数学公式、符号及科学表达式的专业工具
    deepseek 的回答比较广泛,这里讨论的仅为前端页面上允许普通用户按特定规则编辑的输入框,同时支持友好的提示,得让用户知道可以输入什么内容。这里的特定规则就是公式,公式可以被看作是简化版的代码(只有一条语句,并且计算后有一个结果),既然是代码那么必然就涉及到数据类型,函数,计算符号等。
    说了这么多,为了直观的感受,还是来看一下公式编辑器的截图吧

    区域1: 当前公式展示区域,用户也可以在此自由输入
    区域2: 可用的资源(这里支持了变量、函数、表达式等),用户点击此处的资源可快捷的填入区域1中
    区域3: 当前用户输入时获得的提示,例如图中用户正在输入一个变量,此时提示的就是当前变量的路径

公式编辑器应用场景

    以上对公式编辑器有了一个基本的认识,那么它可以应用到什么地方呢,以下列举一些公式编辑器的应用场景
1. 页面引擎
    低代码平台的页面引擎,页面中有许多的变量,用户可通过公式组合这些变量,以获得想要展示的结果。例如:变量中有某个时间,则可以通过公式来输出时间的显示格式,仅需配合一个格式化时间的函数,即可获得多种输出格式
2. 自定义规则
    比如系统需要一种能力,使得用户可以自定义对数据的访问权限的控制。每次查询数据时,计算所配置的公式的值,来匹配当前查询数据的用户是否有权限,若有权限才返回数据,使用公式可以使得配置更为灵活
3. 数据计算
    例如给销售提成的计算,可能对于不同的地区自行管理规则。那么就可以通过让各个地区管理者自行配置公式的方式实现自动计算提成的能力
...

shuttle-formula 介绍

    shuttle-formula 是一个零依赖的公式编辑器,支持公式解析、公式计算、原生 js 渲染、react 渲染、vue 渲染。npm 链接github 链接

安装

  npm i shuttle-formula

结构介绍
    shuttle-formula/core: 该部分是核心包,提供词法分析、语法分析、语法检查、计算表达式等功能;该部分也可运行到 nodejs 上,使得用户输入的公式也能直接在服务器上计算出结果。
    shuttle-formula/render: 该部分提供基础 web 渲染能力,提供灵活的插件入口,可在此基础上扩展能力,定制化公式编辑器
    shuttle-formula/render-react: 该部分提供对接 react 的渲染方式
    shuttle-formula/render-vue: 该部分提供对接 vue 的渲染方式
支持的变量
    内置类型:number、string、boolean、array、object(dynamic)
    自定义类型:自定义类型以“custom-”开头,自定义类型的变量不能参与运算符的运算,但是可以通过自定义函数来进行运算
    变量定义及属性支持,所有类型都支持 label 属性,用于提示

变量类型其他属性说明
number数字
string字符串
boolean布尔
arrayitem: 变量数组
objectprototype: Record<string, 变量>对象
objectdynamic: true异步对象

    示例如下:

// 定义一个number类型的数据
{
  type: 'number',
  label: '测试数字'
}
// 定义一个string类型的数据
{
  type: 'string',
  label: '测试字符串'
}
// 定义一个boolean类型的数据
{
  type: 'boolean',
  label: '测试布尔'
}
// 定义一个array<number>类型的数据
{
  type: 'array',
  label: '测试数组',
  item: {
    type: 'number'
  }
}
// 定义一个object类型的数据, 有一个num(number类型)和一个str(string类型)的字段
{
  type: 'object',
  label: '测试对象',
  prototype: {
    num: {
      type: 'number',
      label: '下钻数字'
    },
    str: {
      type: 'string',
      label: '下钻字符串'
    }
  }
}
// 异步对象object类型,配置了异步对象后,动用户在打开该变量下一层级时会调用setGetDynamicObjectByPath中传入的方法来获取下一层级的变量
{
  type: 'object',
  label: '异步对象',
  dynamic: true
}
// 定义一个自定义类型的数据
{
  type: 'custom-date',
  label: '自定义日期类型',
}

支持的函数
    函数入参: 函数支持多个参数,定义参数时需要定义参数的类型(在用户输入时会做类型检查,不符合函数定义的类型会报错);或者某个参数支持根据用户输入时才确定,可定义为{forwardInput: true}
    可变参数: 设置 loopAfterParams,表示最后几个参数类型可重复出现;若设置了{forwardInput: true}时,想要限制用户的可变参数与第一个输入类型相同则可设置 loopParamsMustSameWithFirstWhenForwardInput: true
    函数返回值: 每个函数必须指定其返回值的类型(不可以是异步对象)
    示例如下:

// 基础用法:定义一个四舍五入的函数,接受一个数字类型的参数,并返回一个数字类型的结果
{
  label: '四舍五入',
  params: [{ define: { type: 'number' } }],
  return: { type: 'number' },
}
// 接受多个类型的参数:定义一个计算长度的函数,接受一个参数,这个参数的类型可以是 字符串或数组,返回值为一个数字类型的结果
{
  label: '计算长度',
  params: [{ define: [{ type: 'string' }, { type: 'array' }] }],
  return: { type: 'number' },
}
// 任意类型:定义一个转字符串的方法,接受一个任意类型的参数,返回一个字符串类型的结果
{
  label: '转字符串',
  params: [{ forwardInput: true }],
  return: { type: 'string' },
}
// 多参数,返回值跟随用户输入:该函数接受两个参数,第一个为数字类型,第二个为任意类型,返回值的类型与第二个参数的实际类型相同
{
  label: '测试函数1',
  params: [{ type: 'number' }, { forwardInput: true }],
  return: { scope: 'forwardParams', paramsIndex: 1 }
}
// 可变参数:定义一个创建数组的方法,接受任意多个任意类型的参数,但是后面的参数类型必须与第一个参数类型一致,并返回一个数组类型,数组项的类型与第一个参数的实际类型相同
{
  label: '创建数组',
  params: [{ forwardInput: true }],
  loopAfterParams: 1,
  loopParamsMustSameWithFirstWhenForwardInput: true,
  return: {
    scope: 'forwardParamsArray',
    item: { scope: 'forwardParams', paramsIndex: 0 },
  }
}
// 自定义校验, 设置scope为customReturn时,可自定义校验逻辑,处理复杂的定义
{
  label: '自定义校验',
  params: [{ forwardInput: true }]
  return: {
    scope: 'customReturn',
    createType: async(getType, ...params) => {
      // params当前函数的实际参数的语法描述
      const firstParams = params[0]
      // getType可获取到制定参数的实际类型
      const firstParamsType = await getType(firstParams.id)
      if (!firstParamsType) {
        return {
          pass: false,
          error: {
            type: 'functionError',
            syntaxId: firstParamsType.id,
            msg: '未找到参数类型',
          },
        }
      }

      if (firstParamsType.type === 'string') {
        return {
          pass: true,
          type: { type: 'number' }
        }
      }

      if (firstParamsType.type === 'number') {
        return {
          pass: true,
          type: { type: 'custom-date' }
        }
      }

      return {
        pass: false,
        error: {
          type: 'functionError',
          syntaxId: firstParamsType.id,
          msg: `不支持参数类型:${firstParamsType.type}`
        },
      }
    }
  }
}

结语

从基本概念到应用场景,再到具体的实现工具 shuttle-formula,我们可以看到公式编辑器在现代软件开发中的重要性。它不仅能够帮助用户轻松创建和编辑复杂的公式,还能在低代码平台、自定义规则配置、数据计算等多个场景中发挥重要作用。

shuttle-formula 作为一个零依赖的公式编辑器,提供了强大的功能和灵活的扩展性,支持多种渲染方式,并且能够在前端和后端环境中无缝运行。无论是开发者还是普通用户,都可以通过 shuttle-formula 轻松实现复杂的公式编辑和计算需求。随着技术的不断进步,公式编辑器的应用场景将会更加广泛,功能也会更加丰富。