antd vue form 手动校验_chaso-form:基于Vue和ElementUI的配置式表单组件

195754893d7e1f567bb7bc9cb8060ddf.png

前言

大家好 ,今天给大家安利一款我自己开发的,基于 vueelement-ui 的配置式表单组件:chaso-form

初衷

做这款表单的初衷其实很简单,因为公司的项目涉及到大量的表单开发,直接导致我在 <template> 中会不断的重复编写 <el-form> 的相关代码,而且表单不易复用,总之效率很低,所以我就开始寻找有没有好的解决方案,最终形成了这样一套配置式的表单组件。

组件特色

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

  • chaso-form 没有预设表单组件,所有表单组件均通过 render 属性传递,通过灵活的 JSX 语法实现高度自定义组件,因此,它非常小巧,不过需要提前安装 vueelement-ui
  • chaso-form 底层采用 $attrs$listeners 接收参数和监听事件,无缝对接 element-ui 中的 Form 文档板块,上手更快(所有<el-form>接受的参数<chaso-form>都支持,所有<el-form-item>接受的参数,column都有相应的字段可以设置,所有的方法、事件和插槽,除了Form-Item Methods,其他都支持);
  • 针对简单场景,可传递 formatter 属性进行格式化输出,在绑定了表单的情况下,可省略 renderformatter 属性,chaso-form 会默认返回 <span> 标签包裹的表单值,当然,你还可以自定义当前的 class
  • 针对复杂表单,比如你的表单可能是下面这样,chaso-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 } // 根据需要可以动态增减
        ]
}
  • 表单需要远程搜索?没问题!
  • 更复杂的场景,想使用自定义组件?没问题!
  • 表单太庞大,考虑模块化开发,方便复用?没问题!

安装

通过 npm 或者 yarn 安装项目

npm i chaso-form
# 或者
yarn add chaso-form

引用组件,根据需要可全局引入或者局部引入

// 组件依赖 vue 和 element-ui

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

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

// 局部引入
// >>> Demo.vue
<script>
import ChasoForm from 'chaso-form'
export default {
 components: {
   ChasoForm
 }
}
</script>

使用方法

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

<script>
export default {
  data() {
    return {
      form: {
        name: 'ChasoForm',
        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 属性
       * 则需在 <chaso-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.chasoFormRef.validateField('age', (err) => {
        if (err) {
          console.error(err);
          return;
        }
        console.log(val);
      });
    }
  }
};
</script>

2. 模块化开发

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

根组件示例:demo.vue

<template>
  <chaso-form
    ref="chasoFormRef"
    :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: 'chaso',
        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相关的组件chaso-table,欢迎大家使用~

Github仓库地址:

chaso-form​gitee.com chaso-table​gitee.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值