Commit bc4f9b65ceae6598167a6fa859f58e71cff973a9

Authored by 刘汉宸
1 parent ef2583ca

feat: 修改SchemaFilter组件

examples/router/routes.js
... ... @@ -136,7 +136,7 @@ const _develops = [
136 136 {
137 137 path: 'introduce',
138 138 name: 'developSchemaIntroduce',
139   - meta: { title: '介绍' },
  139 + meta: { title: 'Schema 介绍' },
140 140 component: () => import('@/views/docs/develop/schema/introduce.md'),
141 141 },
142 142 {
... ...
examples/views/docs/component/schema-filter.md
1 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 11 ```html
12 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 14 </template>
18 15  
19 16 <script>
20 17 export default {
21 18 data() {
22 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 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 43 onReset() {
39 44 console.log('reset');
40 45 }
41 46 }
42   -}
  47 +};
43 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 55 ```html
55 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 58 </template>
70 59  
71 60 <script>
72 61 export default {
73 62 data() {
74 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 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 92 ```html
99 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 95 </template>
108 96  
109 97 <script>
110 98 export default {
111 99 data() {
112 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 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 146 ```html
137 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 158 </template>
140 159  
141 160 <script>
142 161 export default {
143 162 data() {
144 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 197 </script>
157 198 ```
158 199  
... ... @@ -165,20 +206,18 @@ export default {
165 206 参数|说明|类型|可选值|默认值
166 207 -|-|-|-|-
167 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 217 ## Events 事件
179 218  
180 219 事件名称|说明|回调参数
181 220 -|-|-
182 221 input | 表单值变化 | model
183   -search | 查询 | model
184   -reset | 重置表单 | -
185 222 \ No newline at end of file
  223 +search | 搜索 | model
  224 +reset | 重置 | -
186 225 \ No newline at end of file
... ...
examples/views/docs/component/schema-form.md
... ... @@ -590,4 +590,19 @@ export default {
590 590 </script>
591 591 ```
592 592  
593   -:::
594 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 610 \ No newline at end of file
... ...
packages/form/index.vue
... ... @@ -8,8 +8,11 @@
8 8 </template>
9 9  
10 10 <script>
  11 +import MIX_FORM from '../mixins/form';
  12 +
11 13 export default {
12 14 name: 'Form',
  15 + mixins: [MIX_FORM],
13 16 props: {
14 17 value: Object,
15 18 model: Object,
... ... @@ -27,19 +30,5 @@ export default {
27 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 34 </script>
... ...
packages/mixins/form.js 0 → 100644
... ... @@ -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 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 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 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 11 </z-schema-form>
29 12 </template>
30 13  
31 14 <script>
  15 +import MIX_FORM from '../mixins/form';
  16 +import { cloneDeep } from '../utils';
  17 +
32 18 export default {
33 19 name: 'SchemaFilter',
  20 + mixins: [MIX_FORM],
34 21 props: {
35 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 32 type: Number,
47   - default: 6,
  33 + default: 3,
48 34 },
49   - collapsedSpan: {
  35 + span: {
50 36 type: Number,
51 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 42 data() {
69 43 return {
70   - collapsed: true,
71   - model: this.value || {},
  44 + collapsed: false,
  45 + model: this.value,
  46 + originData: {},
72 47 };
73 48 },
74 49 watch: {
... ... @@ -80,50 +55,93 @@ export default {
80 55 },
81 56 },
82 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 105 showCollapsed() {
87   - const { list, visibleNum } = this;
88   - return list.length > visibleNum;
  106 + return this.formattedItems.length > this.display;
89 107 },
90 108 slotKeys() {
91 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 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 139 this.$emit('reset');
111 140 },
112 141 // 折叠
113   - handleCollapse() {
  142 + onCollapse() {
114 143 this.collapsed = !this.collapsed;
115 144 },
116 145 },
117 146 };
118 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   -<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 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 3 <template v-for="(item, index) in schema.items">
4 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 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 7 <item-render v-else :item="item" :value="get(model, item.prop)" :onInput="value => onComponentInput({ value, item })"></item-render>
8 8 <slot :name="`label-${item.prop}`" slot="label" v-bind="slotProps"></slot>
... ... @@ -10,7 +10,7 @@
10 10 </z-form-item>
11 11 </template>
12 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 14 <slot :name="item.prop" :value="get(model, item.prop)" :onInput="value => onComponentInput({ value, item })" v-bind="slotProps"></slot>
15 15 </z-form-item>
16 16 </template>
... ... @@ -20,10 +20,12 @@
20 20 </template>
21 21  
22 22 <script>
  23 +import MIX_FORM from '../mixins/form';
23 24 import { cloneDeep, get, set } from '../utils';
24 25  
25 26 export default {
26 27 name: 'SchemaForm',
  28 + mixins: [MIX_FORM],
27 29 components: {
28 30 ItemRender: {
29 31 functional: true,
... ... @@ -102,11 +104,16 @@ export default {
102 104 },
103 105 methods: {
104 106 get,
105   - bindParam(item, key, def) {
  107 + bindParam(item, key) {
106 108 if (typeof item[key] === 'function') {
107 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 118 bindItemProps(item) {
112 119 const { children, is, props, on, ...other } = item || {};
... ... @@ -115,18 +122,6 @@ export default {
115 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 125 onComponentInput({ value, item }) {
131 126 set(this.model, item.prop, value);
132 127 },
... ...
packages/schema-form/schema-form-render.vue
... ... @@ -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>