前言
大家好 ,今天给大家安利一款我自己开发的,基于
vue
和
element-ui
的配置式表单组件:
chaso-form
初衷
做这款表单的初衷其实很简单,因为公司的项目涉及到大量的表单开发,直接导致我在
<template>
中会不断的重复编写
<el-form>
的相关代码,而且表单不易复用,总之效率很低,所以我就开始寻找有没有好的解决方案,最终形成了这样一套配置式的表单组件。
组件特色
chaso-form
是一款表单组件,基于
vue
和
element-ui
进行的二次封装,无需繁琐的模板代码,所有的表单配置项均可通过属性传递,使代码更干净。
-
没有预设表单组件,所有表单组件均通过chaso-form
属性传递,通过灵活的render
语法实现高度自定义组件,因此,它非常小巧,不过需要提前安装JSX
和vue
;element-ui
-
底层采用chaso-form
和$attrs
接收参数和监听事件,无缝对接$listeners
中的element-ui
文档板块,上手更快(所有Form
接受的参数<el-form>
都支持,所有<chaso-form>
接受的参数,<el-form-item>
都有相应的字段可以设置,所有的方法、事件和插槽,除了Form-Item Methods,其他都支持);column
- 针对简单场景,可传递
属性进行格式化输出,在绑定了表单的情况下,可省略formatter
和render
属性,formatter
会默认返回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>
使用方法
- 基础配置示例
- 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
的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。从 Vue 的 Babel 插件的3.4.0 版本开始,我们会在以 ES2015 语法声明的含有 JSX 的任何方法和 getter 中 (不是函数或箭头函数中) 自动注入
createElement
,这样你就可以去掉
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-formgitee.com chaso-tablegitee.com