editor.vue
8.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
<script>
import { get, set, cloneDeep } from '../utils';
import { renderContext, setDefaultContextClass } from '../utils/vnode';
// 配置是否必填
function isItemRequired(context, config) {
// 渲染函数配置
const contentProps = context.props || {};
// 编辑器统一配置
const editorProps = contentProps.editor || {};
const editorRules = editorProps.rules || {};
if (editorProps.validate !== true) {
return false;
}
// 判断校验规则
let rules = editorRules[config.prop] || [];
const match = editorProps.items.find(i => i.prop === config.prop);
if (match && match.rules) {
let matchRules = match.rules;
if (typeof match.rules === 'function') {
matchRules = match.rules({});
}
rules = matchRules;
}
if (rules.length === 0) {
return false;
}
let result = false;
for (let rule of rules) {
if (rule.required) {
result = true;
break;
}
}
return result;
}
// 标题渲染
function headerRender(h, context, item) {
// 表头插槽
const headerSlot = context.scopedSlots[`header-${item.prop}`];
const required = isItemRequired(context, item);
return function(scope) {
scope.required = required;
// 自定义具名插槽
if (headerSlot) {
return headerSlot(scope);
}
// 自定义渲染函数
if (item.header) {
return item.header(item, h, scope);
}
// 如果是必填项
if (required) {
return h('span', { class: 'required' }, item.label);
}
// 默认取值
return item.label;
};
}
// 单元格渲染
function cellRender(h, context, item) {
const cellSlot = context.scopedSlots[`cell-${item.prop}`];
return function(scope) {
const value = get(scope.row, item.prop);
// 自定义具名插槽
if (cellSlot) {
return cellSlot({ item, value, index: scope.$index, ...scope });
}
// 自定义渲染函数
if (item.render) {
return item.render(value, scope.row, h, scope.$index);
}
// 默认取值
return get(scope.row, item.prop);
};
}
// 表单项渲染
function formItemRender(h, context, item, value, scope, children) {
// 渲染函数配置
const contentProps = context.props || {};
// 编辑器统一配置
const editorProps = get(contentProps, 'editor') || {};
const editorRules = editorProps.rules || {};
let formItemProp = [editorProps.path].filter(i => i);
formItemProp.push(scope.$index);
formItemProp.push(item.prop);
formItemProp = formItemProp.join('.');
// 处理校验规则
let itemRules = item.rules;
if (typeof item.rules === 'function') {
itemRules = item.rules({ item, value, index: scope.$index, ...scope });
}
return h(
editorProps.formItem || 'el-form-item',
{
props: { prop: formItemProp, rules: itemRules || editorRules[item.prop], inlineMessage: true },
},
children,
);
}
// 编辑器渲染
function editorRender(h, context, item) {
const editorSlot = context.scopedSlots[`editor-${item.prop}`];
const contentProps = context.props || {};
return function(scope) {
const value = get(scope.row, item.prop);
if (item.if && typeof item.if === 'function') {
const showEditor = item.if({ item, value, index: scope.$index, ...scope });
if (!showEditor) {
return cellRender(h, context, item)(scope);
}
}
let vnode = {};
// 默认
const setValue = val => {
if (get(contentProps, 'editor.force') === true) {
vnode.componentInstance.$set(scope.row, item.prop, val);
} else {
scope.row[item.prop] = val;
}
};
const inputEvent = val => {
if (get(contentProps, 'editor.deep') === true) {
if (item.prop.indexOf('.') > -1 || item.prop.indexOf('[') > -1) {
let separator = '';
if (item.prop.indexOf('.') > -1) {
separator = '.';
} else if (item.prop.indexOf('[') > -1) {
separator = '[';
}
const path = item.prop.split(separator);
const bindProp = path[0];
const propValue = cloneDeep(scope.row);
set(propValue, item.prop, val);
vnode.componentInstance.$set(scope.row, bindProp, propValue[bindProp]);
} else {
setValue(val);
}
} else {
setValue(val);
}
};
// 向外提供的值
const editorScope = { item, value, index: scope.$index, ...scope, onInput: inputEvent };
// 编辑表单项配置
let itemProps = item.props || {};
if (typeof item.props === 'function') {
itemProps = item.props(editorScope);
}
let itemAttrs = item.attrs || {};
if (typeof item.attrs === 'function') {
itemAttrs = item.attrs(editorScope);
}
let itemOn = item.on || {};
if (typeof item.on === 'function') {
itemOn = item.on(editorScope);
}
if (itemOn.input) {
const itemOnInput = itemOn.input;
itemOn.input = function(e) {
inputEvent(e);
itemOnInput(e);
};
} else {
itemOn.input = inputEvent;
}
// 编辑器统一配置
const editorProps = get(contentProps, 'editor') || {};
// 生成虚拟节点
vnode = h(item.is, {
attrs: itemAttrs,
props: { ...editorProps, ...itemProps, value },
on: itemOn,
});
// 自定义具名插槽
if (editorSlot) {
return editorSlot(editorScope);
}
// 需要校验时外层嵌套校验组件
if (editorProps.validate) {
return formItemRender(h, context, item, value, scope, [vnode]);
}
return vnode;
};
}
// 跟进columns生成列
function createElTableColumns(h, context, columns) {
const props = context.props || {};
const editorConfig = props.editor || {};
return columns.map((item, index) => {
const { attrs, on, ...props } = item;
const items = editorConfig.items || [];
// 当前列编辑器配置
let editorItem = items.find(i => i.prop === item.prop);
// 当前列有编辑器配置或编辑器插槽的情况
const isEditor = editorItem || context.scopedSlots[`editor-${item.prop}`];
if (context.scopedSlots[`editor-${item.prop}`] && !editorItem) {
editorItem = item;
}
if (isEditor) {
if (props.className) {
props.className = [props.className, 'column-editor'].filter(i => i).join(' ');
} else {
props.className = 'column-editor';
}
}
// 处理插槽
const scopedSlots = {
header: headerRender(h, context, item),
default: isEditor ? editorRender(h, context, editorItem) : cellRender(h, context, item),
};
return h('el-table-column', { key: index, attrs, props, on, scopedSlots });
});
}
export default {
name: 'TableEditor',
functional: true,
render(h, context) {
const props = context.props || {};
// 设置默认class名称,用来追加一些默认的样式
let scopedSlots = context.scopedSlots || {};
setDefaultContextClass(context, 'z-table-editor');
// 如有默认插槽则相当于直接写el-table
if (scopedSlots.default) {
return h('el-table', context);
}
const columns = props.columns || [];
// 通过columns快速生成el-table-column
const elTableColumns = createElTableColumns(h, context, columns);
// 前置插槽
const prependSlot = scopedSlots.prepend ? scopedSlots.prepend() : '';
// 左侧插槽
const leftSlot = scopedSlots.left ? scopedSlots.left() : '';
// 右侧插槽
const rightSlot = scopedSlots.right ? scopedSlots.right() : '';
// 后置插槽
const appendSlot = scopedSlots.append ? scopedSlots.append() : '';
// 渲染组件时移除当前组件特有的props,避免透传不必要的参数
delete context.columns;
// 兼容旧版value属性
if (context.props.value && !context.props.data) {
context.props.data = context.props.value;
delete context.props.value;
}
return h('el-table', renderContext(context), [prependSlot, leftSlot, ...elTableColumns, rightSlot, appendSlot]);
},
};
</script>
<style lang="scss">
.z-table-editor {
.column-editor {
.cell {
padding: 2px;
.el-form-item {
margin-bottom: 0;
&.is-error {
::placeholder {
color: red;
}
}
}
.required::before {
content: '*';
color: red;
margin-right: 4px;
}
}
}
}
</style>