@mecc/form

1.2.0 • Public • Published

@mecc/form

Intro 简介

@mecc/form 是一款表单组件,基于 vueelement-ui 进行的二次封装,无需繁琐的模板代码,所有的表单配置项均可通过属性传递,使你的代码更干净。

Feature 特色

  • @mecc/form 没有预设表单组件,所有表单组件均通过 render 属性传递,通过灵活的 JSX 语法实现高度自定义组件,因此,它非常小巧,不过需要提前安装 vueelement-ui

  • @mecc/form 底层采用 $attrs$listeners 接收参数和监听事件,无缝对接 element-ui 中的 Form 文档板块,上手更快(所有 <el-form> 接受的参数 <mecc-form> 都支持,所有 <el-form-item> 接受的参数,column都有相应的字段可以设置,所有的方法、事件和插槽,除了 Form-Item Methods,其他都支持);

  • 针对简单场景,可传递 formatter 属性进行格式化输出,在绑定了表单的情况下,可省略 renderformatter 属性,@mecc/form 会默认返回 <span> 标签包裹的表单值,当然,你还可以自定义当前的 class

  • 针对复杂表单,比如你的表单可能是下面这样,@mecc/form 可以满足你!

export default {
  data() {
    return {
      form: {
        name: '',
        time: {
          start: '2020/01',
          end: '2020/03'
        },
        hobby: [
          sport: { name: 'basketball', point: 10 },
          drink: { name: 'tea', point: 9 } // 根据需要可以动态增减
        ]
}
  • 表单需要远程搜索?没问题!

  • 更复杂的场景,想使用自定义组件?没问题!

  • 表单太庞大,考虑模块化开发,方便复用?没问题!

Install 安装

  • 通过 npm 或者 yarn 安装项目
npm i @mecc/form
# 或者
yarn add @mecc/form
  • 引用组件,根据需要可全局引入或者局部引入
// 组件依赖 vue 和 element-ui

// 全局引入,可配置选项
// >>> main.js
import Vue from 'vue'
import Element from 'element-ui'
import 'element-ui/libs/theme-chalk/index.css'

import MeccForm from '@mecc/form'
Vue.use(MeccForm, {
  emptyText: '--'  // 没有内容的占位符,默认为空
})

// 局部引入
// >>> Demo.vue
<script>
import MeccForm from '@mecc/form'
export default {
    components: {
        MeccForm
    }
}
</script>

Options 配置项

配置项内容可在全局引入时设置,或者直接使用 <mecc-form {...options} /> ,需注意:直接使用的优先级高于全局配置

参数 数据类型 默认值 可选值 说明
emptyText String '-' - 表单数据为空时显示的文本内容
formClass String '' - 自定义class名称

Form Attributes 表单属性

仅展示必填项和新增项,其余参数见 Element Doc Form #Form Attributes

参数 数据类型 是否必须 默认值 可选值 说明
column Array - - 表单列配置项,具体内容见下方说明
model Object - - 表单数据

Form Methods 表单方法

支持全部 el-form 方法,详见 Element Doc Form #Form Methods

Form Events 表单事件

支持全部 el-form 方法,详见 Element Doc Form #Form Events

Form-Item Attributes 表单列属性

支持全部 el-form-item 属性,详见 Element Doc Form #Form Events

Form-Item Methods 表单列方法

暂不支持

Column 列配置项

仅展示必填项和新增项,其余参数见 Element Doc Form #Form-column Attributes

参数 数据类型 是否必须 说明
prop String 设置表单列的别名
label String 设置表单列的显示标签
render Function(h, form, root) => VNode 自定义渲染内容,可选返回VNode
formatter Function(form, root) => string 自定义渲染内容,可选返回字符串
children Array 当数据项类型为[object]时使用,返回column数组,与render/item互斥
item Function(form, root) => [column, ...] 当数据项类型为[array]时使用,可动态增删子节点,返回column数组,与render/children互斥
value - 当上一级数据项类型为[array],切传递了[item]属性时使用,可设置数据项初始值
show Function(form, root) => boolean 是否渲染该列,默认渲染
layout Object 设置布局模式,可传入[el-row]和[el-col]支持的所有属性
renderLabel Function(h, form, root)/VNode 自定义标签内容
renderError Function(h, form, root, {error})/VNode 自定义表单校验信息的显示方式

Usage Example 使用示例

  1. 基础配置示例
  • column(表格列配置): 数组类型,必传
  • model(表格变量绑定):对象类型,必传
<template>
  <mecc-form
    ref="meccFormRef"
    :model="form"
    :column="column"
    :rules="rules"
    size="small"
    label-width="100px"
    empty-text="--"
  />
</template>

<script>
export default {
  data() {
    return {
      form: {
        name: 'mecc',
        age: 18,
        gender: 'male',
        hobby: ['html', 'css', 'js'],
        time: {
          start: new Date().getTime(),
          end: new Date().getTime()
        },
        skill: [
          {
            name: 'math',
            point: 90
          }
        ]
      },
      rules: {
        name: [{ required: true, message: '请输入姓名', trigger: 'blur' }]
      }
    };
  },
  computed: {
    column() {
      /**
       * 注意:如果不传 render 和 formatter 属性
       * 则需在 <mecc-form> 上通过 :model="form" 绑定
       */
      return [
        // 简易模式
        // 返回数据示例:<span>{{ form.name }}<span>
        {
          prop: 'name',
          label: '姓名'
        },

        // show接收一个方法,根据返回值决定表单项是否显示
        {
          prop: 'custom_hidden',
          label: '自定义隐藏',
          show: (form, root) => false
        },

        // 使用formatter格式化数据
        // 返回数据示例:<span class="gender-icon>{{ genderNameMap[form.gender] }}</span>
        {
          prop: 'gender',
          label: '性别',
          class: 'gender-icon',
          formatter: (form, root) => {
            const genderNameMap = { male: '男生', female: '女生' };
            return genderNameMap[form.gender];
          }
        },

        /**
         * render: 自定义显示元素
         * 可使用v-model进行双向绑定
         * 
         * rules: 可单独设置校验规则
         * renderLabel: 自定义标签文本的内容,参数为 (h, form, root)
         * renderError: 自定义表单校验信息的显示方式,参数为 (h, form, root, {error})
         **/
        {
          prop: 'age',
          renderLabel: (h, form, root) => <span style='color: red;'>年龄</span>,
          renderError: (h, form, root, { error }) => (
            <span style='color: blue'>{error}</span>
          ),
          rules: [
            { required: true, message: '请输入年龄', trigger: 'blur' },
            { type: 'number', min: 1, message: 'hahaha', trigger: 'blur' }
          ],
          render: (h, form, root) => (
            <el-input-number
              v-model={form.age}
              onChange={this.handleChange}
              max={20}
              label='描述文字'
            />
          )
        },

        // 注意Vue中JSX语法的书写规则,部分属性无法传递,需进行包裹后方可传递
        {
          prop: 'hobby',
          label: '兴趣',
          render: (h, form, root) => {
            const options = [
              {
                name: '前端',
                id: 'front',
                children: [
                  {
                    name: 'HTML',
                    id: 'html'
                  },
                  {
                    name: 'JavaScript',
                    id: 'js'
                  },
                  {
                    name: 'CSS',
                    id: 'css'
                  }
                ]
              },
              {
                name: '后端',
                id: 'back',
                children: [
                  {
                    name: 'JAVA',
                    id: 'java'
                  },
                  {
                    name: 'Golang',
                    id: 'golang'
                  },
                  {
                    name: 'Python',
                    id: 'python'
                  }
                ]
              }
            ];
            /**
             * 特别注意
             * 由于 el-cascader 需要传递名称为 'props' 的属性
             * 而在 vue 的 JSX 语法解析中,'props' 属性无法正常传递,所以这里需要特殊处理下
             * 详情可参考 https://www.yuque.com/zeka/vue/vu60wg
             */
            const cascaderProps = {
              options,
              props: {
                checkStrictly: true,
                label: 'name',
                value: 'id',
                multiple: true,
                emitPath: false
              },
              clearable: true,
              filterable: true
            };
            return (
              <el-cascader {...{ props: cascaderProps }} v-model={form.hobby} />
            );
          }
        },
        
        /**
         * 嵌套对象的表单 + 快速布局
         *
         * 配置项:children
         * 数据类型:数组(column)
         * 含义:设置对象的 column列
         *
         * 拓展项:layout
         * 数据类型:对象
         * 含义:引入<el-row>和<el-col>进行布局,接受所有参数
         **/
        {
          prop: 'time',
          label: '活动时间',
          layout: {
            type: 'flex',
            align: 'middle',
            justify: 'start'
          },
          children: [
            {
              prop: 'start',
              label: '开始时间',
              layout: {
                span: 10
              },
              render: (h, form, root) => {
                return <el-date-picker v-model={form.start} />;
              }
            },
            {
              prop: 'end',
              label: '结束时间',
              layout: {
                span: 10,
                offset: 1
              },
              render: (h, form, root) => {
                return <el-date-picker v-model={form.end} />;
              }
            }
          ]
        },

        /**
         * 嵌套数组的表单
         *
         * 配置项:item
         * 数据类型:函数
         * 数据结构:(form, root) => column数组
         * 含义:设置数组每一项的数据结构
         *
         * 拓展项:value
         * 数据类型:any
         * 含义:新增数据项时的默认值
         **/
        {
          prop: 'skill',
          label: '能力评级',
          item: (form, root) => {
            return [
              {
                prop: 'name',
                label: '名称',
                render: (h, form, root) => {
                  return <el-input v-model={form.name} />;
                }
              },
              {
                prop: 'point',
                label: '评分',
                value: 80,
                render: (h, form, root) => {
                  return <el-input-number v-model={form.point} />;
                }
              }
            ];
          }
        }
      ];
    }
  },
  methods: {
    handleChange(val) {
      // 这里同样可以使用 el-form 的 Form Methods,同原生 element-ui 的使用方式相同
      this.$refs.meccFormRef.validateField('age', (err) => {
        if (err) {
          console.error(err);
          return;
        }
        console.log(val);
      });
    }
  }
};
</script>
  1. 模块化开发

当表单的复杂度进一步提高,模块化解耦就成了刚需,@mecc/form 的解决方案:本质上是构造 column 数组。

  • 根组件示例:Demo.vue
<template>
  <mecc-form
    ref="meccFormRef"
    :model="form"
    :column="column"
  />
</template>

<script>
import Location from './Location.js';
import Info from './Info.js';
import mixinUtils from './mixin-utils.js'
export default {
  mixins: [mixinUitls],
  data() {
    return {
      form: {
        name: 'mecc',
        location: '',
        gender: 'male',
        hobby: ['html', 'css', 'js']
      }
    };
  },
  computed: {
    column() {
      return [
        {
          prop: 'name',
          label: '姓名'
        },
        Location.call(this, '地区'),
        ...Info.call(this)
      ];
    }
  }
};
</script>
  • 单模块示例:Location.js
import { v4 as uuidv4 } from 'uuid';

// 生成随机ID,保存在指定变量下
const location = uuidv4()

/**
 * 远程搜索接口
 * @param {string} name 搜索关键字
 */
async function fetchData(str) {
  if (!str) return;
  try {
    const resp = await this.$api.search(str);
    // this.setValue 双向绑定数据,见后文`mixin.js`
    this.setValue(location, resp.data);
  } catch (err) {
    console.error(err);
    this.setValue(location, []);
  }
}

export default function Location(customLabel) {

  // 初始化列表数据
  fetchData();

  return {
    prop: 'location',
    label: customLabel
    render: (h, form, root) => (
      // this.getValue 获取双向绑定的数据
      const optionList = (this.getValue(name) || []).map((opt, idx) => (
        <el-option key={idx} label={opt.label} value={opt.value} />
      ));

      return (
        <el-select
          v-model={form.location}
          filterable
          clearable
          remote
          remote-method={fetchData.bind(this)}
        >
          {optionList}
        </el-select>
    )
  };
}

看过这些示例,相信聪明的你一定发现了,render/renderLabel/renderError 函数中都传递了 h 参数,但大部分的场景下函数体内并没有直接使用,那可不可以去掉呢?

在组件的设计中,我尝试过,在 *.vue 文件中可以正常使用,但是抽离到 *.js 文件的时候就会报错,查阅资料后,在 vue 的官网上有这样一段话:

将h作为createElement的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。从 Vue 的 Babel 插件的3.4.0 版本开始,我们会在以 ES2015 语法声明的含有 JSX 的任何方法和 getter 中 (不是函数或箭头函数中) 自动注入const h = this.$createElement,这样你就可以去掉(h)参数了。对于更早版本的插件,如果h在当前作用域中不可用,应用会抛错。

所以,为了兼容更多的情况,我还原了 h 函数,如果你有更好的解决方案,欢迎提PR~

  • 组合模块示例:Info.js
export function Info() {
  return [
      {
        prop: 'gender',
        label: '性别',
        formatter: (form, root) => {
          const genderNameMap = { male: '男生', female: '女生' };
          return genderNameMap[form.gender];
        }
      },
      {
        prop: 'hobby',
        label: '兴趣',
        render: (h, form, root) => {
          const optionList = ['html', 'css', 'javascript'].map(
            return <el-option label={item} value={item} key={item} />
          )
          return <el-select>{optionList}</el-select>
        }
      }
  ]
}
  • 辅助函数:mixin-utils.js
export default {
  data() {
    return { formFetchData: {} };
  },
  methods: {
    setValue(key, val) {
      this.$set(this.formFetchData, key, val);
    },
    getValue(key) {
      return this.formFetchData[key];
    }
  }
};

欢迎使用配套Table组件:@mecc/table

Package Sidebar

Install

npm i @mecc/form

Weekly Downloads

1

Version

1.2.0

License

MIT

Unpacked Size

28.9 kB

Total Files

6

Last publish

Collaborators

  • mecc