├── assets └── demo.gif ├── index.js ├── main-form.vue ├── package.json ├── readme.md └── sub-form-mixin.js /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EddyPan/vue-complex-form/2538d310d3803eb79cf9d30c1fcc24e85908852b/assets/demo.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export { default as MainForm } from './main-form' 2 | export { default as SubFormMixin } from './sub-form-mixin' 3 | -------------------------------------------------------------------------------- /main-form.vue: -------------------------------------------------------------------------------- 1 | 2 | 34 | 35 | 239 | 240 | 243 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-complex-form", 3 | "version": "1.0.2", 4 | "description": "vue 复杂表单方案", 5 | "main": "index.js", 6 | "keywords": [ 7 | "complex-form", 8 | "复杂表单", 9 | "大型表单", 10 | "vue", 11 | "element-ui" 12 | ], 13 | "author": "edwardpan2010@gmail.com", 14 | "license": "MIT", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/EddyPan/vue-complex-form.git" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # vue-complex-form 复杂表单 2 | 3 | 该组件基于VUE和ElementUI设计开发,通过弹出Dialog对话框+标签页的形式,承载复杂表单的新建、编辑、克隆、详情展示等操作。本方案本质是将复杂表单拆分为多个子表单,通过分步填写、验证,提升页面交互体验,降低复杂表单开发门槛,提升开发效率。 4 | 包含MainForm组件和SubFormMixin混入两部分。 5 | 6 | ![演示](https://github.com/EddyPan/vue-complex-form/blob/master/assets/demo.gif) "表单演示") 7 | 8 | ## 引用依赖 9 | npm install vue-complex-form 10 | 11 | ## MainForm 主表单 12 | 13 | 提供复杂表单主入口,通过Tabs标签页进行分组子表单,整合表单验证、表单步骤、表单提交等页面交互功能,开发者仅需关注业务逻辑代码编写。 14 | 15 | ### 引入主表单组件 16 | ```javascript 17 | import { MainForm } from 'vue-complex-form' 18 | 19 | components: { MainForm } 20 | ``` 21 | 22 | ### 引入相关子表单组件 23 | import SubFormComps from './pipeline-details/index' 24 | 25 | ### 编写html模板 26 | 27 | ```html 28 | 35 | ``` 36 | 37 | ### 组装表单数据 38 | ```javascript 39 | // data数据为原始复杂表单数据,通过该方法组转换成相应的数据格式 40 | resolveDataToList(data) { 41 | return [ 42 | { 43 | key: 'baseinfo', 44 | name: '基础信息', 45 | component: SubFormComps.BaseForm, 46 | data: { 47 | name: data.name, 48 | type: data.type 49 | }, 50 | hidden: false, 51 | valid: true 52 | }, 53 | { 54 | key: 'application', 55 | name: '选择应用', 56 | component: SubFormComps.AppForm, 57 | data: { 58 | application: data.application 59 | }, 60 | hidden: false, 61 | valid: true 62 | }, 63 | { 64 | key: 'build', 65 | name: '代码编译', 66 | component: SubFormComps.BuildForm, 67 | data: { 68 | build: data.build 69 | }, 70 | // 可以自定义hidden方法,动态调整是否展示该子表单,linkChannel为表单数据监听通道 71 | hidden: linkChannel => (linkChannel.appType === 'container'), 72 | valid: true 73 | }, 74 | { 75 | key: 'deploy', 76 | name: '部署信息', 77 | component: SubFormComps.DeployForm, 78 | data: { 79 | deployData: data.deployData 80 | }, 81 | hidden: false, 82 | valid: true 83 | } 84 | ] 85 | } 86 | ``` 87 | 88 | ### 提交数据 89 | ```javascript 90 | // 点击确认按钮,并且表单验证通过后会触发该方法 91 | handleSubmit(formData) { 92 | console.log(formData) 93 | // 提交数据到后台服务 94 | 95 | 96 | // 这里需要开发人员提交完数据后主动关闭表单 97 | this.showMainForm = false 98 | } 99 | ``` 100 | 101 | ### 属性参数 102 | 103 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 104 | | -------------- | ---------------------------------------------------------------------------------- | ------- | --------------------------------- | -------- | 105 | | form-title | 表单名称,配合下面的form-type,自动组合为对话框名 | string | -- | '表单' | 106 | | form-type | 表单类型,当form-type为空时,显示对话框名即为表单名 | string | add
edit
clone
detail | '' | 107 | | pre-btn-txt | 上一步按钮文字,在非第一页展示 | string | -- | '上一步' | 108 | | next-btn-txt | 下一步按钮文字,在非最后一页展示 | string | -- | '下一步' | 109 | | submit-btn-txt | 提交表单按钮文字,在可编辑模式下最后一页展示 | string | -- | '确定' | 110 | | calcel-btn-txt | 取消表单按钮文字,在可编辑模式下展示 | string | -- | '取消' | 111 | | close-btn-txt | 关闭表单按钮文字,在不可编辑模式下展示 | string | -- | '关闭' | 112 | | append-to-body | Dialog 自身是否插入至 body 元素上。嵌套的 Dialog 必须指定该属性并赋值为 true | boolean | -- | false | 113 | | width | Dialog 的宽度 | string | -- | '50%' | 114 | | data | 复杂表单数据对象列表,每个对象代表一个子表单,具体对象字段定义,请查看表单数据对象 | array | -- | -- | 115 | 116 | ### 表单数据对象 117 | 118 | | 参数 | 说明 | 类型 | 默认值 | 119 | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ------ | 120 | | key | 表单key,相当于本表单ID,必须唯一 | string | -- | -- | 121 | | name | 表单名称,用于当前Tab页面名称,尽量控制在6个字以内 | string | -- | 122 | | component | 引入的子表单模块 | component instance | -- | 123 | | data | 子表单数据,子表单内部会进行深度拷贝,不会对外层数据造成影响,详细信息参考SubFormMixin | object | -- | 124 | | hidden | 是否展示子表单,hidden为true时,该子表单不展示且数据不会被提交,如果表单是否隐藏受表单数据影响,可通过自定义方法实现动态调整,方法参数为linkChannel对象 | function \| boolean | false | 125 | | valid | 表单验证是否通过,请初始化默认值 | boolean | -- | 126 | 127 | ### 事件 128 | 129 | | 参数 | 说明 | 回调参数 | 130 | | ------ | ---------------------------- | ------------------------------------------- | 131 | | submit | 提交表单且数据验证通过后触发 | formData:object,有效子表单组装后的表单数据 | 132 | | closed | 表单对话框关闭后触发 | -- | 133 | 134 | ## SubForm 子表单 135 | 每个子表单需要开发人员按照实际需求开发,本方案将提供子表单mixin,开发人员只需关注业务逻辑编写。 136 | 137 | ### 引入子表单混入 138 | 139 | ```javascript 140 | import { SubFormMixin } from '@/components/ComplexForm' 141 | 142 | mixins: [SubFormMixin] 143 | ``` 144 | 145 | ### 编写html模板 146 | 147 | **注意:** 如果某个字段需要和其他子表单通信,请主动emit field-change事件,通过linkChannel进行数据交互 148 | 149 | ```html 150 | 158 | 159 | 160 | 161 | 162 | 165 | 170 | 171 | 172 | 173 | ``` 174 | 175 | ### 编写表单验证规则 176 | ```javascript 177 | rules: { 178 | name: [{ required: true, message: '请输入流水线名称', trigger: 'blur' }] 179 | } 180 | ``` 181 | 182 | ### 子表单触发事件到父组件 183 | ```javascript 184 | this.$emit("event", "eventName", args1, args2, args3) 185 | ``` 186 | 187 | ### 表单数据通信,监听表单数据变化 188 | 189 | ```javascript 190 | watch: { 191 | 'linkChannel.appType': { 192 | handler(v) { 193 | console.log(v) 194 | // 根据数据变化调整业务逻辑 195 | } 196 | } 197 | } 198 | ``` 199 | 200 | ## SubFormMixin 子表单混入功能 201 | 子表单通用功能,主要负责数据拷贝、表单验证、linkChannel数据变化传递通道等功能,开发者仅需关注业务逻辑代码编写,无需过多关注混入所需参数,MainForm组件已封装相关参数的传递和逻辑处理。 202 | 203 | ### 注入参数 204 | 205 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 206 | | ------------ | -------- | ------- | ------------------------ | ------ | 207 | | formType | 表单类型 | String | add、edit、clone、detail | -- | 208 | | formDisabled | 禁用表单 | boolean | -- | -- | 209 | 210 | ### 属性参数 211 | 212 | | 参数 | 说明 | 类型 | 默认值 | 213 | | ------------ | ------------------------------------------------------ | ------ | ------ | 214 | | form-key | 表单Key,相当于表单ID,同一组子表单中的formKey不可重复 | string | -- | 215 | | data | 表单数据 | object | {} | 216 | | link-channel | 表单字段变动监听通道 | object | {} | 217 | 218 | ## 鸣谢 219 | 感谢[@why19](https://juejin.cn/user/1794018999272525)的无私奉献,本方案主要分灵感来自于why19的[vue+element大型表单解决方案](https://juejin.cn/post/6964330851173662757) 220 | -------------------------------------------------------------------------------- /sub-form-mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 主表单mixin 3 | */ 4 | import _ from 'lodash' 5 | export default { 6 | inject: ['formType', 'formDisabled'], 7 | props: { 8 | formKey: { 9 | type: String 10 | }, 11 | data: { 12 | type: Object, 13 | default: () => ({}) 14 | }, 15 | linkChannel: { 16 | type: Object, 17 | default: () => ({}) 18 | } 19 | }, 20 | data() { 21 | return { 22 | formData: {}, 23 | ruleResults: {} 24 | } 25 | }, 26 | computed: { 27 | }, 28 | watch: { 29 | data: { 30 | handler(newValue) { 31 | this.formData = _.cloneDeep(newValue) || {} 32 | }, 33 | immediate: true 34 | } 35 | }, 36 | methods: { 37 | validForm() { 38 | let result = false 39 | this.$refs['form'].validate((valid) => { result = valid }) 40 | return result 41 | }, 42 | handleValidate(rule, isValid) { 43 | // 记录表单项结果,用于判断是否所有项都通过 44 | this.ruleResults[rule] = isValid 45 | if (!isValid) { 46 | // 只要本项失败,则通知主表单校验失败 47 | this.$emit('validate', this.formKey, false) 48 | } else { 49 | let result = true 50 | // 如果存在未通过的项,则本表单校验未通过 51 | for (const key in this.ruleResults) { 52 | if (!this.ruleResults[key]) { 53 | result = false 54 | break 55 | } 56 | } 57 | // 当不存在未通过的项时,通知主表单校验通过 58 | if (result) { 59 | this.$emit('validate', this.formKey, true) 60 | } 61 | } 62 | } 63 | } 64 | } 65 | --------------------------------------------------------------------------------