Commit bc4f9b65ceae6598167a6fa859f58e71cff973a9
1 parent
ef2583ca
Exists in
master
and in
2 other branches
feat: 修改SchemaFilter组件
Showing
9 changed files
with
290 additions
and
669 deletions
Show diff stats
examples/router/routes.js
| @@ -136,7 +136,7 @@ const _develops = [ | @@ -136,7 +136,7 @@ const _develops = [ | ||
| 136 | { | 136 | { |
| 137 | path: 'introduce', | 137 | path: 'introduce', |
| 138 | name: 'developSchemaIntroduce', | 138 | name: 'developSchemaIntroduce', |
| 139 | - meta: { title: '介绍' }, | 139 | + meta: { title: 'Schema 介绍' }, |
| 140 | component: () => import('@/views/docs/develop/schema/introduce.md'), | 140 | component: () => import('@/views/docs/develop/schema/introduce.md'), |
| 141 | }, | 141 | }, |
| 142 | { | 142 | { |
examples/views/docs/component/schema-filter.md
| 1 | # Schema Filter 筛选 | 1 | # Schema Filter 筛选 |
| 2 | 2 | ||
| 3 | -根据JSON Schema配置自动生成一个筛选条件表单 | 3 | +通过配置JSON Schema的方式快速生成一个筛选表单 |
| 4 | 4 | ||
| 5 | ## 基础用法 | 5 | ## 基础用法 |
| 6 | 6 | ||
| 7 | -配置`list`属性设置JSON Schema配置列表 | 7 | +`schema`设置配置项,基本参数与`z-schema-form`相同;不同的是,由于本组件包含了展开状态,配置项的显示隐藏需要统一控制,因此本组件中不包含配置项的显隐逻辑。 |
| 8 | 8 | ||
| 9 | -::: snippet 通过`list`配置项目 | 9 | +::: snippet `schema`设置配置项,`loading`设置加载状态 |
| 10 | 10 | ||
| 11 | ```html | 11 | ```html |
| 12 | <template> | 12 | <template> |
| 13 | - <div> | ||
| 14 | - <pre class="demo-model">{{ model }}</pre> | ||
| 15 | - <z-schema-filter v-model="model" :list="list" @search="onSearch" @reset="onReset"></z-schema-filter> | ||
| 16 | - </div> | 13 | + <z-schema-filter v-model="form" :schema="schema" :loading="loading" @search="onSearch" @reset="onReset"></z-schema-filter> |
| 17 | </template> | 14 | </template> |
| 18 | 15 | ||
| 19 | <script> | 16 | <script> |
| 20 | export default { | 17 | export default { |
| 21 | data() { | 18 | data() { |
| 22 | return { | 19 | return { |
| 23 | - model: {}, | ||
| 24 | - list: [ | ||
| 25 | - { type: 'el-input', label: '姓名', key: 'name' }, | ||
| 26 | - { type: 'el-input', label: '年龄', key: 'age' }, | ||
| 27 | - { type: 'el-input', label: '省', key: 'province' }, | ||
| 28 | - { type: 'el-input', label: '市', key: 'city' }, | ||
| 29 | - { type: 'el-input', label: '区', key: 'area' }, | ||
| 30 | - { type: 'el-input', label: '地址', key: 'address' }, | ||
| 31 | - ] | ||
| 32 | - } | 20 | + form: { |
| 21 | + name: '', | ||
| 22 | + age: '', | ||
| 23 | + gender: '', | ||
| 24 | + }, | ||
| 25 | + schema: { | ||
| 26 | + items: [ | ||
| 27 | + { is: 'el-input', prop: 'name', label: '姓名' }, | ||
| 28 | + { is: 'el-input', prop: 'age', label: '年龄' }, | ||
| 29 | + { is: 'el-input', prop: 'gender', label: '性别' }, | ||
| 30 | + ] | ||
| 31 | + }, | ||
| 32 | + loading: false | ||
| 33 | + }; | ||
| 33 | }, | 34 | }, |
| 34 | methods: { | 35 | methods: { |
| 35 | - onSearch(params) { | ||
| 36 | - console.log(JSON.parse(JSON.stringify(params))); | 36 | + onSearch(model) { |
| 37 | + this.loading = true; | ||
| 38 | + console.log(model); | ||
| 39 | + setTimeout(() => { | ||
| 40 | + this.loading = false; | ||
| 41 | + }, 1500); | ||
| 37 | }, | 42 | }, |
| 38 | onReset() { | 43 | onReset() { |
| 39 | console.log('reset'); | 44 | console.log('reset'); |
| 40 | } | 45 | } |
| 41 | } | 46 | } |
| 42 | -} | 47 | +}; |
| 43 | </script> | 48 | </script> |
| 44 | ``` | 49 | ``` |
| 45 | 50 | ||
| 46 | ::: | 51 | ::: |
| 47 | 52 | ||
| 48 | -## 自定义按钮 | ||
| 49 | - | ||
| 50 | -支持自定义按钮占位及按钮组件 | ||
| 51 | - | ||
| 52 | -::: snippet 配置`uncollapsedSpan`改变展开时操作区占位,默认值`24`;插槽`operation`自定义按钮内容; | 53 | +::: snippet 默认显示数量为`3`,超过则会显示展开按钮 |
| 53 | 54 | ||
| 54 | ```html | 55 | ```html |
| 55 | <template> | 56 | <template> |
| 56 | - <z-schema-filter v-model="model" :list="list" :uncollapsedSpan="12"> | ||
| 57 | - <template #operation="{ size, handleSearch, handleReset, handleCollapse, showCollapsed, collapsed, loading }"> | ||
| 58 | - <div style="text-align: right;"> | ||
| 59 | - <el-button-group :size="size"> | ||
| 60 | - <el-button type="primary" round @click="handleSearch" :loading="loading" icon="el-icon-search"><span>查询</span></el-button> | ||
| 61 | - <el-button @click="handleReset"><span>重置</span></el-button> | ||
| 62 | - <el-button round v-if="showCollapsed" @click="handleCollapse"> | ||
| 63 | - <span>{{ collapsed ? '展开' : '收起' }}</span> | ||
| 64 | - </el-button> | ||
| 65 | - </el-button-group> | ||
| 66 | - </div> | ||
| 67 | - </template> | ||
| 68 | - </z-schema-filter> | 57 | + <z-schema-filter v-model="form" :schema="schema"></z-schema-filter> |
| 69 | </template> | 58 | </template> |
| 70 | 59 | ||
| 71 | <script> | 60 | <script> |
| 72 | export default { | 61 | export default { |
| 73 | data() { | 62 | data() { |
| 74 | return { | 63 | return { |
| 75 | - model: {}, | ||
| 76 | - list: [ | ||
| 77 | - { type: 'el-input', label: '姓名', key: 'name' }, | ||
| 78 | - { type: 'el-input', label: '年龄', key: 'age' }, | ||
| 79 | - { type: 'el-input', label: '省', key: 'province' }, | ||
| 80 | - { type: 'el-input', label: '市', key: 'city' }, | ||
| 81 | - { type: 'el-input', label: '区', key: 'area' }, | ||
| 82 | - { type: 'el-input', label: '地址', key: 'address' }, | ||
| 83 | - ] | ||
| 84 | - } | 64 | + form: { |
| 65 | + name: '', | ||
| 66 | + age: '', | ||
| 67 | + gender: '', | ||
| 68 | + }, | ||
| 69 | + schema: { | ||
| 70 | + items: [ | ||
| 71 | + { is: 'el-input', prop: 'name', label: '姓名' }, | ||
| 72 | + { is: 'el-input', prop: 'age', label: '年龄' }, | ||
| 73 | + { is: 'el-input', prop: 'gender', label: '性别' }, | ||
| 74 | + { is: 'el-input', prop: 'address', label: '地址' }, | ||
| 75 | + { is: 'el-input', prop: 'package', label: '包裹' }, | ||
| 76 | + ] | ||
| 77 | + }, | ||
| 78 | + }; | ||
| 85 | }, | 79 | }, |
| 86 | -} | 80 | +}; |
| 87 | </script> | 81 | </script> |
| 88 | ``` | 82 | ``` |
| 89 | 83 | ||
| 90 | ::: | 84 | ::: |
| 91 | 85 | ||
| 92 | -## 自定义数据项 | 86 | +## 默认展示数量 |
| 93 | 87 | ||
| 94 | -支持自定义数据项,一般用于通过JSON Schema较难配置出的,较复杂的局部组件 | 88 | +常见的业务场景下,经常会出现多个筛选条件的情况,可以默认展示多行的搜索项。 |
| 95 | 89 | ||
| 96 | -::: snippet 插槽名与数据项`key`相同 | 90 | +::: snippet `display`设置默认展示的搜索项数量,控制按钮栏占位会根据每行剩余搜索项数量自动计算。 |
| 97 | 91 | ||
| 98 | ```html | 92 | ```html |
| 99 | <template> | 93 | <template> |
| 100 | - <z-schema-filter v-model="model" :list="list"> | ||
| 101 | - <template #name> | ||
| 102 | - <el-input placeholder="请输入内容" v-model="model.name"> | ||
| 103 | - <el-button slot="append" icon="el-icon-search"></el-button> | ||
| 104 | - </el-input> | ||
| 105 | - </template> | ||
| 106 | - </z-schema-filter> | 94 | + <z-schema-filter v-model="form" :schema="schema" :display="7"></z-schema-filter> |
| 107 | </template> | 95 | </template> |
| 108 | 96 | ||
| 109 | <script> | 97 | <script> |
| 110 | export default { | 98 | export default { |
| 111 | data() { | 99 | data() { |
| 112 | return { | 100 | return { |
| 113 | - model: {}, | ||
| 114 | - list: [ | ||
| 115 | - { type: 'el-input', label: '姓名', key: 'name' }, | ||
| 116 | - { type: 'el-input', label: '年龄', key: 'age' }, | ||
| 117 | - { type: 'el-input', label: '省', key: 'province' }, | ||
| 118 | - { type: 'el-input', label: '市', key: 'city' }, | ||
| 119 | - { type: 'el-input', label: '区', key: 'area' }, | ||
| 120 | - { type: 'el-input', label: '地址', key: 'address' }, | ||
| 121 | - ] | ||
| 122 | - } | 101 | + form: { |
| 102 | + name: '', | ||
| 103 | + age: '', | ||
| 104 | + gender: '', | ||
| 105 | + }, | ||
| 106 | + schema: { | ||
| 107 | + items: [ | ||
| 108 | + { is: 'el-input', prop: 'name', label: '姓名' }, | ||
| 109 | + { is: 'el-input', prop: 'age', label: '年龄' }, | ||
| 110 | + { is: 'el-input', prop: 'gender', label: '性别' }, | ||
| 111 | + { is: 'el-input', prop: 'item1', label: '搜索项1' }, | ||
| 112 | + { is: 'el-input', prop: 'item2', label: '搜索项2' }, | ||
| 113 | + { is: 'el-input', prop: 'item3', label: '搜索项3' }, | ||
| 114 | + { is: 'el-input', prop: 'item4', label: '搜索项4' }, | ||
| 115 | + { is: 'el-input', prop: 'item5', label: '搜索项5' }, | ||
| 116 | + { is: 'el-input', prop: 'item6', label: '搜索项6' }, | ||
| 117 | + ] | ||
| 118 | + }, | ||
| 119 | + loading: false | ||
| 120 | + }; | ||
| 123 | }, | 121 | }, |
| 124 | -} | 122 | + methods: { |
| 123 | + onSearch(model) { | ||
| 124 | + this.loading = true; | ||
| 125 | + console.log(model); | ||
| 126 | + setTimeout(() => { | ||
| 127 | + this.loading = false; | ||
| 128 | + }, 1500); | ||
| 129 | + }, | ||
| 130 | + onReset() { | ||
| 131 | + console.log('reset'); | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | +}; | ||
| 125 | </script> | 135 | </script> |
| 126 | ``` | 136 | ``` |
| 127 | 137 | ||
| 128 | ::: | 138 | ::: |
| 129 | 139 | ||
| 130 | -## 展示数量 | 140 | +## 自定义操作栏 |
| 131 | 141 | ||
| 132 | -默认展示数量为`3`个,可以通过配置而改变 | 142 | +特殊业务场景下,可以自定义操作栏 |
| 133 | 143 | ||
| 134 | -::: snippet 设置`visibleNum`改变默认展示数量的大小,`span`改变每一项的占位数量 | 144 | +::: snippet `operation`插槽替换默认操作栏 |
| 135 | 145 | ||
| 136 | ```html | 146 | ```html |
| 137 | <template> | 147 | <template> |
| 138 | - <z-schema-filter :list="list" :visibleNum="2" :span="8" size="large"></z-schema-filter> | 148 | + <z-schema-filter v-model="form" :schema="schema" :display="2" :loading="loading" @search="onSearch" @reset="onReset"> |
| 149 | + <template #operation="{ search, reset, collapse, collapsed, loading }"> | ||
| 150 | + <el-button-group round style="display: flex; justify-content: flex-end"> | ||
| 151 | + <el-button round type="primary" @click="search" :loading="loading" icon="el-icon-search">查询</el-button> | ||
| 152 | + <el-button round @click="reset">重置</el-button> | ||
| 153 | + <el-button round @click="onClick">自定义按钮</el-button> | ||
| 154 | + <el-button round @click="collapse">{{ collapsed ? '收起' : '展开' }}</el-button> | ||
| 155 | + </el-button-group> | ||
| 156 | + </template> | ||
| 157 | + </z-schema-filter> | ||
| 139 | </template> | 158 | </template> |
| 140 | 159 | ||
| 141 | <script> | 160 | <script> |
| 142 | export default { | 161 | export default { |
| 143 | data() { | 162 | data() { |
| 144 | return { | 163 | return { |
| 145 | - list: [ | ||
| 146 | - { type: 'el-input', label: '姓名', key: 'name' }, | ||
| 147 | - { type: 'el-input-number', label: '年龄', key: 'age', style: { width: '100%' }, props: { min: 0 } }, | ||
| 148 | - { type: 'el-input', label: '省', key: 'province' }, | ||
| 149 | - { type: 'el-input', label: '市', key: 'city' }, | ||
| 150 | - { type: 'el-input', label: '区', key: 'area' }, | ||
| 151 | - { type: 'el-input', label: '地址', key: 'address' }, | ||
| 152 | - ] | ||
| 153 | - } | 164 | + form: { |
| 165 | + name: '', | ||
| 166 | + age: '', | ||
| 167 | + gender: '', | ||
| 168 | + }, | ||
| 169 | + schema: { | ||
| 170 | + items: [ | ||
| 171 | + { is: 'el-input', prop: 'name', label: '姓名' }, | ||
| 172 | + { is: 'el-input', prop: 'age', label: '年龄' }, | ||
| 173 | + { is: 'el-input', prop: 'gender', label: '性别' }, | ||
| 174 | + { is: 'el-input', prop: 'address', label: '地址' }, | ||
| 175 | + { is: 'el-input', prop: 'address', label: '地址' }, | ||
| 176 | + ] | ||
| 177 | + }, | ||
| 178 | + loading: false, | ||
| 179 | + }; | ||
| 154 | }, | 180 | }, |
| 155 | -} | 181 | + methods: { |
| 182 | + onSearch(model) { | ||
| 183 | + this.loading = true; | ||
| 184 | + console.log(model); | ||
| 185 | + setTimeout(() => { | ||
| 186 | + this.loading = false; | ||
| 187 | + }, 1500); | ||
| 188 | + }, | ||
| 189 | + onReset() { | ||
| 190 | + console.log('reset'); | ||
| 191 | + }, | ||
| 192 | + onClick() { | ||
| 193 | + console.log('click'); | ||
| 194 | + } | ||
| 195 | + } | ||
| 196 | +}; | ||
| 156 | </script> | 197 | </script> |
| 157 | ``` | 198 | ``` |
| 158 | 199 | ||
| @@ -165,20 +206,18 @@ export default { | @@ -165,20 +206,18 @@ export default { | ||
| 165 | 参数|说明|类型|可选值|默认值 | 206 | 参数|说明|类型|可选值|默认值 |
| 166 | -|-|-|-|- | 207 | -|-|-|-|- |
| 167 | value | 值 | Object | - | {} | 208 | value | 值 | Object | - | {} |
| 168 | -list | JSON Schema配置项列表 | Array | - | [] | ||
| 169 | -labelWidth | 默认Label宽度 | String | 110px | ||
| 170 | -size | 大小 | String | - | small | ||
| 171 | -span | 占位 | Number | - | 24 | ||
| 172 | -uncollapsedSpan | 展开时操作区占位 | Number | - | 24 | ||
| 173 | -visibleNum | 折叠时可视项数量 | Number | - | 3 | ||
| 174 | -loading | 加载状态 | Boolean | - | - | ||
| 175 | -formProps | 默认el-form配置参数 | Object | - | {} | ||
| 176 | -params | 自定义附加参数 | Object | - | - | 209 | +schema | JSON Schema配置项列表 | Object | - | {} |
| 210 | +size | 大小 | String | - | - | ||
| 211 | +loading | 加载状态 | Boolean | - | false | ||
| 212 | +display | 默认收起时显示数量 | Number | - | 3 | ||
| 213 | +span | 子项占位 | Number | - | 6 | ||
| 214 | +collapsedSpan | 展开时操作栏占位,默认自动计算,特殊情况下可手动设置 | Boolean | - | - | ||
| 215 | +uncollapsedSpan | 收起时操作栏占位,默认自动计算,特殊情况下可手动设置 | Boolean | - | - | ||
| 177 | 216 | ||
| 178 | ## Events 事件 | 217 | ## Events 事件 |
| 179 | 218 | ||
| 180 | 事件名称|说明|回调参数 | 219 | 事件名称|说明|回调参数 |
| 181 | -|-|- | 220 | -|-|- |
| 182 | input | 表单值变化 | model | 221 | input | 表单值变化 | model |
| 183 | -search | 查询 | model | ||
| 184 | -reset | 重置表单 | - | ||
| 185 | \ No newline at end of file | 222 | \ No newline at end of file |
| 223 | +search | 搜索 | model | ||
| 224 | +reset | 重置 | - | ||
| 186 | \ No newline at end of file | 225 | \ No newline at end of file |
examples/views/docs/component/schema-form.md
| @@ -590,4 +590,19 @@ export default { | @@ -590,4 +590,19 @@ export default { | ||
| 590 | </script> | 590 | </script> |
| 591 | ``` | 591 | ``` |
| 592 | 592 | ||
| 593 | -::: | ||
| 594 | \ No newline at end of file | 593 | \ No newline at end of file |
| 594 | +::: | ||
| 595 | + | ||
| 596 | +## API | ||
| 597 | + | ||
| 598 | +## Attribute 属性 | ||
| 599 | + | ||
| 600 | +参数|说明|类型|可选值|默认值 | ||
| 601 | +-|-|-|-|- | ||
| 602 | +value | 值 | Object | - | {} | ||
| 603 | +schema | JSON Schema配置项列表 | Object | - | {} | ||
| 604 | + | ||
| 605 | +## Events 事件 | ||
| 606 | + | ||
| 607 | +事件名称|说明|回调参数 | ||
| 608 | +-|-|- | ||
| 609 | +input | 表单值变化 | model | ||
| 595 | \ No newline at end of file | 610 | \ No newline at end of file |
packages/form/index.vue
| @@ -8,8 +8,11 @@ | @@ -8,8 +8,11 @@ | ||
| 8 | </template> | 8 | </template> |
| 9 | 9 | ||
| 10 | <script> | 10 | <script> |
| 11 | +import MIX_FORM from '../mixins/form'; | ||
| 12 | + | ||
| 11 | export default { | 13 | export default { |
| 12 | name: 'Form', | 14 | name: 'Form', |
| 15 | + mixins: [MIX_FORM], | ||
| 13 | props: { | 16 | props: { |
| 14 | value: Object, | 17 | value: Object, |
| 15 | model: Object, | 18 | model: Object, |
| @@ -27,19 +30,5 @@ export default { | @@ -27,19 +30,5 @@ export default { | ||
| 27 | zForm: this, | 30 | zForm: this, |
| 28 | }; | 31 | }; |
| 29 | }, | 32 | }, |
| 30 | - methods: { | ||
| 31 | - validate(callback) { | ||
| 32 | - return this.$refs.form.validate(callback); | ||
| 33 | - }, | ||
| 34 | - validateField(props, callback) { | ||
| 35 | - return this.$refs.form.validateField(props, callback); | ||
| 36 | - }, | ||
| 37 | - resetFields() { | ||
| 38 | - this.$refs.form.resetFields(); | ||
| 39 | - }, | ||
| 40 | - clearValidate(props) { | ||
| 41 | - return this.$refs.form.clearValidate(props); | ||
| 42 | - }, | ||
| 43 | - }, | ||
| 44 | }; | 33 | }; |
| 45 | </script> | 34 | </script> |
| @@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
| 1 | +export default { | ||
| 2 | + methods: { | ||
| 3 | + validate(callback) { | ||
| 4 | + if (this.$refs.form) { | ||
| 5 | + return this.$refs.form.validate(callback); | ||
| 6 | + } | ||
| 7 | + return false; | ||
| 8 | + }, | ||
| 9 | + validateField(props, callback) { | ||
| 10 | + if (this.$refs.form) { | ||
| 11 | + return this.$refs.form.validateField(props, callback); | ||
| 12 | + } | ||
| 13 | + return false; | ||
| 14 | + }, | ||
| 15 | + resetFields() { | ||
| 16 | + if (this.$refs.form) { | ||
| 17 | + this.$refs.form.resetFields(); | ||
| 18 | + } | ||
| 19 | + }, | ||
| 20 | + clearValidate(props) { | ||
| 21 | + if (this.$refs.form) { | ||
| 22 | + return this.$refs.form.clearValidate(props); | ||
| 23 | + } | ||
| 24 | + return false; | ||
| 25 | + }, | ||
| 26 | + }, | ||
| 27 | +}; |
packages/schema-filter/index.vue
| 1 | <template> | 1 | <template> |
| 2 | - <z-schema-form | ||
| 3 | - v-model="model" | ||
| 4 | - class="z-schema-filter" | ||
| 5 | - :list="formattedList" | ||
| 6 | - :span="span" | ||
| 7 | - :labelWidth="labelWidth" | ||
| 8 | - :colClass="colVisibleRender" | ||
| 9 | - :size="size" | ||
| 10 | - :formProps="formProps" | ||
| 11 | - :params="params" | ||
| 12 | - > | 2 | + <z-schema-form ref="form" class="z-schema-filter" v-model="model" :schema="formattedSchema"> |
| 13 | <template v-for="key in slotKeys"> | 3 | <template v-for="key in slotKeys"> |
| 14 | - <template v-if="key === 'operation'"> | ||
| 15 | - <slot :name="key" :slot="key" v-bind="{ size, handleSearch, handleReset, handleCollapse, showCollapsed, collapsed, loading }"></slot> | ||
| 16 | - </template> | ||
| 17 | - <slot v-else :name="key" :slot="key"></slot> | 4 | + <slot :name="key" :slot="key" v-bind="slotProps"></slot> |
| 18 | </template> | 5 | </template> |
| 19 | - <div v-if="!slotKeys.includes('operation')" slot="operation" class="z-schema-filter__button-group"> | ||
| 20 | - <el-button-group :size="size"> | ||
| 21 | - <el-button type="primary" @click="handleSearch" :loading="loading" icon="el-icon-search"><span>查询</span></el-button> | ||
| 22 | - <el-button @click="handleReset"><span>重置</span></el-button> | ||
| 23 | - <el-button v-if="showCollapsed" @click="handleCollapse"> | ||
| 24 | - <span>{{ collapsed ? '展开' : '收起' }}</span> | ||
| 25 | - </el-button> | ||
| 26 | - </el-button-group> | ||
| 27 | - </div> | 6 | + <el-button-group v-if="!slotKeys.includes('operation')" slot="operation" style="display: flex; justify-content: flex-end"> |
| 7 | + <el-button type="primary" @click="onSearch" :loading="loading" icon="el-icon-search">查询</el-button> | ||
| 8 | + <el-button @click="onReset">重置</el-button> | ||
| 9 | + <el-button v-if="showCollapsed" @click="onCollapse">{{ collapsed ? '收起' : '展开' }}</el-button> | ||
| 10 | + </el-button-group> | ||
| 28 | </z-schema-form> | 11 | </z-schema-form> |
| 29 | </template> | 12 | </template> |
| 30 | 13 | ||
| 31 | <script> | 14 | <script> |
| 15 | +import MIX_FORM from '../mixins/form'; | ||
| 16 | +import { cloneDeep } from '../utils'; | ||
| 17 | + | ||
| 32 | export default { | 18 | export default { |
| 33 | name: 'SchemaFilter', | 19 | name: 'SchemaFilter', |
| 20 | + mixins: [MIX_FORM], | ||
| 34 | props: { | 21 | props: { |
| 35 | value: Object, | 22 | value: Object, |
| 36 | - list: Array, | ||
| 37 | - labelWidth: { | ||
| 38 | - type: String, | ||
| 39 | - default: '110px', | ||
| 40 | - }, | ||
| 41 | - size: { | ||
| 42 | - type: String, | ||
| 43 | - default: 'small', | 23 | + schema: { |
| 24 | + type: Object, | ||
| 25 | + default() { | ||
| 26 | + return {}; | ||
| 27 | + }, | ||
| 44 | }, | 28 | }, |
| 45 | - span: { | 29 | + size: String, |
| 30 | + loading: Boolean, | ||
| 31 | + display: { | ||
| 46 | type: Number, | 32 | type: Number, |
| 47 | - default: 6, | 33 | + default: 3, |
| 48 | }, | 34 | }, |
| 49 | - collapsedSpan: { | 35 | + span: { |
| 50 | type: Number, | 36 | type: Number, |
| 51 | default: 6, | 37 | default: 6, |
| 52 | }, | 38 | }, |
| 53 | - uncollapsedSpan: { | ||
| 54 | - type: Number, | ||
| 55 | - default: 24, | ||
| 56 | - }, | ||
| 57 | - visibleNum: { | ||
| 58 | - type: Number, | ||
| 59 | - default: 3, | ||
| 60 | - }, | ||
| 61 | - loading: Boolean, | ||
| 62 | - formProps: { | ||
| 63 | - type: Object, | ||
| 64 | - default: () => ({}), | ||
| 65 | - }, | ||
| 66 | - params: Object, | 39 | + collapsedSpan: Number, |
| 40 | + uncollapsedSpan: Number, | ||
| 67 | }, | 41 | }, |
| 68 | data() { | 42 | data() { |
| 69 | return { | 43 | return { |
| 70 | - collapsed: true, | ||
| 71 | - model: this.value || {}, | 44 | + collapsed: false, |
| 45 | + model: this.value, | ||
| 46 | + originData: {}, | ||
| 72 | }; | 47 | }; |
| 73 | }, | 48 | }, |
| 74 | watch: { | 49 | watch: { |
| @@ -80,50 +55,93 @@ export default { | @@ -80,50 +55,93 @@ export default { | ||
| 80 | }, | 55 | }, |
| 81 | }, | 56 | }, |
| 82 | computed: { | 57 | computed: { |
| 83 | - formattedList() { | ||
| 84 | - return [...this.list, { key: 'operation', label: '', labelWidth: '0px', span: this.collapsed ? this.collapsedSpan : this.uncollapsedSpan }]; | 58 | + filterSize() { |
| 59 | + return this.size || (this.$ELEMENT || {}).size; | ||
| 60 | + }, | ||
| 61 | + formattedItems() { | ||
| 62 | + const items = this.schema.items || []; | ||
| 63 | + const result = []; | ||
| 64 | + const visibleNumber = this.display - 1; | ||
| 65 | + items.forEach((item, index) => { | ||
| 66 | + if (!this.collapsed && index > visibleNumber && index < items.length) { | ||
| 67 | + result.push({ ...item, if: true, show: false }); | ||
| 68 | + } else { | ||
| 69 | + result.push({ ...item, if: true, show: true }); | ||
| 70 | + } | ||
| 71 | + }); | ||
| 72 | + return result; | ||
| 73 | + }, | ||
| 74 | + formattedSchema() { | ||
| 75 | + return { | ||
| 76 | + props: { span: this.span, 'label-width': '75px', size: this.filterSize || 'small', ...(this.schema.props || {}) }, | ||
| 77 | + items: [...this.formattedItems, { prop: 'operation', label: '', labelWidth: '0px', span: this.operationSpan }], | ||
| 78 | + }; | ||
| 79 | + }, | ||
| 80 | + rowItemCount() { | ||
| 81 | + return parseInt(24 / this.span); | ||
| 82 | + }, | ||
| 83 | + rowItemRemain() { | ||
| 84 | + return this.formattedItems.length % this.rowItemCount; | ||
| 85 | + }, | ||
| 86 | + operationSpan() { | ||
| 87 | + if (this.collapsed) { | ||
| 88 | + if (this.collapsedSpan) { | ||
| 89 | + return this.collapsedSpan; | ||
| 90 | + } | ||
| 91 | + return (this.rowItemCount - this.rowItemRemain) * this.span; | ||
| 92 | + } else { | ||
| 93 | + if (this.uncollapsedSpan) { | ||
| 94 | + return this.uncollapsedSpan; | ||
| 95 | + } | ||
| 96 | + if (this.formattedItems.length < this.display) { | ||
| 97 | + return this.span * (this.rowItemCount - this.rowItemRemain); | ||
| 98 | + } | ||
| 99 | + if (this.display < this.rowItemCount - 1) { | ||
| 100 | + return this.span * this.display; | ||
| 101 | + } | ||
| 102 | + return this.span; | ||
| 103 | + } | ||
| 85 | }, | 104 | }, |
| 86 | showCollapsed() { | 105 | showCollapsed() { |
| 87 | - const { list, visibleNum } = this; | ||
| 88 | - return list.length > visibleNum; | 106 | + return this.formattedItems.length > this.display; |
| 89 | }, | 107 | }, |
| 90 | slotKeys() { | 108 | slotKeys() { |
| 91 | return Object.keys(this.$scopedSlots); | 109 | return Object.keys(this.$scopedSlots); |
| 92 | }, | 110 | }, |
| 111 | + slotProps() { | ||
| 112 | + return { | ||
| 113 | + size: this.size, | ||
| 114 | + search: this.onSearch, | ||
| 115 | + reset: this.onReset, | ||
| 116 | + collapse: this.onCollapse, | ||
| 117 | + collapsed: this.collapsed, | ||
| 118 | + loading: this.loading, | ||
| 119 | + }; | ||
| 120 | + }, | ||
| 121 | + }, | ||
| 122 | + created() { | ||
| 123 | + const { originData, ...other } = this._data; | ||
| 124 | + this.originData = cloneDeep(other); | ||
| 93 | }, | 125 | }, |
| 94 | methods: { | 126 | methods: { |
| 95 | - // 渲染列表项class | ||
| 96 | - colVisibleRender(item, index) { | ||
| 97 | - if (this.collapsed) { | ||
| 98 | - const visibleNumber = this.visibleNum ? this.visibleNum - 1 : 2; | ||
| 99 | - return index > visibleNumber && index < this.list.length ? 'z-schema-filter__item hidden' : 'z-schema-filter__item'; | ||
| 100 | - } | ||
| 101 | - return 'z-schema-filter__item'; | ||
| 102 | - }, | ||
| 103 | // 搜索 | 127 | // 搜索 |
| 104 | - handleSearch() { | ||
| 105 | - this.$emit('search', this.model); | 128 | + onSearch() { |
| 129 | + this.$refs.form.validate(valid => { | ||
| 130 | + if (valid) { | ||
| 131 | + this.$emit('search', this.model); | ||
| 132 | + } | ||
| 133 | + }); | ||
| 106 | }, | 134 | }, |
| 107 | // 重置 | 135 | // 重置 |
| 108 | - handleReset() { | ||
| 109 | - this.model = {}; | 136 | + onReset() { |
| 137 | + this.model = cloneDeep(this.originData).model; | ||
| 138 | + this.$refs.form.resetFields(); | ||
| 110 | this.$emit('reset'); | 139 | this.$emit('reset'); |
| 111 | }, | 140 | }, |
| 112 | // 折叠 | 141 | // 折叠 |
| 113 | - handleCollapse() { | 142 | + onCollapse() { |
| 114 | this.collapsed = !this.collapsed; | 143 | this.collapsed = !this.collapsed; |
| 115 | }, | 144 | }, |
| 116 | }, | 145 | }, |
| 117 | }; | 146 | }; |
| 118 | </script> | 147 | </script> |
| 119 | - | ||
| 120 | -<style> | ||
| 121 | -.z-schema-filter__item.hidden { | ||
| 122 | - display: none; | ||
| 123 | -} | ||
| 124 | -.z-schema-filter__button-group { | ||
| 125 | - width: 100%; | ||
| 126 | - box-sizing: border-box; | ||
| 127 | - text-align: right; | ||
| 128 | -} | ||
| 129 | -</style> |
packages/schema-form/index copy.vue
| @@ -1,216 +0,0 @@ | @@ -1,216 +0,0 @@ | ||
| 1 | -<style lang="scss"> | ||
| 2 | -.z-schema-form { | ||
| 3 | - &__flex-wrap { | ||
| 4 | - display: flex; | ||
| 5 | - flex-wrap: wrap; | ||
| 6 | - width: 100%; | ||
| 7 | - } | ||
| 8 | - &__group-title { | ||
| 9 | - font-weight: bold; | ||
| 10 | - padding: 15px 5px; | ||
| 11 | - border-bottom: 1px solid #d9d9d9; | ||
| 12 | - margin-bottom: 15px; | ||
| 13 | - } | ||
| 14 | - &__group-content { | ||
| 15 | - margin: 15px 0px; | ||
| 16 | - } | ||
| 17 | - &__footer { | ||
| 18 | - padding-top: 10px; | ||
| 19 | - display: flex; | ||
| 20 | - align-items: center; | ||
| 21 | - justify-content: center; | ||
| 22 | - } | ||
| 23 | -} | ||
| 24 | -</style> | ||
| 25 | - | ||
| 26 | -<template> | ||
| 27 | - <el-form | ||
| 28 | - ref="form" | ||
| 29 | - class="z-schema-form" | ||
| 30 | - :size="size" | ||
| 31 | - :class="formClass" | ||
| 32 | - :model="formModel" | ||
| 33 | - :label-width="labelWidth" | ||
| 34 | - :label-position="labelPosition || labelWidth ? 'right' : 'top'" | ||
| 35 | - v-bind="formProps" | ||
| 36 | - > | ||
| 37 | - <schema-form-render | ||
| 38 | - :title-class="titleClass" | ||
| 39 | - :content-class="contentClass" | ||
| 40 | - :item-class="itemClass" | ||
| 41 | - :col-class="colClass" | ||
| 42 | - :group-class="groupClass" | ||
| 43 | - :list="formList" | ||
| 44 | - :value="model" | ||
| 45 | - :model="model" | ||
| 46 | - :span="span" | ||
| 47 | - :type="type" | ||
| 48 | - :params="params" | ||
| 49 | - @item-change="onItemChange" | ||
| 50 | - @form-item-change="onFormItemChange" | ||
| 51 | - @item-update="onItemUpdate" | ||
| 52 | - > | ||
| 53 | - <slot v-for="key in slotKeys" :name="key" :slot="key"></slot> | ||
| 54 | - </schema-form-render> | ||
| 55 | - <div v-if="$scopedSlots.footer" class="z-schema-form__footer"> | ||
| 56 | - <slot name="footer" :size="size" :validate="validate" :reset="reset" :model="model"></slot> | ||
| 57 | - </div> | ||
| 58 | - </el-form> | ||
| 59 | -</template> | ||
| 60 | - | ||
| 61 | -<script> | ||
| 62 | -import SchemaFormRender from './schema-form-render'; | ||
| 63 | -import { cloneDeep, set } from '../utils'; | ||
| 64 | - | ||
| 65 | -export default { | ||
| 66 | - name: 'SchemaForm', | ||
| 67 | - components: { SchemaFormRender }, | ||
| 68 | - props: { | ||
| 69 | - value: Object, | ||
| 70 | - list: Array, | ||
| 71 | - formClass: String, | ||
| 72 | - titleClass: String, | ||
| 73 | - contentClass: String, | ||
| 74 | - itemClass: String, | ||
| 75 | - colClass: [String, Function], | ||
| 76 | - groupClass: String, | ||
| 77 | - labelWidth: String, | ||
| 78 | - labelPosition: String, | ||
| 79 | - type: String, | ||
| 80 | - size: { | ||
| 81 | - type: String, | ||
| 82 | - default: 'small', | ||
| 83 | - }, | ||
| 84 | - span: { | ||
| 85 | - type: Number, | ||
| 86 | - default: 24, | ||
| 87 | - }, | ||
| 88 | - formProps: { | ||
| 89 | - type: Object, | ||
| 90 | - default: () => ({}), | ||
| 91 | - }, | ||
| 92 | - params: Object, | ||
| 93 | - }, | ||
| 94 | - data() { | ||
| 95 | - return { | ||
| 96 | - model: {}, | ||
| 97 | - formModel: {}, | ||
| 98 | - formList: [], | ||
| 99 | - }; | ||
| 100 | - }, | ||
| 101 | - computed: { | ||
| 102 | - slotKeys() { | ||
| 103 | - return Object.keys(this.$scopedSlots); | ||
| 104 | - }, | ||
| 105 | - }, | ||
| 106 | - watch: { | ||
| 107 | - value: { | ||
| 108 | - handler(val = {}) { | ||
| 109 | - this.model = val; | ||
| 110 | - this.setFormModel(val); | ||
| 111 | - }, | ||
| 112 | - immediate: true, | ||
| 113 | - }, | ||
| 114 | - list: { | ||
| 115 | - handler(val) { | ||
| 116 | - // 深度克隆传入的列表,避免原始值被修改 | ||
| 117 | - const newList = cloneDeep(this.list); | ||
| 118 | - // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key | ||
| 119 | - const generateFullKey = (list, parentKey) => { | ||
| 120 | - list.forEach(item => { | ||
| 121 | - if (item.group && item.list) { | ||
| 122 | - if (item.group.key) { | ||
| 123 | - item.fullKey = `${parentKey ? `${parentKey}-${item.group.key}` : item.group.key}`; | ||
| 124 | - } else { | ||
| 125 | - item.fullKey = parentKey || item.key; | ||
| 126 | - } | ||
| 127 | - generateFullKey(item.list, item.fullKey); | ||
| 128 | - } else { | ||
| 129 | - item.fullKey = `${parentKey ? `${parentKey}-${item.key}` : item.key}`; | ||
| 130 | - } | ||
| 131 | - }); | ||
| 132 | - }; | ||
| 133 | - generateFullKey(newList); | ||
| 134 | - this.formList = newList; | ||
| 135 | - }, | ||
| 136 | - immediate: true, | ||
| 137 | - }, | ||
| 138 | - }, | ||
| 139 | - methods: { | ||
| 140 | - handleInput(value) { | ||
| 141 | - console.log(value); | ||
| 142 | - }, | ||
| 143 | - /** | ||
| 144 | - * @description 表单项值变化 | ||
| 145 | - * @param {Object} item 表单项值对象 | ||
| 146 | - */ | ||
| 147 | - onItemChange(item) { | ||
| 148 | - this.$emit('input', { ...this.model, ...item }); | ||
| 149 | - }, | ||
| 150 | - /** | ||
| 151 | - * @description 表单相校验值变化 | ||
| 152 | - * @param {Object} item 表单项校验值对象 | ||
| 153 | - */ | ||
| 154 | - onFormItemChange(item) { | ||
| 155 | - this.formModel = { ...this.formModel, ...item }; | ||
| 156 | - }, | ||
| 157 | - /** | ||
| 158 | - * @description 校验表单 | ||
| 159 | - */ | ||
| 160 | - validate(cb) { | ||
| 161 | - return new Promise(resolve => { | ||
| 162 | - this.$refs.form.validate(valid => { | ||
| 163 | - cb && cb(valid); | ||
| 164 | - this.$emit('validate', valid, this.model); | ||
| 165 | - return resolve(valid); | ||
| 166 | - }); | ||
| 167 | - }); | ||
| 168 | - }, | ||
| 169 | - /** | ||
| 170 | - * @description 重置表单 | ||
| 171 | - */ | ||
| 172 | - reset() { | ||
| 173 | - this.$refs.form.clearValidate(); | ||
| 174 | - this.$emit('reset'); | ||
| 175 | - }, | ||
| 176 | - /** | ||
| 177 | - * @description 根据表单值设置表单校验值 | ||
| 178 | - * @param {Object} value 表单值 | ||
| 179 | - */ | ||
| 180 | - setFormModel(value) { | ||
| 181 | - let formModel = {}; | ||
| 182 | - // 递归深度解析表单值,将表单值扁平化为一层的对象并设置为表单校验值对象 | ||
| 183 | - const setFormModelValue = (list, parentKey) => { | ||
| 184 | - list.forEach(item => { | ||
| 185 | - item.fullKey = `${parentKey ? `${parentKey}-${item.key}` : item.key}`; | ||
| 186 | - if (item.value instanceof Object) { | ||
| 187 | - setFormModelValue( | ||
| 188 | - Object.keys(item.value).map(key => ({ key, value: item.value[key] })), | ||
| 189 | - item.fullKey, | ||
| 190 | - ); | ||
| 191 | - } else { | ||
| 192 | - formModel[item.fullKey] = item.value; | ||
| 193 | - } | ||
| 194 | - }); | ||
| 195 | - }; | ||
| 196 | - setFormModelValue(Object.keys(value).map(key => ({ key, value: value[key] }))); | ||
| 197 | - this.formModel = formModel; | ||
| 198 | - }, | ||
| 199 | - /** | ||
| 200 | - * @description 手动更新某一表单项的值 | ||
| 201 | - * @param {Object} param 需要更新的参数对象或者对象数组 { name => 表单项key,可嵌套; value => 更新的值 } | ||
| 202 | - * @example { name: 'a.b.c', value: 123 } | ||
| 203 | - * @example { name: 'd.0.e', value: ['f'] } | ||
| 204 | - */ | ||
| 205 | - onItemUpdate(param) { | ||
| 206 | - this.$nextTick(() => { | ||
| 207 | - const newModel = cloneDeep(this.model); | ||
| 208 | - Object.entries(param).forEach(entry => { | ||
| 209 | - set(newModel, entry[0], entry[1]); | ||
| 210 | - }); | ||
| 211 | - this.$emit('input', newModel); | ||
| 212 | - }); | ||
| 213 | - }, | ||
| 214 | - }, | ||
| 215 | -}; | ||
| 216 | -</script> |
packages/schema-form/index.vue
| 1 | <template> | 1 | <template> |
| 2 | - <z-form ref="form" v-model="model" v-bind="schema.props" v-on="schema.props"> | 2 | + <z-form ref="form" class="z-schema-form" v-model="model" v-bind="schema.props" v-on="schema.on"> |
| 3 | <template v-for="(item, index) in schema.items"> | 3 | <template v-for="(item, index) in schema.items"> |
| 4 | <template v-if="item.is"> | 4 | <template v-if="item.is"> |
| 5 | - <z-form-item v-if="bindParam(item, 'if', true)" v-show="bindParam(item, 'show', true)" v-bind="bindItemProps(item)" :key="index"> | 5 | + <z-form-item v-if="bindParam(item, 'if')" v-show="bindParam(item, 'show')" v-bind="bindItemProps(item)" :key="index"> |
| 6 | <slot v-if="$scopedSlots[item.prop]" :name="item.prop" :value="get(model, item.prop)" :onInput="value => onComponentInput({ value, item })" v-bind="slotProps"> </slot> | 6 | <slot v-if="$scopedSlots[item.prop]" :name="item.prop" :value="get(model, item.prop)" :onInput="value => onComponentInput({ value, item })" v-bind="slotProps"> </slot> |
| 7 | <item-render v-else :item="item" :value="get(model, item.prop)" :onInput="value => onComponentInput({ value, item })"></item-render> | 7 | <item-render v-else :item="item" :value="get(model, item.prop)" :onInput="value => onComponentInput({ value, item })"></item-render> |
| 8 | <slot :name="`label-${item.prop}`" slot="label" v-bind="slotProps"></slot> | 8 | <slot :name="`label-${item.prop}`" slot="label" v-bind="slotProps"></slot> |
| @@ -10,7 +10,7 @@ | @@ -10,7 +10,7 @@ | ||
| 10 | </z-form-item> | 10 | </z-form-item> |
| 11 | </template> | 11 | </template> |
| 12 | <template v-else> | 12 | <template v-else> |
| 13 | - <z-form-item v-if="bindParam(item, 'if', true)" v-show="bindParam(item, 'show', true)" v-bind="bindItemProps(item)" :key="index" :value="get(model, item.prop)"> | 13 | + <z-form-item v-if="bindParam(item, 'if')" v-show="bindParam(item, 'show')" v-bind="bindItemProps(item)" :key="index" :value="get(model, item.prop)"> |
| 14 | <slot :name="item.prop" :value="get(model, item.prop)" :onInput="value => onComponentInput({ value, item })" v-bind="slotProps"></slot> | 14 | <slot :name="item.prop" :value="get(model, item.prop)" :onInput="value => onComponentInput({ value, item })" v-bind="slotProps"></slot> |
| 15 | </z-form-item> | 15 | </z-form-item> |
| 16 | </template> | 16 | </template> |
| @@ -20,10 +20,12 @@ | @@ -20,10 +20,12 @@ | ||
| 20 | </template> | 20 | </template> |
| 21 | 21 | ||
| 22 | <script> | 22 | <script> |
| 23 | +import MIX_FORM from '../mixins/form'; | ||
| 23 | import { cloneDeep, get, set } from '../utils'; | 24 | import { cloneDeep, get, set } from '../utils'; |
| 24 | 25 | ||
| 25 | export default { | 26 | export default { |
| 26 | name: 'SchemaForm', | 27 | name: 'SchemaForm', |
| 28 | + mixins: [MIX_FORM], | ||
| 27 | components: { | 29 | components: { |
| 28 | ItemRender: { | 30 | ItemRender: { |
| 29 | functional: true, | 31 | functional: true, |
| @@ -102,11 +104,16 @@ export default { | @@ -102,11 +104,16 @@ export default { | ||
| 102 | }, | 104 | }, |
| 103 | methods: { | 105 | methods: { |
| 104 | get, | 106 | get, |
| 105 | - bindParam(item, key, def) { | 107 | + bindParam(item, key) { |
| 106 | if (typeof item[key] === 'function') { | 108 | if (typeof item[key] === 'function') { |
| 107 | return item[key]({ model: this.model }); | 109 | return item[key]({ model: this.model }); |
| 108 | } | 110 | } |
| 109 | - return def || item[key]; | 111 | + if (['if', 'show'].includes(key)) { |
| 112 | + if (['', null, undefined].includes(item[key])) { | ||
| 113 | + return true; | ||
| 114 | + } | ||
| 115 | + } | ||
| 116 | + return item[key]; | ||
| 110 | }, | 117 | }, |
| 111 | bindItemProps(item) { | 118 | bindItemProps(item) { |
| 112 | const { children, is, props, on, ...other } = item || {}; | 119 | const { children, is, props, on, ...other } = item || {}; |
| @@ -115,18 +122,6 @@ export default { | @@ -115,18 +122,6 @@ export default { | ||
| 115 | return result; | 122 | return result; |
| 116 | }, {}); | 123 | }, {}); |
| 117 | }, | 124 | }, |
| 118 | - validate(callback) { | ||
| 119 | - return this.$refs.form.validate(callback); | ||
| 120 | - }, | ||
| 121 | - validateField(props, callback) { | ||
| 122 | - return this.$refs.form.validateField(props, callback); | ||
| 123 | - }, | ||
| 124 | - resetFields() { | ||
| 125 | - this.$refs.form.resetFields(); | ||
| 126 | - }, | ||
| 127 | - clearValidate(props) { | ||
| 128 | - return this.$refs.form.clearValidate(props); | ||
| 129 | - }, | ||
| 130 | onComponentInput({ value, item }) { | 125 | onComponentInput({ value, item }) { |
| 131 | set(this.model, item.prop, value); | 126 | set(this.model, item.prop, value); |
| 132 | }, | 127 | }, |
packages/schema-form/schema-form-render.vue
| @@ -1,246 +0,0 @@ | @@ -1,246 +0,0 @@ | ||
| 1 | -<template> | ||
| 2 | - <!-- 在row上使用flex,防止表单组件大小不一导致错位 --> | ||
| 3 | - <component :is="rowComponent" class="z-schema-form__flex-wrap"> | ||
| 4 | - <template v-for="(item, index) in list"> | ||
| 5 | - <!-- 表单项有设置分组时 --> | ||
| 6 | - <component | ||
| 7 | - :is="colComponent" | ||
| 8 | - v-if="item.group && item.list" | ||
| 9 | - :key="index" | ||
| 10 | - :span="type === 'div' ? undefined : item.group.span || 24" | ||
| 11 | - :style="{ width: type === 'div' ? '100%' : undefined }" | ||
| 12 | - :class="colClassRender(item, index, colClass)" | ||
| 13 | - > | ||
| 14 | - <component :is="rowComponent" class="z-schema-form__flex-wrap" :class="groupClass || 'z-schema-form__group'"> | ||
| 15 | - <!-- 表单分组标题 --> | ||
| 16 | - <component :is="rowComponent" :class="titleClass || 'z-schema-form__group-title'" v-if="item.group.title" style="width: 100%;"> | ||
| 17 | - {{ item.group.title || item.group }} | ||
| 18 | - </component> | ||
| 19 | - <!-- 递归本组件 --> | ||
| 20 | - <schema-form-render | ||
| 21 | - :title-class="titleClass" | ||
| 22 | - :item-class="itemClass" | ||
| 23 | - :content-class="contentClass" | ||
| 24 | - :group-class="groupClass" | ||
| 25 | - :class="contentClass || 'z-schema-form__group-content'" | ||
| 26 | - :list="item.list" | ||
| 27 | - :value="value" | ||
| 28 | - :model="itemKey ? model[itemKey] || {} : model" | ||
| 29 | - :itemKey="item.group.key" | ||
| 30 | - :type="type" | ||
| 31 | - @item-change="onItemChange" | ||
| 32 | - @form-item-change="onFormItemChange" | ||
| 33 | - @item-update="onItemUpdate" | ||
| 34 | - :span="type === 'div' ? undefined : span * (24 / (item.group.span || 24))" | ||
| 35 | - ></schema-form-render> | ||
| 36 | - </component> | ||
| 37 | - </component> | ||
| 38 | - <!-- 正常无分组表单项 --> | ||
| 39 | - <template v-else> | ||
| 40 | - <component | ||
| 41 | - :is="colComponent" | ||
| 42 | - v-if="bindItemVisible(item, 'visible')" | ||
| 43 | - v-show="bindItemVisible(item, 'show')" | ||
| 44 | - :span="type === 'div' ? undefined : item.span || span" | ||
| 45 | - :key="index" | ||
| 46 | - :style="{ width: type === 'div' && item.style && item.style.width.includes('%') ? item.style.width : undefined, paddingRight: '10px' }" | ||
| 47 | - :class="colClassRender(item, index, colClass)" | ||
| 48 | - > | ||
| 49 | - <el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.fullKey" :rules="item | bindItemRulesFilter(model)" :class="itemClass || 'z-schema-form__item'"> | ||
| 50 | - <slot v-if="$scopedSlots[item.fullKey]" :name="item.fullKey" :value="itemValue(item)" :model1="value"></slot> | ||
| 51 | - <template v-else> | ||
| 52 | - <!-- 自定义组件 --> | ||
| 53 | - <dynamic-render | ||
| 54 | - v-if="typeof item.type === 'function'" | ||
| 55 | - :render=" | ||
| 56 | - item.type($createElement, { | ||
| 57 | - model: value, | ||
| 58 | - config: { | ||
| 59 | - props: { ...propsFormatter(item.props), value: itemValue(item) }, | ||
| 60 | - style: item.style || { width: '100%' }, | ||
| 61 | - on: { | ||
| 62 | - ...bindItemEvent(item), | ||
| 63 | - input: v => onInput({ value: v, item }), | ||
| 64 | - }, | ||
| 65 | - }, | ||
| 66 | - }) | ||
| 67 | - " | ||
| 68 | - ></dynamic-render> | ||
| 69 | - <component | ||
| 70 | - v-else | ||
| 71 | - :is="item.type" | ||
| 72 | - :value="itemValue(item)" | ||
| 73 | - @input="v => onInput({ value: v, item })" | ||
| 74 | - v-on="bindItemEvent(item)" | ||
| 75 | - v-bind="propsFormatter(item.props)" | ||
| 76 | - :style="item.style || { width: '100%' }" | ||
| 77 | - ></component> | ||
| 78 | - </template> | ||
| 79 | - </el-form-item> | ||
| 80 | - </component> | ||
| 81 | - </template> | ||
| 82 | - </template> | ||
| 83 | - </component> | ||
| 84 | -</template> | ||
| 85 | - | ||
| 86 | -<script> | ||
| 87 | -export default { | ||
| 88 | - name: 'SchemaFormRender', | ||
| 89 | - components: { | ||
| 90 | - DynamicRender: { | ||
| 91 | - functional: true, | ||
| 92 | - render(h, context) { | ||
| 93 | - return context.props.render; | ||
| 94 | - }, | ||
| 95 | - }, | ||
| 96 | - }, | ||
| 97 | - props: { | ||
| 98 | - list: Array, | ||
| 99 | - model: Object, | ||
| 100 | - value: Object, | ||
| 101 | - itemKey: String, | ||
| 102 | - titleClass: String, | ||
| 103 | - contentClass: String, | ||
| 104 | - itemClass: String, | ||
| 105 | - colClass: [String, Function], | ||
| 106 | - groupClass: String, | ||
| 107 | - type: String, | ||
| 108 | - span: Number, | ||
| 109 | - params: Object, | ||
| 110 | - }, | ||
| 111 | - computed: { | ||
| 112 | - rowComponent() { | ||
| 113 | - return this.type === 'div' ? 'div' : 'el-row'; | ||
| 114 | - }, | ||
| 115 | - colComponent() { | ||
| 116 | - return this.type === 'div' ? 'div' : 'el-col'; | ||
| 117 | - }, | ||
| 118 | - }, | ||
| 119 | - filters: { | ||
| 120 | - /** | ||
| 121 | - * @description 绑定表单项规则 | ||
| 122 | - * @param {Object} item 表单项配置 | ||
| 123 | - * @returns {Function} 事件函数 | ||
| 124 | - */ | ||
| 125 | - bindItemRulesFilter(item = {}, model) { | ||
| 126 | - if (item.rules) { | ||
| 127 | - if (typeof item.rules === 'function') { | ||
| 128 | - return item.rules(model); | ||
| 129 | - } else { | ||
| 130 | - return item.rules; | ||
| 131 | - } | ||
| 132 | - } else { | ||
| 133 | - return undefined; | ||
| 134 | - } | ||
| 135 | - }, | ||
| 136 | - }, | ||
| 137 | - methods: { | ||
| 138 | - /** | ||
| 139 | - * @description 渲染col class | ||
| 140 | - * @param {Object} item 表单项配置 | ||
| 141 | - * @param {Object} index 表单项渲染下标 | ||
| 142 | - * @param {Object} colClass 表单项配置 | ||
| 143 | - * @return {String} col class | ||
| 144 | - */ | ||
| 145 | - colClassRender(item, index, colClass) { | ||
| 146 | - if (colClass instanceof Function) { | ||
| 147 | - return colClass(item, index); | ||
| 148 | - } else { | ||
| 149 | - return colClass; | ||
| 150 | - } | ||
| 151 | - }, | ||
| 152 | - /** | ||
| 153 | - * @description 根据表单项的key查询该值 | ||
| 154 | - * @param {Object} item 表单项配置 | ||
| 155 | - * @returns {Any} 返回值 | ||
| 156 | - */ | ||
| 157 | - itemValue(item) { | ||
| 158 | - if (this.itemKey) { | ||
| 159 | - // 如果存在itemKey,即当前项位于嵌套分组内,查询分组名下对应key的值 | ||
| 160 | - const groupItem = this.model[this.itemKey] || {}; | ||
| 161 | - return groupItem[item.key]; | ||
| 162 | - } else { | ||
| 163 | - // 否则即意味着不在分组内,直接查询model下对应key的值 | ||
| 164 | - return this.model[item.key]; | ||
| 165 | - } | ||
| 166 | - }, | ||
| 167 | - /** | ||
| 168 | - * @description 组件有值输入时的事件 | ||
| 169 | - * @param {Object} data { value => 组件值; item => 表单项配置 } | ||
| 170 | - */ | ||
| 171 | - onInput({ value, item }) { | ||
| 172 | - if (this.itemKey) { | ||
| 173 | - this.$emit('item-change', { [this.itemKey]: { ...this.model[this.itemKey], [item.key]: value } }); | ||
| 174 | - } else { | ||
| 175 | - this.$emit('item-change', { [item.key]: value }); | ||
| 176 | - } | ||
| 177 | - this.$emit('form-item-change', { [item.fullKey]: value }); | ||
| 178 | - }, | ||
| 179 | - /** | ||
| 180 | - * @description 当表单项有改动时的事件 | ||
| 181 | - * @param {Any} 表单项值 | ||
| 182 | - */ | ||
| 183 | - onItemChange(value) { | ||
| 184 | - if (this.itemKey) { | ||
| 185 | - this.$emit('item-change', { [this.itemKey]: { ...this.model[this.itemKey], ...value } }); | ||
| 186 | - } else { | ||
| 187 | - this.$emit('item-change', value); | ||
| 188 | - } | ||
| 189 | - }, | ||
| 190 | - /** | ||
| 191 | - * @description 当表单项校验值有改动时的事件 | ||
| 192 | - * @param {Any} 表单项校验值 | ||
| 193 | - */ | ||
| 194 | - onFormItemChange(value) { | ||
| 195 | - this.$emit('form-item-change', value); | ||
| 196 | - }, | ||
| 197 | - /** | ||
| 198 | - * @description 当表单项有手动更新时的事件 | ||
| 199 | - * @param {Any} 表单项值 | ||
| 200 | - */ | ||
| 201 | - onItemUpdate(value) { | ||
| 202 | - this.$emit('item-update', value); | ||
| 203 | - }, | ||
| 204 | - /** | ||
| 205 | - * @description 绑定表单项事件 | ||
| 206 | - * @param {Object} item 表单项配置 | ||
| 207 | - * @returns {Function} 事件函数 | ||
| 208 | - */ | ||
| 209 | - bindItemEvent(item) { | ||
| 210 | - if (item.on) { | ||
| 211 | - if (typeof item.on === 'function') { | ||
| 212 | - return item.on({ model: this.value, update: e => this.$emit('item-update', e) }); | ||
| 213 | - } else { | ||
| 214 | - return item.on; | ||
| 215 | - } | ||
| 216 | - } else { | ||
| 217 | - return undefined; | ||
| 218 | - } | ||
| 219 | - }, | ||
| 220 | - /** | ||
| 221 | - * @description 绑定表单项显示状态 | ||
| 222 | - * @param {Object} item 表单项配置 | ||
| 223 | - * @param {String} type Vue显示类型,可选值:visible、show | ||
| 224 | - * @returns {Boolean} 显示状态 | ||
| 225 | - */ | ||
| 226 | - bindItemVisible(item, type) { | ||
| 227 | - const visible = item[type]; | ||
| 228 | - if (typeof visible === 'function') { | ||
| 229 | - return visible(this.model, this.params || {}); | ||
| 230 | - } | ||
| 231 | - return item[type] !== false; | ||
| 232 | - }, | ||
| 233 | - /** | ||
| 234 | - * @description 格式化props属性 | ||
| 235 | - * @param {Object|Function} props 属性或属性对象 | ||
| 236 | - * @returns {Object} 格式化的属性 | ||
| 237 | - */ | ||
| 238 | - propsFormatter(props) { | ||
| 239 | - if (typeof props === 'function') { | ||
| 240 | - return props(this.model, this.params || {}); | ||
| 241 | - } | ||
| 242 | - return props || {}; | ||
| 243 | - }, | ||
| 244 | - }, | ||
| 245 | -}; | ||
| 246 | -</script> |