Home

Awesome

jformer

根据 vjform 的设计思路,扩展一个简化版的 json 定义方式

集成了 vjform 目前所有开发的扩展,简化了 json 定义方式且支持 vjform 全部功能

配置可用 可视化设计器 编辑

起步

安装包

npm i jformer

项目运行

npm i
npm run dev

特性

支持 vjform 全部原有功能,除此之外加入属性名前缀转换解析,可以直接在属性名上加入特定前缀,实现绑定、计算、事件的转换

整合了组件部分属性,让组件使用更清晰简便

组件属性

整合了部分属性到 config

前缀说明
v-model
params输入的参数值,只会由组件外部更新
config包含 fields datasource listeners,对应 vjform 相应属性
components单独引用的组件,理论上 jformer 支持 vue 项目中引用的任何组件, 如果未在项目中 use 则可以传到这里实现组件支持

fields 组件定义规则

fields 属性定义内部组件,一个内部组件具有如下属性

属性说明
component组件名称,支持所有 html 标签和 vue 项目中引用的组件具有的名称
fieldOptions组件的属性,是 vue 的 render 方法中 createElement 的第二个参数,具体参考 Vue 官方定义, 自定义组件 props 定义的属性均支持
children组件的下级组件

json 定义示例

{
  "component": "div",
  "fieldOptions": {
    "class": "",
    "style": {
      "margin": "10px"
    },
    "domProps": {}
  },
  "children": []
}

组件支持属性的快捷定义,使用 vjform 注册的快捷定义均可支持,常用快捷定义如下

属性说明
text相当于 fieldOptions.domProps.innerText 属性
condition根据条件决定组件是否存在,可配合 $ 前缀实现表达式和关联
events定义组件的事件行为
model具有交互行为的组件可将值关联到model里的某个属性,组件的交互行为会改变 model 里的属性值,例如:inputselect
[
  {
    "component": "p",
    "$:condition": "model.text.length > 4", // 条件显示
    "text": "文本"
  },
  {
    "component": "button",
    "events": [{ "name": "click", "@:handler": "alert('点击事件')" }]
  },
  {
    "component": "input",
    "model": "text" // 关联 model.text 属性
  }
]

fieldOptions 里的属性和组件的快捷定义均可以通过前缀定义实现程序逻辑

前缀定义

基于属性名的前缀定义

基于属性名的前缀定义实际上是简化了原来的转换定义,在属性名上加上相应前缀实现转换功能

前缀说明
$表达式
#文本模板
@事件
@<model 属性>更新 model 属性的事件
^禁止转换

表达式

在属性前面加上 $ 前缀,实现将表达式的结果关联到属性上,直接支持 js 语法对属性进行处理

{
  "component": "div",
  "fieldOptions": {
    "props": {},
    "domProps": {
      "$:innerText": "model.text.length + 1" // 这里支持直接取 model,params,datasource 的属性进行计算关联到属性
    }
  }
}

文本模板

在属性前面加上 # 前缀,实现直接用 es6 的模板字符串输出内容

{
  "component": "div",
  "fieldOptions": {
    "props": {},
    "domProps": {
      "#:innerText": "输入了 ${model.text}"
    }
  }
}

事件

在属性前面加上 @ 前缀,可将表达式生成一个 function 关联到组件事件上

{
  "component": "button",
  // 同样支持 vjform 的快速定义事件
  "events": [
    {
      "name": "click",
      "@:handler": "alert('clicked!')"
    }
  ]
}

如果在 @ 后面加上 model 里的属性名,则可以实现触发事件后将表达式的结果直接更新到 model 属性上

{
  "component": "input",
  "fieldOptions": {
    "domProps": {
      "$:value": "model.text"
    },
    "on": {
      "@text:input": "arguments[0].target.value" // 将输入结果更新到 model.text
    }
  }
}

禁止转换

在属性上加入 ^ 前缀,则属性的值不做转换,保持原有的值

当出现 jformer 嵌套 jformer 或者自定义组件里使用了 jformer 的时候,嵌套的 jformer 属性需要在内部进行处理而不是由外层统一转换处理的时候,可以在组件相应属性上加入 ^ 标识

实现一个 Repeat 组件,内部使用 jformer 呈现动态视图

<template>
  <div v-if="!updating">
    <j-former
      :key="index"
      v-for="(item, index) in data"
      :value="item"
      :params="{ ...params, $index: index }"
      :config="options"
    ></j-former>
  </div>
</template>

<script>
  export default {
    props: {
      data: Array,
      params: Object,
      options: Object
    },
    watch: {
      'data.length': {
        handler() {
          this.updating = true
          this.$nextTick(() => {
            this.updating = false
          })
        }
      }
    },
    data() {
      return {
        updating: false
      }
    }
  }
</script>

在 jformer 中使用 Repeat 组件

{
  "fields": [
    {
      "component": "v-repeat", // 自定义组件,实现根据数组数据遍历生成界面,内部使用 jformer 呈现界面
      "fieldOptions": {
        "props": {
          "$:data": "model.listData",
          "params": {
            "$:listData": "model.listData" // 为了在组件内部实现删除功能,以 params 方式将原数组传递给内部
          },
          // 使用 ^ 前缀,外层 jformer 不对属性值进行转换,而是由内部处理
          "^:options": {
            "fields": [
              {
                "component": "div",
                "fieldOptions": {
                  "style": { "border": "1px solid black", "margin": "10px", "padding": "5px" }
                },
                "children": [
                  { "component": "p", "#:text": "第 ${params.$index + 1} 项" },
                  { "component": "input", "model": ["text"] },
                  {
                    "component": "button",
                    "text": "delete",
                    "fieldOptions": {
                      "on": { "@:click": "params.listData.splice(params.$index ,1)" }
                    }
                  }
                ]
              }
            ]
          }
        }
      }
    },
    {
      "component": "button",
      "text": "add item",
      "fieldOptions": { "on": { "@:click": "model.listData.push({})" } }
    }
  ]
}

基于属性值的转换定义

基于属性值的转换定义规则和前面一样,区别是转换的标识放在属性值里,例如:

模板字符串

{
  "text": "#:输入了 ${model.text.length} 个字"
}

表达式

{
  "text": "$:model.value1 + model.value2"
}

事件

{
  "on": {
    "click": "@:alert('xxx')", // 执行 js
    "input": "@text:arguments[0]" // 更新 model 里的属性
  }
}

JSON 定义完整示例

{
  "datasource": {
    "list": {
      "type": "request",
      "method": "GET",
      "url": "http://localhost:8080/paths/data.json",
      "params": {}
    },
    "detail": {
      "type": "request",
      "method": "GET",
      "url": "http://localhost:8080/paths/detail.json",
      "params": {}
    }
  },
  "listeners": [
    {
      "watch": "model.text",
      "deep": false,
      "actions": [
        { "@:handler": "datasource.load()", "$:condition": "model.text.length >= 4" },
        { "@:handler": "detail.load()" },
        { "@text2:handler": "model.text + 'xxx'" }
      ]
    }
  ],
  "model": {
    "text": "",
    "text2": ""
  },
  "fields": [
    {
      "component": "input",
      "fieldOptions": {
        "domProps": {
          "$:value": "model.text"
        },
        "on": {
          "@text:input": "arguments[0].target.value"
        }
      }
    },
    {
      "component": "input",
      "model": "text2"
    },
    {
      "component": "div",
      "fieldOptions": {
        "props": {},
        "domProps": {
          "$:innerText": "model.text.length + 1"
        }
      }
    },
    {
      "component": "div",
      "fieldOptions": {
        "props": {},
        "domProps": {
          "#:innerText": "输入了 ${model.text}"
        }
      }
    },
    {
      "component": "button",
      "fieldOptions": {
        "props": {},
        "on": {
          "@:click": "datasource.load()"
        }
      }
    }
  ]
}

追加功能

在事件转换设置 model 属性值时,model 属性名可以使用模板字符串

例如:

{
  "component": "el-table",
  "children": [
    {
      "component": "el-table-column",
      "children": [
        {
          "component": "el-input",
          "fieldOptions": {
            "scopedSlot": "default",
            "props": {
              "value": "@:model.list[scope.$index].text"
            },
            "on": {
              "input": "@list[${scope.$index}].text:arguments[0]" // 属性用模板字符串更新数组某个索引元素的属性
            }
          }
        }
      ]
    }
  ]
}