小程序中实现自定义表单
孙泽辉 Lv5

最近做的小程序关于表单的字段需要用户自定义编辑功能,这个表单编辑是整个项目的重点。

选择一个靠谱的表单编辑器,然后实现表单的数据处理是不得不深入研究的问题。

总的来说,自定义表单程序至少需要:设计器(支持拖拉拽修改各种参数)、数据管理(用户填写的表单数据进行处理)

展示层面

表单展示层面分为后台设计表单和用户填写表单页面。

表单编辑器 form-create

经过调研,我决定使用这款表单生成组件。

对,他是个组件,读取配置列表就可以生成不同类型的表单项,这种方式存储方便,展示也方便(没涉及复杂的功能)

form-create 是一个可以通过 JSON 生成具有动态渲染、数据收集、验证和提交功能的表单生成组件。支持5个UI框架,并且支持生成任何 Vue 组件。内置20种常用表单组件和自定义组件,再复杂的表单都可以轻松搞定。

form-create: xaboy/form-create: :fire::fire::fire: 强大的动态表单生成器|form-create is a form generation component that can generate dynamic rendering, data collection, verification and submission functions through JSON. (github.com)

image

项目简介中说到:支持5个UI框架,分别是:

  • element-plus
  • ant-design-vue
  • naive-ui
  • arco-design
  • tdesign

嗯,没有小程序版本,我们用的小程序是Vue3 + uniapp,项目作者虽说后续会支持小程序,但是我们等不了他了,自己手动实现好了。

实现思路大概是:编辑出来表单JSON,前台去读取渲染

其中作者实现了编辑器,通过form-create组件去渲染表单。

form-create-designer: xaboy/form-create-designer: 好用的vue可视化表单设计器 (github.com)

image

编辑器预览的样式是element-ui的,无所谓,我们用的是小程序,能生成配置,让我们小程序读到就可以了。

输入框JSON格式示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
type: "input",
title: "商品名称",
//label名称
field: "goods_name",
//字段名称
value: "",
//input值,
props: {
"type": "text",
//输入框类型,可选值为 text、password、textarea、url、email、date
"clearable": false,
//是否显示清空按钮
"disabled": false,
//设置输入框为禁用状态
"readonly": false,
//设置输入框为只读
"rows": 4,
//文本域默认行数,仅在 textarea 类型下有效
"autosize": false,
//自适应内容高度,仅在 textarea 类型下有效,可传入对象,如 { minRows: 2, maxRows: 6 }
"number": false,
//将用户的输入转换为 Number 类型
"autofocus": false,
//自动获取焦点
"autocomplete": "off",
//原生的自动完成功能,可选值为 off 和 on
"placeholder": "请输入商品名称",
//占位文本
"size": "default",
//输入框尺寸,可选值为large、small、default或者不设置,
"spellcheck": false,
//原生的 spellcheck 属性
"required": false,
},
validate: [{
required: true,
message: '请输入商品名称',
trigger: 'blur'
},
],
},

其中props在编辑器面板右侧可以编码处理,这个是我定义的姓名表单项:用户可以配置placeholder,是否可清空,是否禁用,是否只读……. 当然你可以加任何属性,业务上的也不耽误渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import uniqueId from '@form-create/utils/lib/unique'
import { localeProps, makeRequiredRule, makeHiddenRule } from '@/utils'
import phoneCmp from '@/components/form-item/phone.vue'
const label = '姓名'
const name = 'name'

export default {
icon: 'icon-input',
label,
name,
rule({ t }) {
return {
type: name,
field: uniqueId(),
title: label,
info: '',
$required: false,
props: {},
}
},
component: phoneCmp,
props(_, { t }) {
return localeProps(t, name + '.props', [
makeRequiredRule(),
{
type: 'input',
field: 'placeholder',
title: '输入框占位文本',
},
{ type: 'switch', field: 'clearable', title: '是否可清空' },
{ type: 'switch', field: 'disabled', title: '禁用' },
{ type: 'switch', field: 'readonly', title: '是否只读' },
])
},
}

uniapp 小程序端

小程序端因为设计器没有实现UI库适配,我使用相对稳定一些的uni-ui,相对比其他的功能少点,样式差点,不过这都不重要。

基本上,拿到了配置后走个v-for循环渲染一下表单项就可以了,大概是:

1
2
3
4
5
6
7
8
9
10
11
<!-- diy-form -->
<template>
<uni-forms ref="baseFormRef" label-position="top" :rules="diyRules" :modelValue="diyFormPayload">
<formItem
v-for="item in formList"
:key="item.id"
:field="item"
@change-data="changeOtherData"
/>
</uni-forms>
</template>

uni-forms可以接收我自定义的rule,表单规则是根据生成的配置进行生成的,在尽量适配uni-ui的方式去实现,比如required,邮箱、身份证、手机号之类的验证,都是根据表单项中的参数来确定的。

对于表单项组件,为各种表单项添加v-if进行类型判断分别渲染。

重点说一下数据的流动:

uni-forms动态传入的rule,需要在onReady之后触发setRules()方法(最早要晚于ready)。

所以在生成自定义rule后要调用一下setRules(),但是请求可能早于onReady,那就在onReady同时调用一次即可。

1
2
3
4
5
6
onReady(() => {
initForm()
})
watch(formList, () => {
initForm()
})

diy-form传来的field,经过computed处理一层统一适配form-item

1
2
3
4
5
6
7
8
9
10
11
12
13
// form-item.vue
const _field = computed(() => {
const field = props.field
return {
...field,
...(field?.props || {}),
required: field.$required != false,
readonly: field?.props?.readonly,
disabled: field?.props?.disabled || field?.props?.readonly || false,
placeholder: field?.props?.placeholder || `请输入${field.title}`,
type: field.type
}
})

这里的formVal是要绑定到表单项的,表单项的值要通过刚才说的uni-forms中绑定的modelValue中获取,所以为了不用统一传递,我直接从uni-forms中读取了。

修改也同样,直接改uni-forms中的modelValue即可。

1
2
3
4
5
6
7
8
9
10
11
const emit = defineEmits(['changeData'])
const formVal = computed({
get: () => {
const form = inject<any>('uniForm')
let formVal = form._getDataValue(_field.value.field, form.localData)
return formVal
},
set: (val) => {
emit('changeData', _field.value.field, val)
}
})

最后,给diy-form组件暴露两个方法:验证表单和更新表单数据。

1
2
3
4
5
6
7
8
defineExpose({
validate: async () => {
// 验证表单规则,最终返回用户表单数据
},
updateData: (data) => {
// 更新表单中的值
}
})

数据管理

先放一下,吃饭去了。。。

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Total words 87.1k