Commit 463409cd567cd67ef65f25748e693e9324339134
1 parent
a9b1e408
Exists in
master
and in
2 other branches
feat: 修改SchemaForm组件,更新文档
Showing
4 changed files
with
699 additions
and
204 deletions
Show diff stats
examples/router/routes.js
| @@ -134,6 +134,12 @@ const _develops = [ | @@ -134,6 +134,12 @@ const _develops = [ | ||
| 134 | group: '方案开发', | 134 | group: '方案开发', |
| 135 | children: [ | 135 | children: [ |
| 136 | { | 136 | { |
| 137 | + path: 'introduce', | ||
| 138 | + name: 'developSchemaIntroduce', | ||
| 139 | + meta: { title: '介绍' }, | ||
| 140 | + component: () => import('@/views/docs/develop/schema/introduce.md'), | ||
| 141 | + }, | ||
| 142 | + { | ||
| 137 | path: 'schema-form', | 143 | path: 'schema-form', |
| 138 | name: 'developSchemaForm', | 144 | name: 'developSchemaForm', |
| 139 | meta: { title: 'Schema Form 方案表单' }, | 145 | meta: { title: 'Schema Form 方案表单' }, |
examples/views/docs/component/schema-form.md
| 1 | -# Form 表单 | 1 | +# Schema Form 方案表单 |
| 2 | 2 | ||
| 3 | -根据JSON Schema配置自动生成表单 | 3 | +通过配置JSON Schema的方式快速生成一个表单 |
| 4 | 4 | ||
| 5 | ## 基础用法 | 5 | ## 基础用法 |
| 6 | 6 | ||
| 7 | -配置`list`属性设置JSON Schema配置列表 | 7 | +`schema`设置配置项,其中**props**参数与`z-form`组件参数相同,**items**则对应`z-form-item`组件,默认参数为`<component>`组件与`z-form-item`组件参数的并集。 |
| 8 | 8 | ||
| 9 | -<div class="code-snippet-box"> | ||
| 10 | - | ||
| 11 | -::: snippet `v-model`值动态改变,支持初始化 | 9 | +::: snippet 基础示例 |
| 12 | 10 | ||
| 13 | ```html | 11 | ```html |
| 14 | <template> | 12 | <template> |
| 15 | - <div> | ||
| 16 | - <pre class="demo-model">{{ model }}</pre> | ||
| 17 | - <z-schema-form v-model="model" :list="list"></z-schema-form> | ||
| 18 | - </div> | 13 | + <z-schema-form v-model="form" :schema="schema"></z-schema-form> |
| 19 | </template> | 14 | </template> |
| 20 | 15 | ||
| 21 | <script> | 16 | <script> |
| 22 | export default { | 17 | export default { |
| 23 | data() { | 18 | data() { |
| 24 | return { | 19 | return { |
| 25 | - model: { name: '', age: '' }, | ||
| 26 | - list: [ | ||
| 27 | - { type: 'el-input', label: '姓名', key: 'name' }, | ||
| 28 | - { type: 'el-input-number', label: '年龄', key: 'age', props: { 'controls-position': 'right' } }, | ||
| 29 | - ] | ||
| 30 | - } | ||
| 31 | - } | ||
| 32 | -} | 20 | + form: { |
| 21 | + name: '张三', | ||
| 22 | + age: 27, | ||
| 23 | + address: '上海市青浦区华新镇纪鹤公路1988号', | ||
| 24 | + }, | ||
| 25 | + schema: { | ||
| 26 | + props: { labelWidth: '70px', size: 'small', span: 12 }, | ||
| 27 | + items: [ | ||
| 28 | + { is: 'el-input', prop: 'name', label: '姓名' }, | ||
| 29 | + { is: 'el-input-number', prop: 'age', label: '年龄' }, | ||
| 30 | + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 }, | ||
| 31 | + ] | ||
| 32 | + }, | ||
| 33 | + }; | ||
| 34 | + }, | ||
| 35 | +}; | ||
| 33 | </script> | 36 | </script> |
| 34 | ``` | 37 | ``` |
| 35 | 38 | ||
| 36 | ::: | 39 | ::: |
| 37 | 40 | ||
| 38 | -::: snippet `span`设置行占位 | 41 | +## 表单校验 |
| 42 | + | ||
| 43 | +配置下的`props`对应`z-form`的参数,可以在此统一配置`rules`;配置下的`items`的每一项都对应`z-form-item`,也可以直接配置`rules`设置该项的校验规则。 | ||
| 44 | + | ||
| 45 | +<div class="code-snippet-box"> | ||
| 46 | + | ||
| 47 | +::: snippet 设置**props**下的校验规则示例 | ||
| 39 | 48 | ||
| 40 | ```html | 49 | ```html |
| 41 | <template> | 50 | <template> |
| 42 | - <z-schema-form v-model="model" :list="list" :span="12"></z-schema-form> | 51 | + <z-schema-form v-model="form" :schema="schema"></z-schema-form> |
| 43 | </template> | 52 | </template> |
| 44 | 53 | ||
| 45 | <script> | 54 | <script> |
| 46 | export default { | 55 | export default { |
| 47 | data() { | 56 | data() { |
| 48 | return { | 57 | return { |
| 49 | - model: { name: '', age: '' }, | ||
| 50 | - list: [ | ||
| 51 | - { type: 'el-input', label: '姓名', key: 'name' }, | ||
| 52 | - { type: 'el-input-number', label: '年龄', key: 'age', props: { 'controls-position': 'right' } }, | ||
| 53 | - ] | ||
| 54 | - } | ||
| 55 | - } | ||
| 56 | -} | 58 | + form: { |
| 59 | + name: '张三', | ||
| 60 | + age: 27, | ||
| 61 | + address: '上海市青浦区华新镇纪鹤公路1988号', | ||
| 62 | + }, | ||
| 63 | + schema: { | ||
| 64 | + props: { | ||
| 65 | + labelWidth: '70px', | ||
| 66 | + size: 'small', | ||
| 67 | + span: 12, | ||
| 68 | + rules: { | ||
| 69 | + name: [{ required: true, message: '请输入姓名' }], | ||
| 70 | + age: [{ required: true, message: '请输入有效年龄' }], | ||
| 71 | + } | ||
| 72 | + }, | ||
| 73 | + items: [ | ||
| 74 | + { is: 'el-input', prop: 'name', label: '姓名' }, | ||
| 75 | + { is: 'el-input-number', prop: 'age', label: '年龄' }, | ||
| 76 | + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 }, | ||
| 77 | + ] | ||
| 78 | + }, | ||
| 79 | + }; | ||
| 80 | + }, | ||
| 81 | +}; | ||
| 57 | </script> | 82 | </script> |
| 58 | ``` | 83 | ``` |
| 59 | 84 | ||
| 60 | ::: | 85 | ::: |
| 61 | 86 | ||
| 62 | -::: snippet `label-width`设置标题宽度 | 87 | +::: snippet 设置**items**下的校验规则示例 |
| 63 | 88 | ||
| 64 | ```html | 89 | ```html |
| 65 | <template> | 90 | <template> |
| 66 | - <z-schema-form v-model="model" :list="list" label-width="60px"></z-schema-form> | 91 | + <z-schema-form v-model="form" :schema="schema"></z-schema-form> |
| 67 | </template> | 92 | </template> |
| 68 | 93 | ||
| 69 | <script> | 94 | <script> |
| 70 | export default { | 95 | export default { |
| 71 | data() { | 96 | data() { |
| 72 | return { | 97 | return { |
| 73 | - model: { name: '', age: '' }, | ||
| 74 | - list: [ | ||
| 75 | - { type: 'el-input', label: '姓名', key: 'name' }, | ||
| 76 | - { type: 'el-input-number', label: '年龄', key: 'age', props: { 'controls-position': 'right' } }, | ||
| 77 | - ] | ||
| 78 | - } | ||
| 79 | - } | ||
| 80 | -} | 98 | + form: { |
| 99 | + name: '张三', | ||
| 100 | + age: 27, | ||
| 101 | + address: '上海市青浦区华新镇纪鹤公路1988号', | ||
| 102 | + }, | ||
| 103 | + schema: { | ||
| 104 | + props: { labelWidth: '70px', size: 'small', span: 12 }, | ||
| 105 | + items: [ | ||
| 106 | + { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] }, | ||
| 107 | + { is: 'el-input-number', prop: 'age', label: '年龄', rules: [{ required: true, message: '请输入有效年龄' }] }, | ||
| 108 | + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 }, | ||
| 109 | + ] | ||
| 110 | + }, | ||
| 111 | + }; | ||
| 112 | + }, | ||
| 113 | +}; | ||
| 81 | </script> | 114 | </script> |
| 82 | ``` | 115 | ``` |
| 83 | 116 | ||
| @@ -85,196 +118,378 @@ export default { | @@ -85,196 +118,378 @@ export default { | ||
| 85 | 118 | ||
| 86 | </div> | 119 | </div> |
| 87 | 120 | ||
| 88 | -## 表单校验 | ||
| 89 | - | ||
| 90 | -配置`list`的配置项中的`rules` | 121 | +## 表单提交 |
| 91 | 122 | ||
| 92 | -<div class="code-snippet-box"> | 123 | +表单会默认提供一个`footer插槽`,并提供表单的提交、取消、重置等方法,且相关方法都有对应的事件监听。也可以不使用插槽提供的方法直接自定义。 |
| 93 | 124 | ||
| 94 | -::: snippet `v-model`值动态改变,支持初始化 | 125 | +::: snippet 配置`footer`插槽提供触发方法的示例 |
| 95 | 126 | ||
| 96 | ```html | 127 | ```html |
| 97 | <template> | 128 | <template> |
| 98 | - <div> | ||
| 99 | - <z-schema-form ref="form" v-model="model" :list="list" :span="12" @validate="onValidate" @reset="onReset"></z-schema-form> | ||
| 100 | - <el-button size="small" type="primary" @click="submit">提交</el-button> | ||
| 101 | - <el-button size="small" plain @click="reset">重置</el-button> | ||
| 102 | - </div> | 129 | + <z-schema-form v-model="form" :schema="schema" @submit="onSubmit" @reset="onReset" @cancel="onCancel"> |
| 130 | + <template #footer="{ submit, reset, cancel }"> | ||
| 131 | + <z-form-item> | ||
| 132 | + <el-button type="primary" @click="submit">确定</el-button> | ||
| 133 | + <el-button plain @click="reset">重置</el-button> | ||
| 134 | + <el-button plain @click="cancel">取消</el-button> | ||
| 135 | + </z-form-item> | ||
| 136 | + </template> | ||
| 137 | + </z-schema-form> | ||
| 103 | </template> | 138 | </template> |
| 104 | 139 | ||
| 105 | <script> | 140 | <script> |
| 106 | export default { | 141 | export default { |
| 107 | data() { | 142 | data() { |
| 108 | return { | 143 | return { |
| 109 | - model: { name: '', age: '' }, | ||
| 110 | - list: [ | ||
| 111 | - { type: 'el-input', label: '姓名', key: 'name', rules: [{ required: true, message: '请输入', trigger: 'change' }] }, | ||
| 112 | - { type: 'el-input-number', label: '年龄', key: 'age', props: { 'controls-position': 'right' } }, | ||
| 113 | - ] | ||
| 114 | - } | 144 | + form: { |
| 145 | + name: '张三', | ||
| 146 | + age: 27, | ||
| 147 | + address: '上海市青浦区华新镇纪鹤公路1988号', | ||
| 148 | + }, | ||
| 149 | + schema: { | ||
| 150 | + props: { labelWidth: '70px', size: 'small', span: 12 }, | ||
| 151 | + items: [ | ||
| 152 | + { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] }, | ||
| 153 | + { is: 'el-input-number', prop: 'age', label: '年龄', rules: [{ required: true, message: '请输入有效年龄' }] }, | ||
| 154 | + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 }, | ||
| 155 | + ] | ||
| 156 | + }, | ||
| 157 | + }; | ||
| 115 | }, | 158 | }, |
| 116 | methods: { | 159 | methods: { |
| 117 | - onValidate(valid, model) { | ||
| 118 | - if (valid) { | ||
| 119 | - console.log(model); | ||
| 120 | - } | 160 | + onSubmit(value) { |
| 161 | + console.log(value); | ||
| 121 | }, | 162 | }, |
| 122 | onReset() { | 163 | onReset() { |
| 123 | - Object.keys(this.model).forEach(key => { | ||
| 124 | - this.model[key] = ''; | 164 | + console.log('reset'); |
| 165 | + }, | ||
| 166 | + onCancel() { | ||
| 167 | + console.log('cancal'); | ||
| 168 | + } | ||
| 169 | + } | ||
| 170 | +}; | ||
| 171 | +</script> | ||
| 172 | +``` | ||
| 173 | + | ||
| 174 | +::: | ||
| 175 | + | ||
| 176 | +::: snippet 自定义提交的示例 | ||
| 177 | + | ||
| 178 | +```html | ||
| 179 | +<template> | ||
| 180 | + <z-schema-form ref="form" v-model="form" :schema="schema" @submit="onSubmit" @reset="onReset" @cancel="onCancel"> | ||
| 181 | + <template #footer> | ||
| 182 | + <z-form-item> | ||
| 183 | + <el-button type="primary" @click="onSubmit">确定</el-button> | ||
| 184 | + <el-button plain @click="onReset">重置</el-button> | ||
| 185 | + <el-button plain @click="onCancel">取消</el-button> | ||
| 186 | + </z-form-item> | ||
| 187 | + </template> | ||
| 188 | + </z-schema-form> | ||
| 189 | +</template> | ||
| 190 | + | ||
| 191 | +<script> | ||
| 192 | +export default { | ||
| 193 | + data() { | ||
| 194 | + return { | ||
| 195 | + form: { | ||
| 196 | + name: '张三', | ||
| 197 | + age: 27, | ||
| 198 | + address: '上海市青浦区华新镇纪鹤公路1988号', | ||
| 199 | + }, | ||
| 200 | + schema: { | ||
| 201 | + props: { labelWidth: '70px', size: 'small', span: 12 }, | ||
| 202 | + items: [ | ||
| 203 | + { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] }, | ||
| 204 | + { is: 'el-input-number', prop: 'age', label: '年龄', rules: [{ required: true, message: '请输入有效年龄' }] }, | ||
| 205 | + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 }, | ||
| 206 | + ] | ||
| 207 | + }, | ||
| 208 | + }; | ||
| 209 | + }, | ||
| 210 | + methods: { | ||
| 211 | + onSubmit() { | ||
| 212 | + this.$refs.form.validate(valid => { | ||
| 213 | + if (valid) { | ||
| 214 | + console.log(this.form); | ||
| 215 | + } | ||
| 125 | }); | 216 | }); |
| 126 | }, | 217 | }, |
| 127 | - submit() { | ||
| 128 | - this.$refs.form.validate(); | 218 | + onReset() { |
| 219 | + this.$refs.form.resetFields(); | ||
| 129 | }, | 220 | }, |
| 130 | - reset() { | ||
| 131 | - this.$refs.form.reset(); | 221 | + onCancel() { |
| 222 | + console.log('cancal'); | ||
| 132 | } | 223 | } |
| 133 | } | 224 | } |
| 134 | -} | 225 | +}; |
| 135 | </script> | 226 | </script> |
| 136 | ``` | 227 | ``` |
| 137 | 228 | ||
| 138 | ::: | 229 | ::: |
| 139 | 230 | ||
| 140 | -</div> | 231 | +## 配置复用 |
| 141 | 232 | ||
| 142 | -## 表单分组 | 233 | +因为与`z-form`、`z-form-item`用法相同,所以除了可以用作表单以外,也可用作详情展示,同时因为本身是schema配置项,所以可以做到表单和详情**配置复用**。 |
| 143 | 234 | ||
| 144 | -将表单内容进行分组显示 | ||
| 145 | - | ||
| 146 | -::: snippet 表单配置项的`group`值设置分组 | 235 | +::: snippet 配置复用示例 |
| 147 | 236 | ||
| 148 | ```html | 237 | ```html |
| 149 | <template> | 238 | <template> |
| 150 | - <div> | ||
| 151 | - <pre class="demo-model">{{ model }}</pre> | ||
| 152 | - <z-schema-form v-model="model" :list="list" :span="8" label-width="80px"></z-schema-form> | ||
| 153 | - </div> | 239 | + <el-row> |
| 240 | + <el-col :span="12"> | ||
| 241 | + <div style="margin-bottom: 20px">表单</div> | ||
| 242 | + <z-schema-form v-model="form" :schema="schema"></z-schema-form> | ||
| 243 | + </el-col> | ||
| 244 | + <el-col :span="12"> | ||
| 245 | + <div style="margin-bottom: 20px">详情</div> | ||
| 246 | + <z-schema-form :value="detail" :schema="detailSchema"></z-schema-form> | ||
| 247 | + </el-col> | ||
| 248 | + </el-row> | ||
| 154 | </template> | 249 | </template> |
| 155 | 250 | ||
| 156 | <script> | 251 | <script> |
| 157 | export default { | 252 | export default { |
| 158 | data() { | 253 | data() { |
| 159 | return { | 254 | return { |
| 160 | - model: { name: '', age: '', company: '', address: '', time: '', salary: '' }, | ||
| 161 | - list: [ | ||
| 162 | - { | ||
| 163 | - group: { title: '基础信息' }, | ||
| 164 | - list: [ | ||
| 165 | - { type: 'el-input', label: '姓名', key: 'name', rules: [{ required: true, message: '请输入', trigger: 'change' }], span: 12 }, | ||
| 166 | - { type: 'el-input-number', label: '年龄', key: 'age', props: { 'controls-position': 'right' }, span: 12 }, | ||
| 167 | - ], | ||
| 168 | - }, | ||
| 169 | - { | ||
| 170 | - group: { title: '工作信息' }, | ||
| 171 | - list: [ | ||
| 172 | - { type: 'el-input', label: '公司', key: 'company' }, | ||
| 173 | - { type: 'el-input', label: '地址', key: 'address' }, | ||
| 174 | - { type: 'el-input', label: '时限', key: 'time' }, | ||
| 175 | - { type: 'el-input', label: '薪资', key: 'salary' }, | ||
| 176 | - ], | ||
| 177 | - }, | ||
| 178 | - ] | ||
| 179 | - } | 255 | + form: { |
| 256 | + name: '张三', | ||
| 257 | + age: 27, | ||
| 258 | + address: '上海市青浦区华新镇纪鹤公路1988号', | ||
| 259 | + }, | ||
| 260 | + detail: { | ||
| 261 | + name: '张三', | ||
| 262 | + age: 27, | ||
| 263 | + address: '上海市青浦区华新镇纪鹤公路1988号', | ||
| 264 | + }, | ||
| 265 | + schema: { | ||
| 266 | + props: { labelWidth: '70px', size: 'small', span: 12 }, | ||
| 267 | + items: [ | ||
| 268 | + { is: 'el-input', prop: 'name', label: '姓名' }, | ||
| 269 | + { is: 'el-input-number', prop: 'age', label: '年龄' }, | ||
| 270 | + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 }, | ||
| 271 | + ] | ||
| 272 | + }, | ||
| 273 | + }; | ||
| 180 | }, | 274 | }, |
| 181 | -} | 275 | + computed: { |
| 276 | + detailSchema() { | ||
| 277 | + const schema = this.schema; | ||
| 278 | + // 去掉所有配置项的is参数即默认以文本展示 | ||
| 279 | + return { ...schema, items: schema.items.map(({ is, ...other }) => ({ ...other })) } | ||
| 280 | + } | ||
| 281 | + } | ||
| 282 | +}; | ||
| 182 | </script> | 283 | </script> |
| 183 | ``` | 284 | ``` |
| 184 | 285 | ||
| 185 | ::: | 286 | ::: |
| 186 | 287 | ||
| 187 | -## 自定义渲染 | 288 | +## 自定义插槽 |
| 188 | 289 | ||
| 189 | -对于一些特殊渲染逻辑的表单项,可以进行自定义渲染 | 290 | +在某些特殊情况下,表单项可能是当前页面的临时组件,或者项目并没有全局引入组件,亦或者需要一些特殊的函数去处理,此时可以使用自定义插槽替换默认配置项。插槽默认提供了`value`和`onInput`用于维护当前值。 |
| 190 | 291 | ||
| 191 | -<div class="code-snippet-box"> | ||
| 192 | - | ||
| 193 | -::: snippet 通过`render`方法渲染 | 292 | +::: snippet schema配置中items配置项的`is`设置为`slot`,即可在组件下通过插槽的方式替换配置项。默认支持`具名插槽`、`label`插槽、`error`插槽,与`el-form-item`的插槽类似。 |
| 194 | 293 | ||
| 195 | ```html | 294 | ```html |
| 196 | <template> | 295 | <template> |
| 197 | - <div> | ||
| 198 | - <pre class="demo-model">{{ model }}</pre> | ||
| 199 | - <z-schema-form v-model="model" :list="list" label-width="80px"></z-schema-form> | ||
| 200 | - </div> | 296 | + <z-schema-form v-model="form" :schema="schema"> |
| 297 | + <!-- error插槽,作用:修改当前项的校验信息样式 --> | ||
| 298 | + <template #error-name> | ||
| 299 | + <div><span>请输入</span><el-tag size="mini">格式正确</el-tag><span>的姓名</span></div> | ||
| 300 | + </template> | ||
| 301 | + <!-- label插槽,作用:自定义当前项的label --> | ||
| 302 | + <template #label-age> | ||
| 303 | + <el-tooltip content="说明" placement="top" effect="light"> | ||
| 304 | + <i class="el-icon-question"></i> | ||
| 305 | + </el-tooltip> | ||
| 306 | + <span>年龄</span> | ||
| 307 | + </template> | ||
| 308 | + <!-- 具名插槽,作用:自定义当前项的值 --> | ||
| 309 | + <template #address="{ value, onInput }"> | ||
| 310 | + <div> | ||
| 311 | + <el-link size="mini" type="primary" :underline="false" @click="() => onInput('当前地址xxxxxx')">键入当前地址</el-link> | ||
| 312 | + <el-input type="textarea" :value="value" @input="onInput" /> | ||
| 313 | + </div> | ||
| 314 | + </template> | ||
| 315 | + </z-schema-form> | ||
| 201 | </template> | 316 | </template> |
| 202 | 317 | ||
| 203 | <script> | 318 | <script> |
| 204 | export default { | 319 | export default { |
| 205 | data() { | 320 | data() { |
| 206 | return { | 321 | return { |
| 207 | - model: { name: '', age: '' }, | ||
| 208 | - list: [ | ||
| 209 | - { type: 'el-input', label: '姓名', key: 'name' }, | ||
| 210 | - { type: 'el-input', label: '年龄', key: 'age', type: (h, { model, config }) => { return h('el-input-number', config, model.age) }, props: { 'controls-position': 'right' } }, | ||
| 211 | - ] | ||
| 212 | - } | 322 | + form: { |
| 323 | + name: '张三', | ||
| 324 | + age: 27, | ||
| 325 | + address: '上海市青浦区华新镇纪鹤公路1988号', | ||
| 326 | + }, | ||
| 327 | + schema: { | ||
| 328 | + props: { labelWidth: '70px', size: 'small', span: 12 }, | ||
| 329 | + items: [ | ||
| 330 | + { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] }, | ||
| 331 | + { is: 'el-input-number', prop: 'age', label: '年龄' }, | ||
| 332 | + { is: 'slot', prop: 'address', label: '住址', span: 24 }, | ||
| 333 | + ] | ||
| 334 | + }, | ||
| 335 | + }; | ||
| 213 | }, | 336 | }, |
| 214 | -} | 337 | +}; |
| 215 | </script> | 338 | </script> |
| 216 | ``` | 339 | ``` |
| 217 | 340 | ||
| 218 | ::: | 341 | ::: |
| 219 | 342 | ||
| 220 | -::: snippet 通过`slot`插槽渲染 | 343 | +## 配置子节点 |
| 344 | + | ||
| 345 | +使用表单时,除了可能会出现单组件子节点以外,可能出现带有子节点的子组件。schema也支持对子节点进行配置。 | ||
| 346 | + | ||
| 347 | +::: snippet schema配置中items配置项的`children`可以配置子节点,格式同`<component>`组件。 | ||
| 221 | 348 | ||
| 222 | ```html | 349 | ```html |
| 223 | <template> | 350 | <template> |
| 224 | - <div> | ||
| 225 | - <pre class="demo-model">{{ model }}</pre> | ||
| 226 | - <z-schema-form v-model="model" :list="list" label-width="80px"> | ||
| 227 | - <template slot="name"> | ||
| 228 | - <el-radio v-model="model.name" label="zs">张三</el-radio> | ||
| 229 | - <el-radio v-model="model.name" label="ls">李四</el-radio> | ||
| 230 | - </template> | ||
| 231 | - <template slot="age"> | ||
| 232 | - <el-slider v-model="model.age"></el-slider> | ||
| 233 | - </template> | ||
| 234 | - </z-schema-form> | ||
| 235 | - </div> | 351 | + <z-schema-form v-model="form" :schema="schema"></z-schema-form> |
| 236 | </template> | 352 | </template> |
| 237 | 353 | ||
| 238 | <script> | 354 | <script> |
| 239 | export default { | 355 | export default { |
| 240 | data() { | 356 | data() { |
| 241 | return { | 357 | return { |
| 242 | - model: { name: 'zs', age: 0 }, | ||
| 243 | - list: [ | ||
| 244 | - { type: 'el-input', label: '姓名', key: 'name' }, | ||
| 245 | - { type: 'el-input', label: '年龄', key: 'age' }, | ||
| 246 | - ] | ||
| 247 | - } | 358 | + form: { |
| 359 | + name: '张三', | ||
| 360 | + age: 27, | ||
| 361 | + address: '上海市青浦区华新镇纪鹤公路1988号', | ||
| 362 | + packages: ['零食'], | ||
| 363 | + }, | ||
| 364 | + schema: { | ||
| 365 | + props: { labelWidth: '70px', size: 'small', span: 12 }, | ||
| 366 | + items: [ | ||
| 367 | + { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] }, | ||
| 368 | + { is: 'el-input-number', prop: 'age', label: '年龄' }, | ||
| 369 | + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 }, | ||
| 370 | + { | ||
| 371 | + is: 'el-checkbox-group', | ||
| 372 | + prop: 'packages', | ||
| 373 | + label: '包裹', | ||
| 374 | + children: [ | ||
| 375 | + { is: 'el-checkbox', props: { label: '零食' } }, | ||
| 376 | + { is: 'el-checkbox', props: { label: '手机' } }, | ||
| 377 | + { is: 'el-checkbox', props: { label: '电脑' } }, | ||
| 378 | + ], | ||
| 379 | + span: 24 | ||
| 380 | + }, | ||
| 381 | + ] | ||
| 382 | + }, | ||
| 383 | + }; | ||
| 248 | }, | 384 | }, |
| 249 | -} | 385 | +}; |
| 250 | </script> | 386 | </script> |
| 251 | ``` | 387 | ``` |
| 252 | 388 | ||
| 253 | ::: | 389 | ::: |
| 254 | 390 | ||
| 255 | -</div> | 391 | +## 复杂表单 |
| 392 | + | ||
| 393 | +一般情况下,复杂表单建议使用`z-form`、`z-form-item`等组件构建视图,`不建议`使用schema的方式配置。但是利用schema配置项的一些特性,也可以做到部分复杂表单使用schema配置完成。 | ||
| 394 | + | ||
| 395 | +::: snippet 包含了**插槽**、**子节点**、**自定义子节点**的复杂示例 | ||
| 396 | + | ||
| 397 | +```html | ||
| 398 | +<template> | ||
| 399 | + <z-schema-form v-model="form" :schema="schema" @submit="onSubmit" @cancel="onCancel"> | ||
| 400 | + <template #error-name> | ||
| 401 | + <div><span>请输入</span><el-tag size="mini">格式正确</el-tag><span>的姓名</span></div> | ||
| 402 | + </template> | ||
| 403 | + <template #label-packages> | ||
| 404 | + <el-tooltip content="详情" placement="top" effect="light"> | ||
| 405 | + <i class="el-icon-question"></i> | ||
| 406 | + </el-tooltip> | ||
| 407 | + <span>包裹</span> | ||
| 408 | + </template> | ||
| 409 | + </z-schema-form> | ||
| 410 | +</template> | ||
| 411 | + | ||
| 412 | +<script> | ||
| 413 | +export default { | ||
| 414 | + data() { | ||
| 415 | + return { | ||
| 416 | + form: { | ||
| 417 | + name: '张三', | ||
| 418 | + age: 27, | ||
| 419 | + address: '上海市青浦区华新镇纪鹤公路1988号', | ||
| 420 | + packages: ['零食'], | ||
| 421 | + info: { | ||
| 422 | + mobile: '18888888888', | ||
| 423 | + }, | ||
| 424 | + admin: 'false', | ||
| 425 | + }, | ||
| 426 | + schema: { | ||
| 427 | + props: { labelWidth: '70px', size: 'small', span: 12 }, | ||
| 428 | + items: [ | ||
| 429 | + { is: 'el-input', prop: 'name', label: '姓名', rules: [{ required: true, message: '请输入姓名' }] }, | ||
| 430 | + { is: 'el-input-number', prop: 'age', label: '年龄' }, | ||
| 431 | + { is: 'el-input', prop: 'info.mobile', label: '电话' }, | ||
| 432 | + { is: 'el-divider', props: { 'content-position': 'left' }, children: '收货信息', span: 24, labelWidth: '0px' }, | ||
| 433 | + { is: 'el-input', props: { type: 'textarea' }, prop: 'address', label: '住址', span: 24 }, | ||
| 434 | + { | ||
| 435 | + is: 'el-checkbox-group', | ||
| 436 | + prop: 'packages', | ||
| 437 | + label: '包裹', | ||
| 438 | + children: [ | ||
| 439 | + { is: 'el-checkbox', props: { label: '零食' } }, | ||
| 440 | + { is: 'el-checkbox', props: { label: '手机' } }, | ||
| 441 | + { is: 'el-checkbox', props: { label: '电脑' } }, | ||
| 442 | + ], | ||
| 443 | + }, | ||
| 444 | + { | ||
| 445 | + is: 'el-select', | ||
| 446 | + prop: 'admin', | ||
| 447 | + label: '管理员', | ||
| 448 | + children: [ | ||
| 449 | + { | ||
| 450 | + is: 'el-option', | ||
| 451 | + props: { label: '是', value: 'true' }, | ||
| 452 | + children: [ | ||
| 453 | + { | ||
| 454 | + is: 'div', | ||
| 455 | + attrs: { style: 'display: flex; align-items: center; justify-content: space-between' }, | ||
| 456 | + children: [ | ||
| 457 | + { is: 'span', children: '是' }, | ||
| 458 | + { is: 'el-tag', props: { size: 'mini', type: 'success' }, children: 'yes' }, | ||
| 459 | + ] | ||
| 460 | + }, | ||
| 461 | + ] | ||
| 462 | + }, | ||
| 463 | + { | ||
| 464 | + is: 'el-option', | ||
| 465 | + props: { label: '否', value: 'false' }, | ||
| 466 | + children: [ | ||
| 467 | + { | ||
| 468 | + is: 'div', | ||
| 469 | + attrs: { style: 'display: flex; align-items: center; justify-content: space-between' }, | ||
| 470 | + children: [ | ||
| 471 | + { is: 'span', children: '否' }, | ||
| 472 | + { is: 'el-tag', props: { size: 'mini', type: 'warning' }, children: 'no' }, | ||
| 473 | + ] | ||
| 474 | + }, | ||
| 475 | + ] | ||
| 476 | + }, | ||
| 477 | + ], | ||
| 478 | + }, | ||
| 479 | + ] | ||
| 480 | + }, | ||
| 481 | + }; | ||
| 482 | + }, | ||
| 483 | + methods: { | ||
| 484 | + onSubmit(value) { | ||
| 485 | + console.log(value); | ||
| 486 | + }, | ||
| 487 | + onCancel() { | ||
| 488 | + console.log('cancal'); | ||
| 489 | + } | ||
| 490 | + } | ||
| 491 | +}; | ||
| 492 | +</script> | ||
| 493 | +``` | ||
| 256 | 494 | ||
| 257 | -## API | ||
| 258 | - | ||
| 259 | -## Attribute 属性 | ||
| 260 | - | ||
| 261 | -参数|说明|类型|可选值|默认值 | ||
| 262 | --|-|-|-|- | ||
| 263 | -value | 值 | Object | - | - | ||
| 264 | -list | JSON Schema配置项列表 | Array | - | [] | ||
| 265 | -formClass | 表单class | String | - | - | ||
| 266 | -titleClass | 标题class | String | - | - | ||
| 267 | -contentClass | 内容class | String | - | - | ||
| 268 | -itemClass | 表单项class | String | - | - | ||
| 269 | -groupClass | 表单分组class | String | - | - | ||
| 270 | -labelWidth | 表单项标题宽度 | String | - | - | ||
| 271 | -labelPosition | 表单项标题位置 | String | - | top | ||
| 272 | -size | 大小 | String | - | small | ||
| 273 | -span | 占位 | Number | - | 24 | ||
| 274 | - | ||
| 275 | -## Events 事件 | ||
| 276 | - | ||
| 277 | -事件名称|说明|回调参数 | ||
| 278 | --|-|- | ||
| 279 | -input | 表单值变化 | 表单model | ||
| 280 | -validate | 校验表单 | 是否通过,表单model | ||
| 281 | \ No newline at end of file | 495 | \ No newline at end of file |
| 496 | +::: | ||
| 282 | \ No newline at end of file | 497 | \ No newline at end of file |
| @@ -0,0 +1,260 @@ | @@ -0,0 +1,260 @@ | ||
| 1 | +# 介绍 | ||
| 2 | + | ||
| 3 | +方案开发是一种使用类`JSON Schema`格式的预置数据,快速生成一个业务逻辑视图,并且能够被引用及复用。 | ||
| 4 | + | ||
| 5 | +## 使用场景 | ||
| 6 | + | ||
| 7 | +① 复杂后台管理项目中,某些相同的功能可能会多次出现在不同的使用场景中,并且**同时**具有**相似性**和**差异性**,而**组件化**不能更方便的解决类似的场景。 | ||
| 8 | + | ||
| 9 | +② 逻辑并不复杂,但是表单项非常多,需要反复编写大量**重复**的**布局代码**,导致**代码量大**,业务逻辑代码**维护不便**的场景。 | ||
| 10 | + | ||
| 11 | +③ 不同功能页面下,使用了相同的表单或表格等相同的功能模块,遇到**改动**时,需要重新在多个页面里多次**重复修改**代码的场景。 | ||
| 12 | + | ||
| 13 | +## 示例 | ||
| 14 | + | ||
| 15 | +某页面的详情功能,常规情形下,会出现大量重复的布局组件`el-row`、`el-col`及表单组件`el-form-item`等,进行取值展示时,也需要多次书写`form.xxx`。本是一个没有业务逻辑的展示型页面,但大量书写的重复的代码,导致行数过多,且如遇到增删某个展示项时,还需要重新调整布局代码。总体上来说,代码可读性低,可维护性差。 | ||
| 16 | + | ||
| 17 | +代码: | ||
| 18 | + | ||
| 19 | +```html | ||
| 20 | +<template> | ||
| 21 | + <el-form :model="form" size="small" label-width="100px"> | ||
| 22 | + <el-row> | ||
| 23 | + <el-col :span="12"> | ||
| 24 | + <el-form-item label="线路名称"> | ||
| 25 | + {{ form.dispatchLine.lineName }} | ||
| 26 | + </el-form-item> | ||
| 27 | + </el-col> | ||
| 28 | + <el-col :span="12"> | ||
| 29 | + <el-form-item label="出发站点"> | ||
| 30 | + {{ form.dispatchLine.startStation ? form.dispatchLine.startStation.name : '' }} | ||
| 31 | + </el-form-item> | ||
| 32 | + </el-col> | ||
| 33 | + </el-row> | ||
| 34 | + <el-row> | ||
| 35 | + <el-col :span="12"> | ||
| 36 | + <el-form-item label="目的站点"> | ||
| 37 | + {{ form.dispatchLine.endStation ? form.dispatchLine.endStation.name : '' }} | ||
| 38 | + </el-form-item> | ||
| 39 | + </el-col> | ||
| 40 | + <el-col :span="12"> | ||
| 41 | + <el-form-item label="客户名称"> | ||
| 42 | + {{ form.customer ? form.customer.name : '' }} | ||
| 43 | + </el-form-item> | ||
| 44 | + </el-col> | ||
| 45 | + </el-row> | ||
| 46 | + <el-row> | ||
| 47 | + <el-col :span="12"> | ||
| 48 | + <el-form-item label="线路类型"> | ||
| 49 | + <template> | ||
| 50 | + <dictionary-select-name option-name="LINE_TYPE" :value="form.dispatchLine.lineType"></dictionary-select-name> | ||
| 51 | + </template> | ||
| 52 | + </el-form-item> | ||
| 53 | + </el-col> | ||
| 54 | + <el-col :span="12"> | ||
| 55 | + <el-form-item label="出发城市"> | ||
| 56 | + {{ form.dispatchLine.startStation.city.name }} | ||
| 57 | + </el-form-item> | ||
| 58 | + </el-col> | ||
| 59 | + </el-row> | ||
| 60 | + <el-row> | ||
| 61 | + <el-col :span="12"> | ||
| 62 | + <el-form-item label="经停城市"> | ||
| 63 | + {{ form.dispatchLine.stopOverCityName }} | ||
| 64 | + </el-form-item> | ||
| 65 | + </el-col> | ||
| 66 | + <el-col :span="12"> | ||
| 67 | + <el-form-item label="目的城市"> | ||
| 68 | + {{ form.dispatchLine.endStation.city.name }} | ||
| 69 | + </el-form-item> | ||
| 70 | + </el-col> | ||
| 71 | + </el-row> | ||
| 72 | + </el-form> | ||
| 73 | +</template> | ||
| 74 | + | ||
| 75 | +<script> | ||
| 76 | +export default { | ||
| 77 | + data() { | ||
| 78 | + return { | ||
| 79 | + form: { | ||
| 80 | + dispatchLine: { | ||
| 81 | + startStation: { | ||
| 82 | + city: {}, | ||
| 83 | + }, | ||
| 84 | + endStation: { | ||
| 85 | + city: {}, | ||
| 86 | + }, | ||
| 87 | + }, | ||
| 88 | + customer: {}, | ||
| 89 | + }, | ||
| 90 | + }; | ||
| 91 | + }, | ||
| 92 | +} | ||
| 93 | +</script> | ||
| 94 | +``` | ||
| 95 | + | ||
| 96 | +仅视图层就有53行代码,改用Schema开发后可将视图层代码极致精简,可以使后端开发员修改前端代码时不需要关心界面布局及样式,只关心业务逻辑。 | ||
| 97 | + | ||
| 98 | +使用Schema开发后的代码,视图层1行,而配置项也仅需13行代码,并且无需关心布局代码和多层级数据取值容错: | ||
| 99 | + | ||
| 100 | +```html | ||
| 101 | +<template> | ||
| 102 | + <z-schema-form v-model="form" :schema="schema"></z-schema-form> | ||
| 103 | +</template> | ||
| 104 | + | ||
| 105 | +<script> | ||
| 106 | +export default { | ||
| 107 | + data() { | ||
| 108 | + return { | ||
| 109 | + schema: { | ||
| 110 | + props: { size: 'small', 'label-width': '100px', span: 12 }, | ||
| 111 | + items: [ | ||
| 112 | + { prop: 'dispatchLine.lineName', label: '线路名称' }, | ||
| 113 | + { prop: 'dispatchLine.startStation.name', label: '出发站点' }, | ||
| 114 | + { prop: 'dispatchLine.endStation.name', label: '目的站点' }, | ||
| 115 | + { prop: 'customer.name', label: '客户名称' }, | ||
| 116 | + { is: 'dictionary-select-name', props: { 'option-name': 'LINE_TYPE' }, prop: 'customer.name', label: '线路类型' }, | ||
| 117 | + { prop: 'dispatchLine.startStation.city.name', label: '出发城市' }, | ||
| 118 | + { prop: 'dispatchLine.stopOverCityName', label: '经停城市' }, | ||
| 119 | + { prop: 'dispatchLine.endStation.city.name', label: '经停城市' }, | ||
| 120 | + ] | ||
| 121 | + }, | ||
| 122 | + form: { | ||
| 123 | + dispatchLine: { | ||
| 124 | + startStation: { | ||
| 125 | + city: {}, | ||
| 126 | + }, | ||
| 127 | + endStation: { | ||
| 128 | + city: {}, | ||
| 129 | + }, | ||
| 130 | + }, | ||
| 131 | + customer: {}, | ||
| 132 | + }, | ||
| 133 | + }; | ||
| 134 | + }, | ||
| 135 | +} | ||
| 136 | +</script> | ||
| 137 | +``` | ||
| 138 | + | ||
| 139 | +也可以将Schema配置项单独保存在js文件中,需要的时候引入使用即可: | ||
| 140 | + | ||
| 141 | +```js | ||
| 142 | +// xxx.schema.js | ||
| 143 | +export default { | ||
| 144 | + props: { size: 'small', 'label-width': '100px', span: 12 }, | ||
| 145 | + items: [ | ||
| 146 | + { prop: 'dispatchLine.lineName', label: '线路名称' }, | ||
| 147 | + { prop: 'dispatchLine.startStation.name', label: '出发站点' }, | ||
| 148 | + { prop: 'dispatchLine.endStation.name', label: '目的站点' }, | ||
| 149 | + { prop: 'customer.name', label: '客户名称' }, | ||
| 150 | + { is: 'dictionary-select-name', props: { 'option-name': 'LINE_TYPE' }, prop: 'customer.name', label: '线路类型' }, | ||
| 151 | + { prop: 'dispatchLine.startStation.city.name', label: '出发城市' }, | ||
| 152 | + { prop: 'dispatchLine.stopOverCityName', label: '经停城市' }, | ||
| 153 | + { prop: 'dispatchLine.endStation.city.name', label: '经停城市' }, | ||
| 154 | + ] | ||
| 155 | +} | ||
| 156 | +``` | ||
| 157 | + | ||
| 158 | +这样可以将业务逻辑结构和视图抽离并可复用。 | ||
| 159 | + | ||
| 160 | +```html | ||
| 161 | +<template> | ||
| 162 | + <z-schema-form v-model="form" :schema="schema"></z-schema-form> | ||
| 163 | +</template> | ||
| 164 | + | ||
| 165 | +<script> | ||
| 166 | +import xxxSchema from '@/schema/xxx.schema.js'l | ||
| 167 | + | ||
| 168 | +export default { | ||
| 169 | + data() { | ||
| 170 | + return { | ||
| 171 | + schema: xxxSchema, | ||
| 172 | + form: { | ||
| 173 | + dispatchLine: { | ||
| 174 | + startStation: { | ||
| 175 | + city: {}, | ||
| 176 | + }, | ||
| 177 | + endStation: { | ||
| 178 | + city: {}, | ||
| 179 | + }, | ||
| 180 | + }, | ||
| 181 | + customer: {}, | ||
| 182 | + }, | ||
| 183 | + }; | ||
| 184 | + }, | ||
| 185 | +} | ||
| 186 | +</script> | ||
| 187 | +``` | ||
| 188 | + | ||
| 189 | +也可对原始配置项进行一个差异化的处理,比如配置项相同的情况下改变schema已配置的属性: | ||
| 190 | + | ||
| 191 | +```html | ||
| 192 | +<template> | ||
| 193 | + <z-schema-form v-model="form" :schema="schema"></z-schema-form> | ||
| 194 | +</template> | ||
| 195 | + | ||
| 196 | +<script> | ||
| 197 | +import xxxSchema from '@/schema/xxx.schema.js'l | ||
| 198 | + | ||
| 199 | +export default { | ||
| 200 | + data() { | ||
| 201 | + return { | ||
| 202 | + schema: { ...xxxSchema, props: { size: 'mini', 'label-width': '60px', span: 24 } }, | ||
| 203 | + form: { | ||
| 204 | + dispatchLine: { | ||
| 205 | + startStation: { | ||
| 206 | + city: {}, | ||
| 207 | + }, | ||
| 208 | + endStation: { | ||
| 209 | + city: {}, | ||
| 210 | + }, | ||
| 211 | + }, | ||
| 212 | + customer: {}, | ||
| 213 | + }, | ||
| 214 | + }; | ||
| 215 | + }, | ||
| 216 | +} | ||
| 217 | +</script> | ||
| 218 | +``` | ||
| 219 | + | ||
| 220 | +或者在配置项的基础上再追加几个配置项: | ||
| 221 | + | ||
| 222 | +```html | ||
| 223 | +<template> | ||
| 224 | + <z-schema-form v-model="form" :schema="schema"></z-schema-form> | ||
| 225 | +</template> | ||
| 226 | + | ||
| 227 | +<script> | ||
| 228 | +import xxxSchema from '@/schema/xxx.schema.js'l | ||
| 229 | + | ||
| 230 | +export default { | ||
| 231 | + data() { | ||
| 232 | + return { | ||
| 233 | + schema: { | ||
| 234 | + ...xxxSchema, | ||
| 235 | + items: [ | ||
| 236 | + ...xxxSchema.items, | ||
| 237 | + { prop: 'vehicleLicenseNum', label: '车牌号' }, | ||
| 238 | + { prop: 'remark', label: '备注', spam: 24 }, | ||
| 239 | + ] | ||
| 240 | + }, | ||
| 241 | + form: { | ||
| 242 | + dispatchLine: { | ||
| 243 | + startStation: { | ||
| 244 | + city: {}, | ||
| 245 | + }, | ||
| 246 | + endStation: { | ||
| 247 | + city: {}, | ||
| 248 | + }, | ||
| 249 | + }, | ||
| 250 | + customer: {}, | ||
| 251 | + }, | ||
| 252 | + }; | ||
| 253 | + }, | ||
| 254 | +} | ||
| 255 | +</script> | ||
| 256 | +``` | ||
| 257 | + | ||
| 258 | +## 更多 | ||
| 259 | + | ||
| 260 | +更多具体示例,请查看`schema-form`、`schema-table`、`schema-page`文档说明。 | ||
| 0 | \ No newline at end of file | 261 | \ No newline at end of file |
packages/schema-form/index.vue
| @@ -5,47 +5,33 @@ | @@ -5,47 +5,33 @@ | ||
| 5 | <z-form-item v-bind="keywordFilter(item)" :key="index"> | 5 | <z-form-item v-bind="keywordFilter(item)" :key="index"> |
| 6 | <slot | 6 | <slot |
| 7 | v-if="$scopedSlots[item.prop]" | 7 | v-if="$scopedSlots[item.prop]" |
| 8 | - :name="`${item.prop}`" | 8 | + :name="item.prop" |
| 9 | :value="get(model, item.prop)" | 9 | :value="get(model, item.prop)" |
| 10 | :onInput="value => onComponentInput({ value, item })" | 10 | :onInput="value => onComponentInput({ value, item })" |
| 11 | :props="{ value: get(model, item.prop) }" | 11 | :props="{ value: get(model, item.prop) }" |
| 12 | :listeners="{ input: value => onComponentInput({ value, item }) }" | 12 | :listeners="{ input: value => onComponentInput({ value, item }) }" |
| 13 | + v-bind="slotProps" | ||
| 13 | > | 14 | > |
| 14 | </slot> | 15 | </slot> |
| 15 | - <component v-else :is="item.is" :value="get(model, item.prop)" @input="value => onComponentInput({ value, item })" v-bind="item.props"> | ||
| 16 | - <template v-if="item.render"> | ||
| 17 | - <dynamic-render | ||
| 18 | - v-if="typeof item.render === 'function'" | ||
| 19 | - :render=" | ||
| 20 | - item.render($createElement, { | ||
| 21 | - model, | ||
| 22 | - value: get(model, item.prop), | ||
| 23 | - onInput: value => onComponentInput({ value, item }), | ||
| 24 | - }) | ||
| 25 | - " | ||
| 26 | - ></dynamic-render> | ||
| 27 | - <static-render v-else :render="item.render"></static-render> | ||
| 28 | - </template> | ||
| 29 | - <template v-if="item.children"> | ||
| 30 | - <childrens-render :value="item.children"></childrens-render> | ||
| 31 | - </template> | ||
| 32 | - </component> | ||
| 33 | - <slot :name="`label-${item.prop}`" slot="label"></slot> | ||
| 34 | - <slot :name="`error-${item.prop}`" slot="error"></slot> | 16 | + <item-render v-else :item="item" :value="get(model, item.prop)" :onInput="value => onComponentInput({ value, item })"></item-render> |
| 17 | + <slot :name="`label-${item.prop}`" slot="label" v-bind="slotProps"></slot> | ||
| 18 | + <slot :name="`error-${item.prop}`" slot="error" v-bind="slotProps"></slot> | ||
| 35 | </z-form-item> | 19 | </z-form-item> |
| 36 | </template> | 20 | </template> |
| 37 | <template v-else> | 21 | <template v-else> |
| 38 | - <z-form-item v-bind="keywordFilter(item)" :key="index"> | ||
| 39 | - <slot :name="item.prop"></slot> | 22 | + <z-form-item v-bind="keywordFilter(item)" :key="index" :value="get(model, item.prop)"> |
| 23 | + <slot | ||
| 24 | + :name="item.prop" | ||
| 25 | + :value="get(model, item.prop)" | ||
| 26 | + :onInput="value => onComponentInput({ value, item })" | ||
| 27 | + :props="{ value: get(model, item.prop) }" | ||
| 28 | + :listeners="{ input: value => onComponentInput({ value, item }) }" | ||
| 29 | + v-bind="slotProps" | ||
| 30 | + ></slot> | ||
| 40 | </z-form-item> | 31 | </z-form-item> |
| 41 | </template> | 32 | </template> |
| 42 | </template> | 33 | </template> |
| 43 | - <slot v-if="schema.footer !== false" name="footer"> | ||
| 44 | - <z-form-item> | ||
| 45 | - <el-button type="primary" @click="onSubmit">确定</el-button> | ||
| 46 | - <el-button plain @click="onCancel">取消</el-button> | ||
| 47 | - </z-form-item> | ||
| 48 | - </slot> | 34 | + <slot name="footer" v-bind="slotProps"></slot> |
| 49 | </z-form> | 35 | </z-form> |
| 50 | </template> | 36 | </template> |
| 51 | 37 | ||
| @@ -55,28 +41,33 @@ import { cloneDeep, get, set } from '../utils'; | @@ -55,28 +41,33 @@ import { cloneDeep, get, set } from '../utils'; | ||
| 55 | export default { | 41 | export default { |
| 56 | name: 'SchemaForm', | 42 | name: 'SchemaForm', |
| 57 | components: { | 43 | components: { |
| 58 | - DynamicRender: { | ||
| 59 | - functional: true, | ||
| 60 | - render(h, context) { | ||
| 61 | - return context.props.render; | ||
| 62 | - }, | ||
| 63 | - }, | ||
| 64 | - StaticRender: { | ||
| 65 | - functional: true, | ||
| 66 | - render(h, context) { | ||
| 67 | - return h('span', [context.props.render]); | ||
| 68 | - }, | ||
| 69 | - }, | ||
| 70 | - ChildrensRender: { | 44 | + ItemRender: { |
| 71 | functional: true, | 45 | functional: true, |
| 72 | render(h, context) { | 46 | render(h, context) { |
| 73 | - return context.props.value.map(item => { | ||
| 74 | - if (Array.isArray(item.children) && item.children.length > 0) { | ||
| 75 | - return h('childrens-render', { props: { value: item.children } }); | 47 | + const props = context.props; |
| 48 | + const item = props.item || {}; | ||
| 49 | + let content = []; | ||
| 50 | + if (item.render && typeof item.render === 'function') { | ||
| 51 | + content = context.props.render; | ||
| 52 | + } | ||
| 53 | + if (item.children) { | ||
| 54 | + if (Array.isArray(item.children)) { | ||
| 55 | + if (item.children.length > 0) { | ||
| 56 | + content = item.children.map(i => h('item-render', { props: { item: i } })); | ||
| 57 | + } | ||
| 76 | } else { | 58 | } else { |
| 77 | - return h(item.is, { ...item }); | 59 | + content = [item.children]; |
| 78 | } | 60 | } |
| 79 | - }); | 61 | + } |
| 62 | + let _props = item.props || {}; | ||
| 63 | + if (props.value) { | ||
| 64 | + _props = { ..._props, value: props.value }; | ||
| 65 | + } | ||
| 66 | + let _on = item.on || {}; | ||
| 67 | + if (props.onInput) { | ||
| 68 | + _on = { ..._on, input: props.onInput }; | ||
| 69 | + } | ||
| 70 | + return h(item.is, { attrs: item.attrs, props: _props, on: _on }, content); | ||
| 80 | }, | 71 | }, |
| 81 | }, | 72 | }, |
| 82 | }, | 73 | }, |
| @@ -102,6 +93,13 @@ export default { | @@ -102,6 +93,13 @@ export default { | ||
| 102 | schemaProps() { | 93 | schemaProps() { |
| 103 | return this.schema.props; | 94 | return this.schema.props; |
| 104 | }, | 95 | }, |
| 96 | + slotProps() { | ||
| 97 | + return { | ||
| 98 | + submit: this.onSubmit, | ||
| 99 | + cancel: this.onCancel, | ||
| 100 | + reset: this.onReset, | ||
| 101 | + }; | ||
| 102 | + }, | ||
| 105 | }, | 103 | }, |
| 106 | created() { | 104 | created() { |
| 107 | const { originData, ...other } = this._data; | 105 | const { originData, ...other } = this._data; |
| @@ -120,6 +118,18 @@ export default { | @@ -120,6 +118,18 @@ export default { | ||
| 120 | }, | 118 | }, |
| 121 | methods: { | 119 | methods: { |
| 122 | get, | 120 | get, |
| 121 | + validate(callback) { | ||
| 122 | + return this.$refs.form.validate(callback); | ||
| 123 | + }, | ||
| 124 | + validateField(props, callback) { | ||
| 125 | + return this.$refs.form.validateField(props, callback); | ||
| 126 | + }, | ||
| 127 | + resetFields() { | ||
| 128 | + this.$refs.form.resetFields(); | ||
| 129 | + }, | ||
| 130 | + clearValidate(props) { | ||
| 131 | + return this.$refs.form.clearValidate(props); | ||
| 132 | + }, | ||
| 123 | keywordFilter(val = {}) { | 133 | keywordFilter(val = {}) { |
| 124 | const { children, is, ...other } = val; | 134 | const { children, is, ...other } = val; |
| 125 | return other; | 135 | return other; |
| @@ -135,9 +145,13 @@ export default { | @@ -135,9 +145,13 @@ export default { | ||
| 135 | }); | 145 | }); |
| 136 | }, | 146 | }, |
| 137 | onCancel() { | 147 | onCancel() { |
| 138 | - this.model = cloneDeep(this.originData).model; | ||
| 139 | this.$emit('cancel'); | 148 | this.$emit('cancel'); |
| 140 | }, | 149 | }, |
| 150 | + onReset() { | ||
| 151 | + this.model = cloneDeep(this.originData).model; | ||
| 152 | + this.$refs.form.resetFields(); | ||
| 153 | + this.$emit('reset'); | ||
| 154 | + }, | ||
| 141 | }, | 155 | }, |
| 142 | }; | 156 | }; |
| 143 | </script> | 157 | </script> |